UNPKG

population

Version:

Genetic algorithm

206 lines (172 loc) 6.43 kB
const _ = require('lodash'); const Debug = require('debug'); const debugs = {}; function debug(message, base = 'default') { if (!(base in debugs)) { debugs[base] = base === 'default' ? Debug(`population`) : Debug(`population:${base}`); debugs[base].log = console.log.bind(console); } debugs[base](message); } module.exports = function genetic(options = {}) { options = _.defaultsDeep(options, { eliteRatio: 0.1, newBloodRatio: 0.05, mutationProbability: 0.5, targetFitness: null, targetFitDidntChange: 50000, targetGeneration: 50000, population: [], mutator: () => {}, crossover: () => {}, getFitness: () => {}, attachAllFitness: null, orderByFitness, beforeFitnessCalculated: population => population, afterFitnessCalculated: population => population, hash: () => {}, eliteUniqHash: () => {}, getRandomMember: () => {}, onBestFitness: () => {}, onGenerationBestFitness: () => {} }); let generation = 0; const originalPopulationLength = options.population.length; const eliteRatio = options.eliteRatio; const eliteCount = _.floor(options.population.length * eliteRatio); const newBloodRatio = options.newBloodRatio; const newBloodCount = _.floor(options.population.length * newBloodRatio); const offspringCount = options.population.length - eliteCount - newBloodCount; let lastBestFitness = 0; let bestFitnessEver = null; let fitNotChanged = 0; debug(`Population size: ${options.population.length}`, 'debug'); debug(`Elite size: ${eliteCount}`, 'debug'); debug(`Offspring size: ${offspringCount}`, 'debug'); debug(`New blood size: ${newBloodCount}`, 'debug'); return Object.freeze({ run }); async function run(population = options.population, recursive = false) { debug(`Population count: ${population.length}`, 'debug'); population = await options.beforeFitnessCalculated(population); population = await attachFitness(population); population = await options.afterFitnessCalculated(population); debug(`After afterFitnessCalculated called: ${population.length}`, 'debug'); population = options.orderByFitness(population); population = attachRank(population); population.map(member => { debug(`${_.round(member.fitness, 6)} ${member.rank} ${member.serialized}`, 'silly'); }); const bestFitness = _.map(population, 'fitness')[0]; options.onGenerationBestFitness(bestFitness); if (lastBestFitness !== bestFitness || !recursive) { fitNotChanged = 0; } else { fitNotChanged++; } lastBestFitness = bestFitness; if (bestFitnessEver === null || bestFitnessEver < bestFitness) { options.onBestFitness(population[0]); bestFitnessEver = bestFitness; } debug(`Generation: ${generation + 1}, Best fitness: ${bestFitness}, Fit not changed: ${fitNotChanged}`); if (stopCondition(population)) { return population; } else { const newPopulation = evolve(population); return run(newPopulation, true); } } function stopCondition(population) { if (options.targetFitness !== null && _.map(population, 'fitness')[0] >= options.targetFitness) { debug(`Awesome fitness found, better than ${options.targetFitness}`); return true; } else if (fitNotChanged >= options.targetFitDidntChange) { debug(`Fit didn't change for ${options.targetFitDidntChange} generation.`); return true; } else if (++generation >= options.targetGeneration) { debug(`Reached ${options.targetGeneration} generation.`); return true; } } function evolve(population) { const parents = selectParentPairs(population); const offspringPopulation = generateAllOffspring(parents); const mutatedPopulation = mutatePopulation(offspringPopulation); const newPopulation = mixPopulations(population, mutatedPopulation); newPopulation.map(member => { debug(`${member.serialized}`, 'silly'); }); const uniqNewPopulation = makePopulationUniq(newPopulation); const newBloodPopulation = _.concat(uniqNewPopulation, _.times(originalPopulationLength - uniqNewPopulation.length, options.getRandomMember)); return newBloodPopulation; } function attachFitness(population) { if (_.isFunction(options.attachAllFitness)) { return options.attachAllFitness(population); } else { return Promise.all(population.map(async (member) => { member.fitness = await options.getFitness(member); return member; })); } } function orderByFitness(population) { return _.orderBy(population, ['fitness'], 'desc'); } function attachRank(population) { let maxRank = population.length; return population.map(member => { member.rank = maxRank--; return member; }); } function selectParentPairs(population) { const weightedPopulation = createWeightedPopulation(population); return _.times(offspringCount, () => selectParents(weightedPopulation)) } function selectParents(weightedPopulation) { return [_.sample(weightedPopulation), _.sample(weightedPopulation)]; } function createWeightedPopulation(population) { let weightedPopulation = []; population.map(member => { _.times(member.rank, () => weightedPopulation.push(member)) }); return weightedPopulation; } function generateAllOffspring(allParents) { return allParents.map(generateOffspring); } function generateOffspring(parents) { return options.crossover(parents); } function mutatePopulation(population) { return population.map(member => { if (_.random(1, true) <= options.mutationProbability) { return options.mutator(member); } else { return member; } }); } function mixPopulations(oldPopulation, newPopulation) { const elite = getElite(oldPopulation); elite.map(member => { debug(`${_.round(member.fitness, 6)} ${member.rank} ${member.serialized}`, 'debug'); }); return _.concat(elite, newPopulation); } function getElite(population) { const eliteUniqPopulation = getUniqElite(population); return _.slice(eliteUniqPopulation, 0, eliteCount); } function makePopulationUniq(population) { return _.uniqBy(population, options.hash) } function getUniqElite(population) { return _.uniqBy(population, options.eliteUniqHash) } };