Features 1-10¶
Items in this decade from the roadmap overview. Status and surfaces live on that table. Empty slots stay empty until an item is numbered into this range.
1. Two-input causal windows¶
What. Optional helpers next to
gp.add_window_primitives that register binary rolling operators
over two Array arguments and a Window: rolling_corr,
rolling_cov, and rolling_beta. Each stays causal — output
sample \(t\) depends on the two series on \([t - n + 1, t]\) only.
Samples without enough history are nan, matching the unary kit.
Python, opcode, and Numba paths ship together with parity tests.
Today. add_pair_window_primitives registers causal
rolling_corr, rolling_cov, and rolling_beta. Moments use the
population divisor; beta is the OLS slope of the first series on
the second. The unary kit is unchanged. Python, opcode, and Numba
paths agree.
Benefit. The type tags already allow a second Array. Callers
stop forking the primitive set for the most common two-series
aggregates. Time-series GP and general columnar programs gain the
same language.
Scope. Kit functions plus opcodes, not a named-indicator library. Every new opcode needs a Python reference implementation.
Related: Columnar programs, Genetic programming.
2. Causal time-series unaries¶
What. A second optional kit (for example add_ts_primitives)
for causal operators that the unary rolling stats do not cover:
- time-series rank of the current sample inside a trailing window
(
ts_rank) - index of the window maximum / minimum (
ts_argmax,ts_argmin) - exponential decay or a weighted trailing sum
- a protected one-step change (
delta/delay) that keeps the existingnanwarmup contract
Cross-sectional rank across many series is out of scope unless the caller stacks those series as columns and treats the operation as row-wise.
Today. add_ts_primitives registers causal ts_rank,
ts_argmax, and ts_argmin over an Array and a Window. Rank is
the 1-based average rank of the current sample, scaled by
\((r - 1) / (n - 1)\) so a unique window low is \(0\) and a unique high
is \(1\). A window of 1 is nan. Arg-extremum is the age of the
extreme (0 is the current sample); a tie keeps the most recent.
delay, diff, and ema stay on the unary window kit. Python,
opcode, and Numba paths agree.
Benefit. Programs can say “where this sample sits in the last \(n\) values” and “when the trailing extremum occurred” without each user binding a custom opcode. Same three-backend parity rule as item 1.
Scope. Optional kit, not a domain catalog of named indicators.
Composable windows plus bind_numba_opcode remain the way to add
a specialized kernel.
3. Incremental window kernels¶
What. Keep the public window API. Change the Numba (and, if
needed, NumPy) kernels from a nested scan over (t, j) to running
aggregates: \(O(\text{rows})\) sum / mean / std, and a monotonic
deque for min / max. nan warmup and “nan in the window ⇒ nan
out” for min / max stay identical. The current Python functions
remain the definition; new kernels are accepted only with parity
tests.
Today. Numba walks each rolling opcode in \(O(\text{rows})\):
running sums for rolling_{sum,mean,std} and the pair moments,
and a monotonic index ring for rolling_{min,max} and
ts_argmax / ts_argmin. ts_rank is still a per-window scan.
Python and the opcode backend stay the definition —
sliding_window_view plus a ufunc reduce, and for rolling_std and
the pair moments each window is centered on its own mean before
squaring, in row blocks. The kernel keeps running sums and recenters
a window only once they have lost too many digits, so its result
follows the oracle without depending on how either side rounds.
Parity tests compare Numba to that oracle, including nan recovery,
\(\pm\inf\), columns far from zero, and a variance that collapses.
Benefit. A tree with several rolling nodes on a long column and windows of tens to hundreds of samples spends almost all of its evaluation time here. Same programs, same results, less work per sample. Every current window user benefits; no new primitive names.
4. Batch tape evaluation¶
What. A compiled entry point of the form
interpret_tapes(tapes, matrix) -> (n_individuals, n_rows): one
call, many tapes, one packed (rows, columns) matrix. Document a
recipe that compiles each unique tree string once and then scores
the generation in one shot. evaluate_batch on the toolbox already
replaces map for a generation; this is the matching interpreter.
Today. interpret_tapes(tapes, matrix) returns
(n_individuals, n_rows) from one packed (rows, columns) matrix.
The opcode path unpacks columns once. The Numba path is a compiled
loop over jagged tapes; parallel=True uses one workspace per
thread. Algorithms still call toolbox.evaluate_batch(invalids)
when that operator is registered — the matching interpreter is
this function. compile_tree is unchanged: the opcode runner
still stacks columns per individual.
Benefit. Columnar evaluation becomes population-wide instead of
only per tree. One kernel launch, one matrix, no per-individual
repack or result copy. That is the gap evaluate_batch is waiting
on.
Related: Multiprocessing, Push GP P1.
5. Down-sampled and informed lexicase¶
What. Optional cases= on sel_lexicase and
sel_epsilon_lexicase so one selector call filters on an explicit
subset of case indices. Informed sampling is a case-subset
builder used by lexicase, not a third ad-hoc selector.
sel_lexicase defaults do not change.
Today. sel_lexicase and sel_epsilon_lexicase accept
cases= (keyword-only). When omitted, every fitness index is used.
sample_informed_cases builds a subset by farthest-first traversal
of Hamming distances between case solve vectors (Boldi et al.), so
synonymous cases are not over-sampled. A case is solved when its
value is \(0\). Evaluation stays on the caller.
Benefit. When fitness is a vector of cases — folds, series, regimes, or per-point residuals — full lexicase is \(O(\textit{sel\_count} \times n \times \textit{cases})\) and always uses every case. A subset buys more individuals per evaluation budget. Informed sampling avoids wasting that budget on cases that the current population already treats as interchangeable.
Related: Push GP P2, item 27, item 28.
6. Case-structured evaluation helper¶
What. A thin utility, for example
case_errors(predicted, target, ranges), that given a predicted
series, a target series, and a boolean mask or a list of index
ranges, returns one loss per range. Those values are the cases
lexicase (item 5) and a multi-objective fitness vector consume.
A short addition to the
columnar tutorial states the
honest pattern: ignore non-finite warmup, do not score a vwhere
that hid a nan, and keep prediction–target alignment on the
caller.
Today. tools.case_errors turns aligned predicted and target
series into one MSE per explicit [start, stop) range or per
contiguous True run in a boolean mask. Non-finite samples are
skipped by default; an optional valid mask covers the vwhere
warmup trap documented in the columnar tutorial. There is still no
built-in chronological split or metric catalog.
Benefit. The usual “several segments, hide some” recipe is a few lines if the helper exists, and a class of off-by-one and lookahead bugs if everyone writes it. This is not a split algorithm, a metric catalog, or an evaluation framework — only the reduction from a series to per-case errors.
Scope. No built-in chronological split algorithm. No public fitness functions that assume application-specific state the library does not have.
Related: Push GP P3.
7. Non-bloating semantic variation¶
What. SLIM-style operators next to the existing wrapping pair:
- inflate mutation (classic geometric semantic mutation) plus a
deflate mutation that shortens the genotype while remaining
a ball mutation of radius
msin semantic space - a size-preserving semantic crossover (XOBDn / SLIMMER, or a minimal subset) whose offspring are no larger than the parents
Reuse the current requirement that the primitive set expose lf,
add, mul, and sub. Keep cx_semantic and mut_semantic
for compatibility. A small example that combines HARM with SLIM
shows a non-bloating semantic path.
Today. mut_slim_inflate, mut_slim_deflate, mut_slim, and
cx_slim_donor implement SLIM+SIG2 inflate/deflate mutation and
best-donor crossover (XOBDn). The SlimTree genotype backs those
operators only; standard GP continues to use PrimitiveTree.
cx_semantic and mut_semantic remain the classical wrapping
operators. An example combines HARM with SLIM semantic variation.
Benefit. Geometric semantic variation makes crossover and
mutation geometric in the space of input–output vectors, which
turns a convex loss into a unimodal landscape. Deflate and
size-preserving crossover keep that landscape without producing
trees that cannot be read or that trip static_limit every
generation. HARM remains the bloat control for standard (syntactic)
variation.
8. Quality-diversity archive¶
What. A GridArchive (or equivalent) in records: the caller
supplies a behavior descriptor, then add and random_elites for
variation. A thin ea_map_elites can wrap
evaluate_invalid + archive + var_or. Typical descriptor
coordinates are program length, output variance, or how often a
mask is true — whatever the caller measures.
Today. GridArchive bins caller-supplied behavior descriptors into a
uniform grid and keeps the best individual per cell. add and
random_elites are the variation surface; ea_map_elites wraps
evaluate_invalid, archive updates, and var_or. Fitness stays on
ind.fitness; descriptors stay on the caller.
Benefit. Quality-diversity keeps the best individual per cell instead of a single champion or a single Pareto front. That is the usual antidote to one overfit program when many competent shapes exist. The archive is a data structure, not a framework: no mandatory algorithm class, no learned QD / meta-BBO.
Related: Records, Push GP P4.
9. Compile and clone path¶
What. Four small changes that keep the same APIs and cut waste on long columnar runs:
- LRU compile cache — replace the current “store 1024 entries, then clear the whole dict” policy so a diverse working set is not evicted together.
- Packed matrix on the opcode backend — accept a pre-packed
(rows, columns)array the way the Numba backend already does, instead ofnumpy.stackper individual. - Numba interpreter cache — compile the VM with
cache=True(or an explicit cache directory) so spawned workers do not re-JIT the interpreter in every process. static_limitandclone_individual— when the individual is a sequence of immutable nodes, reject oversized offspring withclone_individualinstead ofdeepcopy. If that is too sharp a change, document that a decorated GP toolbox must register the fast clone (today the decorator alwaysdeepcopys).
Today. CompileCache evicts one LRU entry at a time instead of
clearing the whole compile cache at 1024 entries. The opcode backend
accepts a pre-packed (rows, columns) matrix like Numba.
gp.warmup_numba() sets a default NUMBA_CACHE_DIR when unset so
spawned workers reload the compiled interpreter from disk.
static_limit rejects oversized offspring with
clone_individual instead of deepcopy.
Benefit. Multi-hour runs with many unique trees keep their compile working set, stop restacking columns, start faster in worker processes, and do not recopy whole trees on every rejected variation. No new language, no new selector.
Related: Performance, Differences with DEAP.
10. Vectorized lexicase and plexicase¶
What. Filter lexicase cases against a dense \((n, \textit{cases})\) error matrix instead of a Python loop of list comprehensions. Plexicase (Ding, Chen, Spector) is a tractable approximation of lexicase’s selection distribution: faster, and it yields a probability that can be annealed or mixed. Plexicase is in scope only if item 5 is not enough and profiles show selection dominating evaluation.
Today. fitness_case_matrix packs fitness.values once per
generation. sel_lexicase, sel_epsilon_lexicase, and
sample_informed_cases accept optional matrix= so informed
down-sampling and lexicase filtering share one NumPy array.
Filtering is vectorized; golden RNG streams are unchanged.
Plexicase remains deferred until a profile shows selection still
dominates after downsampling (item 5) and this path.
Benefit. When cases are numerous (many folds, many series, or per-point residuals), selection becomes the next bottleneck after window kernels. A matrix filter is also the input format item 5 needs. Plexicase is an optimization of the selector, not a replacement for down-sampling.