Skip to content

Commit

Permalink
feat(gp): update MEP.decodeChromosome, tests, add docs
Browse files Browse the repository at this point in the history
  • Loading branch information
postspectacular committed Nov 21, 2019
1 parent fb33937 commit e339925
Show file tree
Hide file tree
Showing 5 changed files with 368 additions and 33 deletions.
6 changes: 5 additions & 1 deletion packages/gp/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@thi.ng/gp",
"version": "0.0.1",
"description": "Genetic programming helpers",
"description": "Genetic programming helpers & strategies (tree based & multi-expression programming)",
"module": "./index.js",
"main": "./lib/index.js",
"umd:main": "./lib/index.umd.js",
Expand Down Expand Up @@ -36,16 +36,20 @@
},
"dependencies": {
"@thi.ng/api": "^6.5.0",
"@thi.ng/math": "^1.5.0",
"@thi.ng/random": "^1.1.13",
"@thi.ng/transducers": "^6.0.0",
"@thi.ng/zipper": "^0.0.1"
},
"keywords": [
"AST",
"crossover",
"ES6",
"evolutionary",
"genetic algorithm",
"genetic programming",
"MEP",
"mutation",
"phenotype",
"typescript"
],
Expand Down
33 changes: 25 additions & 8 deletions packages/gp/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,41 @@ export interface GPOpts<OP, T, ARGS> {
*/
terminal: Fn<IRandom, T>;
/**
* Op ID selector for unary functions. If probabilistic, the given
* PRNG MUST be used for repeatable results.
* Operator node generators.
*/
ops: OpGenSpec<ARGS, OP>[];
/**
* Possibly seeded PRNG instance to be used for AST generation /
* editing.
*
* @defaultValue {@link @thi.ng/random#SYSTEM}
*/
rnd?: IRandom;
/**
* Per-gene mutation probability. MUST be < 1 for {@link ASTOpts} to
* avoid infinite tree expansion.
*/
probMutate: number;
}

export interface OpGenSpec<NODE, OP> {
/**
* Function producing/selecting an operator value. If probabilistic,
* the given PRNG MUST be used for repeatable results. The function
* is called with all argument genes/expressions to allow inform the
* operator selection.
*/
fn: Fn2<IRandom, NODE[], OP>;
/**
* Operator / function arity (number or args to generate).
*/
arity: number;
/**
* Probability for this generator to be used.
*/
prob: number;
}

export interface ASTOpts<OP, T> extends GPOpts<OP, T, ASTNode<OP, T>> {
maxDepth: number;
}
Expand All @@ -54,9 +77,3 @@ export interface OpGene<OP, A> {
op: OP;
args: A[];
}

export interface OpGenSpec<NODE, OP> {
fn: Fn2<IRandom, NODE[], OP>;
arity: number;
prob: number;
}
7 changes: 5 additions & 2 deletions packages/gp/src/ast.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { assert } from "@thi.ng/api";
import { SYSTEM } from "@thi.ng/random";
import {
iterate,
Expand All @@ -21,13 +22,15 @@ export class AST<OP, T> {

constructor(opts: ASTOpts<OP, T>) {
this.opts = { rnd: SYSTEM, ...opts };
assert(this.opts.probMutate < 1, "mutation probability must be < 1.0");
const probs = probabilities(this.opts);
this.probTerminal = probs.probTerminal;
this.choices = probs.iter;
}

/**
* Returns a random AST with given max tree depth.
* Returns a random AST with given max tree depth. The max depth
* provided in {@link ASTOpts} is used by default.
*
* @param maxDepth
*/
Expand Down Expand Up @@ -56,7 +59,7 @@ export class AST<OP, T> {

/**
* Probilistically replaces randomly chosen tree nodes with a new
* random AST of given `maxDepth` (default: 1).
* random AST of given `maxDepth` (default: 1). Never mutates root.
*
* @param tree
* @param maxDepth
Expand Down
41 changes: 36 additions & 5 deletions packages/gp/src/mep.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { inRange } from "@thi.ng/math";
import { SYSTEM } from "@thi.ng/random";
import { repeatedly } from "@thi.ng/transducers";
import {
ASTNode,
GeneType,
MEPChromosome,
MEPGene,
Expand All @@ -20,6 +22,15 @@ export class MEP<OP, T> {
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(): MEPChromosome<OP, T> {
const res: MEPChromosome<OP, T> = [];
for (let i = 0, n = this.opts.chromoSize; i < n; i++) {
Expand All @@ -28,21 +39,41 @@ export class MEP<OP, T> {
return res;
}

decodeChromosome(chromosome: MEPChromosome<OP, T>, minDepth = 0) {
const res: any[] = [];
/**
* 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: MEPChromosome<OP, T>,
minDepth = 0,
maxDepth = Infinity
) {
const res: ASTNode<OP, T>[] = [];
const depths: number[] = [];
for (let i = 0; i < chromosome.length; i++) {
const gene = chromosome[i];
if (gene.type == GeneType.TERMINAL) {
res[i] = gene.value;
res[i] = gene;
depths[i] = 1;
} else {
res[i] = [gene.op, ...gene.args.map((g) => res[g])];
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) => depths[i] >= minDepth);
return res.filter((_, i) => inRange(depths[i], minDepth, maxDepth));
}

crossoverSingle(
Expand Down
Loading

0 comments on commit e339925

Please sign in to comment.