EvoForge is a Rust-native evolutionary optimization toolkit.
This repository provides a generic GA core without Python bindings, trading-specific backtests, Redis telemetry, or NumPy/PyO3 dependencies.
- Numeric genomes backed by
Vec<f64>. - Typed gene schema: float, int, bool, categorical.
- Tournament selection.
- Uniform crossover.
- Gaussian mutation with boundary reflection.
- Elitism.
- Deterministic seeded RNG, including generated genome IDs and lineage IDs.
- Ask/tell optimization loop for external fitness evaluators.
- Bounded
run_to_completionAPI for simple in-process fitness functions. - Golden fixture tests for full seeded snapshots.
- JSON roundtrip helpers on
GeneSpecandEvolutionConfig.
- Grammar-guided GP. (Now provided by the
evoforge-grammarsibling crate — see below — keeping the core itself GGGP-free.) - Tree/program genomes.
- Trading backtest logic.
- Python bindings.
- Distributed workers.
- Persistence/checkpoints.
Those should be added as separate modules or crates once the generic core is stable.
evoforge-grammar— grammar-guided GP (Grammatical Evolution) on top of this core. It decodes an arbitrary context-free grammar from an integer codon vector, with the codons encoded asevoforgeIntgenes so the core engine (selection, crossover, mutation, elitism, ask/tell, deterministic RNG) drives the search unchanged. ATarget/Fitnesstrait pair keeps it DSL-agnostic. Status: M0 (generic CFG + GE decoder) and M1 (GE engine) implemented; M2 (a first DSL target) next. Seeevoforge-grammar/README.md.
use evoforge::{Engine, EvolutionConfig, GeneSpec, GeneType};
let schema = vec![
GeneSpec::new("x", -10.0, 10.0, 0.0, GeneType::Float),
GeneSpec::new("y", -10.0, 10.0, 0.0, GeneType::Float),
];
let config = EvolutionConfig {
population_size: 64,
max_generations: 100,
seed: Some(42),
..EvolutionConfig::default()
};
let mut engine = Engine::new(schema, config).unwrap();
engine
.run_to_completion(|genes| {
let x = genes[0];
let y = genes[1];
-(x * x + y * y)
})
.unwrap();
let best = engine.best_genome().unwrap();
println!("best fitness = {:?}", best.fitness);For distributed or external evaluation, use the lower-level ask/tell API:
let batch = engine.ask(64);
let results = batch
.into_iter()
.map(|candidate| {
let fitness = expensive_external_evaluation(&candidate.genes);
(candidate.id, fitness)
})
.collect();
engine.tell(results).unwrap();When EvolutionConfig.seed is set, EvoForge must produce the same full
snapshot for the same crate version, schema, config, and sequence of fitness
values. The golden fixture at
tests/fixtures/sphere_seed_42_snapshot.json verifies:
- generated UUIDs,
- parent lineage,
- gene values,
- best genome,
- generation count,
- population statistics.
The serialized shape of GeneSpec, EvolutionConfig, and the snapshot types
is treated as part of the public API within a major version.
- Patch releases may add new optional fields with defaults.
- Patch releases must not reinterpret existing fields.
- Any change that alters seeded snapshot output requires a new golden fixture and an explicit release note.
- Any breaking serialized-format change should wait for a major version bump.
This means a deterministic run should stay deterministic across patch releases, but exact byte-for-byte JSON is only guaranteed for the same schema, config, seed, and crate version family.
Run:
cargo testThe core crate has no PyO3 dependency. Python bindings, if added later, should live behind an optional feature or in a separate crate.