Files
havox/tutorials/tsp/sketch.js
2026-03-16 18:03:17 +00:00

383 lines
8.4 KiB
JavaScript

/*var seedSlider = document.getElementById('sliderRange')
seedSlider.onChange = function(){
return this.value;
}
var citiesSlider = document.getElementById('nRange')
citiesSlider.onChange = function(){
return this.value;
}*/
var Seed = 21//seedChange(11);
var totalCities = 25//citiesChange(9);
// Sketch One
var randomSearch = function( p ) { // p could be any variable name
var cities = [];
var recordDistance;
var bestEver;
var localSeed = Seed;
var localCities = totalCities;
p.setup = function() {
p.createCanvas(400, 400);
p.setupPoints(Seed, totalCities);
}
p.setupPoints = function(rngSeed, numCities){
p.randomSeed(rngSeed);
for (var i = 0; i < numCities; i++) {
var v = p.createVector(p.random(p.width), p.random(p.height/2));
cities[i] = v;
}
var d = p.calcDistance(cities);
recordDistance = d;
bestEver = cities.slice();
}
p.draw = function() {
if(localSeed != Seed){
p.setupPoints(Seed, localCities);
localSeed = Seed;
}
if(localCities != totalCities){
setupPoints(localSeed, totalCities);
localCities = totalCities;
}
p.background(0);
p.fill(255);
p.stroke(255);
for (var i = 0; i < cities.length; i++) {
p.ellipse(cities[i].x, cities[i].y, 8, 8);
}
p.stroke(255, 0, 255);
p.strokeWeight(4);
p.noFill();
p.beginShape();
for (var i = 0; i < cities.length; i++) {
p.vertex(bestEver[i].x, bestEver[i].y);
}
p.endShape();
p.stroke(255);
p.translate(0 , p.height/2);
p.strokeWeight(1);
p.noFill();
p.beginShape();
for (var i = 0; i < cities.length; i++) {
p.vertex(cities[i].x, cities[i].y);
}
p.endShape();
var i = p.floor(p.random(cities.length));
var j = p.floor(p.random(cities.length));
p.swap(cities, i, j);
var d = p.calcDistance(cities);
if (d < recordDistance) {
recordDistance = d;
bestEver = cities.slice();
}
}
p.swap = function(a, i, j) {
var temp = a[i];
a[i] = a[j];
a[j] = temp;
}
p.calcDistance = function(points) {
var sum = 0;
for (var i = 0; i < points.length - 1; i++) {
var d = p.dist(points[i].x, points[i].y, points[i + 1].x, points[i + 1].y);
sum += d;
}
return sum;
}
};
var myp5 = new p5(randomSearch, 'c1');
// Sketch Two
var lexicographicOrder = function( q ) {
var cities = [];
var order = [];
var totalPermutations;
var count = 0;
var recordDistance;
var bestEver;
q.setup = function() {
q.randomSeed(Seed);
q.createCanvas(400, 400);
for (var i = 0; i < totalCities; i++) {
var v = q.createVector(q.random(q.width), q.random(q.height / 2));
cities[i] = v;
order[i] = i;
}
var d = q.calcDistance(cities, order);
recordDistance = d;
bestEver = order.slice();
}
q.draw = function() {
q.background(0);
q.fill(255);
for (var i = 0; i < cities.length; i++) {
q.ellipse(cities[i].x, cities[i].y, 8, 8);
}
q.stroke(255, 0, 255);
q.strokeWeight(4);
q.noFill();
q.beginShape();
for (var i = 0; i < order.length; i++) {
var n = bestEver[i];
q.vertex(cities[n].x, cities[n].y);
}
q.endShape();
q.translate(0, q.height / 2);
q.stroke(255);
q.strokeWeight(1);
q.noFill();
q.beginShape();
for (var i = 0; i < order.length; i++) {
var n = order[i];
q.vertex(cities[n].x, cities[n].y);
}
q.endShape();
var d = q.calcDistance(cities, order);
if (d < recordDistance) {
recordDistance = d;
bestEver = order.slice();
}
q.nextOrder();
}
q.swap = function(a, i, j) {
var temp = a[i];
a[i] = a[j];
a[j] = temp;
}
q.calcDistance = function(points, order) {
var sum = 0;
for (var i = 0; i < order.length - 1; i++) {
var cityAIndex = order[i];
var cityA = points[cityAIndex];
var cityBIndex = order[i + 1];
var cityB = points[cityBIndex];
var d = q.dist(cityA.x, cityA.y, cityB.x, cityB.y);
sum += d;
}
return sum;
}
// This is my lexical order algorithm
q.nextOrder = function() {
// STEP 1 of the algorithm
// https://www.quora.com/How-would-you-explain-an-algorithm-that-generates-permutations-using-lexicographic-ordering
var largestI = -1;
for (var i = 0; i < order.length - 1; i++) {
if (order[i] < order[i + 1]) {
largestI = i;
}
}
if (largestI == -1) {
q.noLoop();
}
// STEP 2
var largestJ = -1;
for (var j = 0; j < order.length; j++) {
if (order[largestI] < order[j]) {
largestJ = j;
}
}
// STEP 3
q.swap(order, largestI, largestJ);
// STEP 4: reverse from largestI + 1 to the end
var endArray = order.splice(largestI + 1);
endArray.reverse();
order = order.concat(endArray);
}
};
var myp5 = new p5(lexicographicOrder, 'c2');
var geneticSearch = function( p ) {
var cities = [];
var popSize = 500;
var population = [];
var fitness = [];
var recordDistance = Infinity;
var bestEver;
var currentBest;
var statusP;
p.setup = function(){
p.randomSeed(Seed);
p.createCanvas(400, 400);
var order = [];
for (var i = 0; i < totalCities; i++) {
var v = p.createVector(p.random(p.width), p.random(p.height / 2));
cities[i] = v;
order[i] = i;
}
for (var i = 0; i < popSize; i++) {
population[i] = p.shuffle(order);
}
statusP = p.createP('').style('font-size', '32pt');
}
p.draw = function() {
p.background(0);
p.stroke(255);
p.fill(255);
for (var i = 0; i < cities.length; i++) {
p.ellipse(cities[i].x, cities[i].y, 8, 8);
}
p.calculateFitness();
p.normalizeFitness();
p.nextGeneration();
p.noFill();
p.strokeWeight(4);
p.beginShape();
for (var i = 0; i < bestEver.length; i++) {
var n = bestEver[i];
p.stroke(255, 0, 255);
p.vertex(cities[n].x, cities[n].y);
//p.stroke(255);
//p.ellipse(cities[n].x, cities[n].y, 8, 8);
}
p.endShape();
p.translate(0, p.height / 2);
p.stroke(255);
p.strokeWeight(1);
p.noFill();
p.beginShape();
for (var i = 0; i < currentBest.length; i++) {
var n = currentBest[i];
p.vertex(cities[n].x, cities[n].y);
//p.ellipse(cities[n].x, cities[n].y, 8, 8);
}
p.endShape();
}
p.swap = function(a, i, j) {
var temp = a[i];
a[i] = a[j];
a[j] = temp;
}
p.calcDistance = function(points, order) {
var sum = 0;
for (var i = 0; i < order.length - 1; i++) {
var cityAIndex = order[i];
var cityA = points[cityAIndex];
var cityBIndex = order[i + 1];
var cityB = points[cityBIndex];
var d = p.dist(cityA.x, cityA.y, cityB.x, cityB.y);
sum += d;
}
return sum;
}
p.calculateFitness = function() {
var currentRecord = Infinity;
for (var i = 0; i < population.length; i++) {
var d = p.calcDistance(cities, population[i]);
if (d < recordDistance) {
recordDistance = d;
bestEver = population[i];
}
if (d < currentRecord) {
currentRecord = d;
currentBest = population[i];
}
fitness[i] = 1 / (p.pow(d, 8) + 1);
}
}
p.normalizeFitness = function() {
var sum = 0;
for (var i = 0; i < fitness.length; i++) {
sum += fitness[i];
}
for (var i = 0; i < fitness.length; i++) {
fitness[i] = fitness[i] / sum;;
}
}
p.nextGeneration = function() {
var newPopulation = [];
for (var i = 0; i < population.length; i++) {
var orderA = p.pickOne(population, fitness);
var orderB = p.pickOne(population, fitness);
var order = p.crossOver(orderA, orderB);
p.mutate(order, 0.01);
newPopulation[i] = order;
}
population = newPopulation;
}
p.pickOne = function(list, prob) {
var index = 0;
var r = p.random(1);
while (r > 0) {
r = r - prob[index];
index++;
}
index--;
return list[index].slice();
}
p.crossOver = function(orderA, orderB) {
var start = p.floor(p.random(orderA.length));
var end = p.floor(p.random(start + 1, orderA.length));
var neworder = orderA.slice(start, end);
// var left = totalCities - neworder.length;
for (var i = 0; i < orderB.length; i++) {
var city = orderB[i];
if (!neworder.includes(city)) {
neworder.push(city);
}
}
return neworder;
}
p.mutate = function(order, mutationRate) {
for (var i = 0; i < totalCities; i++) {
if (p.random(1) < mutationRate) {
var indexA = p.floor(p.random(order.length));
var indexB = (indexA + 1) % totalCities;
p.swap(order, indexA, indexB);
}
}
}
}
var myp5 = new p5(geneticSearch, 'c3');