pub struct EvolutionaryHarness<B, S, F>{ /* private fields */ }Expand description
Wraps a Strategy into a BenchEnv so the benchmark harness can
drive it.
§Example
use burn::backend::Flex;
use rlevo_core::fitness::FitnessEvaluable;
use rlevo_core::evaluation::BenchEnv;
use rlevo_evolution::algorithms::ga::{GaConfig, GeneticAlgorithm};
use rlevo_evolution::fitness::FromFitnessEvaluable;
use rlevo_evolution::strategy::EvolutionaryHarness;
struct Sphere;
struct SphereFit;
impl FitnessEvaluable for SphereFit {
type Individual = Vec<f64>;
type Landscape = Sphere;
fn evaluate(&self, x: &Self::Individual, _: &Self::Landscape) -> f64 {
x.iter().map(|v| v * v).sum()
}
}
let device = Default::default();
let mut harness = EvolutionaryHarness::<Flex, _, _>::new(
GeneticAlgorithm::<Flex>::new(),
GaConfig::default_for(32, 5),
FromFitnessEvaluable::new(SphereFit, Sphere),
0, device, 100,
).expect("valid params");
harness.reset();
while !harness.step(()).done {}Each step runs one generation (ask → evaluate →
tell). The harness is the sole canonicaliser: it reads the fitness fn’s
ObjectiveSense, negates a
Minimize objective into the engine’s maximise space before tell, and
maps the metrics back to the declared sense for reporting. The reward
returned is the canonical best_fitness_ever directly (already
higher-is-better — no negation), so the per-episode cumulative return
(Σ step rewards) integrates the optimization trajectory. The harness only
exposes episode-level returns to reporters, so the “best at end” signal
would otherwise be lost.
§Determinism and parallel execution
Burn backends seed their tensor RNG through process-global state —
the flex backend uses a Mutex<Option<FlexRng>>, the
wgpu backend a per-device seeded stream. When multiple harness
instances run in parallel threads (e.g.
Evaluator::run_suite with the default rayon pool), their
interleaved B::seed(...) → Tensor::random(...) call pairs race on
that shared state and destroy bit-reproducibility across runs.
For deterministic reproduction, pass
EvaluatorConfig::num_threads = Some(1) or run one harness per
process. The tests/determinism.rs and tests/rastrigin_run_suite.rs
integration tests both enforce serial execution for this reason.
Implementations§
Source§impl<B, S, F> EvolutionaryHarness<B, S, F>
impl<B, S, F> EvolutionaryHarness<B, S, F>
Sourcepub fn new(
strategy: S,
params: S::Params,
fitness_fn: F,
seed: u64,
device: B::Device,
max_generations: usize,
) -> Result<Self, ConfigError>
pub fn new( strategy: S, params: S::Params, fitness_fn: F, seed: u64, device: B::Device, max_generations: usize, ) -> Result<Self, ConfigError>
Build a new harness from its parts.
The caller-supplied params are validated up front — this is the
harness consumption chokepoint (ADR 0026), so an invalid configuration
is rejected here rather than surfacing as a panic deep inside a
strategy’s tensor code.
The harness is lazily initialized — the first reset
call materializes the initial state on the supplied device.
§Errors
Returns a ConfigError when params fails Validate::validate,
naming the offending field and violated invariant.
Sourcepub fn with_observer(self, observer: SharedPopulationObserver) -> Self
pub fn with_observer(self, observer: SharedPopulationObserver) -> Self
Attach a per-generation PopulationObserver.
The observer is called once per step call, after the
canonical tracing::info!("evolution generation", …) event. It
receives a PopulationSnapshot
carrying the full per-individual fitness vector for the completed
generation. The intended consumer is a benchmark-tier recording sink
that persists population-level data alongside the scalar metric stream.
Attaching an observer adds one device→host transfer of the fitness tensor per generation; runs without an observer pay nothing.
Sourcepub fn latest_metrics(&self) -> Option<&StrategyMetrics>
pub fn latest_metrics(&self) -> Option<&StrategyMetrics>
Snapshot of the most recent generation’s metrics, if any.
Sourcepub fn generation(&self) -> usize
pub fn generation(&self) -> usize
Generation counter — number of completed tell calls.
Sourcepub fn best(&self) -> Option<(S::Genome, f32)>
pub fn best(&self) -> Option<(S::Genome, f32)>
Forward to Strategy::best when a state exists.
The strategy tracks the best genome in canonical (maximise) space;
the returned fitness is mapped back to the objective’s declared sense so
a Minimize landscape reads as its natural cost.
Sourcepub fn reset(&mut self)
pub fn reset(&mut self)
Reset to a fresh initial state.
Inherent shape (infallible): EvolutionaryHarness cannot legitimately
fail to reset — it is a deterministic optimization driver. The
BenchEnv trait impl wraps this in Ok(()) so the harness is
callable both directly (this method) and via the BenchEnv surface
when fed to Evaluator::run_suite.
Sourcepub fn step(&mut self, _action: ()) -> BenchStep<()>
pub fn step(&mut self, _action: ()) -> BenchStep<()>
Run one ask → evaluate → tell generation.
Inherent shape (infallible). The BenchEnv trait impl wraps this
in Ok(...). See Self::reset for the rationale.
§Panics
Panics if reset has not been called first. Also panics
if an observer is attached and the natural-fitness tensor cannot be read
back to host as f32 (a device→host transfer failure).
Trait Implementations§
Source§impl<B, S, F> BenchEnv for EvolutionaryHarness<B, S, F>
impl<B, S, F> BenchEnv for EvolutionaryHarness<B, S, F>
Source§type Observation = ()
type Observation = ()
Source§fn reset(&mut self) -> Result<Self::Observation, BenchError>
fn reset(&mut self) -> Result<Self::Observation, BenchError>
Source§fn step(
&mut self,
action: Self::Action,
) -> Result<BenchStep<Self::Observation>, BenchError>
fn step( &mut self, action: Self::Action, ) -> Result<BenchStep<Self::Observation>, BenchError>
action and advance the environment by one step. Read moreAuto Trait Implementations§
impl<B, S, F> !RefUnwindSafe for EvolutionaryHarness<B, S, F>
impl<B, S, F> !UnwindSafe for EvolutionaryHarness<B, S, F>
impl<B, S, F> Freeze for EvolutionaryHarness<B, S, F>
impl<B, S, F> Send for EvolutionaryHarness<B, S, F>
impl<B, S, F> Sync for EvolutionaryHarness<B, S, F>
impl<B, S, F> Unpin for EvolutionaryHarness<B, S, F>
impl<B, S, F> UnsafeUnpin for EvolutionaryHarness<B, S, F>where
S: UnsafeUnpin,
<S as Strategy<B>>::Params: UnsafeUnpin,
F: UnsafeUnpin,
<B as BackendTypes>::Device: UnsafeUnpin,
<S as Strategy<B>>::State: UnsafeUnpin,
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more