Personal blog written from scratch using Node.js, Bootstrap, and MySQL. https://jrtechs.net
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

484 lines
16 KiB

  1. <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
  2. <script
  3. src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
  4. integrity="sha256-3edrmyuQ0w65f8gfBsqowzjJe2iM6n0nKciPUp8y+7E="
  5. crossorigin="anonymous">
  6. </script>
  7. <script>
  8. class Gene
  9. {
  10. /**
  11. * Constructs a new Gene to store in a chromosome.
  12. * @param min minimum value that this gene can store
  13. * @param max value this gene can possibly be
  14. * @param value normalized value
  15. */
  16. constructor(min, max, value)
  17. {
  18. this.min = min;
  19. this.max = max;
  20. this.value = value;
  21. }
  22. /**
  23. * De-normalizes the value of the gene
  24. * @returns {*}
  25. */
  26. getRealValue()
  27. {
  28. return (this.max - this.min) * this.value + this.min;
  29. }
  30. getValue()
  31. {
  32. return this.value;
  33. }
  34. setValue(val)
  35. {
  36. this.value = val;
  37. }
  38. makeClone()
  39. {
  40. return new Gene(this.min, this.max, this.value);
  41. }
  42. makeRandomGene()
  43. {
  44. return new Gene(this.min, this.max, Math.random());
  45. }
  46. }
  47. class Chromosome
  48. {
  49. /**
  50. * Constructs a chromosome by making a copy of
  51. * a list of genes.
  52. * @param geneArray
  53. */
  54. constructor(geneArray)
  55. {
  56. this.genes = [];
  57. for(let i = 0; i < geneArray.length; i++)
  58. {
  59. this.genes.push(geneArray[i].makeClone());
  60. }
  61. }
  62. getGenes()
  63. {
  64. return this.genes;
  65. }
  66. /**
  67. * Mutates a random gene.
  68. */
  69. mutate()
  70. {
  71. this.genes[Math.round(Math.random() * (this.genes.length-1))].setValue(Math.random());
  72. }
  73. /**
  74. * Creates a totally new chromosome with same
  75. * genetic structure as this chromosome but different
  76. * values.
  77. * @returns {Chromosome}
  78. */
  79. createRandomChromosome()
  80. {
  81. let geneAr = [];
  82. for(let i = 0; i < this.genes.length; i++)
  83. {
  84. geneAr.push(this.genes[i].makeRandomGene());
  85. }
  86. return new Chromosome(geneAr);
  87. }
  88. }
  89. /**
  90. * Mates two chromosomes using the blending method
  91. * and returns a list of 2 offspring.
  92. * @param father
  93. * @param mother
  94. * @returns {Chromosome[]}
  95. */
  96. const breed = function(father, mother)
  97. {
  98. let son = new Chromosome(father.getGenes());
  99. let daughter = new Chromosome(mother.getGenes());
  100. for(let i = 0;i < son.getGenes().length; i++)
  101. {
  102. let blendCoef = Math.random();
  103. blendGene(son.getGenes()[i], daughter.getGenes()[i], blendCoef);
  104. }
  105. return [son, daughter];
  106. };
  107. /**
  108. * Blends two genes together based on a random blend
  109. * coefficient.
  110. **/
  111. const blendGene = function(gene1, gene2, blendCoef)
  112. {
  113. let value1 = (blendCoef * gene1.getValue()) +
  114. (gene2.getValue() * (1- blendCoef));
  115. let value2 = ((1-blendCoef) * gene1.getValue()) +
  116. (gene2.getValue() * blendCoef);
  117. gene1.setValue(value1);
  118. gene2.setValue(value2);
  119. };
  120. /**
  121. * Helper function to sort an array
  122. *
  123. * @param prop name of JSON property to sort by
  124. * @returns {function(*, *): number}
  125. */
  126. function predicateBy(prop)
  127. {
  128. return function(a,b)
  129. {
  130. var result;
  131. if(a[prop] > b[prop])
  132. {
  133. result = 1;
  134. }
  135. else if(a[prop] < b[prop])
  136. {
  137. result = -1;
  138. }
  139. return result;
  140. }
  141. }
  142. /**
  143. * Function which computes the fitness of everyone in the
  144. * population and returns the most fit survivors. Method
  145. * known as elitism.
  146. *
  147. * @param population
  148. * @param keepNumber
  149. * @param fitnessFunction
  150. * @returns {{average: number,
  151. * survivors: Array, bestFit: Chromosome }}
  152. */
  153. const naturalSelection = function(population, keepNumber, fitnessFunction)
  154. {
  155. let fitnessArray = [];
  156. let total = 0;
  157. for(let i = 0; i < population.length; i++)
  158. {
  159. const fitness = fitnessFunction(population[i]);
  160. console.log(fitness);
  161. fitnessArray.push({fit:fitness, chrom: population[i]});
  162. total+= fitness;
  163. }
  164. fitnessArray.sort(predicateBy("fit"));
  165. let survivors = [];
  166. let bestFitness = fitnessArray[0].fit;
  167. let bestChromosome = fitnessArray[0].chrom;
  168. for(let i = 0; i < keepNumber; i++)
  169. {
  170. survivors.push(fitnessArray[i].chrom);
  171. }
  172. return {average: total/population.length, survivors: survivors, bestFit: bestFitness, bestChrom: bestChromosome};
  173. };
  174. /**
  175. * Randomly everyone in the population
  176. *
  177. * @param population
  178. * @param desiredPopulationSize
  179. */
  180. const matePopulation = function(population, desiredPopulationSize)
  181. {
  182. const originalLength = population.length;
  183. while(population.length < desiredPopulationSize)
  184. {
  185. let index1 = Math.round(Math.random() * (originalLength-1));
  186. let index2 = Math.round(Math.random() * (originalLength-1));
  187. if(index1 !== index2)
  188. {
  189. const babies = breed(population[index1], population[index2]);
  190. population.push(babies[0]);
  191. population.push(babies[1]);
  192. }
  193. }
  194. };
  195. /**
  196. * Randomly mutates the population
  197. **/
  198. const mutatePopulation = function(population, mutatePercentage)
  199. {
  200. if(population.length >= 2)
  201. {
  202. let mutations = mutatePercentage *
  203. population.length *
  204. population[0].getGenes().length;
  205. for(let i = 0; i < mutations; i++)
  206. {
  207. population[i].mutate();
  208. }
  209. }
  210. else
  211. {
  212. console.log("Error, population too small to mutate");
  213. }
  214. };
  215. /**
  216. * Introduces x random chromosomes to the population.
  217. * @param population
  218. * @param immigrationSize
  219. */
  220. const newBlood = function(population, immigrationSize)
  221. {
  222. for(let i = 0; i < immigrationSize; i++)
  223. {
  224. let geneticChromosome = population[0];
  225. population.push(geneticChromosome.createRandomChromosome());
  226. }
  227. };
  228. let costx = Math.random() * 10;
  229. let costy = Math.random() * 10;
  230. /** Defines the cost as the "distance" to a 2-d point.
  231. * @param chromosome
  232. * @returns {number}
  233. */
  234. const basicCostFunction = function(chromosome)
  235. {
  236. return Math.abs(chromosome.getGenes()[0].getRealValue() - costx) +
  237. Math.abs(chromosome.getGenes()[1].getRealValue() - costy);
  238. };
  239. /**
  240. * Creates a totally random population based on a desired size
  241. * and a prototypical chromosome.
  242. *
  243. * @param geneticChromosome
  244. * @param populationSize
  245. * @returns {Array}
  246. */
  247. const createRandomPopulation = function(geneticChromosome, populationSize)
  248. {
  249. let population = [];
  250. for(let i = 0; i < populationSize; i++)
  251. {
  252. population.push(geneticChromosome.createRandomChromosome());
  253. }
  254. return population;
  255. };
  256. /**
  257. * Runs the genetic algorithm by going through the processes of
  258. * natural selection, mutation, mating, and immigrations. This
  259. * process will continue until an adequately performing chromosome
  260. * is found or a generation threshold is passed.
  261. *
  262. * @param geneticChromosome Prototypical chromosome: used so algo knows
  263. * what the dna of the population looks like.
  264. * @param costFunction Function which defines how bad a Chromosome is
  265. * @param populationSize Desired population size for population
  266. * @param maxGenerations Cut off level for number of generations to run
  267. * @param desiredCost Sufficient cost to terminate program at
  268. * @param mutationRate Number between [0,1] representing proportion of genes
  269. * to mutate each generation
  270. * @param keepNumber Number of Organisms which survive each generation
  271. * @param newBloodNumber Number of random immigrants to introduce into
  272. * the population each generation.
  273. * @returns {*}
  274. */
  275. const runGeneticOptimization = function(geneticChromosome, costFunction,
  276. populationSize, maxGenerations,
  277. desiredCost, mutationRate, keepNumber,
  278. newBloodNumber)
  279. {
  280. let population = createRandomPopulation(geneticChromosome, populationSize);
  281. let generation = 0;
  282. let bestCost = Number.MAX_VALUE;
  283. let bestChromosome = geneticChromosome;
  284. do
  285. {
  286. matePopulation(population, populationSize);
  287. newBlood(population, newBloodNumber);
  288. mutatePopulation(population, mutationRate);
  289. let generationResult = naturalSelection(population, keepNumber, costFunction);
  290. if(bestCost > generationResult.bestFit)
  291. {
  292. bestChromosome = generationResult.bestChrom;
  293. bestCost = generationResult.bestFit;
  294. }
  295. population = generationResult.survivors;
  296. generation++;
  297. console.log("Generation " + generation + " Best Cost: " + bestCost);
  298. }while(generation < maxGenerations && bestCost > desiredCost);
  299. return bestChromosome;
  300. };
  301. /**
  302. * Ugly globals used to keep track of population state for the graph.
  303. */
  304. let genericChromosomeG, costFunctionG,
  305. populationSizeG, maxGenerationsG,
  306. desiredCostG, mutationRateG, keepNumberG,
  307. newBloodNumberG, populationG, generationG,
  308. bestCostG = Number.MAX_VALUE, bestChromosomeG = genericChromosomeG;
  309. const runGeneticOptimizationForGraph = function()
  310. {
  311. let generationResult = naturalSelection(populationG, keepNumberG, costFunctionG);
  312. stats.push([generationG, generationResult.bestFit, generationResult.average]);
  313. if(bestCostG > generationResult.bestFit)
  314. {
  315. bestChromosomeG = generationResult.bestChrom;
  316. bestCostG = generationResult.bestFit;
  317. }
  318. populationG = generationResult.survivors;
  319. generationG++;
  320. console.log("Generation " + generationG + " Best Cost: " + bestCostG);
  321. console.log(generationResult);
  322. matePopulation(populationG, populationSizeG);
  323. newBlood(populationG, newBloodNumberG);
  324. mutatePopulation(populationG, mutationRateG);
  325. createGraph();
  326. };
  327. let stats = [];
  328. const createGraph = function()
  329. {
  330. var dataPoints = [];
  331. console.log(dataPoints);
  332. var data = new google.visualization.DataTable();
  333. data.addColumn('number', 'Gene 1');
  334. data.addColumn('number', 'Gene 2');
  335. for(let i = 0; i < populationG.length; i++)
  336. {
  337. data.addRow([populationG[i].getGenes()[0].getRealValue(),
  338. populationG[i].getGenes()[1].getRealValue()]);
  339. }
  340. var options = {
  341. title: 'Genetic Evolution On Two Genes Generation: ' + generationG,
  342. hAxis: {title: 'Gene 1', minValue: 0, maxValue: 10},
  343. vAxis: {title: 'Gene 2', minValue: 0, maxValue: 10},
  344. };
  345. var chart = new google.visualization.ScatterChart(document.getElementById('chart_div'));
  346. chart.draw(data, options);
  347. //line chart stuff
  348. var line_data = new google.visualization.DataTable();
  349. line_data.addColumn('number', 'Generation');
  350. line_data.addColumn('number', 'Best');
  351. line_data.addColumn('number', 'Average');
  352. line_data.addRows(stats);
  353. console.log(stats);
  354. var lineChartOptions = {
  355. hAxis: {
  356. title: 'Generation'
  357. },
  358. vAxis: {
  359. title: 'Cost'
  360. },
  361. colors: ['#AB0D06', '#007329']
  362. };
  363. var chart = new google.visualization.LineChart(document.getElementById('line_chart'));
  364. chart.draw(line_data, lineChartOptions);
  365. };
  366. let gene1 = new Gene(1,10,10);
  367. let gene2 = new Gene(1,10,0.4);
  368. let geneList = [gene1, gene2];
  369. let exampleOrganism = new Chromosome(geneList);
  370. genericChromosomeG = exampleOrganism;
  371. costFunctionG = basicCostFunction;
  372. populationSizeG = 100;
  373. maxGenerationsG = 30;
  374. desiredCostG = 0.00001;
  375. mutationRateG = 0.3;
  376. keepNumberG = 30;
  377. newBloodNumberG = 10;
  378. generationG = 0;
  379. function verifyForm()
  380. {
  381. if(Number($("#populationSize").val()) <= 1)
  382. {
  383. alert("Population size must be greater than one.");
  384. return false;
  385. }
  386. if(Number($("#mutationRate").val()) > 1 ||
  387. Number($("#mutationRate").val()) < 0)
  388. {
  389. alert("Mutation rate must be between zero and one.");
  390. return false;
  391. }
  392. if(Number($("#survivalSize").val()) < 0)
  393. {
  394. alert("Survival size can't be less than one.");
  395. return false;
  396. }
  397. if(Number($("#newBlood").val()) < 0)
  398. {
  399. alert("New organisms can't be a negative number.");
  400. return false;
  401. }
  402. return true;
  403. }
  404. function resetPopulation()
  405. {
  406. if(verifyForm())
  407. {
  408. stats = [];
  409. autoRunning = false;
  410. $("#runAutoOptimizer").val("Auto Run");
  411. populationSizeG = $("#populationSize").val();
  412. mutationRateG = $("#mutationRate").val();
  413. keepNumberG = $("#survivalSize").val();
  414. newBloodNumberG = $("#newBlood").val();
  415. generationG = 0;
  416. populationG = createRandomPopulation(genericChromosomeG, populationSizeG);
  417. createGraph();
  418. }
  419. }
  420. populationG = createRandomPopulation(genericChromosomeG, populationSizeG);
  421. window.onload = function (){
  422. google.charts.load('current', {packages: ['corechart', 'line']});
  423. google.charts.load('current', {'packages':['corechart']}).then(function()
  424. {
  425. createGraph();
  426. })
  427. };
  428. let autoRunning = false;
  429. function runAutoOptimizer()
  430. {
  431. if(autoRunning === true)
  432. {
  433. runGeneticOptimizationForGraph();
  434. setTimeout(runAutoOptimizer, 1000);
  435. }
  436. }
  437. function startStopAutoRun()
  438. {
  439. autoRunning = !autoRunning;
  440. if(autoRunning)
  441. {
  442. $("#runAutoOptimizer").val("Stop Auto Run");
  443. }
  444. else
  445. {
  446. $("#runAutoOptimizer").val("Resume Auto Run");
  447. }
  448. runAutoOptimizer();
  449. }
  450. </script>
  451. <div id="chart_div"></div>
  452. <div id="line_chart"></div>
  453. <input class='btn btn-primary' id="runOptimizer" onclick='runGeneticOptimizationForGraph()' type="button" value="Next Generation">
  454. <input class='btn btn-primary' id="runAutoOptimizer" onclick='startStopAutoRun()' type="button" value="Auto Run">
  455. <br>
  456. <br>
  457. <div class="card">
  458. <div class="card-header">
  459. <h2>Population Variables</h2>
  460. </div>
  461. <form class="card-body">
  462. <div class="row p-2">
  463. <div class="col">
  464. <label for="populationSize">Population Size</label>
  465. <input type="text" class="form-control" value="100" id="populationSize" placeholder="Population Size" required>
  466. </div>
  467. <div class="col">
  468. <label for="populationSize">Survival Size</label>
  469. <input type="text" class="form-control" value="20" id="survivalSize" placeholder="Survival Size" required>
  470. </div>
  471. </div>
  472. <div class="row p-2">
  473. <div class="col">
  474. <label for="populationSize">Mutation Rate</label>
  475. <input type="text" class="form-control" value="0.03" id="mutationRate" placeholder="Mutation Rate" required>
  476. </div>
  477. <div class="col">
  478. <label for="populationSize">New Organisms Per Generation</label>
  479. <input type="text" class="form-control" value="5" id="newBlood" placeholder="New Organisms" required>
  480. </div>
  481. </div>
  482. <br>
  483. <input class='btn btn-primary' id="reset" onclick='resetPopulation()' type="button" value="Reset Population">
  484. </form>
  485. </div>