UNPKG

genetic-js-no-ww

Version:

Advanced genetic and evolutionary algorithm library (no web workers fork)

567 lines (428 loc) 12.1 kB
<!DOCTYPE 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>