Skip to main content

MemeticWrapper

Struct MemeticWrapper 

Source
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(&params, &mut rng, &device);
let mut scorer = Sphere;
for _ in 0..3 {
    let (pop, asked) = strategy.ask(&params, &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(&params, 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>>,

Source

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>>,

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
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>>,

Source§

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)

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)

Refines a covered subset of the population, writes back the refined gains per the WritebackPolicy, then delegates to the inner tell.

§Flow
  1. Host-pull the fitness vector and one flat read-only host copy of the population; read [pop_size, dim] and the device.
  2. Compute coverage indices (Full = all; TopK = the k largest fitnesses, ties by lower index), then process them in ascending index order so RNG consumption is a pure function of the (fitness, index) ranking.
  3. Draw exactly one rng.next_u64() unconditionally (so the harness RNG stream position is policy-invariant) and derive two independent sub-streams: ls_rng for refinement (SeedPurpose::LocalSearch) and mask_rng for the writeback Bernoulli (SeedPurpose::Replacement). The split is load-bearing: mask draws never perturb refinement draws, which makes Partial(1.0) bit-identical to Lamarckian and Partial(0.0) to Baldwinian.
  4. 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) → one mask_rng Bernoulli per refined index).
  5. Write back only Lamarckian rows via slice_assign onto the original population tensor. When there are zero writeback rows, the exact tensor returned by ask is handed to the inner tell — no host round-trip.
  6. Rebuild the fitness tensor and delegate to the inner tell, returning its metrics verbatim alongside generation + 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)>

Delegates to the inner strategy’s best.

Source§

type Params = MemeticParams<<S as Strategy<B>>::Params, <L as LocalSearch<B>>::Params>

Static parameters for a run.
Source§

type State = MemeticState<<S as Strategy<B>>::State>

Generation-to-generation state.
Source§

type Genome = Tensor<B, 2>

Genome container produced by ask.

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>
where S: Unpin, L: Unpin, F: Unpin,

§

impl<B, S, L, F> UnsafeUnpin for MemeticWrapper<B, S, L, F>

§

impl<B, S, L, F> UnwindSafe for MemeticWrapper<B, S, L, F>
where S: UnwindSafe, L: UnwindSafe, F: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Adaptor<()> for T

Source§

fn adapt(&self)

Adapt the type to be passed to a metric.
Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> Downcast<T> for T

Source§

fn downcast(&self) -> &T

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoComptime for T

Source§

fn comptime(self) -> Self

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> Upcast<T> for T

Source§

fn upcast(&self) -> Option<&T>

Source§

impl<T> WasmNotSend for T
where T: Send,

Source§

impl<T> WasmNotSendSync for T

Source§

impl<T> WasmNotSync for T
where T: Sync,

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more