use std::fmt::Debug;
use std::marker::PhantomData;
use burn::module::Module;
use burn::tensor::{Tensor, backend::Backend};
use rand::Rng;
use crate::param_reshaper::ModuleReshaper;
use crate::strategy::{Strategy, StrategyMetrics};
pub struct WeightOnly<B, S, M>
where
B: Backend,
S: Strategy<B, Genome = Tensor<B, 2>>,
M: Module<B>,
{
inner: S,
reshaper: ModuleReshaper<B, M>,
_backend: PhantomData<fn() -> B>,
}
impl<B, S, M> WeightOnly<B, S, M>
where
B: Backend,
S: Strategy<B, Genome = Tensor<B, 2>>,
M: Module<B>,
{
pub fn new(inner: S, template: M) -> Self {
Self {
inner,
reshaper: ModuleReshaper::new(template),
_backend: PhantomData,
}
}
#[must_use]
pub fn num_params(&self) -> usize {
self.reshaper.num_params()
}
#[must_use]
pub fn reshaper(&self) -> &ModuleReshaper<B, M> {
&self.reshaper
}
#[must_use]
pub fn inner(&self) -> &S {
&self.inner
}
}
impl<B, S, M> Debug for WeightOnly<B, S, M>
where
B: Backend,
S: Strategy<B, Genome = Tensor<B, 2>>,
M: Module<B>,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WeightOnly")
.field("num_params", &self.reshaper.num_params())
.finish_non_exhaustive()
}
}
impl<B, S, M> Strategy<B> for WeightOnly<B, S, M>
where
B: Backend,
S: Strategy<B, Genome = Tensor<B, 2>>,
M: Module<B> + Sync,
{
type Params = S::Params;
type State = S::State;
type Genome = Tensor<B, 2>;
fn init(
&self,
params: &Self::Params,
rng: &mut dyn Rng,
device: &<B as burn::tensor::backend::BackendTypes>::Device,
) -> Self::State {
self.inner.init(params, rng, device)
}
fn ask(
&self,
params: &Self::Params,
state: &Self::State,
rng: &mut dyn Rng,
device: &<B as burn::tensor::backend::BackendTypes>::Device,
) -> (Self::Genome, Self::State) {
self.inner.ask(params, state, rng, device)
}
fn tell(
&self,
params: &Self::Params,
population: Self::Genome,
fitness: Tensor<B, 1>,
state: Self::State,
rng: &mut dyn Rng,
) -> (Self::State, StrategyMetrics) {
self.inner.tell(params, population, fitness, state, rng)
}
fn best(&self, state: &Self::State) -> Option<(Self::Genome, f32)> {
self.inner.best(state)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::algorithms::de::DifferentialEvolution;
use crate::algorithms::es_classical::EvolutionStrategy;
use crate::algorithms::ga::{GaConfig, GeneticAlgorithm};
use crate::rng::{SeedPurpose, seed_stream};
use burn::backend::Flex;
use burn::module::Module;
use burn::nn::{Linear, LinearConfig};
type TestBackend = Flex;
#[derive(Module, Debug)]
struct Mlp<B: Backend> {
l1: Linear<B>,
l2: Linear<B>,
}
impl<B: Backend> Mlp<B> {
fn new(device: &B::Device) -> Self {
Self {
l1: LinearConfig::new(2, 3).init(device),
l2: LinearConfig::new(3, 1).init(device),
}
}
}
fn assert_strategy<T: Strategy<TestBackend>>(_: &T) {}
#[test]
fn composes_with_all_phase1_strategies() {
let device = Default::default();
let template = Mlp::<TestBackend>::new(&device);
let ga = WeightOnly::new(GeneticAlgorithm::<TestBackend>::new(), template.clone());
let es = WeightOnly::new(EvolutionStrategy::<TestBackend>::new(), template.clone());
let de = WeightOnly::new(DifferentialEvolution::<TestBackend>::new(), template);
assert_eq!(ga.num_params(), 13);
assert_eq!(es.num_params(), 13);
assert_eq!(de.num_params(), 13);
assert_strategy(&ga);
assert_strategy(&es);
assert_strategy(&de);
}
#[test]
fn test_weight_only_delegates_init_ask_tell_roundtrip() {
let device = Default::default();
let template = Mlp::<TestBackend>::new(&device);
let strategy = WeightOnly::new(GeneticAlgorithm::<TestBackend>::new(), template.clone());
let num_params: usize = strategy.num_params();
let params: GaConfig = GaConfig::default_for(8, num_params);
let mut rng = seed_stream(0, 0, SeedPurpose::Init);
let state = strategy.init(¶ms, &mut rng, &device);
let (pop, state) = strategy.ask(¶ms, &state, &mut rng, &device);
assert_eq!(
pop.dims(),
[8, num_params],
"ask must return a (pop_size, num_params) population"
);
let fitness = Tensor::<TestBackend, 1>::full([8], 1.0, &device);
let (state, metrics) = strategy.tell(¶ms, pop, fitness, state, &mut rng);
assert_eq!(
metrics.population_size(),
8,
"tell metrics must report the delegated population size"
);
assert!(
strategy.best(&state).is_some(),
"best must exist after one tell"
);
assert_eq!(
strategy.reshaper().clone().num_params(),
strategy.num_params(),
"cloned reshaper must report the same genome width"
);
}
}