genetic-search
Version:
Multiprocessing genetic algorithm implementation library
190 lines • 5.62 kB
JavaScript
import { arrayBinaryOperation, createFilledArray } from "./utils";
/**
* A dummy phenome cache implementation that does nothing.
*
* This class is used when the {@link GeneticSearch} is created without a
* phenome cache.
*
* @category Cache
* @category Strategies
*/
export class DummyPhenomeCache {
getReady(_) {
return undefined;
}
get(_, defaultValue) {
return defaultValue;
}
set(_, __) {
return;
}
clear(_) {
return;
}
export() {
return {};
}
import(_) {
return;
}
}
/**
* A simple phenome cache implementation.
*
* This cache stores the constant phenome value for each genome.
*
* @category Cache
* @category Strategies
*/
export class SimplePhenomeCache {
constructor() {
this.cache = new Map();
}
get(genomeId, defaultValue) {
return this.cache.has(genomeId)
? this.cache.get(genomeId)
: defaultValue;
}
getReady(genomeId) {
return this.cache.has(genomeId) ? this.get(genomeId) : undefined;
}
set(genomeId, phenome) {
this.cache.set(genomeId, phenome);
}
clear(excludeGenomeIds) {
const excludeIdsSet = new Set(excludeGenomeIds);
for (const id of this.cache.keys()) {
if (!excludeIdsSet.has(id)) {
this.cache.delete(id);
}
}
}
export() {
return Object.fromEntries(this.cache);
}
import(data) {
this.cache.clear();
for (const [id, cacheItem] of Object.entries(data)) {
this.cache.set(Number(id), cacheItem);
}
}
}
/**
* A phenome cache implementation that stores the phenome for each genome as a
* weighted average of all phenome that have been set for that genome.
*
* @category Cache
* @category Strategies
*/
export class AveragePhenomeCache {
constructor() {
/**
* A map of genome IDs to their respective phenome and the number of times they have been set.
*
* The key is the genome ID, and the value is an array with two elements. The first element is the
* current phenome for the genome, and the second element is the number of times the phenome have
* been set.
*/
this.cache = new Map();
}
get(genomeId, defaultValue) {
if (!this.cache.has(genomeId)) {
return defaultValue;
}
const [row, count] = this.cache.get(genomeId);
return row.map((x) => x / count);
}
getReady() {
return undefined;
}
set(genomeId, phenome) {
if (!this.cache.has(genomeId)) {
this.cache.set(genomeId, [phenome, 1]);
return;
}
const [row, count] = this.cache.get(genomeId);
this.cache.set(genomeId, [row.map((x, i) => x + phenome[i]), count + 1]);
}
clear(excludeGenomeIds) {
const excludeIdsSet = new Set(excludeGenomeIds);
for (const id of this.cache.keys()) {
if (!excludeIdsSet.has(id)) {
this.cache.delete(id);
}
}
}
export() {
return Object.fromEntries(this.cache);
}
import(data) {
this.cache.clear();
for (const [id, cacheItem] of Object.entries(data)) {
this.cache.set(Number(id), cacheItem);
}
}
}
/**
* A phenome cache implementation that stores the phenome for each genome as a
* weighted average of all phenome that have been set for that genome.
*
* The closer the genome age is to 0, the closer the phenome are to the average phenome of the population,
* which helps to combat outliers for new genomes.
*
* @category Cache
* @category Strategies
*/
export class WeightedAgeAveragePhenomeCache extends AveragePhenomeCache {
/**
* Constructs a new WeightedAgeAveragePhenomeCache.
* @param weight The weight factor used for calculating the weighted average.
*/
constructor(weight) {
super();
/**
* The current average phenome row, or undefined if not yet calculated.
*/
this.averageRow = undefined;
this.weight = weight;
}
set(genomeId, phenome) {
super.set(genomeId, phenome);
this.resetAverageRow();
}
get(genomeId, defaultValue) {
const row = super.get(genomeId, defaultValue);
if (row === undefined) {
return undefined;
}
if (!this.refreshAverageRow()) {
return row;
}
const [, age] = this.cache.get(genomeId);
const averageDiff = arrayBinaryOperation(row, this.averageRow, (lhs, rhs) => lhs - rhs);
const weightedAverageDiff = averageDiff.map((x) => x * this.weight / age);
return arrayBinaryOperation(row, weightedAverageDiff, (lhs, rhs) => lhs - rhs);
}
refreshAverageRow() {
if (this.cache.size === 0) {
this.resetAverageRow();
return false;
}
let weightedTotal = 0;
const result = createFilledArray(this.getPhenomeCount(), 0);
for (const phenome of this.cache.values()) {
const [row, weight] = phenome;
for (let i = 0; i < row.length; ++i) {
result[i] += row[i];
}
weightedTotal += weight;
}
this.averageRow = result.map((x) => x / weightedTotal);
return true;
}
resetAverageRow() {
this.averageRow = undefined;
}
getPhenomeCount() {
return this.cache.values().next().value[0].length;
}
}
//# sourceMappingURL=cache.js.map