use std::marker::PhantomData;
use burn::tensor::{Int, Tensor, backend::Backend};
use rand::Rng;
use crate::strategy::{Strategy, StrategyMetrics};
use rlevo_core::config::{self, ConfigError, Validate};
#[derive(Debug, Clone)]
pub struct AcoPermConfig {
pub pop_size: usize,
pub n_nodes: usize,
pub rho: f32,
pub alpha: f32,
pub beta: f32,
}
impl AcoPermConfig {
#[must_use]
pub fn default_for(pop_size: usize, n_nodes: usize) -> Self {
Self {
pop_size,
n_nodes,
rho: 0.5,
alpha: 1.0,
beta: 2.0,
}
}
}
impl Validate for AcoPermConfig {
fn validate(&self) -> Result<(), ConfigError> {
const C: &str = "AcoPermConfig";
config::at_least(C, "pop_size", self.pop_size, 1)?;
config::at_least(C, "n_nodes", self.n_nodes, 1)?;
config::positive(C, "rho", f64::from(self.rho))?;
config::in_range(C, "rho", 0.0, 1.0, f64::from(self.rho))?;
config::in_range(C, "alpha", 0.0, f64::INFINITY, f64::from(self.alpha))?;
config::in_range(C, "beta", 0.0, f64::INFINITY, f64::from(self.beta))?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct AcoPermState<B: Backend> {
_backend: PhantomData<fn() -> B>,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct AntColonyPermutation<B: Backend> {
_backend: PhantomData<fn() -> B>,
}
impl<B: Backend> AntColonyPermutation<B> {
#[must_use]
pub fn new() -> Self {
Self {
_backend: PhantomData,
}
}
}
impl<B: Backend> Strategy<B> for AntColonyPermutation<B> {
type Params = AcoPermConfig;
type State = AcoPermState<B>;
type Genome = Tensor<B, 2, Int>;
fn init(
&self,
_params: &AcoPermConfig,
_rng: &mut dyn Rng,
_device: &<B as burn::tensor::backend::BackendTypes>::Device,
) -> AcoPermState<B> {
todo!(
"permutation ACO is not yet implemented; \
use AntColonyReal for continuous problems in the meantime"
)
}
fn ask(
&self,
_params: &AcoPermConfig,
_state: &AcoPermState<B>,
_rng: &mut dyn Rng,
_device: &<B as burn::tensor::backend::BackendTypes>::Device,
) -> (Self::Genome, AcoPermState<B>) {
todo!("permutation ACO is not yet implemented")
}
fn tell(
&self,
_params: &AcoPermConfig,
_population: Self::Genome,
_fitness: Tensor<B, 1>,
_state: AcoPermState<B>,
_rng: &mut dyn Rng,
) -> (AcoPermState<B>, StrategyMetrics) {
todo!("permutation ACO is not yet implemented")
}
fn best(&self, _state: &AcoPermState<B>) -> Option<(Self::Genome, f32)> {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use burn::backend::Flex;
type TestBackend = Flex;
#[test]
fn stub_is_constructible() {
let _strategy = AntColonyPermutation::<TestBackend>::new();
let _params = AcoPermConfig::default_for(32, 20);
}
#[test]
fn default_config_validates() {
assert!(AcoPermConfig::default_for(32, 20).validate().is_ok());
}
#[test]
fn rejects_rho_above_one() {
let mut cfg = AcoPermConfig::default_for(32, 20);
cfg.rho = 1.5;
assert_eq!(cfg.validate().unwrap_err().field, "rho");
}
#[test]
#[should_panic(expected = "permutation ACO is not yet implemented")]
fn init_panics_with_clear_message() {
use rand::SeedableRng;
let strategy = AntColonyPermutation::<TestBackend>::new();
let params = AcoPermConfig::default_for(4, 5);
let mut rng = rand::rngs::StdRng::seed_from_u64(0);
let _ = strategy.init(¶ms, &mut rng, &Default::default());
}
#[test]
#[should_panic(expected = "permutation ACO is not yet implemented")]
fn ask_panics_with_clear_message() {
use rand::SeedableRng;
let strategy = AntColonyPermutation::<TestBackend>::new();
let params = AcoPermConfig::default_for(4, 5);
let state = AcoPermState::<TestBackend> {
_backend: PhantomData,
};
let mut rng = rand::rngs::StdRng::seed_from_u64(0);
let _ = strategy.ask(¶ms, &state, &mut rng, &Default::default());
}
#[test]
#[should_panic(expected = "permutation ACO is not yet implemented")]
fn tell_panics_with_clear_message() {
use burn::tensor::TensorData;
use rand::SeedableRng;
let strategy = AntColonyPermutation::<TestBackend>::new();
let params = AcoPermConfig::default_for(2, 3);
let state = AcoPermState::<TestBackend> {
_backend: PhantomData,
};
let device = Default::default();
let population = Tensor::<TestBackend, 2, Int>::from_data(
TensorData::new(vec![0i64, 1, 2, 2, 1, 0], [2, 3]),
&device,
);
let fitness =
Tensor::<TestBackend, 1>::from_data(TensorData::new(vec![1.0f32, 2.0], [2]), &device);
let mut rng = rand::rngs::StdRng::seed_from_u64(0);
let _ = strategy.tell(¶ms, population, fitness, state, &mut rng);
}
#[test]
fn best_returns_none_on_stub_state() {
let strategy = AntColonyPermutation::<TestBackend>::new();
let state = AcoPermState::<TestBackend> {
_backend: PhantomData,
};
assert!(strategy.best(&state).is_none());
}
}