Skip to content

Genetic programming

Prefix-tree GP (loosely typed, strongly typed, ADFs) is still there. The following is extra.

  1. 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.
  2. 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.
  3. A zero-arity callable terminal can format as name() when add_terminal(..., call_zero=True), so the default eval compile path calls it instead of looking up the function object. Action terminals (Santa Fe ant) stay uncalled names by default.
  4. tree_to_infix is an infix pretty-printer for logs and papers. It is display-only.
  5. make_column_pset(names) builds a strongly typed set with one Array input per column. The type tags are Array (1D float64), Mask (1D bool), and Window (int). Argument order is column order.
  6. add_numpy_primitives registers a vectorized kit: arithmetic, protected vdiv / vlog / vsqrt, comparisons that produce a Mask, mask logic, and vwhere. Protected ops only replace a non-finite result that the operation itself fabricated; an input nan comes back out as nan.
  7. add_window_primitives registers causal delay, diff, rolling_{sum,mean,std,min,max}, and ema. The window is [t-n+1, t]; samples without enough history are nan. Look-ahead is forbidden. add_window_ephemeral samples inclusive integer lengths.
  8. compile_tree caches the default eval backend by expression text and context identity. backend="opcode" lowers the tree to a postfix tape and runs a NumPy stack machine. backend="numba" (the deap-er[numba] extra) runs the same tape in one process-wide compiled interpreter. Custom kernels bind at or above USER_BASE and pass one dispatcher; they are not a second interpreter.
  9. Algorithms call toolbox.evaluate_batch(invalids) when that operator is registered, otherwise toolbox.map(toolbox.evaluate, invalids). ea_generate_update and ea_generate_update_restarts go through the same evaluate_invalid path. A generation can be scored against one shared matrix without changing map's contract.
  10. tools.clone_individual shallow-copies a list/array individual and deepcopies only the fitness. GP toolboxes should register it; the default Toolbox clone remains deepcopy.
  11. cx_semantic builds each child from a snapshot of the original parents. The second child is no longer derived from the already mutated first child.
  12. cx_one_point always groups nodes by return type. An object root on the first parent no longer disables strongly typed matching.
  13. static_limit replaces an oversized offspring with a clone_individual copy 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.
  14. 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.
  15. add_primitive and add_terminal reject a name that matches a primitive-set argument, so a compiled lambda parameter cannot shadow the symbol. rename_arguments rejects the inverse: a new name that is already an argument, primitive, or terminal.
  16. mut_insert leaves the tree unchanged when a sibling type has no terminals, instead of raising IndexError.
  17. add_pair_window_primitives registers causal rolling_corr, rolling_cov, and rolling_beta over two Array arguments and a Window. Moments use the population divisor; beta is the OLS slope of the first series on the second. Python, opcode, and Numba paths agree.
  18. add_ts_primitives registers causal ts_rank, ts_argmax, and ts_argmin. Rank is the average rank of the current sample scaled to \([0, 1]\); a window of 1 is nan. Arg-extremum is how many samples ago the extreme occurred (0 is now); a tie keeps the most recent. Python, opcode, and Numba paths agree.
  19. interpret_tapes scores 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, and parallel=True gives 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 by str(tree) and lowered from the tree object. tape_lookback returns the program's causal bound; suffix_rescore writes a dirty suffix onto a cached prefix so the series matches that full-matrix oracle.
  20. SlimTree stores a GP head plus semantic delta blocks. mut_slim, mut_slim_inflate, and mut_slim_deflate append or remove deltas without re-wrapping the whole tree; cx_slim_donor swaps a donor block size-preservingly. compile_slim_tree evaluates \(\mathrm{head} + \sum \delta_i\).
  21. PrimitiveTree slice assignment treats a missing start as 0. tree[:] and tree[:n] no longer raise TypeError when the replacement is a complete tree.
  22. tune_ephemerals extracts ephemeral floats and Window ints in documented prefix order (SlimTree: head, then deltas), runs a short boxed Strategy / StrategySeparable generate / update loop, writes repaired values back, and invalidates fitness plus the compile-cache entry for the old expression.
  23. semantic_moments, semantic_solve_bits, and semantic_project turn an interpret_tapes (n_individuals, n_rows) pack into a behavior descriptor (per-row moments, lexicase solve bits, or a caller PCA / random basis). semantic_nearest looks up cosine or Euclidean neighbors on the finite / valid= mask. SemanticSurrogate is a last-generation linear or nearest-neighbor stand-in. Fitness stays on ind.fitness; the archive still ranks a cell by fitness.
  24. promote_subtree lifts a complete typed subtree into the same PrimitiveSetTyped as a generated primitive (promo0, …). Later generate / mutation can sample that name. The library is capped; the least-used promoted name is evicted, not a built-in. Columnar sets bind at USER_BASE and lower_tree expands the body so tapes stay on builtin opcodes. add_adf remains the static path.
  25. PrimitiveTree.from_string accepts an int literal in a Window slot. str(tree) writes window lengths as integers; the opcode backend no longer TypeErrors when compiling that text, and a stringified windowed tree round-trips. The literal restores as the set's window ephemeral, so mut_ephemeral can 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 raises gp.ProgramError, not OverflowError.
  26. PrimitiveTree.from_string rejects extra tokens and incomplete calls. add(ARG0, 2, 3) and add(ARG0) no longer stringify as a leftover leaf and compile as the constant \(3\) or the identity. The root must return a subtype of prim_set.ret; DEAP accepted any root type. Invalid programs raise gp.ProgramError, a subclass of ValueError and TypeError, and compile_tree / lower_tree check every leaf against its slot with the same rules, so a compiled tree's text parses back.
  27. affine_scale fits Keijzer \(a + b\,f(x)\) on the same valid= mask as case_errors. Darwinian callers use the scaled series only for fitness / case errors. Lamarckian write_affine_scale writes \(a\) and \(b\) back as ephemerals wrapping a PrimitiveTree, or as wrapping Slim deltas, then invalidates fitness and the compile cache for that expression.
  28. LinearPolicyProgram, PushPolicyProgram, and step_policy_loop implement the private Push GP loop behind the P11–P14 firewall. policy_observe supplies summary observations; linear_policy_decide / push_policy_decide emit discrete action tokens; apply_policy_action dispatches them. No column loads, no Window Push type, and no public PushTree on gp or tools. Tapes remain the only interpret_tapes target. Not a second public genome (Push GP P19).
  29. On the opcode backend, interpret_tapes hash-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, warmup nan contract, and per-tape fill semantics 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 about rtol=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. Its rolling_std, rolling_corr, rolling_cov, and rolling_beta center 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 single 3.4e300 sample or only 1e-170 deviations, is scaled by a power of two first, so it gets its true finite value instead of inf, nan, or 0. See the columnar GP tutorial.
  30. HARM natural_histogram does not wrap hist[-1] when a tree has size \(0\). The left-neighbor bin is updated only for ind_size >= 1, matching the existing ind_size - 2 guard.
  31. register_gp wires the standard tree-GP toolbox: clone_individual, compile_tree, half-and-half init, one-point crossover, uniform mutation, and a height static_limit. columnar_pset builds the typed column set and registers the NumPy / window kits (optional pair-window, time-series, and window ephemeral) in one call. evaluate_columnar is the evaluate_batch helper: unique trees are lowered once, scored with interpret_tapes, and warmup nan samples are dropped from the MSE. DEAP leaves that wiring on every caller.
  32. MEMETIC_DEFAULT_N_GEN and MEMETIC_MAX_N_GEN document the recommended memetic polish. tune_ephemerals_budget caps inner n_gen to remaining n_evals, defaults to the small generation count, and judges trials on a caller-marked held-out exam when one exists. cap_tune_n_gen and estimate_tune_ephemerals_evals share the tune cost model with PolicyActionGuard. Raw tune_ephemerals is unchanged; policy caps stay on the guard.
  33. bounds_from_matrix, tape_interval, and tape_flags propagate column bounds through the builtin opcode kit and certificate identically-nan, constant, or warmup-hiding vwhere programs before interpret_tapes. evaluate_columnar(..., static_filter=True) skips them with the existing empty sentinel. Not a substitute for the runtime warmup contract or case_errors(..., valid=).
  34. cx_homologous swaps subtrees at the same root-to-node path when return types match, otherwise falling back to type-matched one-point. cx_one_point_semantic picks the type-matched partner whose interpret_tape row is nearest to the anchor subtree via semantic_nearest on batched interpret_tapes rows. cx_one_point remains the default mate.
  35. 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 a set of classes, so one seed bred different children in different processes and a checkpoint resumed in a fresh process left the original path.
  36. rename_arguments renames the argument terminal's name as well as its value. DEAP updated only value, so the terminal kept reporting the old ARG0 name after the rename.
  37. PrimitiveTree.from_string restores a number whose type does not fit its slot as that slot's ephemeral when the set has one. An ephemeral registered as float but drawing randint values prints as add(ARG0, 2); DEAP raised TypeError parsing its own str(tree), and the tree no longer round-tripped.
  38. PrimitiveTree.from_string checks every parenthesis and comma against the arity of each primitive, and str(tree) raises gp.ProgramError for a node list that is missing arguments or holds nodes after its complete root. str(tree) and every compile_tree backend also raise gp.ProgramError for an element that is not a Primitive or Terminal, such as the string "vadd", instead of AttributeError. DEAP split the text on () and , and dropped them, so vadd(a, b)) and vadd(a b) parsed, and str() of [vadd, a] printed a, silently discarding the primitive.
  39. The default python backend of compile_tree compiles 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. DEAP eval'd one nested expression and documented a 90-level ceiling; current Python raised a raw SyntaxError for a tree that from_string and lower_tree accept.
  40. mut_node_replacement and mut_shrink can 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 from randrange(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.
  41. make_column_pset and columnar_pset take strides=, 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, and delay and diff keep 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.
  42. interpret_tapes, evaluate_columnar, suffix_rescore, cx_one_point_semantic, and bind_tape take precision="float32", which keeps a float32 column matrix without the float64 copy, halving its memory. Columns widen to float64 as they are read, so the scores match the float64 table bit for bit, and warmup_numba(precisions=...) compiles the float32 kernels 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.