UNPKG

genetic-search

Version:

Multiprocessing genetic algorithm implementation library

425 lines 16 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { normalizePhenomeMatrix, arrayBinaryOperation, arraySum, getRandomArrayItem } from "./utils"; import { sort, zip } from "./itertools"; /** * Base class for mutation strategies. * * @template TGenome The type of genome objects in the population. * @template TConfig The type of configuration for the mutation strategy. * * @category Strategies * @category Mutation */ export class BaseMutationStrategy { /** * Constructs a new instance of the mutation strategy. * * @param config The configuration for the mutation strategy. */ constructor(config) { this.config = config; } } /** * Base class for phenome strategies. * * @template TGenome The type of genome objects in the population. * @template TConfig The type of configuration for the phenome strategy. * @template TTaskConfig The type of configuration required to execute the task of the calculating phenome. * * @category Strategies * @category Phenome */ export class BasePhenomeStrategy { /** * Constructs a new instance of the phenome strategy. * * @param config The configuration for the phenome strategy. */ constructor(config) { this.config = config; } /** * Collects and caches the phenome for a given population of genomes. * * @param population The population of genomes to collect phenome for. * @param cache The cache used to store and retrieve phenomes. * @returns A promise that resolves to a matrix of phenomes for the generation. */ collect(population, cache) { return __awaiter(this, void 0, void 0, function* () { const pairs = population.map((genome) => [genome.id, cache.getReady(genome.id)]); const resultsMap = new Map(pairs); const genomesToRun = population.filter((genome) => resultsMap.get(genome.id) === undefined); const newResults = yield this.execTasks(genomesToRun.map((genome) => this.createTaskInput(genome))); for (const [genome, result] of zip(genomesToRun, newResults)) { cache.set(genome.id, result); } for (const [genome, result] of zip(genomesToRun, newResults)) { resultsMap.set(genome.id, cache.get(genome.id, result)); } return population.map((genome) => resultsMap.get(genome.id)); }); } /** * Executes the tasks to calculate the phenome of the given genomes. * * @param inputs The inputs for the tasks to execute. * @returns A promise that resolves to an array of phenome for each genome. */ execTasks(inputs) { return __awaiter(this, void 0, void 0, function* () { var _a, _b; const result = []; for (const input of inputs) { const taskResult = yield this.config.task(input); (_b = (_a = this.config).onTaskResult) === null || _b === void 0 ? void 0 : _b.call(_a, taskResult, input); result.push(taskResult); } return result; }); } } /** * A fitness strategy that calculates the fitness of a genome based on a reference loss. * * The fitness of a genome is calculated as the negative sum of the absolute differences between the reference loss * and the loss calculated for the genome. * * @category Strategies * @category Fitness */ export class ReferenceLossFitnessStrategy { /** * Constructor of the reference loss fitness strategy. * * @param referenceConfig The configuration for the reference loss fitness strategy. */ constructor(referenceConfig) { this.referenceConfig = referenceConfig; } score(results) { const normalizedLosses = this.formatLosses(results); return normalizedLosses.map((x) => -arraySum(x)); } /** * Formats the losses by normalizing the phenome matrix and applying weights to each row. * * @param results The generation phenome matrix to format. * @returns A matrix of formatted losses. */ formatLosses(results) { return normalizePhenomeMatrix(results, this.referenceConfig.reference).map((result) => this.weighRow(result)); } /** * Weighs a row of phenome by multiplying each element with its corresponding weight. * * @param result The genome phenome row to weigh. * @returns A genome phenome row with applied weights. */ weighRow(result) { return arrayBinaryOperation(result, this.referenceConfig.weights, (x, y) => x * y); } } /** * Sorts a given iterable of genomes, fitness scores, and phenome rows in ascending order of their fitness scores. * * @param input An iterable containing tuples of genomes, their fitness scores, and their associated phenome rows. * @returns An array of sorted tuples of genomes, fitness scores, and phenome rows. * * @category Strategies * @category Sorting */ export class AscendingSortingStrategy { /** * Sorts a given iterable of genomes, fitness scores, and phenome rows in ascending order of their fitness scores. * * @param input An iterable containing tuples of genomes, their fitness scores, and their associated phenome rows. * @returns An array of sorted tuples of genomes, fitness scores, and phenome rows. */ sort(input) { return [...sort(input, (lhs, rhs) => lhs.fitness - rhs.fitness)]; } } /** * Sorts a given iterable of genomes, fitness scores, and phenome rows in descending order of their fitness scores. * * @param input An iterable containing tuples of genomes, their fitness scores, and their associated phenome rows. * @returns An array of sorted tuples of genomes, fitness scores, and phenome rows. * * @category Strategies * @category Sorting */ export class DescendingSortingStrategy { /** * Sorts a given iterable of genomes, fitness scores, and phenome rows in descending order of their fitness scores. * * @param input An iterable containing tuples of genomes, their fitness scores, and their associated phenome rows. * @returns An array of sorted tuples of genomes, fitness scores, and phenome rows. */ sort(input) { return [...sort(input, (lhs, rhs) => rhs.fitness - lhs.fitness)]; } } /** * A random selection strategy. * * This selection strategy randomly selects parents for mutation and crossover. * * @template TGenome The type of genome objects in the population. * * @category Strategies * @category Selection */ export class RandomSelectionStrategy { /** * Constructor of the random selection strategy. * * @param crossoverParentsCount The number of parents to select for crossover. */ constructor(crossoverParentsCount) { this.crossoverParentsCount = crossoverParentsCount; } /** * Selects parents for crossover. * * @param input The population extended with fitness scores and phenome to select parents from. * @param count The number of parents to select. * @returns An array of parents arrays. */ selectForCrossover(input, count) { const result = []; for (let i = 0; i < count; i++) { const parents = []; for (let j = 0; j < this.crossoverParentsCount; j++) { parents.push(getRandomArrayItem(input).genome); } result.push(parents); } return result; } /** * Selects parents for mutation. * * @param input The population extended with fitness scores and phenome to select parents from. * @param count The number of parents to select. * @returns An array of parents. */ selectForMutation(input, count) { const result = []; for (let i = 0; i < count; i++) { result.push(getRandomArrayItem(input).genome); } return result; } } /** * A truncation selection strategy. * * This selection strategy selects the top `sliceThresholdRate` fraction of the population * as parents for mutation and crossover. * * @template TGenome The type of genome objects in the population. * * @category Strategies * @category Selection */ export class TruncationSelectionStrategy extends RandomSelectionStrategy { /** * Constructor of the truncation selection strategy. * * @param crossoverParentsCount The number of parents to select for crossover. * @param sliceThresholdRate The rate at which to slice the population. */ constructor(crossoverParentsCount, sliceThresholdRate) { super(crossoverParentsCount); this.sliceThresholdRate = sliceThresholdRate; } /** * Selects parents for crossover. * * @param input The population extended with fitness scores and phenome to select parents from. * @param count The number of parents to select. * @returns An array of parents arrays. */ selectForCrossover(input, count) { if (this.sliceThresholdRate) { input = input.slice(0, input.length * this.sliceThresholdRate); } return super.selectForCrossover(input, count); } /** * Selects parents for mutation. * * @param input The population extended with fitness scores and phenome to select parents from. * @param count The number of parents to select. * @returns An array of parents. */ selectForMutation(input, count) { if (this.sliceThresholdRate) { input = input.slice(0, input.length * this.sliceThresholdRate); } return super.selectForMutation(input, count); } } /** * A selection strategy that uses a tournament to select parents. * * This selection strategy runs a tournament between random participants from the population, * and selects the best participant as a parent. * * @template TGenome The type of genome objects in the population. * * @category Strategies * @category Selection */ export class TournamentSelectionStrategy { /** * Constructor of the tournament selection strategy. * * @param crossoverParentsCount The number of parents to select for crossover. * @param tournamentSize The number of participants in a tournament. */ constructor(crossoverParentsCount, tournamentSize) { this.crossoverParentsCount = crossoverParentsCount; this.tournamentSize = tournamentSize; } /** * Selects parents for crossover. * * @param input The population extended with fitness scores and phenome to select parents from. * @param count The number of parents to select. * @returns An array of parents arrays. */ selectForCrossover(input, count) { const result = []; for (let i = 0; i < count; i++) { const parents = []; for (let j = 0; j < this.crossoverParentsCount; j++) { // Select the best from the tournament parents.push(this.runTournament(input).genome); } result.push(parents); } return result; } /** * Selects parents for mutation. * * @param input The population extended with fitness scores and phenome to select parents from. * @param count The number of parents to select. * @returns An array of parents. */ selectForMutation(input, count) { const result = []; for (let i = 0; i < count; i++) { // Select the best from the tournament result.push(this.runTournament(input).genome); } return result; } /** * Conducts a tournament and returns the best participant. * * @param input The population. * @returns The best `EvaluatedGenome` from the tournament. */ runTournament(input) { // Select random participants const tournamentParticipants = []; for (let k = 0; k < this.tournamentSize; k++) { tournamentParticipants.push(getRandomArrayItem(input)); } // Sort participants and select the best tournamentParticipants.sort((lhs, rhs) => rhs.fitness - lhs.fitness); return tournamentParticipants[0]; // Best participant } } /** * A proportional selection strategy. * * This selection strategy selects parents for mutation and crossover based on their fitness proportion. * * @template TGenome The type of genome objects in the population. * * @category Strategies * @category Selection */ export class ProportionalSelectionStrategy { /** * Constructor of the proportional selection strategy. * * @param crossoverParentsCount The number of parents to select for crossover. */ constructor(crossoverParentsCount) { this.crossoverParentsCount = crossoverParentsCount; } /** * Selects parents for crossover using proportional selection. * * @param input The population extended with fitness scores and phenome to select parents from. * @param count The number of parents to select. * @returns An array of parents arrays. */ selectForCrossover(input, count) { const result = []; // Calculate total fitness of the population const totalFitness = input.reduce((acc, evaluatedGenome) => acc + evaluatedGenome.fitness, 0); for (let i = 0; i < count; i++) { const parents = []; for (let j = 0; j < this.crossoverParentsCount; j++) { // Roulette wheel selection based on fitness proportion const selectedGenome = this.selectByFitness(input, totalFitness); parents.push(selectedGenome.genome); } result.push(parents); } return result; } /** * Selects parents for mutation using proportional selection. * * @param input The population extended with fitness scores and phenome to select parents from. * @param count The number of parents to select. * @returns An array of parents. */ selectForMutation(input, count) { const result = []; // Calculate total fitness of the population const totalFitness = input.reduce((acc, evaluatedGenome) => acc + evaluatedGenome.fitness, 0); for (let i = 0; i < count; i++) { // Roulette wheel selection based on fitness proportion const selectedGenome = this.selectByFitness(input, totalFitness); result.push(selectedGenome.genome); } return result; } /** * Helper method to select an individual based on fitness proportion. * * @param input The population. * @param totalFitness The sum of all fitness scores in the population. * @returns A selected `EvaluatedGenome`. */ selectByFitness(input, totalFitness) { let randomValue = Math.random() * totalFitness; let result = input[input.length - 1]; // Fallback value in case of floating-point imprecisions for (const evaluatedGenome of input) { randomValue -= evaluatedGenome.fitness; if (randomValue <= 0) { result = evaluatedGenome; break; } } return result; } } //# sourceMappingURL=strategies.js.map