|
|
@ -12,6 +12,12 @@ |
|
|
|
<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; |
|
|
@ -19,6 +25,10 @@ |
|
|
|
this.value = value; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* De-normalizes the value of the gene |
|
|
|
* @returns {*} |
|
|
|
*/ |
|
|
|
getRealValue() |
|
|
|
{ |
|
|
|
return (this.max - this.min) * this.value + this.min; |
|
|
@ -48,6 +58,11 @@ |
|
|
|
|
|
|
|
class Chromosome |
|
|
|
{ |
|
|
|
/** |
|
|
|
* Constructs a chromosome by making a copy of |
|
|
|
* a list of genes. |
|
|
|
* @param geneArray |
|
|
|
*/ |
|
|
|
constructor(geneArray) |
|
|
|
{ |
|
|
|
this.genes = []; |
|
|
@ -62,11 +77,20 @@ |
|
|
|
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 = []; |
|
|
@ -79,6 +103,13 @@ |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 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()); |
|
|
@ -89,10 +120,31 @@ |
|
|
|
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) |
|
|
@ -110,7 +162,17 @@ |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 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 = []; |
|
|
@ -135,22 +197,17 @@ |
|
|
|
return {average: total/population.length, survivors: survivors, bestFit: bestFitness, bestChrom: bestChromosome}; |
|
|
|
}; |
|
|
|
|
|
|
|
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); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Randomly everyone in the population |
|
|
|
* |
|
|
|
* @param population |
|
|
|
* @param desiredPopulationSize |
|
|
|
*/ |
|
|
|
const matePopulation = function(population, desiredPopulationSize) |
|
|
|
{ |
|
|
|
let pairsNeeded = (desiredPopulationSize - population.length)/2; |
|
|
|
const originalLength = population.length; |
|
|
|
for(let i = 0; i < pairsNeeded; i++) |
|
|
|
while(population.length < desiredPopulationSize) |
|
|
|
{ |
|
|
|
let index1 = Math.round(Math.random() * (originalLength-1)); |
|
|
|
let index2 = Math.round(Math.random() * (originalLength-1)); |
|
|
@ -163,7 +220,9 @@ |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Randomly mutates the population |
|
|
|
**/ |
|
|
|
const mutatePopulation = function(population, mutatePercentage) |
|
|
|
{ |
|
|
|
if(population.length >= 2) |
|
|
@ -182,6 +241,11 @@ |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Introduces x random chromosomes to the population. |
|
|
|
* @param population |
|
|
|
* @param immigrationSize |
|
|
|
*/ |
|
|
|
const newBlood = function(population, immigrationSize) |
|
|
|
{ |
|
|
|
for(let i = 0; i < immigrationSize; i++) |
|
|
@ -192,15 +256,28 @@ |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
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) |
|
|
|
{ |
|
|
|
console.log(chromosome); |
|
|
|
console.log((chromosome.getGenes()[0].getRealValue())); |
|
|
|
return Math.abs(chromosome.getGenes()[0].getRealValue() - 6) + |
|
|
|
Math.abs(chromosome.getGenes()[1].getRealValue() - 2); |
|
|
|
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 = []; |
|
|
@ -212,18 +289,34 @@ |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 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); |
|
|
@ -240,21 +333,20 @@ |
|
|
|
|
|
|
|
generation++; |
|
|
|
console.log("Generation " + generation + " Best Cost: " + bestCost); |
|
|
|
|
|
|
|
console.log(generationResult); |
|
|
|
}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() |
|
|
|
const runGeneticOptimizationForGraph = function() |
|
|
|
{ |
|
|
|
let generationResult = naturalSelection(populationG, keepNumberG, costFunctionG); |
|
|
|
|
|
|
@ -416,7 +508,7 @@ |
|
|
|
{ |
|
|
|
if(autoRunning === true) |
|
|
|
{ |
|
|
|
runGeneticOptimizationforGraph(); |
|
|
|
runGeneticOptimizationForGraph(); |
|
|
|
setTimeout(runAutoOptimizer, 1000); |
|
|
|
} |
|
|
|
} |
|
|
@ -449,7 +541,7 @@ |
|
|
|
|
|
|
|
<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="runOptimizer" onclick='runGeneticOptimizationForGraph()' type="button" value="Next Generation"> |
|
|
|
<input class='btn btn-primary' id="runAutoOptimizer" onclick='startStopAutoRun()' type="button" value="Auto Run"> |
|
|
|
|
|
|
|
<div class="card"> |
|
|
|