1use crate::geometry::{Boundary, Geometry};
4use crate::result::SolveResult;
5use crate::Result;
6
7#[cfg(feature = "serde")]
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
13pub enum Strategy {
14 #[default]
16 BottomLeftFill,
17 NfpGuided,
19 GeneticAlgorithm,
21 Brkga,
23 SimulatedAnnealing,
25 ExtremePoint,
27 Gdrr,
29 Alns,
31 MilpExact,
33 HybridExact,
35}
36
37impl Strategy {
38 pub const NAMES: [&'static str; 10] = [
46 "blf",
47 "nfp",
48 "ga",
49 "brkga",
50 "sa",
51 "ep",
52 "gdrr",
53 "alns",
54 "milpexact",
55 "hybridexact",
56 ];
57
58 pub fn parse(name: &str) -> Option<Self> {
59 match name.trim().to_lowercase().as_str() {
60 "blf" | "bottomleftfill" => Some(Self::BottomLeftFill),
61 "nfp" | "nfpguided" => Some(Self::NfpGuided),
62 "ga" | "genetic" | "geneticalgorithm" => Some(Self::GeneticAlgorithm),
63 "brkga" => Some(Self::Brkga),
64 "sa" | "simulatedannealing" => Some(Self::SimulatedAnnealing),
65 "ep" | "extremepoint" => Some(Self::ExtremePoint),
66 "gdrr" => Some(Self::Gdrr),
67 "alns" => Some(Self::Alns),
68 "milpexact" | "exact" => Some(Self::MilpExact),
69 "hybridexact" | "hybrid" => Some(Self::HybridExact),
70 _ => None,
71 }
72 }
73}
74
75#[derive(Debug, Clone)]
77#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
78pub struct Config {
79 pub strategy: Strategy,
81
82 pub spacing: f64,
85
86 pub margin: f64,
88
89 pub time_limit_ms: u64,
95
96 pub target_utilization: Option<f64>,
98
99 pub threads: usize,
101
102 pub population_size: usize,
105
106 pub max_generations: u32,
108
109 pub crossover_rate: f64,
111
112 pub mutation_rate: f64,
114
115 pub elite_count: usize,
117
118 pub seed: Option<u64>,
121}
122
123impl Default for Config {
124 fn default() -> Self {
125 Self {
126 strategy: Strategy::default(),
127 spacing: 0.0,
128 margin: 0.0,
129 time_limit_ms: 30000,
130 target_utilization: None,
131 threads: 0,
132 population_size: 100,
133 max_generations: 500,
134 crossover_rate: 0.85,
135 mutation_rate: 0.05,
136 elite_count: 5,
137 seed: None,
138 }
139 }
140}
141
142impl Config {
143 pub fn new() -> Self {
145 Self::default()
146 }
147
148 pub fn validate(&self) -> Result<()> {
156 use crate::error::{check_at_least, check_range};
157 check_at_least("spacing", self.spacing, 0.0)?;
158 check_at_least("margin", self.margin, 0.0)?;
159 check_range("crossover_rate", self.crossover_rate, 0.0, 1.0)?;
160 check_range("mutation_rate", self.mutation_rate, 0.0, 1.0)?;
161 if let Some(util) = self.target_utilization {
162 check_range("target_utilization", util, 0.0, 1.0)?;
163 }
164 Ok(())
165 }
166
167 pub fn with_strategy(mut self, strategy: Strategy) -> Self {
169 self.strategy = strategy;
170 self
171 }
172
173 pub fn with_spacing(mut self, spacing: f64) -> Self {
175 self.spacing = spacing;
176 self
177 }
178
179 pub fn with_margin(mut self, margin: f64) -> Self {
181 self.margin = margin;
182 self
183 }
184
185 pub fn with_time_limit(mut self, ms: u64) -> Self {
187 self.time_limit_ms = ms;
188 self
189 }
190
191 pub fn with_target_utilization(mut self, util: f64) -> Self {
193 self.target_utilization = Some(util);
194 self
195 }
196
197 pub fn with_seed(mut self, seed: u64) -> Self {
199 self.seed = Some(seed);
200 self
201 }
202}
203
204pub type ProgressCallback = Box<dyn Fn(ProgressInfo) + Send + Sync>;
206
207#[derive(Debug, Clone, Default)]
209pub struct ProgressInfo {
210 pub iteration: u32,
212 pub total_iterations: u32,
214 pub utilization: f64,
216 pub best_fitness: f64,
218 pub items_placed: usize,
220 pub total_items: usize,
222 pub elapsed_ms: u64,
224 pub phase: String,
226 pub running: bool,
228}
229
230impl ProgressInfo {
231 pub fn new() -> Self {
233 Self {
234 running: true,
235 ..Default::default()
236 }
237 }
238
239 pub fn with_iteration(mut self, current: u32, total: u32) -> Self {
241 self.iteration = current;
242 self.total_iterations = total;
243 self
244 }
245
246 pub fn with_utilization(mut self, utilization: f64) -> Self {
248 self.utilization = utilization;
249 self
250 }
251
252 pub fn with_fitness(mut self, fitness: f64) -> Self {
254 self.best_fitness = fitness;
255 self
256 }
257
258 pub fn with_items(mut self, placed: usize, total: usize) -> Self {
260 self.items_placed = placed;
261 self.total_items = total;
262 self
263 }
264
265 pub fn with_elapsed(mut self, elapsed_ms: u64) -> Self {
267 self.elapsed_ms = elapsed_ms;
268 self
269 }
270
271 pub fn with_phase(mut self, phase: impl Into<String>) -> Self {
273 self.phase = phase.into();
274 self
275 }
276
277 pub fn finished(mut self) -> Self {
279 self.running = false;
280 self
281 }
282
283 pub fn progress_percent(&self) -> f64 {
285 if self.total_iterations > 0 {
286 self.iteration as f64 / self.total_iterations as f64
287 } else {
288 0.0
289 }
290 }
291}
292
293pub trait Solver {
295 type Geometry: Geometry;
297 type Boundary: Boundary;
299 type Scalar;
301
302 fn solve(
304 &self,
305 geometries: &[Self::Geometry],
306 boundary: &Self::Boundary,
307 ) -> Result<SolveResult<Self::Scalar>>;
308
309 fn solve_with_progress(
311 &self,
312 geometries: &[Self::Geometry],
313 boundary: &Self::Boundary,
314 callback: ProgressCallback,
315 ) -> Result<SolveResult<Self::Scalar>>;
316
317 fn cancel(&self);
319}
320
321#[cfg(test)]
322mod config_tests {
323 use super::Config;
324
325 #[test]
326 fn a_target_utilization_above_one_is_refused_not_clamped() {
327 let config = Config::default().with_target_utilization(1.5);
328 assert_eq!(config.target_utilization, Some(1.5), "stored as given");
329 let err = config.validate().expect_err("1.5 is not a utilization");
330 assert!(err.to_string().contains("target_utilization"), "{err}");
331 assert!(Config::default()
332 .with_target_utilization(0.9)
333 .validate()
334 .is_ok());
335 let nan_rate = Config {
336 mutation_rate: f64::NAN,
337 ..Config::default()
338 };
339 assert!(nan_rate.validate().is_err());
340 }
341}