| <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script> | |
| <script | |
|         src="https://code.jquery.com/jquery-3.3.1.slim.min.js" | |
|         integrity="sha256-3edrmyuQ0w65f8gfBsqowzjJe2iM6n0nKciPUp8y+7E=" | |
|         crossorigin="anonymous"> | |
| </script> | |
| <script> | |
|     class Gene | |
|     { | |
|         /** | |
|          * Constructs a new Gene to store in a chromosome. | |
|          * @param min minimum value that this gene can store | |
|          * @param max value this gene can possibly be | |
|          * @param value normalized value | |
|          */ | |
|         constructor(min, max, value) | |
|         { | |
|             this.min = min; | |
|             this.max = max; | |
|             this.value = value; | |
|         } | |
|         /** | |
|          * De-normalizes the value of the gene | |
|          * @returns {*} | |
|          */ | |
|         getRealValue() | |
|         { | |
|             return (this.max - this.min) * this.value + this.min; | |
|         } | |
|         getValue() | |
|         { | |
|             return this.value; | |
|         } | |
|         setValue(val) | |
|         { | |
|             this.value = val; | |
|         } | |
|         makeClone() | |
|         { | |
|             return new Gene(this.min, this.max, this.value); | |
|         } | |
|         makeRandomGene() | |
|         { | |
|             return new Gene(this.min, this.max, Math.random()); | |
|         } | |
|     } | |
|     class Chromosome | |
|     { | |
|         /** | |
|          * Constructs a chromosome by making a copy of | |
|          * a list of genes. | |
|          * @param geneArray | |
|          */ | |
|         constructor(geneArray) | |
|         { | |
|             this.genes = []; | |
|             for(let i = 0; i < geneArray.length; i++) | |
|             { | |
|                 this.genes.push(geneArray[i].makeClone()); | |
|             } | |
|         } | |
|         getGenes() | |
|         { | |
|             return this.genes; | |
|         } | |
|         /** | |
|          * Mutates a random gene. | |
|          */ | |
|         mutate() | |
|         { | |
|             this.genes[Math.round(Math.random() * (this.genes.length-1))].setValue(Math.random()); | |
|         } | |
|         /** | |
|          * Creates a totally new chromosome with same | |
|          * genetic structure as this chromosome but different | |
|          * values. | |
|          * @returns {Chromosome} | |
|          */ | |
|         createRandomChromosome() | |
|         { | |
|             let geneAr = []; | |
|             for(let i = 0; i < this.genes.length; i++) | |
|             { | |
|                 geneAr.push(this.genes[i].makeRandomGene()); | |
|             } | |
|             return new Chromosome(geneAr); | |
|         } | |
|     } | |
|     /** | |
|      * Mates two chromosomes using the blending method | |
|      * and returns a list of 2 offspring. | |
|      * @param father | |
|      * @param mother | |
|      * @returns {Chromosome[]} | |
|      */ | |
|     const breed = function(father, mother) | |
|     { | |
|         let son = new Chromosome(father.getGenes()); | |
|         let daughter = new Chromosome(mother.getGenes()); | |
|         for(let i = 0;i < son.getGenes().length; i++) | |
|         { | |
|             let blendCoef = Math.random(); | |
|             blendGene(son.getGenes()[i], daughter.getGenes()[i], blendCoef); | |
|         } | |
|         return [son, daughter]; | |
|     }; | |
|     /** | |
|      * Blends two genes together based on a random blend | |
|      * coefficient. | |
|      **/ | |
|     const blendGene = function(gene1, gene2, blendCoef) | |
|     { | |
|         let value1 = (blendCoef * gene1.getValue()) + | |
|             (gene2.getValue() * (1- blendCoef)); | |
|         let value2 = ((1-blendCoef) * gene1.getValue()) + | |
|             (gene2.getValue() * blendCoef); | |
|         gene1.setValue(value1); | |
|         gene2.setValue(value2); | |
|     }; | |
|     /** | |
|      * Helper function to sort an array | |
|      * | |
|      * @param prop name of JSON property to sort by | |
|      * @returns {function(*, *): number} | |
|      */ | |
|     function predicateBy(prop) | |
|     { | |
|         return function(a,b) | |
|         { | |
|             var result; | |
|             if(a[prop] > b[prop]) | |
|             { | |
|                 result =  1; | |
|             } | |
|             else if(a[prop] < b[prop]) | |
|             { | |
|                 result = -1; | |
|             } | |
|             return result; | |
|         } | |
|     } | |
|     /** | |
|      * Function which computes the fitness of everyone in the | |
|      * population and returns the most fit survivors. Method | |
|      * known as elitism. | |
|      * | |
|      * @param population | |
|      * @param keepNumber | |
|      * @param fitnessFunction | |
|      * @returns {{average: number, | |
|      * survivors: Array, bestFit: Chromosome }} | |
|      */ | |
|     const naturalSelection = function(population, keepNumber, fitnessFunction) | |
|     { | |
|         let fitnessArray = []; | |
|         let total = 0; | |
|         for(let i = 0; i < population.length; i++) | |
|         { | |
|             const fitness = fitnessFunction(population[i]); | |
|             fitnessArray.push({fit:fitness, chrom: population[i]}); | |
|             total+= fitness; | |
|         } | |
|         fitnessArray.sort(predicateBy("fit")); | |
|         let survivors = []; | |
|         let bestFitness = fitnessArray[0].fit; | |
|         let bestChromosome = fitnessArray[0].chrom; | |
|         for(let i = 0; i < keepNumber; i++) | |
|         { | |
|             survivors.push(fitnessArray[i].chrom); | |
|         } | |
|         return {average: total/population.length, survivors: survivors, bestFit: bestFitness, bestChrom: bestChromosome}; | |
|     }; | |
|     /** | |
|      * Randomly  everyone in the population | |
|      * | |
|      * @param population | |
|      * @param desiredPopulationSize | |
|      */ | |
|     const matePopulation = function(population, desiredPopulationSize) | |
|     { | |
|         const originalLength = population.length; | |
|         while(population.length < desiredPopulationSize) | |
|         { | |
|             let index1 = Math.round(Math.random() * (originalLength-1)); | |
|             let index2 = Math.round(Math.random() * (originalLength-1)); | |
|             if(index1 !== index2) | |
|             { | |
|                 const babies = breed(population[index1], population[index2]); | |
|                 population.push(babies[0]); | |
|                 population.push(babies[1]); | |
|             } | |
|         } | |
|     }; | |
|     /** | |
|      * Randomly mutates the population | |
|      **/ | |
|     const mutatePopulation = function(population, mutatePercentage) | |
|     { | |
|         if(population.length >= 2) | |
|         { | |
|             let mutations = mutatePercentage * | |
|                 population.length * | |
|                 population[0].getGenes().length; | |
|             for(let i = 0; i < mutations; i++) | |
|             { | |
|                 population[i].mutate(); | |
|             } | |
|         } | |
|         else | |
|         { | |
|             console.log("Error, population too small to mutate"); | |
|         } | |
|     }; | |
|     /** | |
|      * Introduces x random chromosomes to the population. | |
|      * @param population | |
|      * @param immigrationSize | |
|      */ | |
|     const newBlood = function(population, immigrationSize) | |
|     { | |
|         for(let i = 0; i < immigrationSize; i++) | |
|         { | |
|             let geneticChromosome = population[0]; | |
|             population.push(geneticChromosome.createRandomChromosome()); | |
|         } | |
|     }; | |
|     let costx = Math.random() * 10; | |
|     let costy = Math.random() * 10; | |
|     /** Defines the cost as the "distance" to a 2-d point. | |
|      * @param chromosome | |
|      * @returns {number} | |
|      */ | |
|     const basicCostFunction = function(chromosome) | |
|     { | |
|         return Math.abs(chromosome.getGenes()[0].getRealValue() - costx) + | |
|             Math.abs(chromosome.getGenes()[1].getRealValue() - costy); | |
|     }; | |
|     /** | |
|      * Creates a totally random population based  on a desired size | |
|      * and a prototypical chromosome. | |
|      * | |
|      * @param geneticChromosome | |
|      * @param populationSize | |
|      * @returns {Array} | |
|      */ | |
|     const createRandomPopulation = function(geneticChromosome, populationSize) | |
|     { | |
|         let population = []; | |
|         for(let i = 0; i < populationSize; i++) | |
|         { | |
|             population.push(geneticChromosome.createRandomChromosome()); | |
|         } | |
|         return population; | |
|     }; | |
|     /** | |
|      * Runs the genetic algorithm by going through the processes of | |
|      * natural selection, mutation, mating, and immigrations. This | |
|      * process will continue until an adequately performing chromosome | |
|      * is found or a generation threshold is passed. | |
|      * | |
|      * @param geneticChromosome Prototypical chromosome: used so algo knows | |
|      *                            what the dna of the population looks like. | |
|      * @param costFunction Function which defines how bad a Chromosome is | |
|      * @param populationSize Desired population size for population | |
|      * @param maxGenerations Cut off level for number of generations to run | |
|      * @param desiredCost Sufficient cost to terminate program at | |
|      * @param mutationRate Number between [0,1] representing proportion of genes | |
|      * to mutate each generation | |
|      * @param keepNumber Number of Organisms which survive each generation | |
|      * @param newBloodNumber Number of random immigrants to introduce into | |
|      * the population each generation. | |
|      * @returns {*} | |
|      */ | |
|     const runGeneticOptimization = function(geneticChromosome, costFunction, | |
|                                             populationSize, maxGenerations, | |
|                                             desiredCost, mutationRate, keepNumber, | |
|                                             newBloodNumber) | |
|     { | |
|         let population = createRandomPopulation(geneticChromosome, populationSize); | |
|         let generation = 0; | |
|         let bestCost = Number.MAX_VALUE; | |
|         let bestChromosome = geneticChromosome; | |
|         do | |
|         { | |
|             matePopulation(population, populationSize); | |
|             newBlood(population, newBloodNumber); | |
|             mutatePopulation(population, mutationRate); | |
|             let generationResult = naturalSelection(population, keepNumber, costFunction); | |
|             if(bestCost > generationResult.bestFit) | |
|             { | |
|                 bestChromosome = generationResult.bestChrom; | |
|                 bestCost = generationResult.bestFit; | |
|             } | |
|             population = generationResult.survivors; | |
|             generation++; | |
|             console.log("Generation " + generation + " Best Cost: " + bestCost); | |
|         }while(generation < maxGenerations && bestCost > desiredCost); | |
|         return bestChromosome; | |
|     }; | |
|     /** | |
|      * Ugly globals used to keep track of population state for the graph. | |
|      */ | |
|     let genericChromosomeG, costFunctionG, | |
|         populationSizeG, maxGenerationsG, | |
|         desiredCostG, mutationRateG, keepNumberG, | |
|         newBloodNumberG, populationG, generationG, | |
|         bestCostG = Number.MAX_VALUE, bestChromosomeG = genericChromosomeG; | |
|     const runGeneticOptimizationForGraph = function() | |
|     { | |
|         let generationResult = naturalSelection(populationG, keepNumberG, costFunctionG); | |
|         stats.push([generationG, generationResult.bestFit, generationResult.average]); | |
|         if(bestCostG > generationResult.bestFit) | |
|         { | |
|             bestChromosomeG = generationResult.bestChrom; | |
|             bestCostG = generationResult.bestFit; | |
|         } | |
|         populationG = generationResult.survivors; | |
|         generationG++; | |
|         console.log("Generation " + generationG + " Best Cost: " + bestCostG); | |
|         console.log(generationResult); | |
|         matePopulation(populationG, populationSizeG); | |
|         newBlood(populationG, newBloodNumberG); | |
|         mutatePopulation(populationG, mutationRateG); | |
|         createGraph(); | |
|     }; | |
|     let stats = []; | |
|     const createGraph = function() | |
|     { | |
|         var dataPoints = []; | |
|         console.log(dataPoints); | |
|         var data = new google.visualization.DataTable(); | |
|         data.addColumn('number', 'Gene 1'); | |
|         data.addColumn('number', 'Gene 2'); | |
|         for(let i = 0; i < populationG.length; i++) | |
|         { | |
|             data.addRow([populationG[i].getGenes()[0].getRealValue(), | |
|                 populationG[i].getGenes()[1].getRealValue()]); | |
|         } | |
|         var options = { | |
|             title: 'Genetic Evolution On Two Genes Generation: ' + generationG, | |
|             hAxis: {title: 'Gene 1', minValue: 0, maxValue: 10}, | |
|             vAxis: {title: 'Gene 2', minValue: 0, maxValue: 10}, | |
|         }; | |
|         var chart = new google.visualization.ScatterChart(document.getElementById('chart_div')); | |
|         chart.draw(data, options); | |
|         //line chart stuff | |
|         var line_data = new google.visualization.DataTable(); | |
|         line_data.addColumn('number', 'Generation'); | |
|         line_data.addColumn('number', 'Best'); | |
|         line_data.addColumn('number', 'Average'); | |
|         line_data.addRows(stats); | |
|         console.log(stats); | |
|         var lineChartOptions = { | |
|             hAxis: { | |
|                 title: 'Generation' | |
|             }, | |
|             vAxis: { | |
|                 title: 'Cost' | |
|             }, | |
|             colors: ['#AB0D06', '#007329'] | |
|         }; | |
|         var chart = new google.visualization.LineChart(document.getElementById('line_chart')); | |
|         chart.draw(line_data, lineChartOptions); | |
|     }; | |
|     let gene1 = new Gene(1,10,10); | |
|     let gene2 = new Gene(1,10,0.4); | |
|     let geneList = [gene1, gene2]; | |
|     let exampleOrganism = new Chromosome(geneList); | |
|     genericChromosomeG = exampleOrganism; | |
|     costFunctionG = basicCostFunction; | |
|     populationSizeG = 100; | |
|     maxGenerationsG = 30; | |
|     desiredCostG = 0.00001; | |
|     mutationRateG = 0.3; | |
|     keepNumberG = 30; | |
|     newBloodNumberG = 10; | |
|     generationG = 0; | |
|     function verifyForm() | |
|     { | |
|         if(Number($("#populationSize").val()) <= 1) | |
|         { | |
|             alert("Population size must be greater than one."); | |
|             return false; | |
|         } | |
|         if(Number($("#mutationRate").val()) > 1 || | |
|             Number($("#mutationRate").val()) < 0) | |
|         { | |
|             alert("Mutation rate must be between zero and one."); | |
|             return false; | |
|         } | |
|         if(Number($("#survivalSize").val()) < 0) | |
|         { | |
|             alert("Survival size can't be less than one."); | |
|             return false; | |
|         } | |
|         if(Number($("#newBlood").val()) < 0) | |
|         { | |
|             alert("New organisms can't be a negative number."); | |
|             return false; | |
|         } | |
|         return true; | |
|     } | |
|     function resetPopulation() | |
|     { | |
|         if(verifyForm()) | |
|         { | |
|             stats = []; | |
|             autoRunning = false; | |
|             $("#runAutoOptimizer").val("Auto Run"); | |
|             populationSizeG = $("#populationSize").val(); | |
|             mutationRateG = $("#mutationRate").val(); | |
|             keepNumberG = $("#survivalSize").val(); | |
|             newBloodNumberG = $("#newBlood").val(); | |
|             generationG = 0; | |
|             populationG = createRandomPopulation(genericChromosomeG, populationSizeG); | |
|             createGraph(); | |
|         } | |
|     } | |
|     populationG = createRandomPopulation(genericChromosomeG, populationSizeG); | |
|     window.onload = function (){ | |
|         google.charts.load('current', {packages: ['corechart', 'line']}); | |
|         google.charts.load('current', {'packages':['corechart']}).then(function() | |
|         { | |
|             createGraph(); | |
|         }) | |
|     }; | |
|     let autoRunning = false; | |
|     function runAutoOptimizer() | |
|     { | |
|         if(autoRunning === true) | |
|         { | |
|             runGeneticOptimizationForGraph(); | |
|             setTimeout(runAutoOptimizer, 1000); | |
|         } | |
|     } | |
|     function startStopAutoRun() | |
|     { | |
|         autoRunning = !autoRunning; | |
|         if(autoRunning) | |
|         { | |
|             $("#runAutoOptimizer").val("Stop Auto Run"); | |
|         } | |
|         else | |
|         { | |
|             $("#runAutoOptimizer").val("Resume Auto Run"); | |
|         } | |
|         runAutoOptimizer(); | |
|     } | |
| </script> | |
| <div id="chart_div"></div> | |
| <div id="line_chart"></div> | |
| <input class='btn btn-primary' id="runOptimizer" onclick='runGeneticOptimizationForGraph()' type="button" value="Next Generation"> | |
| <input class='btn btn-primary' id="runAutoOptimizer" onclick='startStopAutoRun()' type="button" value="Auto Run"> | |
| <br> | |
| <br> | |
| <div class="card"> | |
|     <div class="card-header"> | |
|         <h2>Population Variables</h2> | |
|     </div> | |
|     <form class="card-body"> | |
|         <div class="row p-2"> | |
|             <div class="col"> | |
|                 <label for="populationSize">Population Size</label> | |
|                 <input type="text" class="form-control" value="100" id="populationSize" placeholder="Population Size" required> | |
|             </div> | |
|             <div class="col"> | |
|                 <label for="populationSize">Survival Size</label> | |
|                 <input type="text" class="form-control" value="20" id="survivalSize" placeholder="Survival Size" required> | |
|             </div> | |
|         </div> | |
|         <div class="row p-2"> | |
|             <div class="col"> | |
|                 <label for="populationSize">Mutation Rate</label> | |
|                 <input type="text" class="form-control" value="0.03" id="mutationRate" placeholder="Mutation Rate" required> | |
|             </div> | |
|             <div class="col"> | |
|                 <label for="populationSize">New Organisms Per Generation</label> | |
|                 <input type="text" class="form-control" value="5" id="newBlood" placeholder="New Organisms" required> | |
|             </div> | |
|         </div> | |
|         <br> | |
|         <input class='btn btn-primary' id="reset" onclick='resetPopulation()' type="button" value="Reset Population"> | |
|     </form> | |
| </div> |