import java.applet.Applet;
import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Label;
import java.awt.Panel;
import java.awt.TextField;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
/**
* This class implements the Traveling Salesman problem as a Java applet.
*/
public class TravelingSalesman extends Applet implements Runnable {
private static final Random RANDOM = new Random();
/**
* true: view the algorithm run once. false: run many times and write data.
*/
private boolean gettingData = false;
private static final long serialVersionUID = 1731368983441078792L;
/**
* How many cities to use.
*/
protected int cityCount;
/**
* How many chromosomes to use.
*/
protected int populationSize;
/**
* The part of the population eligible for mating.
*/
protected int matingPopulationSize;
/**
* The part of the population selected for mating.
*/
protected int selectedParents;
/**
* The current generation
*/
protected int generation;
/**
* The background worker thread.
*/
protected Thread worker = null;
/**
* Is the thread started.
*/
protected boolean started = false;
/**
* The list of cities.
*/
protected City[] cities;
/**
* The list of chromosomes being displayed
*/
protected Chromosome[] chromosomes;
/** The number of populations the chromosomes are initially divided into */
private static final int SUB_POPULATIONS = 10;
/**
* The subpopulations. After 600 generations this reduces to a single
* population (an array containing one array only).
*/
private Chromosome[][] populations;
/**
* The Start button.
*/
private Button ctrlStart;
/**
* The TextField that holds the number of cities.
*/
private TextField ctrlCities;
/**
* The TextField for the population size.
*/
private TextField ctrlPopulationSize;
/**
* Holds the buttons and other controls, forms a strip across the bottom of
* the applet.
*/
private Panel ctrlButtons;
/**
* The current status, which is displayed just above the controls.
*/
private String status = "";
@Override
public void init() {
setLayout(new BorderLayout());
// setup the controls
ctrlButtons = new Panel();
ctrlStart = new Button("Start");
ctrlButtons.add(ctrlStart);
ctrlButtons.add(new Label("# Cities:"));
ctrlButtons.add(ctrlCities = new TextField(5));
ctrlButtons.add(new Label("Population Size:"));
ctrlButtons.add(ctrlPopulationSize = new TextField(5));
this.add(ctrlButtons, BorderLayout.SOUTH);
// set the default values
ctrlPopulationSize.setText("1000");
ctrlCities.setText("100");
// add an action listener for the button
ctrlStart.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
if (gettingData) {
getData();
} else {
startThread();
}
}
});
started = false;
update();
}
private void log(String s) {
if (printer != null) {
printer.print(s);
}
System.out.print(s);
}
/** Runs the algorithm multiple times and writes the results to a file */
private void getData() {
File file = new File("data_100.csv");
boolean overwrite = false;
if (file.exists() && !overwrite) {
throw new IllegalArgumentException(
"File already exists: change name to avoid data loss or set overwrite = true");
}
try {
printer = new PrintWriter(file);
int runs = 50;
double sum = 0;
double min = Double.POSITIVE_INFINITY;
double max = Double.NEGATIVE_INFINITY;
double costs[] = new double[runs];
for (int run = 0; run < runs; run++) {
log("Run, " + run + "\n");
startThread();
try {
// Wait for the current run to finish
worker.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
double cost = chromosomes[0].getCost();
sum += cost;
min = Math.min(min, cost);
max = Math.max(max, cost);
costs[run] = cost;
}
double mean = sum / runs;
double variance = 0;
for (double cost : costs) {
variance += (mean - cost) * (mean - cost);
}
variance /= runs;
log("Mean, " + mean + "\n");
log("Min, " + min + "\n");
log("Max, " + max + "\n");
log("Variance, " + variance + "\n");
log("Cities, " + cityCount + "\n");
// Notify completion
for (int x = 0; x < 10; x++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
Toolkit.getDefaultToolkit().beep();
}
printer.close();
} catch (FileNotFoundException e1) {
e1.printStackTrace();
}
}
/**
* Start the background thread.
*/
public void startThread() {
try {
cityCount = Integer.parseInt(ctrlCities.getText());
} catch (NumberFormatException e) {
cityCount = 200;
}
try {
populationSize = Integer.parseInt(ctrlPopulationSize.getText());
} catch (NumberFormatException e) {
populationSize = 1000;
}
FontMetrics fm = getGraphics().getFontMetrics();
int bottom = ctrlButtons.getBounds().y - fm.getHeight() - 2;
// Create a random list of cities
cities = new City[cityCount];
for (int i = 0; i < cityCount; i++) {
cities[i] = new City(
(int) (Math.random() * (getBounds().width - 10)),
(int) (Math.random() * (bottom - 10)));
}
// Create subpopulations
populations = new Chromosome[SUB_POPULATIONS][populationSize
/ SUB_POPULATIONS];
for (Chromosome[] population : populations) {
for (int i = 0; i < population.length; i++) {
population[i] = new Chromosome(cities);
}
}
// Display the first subpopulation in the GUI
chromosomes = populations[0];
// Start up the background thread
generation = 0;
worker = new Thread(this);
worker.setPriority(Thread.MIN_PRIORITY);
worker.start();
started = true;
}
/**
* Update the display
*/
public void update() {
Image img = createImage(getBounds().width, getBounds().height);
Graphics g = img.getGraphics();
FontMetrics fm = g.getFontMetrics();
int width = getBounds().width;
int bottom = ctrlButtons.getBounds().y - fm.getHeight() - 2;
g.setColor(Color.black);
g.fillRect(0, 0, width, bottom);
if (started && (cities != null)) {
g.setColor(Color.green);
for (int i = 0; i < cityCount; i++) {
int xpos = cities[i].getx();
int ypos = cities[i].gety();
g.fillOval(xpos - 5, ypos - 5, 10, 10);
}
g.setColor(Color.white);
for (int i = 0; i < cityCount; i++) {
int icity = chromosomes[0].getCity(i);
if (i != 0) {
int last = chromosomes[0].getCity(i - 1);
g.drawLine(cities[icity].getx(), cities[icity].gety(),
cities[last].getx(), cities[last].gety());
}
}
}
g.drawString(status, 0, bottom);
getGraphics().drawImage(img, 0, 0, this);
}
/**
* Update the status.
*
* @param status
* The status.
*/
public void setStatus(String status) {
this.status = status;
}
/**
* The main loop for the background thread.
*/
@Override
public void run() {
if (!gettingData) {
update();
}
// For printing. Note that every run will print a row for every
// subpopulation, showing the costs in parallel. The first row will
// contain the final results of the whole population at the end.
List<ArrayList<Integer>> costs = new ArrayList<>(SUB_POPULATIONS);
for (Chromosome[] population : populations) {
sortChromosomes(population);
costs.add(new ArrayList<Integer>());
}
while (generation < 1000) {
generation++;
if (generation == 600) {
Chromosome[][] populationsNew = new Chromosome[1][populations[0].length
* SUB_POPULATIONS];
int i = 0;
for (Chromosome[] population : populations) {
for (Chromosome chromosome : population) {
populationsNew[0][i++] = chromosome;
}
}
populations = populationsNew;
sortChromosomes(populations[0]);
chromosomes = populations[0];
}
for (Chromosome[] population : populations) {
tournament(population);
sortChromosomes(population);
}
for (int i = 0; i < populations.length; i++) {
costs.get(i).add((int) populations[i][0].getCost());
}
// Chromosome.sortChromosomes(chromosomes, matingPopulationSize);
if (!gettingData) {
setStatus("Generation " + generation + " Cost "
+ (int) chromosomes[0].getCost());
update();
}
}
setStatus("Solution found after " + generation + " generations.");
// Print out costs at each generation for all the subpopulations (see
// above)
for (ArrayList<Integer> costList : costs) {
log(costList.get(0) + "");
for (int i = 1; i < costList.size(); i++) {
log("," + costList.get(i));
}
log("\n");
}
}
/**
* Selects random pairs of chromosomes to recombine and compete for the
* chance to produce mutants
*/
private void tournament(Chromosome[] population) {
// Create a list of shuffled positions
List<Integer> positions = new ArrayList<>(population.length);
for (int i = 0; i < population.length; i++) {
positions.add(i);
}
Collections.shuffle(positions);
// Select pairs, produce a child, and mutate the winner
for (int i = 0; i < population.length; i += 2) {
Integer pos1 = positions.get(i);
Integer pos2 = positions.get(i + 1);
Chromosome c1 = population[pos1];
Chromosome c2 = population[pos2];
// Recombine
Chromosome child = new Chromosome(c1, c2);
child.calculateCost(cities);
// Find the best of the three
Chromosome winner = c1.getCost() < c2.getCost() ? c1 : c2;
winner = winner.getCost() < child.getCost() ? winner : child;
// Mutate the winner
mutateInto(winner, pos1, population);
mutateInto(winner, pos2, population);
}
}
/**
* Creates a child of the given chromosome and places it at position newpos.
* A mutant is created and if that mutant is better than the parent, it is
* the child. Otherwise the child is identical to the parent.
*/
private void mutateInto(Chromosome original, int newpos,
Chromosome[] population) {
Chromosome mutant;
Chromosome best = new Chromosome(original);
mutant = new Chromosome(original);
// Perform a random mutation
int mutation = RANDOM.nextInt(4);
switch (mutation) {
case 0:
mutant.invert();
break;
case 1:
mutant.translocate();
break;
case 2:
mutant.transpose();
break;
case 3:
mutant.shift();
break;
}
mutant.calculateCost(cities);
// Only keep beneficial mutations
if (mutant.getCost() < original.getCost()) {
best = mutant;
}
population[newpos] = best;
}
/** Orders chromosomes by cost */
private static final Comparator<Chromosome> COST_COMPARATOR = new Comparator<Chromosome>() {
@Override
public int compare(Chromosome c1, Chromosome c2) {
return (int) Math.signum(c1.getCost() - c2.getCost());
}
};
private PrintWriter printer;
/** Calculate costs, then sort the entire array */
private void sortChromosomes(Chromosome[] array) {
for (Chromosome c : array) {
c.calculateCost(cities);
}
Arrays.sort(array, COST_COMPARATOR);
}
@Override
public void paint(Graphics g) {
update();
}
}