Repository where I mostly put random python scripts.
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.

578 lines
19 KiB

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