population.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. import {math} from "./math.js";
  2. export const population = (function() {
  3. return {
  4. Population: class {
  5. constructor(params) {
  6. this._params = params;
  7. this._population = [...Array(this._params.population_size)].map(
  8. _ => ({fitness: 1, genotype: this._CreateRandomGenotype()}));
  9. this._lastGeneration = null;
  10. this._generations = 0;
  11. }
  12. _CreateRandomGenotype() {
  13. return [...Array(this._params.genotype.size)].map(
  14. _ => Math.random() * 2 - 1);
  15. }
  16. Fittest() {
  17. return this._lastGeneration.parents[0];
  18. }
  19. Step(tgtImgData) {
  20. const parents = this._population.sort(
  21. (a, b) => (b.fitness - a.fitness));
  22. this._lastGeneration = {parents: parents};
  23. this._generations += 1;
  24. this._population = this._BreedNewPopulation(parents);
  25. }
  26. _BreedNewPopulation(parents) {
  27. function _RouletteSelection(sortedParents, totalFitness) {
  28. const roll = Math.random() * totalFitness;
  29. let sum = 0;
  30. for (let p of sortedParents) {
  31. sum += p.fitness;
  32. if (roll < sum) {
  33. return p;
  34. }
  35. }
  36. return sortedParents[sortedParents.length - 1];
  37. }
  38. function _RandomParent(sortedParents, otherParent, totalFitness) {
  39. const p = _RouletteSelection(sortedParents, totalFitness);
  40. return p;
  41. }
  42. function _CopyGenotype(g) {
  43. return ({
  44. fitness: g.fitness,
  45. genotype: [...g.genotype],
  46. });
  47. }
  48. const newPopulation = [];
  49. const totalFitness = parents.reduce((t, p) => t + p.fitness, 0);
  50. const numChildren = Math.ceil(
  51. parents.length * this._params.breed.childrenPercentage);
  52. const top = [...parents.slice(0, Math.ceil(
  53. parents.length * this._params.breed.selectionCutoff))];
  54. for (let j = 0; j < numChildren; j++) {
  55. const i = j % top.length;
  56. const p1 = top[i];
  57. const p2 = _RandomParent(parents, p1, totalFitness);
  58. const index = Math.round(Math.random() * p1.genotype.length);
  59. const g = p1.genotype.slice(0, index).concat(
  60. p2.genotype.slice(index));
  61. newPopulation.push(_CopyGenotype({fitness: 1, genotype: g}));
  62. }
  63. // Let's say keep top X% go through, but with mutations
  64. const topX = [...parents.slice(0, Math.ceil(
  65. parents.length * this._params.breed.immortalityCutoff))];
  66. newPopulation.push(...topX.map(x => _CopyGenotype(x)));
  67. // Mutations!
  68. for (let p of newPopulation) {
  69. const genotypeLength = p.genotype.length;
  70. const mutationOdds = this._params.mutation.odds;
  71. const mutationMagnitude = this._params.mutation.magnitude;
  72. function _Mutate(x) {
  73. const roll = Math.random();
  74. if (roll < mutationOdds) {
  75. const magnitude = mutationMagnitude * math.rand_normalish();
  76. return x + magnitude;
  77. }
  78. return x;
  79. }
  80. p.genotype = p.genotype.map(g => _Mutate(g));
  81. }
  82. // Immortality granted to the winners from the last life.
  83. // May the odds be forever in your favour.
  84. newPopulation.push(...topX.map(x => _CopyGenotype(x)));
  85. // Create a bunch of random crap to fill out the rest.
  86. while (newPopulation.length < parents.length) {
  87. newPopulation.push(
  88. {fitness: 1, genotype: this._CreateRandomGenotype()});
  89. }
  90. return newPopulation;
  91. }
  92. },
  93. };
  94. })();