This commit is contained in:
Jay
2026-03-10 21:01:46 +00:00
commit 9006b2e06a
242 changed files with 30823 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
<?php
if($_SERVER['HTTP_X_FORWARDED_PROTO'] !== 'https') {
header('Location: https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
die();
}
?>
<!DOCTYPE html>
<html>
<link rel="shortcut icon" type"image/ico" href"../Media/iconImage.ico">
<link href="style.css" rel="stylesheet" type="text/css">
<script
src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.2.0/p5.min.js"
integrity="sha512-b/htz6gIyFi3dwSoZ0Uv3cuv3Ony7EeKkacgrcVg8CMzu90n777qveu0PBcbZUA7TzyENGtU+qZRuFAkfqgyoQ=="
crossorigin="anonymous"
></script>
<title>Travelling Sales Person</title>
<h1>Travelling Sales Person Problem</h1>
<button id="backButton" onclick="window.location.href='../../tutorials.html'">Back</button>
<head>
<meta charset="UTF-8">
<div class="pictureContainer">
<img src="https://optimization.mccormick.northwestern.edu/images/e/ea/48StatesTSP.png"
alt="TSP Problem" id="Conway">
</div>
<p>The travelling salesman problem (TSP) asks the following question: "Given a list of cities and the distances between each pair of cities, what is the shortest possible route that visits each city and returns to the origin city?" </p>
<p>The problem was first formulated in 1930 and is one of the most intensively studied problems in optimization. It is used as a benchmark for many optimization methods. Even though the problem is computationally difficult, a large number of heuristics and exact algorithms are known, so that some instances with tens of thousands of cities can be solved completely and even problems with millions of cities can be approximated within a small fraction of 1%.</p>
<p>The TSP has several applications even in its purest formulation, such as planning, logistics, and the manufacture of microchips. Slightly modified, it appears as a sub-problem in many areas, such as DNA sequencing. In these applications, the concept city represents, for example, customers, soldering points, or DNA fragments, and the concept distance represents travelling times or cost, or a similarity measure between DNA fragments. The TSP also appears in astronomy, as astronomers observing many sources will want to minimize the time spent moving the telescope between the sources. In many applications, additional constraints such as limited resources or time windows may be imposed.</p>
<h2>These are some of the algorithms I used</h2>
<p>Note the purple route is the best route it's found so far and the thin white lines are the routes it's trying real time.</p>
</head>
<body>
<div class="canvasBody">
<h3>Random Sort</h3>
<span id="c1"></span>
<p class="canvasText">This canvas sorts through random possiblities. Every frame the program chooses two random points (cities) and swaps them around. eg say the order was London, Paris, Madrid, the program would swap London and Paris so that the new order is: Paris, London, Madrid. The program then compares the distance against the record distance to decide whether the new order is better than the old order. This search method is the most inefficient way, the worst case scenario is never ending, as the point swaping is random the program may never reach the optimum route</p><br>
<h3>Lexicographic Order</h3>
<span id="c2"></span>
<p class="canvasText">This canvas sorts through all possible orders sequentially, so after n! (where n is the number of points) this algorithm is guaranteed to have found the quickest possible route. However it is highly inefficient always taking n! frames to complete and as n increases, time taken increases exponentially.</p>
<a target="_blank" href="https://www.quora.com/How-would-you-explain-an-algorithm-that-generates-permutations-using-lexicographic-ordering">Click here to learn more about the algorithm</a><br>
<h3>Genetic Algorithm</h3>
<span id="c3"></span>
<p class="canvasText">This canvas is the most efficient at finding the quickest route, it is a mixture of the two methods above. It starts off by creating a population of orders, a fitness is then generated for each order in the population. This fitness decides how likely the order is to be picked and is based on the distance it takes (lower distance is better). When two orders are picked, the algorithm splices the two together at a random term, it's then mutated and compared against the record distance. This takes the least amount of time to find the shortest distance as the algorithm doesn't search through permuations that are obviously longer due to the order.</p><br>
</div>
</body>
<script src="sketch.js"></script>
<footer><p>This page was inspired by The Coding Train</p><a href="https://www.youtube.com/channel/UCvjgXvBlbQiydffZU7m1_aw">Check him out here</a></footer>
</html>

View File

@@ -0,0 +1,382 @@
/*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');

View File

@@ -0,0 +1,160 @@
@import url('https://fonts.googleapis.com/css?family=Roboto+Condensed');
a{font-family: 'Roboto Condensed', sans-serif; font-size: 18pt;}
h1 {
font-family: 'Roboto Condensed', sans-serif;
margin: 0;
padding: 0 0 15px 0;
}
h2 {
font-family: 'Roboto Condensed', sans-serif;
margin: 0;
padding: 0 0 15px 0;
text-align: center;
text-decoration: bold;
}
@media (min-width: 350px) {
h1 {font-size: 3.25em;}
img{height: 40px;}
p{font-size: 10px;}
h2{font-size: 17px;}
}
@media (min-width: 400px) {
h1 {font-size: 3.25em;}
img{height: 45px;}
p{font-size: 15px;}
h2{font-size: 17px;}
}
@media (min-width: 440px) {
h1 {font-size: 3.5em;}
img {height: 100px;}
p{font-size: 16px;}
h2{font-size: 18px;}
}
@media (min-width: 500px) {
h1 {font-size: 3.75em;}
img{height: 125px;}
p{font-size: 16px;}
h2{font-size: 19px;}
}
@media (min-width: 630px) {
h1 {font-size: 5em;}
img{height: 150px;}
p{font-size: 20px;}
h2{font-size: 24px;}
}
@media (min-width: 768px) {
h1 {
font-size: 5em;
padding-bottom: 30px;
}
img{height: 175px;}
p{font-size: 22px;}
h2{font-size: 26px;}
}
@media (min-width: 1200px) {
h1 {font-size: 8em;}
img{height: 250px;}
p{font-size: 24px;}
h2{font-size: 28px;}
}
p{
font-family: 'Roboto Condensed', sans-serif;
}
h3{
text-align: center;
font-size: 30px;
font-family: 'Roboto Condensed', sans-serif;
}
footer{
padding: 20px;
background-color: #e0e0e0;
font-family: 'Roboto Condensed', sans-serif;
}
@keyframes dimImg{
from {opacity: 1;
filter: alpha(opacity=100);}
to {opacity: 0.4;
filter: alpha(opacity=50);}
}
@keyframes revealText{
from {opacity: 0.4;
filter: alpha(opacity=50);}
to {opacity: 1;
filter: alpha(opacity=100);}
}
.pictureContainer{
float: right;
position: relative;
}
.pictureContainer a{
opacity: 0;
position: absolute;
text-align: center;
top: 5px;
left: 5px;
}
.pictureContainer:hover img{
animation-name: dimImg;
animation-duration: 1s;
opacity: 0.4;
filter: alpha(opacity=50);
}
.pictureContainer:hover a{
animation-name: revealText;
animation-duration: 1s;
opacity: 1;
}
.canvasText{
margin: 0px;
display: inline-block;
text-align: left;
}
#c1, #c2, #c3{
display: block;
margin-left: auto;
margin-right: auto;
width: 50%;
}
.button{
padding: 16px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
-webkit-transition-duration: 0.4s; /* Safari */
transition-duration: 0.4s;
cursor: pointer;
background-color: white;
color: black;
border: 2px solid #555555;
float: right;
}
.button:hover {
background-color: #555555;
color: white;
}