Skip to content

[GGGP M1] Generic GE engine over evoforge (GrammarEngine + traits) #2

Description

@mnemonik-dev

Summary

Milestone M1: turn the M0 decoder into a running Grammatical-Evolution engine by reusing
evoforge. Key reuse (locked D1): GE codons are encoded as evoforge Int genes, so the existing
population / tournament / elitism / gaussian-mutation / ask-tell run GE with no new operator
code
. Introduce the Target and Fitness traits so the engine stays DSL-agnostic.

Spec: https://github.com/mnemonik-dev/genetic_algorithms/blob/main/docs/technical-reference/gggp-implementation-spec.md (§3.2, §3.4, §3.5, §4, §11 reuse map)

⭐ Reuse from autonomous-eden/gggp_bundle (reference, not wholesale)

  • Target/phenotype shapegggp_bundle/rust/src/gggp/phenotype.rs: the Phenotype trait +
    OutputType::{SourceCode(String), Embedding, GlyphMap}. Model our Target::Artifact on
    OutputType::SourceCode; drop the embedding/glyph variants.
  • Engine cross-checkgggp_bundle/rust/src/gggp/mod.rs: Gggp (init_population,
    calc_fitnesses, sort_population, crossover, mutation) and select_tournament /
    SelectionMatrix. Do NOT port Gggp wholesale — D1 says reuse evoforge's engine. Use these
    only as a behavior reference / sanity check.
  • Their crossover/mutation are node/subtree-aware (CFG-GP style) — that's M3 territory; M1 stays on
    evoforge's linear ops over the Int-codon genome.

Scope

  • src/codon.rscodon_schema(genome_len, max_codon) -> Vec<evoforge::GeneSpec> (all GeneType::Int, range [0, max_codon]).
  • src/target.rstrait Target { type Artifact; type Error; fn grammar(&self)->&Grammar; fn build(&self,&Derivation)->Result<Artifact,Error>; fn repair(&self,&mut Artifact); fn validate(&self,&Artifact)->Result<(),Error>; } + trait Fitness<A> { fn score(&mut self,&A)->f64; } (blanket impl for FnMut(&A)->f64) + Candidate<A>.
  • src/engine.rsGrammarEngine<T: Target> wrapping evoforge::Engine over codon_schema:
    new(target, GrammarConfig), ask(batch) -> Vec<Candidate<T::Artifact>> (decode → buildrepair
    validate; drop/penalize failures), tell(results), run_to_completion(fitness), best(),
    snapshot() -> GrammarSnapshot (serde: grammar + codons + best + stats).
  • GrammarConfig { genome_len, max_codon, map: MapConfig, evo: evoforge::EvolutionConfig, metadata: serde_json::Value }.
  • A trivial in-crate Target (the toy grammar from M0) for tests; a toy Fitness.

Acceptance criteria

  • cargo test -p evoforge-grammar0 failed.
  • Determinism: two GrammarEngine runs with identical seed + grammar + config produce byte-identical
    snapshot() JSON (golden fixture committed under tests/fixtures/).
  • Convergence: on a toy Fitness (e.g. "reward outputs containing token X"), best().fitness
    strictly improves from generation 0 to the final generation within a fixed budget
    (population × generations).
  • Validity at the boundary: ask() never returns a candidate whose artifact fails Target::validate.
  • cargo fmt --check + cargo clippy -- -D warnings clean.

Verify

cargo test -p evoforge-grammar
cargo run -p evoforge-grammar --example evolve_toy   # now runs a full GE loop

Anti-goals (do NOT)

  • Do not implement the Archimedes adapter here (M2) — use the toy Target only.
  • Do not add one-point crossover yet (D2 → M3); reuse evoforge's Uniform.
  • Do not add NSGA-II / Vec<f64> fitness (D5 → v2) — scalar only.
  • Do not port gggp_bundle's Gggp engine wholesale (D1 — reuse evoforge). Do not add
    Python/PyO3 (D7). Do not modify evoforge core.

Notes

Depends on M0 (#1). Blocks M2 (#3).

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions