Genetic programming¶
Prefix-tree GP (loosely typed, strongly typed, ADFs) is still there. The following is extra.
generate()closes a type that has terminals but no primitives. A leaf-only type — a rolling window length is the usual case — can appear in a strongly typed tree.add_primitive(..., weight=)implements weighted primitive sampling. Equal weights keep the previous RNG stream. The same weights apply to node replacement and insert mutation. Terminals stay uniform.- A zero-arity callable terminal can format as
name()whenadd_terminal(..., call_zero=True), so the defaultevalcompile path calls it instead of looking up the function object. Action terminals (Santa Fe ant) stay uncalled names by default. tree_to_infixis an infix pretty-printer for logs and papers. It is display-only.make_column_pset(names)builds a strongly typed set with oneArrayinput per column. The type tags areArray(1Dfloat64),Mask(1Dbool), andWindow(int). Argument order is column order.add_numpy_primitivesregisters a vectorized kit: arithmetic, protectedvdiv/vlog/vsqrt, comparisons that produce aMask, mask logic, andvwhere. Protected ops only replace a non-finite result that the operation itself fabricated; an inputnancomes back out asnan.add_window_primitivesregisters causaldelay,diff,rolling_{sum,mean,std,min,max}, andema. The window is[t-n+1, t]; samples without enough history arenan. Look-ahead is forbidden.add_window_ephemeralsamples inclusive integer lengths.compile_treecaches the defaultevalbackend by expression text and context identity.backend="opcode"lowers the tree to a postfix tape and runs a NumPy stack machine.backend="numba"(thedeap-er[numba]extra) runs the same tape in one process-wide compiled interpreter. Custom kernels bind at or aboveUSER_BASEand pass one dispatcher; they are not a second interpreter.- Algorithms call
toolbox.evaluate_batch(invalids)when that operator is registered, otherwisetoolbox.map(toolbox.evaluate, invalids).ea_generate_updateandea_generate_update_restartsgo through the sameevaluate_invalidpath. A generation can be scored against one shared matrix without changingmap's contract. tools.clone_individualshallow-copies a list/array individual and deepcopies only the fitness. GP toolboxes should register it; the default Toolbox clone remainsdeepcopy.cx_semanticbuilds each child from a snapshot of the original parents. The second child is no longer derived from the already mutated first child.cx_one_pointalways groups nodes by return type. Anobjectroot on the first parent no longer disables strongly typed matching.static_limitreplaces an oversized offspring with aclone_individualcopy of a parent, so the two offspring slots never share one parent object. Only the positional arguments of the first argument's type are parents, so a primitive set or generator bound positionally is neither deep-copied nor returned as a child.- HARM places the size cutoff on evaluated individuals only, scales the half-life by the cutoff (not by each individual's size), and does not crash on an empty candidate slice.
add_primitiveandadd_terminalreject a name that matches a primitive-set argument, so a compiled lambda parameter cannot shadow the symbol.rename_argumentsrejects the inverse: a new name that is already an argument, primitive, or terminal.mut_insertleaves the tree unchanged when a sibling type has no terminals, instead of raisingIndexError.add_pair_window_primitivesregisters causalrolling_corr,rolling_cov, androlling_betaover twoArrayarguments and aWindow. Moments use the population divisor; beta is the OLS slope of the first series on the second. Python, opcode, and Numba paths agree.add_ts_primitivesregisters causalts_rank,ts_argmax, andts_argmin. Rank is the average rank of the current sample scaled to \([0, 1]\); a window of 1 isnan. Arg-extremum is how many samples ago the extreme occurred (0is now); a tie keeps the most recent. Python, opcode, and Numba paths agree.interpret_tapesscores many tapes against one packed(rows, columns)matrix and returns(n_individuals, n_rows). The opcode path unpacks columns once. The Numba path is a compiled loop; each serial call allocates its own workspace, so several threads can call it at once, andparallel=Truegives each thread its own workspace. The interpreter and the builtin batch kernels load from Numba's disk cache in later processes. Unique programs are compiled once bystr(tree)and lowered from the tree object.tape_lookbackreturns the program's causal bound;suffix_rescorewrites a dirty suffix onto a cached prefix so the series matches that full-matrix oracle.SlimTreestores a GP head plus semantic delta blocks.mut_slim,mut_slim_inflate, andmut_slim_deflateappend or remove deltas without re-wrapping the whole tree;cx_slim_donorswaps a donor block size-preservingly.compile_slim_treeevaluates \(\mathrm{head} + \sum \delta_i\).PrimitiveTreeslice assignment treats a missing start as0.tree[:]andtree[:n]no longer raiseTypeErrorwhen the replacement is a complete tree.tune_ephemeralsextracts ephemeral floats andWindowints in documented prefix order (SlimTree: head, then deltas), runs a short boxedStrategy/StrategySeparablegenerate/updateloop, writes repaired values back, and invalidates fitness plus the compile-cache entry for the old expression.semantic_moments,semantic_solve_bits, andsemantic_projectturn aninterpret_tapes(n_individuals, n_rows)pack into a behavior descriptor (per-row moments, lexicase solve bits, or a caller PCA / random basis).semantic_nearestlooks up cosine or Euclidean neighbors on the finite /valid=mask.SemanticSurrogateis a last-generation linear or nearest-neighbor stand-in. Fitness stays onind.fitness; the archive still ranks a cell by fitness.promote_subtreelifts a complete typed subtree into the samePrimitiveSetTypedas a generated primitive (promo0, …). Latergenerate/ mutation can sample that name. The library is capped; the least-used promoted name is evicted, not a built-in. Columnar sets bind atUSER_BASEandlower_treeexpands the body so tapes stay on builtin opcodes.add_adfremains the static path.PrimitiveTree.from_stringaccepts anintliteral in aWindowslot.str(tree)writes window lengths as integers; the opcode backend no longerTypeErrors when compiling that text, and a stringified windowed tree round-trips. The literal restores as the set's window ephemeral, somut_ephemeralcan resample it. Window lengths must be integers in \([1, 2^{31} - 1]\) at parse, compile, and lowering time; a literal too large for a float raisesgp.ProgramError, notOverflowError.PrimitiveTree.from_stringrejects extra tokens and incomplete calls.add(ARG0, 2, 3)andadd(ARG0)no longer stringify as a leftover leaf and compile as the constant \(3\) or the identity. The root must return a subtype ofprim_set.ret; DEAP accepted any root type. Invalid programs raisegp.ProgramError, a subclass ofValueErrorandTypeError, andcompile_tree/lower_treecheck every leaf against its slot with the same rules, so a compiled tree's text parses back.affine_scalefits Keijzer \(a + b\,f(x)\) on the samevalid=mask ascase_errors. Darwinian callers use the scaled series only for fitness / case errors. Lamarckianwrite_affine_scalewrites \(a\) and \(b\) back as ephemerals wrapping aPrimitiveTree, or as wrapping Slim deltas, then invalidates fitness and the compile cache for that expression.LinearPolicyProgram,PushPolicyProgram, andstep_policy_loopimplement the private Push GP loop behind the P11–P14 firewall.policy_observesupplies summary observations;linear_policy_decide/push_policy_decideemit discrete action tokens;apply_policy_actiondispatches them. No column loads, noWindowPush type, and no publicPushTreeongportools. Tapes remain the onlyinterpret_tapestarget. Not a second public genome (Push GP P19).- On the opcode backend,
interpret_tapeshash-conses postfix subexpressions across a batch and evaluates each unique sub-tape once against the packed matrix. Shared suffixes are stitched from one oracle result per node, and each node's column is freed after its last consumer has run, so peak memory follows the stack depth rather than the number of unique nodes. The return shape, warmupnancontract, and per-tapefillsemantics are unchanged. The Numba backend runs every batch, serial or parallel, on its compiled per-tape kernel; NaN patterns match the opcode backend and values agree to aboutrtol=1e-9. Its rolling sums and means sum each window on its own in NumPy's pairwise order, so they match the opcode backend bit for bit, and no earlier sample or packed symbol leaks into a later window. Itsrolling_std,rolling_corr,rolling_cov, androlling_betacenter each window on its own mean, as the opcode backend does, so a value depends only on the samples in its window. On both backends a window whose squared deviations would overflow or underflow, such as one holding a single3.4e300sample or only1e-170deviations, is scaled by a power of two first, so it gets its true finite value instead ofinf,nan, or0. See the columnar GP tutorial. - HARM
natural_histogramdoes not wraphist[-1]when a tree has size \(0\). The left-neighbor bin is updated only forind_size >= 1, matching the existingind_size - 2guard. register_gpwires the standard tree-GP toolbox:clone_individual,compile_tree, half-and-half init, one-point crossover, uniform mutation, and a heightstatic_limit.columnar_psetbuilds the typed column set and registers the NumPy / window kits (optional pair-window, time-series, and window ephemeral) in one call.evaluate_columnaris theevaluate_batchhelper: unique trees are lowered once, scored withinterpret_tapes, and warmupnansamples are dropped from the MSE. DEAP leaves that wiring on every caller.MEMETIC_DEFAULT_N_GENandMEMETIC_MAX_N_GENdocument the recommended memetic polish.tune_ephemerals_budgetcaps innern_gento remainingn_evals, defaults to the small generation count, and judges trials on a caller-marked held-out exam when one exists.cap_tune_n_genandestimate_tune_ephemerals_evalsshare the tune cost model withPolicyActionGuard. Rawtune_ephemeralsis unchanged; policy caps stay on the guard.bounds_from_matrix,tape_interval, andtape_flagspropagate column bounds through the builtin opcode kit and certificate identically-nan, constant, or warmup-hidingvwhereprograms beforeinterpret_tapes.evaluate_columnar(..., static_filter=True)skips them with the existingemptysentinel. Not a substitute for the runtime warmup contract orcase_errors(..., valid=).cx_homologousswaps subtrees at the same root-to-node path when return types match, otherwise falling back to type-matched one-point.cx_one_point_semanticpicks the type-matched partner whoseinterpret_taperow is nearest to the anchor subtree viasemantic_neareston batchedinterpret_tapesrows.cx_one_pointremains the default mate.- Typed crossover (
cx_one_point,cx_one_point_leaf_biased,cx_homologous,cx_one_point_semantic) draws the swapped return type from the shared types in first-occurrence order in the first parent. DEAP drew it from asetof classes, so one seed bred different children in different processes and a checkpoint resumed in a fresh process left the original path. rename_argumentsrenames the argument terminal'snameas well as itsvalue. DEAP updated onlyvalue, so the terminal kept reporting the oldARG0name after the rename.PrimitiveTree.from_stringrestores a number whose type does not fit its slot as that slot's ephemeral when the set has one. An ephemeral registered asfloatbut drawingrandintvalues prints asadd(ARG0, 2); DEAP raisedTypeErrorparsing its ownstr(tree), and the tree no longer round-tripped.PrimitiveTree.from_stringchecks every parenthesis and comma against the arity of each primitive, andstr(tree)raisesgp.ProgramErrorfor a node list that is missing arguments or holds nodes after its complete root.str(tree)and everycompile_treebackend also raisegp.ProgramErrorfor an element that is not aPrimitiveorTerminal, such as the string"vadd", instead ofAttributeError. DEAP split the text on()and,and dropped them, sovadd(a, b))andvadd(a b)parsed, andstr()of[vadd, a]printeda, silently discarding the primitive.- The default
pythonbackend ofcompile_treecompiles a tree of any depth. Past 64 levels it binds each primitive call to a local instead of nesting the calls, so Python's 200-level parenthesis limit no longer applies. DEAPeval'd one nested expression and documented a 90-level ceiling; current Python raised a rawSyntaxErrorfor a tree thatfrom_stringandlower_treeaccept. mut_node_replacementandmut_shrinkcan pick the root. Node replacement swaps it for a primitive with the same signature, and shrink can drop the outermost primitive in favour of a same-typed argument; both stay type-valid. DEAP drew fromrandrange(1, len(individual))and skipped index 0 when shrinking, so a program whose only primitive is the root could never have its operator changed by node replacement.make_column_psetandcolumnar_psettakestrides=, the rows per step of each column that holds its value for several rows. A window then counts steps of its series: a pointwise node takes the gcd of its arguments' strides, anddelayanddiffkeep theirs. Lowering scales the window to rows on every backend, so the text form, pickles, and every stride-1 set are unchanged. DEAP has no columnar windows.interpret_tapes,evaluate_columnar,suffix_rescore,cx_one_point_semantic, andbind_tapetakeprecision="float32", which keeps afloat32column matrix without thefloat64copy, halving its memory. Columns widen tofloat64as they are read, so the scores match thefloat64table bit for bit, andwarmup_numba(precisions=...)compiles thefloat32kernels ahead of time. The default"float64"is unchanged. DEAP has no packed column matrix.
The columnar contract is in the columnar GP tutorial. The private Push policy loop is in the Push GP tutorial. Shared-array evaluation is in the multiprocessing tutorial.