// Cloned by Guilherme Tabelini on 29 Oct 2021 from World "GA (Finnegans Wake)" by "Coding Train" project // Please leave this clone trail here.// port of // https://github.com/nature-of-code/noc-examples-processing/tree/master/chp09_ga/NOC_9_01_GA_Shakespeare_simplified// hugely modified // because ported from Processing to JS// The Nature of Code// Daniel Shiffman// http://natureofcode.com// Genetic Algorithm, Evolving Shakespeare// Demonstration of using a genetic algorithm to perform a searchconst popsize =250;// Population sizeconst pm =0.1;// Mutation rate// where score = no. of chars that match // fitness = POWERBASE ^ score// and we look at sum of all fitnesses// so the bigger POWERBASE is, the more it focuses on letting only the very fittest reproduceconst POWERBASE =8;// turn on to see how it works in console:const debugMutate =false;const debugCrossover =false;const debugMate =false;// target string:const target ="riverrun, past Eve and Adam's, from swerve of shore to bend of bay, to be, or not to be that's the question that I would like answered. We saw earlier the GA World on Ancient Brain. Now we have done enough theory to understand all its code. So let us look at it again. Click on the image below to run this Finnegans wake demo on Ancient Brain. When you are finished running this demo world, click BACK to return to the FutureLearn page.";// Finnegans Wake opening line // "To be, or not to be.";const len = target.length;const maxscore = len;// maximum possible score var maxscoreFound =false;// exit loop when find one var population;var generation;var sumscore, sumfitness;const searching_audio ="/uploads/codingtrain/rain.mp3";// http://soundbible.com/2011-Rain-Background.htmlconst success_audio ="/uploads/codingtrain/movie.start.mp3";// http://soundbible.com/1676-Movie-Start-Music.htmlvar background_audio;// DNA is string of printable chars // https://en.wikipedia.org/wiki/ASCII#Printable_charactersconst LOCHAR =32;const HICHAR =126;const NOCHARS =(HICHAR-LOCHAR)+1;// no. of possible chars function randomChar(){var c = AB.randomIntAtoB ( LOCHAR, HICHAR );return(String.fromCharCode(c));}// setup()// # Step 1: The Population // # Create an empty population (an array or ArrayList)// # Fill it with DNA encoded objects (pick random values to start)// draw()// # Step 1: Selection // # Create an empty mating pool (an empty ArrayList)// # For every member of the population, evaluate its fitness based on some criteria / function, // and add it to the mating pool in a manner consistant with its fitness, i.e. the more fit it // is the more times it appears in the mating pool, in order to be more likely picked for reproduction.// # Step 2: Reproduction Create a new empty population// # Fill the new population by executing the following steps:// 1. Pick two "parent" objects from the mating pool.// 2. Crossover -- create a "child" object by mating these two parents.// 3. Mutation -- mutate the child's DNA based on a given probability.// 4. Add the child object to the new population.// # Replace the old population with the new population// // # Rinse and repeat// escape HTML // https://stackoverflow.com/questions/6234773/can-i-escape-html-special-chars-in-javascript function escapeAllHtml ( str ){return(
str
.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'"));}// The Nature of Code// Daniel Shiffman// http://natureofcode.com// Genetic Algorithm, Evolving Shakespeare// A class to describe a psuedo-DNA, i.e. genotype// Here, a virtual organism's DNA is an array of character.// Functionality:// -- convert DNA into a string// -- calculate DNA's "fitness"// -- mate DNA with another set of DNA// -- mutate DNA//--- DNA class function DNA(){// this code executes on construction:this.genes =newArray(len);for(var i =0; i < len; i++)this.genes[i]= randomChar();this.score;this.fitness;// Converts character array to a Stringthis.getPhrase =function(){var s ="";for(var i =0; i < len; i++)
s = s +this.genes[i];return( s );};// Fitness function (how many correct characters)this.calcFitness =function(){this.score =0;for(var i =0; i < len; i++)if(this.genes[i]== target.charAt(i))this.score++;if(this.score == maxscore ) maxscoreFound =true;// all fitness zero causes problems, maybe even divide by zero // at start, most have char score zero - could even all have score zero // x to power of score will always be non-zero this.fitness =Math.pow ( POWERBASE,this.score );};// Crossoverthis.crossover =function(partner){// A new childvar child =new DNA();var x = AB.randomIntAtoB (0, len-1);// Pick a cut point // Half from one, half from the otherfor(var i =0; i < len; i++){if(i < x) child.genes[i]=this.genes[i];else child.genes[i]= partner.genes[i];}if( debugCrossover ){
console.log ("--- start crossover -----------")
console.log ("parent "+this.getPhrase());
console.log ("parent "+ partner.getPhrase());
console.log ("x "+ x );
console.log ("child "+ child.getPhrase());}return(child);};// Based on a mutation probability, picks a new random characterthis.mutate =function(){for(var i =0; i < len; i++)if( AB.randomEventProb ( pm - pm*(generation/(generation+100)))){if( debugMutate ){
console.log ("--- start mutate -----------")
console.log ("original "+this.getPhrase());
console.log ("i "+ i );}this.genes[i]= randomChar();if( debugMutate )
console.log ("new "+this.getPhrase());}};}// end of DNA class function setup(){// background_audio = AB.backgroundMusic ( searching_audio ); // slower frame rate to see how it is working
frameRate (60);
noCanvas();
AB.headerRHS();
AB.newDiv("header");
$("#header").css({"padding-top":"30","padding-left":"30","padding-bottom":"10"});
$("#header").html("<h1> Writing the first line of Finnegans Wake </h1> "+"<p><img border=1 src='/uploads/codingtrain/finnegans.jpg'> <br>"+"Image from <a href='https://archive.org/details/finneganswake00joycuoft/page/n15'>here</a>.</p>");
population =newArray(popsize);for(var i =0; i < popsize; i++)
population[i]=new DNA();
generation =1;
calcAllFitness();
drawPopulation();}function draw()// every step, move the population on and draw it {// P5 shows runs header by default // to get rid of run header:// AB.hideRunHeader();
stepPopulation();
generation++;
calcAllFitness();
drawPopulation();if( maxscoreFound ){
noLoop();// end P5 loop
background_audio.pause();
background_audio = AB.backgroundMusic ( success_audio );}}function calcAllFitness(){
sumscore =0;
sumfitness =0;for(var i =0; i < popsize; i++){
population[i].calcFitness();
sumscore = sumscore + population[i].score;
sumfitness = sumfitness + population[i].fitness;}}function pickFitMate()// pick an individual for mating, based on fitness // fittest need to be more likely to be picked for mating // uses simple summation of fitness (not Boltzmann), plus spin the weighted wheel// fitness cannot be zero or could get divide by zero {var p = AB.randomFloatAtoB (0, sumfitness );// random point between 0 and sum of fitnesses // find the individual intersecting this pointif( debugMate ){
console.log ("--- start pick mate ----------");
console.log ("p "+ p);}var x =0;for(var i =0; i < popsize; i++){
x = x + population[i].fitness;if( x > p )return i;}return(popsize-1);}function stepPopulation(){var newpopulation =newArray(popsize);for(var i =0; i < popsize; i++){var c = pickFitMate();if( debugMate ) console.log ("picked "+ c );var d = pickFitMate();if( debugMate ) console.log ("picked "+ d );var child = population[c].crossover ( population[d]);
child.mutate();
newpopulation[i]= child;}
population = newpopulation;}function drawPopulation(){var s ="<p> Generation "+ generation +"<br>"+" Total population score "+ sumscore +"<br>"+" Total population fitness "+ sumfitness.toPrecision(3)+"</p>";if( maxscoreFound ){
s = s +"<p><b> Maximum possible score found. Highlighted in green below. <br> "+"Random search would have to search number of possible strings = "+ NOCHARS +"<sup>"+ len +"</sup> <br>"+"But we found goal string after searching only "+ generation +" * "+ popsize +" strings.</b> <p>";}
s = s +"<table border=1 BORDERCOLOR=silver cellpadding=5 cellspacing=0 >"+"<tr style='background-color:#ffffcc;'><td> Population member </td><td> String </td><td> Score </td><td> Fitness </td></tr>";for(var i =0; i < popsize; i++){if( population[i].score == maxscore ) s = s +"<tr style='background-color:#ccffcc;'>";else s = s +"<tr>";
s = s +"<td>"+ i +"</td>"+"<td style='font-size:larger'><tt>"+ escapeAllHtml ( population[i].getPhrase())+"</tt></td>"+"<td>"+ population[i].score +"</td>"+"<td>"+ population[i].fitness.toPrecision(3)+"</td></tr>";}
s = s +"</table>";
AB.newDiv("theoutput");
$("#theoutput").css({"padding-left":"30","padding-bottom":"30"});
$("#theoutput").html(s);}