u_nesting_core/
solver.rs

1//! Solver traits and configuration.
2
3use crate::geometry::{Boundary, Geometry};
4use crate::result::SolveResult;
5use crate::Result;
6
7#[cfg(feature = "serde")]
8use serde::{Deserialize, Serialize};
9
10/// Optimization strategy.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
13pub enum Strategy {
14    /// Bottom-Left Fill (fast, lower quality).
15    #[default]
16    BottomLeftFill,
17    /// NFP-guided placement (balanced).
18    NfpGuided,
19    /// Genetic Algorithm (slower, higher quality).
20    GeneticAlgorithm,
21    /// Biased Random-Key Genetic Algorithm (balanced, robust).
22    Brkga,
23    /// Simulated Annealing.
24    SimulatedAnnealing,
25    /// Extreme Point heuristic (3D only).
26    ExtremePoint,
27    /// Goal-Driven Ruin and Recreate (GDRR).
28    Gdrr,
29    /// Adaptive Large Neighborhood Search (ALNS).
30    Alns,
31    /// MILP-based exact solver (small instances, optimal solution).
32    MilpExact,
33    /// Hybrid: Try exact first, fallback to heuristic on timeout.
34    HybridExact,
35}
36
37/// Common configuration for solvers.
38#[derive(Debug, Clone)]
39#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
40pub struct Config {
41    /// Optimization strategy.
42    pub strategy: Strategy,
43
44    /// Minimum spacing between geometries.
45    pub spacing: f64,
46
47    /// Margin from boundary edges.
48    pub margin: f64,
49
50    /// Maximum computation time in milliseconds (0 = unlimited).
51    pub time_limit_ms: u64,
52
53    /// Target utilization (0.0 - 1.0). Solver stops if reached.
54    pub target_utilization: Option<f64>,
55
56    /// Number of threads to use (0 = auto).
57    pub threads: usize,
58
59    // GA-specific parameters
60    /// Population size for GA.
61    pub population_size: usize,
62
63    /// Number of generations for GA.
64    pub max_generations: u32,
65
66    /// Crossover rate for GA (0.0 - 1.0).
67    pub crossover_rate: f64,
68
69    /// Mutation rate for GA (0.0 - 1.0).
70    pub mutation_rate: f64,
71
72    /// Elite count for GA.
73    pub elite_count: usize,
74}
75
76impl Default for Config {
77    fn default() -> Self {
78        Self {
79            strategy: Strategy::default(),
80            spacing: 0.0,
81            margin: 0.0,
82            time_limit_ms: 30000,
83            target_utilization: None,
84            threads: 0,
85            population_size: 100,
86            max_generations: 500,
87            crossover_rate: 0.85,
88            mutation_rate: 0.05,
89            elite_count: 5,
90        }
91    }
92}
93
94impl Config {
95    /// Creates a new configuration with default values.
96    pub fn new() -> Self {
97        Self::default()
98    }
99
100    /// Sets the optimization strategy.
101    pub fn with_strategy(mut self, strategy: Strategy) -> Self {
102        self.strategy = strategy;
103        self
104    }
105
106    /// Sets the spacing between geometries.
107    pub fn with_spacing(mut self, spacing: f64) -> Self {
108        self.spacing = spacing;
109        self
110    }
111
112    /// Sets the margin from boundary edges.
113    pub fn with_margin(mut self, margin: f64) -> Self {
114        self.margin = margin;
115        self
116    }
117
118    /// Sets the time limit in milliseconds.
119    pub fn with_time_limit(mut self, ms: u64) -> Self {
120        self.time_limit_ms = ms;
121        self
122    }
123
124    /// Sets the target utilization.
125    pub fn with_target_utilization(mut self, util: f64) -> Self {
126        self.target_utilization = Some(util.clamp(0.0, 1.0));
127        self
128    }
129}
130
131/// Progress callback for long-running operations.
132pub type ProgressCallback = Box<dyn Fn(ProgressInfo) + Send + Sync>;
133
134/// Progress information during solving.
135#[derive(Debug, Clone, Default)]
136pub struct ProgressInfo {
137    /// Current iteration/generation number.
138    pub iteration: u32,
139    /// Total expected iterations (0 if unknown).
140    pub total_iterations: u32,
141    /// Current best utilization (0.0 to 1.0).
142    pub utilization: f64,
143    /// Current best fitness value.
144    pub best_fitness: f64,
145    /// Number of items placed.
146    pub items_placed: usize,
147    /// Total number of items.
148    pub total_items: usize,
149    /// Elapsed time in milliseconds.
150    pub elapsed_ms: u64,
151    /// Current phase/stage description.
152    pub phase: String,
153    /// Whether the solver is still running.
154    pub running: bool,
155}
156
157impl ProgressInfo {
158    /// Creates a new progress info with default values.
159    pub fn new() -> Self {
160        Self {
161            running: true,
162            ..Default::default()
163        }
164    }
165
166    /// Sets the iteration info.
167    pub fn with_iteration(mut self, current: u32, total: u32) -> Self {
168        self.iteration = current;
169        self.total_iterations = total;
170        self
171    }
172
173    /// Sets the utilization.
174    pub fn with_utilization(mut self, utilization: f64) -> Self {
175        self.utilization = utilization;
176        self
177    }
178
179    /// Sets the best fitness.
180    pub fn with_fitness(mut self, fitness: f64) -> Self {
181        self.best_fitness = fitness;
182        self
183    }
184
185    /// Sets the items placed info.
186    pub fn with_items(mut self, placed: usize, total: usize) -> Self {
187        self.items_placed = placed;
188        self.total_items = total;
189        self
190    }
191
192    /// Sets the elapsed time.
193    pub fn with_elapsed(mut self, elapsed_ms: u64) -> Self {
194        self.elapsed_ms = elapsed_ms;
195        self
196    }
197
198    /// Sets the phase description.
199    pub fn with_phase(mut self, phase: impl Into<String>) -> Self {
200        self.phase = phase.into();
201        self
202    }
203
204    /// Marks the solver as finished.
205    pub fn finished(mut self) -> Self {
206        self.running = false;
207        self
208    }
209
210    /// Calculates the progress percentage (0.0 to 1.0).
211    pub fn progress_percent(&self) -> f64 {
212        if self.total_iterations > 0 {
213            self.iteration as f64 / self.total_iterations as f64
214        } else {
215            0.0
216        }
217    }
218}
219
220/// Trait for nesting/packing solvers.
221pub trait Solver {
222    /// The geometry type this solver handles.
223    type Geometry: Geometry;
224    /// The boundary type this solver handles.
225    type Boundary: Boundary;
226    /// The scalar type for coordinates.
227    type Scalar;
228
229    /// Solves the nesting/packing problem.
230    fn solve(
231        &self,
232        geometries: &[Self::Geometry],
233        boundary: &Self::Boundary,
234    ) -> Result<SolveResult<Self::Scalar>>;
235
236    /// Solves with a progress callback.
237    fn solve_with_progress(
238        &self,
239        geometries: &[Self::Geometry],
240        boundary: &Self::Boundary,
241        callback: ProgressCallback,
242    ) -> Result<SolveResult<Self::Scalar>>;
243
244    /// Cancels an ongoing solve operation.
245    fn cancel(&self);
246}