pub struct MemeticWrapper<B, S, L, F>where
B: Backend,
S: Strategy<B, Genome = Tensor<B, 2>>,
L: LocalSearch<B>,
F: BatchFitnessFn<B, Tensor<B, 2>>,{ /* private fields */ }Expand description
Wraps an inner Strategy with per-individual LocalSearch refinement.
MemeticWrapper is itself a Strategy<B, Genome = Tensor<B, 2>>, so it
composes with any real-valued strategy and drops into
EvolutionaryHarness unchanged.
§The two fitness instances
The harness owns its own fitness instance (it calls evaluate_batch once
per generation to score the asked population); this wrapper owns a separate
instance behind a Mutex, used only to score local-search probes.
If F is stateful (counters, caches, RNG), the two instances must share
that state via interior mutability (e.g. Arc<AtomicUsize>) — otherwise
they silently diverge. A naive #[derive(Clone)]-then-pass approach gives
each instance an independent counter, and an evaluation-budget accounting
across both will under-count. The headline Rastrigin benchmark shares a
single Arc<AtomicUsize> eval counter across both instances for exactly this
reason.
§Example
Wrap Differential Evolution with hill-climbing refinement and drive a couple of generations by hand:
use burn::backend::Flex;
use burn::tensor::{Tensor, TensorData, backend::Backend};
use rand::{rngs::StdRng, SeedableRng};
use rlevo_evolution::Strategy;
use rlevo_evolution::algorithms::de::{DeConfig, DifferentialEvolution};
use rlevo_evolution::algorithms::memetic::{
CoveragePolicy, MemeticParams, MemeticWrapper, WritebackPolicy,
};
use rlevo_evolution::fitness::BatchFitnessFn;
use rlevo_evolution::local_search::{HillClimbing, HillClimbingParams};
use rlevo_core::bounds::Bounds;
// Sphere objective: sum of squares per row (a cost → Minimize).
use rlevo_core::objective::ObjectiveSense;
struct Sphere;
impl<B: Backend> BatchFitnessFn<B, Tensor<B, 2>> for Sphere {
fn evaluate_batch(
&mut self,
pop: &Tensor<B, 2>,
device: &B::Device,
) -> Tensor<B, 1> {
let squared = pop.clone() * pop.clone();
squared.sum_dim(1).squeeze_dim::<1>(1)
}
fn sense(&self) -> ObjectiveSense { ObjectiveSense::Minimize }
}
let device = Default::default();
let bounds = Bounds::new(-5.12, 5.12);
let strategy = MemeticWrapper::<Flex, _, _, _>::new(
DifferentialEvolution::<Flex>::new(),
HillClimbing,
Sphere,
);
let params = MemeticParams {
inner: DeConfig::default_for(16, 4),
local: HillClimbingParams::default_for(bounds),
writeback: WritebackPolicy::Lamarckian,
coverage: CoveragePolicy::TopK { k: 2 },
};
let mut rng = StdRng::seed_from_u64(0);
let mut state = strategy.init(¶ms, &mut rng, &device);
let mut scorer = Sphere;
for _ in 0..3 {
let (pop, asked) = strategy.ask(¶ms, &state, &mut rng, &device);
// The harness would do this; here we score it ourselves.
let fitness = scorer.evaluate_batch(&pop, &device);
let (next, _metrics) = strategy.tell(¶ms, pop, fitness, asked, &mut rng);
state = next;
}
assert!(strategy.best(&state).is_some());Implementations§
Source§impl<B, S, L, F> MemeticWrapper<B, S, L, F>where
B: Backend,
S: Strategy<B, Genome = Tensor<B, 2>>,
L: LocalSearch<B>,
F: BatchFitnessFn<B, Tensor<B, 2>>,
impl<B, S, L, F> MemeticWrapper<B, S, L, F>where
B: Backend,
S: Strategy<B, Genome = Tensor<B, 2>>,
L: LocalSearch<B>,
F: BatchFitnessFn<B, Tensor<B, 2>>,
Sourcepub fn new(inner: S, local: L, fitness: F) -> Self
pub fn new(inner: S, local: L, fitness: F) -> Self
Builds a memetic wrapper from an inner strategy, a local searcher, and a fitness function used only for local-search probes.
The harness owns a separate fitness instance; if F is stateful
(counters, caches, RNG), the two instances must share that state via
interior mutability (e.g. Arc<AtomicUsize>) — otherwise they silently
diverge. See the type-level docs.
Trait Implementations§
Source§impl<B, S, L, F> Debug for MemeticWrapper<B, S, L, F>where
B: Backend,
S: Strategy<B, Genome = Tensor<B, 2>>,
L: LocalSearch<B>,
F: BatchFitnessFn<B, Tensor<B, 2>>,
impl<B, S, L, F> Debug for MemeticWrapper<B, S, L, F>where
B: Backend,
S: Strategy<B, Genome = Tensor<B, 2>>,
L: LocalSearch<B>,
F: BatchFitnessFn<B, Tensor<B, 2>>,
Source§impl<B, S, L, F> Strategy<B> for MemeticWrapper<B, S, L, F>where
B: Backend,
S: Strategy<B, Genome = Tensor<B, 2>>,
L: LocalSearch<B>,
F: BatchFitnessFn<B, Tensor<B, 2>>,
impl<B, S, L, F> Strategy<B> for MemeticWrapper<B, S, L, F>where
B: Backend,
S: Strategy<B, Genome = Tensor<B, 2>>,
L: LocalSearch<B>,
F: BatchFitnessFn<B, Tensor<B, 2>>,
Source§fn init(
&self,
params: &Self::Params,
rng: &mut dyn Rng,
device: &<B as BackendTypes>::Device,
) -> Self::State
fn init( &self, params: &Self::Params, rng: &mut dyn Rng, device: &<B as BackendTypes>::Device, ) -> Self::State
Delegates to the inner strategy’s init and seeds the memetic
generation counter to zero.
Source§fn ask(
&self,
params: &Self::Params,
state: &Self::State,
rng: &mut dyn Rng,
device: &<B as BackendTypes>::Device,
) -> (Self::Genome, Self::State)
fn ask( &self, params: &Self::Params, state: &Self::State, rng: &mut dyn Rng, device: &<B as BackendTypes>::Device, ) -> (Self::Genome, Self::State)
Pure delegation to the inner strategy’s ask. The generation counter is
unchanged here — it increments only in tell.
Source§fn tell(
&self,
params: &Self::Params,
population: Self::Genome,
fitness: Tensor<B, 1>,
state: Self::State,
rng: &mut dyn Rng,
) -> (Self::State, StrategyMetrics)
fn tell( &self, params: &Self::Params, population: Self::Genome, fitness: Tensor<B, 1>, state: Self::State, rng: &mut dyn Rng, ) -> (Self::State, StrategyMetrics)
Refines a covered subset of the population, writes back the refined gains
per the WritebackPolicy, then delegates to the inner tell.
§Flow
- Host-pull the fitness vector and one flat read-only host copy of the
population; read
[pop_size, dim]and the device. - Compute coverage indices (
Full= all;TopK= theklargest fitnesses, ties by lower index), then process them in ascending index order so RNG consumption is a pure function of the(fitness, index)ranking. - Draw exactly one
rng.next_u64()unconditionally (so the harness RNG stream position is policy-invariant) and derive two independent sub-streams:ls_rngfor refinement (SeedPurpose::LocalSearch) andmask_rngfor the writeback Bernoulli (SeedPurpose::Replacement). The split is load-bearing: mask draws never perturb refinement draws, which makesPartial(1.0)bit-identical toLamarckianandPartial(0.0)toBaldwinian. - Lock the fitness once, refine each covered row, always set
refined_fit[i]to the refined fitness, and decide writeback (Lamarckian → always; Baldwinian → never;Partial(p)→ onemask_rngBernoulli per refined index). - Write back only Lamarckian rows via
slice_assignonto the original population tensor. When there are zero writeback rows, the exact tensor returned byaskis handed to the innertell— no host round-trip. - Rebuild the fitness tensor and delegate to the inner
tell, returning its metrics verbatim alongsidegeneration + 1.
Refinement runs on every tell, including the first. For a wrapped
DE this means gen-0 refinement happens before DE’s empty-fitness sentinel
stash; under Baldwinian writeback the inner population still carries the
unrefined genomes but the refined fitness, which raises DE’s greedy
replacement bar — the intended Baldwin effect.
Refined fitness is never clamped against the old fitness: the
LocalSearch contract already guarantees monotone non-worsening, and
clamping would manufacture a stale fitness on Lamarckian rows.
Source§fn best(&self, state: &Self::State) -> Option<(Self::Genome, f32)>
fn best(&self, state: &Self::State) -> Option<(Self::Genome, f32)>
Delegates to the inner strategy’s best.
Source§type Params = MemeticParams<<S as Strategy<B>>::Params, <L as LocalSearch<B>>::Params>
type Params = MemeticParams<<S as Strategy<B>>::Params, <L as LocalSearch<B>>::Params>
Auto Trait Implementations§
impl<B, S, L, F> !Freeze for MemeticWrapper<B, S, L, F>
impl<B, S, L, F> !RefUnwindSafe for MemeticWrapper<B, S, L, F>
impl<B, S, L, F> Send for MemeticWrapper<B, S, L, F>
impl<B, S, L, F> Sync for MemeticWrapper<B, S, L, F>
impl<B, S, L, F> Unpin for MemeticWrapper<B, S, L, F>
impl<B, S, L, F> UnsafeUnpin for MemeticWrapper<B, S, L, F>
impl<B, S, L, F> UnwindSafe for MemeticWrapper<B, S, L, F>
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