Skip to content

EA-as-PPL: two-layer refactor — standalone classic EC + fugue-native inference (0.2.0) - #19

Merged
alexnodeland merged 2 commits into
mainfrom
feat/ea-as-ppl-refactor
Jul 28, 2026
Merged

EA-as-PPL: two-layer refactor — standalone classic EC + fugue-native inference (0.2.0)#19
alexnodeland merged 2 commits into
mainfrom
feat/ea-as-ppl-refactor

Conversation

@alexnodeland

Copy link
Copy Markdown
Owner

Implements the fugue-evo half of the cross-repo EA-as-PPL plan (#18), consuming the upstream primitives from alexnodeland/fugue#45 (fugue-ppl 0.2.1). Two commits: Phases 0–3, then Phases 4–5.

Phase 0 — trait split + ppl feature gate

  • EvolutionaryGenome loses to_trace/from_trace/trace_prefix; they move to the new TraceGenome extension trait (genome::trace_genome, behind the ppl feature, default on). ChoiceValue re-export moves with them.
  • fugue-ppl becomes optional: --no-default-features --features std,parallel,checkpoint builds the entire classic layer (all 8 algorithms, operators, wasm crate) with zero fugue dependency — 595 tests pass in that config.

Phases 1–3 — the inference layer, rebuilt on fugue (fugue_integrationinference, deprecated alias kept)

  • Priors are programs: GenomePrior::model() -> fugue::Model<G> returns the decoded genome (the model's return value is the decode). Built-ins: UniformBoxPrior, GaussianPrior, BitStringPrior, PermutationPrior. The Prior enum and all hand-written density code (log_prior_density etc.) are deleted — every density is a ScoreGivenTrace replay of the target program.
  • The Boltzmann target is a literal program: prior.model().bind(|g| factor(β·f(g))). target_model() (fixed β) drives MH; smc_model() (untempered) drives SMC, where fugue's adaptive tempering applies β exactly once — fixing the old hand-rolled SMC's β double-counting.
  • EvolutionStep deletedEvolutionChain over fugue::adaptive_single_site_mh. The old proposal only perturbed F64 choices, so BitString/Permutation chains silently never moved; typed proposals now move every site kind. New regressions: test_bitstring_chain_moves, test_permutation_chain_moves.
  • Permutation's trace encoding is now the Lehmer code (ranks vs the shrinking available list), matching the sequential-categorical PermutationPrior — this is what makes single-site permutation moves live (a value encoding turns every single-site change into a duplicate; a design refinement over the plan's ambiguous "Categorical(remaining)" wording).
  • EvolutionarySMC deletedEvolutionSMC::run/run_with_kernel over fugue::adaptive_smc_with_kernel: adaptive ESS-driven β ladder, per-particle rejuvenation, the population-coupled CrossoverKernel, and an unbiased log-evidence estimate. EvolutionPosterior recovers genomes by decode-replay.
  • BayesianAdaptiveGA repointed to the new model API; conjugate Beta/Gamma backed by rand_distr.

Phase 4 — genetic programming as exact inference

ArithmeticGrammarPrior (inference/grammar.rs): a PCFG over expression trees as a fugue program with tree-path addresses. Structure lives in the choices, so subtree regeneration is fugue's ordinary single-site MH (a #leaf flip births/kills the subtree with automatic RJMCMC corrections) and subtree crossover is the crossover kernel with a shared-node-path union mask. Parsimony is the grammar prior itself.

Flagship examples/symbolic_regression_inference.rs: fits x²+1 by sampling the posterior over programs — MAP recovered by decode-replay (predictions match truth to 3 decimals), posterior predictive, and grammar comparison by Bayes factor (the parsimonious {Add,Sub,Mul} grammar beats the 12-function set by exp(10.6)). Pinned by test_symreg_recovers_known_expression.

Phase 5 — repositioning

Two-layer story in lib.rs docs, README, CLAUDE.md (with inference-layer invariants), and the website architecture page (rewritten from the stale ConditioningHandler content). CHANGELOG 0.2.0 entry carries the full migration guide. Version 0.1.1 → 0.2.0 (pre-1.0 breaking-minor).

Validation

  • Default features: 9/9 suites, 0 failures (includes 42 inference-layer tests).
  • --no-default-features --features std,parallel,checkpoint (fugue absent): 9/9 suites, 0 failures.
  • Regression anchors green throughout: EV-16 (conjugate SMC posterior, + new analytic evidence check), EV-52 (weighted trace = β·f), EV-90 (MH truncated-exponential mean 1.0746), EV-53 (conjugate updates/Thompson preference), plus the new dead-chain and symreg anchors.
  • clippy & fmt clean; wasm crate compiles unchanged; checkpoint format untouched (CHECKPOINT_VERSION = 1, no genome struct fields changed).

Depends on fugue#45 (path dependency already points at the sibling checkout; version = "0.2.1" is honored on registry resolution).

🤖 Generated with Claude Code

https://claude.ai/code/session_01HmWLBR9Zq6hPG5o7URDuNJ

alexnodeland and others added 2 commits July 28, 2026 09:44
…ce layer

Phase 0: EvolutionaryGenome loses to_trace/from_trace/trace_prefix; they move
to the TraceGenome extension trait (genome/trace_genome.rs) behind the new
`ppl` feature. fugue-ppl becomes an optional dependency; the classic layer
(all algorithms, operators, wasm crate) compiles fugue-free with
--no-default-features --features std,parallel,checkpoint (595 tests pass).

Phases 1-3: fugue_integration is rewritten as `inference` (deprecated alias
kept):
- GenomePrior replaces the closed Prior enum: the prior is an arbitrary fugue
  Model<G> returning the decoded genome. Ships UniformBoxPrior, GaussianPrior,
  BitStringPrior, PermutationPrior. All density code is deleted; scoring is
  ScoreGivenTrace replay.
- Permutation's trace encoding switches to Lehmer-code ranks so single-site
  moves decode to valid distinct permutations (a value encoding makes every
  single-site change a duplicate and freezes the chain).
- EvolutionModel<P, F> builds the Boltzmann target as a literal program:
  prior.model().bind(|g| factor(beta*f(g))). target_model() for MH (fixed
  beta), smc_model() untempered for SMC (beta applied exactly once by fugue's
  tempering - fixes the old double-counting).
- EvolutionStep deleted; EvolutionChain delegates to fugue
  adaptive_single_site_mh (typed proposals move every site kind). New
  dead-chain regressions: test_bitstring_chain_moves,
  test_permutation_chain_moves.
- EvolutionarySMC deleted; EvolutionSMC::run delegates to fugue
  adaptive_smc_with_kernel with the CrossoverKernel population move.
  EvolutionPosterior readouts recover genomes by decode-replay; log-evidence
  comes free and is checked against the analytic marginal likelihood.
- BayesianAdaptiveGA repointed to the new model API; fugue Beta/Gamma swapped
  for rand_distr.

Anchors kept green: EV-16 (conjugate SMC posterior), EV-52 (weighted trace =
beta*f), EV-90 (MH bounds/truncated mean), EV-53 (conjugate updates/Thompson).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HmWLBR9Zq6hPG5o7URDuNJ
…ioning

Phase 4 (inference/grammar.rs): ArithmeticGrammarPrior - expression trees as
a probabilistic context-free grammar program with tree-path addresses
(node/0/1#leaf, #func, #tkind, #var, #const). Because an execution's
structure is encoded in its own choices, fugue's generic machinery becomes
genetic programming: single-site MH on a structural site births/kills
subtrees with automatic RJMCMC corrections (subtree regeneration), and
subtree_crossover_mask() + fugue::CrossoverKernel grafts the subtrees under
one shared node path between particles (subtree crossover). Parsimony is the
grammar prior. EvolutionSMC::run_with_kernel added for explicit kernels.

Flagship: examples/symbolic_regression_inference.rs - symbolic regression of
x^2+1 posed as exact Bayesian inference (PCFG prior, Gaussian likelihood
factor, tempered SMC with both genetic moves, MAP by decode-replay,
posterior predictive, grammar comparison by Bayes factor). Pinned by
test_symreg_recovers_known_expression plus grammar log-prior and
depth-parsimony analytic tests.

Phase 5: two-layer repositioning - lib.rs crate docs, README tagline and
architecture sections, CLAUDE.md (with inference-layer invariants), website
architecture page rewritten as "Evolution as inference" (replacing stale
ConditioningHandler/ResamplingHandler content), CHANGELOG 0.2.0 entry with
full breaking-change migration notes, version 0.2.0, crate description
updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HmWLBR9Zq6hPG5o7URDuNJ
Copilot AI review requested due to automatic review settings July 28, 2026 13:54

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants