genetic-js-no-ww
Version:
Advanced genetic and evolutionary algorithm library (no web workers fork)
567 lines (428 loc) • 12.1 kB
HTML
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>Genetic.js Curve Fitter</title>
<script src="../lib/genetic.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</head>
<body>
<h1>Genetic Curve Fitter</h1>
Dataset:
<select id="dataset">
<option value=""> - Choose - </option>
<option value="clear">Clear</option>
<option value="parabola">Parabola</option>
<option value="linear">Linear (with noise)</option>
<option value="sinusoidal">Sinusoidal</option>
<option value="sinusoidal2">Sinusoidal (with noise)</option>
</select>
<br/>Iterations:
<select id="iterations">
<option>50</option>
<option>100</option>
<option>300</option>
<option selected="selected">500</option>
<option>1000</option>
<option>2000</option>
</select>
<br/>Degree:
<select id="degree">
<option value="0">0 (constant)</option>
<option value="1">1 (line)</option>
<option value="2">2 (parabola)</option>
<option value="3">3 (polynomial)</option>
<option value="4" selected="selected">4 (polynomial)</option>
</select>
<br/>Mutation:
<select id="mutation">
<option>0.0</option>
<option>0.1</option>
<option>0.2</option>
<option>0.3</option>
<option>0.4</option>
<option>0.5</option>
<option>0.6</option>
<option>0.7</option>
<option>0.8</option>
<option>0.9</option>
<option selected="selected">1.0</option>
</select>
with
<select id="single-selection">
<option>Genetic.Select1.Tournament2</option>
<option>Genetic.Select1.Tournament3</option>
<option>Genetic.Select1.Fittest</option>
<option>Genetic.Select1.Random</option>
<option>Genetic.Select1.RandomLinearRank</option>
<option>Genetic.Select1.Sequential</option>
</select>
<br/>Crossover:
<select id="crossover">
<option>0.0</option>
<option>0.1</option>
<option>0.2</option>
<option>0.3</option>
<option selected="selected">0.4</option>
<option>0.5</option>
<option>0.6</option>
<option>0.7</option>
<option>0.8</option>
<option>0.9</option>
<option>1.0</option>
</select>
with
<select id="pair-selection">
<option>Genetic.Select2.Tournament2</option>
<option>Genetic.Select2.Tournament3</option>
<option>Genetic.Select2.Random</option>
<option>Genetic.Select2.RandomLinearRank</option>
<option>Genetic.Select2.Sequential</option>
<option selected="selected">Genetic.Select2.FittestRandom</option>
</select>
<br/><button id="solve">Compute</button>
<div>
<br/>Solution: <span id="solution">Press 'Compute' Button</span>
<br/>Error: <span id="bestfit"></span>
<br/>Avg Error per Vertex: <span id="vertexerror"></span>
<br/>Generation: <span id="generation"></span>
<br/>Average error: <span id="avgbestfit"></span>
<br/>error stdev: <span id="errorstdev"></span>
</div>
<canvas id="scratch" style="width: 800px; height: 500px; cursor: crosshair;"></canvas>
<script>
var graph = new Graph(document.getElementById("scratch"), 10, 10);
var genetic = Genetic.create();
genetic.optimize = Genetic.Optimize.Minimize;
genetic.select1 = Genetic.Select1.Tournament2;
genetic.select2 = Genetic.Select2.FittestRandom;
genetic.seed = function() {
var a = [];
// create coefficients for polynomial with values between (-0.5, 0.5)
var i;
for (i=0;i<this.userData["terms"];++i) {
a.push(Math.random()-0.01);
}
return a;
};
genetic.mutate = function(entity) {
// allow chromosomal drift with this range (-0.05, 0.05)
var drift = ((Math.random()-0.5)*2)*0.05;
var i = Math.floor(Math.random()*entity.length);
entity[i] += drift;
return entity;
};
genetic.crossover = function(mother, father) {
// crossover via interpolation
function lerp(a, b, p) {
return a + (b-a)*p;
}
var len = mother.length;
var i = Math.floor(Math.random()*len);
var r = Math.random();
var son = [].concat(father);
var daughter = [].concat(mother);
son[i] = lerp(father[i], mother[i], r);
daughter[i] = lerp(mother[i], father[i], r);
return [son, daughter];
};
// example 3 term polynomial: cx^0 + bx^1 + ax^2
genetic.evaluatePoly = function(coefficients, x) {
var s = 0;
var p = 1;
var i;
for (i=0;i<coefficients.length;++i) {
s += p*coefficients[i];
p *= x;
}
return s;
}
genetic.fitness = function(entity) {
var sumSqErr = 0;
var vertices = this.userData["vertices"];
var i;
for (i=0;i<vertices.length;++i) {
var err = this.evaluatePoly(entity, vertices[i][0]) - vertices[i][1];
sumSqErr += err*err;
}
return Math.sqrt(sumSqErr);
};
genetic.generation = function(pop, generation, stats) {
};
genetic.notification = function(pop, generation, stats, isFinished) {
function poly(entity) {
var a = [];
var i;
for (i=entity.length-1;i>=0;--i) {
var buf = entity[i].toPrecision(2);
if (i > 1)
buf += "<em><b>x<sup>" + i + "<sup></b></em>";
else if (i == 1)
buf += "<em><b>x</b></em>";
a.push(buf);
}
return a.join(" + ");
}
function lerp(a, b, p) {
return a + (b-a)*p;
}
if (generation == 0) {
graph.solutions = [];
}
$("#solution").html(poly(pop[0].entity));
$("#generation").html(generation+1);
$("#bestfit").html(pop[0].fitness.toPrecision(4))
$("#vertexerror").html((pop[0].fitness/graph.vertices.length).toPrecision(4));
;
$("#avgbestfit").html(stats.mean.toPrecision(4));
$("#errorstdev").html(stats.stdev.toPrecision(4));
var last = graph.last||"";
var str = pop[0].entity.join(",");
if (last != str || isFinished) {
if (last != str) {
graph.solutions.push(pop[0].entity);
graph.last = str;
}
graph.draw();
var i;
var start = Math.max(0, graph.solutions.length-10);
if (isFinished) {
start = 0;
}
for (i=start;i<graph.solutions.length;++i) {
var p = (i-start)/(graph.solutions.length-start);
var r = Math.round(lerp(90,255,p));
var g = Math.round(lerp(0,255,0));
var b = Math.round(lerp(200,50,p));
var alpha = lerp(0.5,1,p);
var strokeStyle = "rgba(" + r + "," + g + "," + b + "," + alpha + ")";
var lineWidth = Math.floor(lerp(10,1,p));
//var strokeStyle = i == graph.solutions.length-1 ? "#00f" : "rgba(0,0,0,0.1)";
graph.drawFunction(graph.solutions[i], strokeStyle, lineWidth);
}
graph.drawVertices();
}
};
function Graph(canvas, xmax, ymax) {
this.canvas = document.getElementById("scratch");
this.xmax = xmax;
this.ymax = ymax;
// canvas dimensions
this.width = parseInt(canvas.style.width);
this.height = parseInt(canvas.style.height);
// retina
var dpr = window.devicePixelRatio || 1;
canvas.width = this.width*dpr;
canvas.height = this.height*dpr;
this.ctx = canvas.getContext("2d");
this.ctx.scale(dpr, dpr);
this.bound = [0,this.width-1,this.height-1,0];
this.bound[0] += 25;
this.bound[1] -= 25;
this.bound[2] -= 25;
this.bound[3] += 25;
this.vertices = [];
this.solutions = [];
}
Graph.prototype.drawFunction = function(coefficients, strokeStyle, lineWidth) {
var ctx = this.ctx;
ctx.save();
var bound = this.bound;
ctx.strokeStyle = strokeStyle;
var xmax = this.xmax;
var ymax = this.ymax;
var xstride = (bound[1]-bound[3])/xmax;
var ystride = (bound[2]-bound[0])/ymax;
var inc = 1/xstride;
ctx.lineWidth = lineWidth;
ctx.beginPath();
var x;
for (x=0;x<xmax;x+=inc) {
var cx = x*xstride + bound[3];
var cy = bound[2] - genetic.evaluatePoly(coefficients, x)*ystride;
if (x == 0) {
ctx.moveTo(cx, cy);
} else {
ctx.lineTo(cx, cy);
}
}
ctx.stroke();
ctx.restore();
}
Graph.prototype.draw = function() {
var ctx = this.ctx;
ctx.save();
var bound = this.bound;
ctx.strokeStyle = "#000";
ctx.fillStyle = "#000";
ctx.clearRect(0, 0, this.width, this.height);
var xmax = this.xmax;
var ymax = this.ymax;
var xstride = (bound[1]-bound[3])/xmax;
var ystride = (bound[2]-bound[0])/ymax;
var i;
// x-grid
for (i=0;i<=xmax;++i) {
var cx = i*xstride + bound[3];
var y = bound[2];
ctx.strokeStyle = "#eee";
ctx.beginPath();
ctx.moveTo(cx, bound[0]);
ctx.lineTo(cx, y);
ctx.stroke();
}
// y-grid
for (i=0;i<=ymax;++i) {
var cx = bound[3];
var y = bound[2] - i*ystride;
ctx.beginPath();
ctx.moveTo(cx, y);
ctx.lineTo(bound[1], y);
ctx.stroke();
}
// x/y bars
ctx.beginPath();
ctx.strokeStyle = "#bbb";
ctx.moveTo(bound[3], bound[0]);
ctx.lineTo(bound[3], bound[2]);
ctx.lineTo(bound[1], bound[2]);
ctx.lineWidth = 3;
ctx.stroke();
ctx.lineWidth = 1;
var i;
// x bars
ctx.strokeStyle = "#000";
for (i=0;i<=xmax;++i) {
var cx = i*xstride + bound[3];
var y = bound[2];
ctx.beginPath();
ctx.moveTo(cx, y);
ctx.lineTo(cx, y+4);
ctx.stroke();
ctx.font = "12px sans-serif";
ctx.textAlign = "center";
ctx.fillText(i,cx,y+16);
}
// y bars
for (i=0;i<=ymax;++i) {
var cx = bound[3];
var y = bound[2] - i*ystride;
ctx.beginPath();
ctx.moveTo(cx, y);
ctx.lineTo(cx-4, y);
ctx.stroke();
ctx.font = "12px sans-serif";
ctx.textAlign = "right";
ctx.fillText(i,cx-8,y+4);
}
ctx.restore();
};
Graph.prototype.drawVertices = function() {
var ctx = this.ctx;
ctx.save();
var bound = this.bound;
var xmax = this.xmax;
var ymax = this.ymax;
var xstride = (bound[1]-bound[3])/xmax;
var ystride = (bound[2]-bound[0])/ymax;
var i;
ctx.fillStyle = "#000";
ctx.strokeStyle = "#fff";
ctx.lineWidth = 2;
// vertices
for (i=0;i<this.vertices.length;++i) {
var cx = this.vertices[i][0]*xstride + bound[3];
var cy = bound[2] - this.vertices[i][1]*ystride;
ctx.beginPath();
ctx.arc(cx, cy, 3, 0, 2*Math.PI);
ctx.fill();
ctx.stroke();
}
ctx.restore();
};
$(document).ready(function () {
$("#scratch").click(function (e) {
var bound = graph.bound;
var xmax = graph.xmax;
var ymax = graph.ymax;
var xstride = (bound[1]-bound[3])/xmax;
var ystride = (bound[2]-bound[0])/ymax;
var x = (e.offsetX || e.clientX - $(e.target).offset().left);
var y = (e.offsetY || e.clientY - $(e.target).offset().top);
var cx = ((x - bound[3])/(bound[1]-bound[3]))*xmax;
var cy = ymax - ((y - bound[0])/(bound[2]-bound[0]))*ymax;
graph.vertices.push([cx, cy]);
graph.draw();
graph.drawVertices();
});
$("#solve").click(function () {
var config = {
"iterations": $("#iterations").val()
, "size": 250
, "crossover": parseFloat($("#crossover").val())
, "mutation": parseFloat($("#mutation").val())
, "skip": 10
};
var userData = {
"terms": parseInt($("#degree").val())+1
, "vertices": graph.vertices
};
genetic.evolve(config, userData);
});
$("#dataset").change(function () {
var v = $(this).val();
$("#dataset option:selected").prop("selected", false);
$("#dataset option:first").prop("selected", "selected");
if (v == "") {
return;
} else if (v == "clear") {
graph.vertices = [];
} else if (v == "parabola") {
graph.vertices = [];
graph.vertices.push([2,2]);
graph.vertices.push([5,8]);
graph.vertices.push([8,2]);
} else if (v == "linear") {
graph.vertices = [];
var x;
var b = Math.random()*3;
var m = Math.random()+0.5;
var n = 100;
for (i=0;i<n;++i) {
var cx = Math.random()*10;
var cy = m*cx + b + (Math.random()-0.5)*2;
graph.vertices.push([cx,cy]);
}
} else if (v == "sinusoidal") {
graph.vertices = [];
var n = 20;
var off = Math.random()*2*3.1415927;
var stride = 10/n;
var i;
for (i=0;i<n;++i) {
graph.vertices.push([i*stride,Math.sin((off + i/n)*2*3.1415627)*3 + 5]);
}
} else if (v == "sinusoidal2") {
graph.vertices = [];
var n = 200;
var off = Math.random()*2*3.1415927;
var stride = 10/n;
var i;
for (i=0;i<n;++i) {
graph.vertices.push([i*stride,Math.sin((off + i/n)*2*3.1415627)*3 + 5 + (Math.random()-0.5)*2]);
}
}
graph.draw();
graph.drawVertices();
});
$("#single-selection").change(function () {
genetic.select1 = eval($(this).val());
});
$("#pair-selection").change(function () {
genetic.select2 = eval($(this).val());
});
$("#dataset").val("sinusoidal2").change();
});
</script>
</body>
</html>