package CVRP;
import CVRP.algorithms.SimpleGA;
public class CVRP {
public static void test(){// check
LimitedPriorityQueue lpq = new LimitedPriorityQueue(5, new Double(1));// capacity of this queue is 5,
// the second param provides they type of fitness for this lpq
lpq.printArray(0);
lpq.push(2, new Double(45));// <index, fitness>
lpq.push(4, new Double(50));// 'lpq' puts the one with smaller fitness in the front
lpq.push(5, new Double(34));
lpq.push(6, new Double(20));
lpq.push(7, new Double(59));
lpq.push(8, new Double(12));
lpq.push(9, new Double(30));
lpq.printArray(1);
}
public static void main(String[] args) {
test();// test the function of lpq
SimpleGA sga = new SimpleGA();
sga.randomise(); // get 2000 initial populations
sga.run(30000);
sga.printBestPath();
}
}
package CVRP;
import java.util.ArrayList;
public class LimitedPriorityQueue {
private ArrayList<WeightLookup> values;
private int size;// max #population this queue can store
public LimitedPriorityQueue(int size, Number sample){// 'sample' provides the type of fitness for this priorityQueue
values = new ArrayList<WeightLookup>(size);
this.size = size;
if(sample instanceof Integer)
for(int i = 0; i < size; i++)
values.add(i, new WeightLookup(0, (Number) new Integer(Integer.MAX_VALUE)));
else if (sample instanceof Double)
for(int i = 0; i < size; i++)
values.add(i, new WeightLookup(0, (Number) new Double(Integer.MAX_VALUE)));
else if (sample instanceof Float)
for(int i = 0; i < size; i++)
values.add(i, new WeightLookup(0, (Number) new Float(Integer.MAX_VALUE)));
}
public boolean push(int index, Number newVal){// sort those populations by 'fitness', the smaller one is in the front
// put this newVal on its proper position in 'values'
for(int i = 0; i < values.size(); i++){
if(lessThan(newVal,values.get(i).weight)){
for(int j = values.size() - 1; j > i; j--){ // move backward to make room for 'newVal'
values.set(j, values.get(j - 1));
}
values.set(i, new WeightLookup(index, newVal));
return true;
}
}
return false;
}
public void printArray(int iteration){
// at the 'iteration' iteration, print the indices and fitness of all the populations in the current LimitedPriorityQueue
String s = iteration + ": ";
for(int i = 0; i < values.size(); i++){
s = s.concat("{" + String.valueOf(values.get(i).index) + ", " + String.valueOf(values.get(i).weight + "}"));
if(i < values.size() - 1) s = s.concat(", ");
}
System.out.println(s);
}
public int[] getIndices(){// from weights sorted from small to large, get their indices
int[] indices = new int[size];
for(int i = 0; i < size; i++){
indices[i] = values.get(i).index;
}
return indices;
}
private boolean lessThan(Number a, Number b){
if(a instanceof Integer){
if((Integer)a < (Integer)b) return true;
} else if(a instanceof Double){
if((Double)a < (Double)b) return true;
} else if(a instanceof Float){
if((Float)a < (Float)b) return true;
}
return false;
}
private class WeightLookup{
public int index;
public Number weight;
public WeightLookup(int index, Number weight){
this.index = index;
this.weight = weight;
}
}
}
package CVRP;
public class CVRPData {
/** The capacity that all vehicles in fruitybun-data.vrp have. */
public static final int VEHICLE_CAPACITY = 220;
public static final int VEHICLE_AMOUNT = 20;
/** The number of nodes in the fruitybun CVRP i.e. the depot and the customers */
public static final int NUM_NODES = 76; // add the constraint about #vehicles available to use
/** Return the demand for a given node. */
public static int getDemand(int node) {
if (!nodeIsValid(node)) {
System.err.println("Error: demand for node " + node +
" was requested from getDemand() but only nodes 1.." + NUM_NODES + " exist");
System.exit(-1);
}
return demand[node];
}
/** Return the Euclidean distance between the two given nodes */
public static double getDistance(int node1, int node2) {
if (!nodeIsValid(node1)) {
System.err.println("Error: distance for node " + node1 +
" was requested from getDistance() but only nodes 1.." + NUM_NODES + " exist");
System.exit(-1);
}
if (!nodeIsValid(node2)) {
System.err.println("Error: distance for node " + node2 +
" was requested from getDistance() but only nodes 1.." + NUM_NODES + " exist");
System.exit(-1);
}
int x1 = coords[node1][X_COORDINATE];
int y1 = coords[node1][Y_COORDINATE];
int x2 = coords[node2][X_COORDINATE];
int y2 = coords[node2][Y_COORDINATE];
// compute Euclidean distance
return Math.sqrt(Math.pow((x1-x2),2) + Math.pow((y1-y2),2));
}
public static double getPathDistance(int[] path){
double dist = 0;
for (int i = 0; i < path.length - 1; i++){
dist += getDistance(path[i], path[i+1]);
}
return dist;
}
/** Return true if the given node is within the valid range (1..NUM_NODES), false otherwise */
private static boolean nodeIsValid(int node) {
if (node < 1 || node > NUM_NODES)
return false;
else
return true;
}
public static final int X_COORDINATE = 0; // x-axis coordinate is dimension 0 in coords[][]
public static final int Y_COORDINATE = 1; // y-axis coordinate is dimension 1 in coords[][]
// Return the coordinates for rendering
public static int[][] getCoords(){
return coords;
}
public static int getPathDemand(int[] path){
int totalDemand = 0;
for(int i = 1; i < path.length; i++){
totalDemand += demand[path[i]];
}
return totalDemand;
}
// Return whether the path is valid
public static boolean pathIsValid(int[] path){
return getPathDemand(path) <= VEHICLE_CAPACITY;
}
// Strip the depot from the input [][] and return a single []
public static int[] stripDepots(int[][] paths){ // flatten all the path in this population 'paths'
int[] chromosome = new int[NUM_NODES - 1];// !! +VEHICLE_AMOUNT-1
int k = 0;
for(int i = 0; i < paths.length; i++){
if(paths[i] == null) break;// recall that we create paths as new int[76][], but we may not use so much in practice
for(int j = 1; j < paths[i].length - 1; j++){
chromosome[k] = paths[i][j];
k++;
}
// // add depot
// chromosome[k] = 1;
}
return chromosome;
}
// 2-dimensional array with the coordinates of each node in fruitybun-data.vrp.
// coords[0] is a dummy entry which is not used. Node 1 is at coords[1], node 2 is at coords[2] and so on.
// A more Object-Oriented solution would have been to create coordinate objects but that may be overkill.
private static int [][] coords = new int[][]
{{-1, -1}, // dummy entry to make index of array match indexing of nodes in fruitybun-data.vrp
{40, 40}, // the coordinates of node 1 (the depot)
{22, 22}, // the coordinates of node 2 ...
{36, 26},
{21, 45},
{45, 35},
{55, 20},
{33, 34},
{50, 50},
{55, 45},
{26, 59}, // node 10
{40, 66},
{55, 65},
{35, 51},
{62, 35},
{62, 57},
{62, 24},
{21, 36},
{33, 44},
{9, 56},
{62, 48}, // node 20
{66, 14},
{44, 13},
{26, 13},
{11, 28},
{7, 43},
{17, 64},
{41, 46},
{55, 34},
{35, 16},
{52, 26}, // node 30
{43, 26},
{31, 76},
{22, 53},
{26, 29},
{50, 40},
{55, 50},
{54, 10},
{60, 15},
{47, 66},
{30, 60}, // node 40
{30, 50},
{12, 17},
{15, 14},
{16, 19},
{21, 48},
{50, 30},
{51, 42},
{50, 15},
{48, 21},
{12, 38}, // node 50
{15, 56},
{29, 39},
{54, 38},
{55, 57},
{67, 41},
{10, 70},
{6, 25},
{65, 27},
{40, 60},
{70, 64}, // node 60
{64, 4},
{36, 6},
{30, 20},
{20, 30},
{15, 5},
{50, 70},
{57, 72},
{45, 42},
{38, 33},
{50, 4}, // node 70
{66, 8},
{59, 5},
{35, 60},
{27, 24},
{40, 20},
{40, 37}};// node 76
private static int[] demand = new int[]
{9999999, // dummy entry to make index of array match indexing of nodes in fruitybun-data.vrp
0, // node 1
18, // node 2
26, // node 3
11, // node 4
30, // node 5
21, // node 6
19, // node 7
15, // node 8
16, // node 9
29, // node 10
26, // node 11
37, // node 12
16, // node 13
12, // node 14
31, // node 15
8, // node 16
19, // node 17
20, // node 18
13, // node 19
15, // node 20
22, // node 21
28, // node 22
12, // node 23
6, // node 24
27, // node 25
14, // node 26
18, // node 27
17, // node 28
29, // node 29
13, // node 30
22, // node 31
25, // node 32
28, // node 33
27, // node 34
19, // node 35
10, // node 36
12, // node 37
14, // node 38
24, // node 39
16, // node 40
33, // node 41
15, // node 42
11, // node 43
18, // node 44
17, // node 45
21, // node 46
27, // node 47
19, // node 48
20, // node 49
5, // node 50
22, // node 51
12, // node 52
19, // node 53
22, // node 54
16, // node 55
7, // node 56
26, // node 57
14, // node 58
21, // node 59
24, // node 60
13, // node 61
15, // node 62
18, // node 63
11, // node 64
28, // node 65
9, // node 66
37, // node 67
30, // node 68
10, // node 69
8, // node 70
11, // node 71
3, // node 72
1, // node 73
6, // node 74
10, // node 75
20};// node 76
}
package CVRP.algorithms;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import CVRP.CVRPData;
public abstract class Algorithm {
protected ArrayList<Double> weights = new ArrayList<Double>();
protected ArrayList<int[][]> chromosomes = new ArrayList<int[][]>();
protected ArrayList<Score> scores = new ArrayList<Score>();
// Private method for converting arraylist<Integer> to int[]
protected static int[] convertIntegers(List<Integer> integers) {
int[] ret = new int[integers.size()];
Iterator<Integer> iterator = integers.iterator();
for (int i = 0; i < ret.length; i++)
{
ret[i] = iterator.next().intValue();
}
return ret;
}
private static double[] convertDoubles(List<Double> doubles) {
double[] ret = new double[doubles.size()];
Iterator<Double> iterator = doubles.iterator();
for (int i = 0; i < ret.length; i++)
{
ret[i] = iterator.next().doubleValue();
}
return ret;
}
// Abstract method for path calculation
// Must be overriden by sub classes
// This method should store on each iteration the best weight path
// it has achieved in the weights arraylist
public abstract void run(int iterations);
// Abstract method for returning the name of the algorithm
public abstract String getName();
// Randomise trucks
public void randomise(){// randomly generate 2000 populations
for(int j = 0; j < 2000; j++){
int[][] paths = new int[76][];
ArrayList<Integer> nodes = new ArrayList<Integer>();
for(int i = 2; i < CVRPData.getCoords().length; i++){
nodes.add(new Integer(i));
}
ArrayList<Integer> path = new ArrayList<Integer>();// new path
path.add(new Integer(1)); // starts with Depot1
Random rand = new Random();
int pos = 0;
while(CVRPData.pathIsValid(convertIntegers(path)) && nodes.size() > 0){
// Choose a random number from the ArrayList
int getNodeVal = rand.nextInt(nodes.size());
//System.out.println(getNodeVal);
Integer i = nodes.get(getNodeVal);
path.add(i);
if(CVRPData.pathIsValid(convertIntegers(path))){
// The path is valid, so we need to remove the node from the set
nodes.remove(getNodeVal);//
} else {
path.remove(path.size() - 1); // Remove the last item
path.add(new Integer(1));
paths[pos++] = convertIntegers(path);
path = new ArrayList<Integer>();
path.add(new Integer(1));
}
}
// Store the last path as well
path.add(new Integer(1));
paths[pos++] = convertIntegers(path);
chromosomes.add(paths);
}
}
// Return the scores arraylist
public ArrayList<Score> getScores(){
return scores;
}
public void addScore(double best, double mean, double median){
scores.add(new Score(best, mean, median));
}
// Return the ArrayList of Chromosomes
public ArrayList<int[][]> getChromosomes(){
return chromosomes;
}
public class Score{
public double best;
public double mean;
public double median;
public Score(double best, double mean, double median){
this.best = best;
this.mean = mean;
this.median = median;
}
}
}
package CVRP.algorithms;
import java.util.ArrayList;
import java.util.Random;
import CVRP.CVRPData;
import CVRP.LimitedPriorityQueue;
public class SimpleGA extends Algorithm {
private final double probability = 0.002;
private final double zeroDeltaAddition = 0.00015;
private final int queueLimit = 222;
private int zeroDeltaIterations = 1;
private double prevWeight;
private int[][] bestPath = generateRandomPath(); // initial population
// Run the genetic algorithm to improve the results
@Override
public void run(int iterations) {// at most 'iteration' iterations
// Do iterations
for (int i = 0; i < iterations; i++) {
// Find the best 5 paths of the generated paths
LimitedPriorityQueue lpq = new LimitedPriorityQueue(queueLimit,// limit size of lpq, by default 222 // keep 222 best populations
new Double(1));
// shortestPathLength.
// in main func, we've already called randomise(), so getChromosomes will return 2000 feasible populations as initial ones
ArrayList<int[][]> chromosomes = getChromosomes();// list of populations, one population means s feasible solution
// compute their fitness and push them into lpq, and then lpq will sort out the best 222 populations among them
for (int j = 0; j < chromosomes.size(); j++) {
double length = fitness(chromosomes.get(j));
lpq.push(j, new Double(length));// path j , its fitness // the smaller fitness, the better
}
if (i % 1000 == 0) { // print the fitness of the current population every 1000 iterations
lpq.printArray(i);
System.out.println("Best path: " + fitness(bestPath));
}
int[] indices = lpq.getIndices();// indices of populations sorted by key 'fitness'
// Cross the values over to form new paths
pmx(indices);
if (fitness(chromosomes.get(indices[0])) < fitness(bestPath))
bestPath = chromosomes.get(indices[0]);// best population from LimitedPriorityQueue
// If iterations > 1000 then we keep the first 2 and scrap the rest.
if (zeroDeltaIterations == 5000) {
// If we've had 10,000 iterations with no change. Scrap all
// (keep the best?)
// We're probably stuck in a local minima
randomise();
zeroDeltaIterations = 0;
} else if (zeroDeltaIterations % 300 == 0 && zeroDeltaIterations >= 300) {
// We've probably hit a local minima, so generate a lot more
// random ones
int[][] pathA = chromosomes.get(indices[0]);// keep only the best one and generate new ones
chromosomes.clear();
chromosomes.add(pathA);
for (int k = 0; k < 200; k++) {
chromosomes.add(generateRandomPath());
}
}
double delta = fitness(chromosomes.get(indices[0])) - prevWeight;
if (Math.abs(delta) < 0.2) {
zeroDeltaIterations++;
} else {
zeroDeltaIterations = 0;
}
prevWeight = fitness(chromosomes.get(indices[0]));// record the best fitness at the current iteration
weights.add(new Double(fitness(bestPath)));
}
}
public int[][] getBestPath() {
return bestPath;
}
public void printBestPath(){// [{path1},{path2},...]
String s = "[";
int vehi=0;
for(int[] path : bestPath){
if(path == null){
s = s.substring(0, s.length() - 1);// drops the last ','
break;
}
s = s.concat("{");
for(int i = 0; i < path.length; i++){
s = s.concat(String.valueOf(path[i])); // append this path into s
if(i < path.length - 1) s = s.concat(",");
}
s = s.concat("},\n");
vehi++;
}
s = s.concat("]\t "+vehi);
System.out.println(s);
}
private int[][] generateRandomPath() { // generate feasiable paths
int[][] paths = new int[76][];// create 76 paths for 76 nodes, but we don't need so much in practice
ArrayList<Integer> nodes = new ArrayList<Integer>();
for (int i = 2; i < CVRPData.getCoords().length; i++) {
nodes.add(new Integer(i));
}
ArrayList<Integer> path = new ArrayList<Integer>();
path.add(new Integer(1)); // start with Depot 1
Random rand = new Random();// random generator
int pos = 0;
while (CVRPData.pathIsValid(convertIntegers(path)) && nodes.size() > 0) {
// Choose a random number from the ArrayList
int getNodeVal = rand.nextInt(nodes.size());
// System.out.println(getNodeVal);
Integer i = nodes.get(getNodeVal);
path.add(i);
if (CVRPData.pathIsValid(convertIntegers(path))) {
// The path is valid, so we need to remove the node from the set
nodes.remove(getNodeVal);// that is, nodes.pop
} else {
path.remove(path.size() - 1); // Remove the last item
path.add(new Integer(1));// end up with Depot 1
paths[pos++] = convertIntegers(path);// paths are 2D array
path = new ArrayList<Integer>();
path.add(new Integer(1));// start with Depot 1
}
}
// Store the last path as well
path.add(new Integer(1));
paths[pos++] = convertIntegers(path);
return paths;
}
private int[] swap(int a, int b, int[] chromosome) { // swap two values, a and b, on chromosome
// Find the position of a and b in each chromosome
int aPos = 0, bPos = 0;
for (int i = 0; i < chromosome.length; i++) {
if (chromosome[i] == a)
aPos = i;
if (chromosome[i] == b)
bPos = i;
}
// Swap the values
int tmp = chromosome[aPos];
chromosome[aPos] = chromosome[bPos];
chromosome[bPos] = tmp;
// Return the new chromosome
return chromosome;
}
private void pmx(int[] indices) {
// record the currently best 10 individuals and use them to generate new individuals
// and then return the new population made up from new individuals
// Create a new oldchromosomes object to store the current ones
ArrayList<int[][]> oldChromosomes = new ArrayList<int[][]>();
for (int j = 0; j < indices.length && j < 10; j++) { //at most the first 10 best populations remain
oldChromosomes.add(chromosomes.get(indices[j]));
}
// Reinitialise the chromosome list
// record newly generated individuals
chromosomes = new ArrayList<int[][]>();
ArrayList<int[]> tmpPaths = new ArrayList<int[]>();// temporary list of flatten populations
//put those old populations in oldChromosome in tmpPaths
for (int i = 0; i < oldChromosomes.size(); i++) {
int[] strippedPath = CVRPData.stripDepots(oldChromosomes.get(i));// flatten the ith population
tmpPaths.add(strippedPath);
}
// crossover each of the 5 with each other to give 25
Random rnd = new Random();
for (int i = 0; i < tmpPaths.size(); i++) { // Population i V.S Population j
for (int j = 0; j < tmpPaths.size(); j++) {
// Cross the 2 indices over
int start = rnd.nextInt() % (CVRPData.NUM_NODES - 2);
int end = rnd.nextInt() % (CVRPData.NUM_NODES - 2);
if (start < 0)
start = start * -1;
if (end < 0)
end = end * -1;
while (start == end)
end = rnd.nextInt() & CVRPData.NUM_NODES - 1;// a random int no more than CVRPData.NUM_NODES - 1
if (start > end) {
// We need to swap them
int tmp = start;
start = end;
end = tmp;
}
// now, we can be sure that start<end
int[] newPathA = tmpPaths.get(i);// Population i
int[] newPathB = tmpPaths.get(j); //Population j
// Between start and end we need to swap the values
for (int k = start; k < end; k++) {
int a = tmpPaths.get(i)[k];
int b = tmpPaths.get(j)[k];
newPathA = swap(a, b, newPathA);
newPathB = swap(a, b, newPathB);
}
// Randomly apply a mutation
newPathA = mutate(newPathA);
newPathB = mutate(newPathB);
chromosomes.add(makeValidPaths(newPathA));// from a flatten population, generate a series of paths
chromosomes.add(makeValidPaths(newPathB));
}
}
}
private int[] mutate(int[] path) {// operator
Random rnd = new Random();
for (int i = 0; i < path.length; i++) {
if (rnd.nextDouble() < (probability + (zeroDeltaIterations % 1000)
* zeroDeltaAddition)) {
// We are swapping two values, path[i] and path[swapWith]
int swapWith = rnd.nextInt() % (CVRPData.NUM_NODES - 2) + 1;
if (swapWith < 0)
swapWith = swapWith * -1;
path = swap(path[i], path[swapWith], path);
}
}
return path;
}
private int[][] makeValidPaths(int[] inpath) {
// from inpath, a flatten population, make valid paths and add them into paths, that is, the current population
//!! skip that depot
ArrayList<Integer> nodes = new ArrayList<Integer>();
for (int i = 0; i < inpath.length; i++) {
// if(inpath[i]==1){// ! newly add
// continue;
// }
nodes.add(new Integer(inpath[i]));
}
int[][] paths = new int[76][];// at most 76 vehicles are needed
ArrayList<Integer> path = new ArrayList<Integer>();
path.add(new Integer(1));
int j = 0;// ?? why add this variable
int pos = 0;
while (CVRPData.pathIsValid(convertIntegers(path)) && nodes.size() > 0) {
Integer i = nodes.get(0);
path.add(i);
if (CVRPData.pathIsValid(convertIntegers(path))) {
// The path is valid, so we need to remove the node from the set
nodes.remove(0);// we take this node away
j++;
} else {
path.remove(path.size() - 1); // Remove the last item, which is that newly added node
path.add(new Integer(1));// end this path
paths[pos++] = convertIntegers(path);// and put this path in `paths`
// prepare for the next iteration
path = new ArrayList<Integer>();// create a new path
path.add(new Integer(1));
}
}
// Store the last path as well
path.add(new Integer(1));// end this last path
paths[pos++] = convertIntegers(path);
return paths;
}
// Calculate the fitness of this current population, that is, a given paths_array
private double fitness(int[][] paths) {// input several paths
// We want to minimise the path weight
double weight = 0.0;
int vehi=0;
int clientNum=0;
for (int[] path : paths) {
if (path == null){
if(vehi<=CVRPData.VEHICLE_AMOUNT){
return weight; // we want the total distance smaller
}else{
return -clientNum;// we want the amount of clients served larger
}
// return weight;
}
weight += CVRPData.getPathDistance(path); // !! add max #clientsServed
vehi++;
clientNum+=path.length-2; // except two depots
}
if(vehi<=CVRPData.VEHICLE_AMOUNT){
return weight; // we want the total distance smaller
}else{
return -clientNum;// we want the amount of clients served larger
}
// return weight;
}
@Override
public String getName() {
return "SimpleGA-" + (int)fitness(bestPath);
}
}