UNPKG

@thi.ng/gp

Version:

Genetic programming helpers & strategies (tree based & multi-expression programming)

100 lines (99 loc) 3.17 kB
import { inRange } from "@thi.ng/math/interval"; import { SYSTEM } from "@thi.ng/random/system"; import { repeatedly } from "@thi.ng/transducers/repeatedly"; import { opNode, probabilities, terminalNode } from "./utils.js"; class MEP { opts; probTerminal; choices; constructor(opts) { this.opts = { rnd: SYSTEM, ...opts }; const probs = probabilities(this.opts); this.probTerminal = probs.probTerminal; this.choices = probs.iter; } /** * Returns a new random {@link MEPChromosome} with the configured * probabilities of operator and terminal nodes (constants). * * @remarks * See {@link MEP.decodeChromosome} for conversion to * {@link ASTNode}s. * */ randomChromosome() { const res = []; for (let i = 0, n = this.opts.chromoSize; i < n; i++) { res[i] = this.randomGene(i); } return res; } /** * Decodes given chromosome into an array of {@link ASTNode}s and * optionally applies tree depth filter (by default includes all). * * @remarks * A {@link MEPChromosome} encodes multiple solutions (one per gene * slot), therefore a chromosome of length `n` will produce the same * number ASTs (less if min/max tree depth filters are applied). * * @param chromosome - * @param minDepth - * @param maxDepth - */ decodeChromosome(chromosome, minDepth = 0, maxDepth = Infinity) { const res = []; const depths = []; for (let i = 0; i < chromosome.length; i++) { const gene = chromosome[i]; if (gene.type == "term") { res[i] = gene; depths[i] = 1; } else { res[i] = opNode( gene.op, gene.args.map((g) => res[g]) ); depths[i] = 1 + gene.args.reduce((d, a) => Math.max(d, depths[a]), 0); } } return res.filter((_, i) => inRange(depths[i], minDepth, maxDepth)); } crossoverSingle(chromo1, chromo2, cut) { cut = cut !== void 0 ? cut : this.opts.rnd.int() % Math.min(chromo1.length, chromo2.length); return [ chromo1.slice(0, cut).concat(chromo2.slice(cut)), chromo2.slice(0, cut).concat(chromo1.slice(cut)) ]; } crossoverUniform(chromo1, chromo2) { const rnd = this.opts.rnd; const res = []; const minLen = Math.min(chromo1.length, chromo2.length); for (let i = 0; i < minLen; i++) { res[i] = rnd.probability(0.5) ? chromo1[i] : chromo2[i]; } return chromo1.length > minLen ? res.concat(chromo1.slice(minLen)) : chromo2.length > minLen ? res.concat(chromo2.slice(minLen)) : res; } mutate(chromo) { const { rnd, probMutate } = this.opts; const res = new Array(chromo.length); for (let i = chromo.length; i-- > 0; ) { res[i] = rnd.probability(probMutate) ? this.randomGene(i) : chromo[i]; } return res; } randomGene(i) { const geneID = this.choices.next().value; const rnd = this.opts.rnd; if (i === 0 || geneID === 0) { return terminalNode(this.opts.terminal(rnd)); } const op = this.opts.ops[geneID - 1]; const args = [...repeatedly(() => rnd.int() % i, op.arity)]; return opNode(op.fn(rnd, args), args); } } export { MEP };