Skip to main content

rlevo_evolution/algorithms/metaheuristic/
aco_perm.rs

1//! Ant Colony Optimization over permutation genomes — **stub**.
2//!
3//! This module path is reserved so the public API surface is stable,
4//! but the implementation is deferred to a future release. A useful
5//! ACO-TSP implementation is a substantial design exercise of its own
6//! (pheromone-matrix update, tour construction, candidate lists) and
7//! does not belong inside the continuous-domain strategies that the
8//! other nine swarm algorithms in this module target.
9//!
10//! All trait methods below panic with `todo!()`. The struct exists so
11//! the [`Permutation`](crate::genome::Permutation) genome kind has at
12//! least one declared consumer and downstream crates can
13//! unambiguously reference the future API surface.
14
15use std::marker::PhantomData;
16
17use burn::tensor::{Int, Tensor, backend::Backend};
18use rand::Rng;
19
20use crate::strategy::{Strategy, StrategyMetrics};
21use rlevo_core::config::{self, ConfigError, Validate};
22
23/// Static configuration placeholder for the permutation ACO.
24#[derive(Debug, Clone)]
25pub struct AcoPermConfig {
26    /// Number of ants.
27    pub pop_size: usize,
28    /// Number of nodes in the graph (permutation length).
29    pub n_nodes: usize,
30    /// Pheromone evaporation rate, `ρ ∈ (0, 1]`.
31    pub rho: f32,
32    /// Heuristic exponent `α` (pheromone weight).
33    pub alpha: f32,
34    /// Heuristic exponent `β` (desirability weight).
35    pub beta: f32,
36}
37
38impl AcoPermConfig {
39    /// Default configuration for a given population size and graph node count.
40    #[must_use]
41    pub fn default_for(pop_size: usize, n_nodes: usize) -> Self {
42        Self {
43            pop_size,
44            n_nodes,
45            rho: 0.5,
46            alpha: 1.0,
47            beta: 2.0,
48        }
49    }
50}
51
52impl Validate for AcoPermConfig {
53    fn validate(&self) -> Result<(), ConfigError> {
54        const C: &str = "AcoPermConfig";
55        config::at_least(C, "pop_size", self.pop_size, 1)?;
56        config::at_least(C, "n_nodes", self.n_nodes, 1)?;
57        // ρ ∈ (0, 1]: strictly positive and at most one.
58        config::positive(C, "rho", f64::from(self.rho))?;
59        config::in_range(C, "rho", 0.0, 1.0, f64::from(self.rho))?;
60        config::in_range(C, "alpha", 0.0, f64::INFINITY, f64::from(self.alpha))?;
61        config::in_range(C, "beta", 0.0, f64::INFINITY, f64::from(self.beta))?;
62        Ok(())
63    }
64}
65
66/// Generation state placeholder.
67#[derive(Debug, Clone)]
68pub struct AcoPermState<B: Backend> {
69    _backend: PhantomData<fn() -> B>,
70}
71
72/// Ant Colony Optimization for permutation problems (TSP, QAP, …).
73///
74/// **Not yet implemented** — planned for a future release.
75///
76/// # Panics
77///
78/// All [`Strategy`] methods except `best` invoke `todo!()`. The struct
79/// is constructible (`new`, `Default`) and useful for declaring
80/// downstream API surface; do not drive it through a harness.
81///
82/// # Example
83///
84/// ```no_run
85/// use burn::backend::Flex;
86/// use rlevo_evolution::algorithms::metaheuristic::aco_perm::{AntColonyPermutation, AcoPermConfig};
87///
88/// let strategy = AntColonyPermutation::<Flex>::new();
89/// let params = AcoPermConfig::default_for(32, 20);
90/// let _ = (strategy, params);
91/// ```
92#[derive(Debug, Clone, Copy, Default)]
93pub struct AntColonyPermutation<B: Backend> {
94    _backend: PhantomData<fn() -> B>,
95}
96
97impl<B: Backend> AntColonyPermutation<B> {
98    /// Builds a new (stateless) strategy object (stub; all methods `todo!()`).
99    #[must_use]
100    pub fn new() -> Self {
101        Self {
102            _backend: PhantomData,
103        }
104    }
105}
106
107impl<B: Backend> Strategy<B> for AntColonyPermutation<B> {
108    type Params = AcoPermConfig;
109    type State = AcoPermState<B>;
110    type Genome = Tensor<B, 2, Int>;
111
112    fn init(
113        &self,
114        _params: &AcoPermConfig,
115        _rng: &mut dyn Rng,
116        _device: &<B as burn::tensor::backend::BackendTypes>::Device,
117    ) -> AcoPermState<B> {
118        todo!(
119            "permutation ACO is not yet implemented; \
120             use AntColonyReal for continuous problems in the meantime"
121        )
122    }
123
124    fn ask(
125        &self,
126        _params: &AcoPermConfig,
127        _state: &AcoPermState<B>,
128        _rng: &mut dyn Rng,
129        _device: &<B as burn::tensor::backend::BackendTypes>::Device,
130    ) -> (Self::Genome, AcoPermState<B>) {
131        todo!("permutation ACO is not yet implemented")
132    }
133
134    fn tell(
135        &self,
136        _params: &AcoPermConfig,
137        _population: Self::Genome,
138        _fitness: Tensor<B, 1>,
139        _state: AcoPermState<B>,
140        _rng: &mut dyn Rng,
141    ) -> (AcoPermState<B>, StrategyMetrics) {
142        todo!("permutation ACO is not yet implemented")
143    }
144
145    fn best(&self, _state: &AcoPermState<B>) -> Option<(Self::Genome, f32)> {
146        None
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153    use burn::backend::Flex;
154
155    type TestBackend = Flex;
156
157    #[test]
158    fn stub_is_constructible() {
159        let _strategy = AntColonyPermutation::<TestBackend>::new();
160        let _params = AcoPermConfig::default_for(32, 20);
161    }
162
163    #[test]
164    fn default_config_validates() {
165        assert!(AcoPermConfig::default_for(32, 20).validate().is_ok());
166    }
167
168    #[test]
169    fn rejects_rho_above_one() {
170        let mut cfg = AcoPermConfig::default_for(32, 20);
171        cfg.rho = 1.5;
172        assert_eq!(cfg.validate().unwrap_err().field, "rho");
173    }
174
175    #[test]
176    #[should_panic(expected = "permutation ACO is not yet implemented")]
177    fn init_panics_with_clear_message() {
178        use rand::SeedableRng;
179        let strategy = AntColonyPermutation::<TestBackend>::new();
180        let params = AcoPermConfig::default_for(4, 5);
181        let mut rng = rand::rngs::StdRng::seed_from_u64(0);
182        let _ = strategy.init(&params, &mut rng, &Default::default());
183    }
184
185    #[test]
186    #[should_panic(expected = "permutation ACO is not yet implemented")]
187    fn ask_panics_with_clear_message() {
188        use rand::SeedableRng;
189        let strategy = AntColonyPermutation::<TestBackend>::new();
190        let params = AcoPermConfig::default_for(4, 5);
191        let state = AcoPermState::<TestBackend> {
192            _backend: PhantomData,
193        };
194        let mut rng = rand::rngs::StdRng::seed_from_u64(0);
195        let _ = strategy.ask(&params, &state, &mut rng, &Default::default());
196    }
197
198    #[test]
199    #[should_panic(expected = "permutation ACO is not yet implemented")]
200    fn tell_panics_with_clear_message() {
201        use burn::tensor::TensorData;
202        use rand::SeedableRng;
203        let strategy = AntColonyPermutation::<TestBackend>::new();
204        let params = AcoPermConfig::default_for(2, 3);
205        let state = AcoPermState::<TestBackend> {
206            _backend: PhantomData,
207        };
208        let device = Default::default();
209        let population = Tensor::<TestBackend, 2, Int>::from_data(
210            TensorData::new(vec![0i64, 1, 2, 2, 1, 0], [2, 3]),
211            &device,
212        );
213        let fitness =
214            Tensor::<TestBackend, 1>::from_data(TensorData::new(vec![1.0f32, 2.0], [2]), &device);
215        let mut rng = rand::rngs::StdRng::seed_from_u64(0);
216        let _ = strategy.tell(&params, population, fitness, state, &mut rng);
217    }
218
219    #[test]
220    fn best_returns_none_on_stub_state() {
221        let strategy = AntColonyPermutation::<TestBackend>::new();
222        let state = AcoPermState::<TestBackend> {
223            _backend: PhantomData,
224        };
225        assert!(strategy.best(&state).is_none());
226    }
227}