1use crate::boundary::Boundary2D;
19use crate::clamp_placement_to_boundary;
20use crate::geometry::Geometry2D;
21use crate::nfp::{
22 compute_ifp_with_margin_and_mirror, compute_nfp_mirrored, find_bottom_left_placement,
23 verify_no_overlap_mirrored, Nfp, PlacedGeometry,
24};
25use std::sync::atomic::{AtomicBool, Ordering};
26use std::sync::Arc;
27use u_nesting_core::brkga::{BrkgaConfig, BrkgaProblem, BrkgaRunner, RandomKeyChromosome};
28use u_nesting_core::geometry::{Boundary, Geometry};
29use u_nesting_core::solver::Config;
30use u_nesting_core::{Placement, SolveResult};
31
32use crate::placement_utils::{expand_nfp, nesting_fitness, shrink_ifp, InstanceInfo};
33
34pub struct BrkgaNestingProblem {
36 geometries: Vec<Geometry2D>,
38 boundary: Boundary2D,
40 config: Config,
42 instances: Vec<InstanceInfo>,
44 rotation_angles: Vec<Vec<f64>>,
46 any_allow_flip: bool,
49 cancelled: Arc<AtomicBool>,
51}
52
53impl BrkgaNestingProblem {
54 pub fn new(
56 geometries: Vec<Geometry2D>,
57 boundary: Boundary2D,
58 config: Config,
59 cancelled: Arc<AtomicBool>,
60 ) -> Self {
61 let mut instances = Vec::new();
63 let mut rotation_angles = Vec::new();
64 let mut any_allow_flip = false;
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 any_allow_flip = any_allow_flip || geom.allow_flip();
72
73 for instance_num in 0..geom.quantity() {
75 instances.push(InstanceInfo {
76 geometry_idx: geom_idx,
77 instance_num,
78 });
79 }
80 }
81
82 Self {
83 geometries,
84 boundary,
85 config,
86 instances,
87 rotation_angles,
88 any_allow_flip,
89 cancelled,
90 }
91 }
92
93 pub fn num_instances(&self) -> usize {
95 self.instances.len()
96 }
97
98 pub fn decode(&self, chromosome: &RandomKeyChromosome) -> (Vec<Placement<f64>>, f64, usize) {
109 let n = self.instances.len();
110 if n == 0 || chromosome.len() < n {
111 return (Vec::new(), 0.0, 0);
112 }
113
114 let order = chromosome.decode_as_permutation();
116 let order: Vec<usize> = order.into_iter().take(n).collect();
118
119 let mut placements = Vec::new();
120 let mut placed_geometries: Vec<PlacedGeometry> = Vec::new();
121 let mut total_placed_area = 0.0;
122 let mut placed_count = 0;
123
124 let margin = self.config.margin;
125 let spacing = self.config.spacing;
126
127 let boundary_polygon = self.get_boundary_polygon_with_margin(margin);
129
130 let sample_step = self.compute_sample_step();
132
133 for &instance_idx in &order {
135 if self.cancelled.load(Ordering::Relaxed) {
136 break;
137 }
138
139 if instance_idx >= self.instances.len() {
140 continue;
141 }
142
143 let info = &self.instances[instance_idx];
144 let geom = &self.geometries[info.geometry_idx];
145
146 let rotation_key_idx = n + instance_idx;
148 let num_rotations = self
149 .rotation_angles
150 .get(info.geometry_idx)
151 .map(|a| a.len())
152 .unwrap_or(1);
153
154 let rotation_idx = if rotation_key_idx < chromosome.len() {
155 chromosome.decode_as_discrete(rotation_key_idx, num_rotations)
156 } else {
157 0
158 };
159
160 let rotation_angle = self
161 .rotation_angles
162 .get(info.geometry_idx)
163 .and_then(|angles| angles.get(rotation_idx))
164 .copied()
165 .unwrap_or(0.0);
166
167 let mirror_key_idx = 2 * n + instance_idx;
169 let mirror = self.any_allow_flip
170 && mirror_key_idx < chromosome.len()
171 && chromosome.decode_as_discrete(mirror_key_idx, 2) == 1
172 && geom.allow_flip();
173
174 let ifp = match compute_ifp_with_margin_and_mirror(
176 &boundary_polygon,
177 geom,
178 rotation_angle,
179 0.0,
180 mirror,
181 ) {
182 Ok(ifp) => ifp,
183 Err(_) => continue,
184 };
185
186 if ifp.is_empty() {
187 continue;
188 }
189
190 let mut nfps: Vec<Nfp> = Vec::new();
192 for placed in &placed_geometries {
193 let placed_exterior = placed.translated_exterior();
196 let placed_geom = Geometry2D::new(format!("_placed_{}", placed.geometry.id()))
197 .with_polygon(placed_exterior);
198
199 if let Ok(nfp) =
200 compute_nfp_mirrored(&placed_geom, geom, rotation_angle, false, mirror)
201 {
202 let expanded = self.expand_nfp(&nfp, spacing);
203 nfps.push(expanded);
204 }
205 }
206
207 let ifp_shrunk = self.shrink_ifp(&ifp, spacing);
209
210 let nfp_refs: Vec<&Nfp> = nfps.iter().collect();
214 if let Some((x, y)) = find_bottom_left_placement(&ifp_shrunk, &nfp_refs, sample_step) {
215 let geom_aabb = geom.aabb_at_rotation_mirrored(rotation_angle, mirror);
219 let boundary_aabb = self.boundary.aabb();
220
221 if let Some((clamped_x, clamped_y)) =
222 clamp_placement_to_boundary(x, y, geom_aabb, boundary_aabb)
223 {
224 let was_clamped = (clamped_x - x).abs() > 1e-6 || (clamped_y - y).abs() > 1e-6;
227 if was_clamped {
228 if !verify_no_overlap_mirrored(
230 geom,
231 (clamped_x, clamped_y),
232 rotation_angle,
233 mirror,
234 &placed_geometries,
235 ) {
236 continue; }
238 }
239
240 let placement = Placement::new_2d(
241 geom.id().clone(),
242 info.instance_num,
243 clamped_x,
244 clamped_y,
245 rotation_angle,
246 )
247 .with_mirrored(mirror);
248
249 placements.push(placement);
250 placed_geometries.push(
251 PlacedGeometry::new(geom.clone(), (clamped_x, clamped_y), rotation_angle)
252 .with_mirrored(mirror),
253 );
254 total_placed_area += geom.measure();
255 placed_count += 1;
256 }
257 }
258 }
259
260 let utilization = total_placed_area / self.boundary.measure();
261 (placements, utilization, placed_count)
262 }
263
264 fn get_boundary_polygon_with_margin(&self, margin: f64) -> Vec<(f64, f64)> {
266 let (b_min, b_max) = self.boundary.aabb();
267 vec![
268 (b_min[0] + margin, b_min[1] + margin),
269 (b_max[0] - margin, b_min[1] + margin),
270 (b_max[0] - margin, b_max[1] - margin),
271 (b_min[0] + margin, b_max[1] - margin),
272 ]
273 }
274
275 fn compute_sample_step(&self) -> f64 {
277 if self.geometries.is_empty() {
278 return 1.0;
279 }
280
281 let mut min_dim = f64::INFINITY;
282 for geom in &self.geometries {
283 let (g_min, g_max) = geom.aabb();
284 let width = g_max[0] - g_min[0];
285 let height = g_max[1] - g_min[1];
286 min_dim = min_dim.min(width).min(height);
287 }
288
289 (min_dim / 4.0).clamp(0.5, 10.0)
290 }
291
292 fn expand_nfp(&self, nfp: &Nfp, spacing: f64) -> Nfp {
294 expand_nfp(nfp, spacing)
295 }
296
297 fn shrink_ifp(&self, ifp: &Nfp, spacing: f64) -> Nfp {
299 shrink_ifp(ifp, spacing)
300 }
301}
302
303impl BrkgaProblem for BrkgaNestingProblem {
304 fn num_keys(&self) -> usize {
305 let n = self.instances.len();
308 if self.any_allow_flip {
309 n * 3
310 } else {
311 n * 2
312 }
313 }
314
315 fn evaluate(&self, chromosome: &mut RandomKeyChromosome) {
316 let (_, utilization, placed_count) = self.decode(chromosome);
317 let fitness = nesting_fitness(placed_count, self.instances.len(), utilization);
318 chromosome.set_fitness(fitness);
319 }
320
321 fn on_generation(
322 &self,
323 generation: u32,
324 best: &RandomKeyChromosome,
325 _population: &[RandomKeyChromosome],
326 ) {
327 log::debug!(
328 "BRKGA Generation {}: fitness={:.4}",
329 generation,
330 best.fitness()
331 );
332 }
333}
334
335pub fn run_brkga_nesting(
337 geometries: &[Geometry2D],
338 boundary: &Boundary2D,
339 config: &Config,
340 brkga_config: BrkgaConfig,
341 cancelled: Arc<AtomicBool>,
342) -> SolveResult<f64> {
343 let problem = BrkgaNestingProblem::new(
344 geometries.to_vec(),
345 boundary.clone(),
346 config.clone(),
347 cancelled.clone(),
348 );
349
350 let runner = BrkgaRunner::with_cancellation(brkga_config, problem, cancelled.clone());
351
352 let brkga_result = match config.seed {
355 Some(seed) => {
356 use rand::SeedableRng;
357 runner.run_with_rng(&mut rand::rngs::StdRng::seed_from_u64(seed))
358 }
359 None => runner.run(),
360 };
361
362 let problem = BrkgaNestingProblem::new(
364 geometries.to_vec(),
365 boundary.clone(),
366 config.clone(),
367 Arc::new(AtomicBool::new(false)),
368 );
369
370 let (placements, utilization, _placed_count) = problem.decode(&brkga_result.best);
371
372 let mut unplaced = Vec::new();
374 let mut placed_ids: std::collections::HashSet<String> = std::collections::HashSet::new();
375 for p in &placements {
376 placed_ids.insert(p.geometry_id.clone());
377 }
378 for geom in geometries {
379 if !placed_ids.contains(geom.id()) {
380 unplaced.push(geom.id().clone());
381 }
382 }
383
384 let mut result = SolveResult::new();
385 result.placements = placements;
386 result.unplaced = unplaced;
387 result.boundaries_used = 1;
388 result.utilization = utilization;
389 result.computation_time_ms = brkga_result.elapsed.as_millis() as u64;
390 result.generations = Some(brkga_result.generations);
391 result.best_fitness = Some(brkga_result.best.fitness());
392 result.fitness_history = Some(brkga_result.history);
393 result.strategy = Some("BRKGA".to_string());
394 result.cancelled = cancelled.load(Ordering::Relaxed);
395 result.target_reached = brkga_result.target_reached;
396
397 result
398}
399
400#[cfg(test)]
401mod tests {
402 use super::*;
403
404 #[test]
405 fn test_brkga_nesting_basic() {
406 let geometries = vec![
407 Geometry2D::rectangle("R1", 20.0, 10.0).with_quantity(2),
408 Geometry2D::rectangle("R2", 15.0, 15.0).with_quantity(2),
409 ];
410
411 let boundary = Boundary2D::rectangle(100.0, 50.0);
412 let config = Config::default();
413 let brkga_config = BrkgaConfig::default()
414 .with_population_size(30)
415 .with_max_generations(20);
416
417 let result = run_brkga_nesting(
418 &geometries,
419 &boundary,
420 &config,
421 brkga_config,
422 Arc::new(AtomicBool::new(false)),
423 );
424
425 assert!(result.utilization > 0.0);
426 assert!(!result.placements.is_empty());
427 assert_eq!(result.strategy, Some("BRKGA".to_string()));
428 }
429
430 #[test]
431 fn test_brkga_nesting_all_placed() {
432 let geometries = vec![Geometry2D::rectangle("R1", 20.0, 20.0).with_quantity(4)];
433
434 let boundary = Boundary2D::rectangle(100.0, 100.0);
435 let config = Config::default();
436 let brkga_config = BrkgaConfig::default()
437 .with_population_size(30)
438 .with_max_generations(30);
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_eq!(result.placements.len(), 4);
450 assert!(result.unplaced.is_empty());
451 }
452
453 #[test]
454 fn test_brkga_nesting_with_rotation() {
455 let geometries = vec![Geometry2D::rectangle("R1", 30.0, 10.0)
456 .with_quantity(3)
457 .with_rotations(vec![0.0, 90.0])];
458
459 let boundary = Boundary2D::rectangle(50.0, 50.0);
460 let config = Config::default();
461 let brkga_config = BrkgaConfig::default()
462 .with_population_size(30)
463 .with_max_generations(30);
464
465 let result = run_brkga_nesting(
466 &geometries,
467 &boundary,
468 &config,
469 brkga_config,
470 Arc::new(AtomicBool::new(false)),
471 );
472
473 assert!(result.utilization > 0.0);
474 assert!(!result.placements.is_empty());
475 }
476
477 #[test]
478 fn test_brkga_problem_decode() {
479 use rand::SeedableRng;
480
481 let geometries = vec![Geometry2D::rectangle("R1", 20.0, 10.0).with_quantity(2)];
482
483 let boundary = Boundary2D::rectangle(100.0, 50.0);
484 let config = Config::default();
485 let cancelled = Arc::new(AtomicBool::new(false));
486
487 let problem = BrkgaNestingProblem::new(geometries, boundary, config, cancelled);
488
489 assert_eq!(problem.num_instances(), 2);
490 assert_eq!(problem.num_keys(), 4);
492
493 let mut rng = rand::rngs::StdRng::seed_from_u64(12345);
495 let chromosome = RandomKeyChromosome::random(problem.num_keys(), &mut rng);
496 let (placements, utilization, placed_count) = problem.decode(&chromosome);
497
498 assert_eq!(placements.len(), placed_count);
500 if placed_count > 0 {
501 assert!(utilization > 0.0);
502 }
503 }
504
505 #[test]
506 fn test_brkga_num_keys_includes_mirror_block_only_when_allowed() {
507 let boundary = Boundary2D::rectangle(65.0, 45.0);
508 let cancelled = Arc::new(AtomicBool::new(false));
509
510 let plain = vec![Geometry2D::rectangle("R", 10.0, 10.0).with_quantity(2)];
511 let problem = BrkgaNestingProblem::new(
512 plain,
513 boundary.clone(),
514 Config::default(),
515 cancelled.clone(),
516 );
517 assert_eq!(
518 problem.num_keys(),
519 4,
520 "2 instances * 2 blocks (order + rotation)"
521 );
522
523 let flippable = vec![Geometry2D::rectangle("R", 10.0, 10.0)
524 .with_flip(true)
525 .with_quantity(2)];
526 let problem = BrkgaNestingProblem::new(flippable, boundary, Config::default(), cancelled);
527 assert_eq!(
528 problem.num_keys(),
529 6,
530 "2 instances * 3 blocks (order + rotation + mirror)"
531 );
532 }
533
534 fn chiral_l(id: &str) -> Geometry2D {
537 Geometry2D::l_shape(id, 30.0, 20.0, 20.0, 10.0)
538 }
539
540 fn polygons_overlap(a: &[(f64, f64)], b: &[(f64, f64)]) -> bool {
541 for i in 0..a.len() {
542 let (a1, a2) = (a[i], a[(i + 1) % a.len()]);
543 for j in 0..b.len() {
544 let (b1, b2) = (b[j], b[(j + 1) % b.len()]);
545 if crate::polygon_ops::segments_intersect(a1, a2, b1, b2) {
546 return true;
547 }
548 }
549 }
550 false
551 }
552
553 #[test]
564 fn test_brkga_decode_mirror_no_overlap() {
565 let geometries = vec![chiral_l("L").with_flip(true).with_quantity(2)];
566 let boundary = Boundary2D::rectangle(65.0, 45.0);
567 let config = Config::default().with_spacing(1.0);
568 let problem = BrkgaNestingProblem::new(
569 geometries.clone(),
570 boundary,
571 config,
572 Arc::new(AtomicBool::new(false)),
573 );
574 assert_eq!(problem.num_keys(), 6);
575
576 let mut chromosome = RandomKeyChromosome::new(6);
577 chromosome.keys = vec![
578 0.01, 0.02, 0.5, 0.5, 0.25, 0.75, ];
584
585 let (placements, utilization, placed_count) = problem.decode(&chromosome);
586
587 assert_eq!(
588 placed_count, 2,
589 "both instances should fit in this boundary"
590 );
591 assert_eq!(placements.len(), 2);
592 assert!(utilization > 0.0);
593 assert!(!placements[0].mirrored);
594 assert!(
595 placements[1].mirrored,
596 "instance 1's mirror key decoded to true and allow_flip is set — decode() must honor it"
597 );
598
599 let poly0 = PlacedGeometry::new(
600 geometries[0].clone(),
601 (placements[0].x(), placements[0].y()),
602 placements[0].angle(),
603 )
604 .with_mirrored(placements[0].mirrored)
605 .translated_exterior();
606 let poly1 = PlacedGeometry::new(
607 geometries[0].clone(),
608 (placements[1].x(), placements[1].y()),
609 placements[1].angle(),
610 )
611 .with_mirrored(placements[1].mirrored)
612 .translated_exterior();
613 assert!(
614 !polygons_overlap(&poly0, &poly1),
615 "unmirrored instance 0 and mirrored instance 1 must not overlap"
616 );
617 }
618
619 #[test]
620 fn test_brkga_decode_mirror_ignored_without_allow_flip() {
621 let geometries = vec![chiral_l("L").with_quantity(1)];
622 let boundary = Boundary2D::rectangle(65.0, 45.0);
623 let problem = BrkgaNestingProblem::new(
624 geometries,
625 boundary,
626 Config::default(),
627 Arc::new(AtomicBool::new(false)),
628 );
629 assert_eq!(problem.num_keys(), 2);
632
633 let mut chromosome = RandomKeyChromosome::new(2);
634 chromosome.keys = vec![0.01, 0.5];
635
636 let (placements, _utilization, placed_count) = problem.decode(&chromosome);
637 assert_eq!(placed_count, 1);
638 assert!(
639 !placements[0].mirrored,
640 "allow_flip=false must suppress mirroring"
641 );
642 }
643}