|
|
- <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>
|