1use crate::boundary::Boundary2D;
19use crate::clamp_placement_to_boundary;
20use crate::geometry::Geometry2D;
21use crate::nfp::{compute_ifp, compute_nfp, find_bottom_left_placement, Nfp, PlacedGeometry};
22use std::sync::atomic::{AtomicBool, Ordering};
23use std::sync::Arc;
24use u_nesting_core::brkga::{BrkgaConfig, BrkgaProblem, BrkgaRunner, RandomKeyChromosome};
25use u_nesting_core::geometry::{Boundary, Geometry};
26use u_nesting_core::solver::Config;
27use u_nesting_core::{Placement, SolveResult};
28
29#[derive(Debug, Clone)]
31struct InstanceInfo {
32 geometry_idx: usize,
34 instance_num: usize,
36}
37
38pub struct BrkgaNestingProblem {
40 geometries: Vec<Geometry2D>,
42 boundary: Boundary2D,
44 config: Config,
46 instances: Vec<InstanceInfo>,
48 rotation_angles: Vec<Vec<f64>>,
50 cancelled: Arc<AtomicBool>,
52}
53
54impl BrkgaNestingProblem {
55 pub fn new(
57 geometries: Vec<Geometry2D>,
58 boundary: Boundary2D,
59 config: Config,
60 cancelled: Arc<AtomicBool>,
61 ) -> Self {
62 let mut instances = Vec::new();
64 let mut rotation_angles = Vec::new();
65
66 for (geom_idx, geom) in geometries.iter().enumerate() {
67 let angles = geom.rotations();
69 let angles = if angles.is_empty() { vec![0.0] } else { angles };
70 rotation_angles.push(angles);
71
72 for instance_num in 0..geom.quantity() {
74 instances.push(InstanceInfo {
75 geometry_idx: geom_idx,
76 instance_num,
77 });
78 }
79 }
80
81 Self {
82 geometries,
83 boundary,
84 config,
85 instances,
86 rotation_angles,
87 cancelled,
88 }
89 }
90
91 pub fn num_instances(&self) -> usize {
93 self.instances.len()
94 }
95
96 pub fn decode(&self, chromosome: &RandomKeyChromosome) -> (Vec<Placement<f64>>, f64, usize) {
102 let n = self.instances.len();
103 if n == 0 || chromosome.len() < n {
104 return (Vec::new(), 0.0, 0);
105 }
106
107 let order = chromosome.decode_as_permutation();
109 let order: Vec<usize> = order.into_iter().take(n).collect();
111
112 let mut placements = Vec::new();
113 let mut placed_geometries: Vec<PlacedGeometry> = Vec::new();
114 let mut total_placed_area = 0.0;
115 let mut placed_count = 0;
116
117 let margin = self.config.margin;
118 let spacing = self.config.spacing;
119
120 let boundary_polygon = self.get_boundary_polygon_with_margin(margin);
122
123 let sample_step = self.compute_sample_step();
125
126 for &instance_idx in &order {
128 if self.cancelled.load(Ordering::Relaxed) {
129 break;
130 }
131
132 if instance_idx >= self.instances.len() {
133 continue;
134 }
135
136 let info = &self.instances[instance_idx];
137 let geom = &self.geometries[info.geometry_idx];
138
139 let rotation_key_idx = n + instance_idx;
141 let num_rotations = self
142 .rotation_angles
143 .get(info.geometry_idx)
144 .map(|a| a.len())
145 .unwrap_or(1);
146
147 let rotation_idx = if rotation_key_idx < chromosome.len() {
148 chromosome.decode_as_discrete(rotation_key_idx, num_rotations)
149 } else {
150 0
151 };
152
153 let rotation_angle = self
154 .rotation_angles
155 .get(info.geometry_idx)
156 .and_then(|angles| angles.get(rotation_idx))
157 .copied()
158 .unwrap_or(0.0);
159
160 let ifp = match compute_ifp(&boundary_polygon, geom, rotation_angle) {
162 Ok(ifp) => ifp,
163 Err(_) => continue,
164 };
165
166 if ifp.is_empty() {
167 continue;
168 }
169
170 let mut nfps: Vec<Nfp> = Vec::new();
172 for placed in &placed_geometries {
173 let placed_exterior = placed.translated_exterior();
174 let placed_geom = Geometry2D::new(format!("_placed_{}", placed.geometry.id()))
175 .with_polygon(placed_exterior);
176
177 if let Ok(nfp) = compute_nfp(&placed_geom, geom, rotation_angle) {
178 let expanded = self.expand_nfp(&nfp, spacing);
179 nfps.push(expanded);
180 }
181 }
182
183 let ifp_shrunk = self.shrink_ifp(&ifp, spacing);
185
186 let nfp_refs: Vec<&Nfp> = nfps.iter().collect();
190 if let Some((x, y)) = find_bottom_left_placement(&ifp_shrunk, &nfp_refs, sample_step) {
191 let geom_aabb = geom.aabb_at_rotation(rotation_angle);
193 let boundary_aabb = self.boundary.aabb();
194
195 if let Some((clamped_x, clamped_y)) =
196 clamp_placement_to_boundary(x, y, geom_aabb, boundary_aabb)
197 {
198 let placement = Placement::new_2d(
199 geom.id().clone(),
200 info.instance_num,
201 clamped_x,
202 clamped_y,
203 rotation_angle,
204 );
205
206 placements.push(placement);
207 placed_geometries.push(PlacedGeometry::new(
208 geom.clone(),
209 (clamped_x, clamped_y),
210 rotation_angle,
211 ));
212 total_placed_area += geom.measure();
213 placed_count += 1;
214 }
215 }
216 }
217
218 let utilization = total_placed_area / self.boundary.measure();
219 (placements, utilization, placed_count)
220 }
221
222 fn get_boundary_polygon_with_margin(&self, margin: f64) -> Vec<(f64, f64)> {
224 let (b_min, b_max) = self.boundary.aabb();
225 vec![
226 (b_min[0] + margin, b_min[1] + margin),
227 (b_max[0] - margin, b_min[1] + margin),
228 (b_max[0] - margin, b_max[1] - margin),
229 (b_min[0] + margin, b_max[1] - margin),
230 ]
231 }
232
233 fn compute_sample_step(&self) -> f64 {
235 if self.geometries.is_empty() {
236 return 1.0;
237 }
238
239 let mut min_dim = f64::INFINITY;
240 for geom in &self.geometries {
241 let (g_min, g_max) = geom.aabb();
242 let width = g_max[0] - g_min[0];
243 let height = g_max[1] - g_min[1];
244 min_dim = min_dim.min(width).min(height);
245 }
246
247 (min_dim / 4.0).clamp(0.5, 10.0)
248 }
249
250 fn expand_nfp(&self, nfp: &Nfp, spacing: f64) -> Nfp {
252 if spacing <= 0.0 {
253 return nfp.clone();
254 }
255
256 let expanded_polygons: Vec<Vec<(f64, f64)>> = nfp
257 .polygons
258 .iter()
259 .map(|polygon| {
260 let (cx, cy) = polygon_centroid(polygon);
261 polygon
262 .iter()
263 .map(|&(x, y)| {
264 let dx = x - cx;
265 let dy = y - cy;
266 let dist = (dx * dx + dy * dy).sqrt();
267 if dist > 1e-10 {
268 let scale = (dist + spacing) / dist;
269 (cx + dx * scale, cy + dy * scale)
270 } else {
271 (x, y)
272 }
273 })
274 .collect()
275 })
276 .collect();
277
278 Nfp::from_polygons(expanded_polygons)
279 }
280
281 fn shrink_ifp(&self, ifp: &Nfp, spacing: f64) -> Nfp {
283 if spacing <= 0.0 {
284 return ifp.clone();
285 }
286
287 let shrunk_polygons: Vec<Vec<(f64, f64)>> = ifp
288 .polygons
289 .iter()
290 .filter_map(|polygon| {
291 let (cx, cy) = polygon_centroid(polygon);
292 let shrunk: Vec<(f64, f64)> = polygon
293 .iter()
294 .map(|&(x, y)| {
295 let dx = x - cx;
296 let dy = y - cy;
297 let dist = (dx * dx + dy * dy).sqrt();
298 if dist > spacing + 1e-10 {
299 let scale = (dist - spacing) / dist;
300 (cx + dx * scale, cy + dy * scale)
301 } else {
302 (cx, cy)
303 }
304 })
305 .collect();
306
307 if shrunk.len() >= 3 {
308 Some(shrunk)
309 } else {
310 None
311 }
312 })
313 .collect();
314
315 Nfp::from_polygons(shrunk_polygons)
316 }
317}
318
319fn polygon_centroid(polygon: &[(f64, f64)]) -> (f64, f64) {
321 if polygon.is_empty() {
322 return (0.0, 0.0);
323 }
324
325 let sum: (f64, f64) = polygon
326 .iter()
327 .fold((0.0, 0.0), |acc, &(x, y)| (acc.0 + x, acc.1 + y));
328 let n = polygon.len() as f64;
329 (sum.0 / n, sum.1 / n)
330}
331
332impl BrkgaProblem for BrkgaNestingProblem {
333 fn num_keys(&self) -> usize {
334 self.instances.len() * 2
336 }
337
338 fn evaluate(&self, chromosome: &mut RandomKeyChromosome) {
339 let total_instances = self.instances.len();
340 let (_, utilization, placed_count) = self.decode(chromosome);
341
342 let placement_ratio = placed_count as f64 / total_instances.max(1) as f64;
344
345 let fitness = placement_ratio * 100.0 + utilization * 10.0;
348
349 chromosome.set_fitness(fitness);
350 }
351
352 fn on_generation(
353 &self,
354 generation: u32,
355 best: &RandomKeyChromosome,
356 _population: &[RandomKeyChromosome],
357 ) {
358 log::debug!(
359 "BRKGA Generation {}: fitness={:.4}",
360 generation,
361 best.fitness()
362 );
363 }
364}
365
366pub fn run_brkga_nesting(
368 geometries: &[Geometry2D],
369 boundary: &Boundary2D,
370 config: &Config,
371 brkga_config: BrkgaConfig,
372 cancelled: Arc<AtomicBool>,
373) -> SolveResult<f64> {
374 let problem = BrkgaNestingProblem::new(
375 geometries.to_vec(),
376 boundary.clone(),
377 config.clone(),
378 cancelled.clone(),
379 );
380
381 let runner = BrkgaRunner::with_cancellation(brkga_config, problem, cancelled.clone());
382
383 let brkga_result = runner.run();
384
385 let problem = BrkgaNestingProblem::new(
387 geometries.to_vec(),
388 boundary.clone(),
389 config.clone(),
390 Arc::new(AtomicBool::new(false)),
391 );
392
393 let (placements, utilization, _placed_count) = problem.decode(&brkga_result.best);
394
395 let mut unplaced = Vec::new();
397 let mut placed_ids: std::collections::HashSet<String> = std::collections::HashSet::new();
398 for p in &placements {
399 placed_ids.insert(p.geometry_id.clone());
400 }
401 for geom in geometries {
402 if !placed_ids.contains(geom.id()) {
403 unplaced.push(geom.id().clone());
404 }
405 }
406
407 let mut result = SolveResult::new();
408 result.placements = placements;
409 result.unplaced = unplaced;
410 result.boundaries_used = 1;
411 result.utilization = utilization;
412 result.computation_time_ms = brkga_result.elapsed.as_millis() as u64;
413 result.generations = Some(brkga_result.generations);
414 result.best_fitness = Some(brkga_result.best.fitness());
415 result.fitness_history = Some(brkga_result.history);
416 result.strategy = Some("BRKGA".to_string());
417 result.cancelled = cancelled.load(Ordering::Relaxed);
418 result.target_reached = brkga_result.target_reached;
419
420 result
421}
422
423#[cfg(test)]
424mod tests {
425 use super::*;
426
427 #[test]
428 fn test_brkga_nesting_basic() {
429 let geometries = vec![
430 Geometry2D::rectangle("R1", 20.0, 10.0).with_quantity(2),
431 Geometry2D::rectangle("R2", 15.0, 15.0).with_quantity(2),
432 ];
433
434 let boundary = Boundary2D::rectangle(100.0, 50.0);
435 let config = Config::default();
436 let brkga_config = BrkgaConfig::default()
437 .with_population_size(30)
438 .with_max_generations(20);
439
440 let result = run_brkga_nesting(
441 &geometries,
442 &boundary,
443 &config,
444 brkga_config,
445 Arc::new(AtomicBool::new(false)),
446 );
447
448 assert!(result.utilization > 0.0);
449 assert!(!result.placements.is_empty());
450 assert_eq!(result.strategy, Some("BRKGA".to_string()));
451 }
452
453 #[test]
454 fn test_brkga_nesting_all_placed() {
455 let geometries = vec![Geometry2D::rectangle("R1", 20.0, 20.0).with_quantity(4)];
456
457 let boundary = Boundary2D::rectangle(100.0, 100.0);
458 let config = Config::default();
459 let brkga_config = BrkgaConfig::default()
460 .with_population_size(30)
461 .with_max_generations(30);
462
463 let result = run_brkga_nesting(
464 &geometries,
465 &boundary,
466 &config,
467 brkga_config,
468 Arc::new(AtomicBool::new(false)),
469 );
470
471 assert_eq!(result.placements.len(), 4);
473 assert!(result.unplaced.is_empty());
474 }
475
476 #[test]
477 fn test_brkga_nesting_with_rotation() {
478 let geometries = vec![Geometry2D::rectangle("R1", 30.0, 10.0)
479 .with_quantity(3)
480 .with_rotations(vec![0.0, 90.0])];
481
482 let boundary = Boundary2D::rectangle(50.0, 50.0);
483 let config = Config::default();
484 let brkga_config = BrkgaConfig::default()
485 .with_population_size(30)
486 .with_max_generations(30);
487
488 let result = run_brkga_nesting(
489 &geometries,
490 &boundary,
491 &config,
492 brkga_config,
493 Arc::new(AtomicBool::new(false)),
494 );
495
496 assert!(result.utilization > 0.0);
497 assert!(!result.placements.is_empty());
498 }
499
500 #[test]
501 fn test_brkga_problem_decode() {
502 use rand::SeedableRng;
503
504 let geometries = vec![Geometry2D::rectangle("R1", 20.0, 10.0).with_quantity(2)];
505
506 let boundary = Boundary2D::rectangle(100.0, 50.0);
507 let config = Config::default();
508 let cancelled = Arc::new(AtomicBool::new(false));
509
510 let problem = BrkgaNestingProblem::new(geometries, boundary, config, cancelled);
511
512 assert_eq!(problem.num_instances(), 2);
513 assert_eq!(problem.num_keys(), 4);
515
516 let mut rng = rand::rngs::StdRng::seed_from_u64(12345);
518 let chromosome = RandomKeyChromosome::random(problem.num_keys(), &mut rng);
519 let (placements, utilization, placed_count) = problem.decode(&chromosome);
520
521 assert_eq!(placements.len(), placed_count);
523 if placed_count > 0 {
524 assert!(utilization > 0.0);
525 }
526 }
527}