Add projects and tutorials
This commit is contained in:
79
tutorials/tsp/index.html
Normal file
79
tutorials/tsp/index.html
Normal file
@@ -0,0 +1,79 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<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'">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>
|
||||
382
tutorials/tsp/sketch.js
Normal file
382
tutorials/tsp/sketch.js
Normal 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');
|
||||
160
tutorials/tsp/style.css
Normal file
160
tutorials/tsp/style.css
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user