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 fit → sample 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 fromparams. On this path thepopulationandfitnesstensors are ignored;EdaStrategy’sinitpasses them as a0 × 0population and a length-0fitness tensor, so a model must never read their contents whenprevisNone. - Host RNG only. All randomness in
samplemust come from the suppliedrng. Implementations must never callTensor::randomorB::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 withTensor::from_data. - Selection order. The population rows handed to
fitbyEdaStrategy’stellarrive 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. Syncstate.StaterequiresSync, deliberately exceedingcrate::Strategy::State’sSend-only bound, to leave room for a future thread-shared CMA-ES state.- Sanitized input assumed.
fitandsampleare infallible by design. TheEdaStrategydriver sanitizes fitness (NaN→-inf) and clamps the selected-row count to≥ 2before callingfit, so the supported call path never supplies empty populations or non-finite fitness. Callers that invokefit/sampledirectly (bypassing the driver) must uphold the same preconditions; otherwise behaviour is unspecified — implementations may emitNaNstatistics or panic insample.
§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, ¶ms, None, pop.clone(), fitness.clone(), &device);
let next = ProbabilityModel::<Flex>::fit(&model, ¶ms, 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§
Required Methods§
Sourcefn 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 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.
Sourcefn sample(
&self,
state: &Self::State,
n: usize,
rng: &mut dyn Rng,
device: &<B as BackendTypes>::Device,
) -> Tensor<B, 2>
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".