rlevo_evolution/algorithms/metaheuristic/
aco_perm.rs1use 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#[derive(Debug, Clone)]
25pub struct AcoPermConfig {
26 pub pop_size: usize,
28 pub n_nodes: usize,
30 pub rho: f32,
32 pub alpha: f32,
34 pub beta: f32,
36}
37
38impl AcoPermConfig {
39 #[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 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#[derive(Debug, Clone)]
68pub struct AcoPermState<B: Backend> {
69 _backend: PhantomData<fn() -> B>,
70}
71
72#[derive(Debug, Clone, Copy, Default)]
93pub struct AntColonyPermutation<B: Backend> {
94 _backend: PhantomData<fn() -> B>,
95}
96
97impl<B: Backend> AntColonyPermutation<B> {
98 #[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(¶ms, &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(¶ms, &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(¶ms, 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}