// Cloned by Rufus Tenali on 22 Nov 2023 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 =100;// Population sizeconst pm =0.01;// 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 =4// turn on to see how it works in console:const debugMutate =false;const debugCrossover =false;const debugMate =false;// target string:const target ="Re kabira maan jaa Re faqeera maan jaa Aaja tujhko pukaare teri parchaiyan";// 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 )){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 (1000);
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);}