UNPKG

genetic-search

Version:

Multiprocessing genetic algorithm implementation library

435 lines 13.5 kB
import { zip } from "./itertools"; /** * Generates unique identifiers for genomes. * * @template TGenome The type of genome objects in the population. * * @category Utils */ export class IdGenerator { constructor() { this.id = 1; } nextId() { return this.id++; } reset(population) { this.id = population.reduce((max, genome) => Math.max(max, genome.id), 0) + 1; } } /** * Manages an array of `T` objects. * * @template T The type of the objects to manage. */ export class ArrayManager { /** * Creates a new `ArrayManager` from an array of `T` objects. * * @param data The array of `T` objects to manage. */ constructor(data) { this._data = data; } /** * Updates elements of the managed array that match the given filter. * * @param filter A function that returns true if the element should be updated. * @param update A function that updates the element. * * @returns The updated items. */ update(filter, update) { let updated = []; for (const genome of this._data) { if (filter(genome)) { update(genome); updated.push(genome); } } return updated; } /** * Removes elements of the managed array that match the given filter and optionally sorts the rest of the array. * * @param filter A function that returns true if the element should be removed. * @param maxCount The maximum number of elements to remove. * @param order The order to sort the remaining elements. * * @returns The removed items. */ remove(filter, maxCount = Infinity, order = 'asc') { const buf = [...this._data]; if (order === 'desc') { buf.reverse(); } this._data.length = 0; let removed = []; for (const genome of buf) { if (filter(genome) && removed.length < maxCount) { removed.push(genome); } else { this._data.push(genome); } } if (order === 'desc') { this._data.reverse(); } return removed; } } /** * Creates a deep copy of an object. * * @template T The type of the object to copy. * @param obj The object to copy. * @returns A deep copy of the object. * * @category Utils */ export const fullCopyObject = (obj) => JSON.parse(JSON.stringify(obj)); /** * Rounds a number to a given precision. * * @param value The number to round. * @param precision The precision to round to. * @returns The rounded number. * * @category Utils */ export function round(value, precision) { return Number(value.toFixed(precision)); } /** * Generates an array of a given length, filled with a specified value. * * @template T The type of the values in the array. * @param length The length of the array to generate. * @param value The value to fill the array with. * @returns An array of the specified length, filled with the specified value. * * @category Utils */ export function createFilledArray(length, value) { return Array.from({ length }, () => value); } /** * Calculates the sum of an array of numbers. * * @param input The array of numbers to sum. * @returns The sum of the input array. * * @category Utils */ export function arraySum(input) { return input.reduce((acc, val) => acc + val, 0); } /** * Calculates the mean of an array of numbers. * * @param input The array of numbers to calculate the mean of. * @returns The mean of the input array. * * @category Utils */ export function arrayMean(input) { return arraySum(input) / input.length; } /** * Calculates the median of a sorted array of numbers. * * @param sortedInput The sorted array of numbers to find the median of. * @returns The median value of the input array. * * @category Utils */ export function arrayMedian(sortedInput) { const middleIndex = Math.floor(sortedInput.length / 2); if (sortedInput.length % 2 !== 0) { return sortedInput[middleIndex]; } return (sortedInput[middleIndex - 1] + sortedInput[middleIndex]) / 2; } /** * Applies a binary operator to two arrays of the same length. * * @template T The type of the values in the arrays. * @param lhs The left-hand side array. * @param rhs The right-hand side array. * @param operator The binary operator to apply. * @returns A new array with the result of applying the operator to each pair of values. * * @category Utils */ export function arrayBinaryOperation(lhs, rhs, operator) { const result = []; const len = Math.min(lhs.length, rhs.length); for (let i = 0; i < len; ++i) { result.push(operator(lhs[i], rhs[i])); } return result; } /** * Returns a random element from the input array. * * @template T The type of the values in the array. * @param input The array to select a random element from. * @returns A random element from the input array. * * @category Utils */ export function getRandomArrayItem(input) { return input[Math.floor(Math.random() * input.length)]; } /** * Normalizes an array of numbers to the range from -1 to 1, where the `reference` value is mapped to 0. * * @param input The array of numbers to normalize. * @param reference The reference value to map to 0. * @returns The normalized array of numbers. * * @category Utils */ export function normalizePhenomeRow(input, reference) { // Find the minimum and maximum values in the array const minVal = Math.min(...input); const maxVal = Math.max(...input); // Calculate the maximum distance relative to the reference const maxDistance = Math.max(Math.abs(maxVal - reference), Math.abs(minVal - reference)); const denominator = maxDistance || 1; return input.map(num => { // Normalize each number to the range from -1 to 1, where reference = 0 return (num - reference) / denominator; }); } /** * Normalizes the columns of a matrix of phenome to the range from -1 to 1, where the `reference` value is mapped to 0. * * @param input The matrix of phenome to normalize. * @param reference The reference value to map to 0. * @returns The normalized matrix of phenome. * * @category Utils */ export function normalizePhenomeMatrixColumns(input, reference) { const result = fullCopyObject(input); if (result.length === 0) { return result; } for (let i = 0; i < result[0].length; i++) { const columnNormalized = normalizePhenomeRow(result.map((row) => row[i]), reference[i]); for (let j = 0; j < result.length; j++) { result[j][i] = columnNormalized[j]; } } return result; } /** * Normalizes the columns of a matrix of phenome to the range from -1 to 1, where the `reference` value is mapped to 0. * * @param matrix The matrix of phenome to normalize. * @param reference The reference value to map to 0. * @param abs Whether to take the absolute value of the normalized values. * @returns The normalized matrix of phenome. * * @category Utils */ export function normalizePhenomeMatrix(matrix, reference, abs = true) { const result = normalizePhenomeMatrixColumns(matrix, reference); if (abs) { return result.map((row) => row.map((x) => Math.abs(x))); } return result; } /** * Creates an empty `StatSummary` object. * * A `StatSummary` object contains the count of genomes in the population, * as well as the best, second best, mean, median, and worst values. * This function initializes a `StatSummary` with all values set to zero. * * @returns An initialized `StatSummary` object with all fields set to zero. * * @category Utils */ export function createEmptyStatSummary() { return { count: 0, best: 0, second: 0, mean: 0, median: 0, worst: 0, }; } /** * Creates an empty `GroupedStatSummary` object. * * A `GroupedStatSummary` object contains a summary of the statistics of a population of genomes, * grouped by origin into three categories: initial, crossover, and mutation. * This function initializes a `GroupedStatSummary` with all values set to zero. * * @returns An initialized `GroupedStatSummary` object with all fields set to zero. * * @category Utils */ export function createEmptyGroupedStatSummary() { return { initial: createEmptyStatSummary(), crossover: createEmptyStatSummary(), mutation: createEmptyStatSummary(), }; } /** * Creates an empty `RangeStatSummary` object. * * A `RangeStatSummary` object contains the minimum, mean, and maximum values * for a set of numerical data. * This function initializes a `RangeStatSummary` with all values set to zero. * * @returns An initialized `RangeStatSummary` object with all fields set to zero. * * @category Utils */ export function createEmptyRangeStatSummary() { return { min: 0, mean: 0, max: 0, }; } /** * Calculates a summary of the statistics for a sorted array of numbers. * * The summary includes the count of numbers in the array, * as well as the best, second best, mean, median, and worst values. * * @param sortedSource The sorted array of numbers. * @returns A summary of the statistics for the array of numbers. * * @category Utils */ export function calcStatSummary(sortedSource) { var _a; if (sortedSource.length === 0) { return createEmptyStatSummary(); } return { count: sortedSource.length, best: sortedSource[0], second: (_a = sortedSource[1]) !== null && _a !== void 0 ? _a : 0, mean: arrayMean(sortedSource), median: arrayMedian(sortedSource), worst: sortedSource[sortedSource.length - 1], }; } /** * Calculates a summary of the statistics for a sorted array of numbers. * * The summary includes the minimum, mean, and maximum values * for a set of numerical data. * * @param source The array of numbers. * @returns A summary of the statistics for the array of numbers. * * @category Utils */ export function calcRangeStatSummary(source) { if (source.length === 0) { return createEmptyRangeStatSummary(); } return { min: Math.min(...source), mean: arrayMean(source), max: Math.max(...source), }; } /** * Rounds the fields of a StatSummary object to a given precision. * * @param summary The StatSummary object to round. * @param precision The number of decimal places to round to. * @returns A new StatSummary object with rounded fields. * * @category Utils */ export function roundStatSummary(summary, precision) { return { count: summary.count, best: round(summary.best, precision), second: round(summary.second, precision), mean: round(summary.mean, precision), median: round(summary.median, precision), worst: round(summary.worst, precision), }; } /** * Rounds the fields of a GroupedStatSummary object to a given precision. * * This function rounds the statistics in each category of the GroupedStatSummary * (initial, crossover, mutation) to the specified number of decimal places. * * @param summary The GroupedStatSummary object to round. * @param precision The number of decimal places to round to. * @returns A new GroupedStatSummary object with rounded fields. * * @category Utils */ export function roundGroupedStatSummary(summary, precision) { return { initial: roundStatSummary(summary.initial, precision), crossover: roundStatSummary(summary.crossover, precision), mutation: roundStatSummary(summary.mutation, precision), }; } /** * Rounds the fields of a RangeStatSummary object to a given precision. * * This function rounds the minimum, mean, and maximum values of the RangeStatSummary * to the specified number of decimal places. * * @param summary The RangeStatSummary object to round. * @param precision The number of decimal places to round to. * @returns A new RangeStatSummary object with rounded fields. * * @category Utils */ export function roundRangeStatSummary(summary, precision) { return { min: round(summary.min, precision), mean: round(summary.mean, precision), max: round(summary.max, precision), }; } /** * Creates an array of `EvaluatedGenome` objects from a population and its fitness and phenome. * * @param population The population of genomes. * @param fitnessColumn The fitness column of the population. * @param phenomeMatrix The phenome matrix of the population. * @returns An array of EvaluatedGenome objects. * * @category Utils */ export function createEvaluatedPopulation(population, fitnessColumn, phenomeMatrix) { const zipped = [...zip(population, fitnessColumn, phenomeMatrix)]; return zipped.map(([genome, fitness, phenome]) => ({ genome, fitness, phenome })); } /** * Extracts the population, fitness column, and phenome matrix from an array of * [[EvaluatedGenome]] objects. * * @param input The array of [[EvaluatedGenome]] objects. * @returns An array of three elements: the population, fitness column, and phenome matrix. * * @category Utils */ export function extractEvaluatedPopulation(input) { return [ input.map((x) => x.genome), input.map((x) => x.fitness), input.map((x) => x.phenome), ]; } //# sourceMappingURL=utils.js.map