Skip to main content

ProbabilityModel

Trait ProbabilityModel 

Source
pub trait ProbabilityModel<B: Backend>: Send + Sync {
    type Params: Clone + Debug + Send + Sync;
    type State: Clone + Debug + Send + Sync;

    // Required methods
    fn fit(
        &self,
        params: &Self::Params,
        prev: Option<&Self::State>,
        population: Tensor<B, 2>,
        fitness: Tensor<B, 1>,
        device: &<B as BackendTypes>::Device,
    ) -> Self::State;
    fn sample(
        &self,
        state: &Self::State,
        n: usize,
        rng: &mut dyn Rng,
        device: &<B as BackendTypes>::Device,
    ) -> Tensor<B, 2>;
}
Expand description

A probabilistic model of a promising region of search space.

EDAs run a fitsample loop: each generation, fit estimates model parameters from the (already truncation-selected) population, then sample draws the next candidate population from the fitted model. The model fully replaces the crossover and mutation operators of a classical GA.

The two associated types separate static per-model configuration (Params, e.g. learning rates, initial means) from the evolving fitted statistics (State, e.g. per-dimension means and variances). State is deliberately Sync (a stronger bound than the Send-only crate::Strategy::State) so a future covariance-carrying CMA-ES state can be shared across threads; the same fit/sample shape is intended to host that model unchanged.

The fitness tensor is passed to fit so models that weight or rank the selected individuals (rank-μ updates, weighted MLE) can use it. The univariate models shipped here perform an unweighted maximum-likelihood fit and ignore it.

§Invariants

  • Prior path. When prev = None, the model builds its prior purely from params. On this path the population and fitness tensors are ignored; EdaStrategy’s init passes them as a 0 × 0 population and a length-0 fitness tensor, so a model must never read their contents when prev is None.
  • Host RNG only. All randomness in sample must come from the supplied rng. Implementations must never call Tensor::random or B::seed: Burn’s GPU PRNG kernels are seeded through process-global state, which interleaves across parallel strategy calls and breaks the per-stream determinism the crate guarantees. Sample on the host and upload with Tensor::from_data.
  • Selection order. The population rows handed to fit by EdaStrategy’s tell arrive in ascending-fitness order (best first), deterministically. Models that need the best or worst row (e.g. PBIL, cGA) must still compute argmin/argmax themselves rather than assume a fixed index — the ascending order is a convenience, not a contract a model may hard-code against.
  • Sync state. State requires Sync, deliberately exceeding crate::Strategy::State’s Send-only bound, to leave room for a future thread-shared CMA-ES state.
  • Sanitized input assumed. fit and sample are infallible by design. The EdaStrategy driver sanitizes fitness (NaN-inf) and clamps the selected-row count to ≥ 2 before calling fit, so the supported call path never supplies empty populations or non-finite fitness. Callers that invoke fit/sample directly (bypassing the driver) must uphold the same preconditions; otherwise behaviour is unspecified — implementations may emit NaN statistics or panic in sample.

§Examples

use burn::backend::Flex;
use burn::tensor::{Tensor, TensorData};
use rand::{rngs::StdRng, SeedableRng};
use rlevo_evolution::ProbabilityModel;
use rlevo_evolution::algorithms::eda::{UnivariateGaussian, UnivariateGaussianParams};

let device = Default::default();
let model = UnivariateGaussian;
let params = UnivariateGaussianParams::default_for(2);

// Fit to a tiny two-row, two-column selected population.
let pop = Tensor::<Flex, 2>::from_data(
    TensorData::new(vec![0.0_f32, 0.0, 2.0, 2.0], [2, 2]),
    &device,
);
let fitness = Tensor::<Flex, 1>::from_data(TensorData::new(vec![0.0_f32, 8.0], [2]), &device);
let state =
    ProbabilityModel::<Flex>::fit(&model, &params, None, pop.clone(), fitness.clone(), &device);
let next = ProbabilityModel::<Flex>::fit(&model, &params, Some(&state), pop, fitness, &device);

// Sample a fresh population from the fitted model.
let mut rng = StdRng::seed_from_u64(0);
let drawn: Tensor<Flex, 2> = model.sample(&next, 4, &mut rng, &device);
assert_eq!(drawn.dims(), [4, 2]);

Required Associated Types§

Source

type Params: Clone + Debug + Send + Sync

Static, per-run model configuration (learning rates, priors, …).

Source

type State: Clone + Debug + Send + Sync

Evolving fitted statistics carried generation to generation.

Required Methods§

Source

fn fit( &self, params: &Self::Params, prev: Option<&Self::State>, population: Tensor<B, 2>, fitness: Tensor<B, 1>, device: &<B as BackendTypes>::Device, ) -> Self::State

Fit the model to the selected population.

When prev = None the returned state is a prior derived purely from params (see the trait Invariants); population and fitness are ignored on that path. Otherwise the model is refit to the supplied selected rows (ascending fitness order). fitness is available for weighted or rank-based updates; unweighted models ignore it.

Source

fn sample( &self, state: &Self::State, n: usize, rng: &mut dyn Rng, device: &<B as BackendTypes>::Device, ) -> Tensor<B, 2>

Draw n candidate genomes from the fitted model.

All randomness must be drawn from rng (host RNG only — never Tensor::random/B::seed). The returned tensor has shape (n, D) where D is the model’s genome dimensionality.

§Panics

Implementations may panic if state was not produced by a prior fit call — e.g. a user-constructed state with non-finite mean or variance (concrete impls call Normal::new(...).expect(...), which panics on non-finite parameters).

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§