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.

483 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. fitnessArray.push({fit:fitness, chrom: population[i]});
  161. total+= fitness;
  162. }
  163. fitnessArray.sort(predicateBy("fit"));
  164. let survivors = [];
  165. let bestFitness = fitnessArray[0].fit;
  166. let bestChromosome = fitnessArray[0].chrom;
  167. for(let i = 0; i < keepNumber; i++)
  168. {
  169. survivors.push(fitnessArray[i].chrom);
  170. }
  171. return {average: total/population.length, survivors: survivors, bestFit: bestFitness, bestChrom: bestChromosome};
  172. };
  173. /**
  174. * Randomly everyone in the population
  175. *
  176. * @param population
  177. * @param desiredPopulationSize
  178. */
  179. const matePopulation = function(population, desiredPopulationSize)
  180. {
  181. const originalLength = population.length;
  182. while(population.length < desiredPopulationSize)
  183. {
  184. let index1 = Math.round(Math.random() * (originalLength-1));
  185. let index2 = Math.round(Math.random() * (originalLength-1));
  186. if(index1 !== index2)
  187. {
  188. const babies = breed(population[index1], population[index2]);
  189. population.push(babies[0]);
  190. population.push(babies[1]);
  191. }
  192. }
  193. };
  194. /**
  195. * Randomly mutates the population
  196. **/
  197. const mutatePopulation = function(population, mutatePercentage)
  198. {
  199. if(population.length >= 2)
  200. {
  201. let mutations = mutatePercentage *
  202. population.length *
  203. population[0].getGenes().length;
  204. for(let i = 0; i < mutations; i++)
  205. {
  206. population[i].mutate();
  207. }
  208. }
  209. else
  210. {
  211. console.log("Error, population too small to mutate");
  212. }
  213. };
  214. /**
  215. * Introduces x random chromosomes to the population.
  216. * @param population
  217. * @param immigrationSize
  218. */
  219. const newBlood = function(population, immigrationSize)
  220. {
  221. for(let i = 0; i < immigrationSize; i++)
  222. {
  223. let geneticChromosome = population[0];
  224. population.push(geneticChromosome.createRandomChromosome());
  225. }
  226. };
  227. let costx = Math.random() * 10;
  228. let costy = Math.random() * 10;
  229. /** Defines the cost as the "distance" to a 2-d point.
  230. * @param chromosome
  231. * @returns {number}
  232. */
  233. const basicCostFunction = function(chromosome)
  234. {
  235. return Math.abs(chromosome.getGenes()[0].getRealValue() - costx) +
  236. Math.abs(chromosome.getGenes()[1].getRealValue() - costy);
  237. };
  238. /**
  239. * Creates a totally random population based on a desired size
  240. * and a prototypical chromosome.
  241. *
  242. * @param geneticChromosome
  243. * @param populationSize
  244. * @returns {Array}
  245. */
  246. const createRandomPopulation = function(geneticChromosome, populationSize)
  247. {
  248. let population = [];
  249. for(let i = 0; i < populationSize; i++)
  250. {
  251. population.push(geneticChromosome.createRandomChromosome());
  252. }
  253. return population;
  254. };
  255. /**
  256. * Runs the genetic algorithm by going through the processes of
  257. * natural selection, mutation, mating, and immigrations. This
  258. * process will continue until an adequately performing chromosome
  259. * is found or a generation threshold is passed.
  260. *
  261. * @param geneticChromosome Prototypical chromosome: used so algo knows
  262. * what the dna of the population looks like.
  263. * @param costFunction Function which defines how bad a Chromosome is
  264. * @param populationSize Desired population size for population
  265. * @param maxGenerations Cut off level for number of generations to run
  266. * @param desiredCost Sufficient cost to terminate program at
  267. * @param mutationRate Number between [0,1] representing proportion of genes
  268. * to mutate each generation
  269. * @param keepNumber Number of Organisms which survive each generation
  270. * @param newBloodNumber Number of random immigrants to introduce into
  271. * the population each generation.
  272. * @returns {*}
  273. */
  274. const runGeneticOptimization = function(geneticChromosome, costFunction,
  275. populationSize, maxGenerations,
  276. desiredCost, mutationRate, keepNumber,
  277. newBloodNumber)
  278. {
  279. let population = createRandomPopulation(geneticChromosome, populationSize);
  280. let generation = 0;
  281. let bestCost = Number.MAX_VALUE;
  282. let bestChromosome = geneticChromosome;
  283. do
  284. {
  285. matePopulation(population, populationSize);
  286. newBlood(population, newBloodNumber);
  287. mutatePopulation(population, mutationRate);
  288. let generationResult = naturalSelection(population, keepNumber, costFunction);
  289. if(bestCost > generationResult.bestFit)
  290. {
  291. bestChromosome = generationResult.bestChrom;
  292. bestCost = generationResult.bestFit;
  293. }
  294. population = generationResult.survivors;
  295. generation++;
  296. console.log("Generation " + generation + " Best Cost: " + bestCost);
  297. }while(generation < maxGenerations && bestCost > desiredCost);
  298. return bestChromosome;
  299. };
  300. /**
  301. * Ugly globals used to keep track of population state for the graph.
  302. */
  303. let genericChromosomeG, costFunctionG,
  304. populationSizeG, maxGenerationsG,
  305. desiredCostG, mutationRateG, keepNumberG,
  306. newBloodNumberG, populationG, generationG,
  307. bestCostG = Number.MAX_VALUE, bestChromosomeG = genericChromosomeG;
  308. const runGeneticOptimizationForGraph = function()
  309. {
  310. let generationResult = naturalSelection(populationG, keepNumberG, costFunctionG);
  311. stats.push([generationG, generationResult.bestFit, generationResult.average]);
  312. if(bestCostG > generationResult.bestFit)
  313. {
  314. bestChromosomeG = generationResult.bestChrom;
  315. bestCostG = generationResult.bestFit;
  316. }
  317. populationG = generationResult.survivors;
  318. generationG++;
  319. console.log("Generation " + generationG + " Best Cost: " + bestCostG);
  320. console.log(generationResult);
  321. matePopulation(populationG, populationSizeG);
  322. newBlood(populationG, newBloodNumberG);
  323. mutatePopulation(populationG, mutationRateG);
  324. createGraph();
  325. };
  326. let stats = [];
  327. const createGraph = function()
  328. {
  329. var dataPoints = [];
  330. console.log(dataPoints);
  331. var data = new google.visualization.DataTable();
  332. data.addColumn('number', 'Gene 1');
  333. data.addColumn('number', 'Gene 2');
  334. for(let i = 0; i < populationG.length; i++)
  335. {
  336. data.addRow([populationG[i].getGenes()[0].getRealValue(),
  337. populationG[i].getGenes()[1].getRealValue()]);
  338. }
  339. var options = {
  340. title: 'Genetic Evolution On Two Genes Generation: ' + generationG,
  341. hAxis: {title: 'Gene 1', minValue: 0, maxValue: 10},
  342. vAxis: {title: 'Gene 2', minValue: 0, maxValue: 10},
  343. };
  344. var chart = new google.visualization.ScatterChart(document.getElementById('chart_div'));
  345. chart.draw(data, options);
  346. //line chart stuff
  347. var line_data = new google.visualization.DataTable();
  348. line_data.addColumn('number', 'Generation');
  349. line_data.addColumn('number', 'Best');
  350. line_data.addColumn('number', 'Average');
  351. line_data.addRows(stats);
  352. console.log(stats);
  353. var lineChartOptions = {
  354. hAxis: {
  355. title: 'Generation'
  356. },
  357. vAxis: {
  358. title: 'Cost'
  359. },
  360. colors: ['#AB0D06', '#007329']
  361. };
  362. var chart = new google.visualization.LineChart(document.getElementById('line_chart'));
  363. chart.draw(line_data, lineChartOptions);
  364. };
  365. let gene1 = new Gene(1,10,10);
  366. let gene2 = new Gene(1,10,0.4);
  367. let geneList = [gene1, gene2];
  368. let exampleOrganism = new Chromosome(geneList);
  369. genericChromosomeG = exampleOrganism;
  370. costFunctionG = basicCostFunction;
  371. populationSizeG = 100;
  372. maxGenerationsG = 30;
  373. desiredCostG = 0.00001;
  374. mutationRateG = 0.3;
  375. keepNumberG = 30;
  376. newBloodNumberG = 10;
  377. generationG = 0;
  378. function verifyForm()
  379. {
  380. if(Number($("#populationSize").val()) <= 1)
  381. {
  382. alert("Population size must be greater than one.");
  383. return false;
  384. }
  385. if(Number($("#mutationRate").val()) > 1 ||
  386. Number($("#mutationRate").val()) < 0)
  387. {
  388. alert("Mutation rate must be between zero and one.");
  389. return false;
  390. }
  391. if(Number($("#survivalSize").val()) < 0)
  392. {
  393. alert("Survival size can't be less than one.");
  394. return false;
  395. }
  396. if(Number($("#newBlood").val()) < 0)
  397. {
  398. alert("New organisms can't be a negative number.");
  399. return false;
  400. }
  401. return true;
  402. }
  403. function resetPopulation()
  404. {
  405. if(verifyForm())
  406. {
  407. stats = [];
  408. autoRunning = false;
  409. $("#runAutoOptimizer").val("Auto Run");
  410. populationSizeG = $("#populationSize").val();
  411. mutationRateG = $("#mutationRate").val();
  412. keepNumberG = $("#survivalSize").val();
  413. newBloodNumberG = $("#newBlood").val();
  414. generationG = 0;
  415. populationG = createRandomPopulation(genericChromosomeG, populationSizeG);
  416. createGraph();
  417. }
  418. }
  419. populationG = createRandomPopulation(genericChromosomeG, populationSizeG);
  420. window.onload = function (){
  421. google.charts.load('current', {packages: ['corechart', 'line']});
  422. google.charts.load('current', {'packages':['corechart']}).then(function()
  423. {
  424. createGraph();
  425. })
  426. };
  427. let autoRunning = false;
  428. function runAutoOptimizer()
  429. {
  430. if(autoRunning === true)
  431. {
  432. runGeneticOptimizationForGraph();
  433. setTimeout(runAutoOptimizer, 1000);
  434. }
  435. }
  436. function startStopAutoRun()
  437. {
  438. autoRunning = !autoRunning;
  439. if(autoRunning)
  440. {
  441. $("#runAutoOptimizer").val("Stop Auto Run");
  442. }
  443. else
  444. {
  445. $("#runAutoOptimizer").val("Resume Auto Run");
  446. }
  447. runAutoOptimizer();
  448. }
  449. </script>
  450. <div id="chart_div"></div>
  451. <div id="line_chart"></div>
  452. <input class='btn btn-primary' id="runOptimizer" onclick='runGeneticOptimizationForGraph()' type="button" value="Next Generation">
  453. <input class='btn btn-primary' id="runAutoOptimizer" onclick='startStopAutoRun()' type="button" value="Auto Run">
  454. <br>
  455. <br>
  456. <div class="card">
  457. <div class="card-header">
  458. <h2>Population Variables</h2>
  459. </div>
  460. <form class="card-body">
  461. <div class="row p-2">
  462. <div class="col">
  463. <label for="populationSize">Population Size</label>
  464. <input type="text" class="form-control" value="100" id="populationSize" placeholder="Population Size" required>
  465. </div>
  466. <div class="col">
  467. <label for="populationSize">Survival Size</label>
  468. <input type="text" class="form-control" value="20" id="survivalSize" placeholder="Survival Size" required>
  469. </div>
  470. </div>
  471. <div class="row p-2">
  472. <div class="col">
  473. <label for="populationSize">Mutation Rate</label>
  474. <input type="text" class="form-control" value="0.03" id="mutationRate" placeholder="Mutation Rate" required>
  475. </div>
  476. <div class="col">
  477. <label for="populationSize">New Organisms Per Generation</label>
  478. <input type="text" class="form-control" value="5" id="newBlood" placeholder="New Organisms" required>
  479. </div>
  480. </div>
  481. <br>
  482. <input class='btn btn-primary' id="reset" onclick='resetPopulation()' type="button" value="Reset Population">
  483. </form>
  484. </div>