1use crate::boundary::Boundary2D;
7use crate::clamp_placement_to_boundary;
8use crate::geometry::Geometry2D;
9use crate::nfp::{
10 compute_ifp, compute_nfp, find_bottom_left_placement, verify_no_overlap, Nfp, PlacedGeometry,
11};
12use rand::prelude::*;
13use std::sync::atomic::{AtomicBool, Ordering};
14use std::sync::Arc;
15use u_nesting_core::ga::{GaConfig, GaProblem, GaProgress, GaRunner, Individual};
16use u_nesting_core::geometry::{Boundary, Geometry};
17use u_nesting_core::solver::{Config, ProgressCallback, ProgressInfo};
18use u_nesting_core::{Placement, SolveResult};
19
20use crate::placement_utils::{expand_nfp, nesting_fitness, shrink_ifp, InstanceInfo};
21
22#[derive(Debug, Clone)]
24pub struct NestingChromosome {
25 pub order: Vec<usize>,
27 pub rotations: Vec<usize>,
29 fitness: f64,
31 placed_count: usize,
33 total_count: usize,
35}
36
37impl NestingChromosome {
38 pub fn new(num_instances: usize, _rotation_options: usize) -> Self {
40 Self {
41 order: (0..num_instances).collect(),
42 rotations: vec![0; num_instances],
43 fitness: f64::NEG_INFINITY,
44 placed_count: 0,
45 total_count: num_instances,
46 }
47 }
48
49 pub fn random_with_options<R: Rng>(
51 num_instances: usize,
52 rotation_options: usize,
53 rng: &mut R,
54 ) -> Self {
55 let mut order: Vec<usize> = (0..num_instances).collect();
56 order.shuffle(rng);
57
58 let rotations: Vec<usize> = (0..num_instances)
59 .map(|_| rng.random_range(0..rotation_options.max(1)))
60 .collect();
61
62 Self {
63 order,
64 rotations,
65 fitness: f64::NEG_INFINITY,
66 placed_count: 0,
67 total_count: num_instances,
68 }
69 }
70
71 pub fn set_fitness(&mut self, fitness: f64, placed_count: usize) {
73 self.fitness = fitness;
74 self.placed_count = placed_count;
75 }
76
77 pub fn order_crossover<R: Rng>(&self, other: &Self, rng: &mut R) -> Self {
79 let n = self.order.len();
80 if n < 2 {
81 return self.clone();
82 }
83
84 let (mut p1, mut p2) = (rng.random_range(0..n), rng.random_range(0..n));
86 if p1 > p2 {
87 std::mem::swap(&mut p1, &mut p2);
88 }
89
90 let mut child_order = vec![usize::MAX; n];
92 let mut used = vec![false; n];
93
94 for i in p1..=p2 {
95 child_order[i] = self.order[i];
96 used[self.order[i]] = true;
97 }
98
99 let mut j = (p2 + 1) % n;
101 for i in 0..n {
102 let idx = (p2 + 1 + i) % n;
103 if child_order[idx] == usize::MAX {
104 while used[other.order[j]] {
105 j = (j + 1) % n;
106 }
107 child_order[idx] = other.order[j];
108 used[other.order[j]] = true;
109 j = (j + 1) % n;
110 }
111 }
112
113 let rotations: Vec<usize> = self
115 .rotations
116 .iter()
117 .zip(&other.rotations)
118 .map(|(a, b)| if rng.random() { *a } else { *b })
119 .collect();
120
121 Self {
122 order: child_order,
123 rotations,
124 fitness: f64::NEG_INFINITY,
125 placed_count: 0,
126 total_count: self.total_count,
127 }
128 }
129
130 pub fn swap_mutate<R: Rng>(&mut self, rng: &mut R) {
132 if self.order.len() < 2 {
133 return;
134 }
135
136 let i = rng.random_range(0..self.order.len());
137 let j = rng.random_range(0..self.order.len());
138 self.order.swap(i, j);
139 self.fitness = f64::NEG_INFINITY;
140 }
141
142 pub fn rotation_mutate<R: Rng>(&mut self, rotation_options: usize, rng: &mut R) {
144 if self.rotations.is_empty() || rotation_options <= 1 {
145 return;
146 }
147
148 let idx = rng.random_range(0..self.rotations.len());
149 self.rotations[idx] = rng.random_range(0..rotation_options);
150 self.fitness = f64::NEG_INFINITY;
151 }
152
153 pub fn inversion_mutate<R: Rng>(&mut self, rng: &mut R) {
155 let n = self.order.len();
156 if n < 2 {
157 return;
158 }
159
160 let (mut p1, mut p2) = (rng.random_range(0..n), rng.random_range(0..n));
161 if p1 > p2 {
162 std::mem::swap(&mut p1, &mut p2);
163 }
164
165 self.order[p1..=p2].reverse();
166 self.fitness = f64::NEG_INFINITY;
167 }
168}
169
170impl Individual for NestingChromosome {
171 type Fitness = f64;
172
173 fn fitness(&self) -> f64 {
174 self.fitness
175 }
176
177 fn random<R: Rng>(rng: &mut R) -> Self {
178 Self::random_with_options(0, 1, rng)
180 }
181
182 fn crossover<R: Rng>(&self, other: &Self, rng: &mut R) -> Self {
183 self.order_crossover(other, rng)
184 }
185
186 fn mutate<R: Rng>(&mut self, rng: &mut R) {
187 let r: f64 = rng.random();
189 if r < 0.5 {
190 self.swap_mutate(rng);
191 } else if r < 0.8 {
192 self.inversion_mutate(rng);
193 } else {
194 self.rotation_mutate(4, rng);
196 }
197 }
198}
199
200pub struct NestingProblem {
202 geometries: Vec<Geometry2D>,
204 boundary: Boundary2D,
206 config: Config,
208 instances: Vec<InstanceInfo>,
210 rotation_angles: Vec<Vec<f64>>,
212 rotation_options: usize,
214 cancelled: Arc<AtomicBool>,
216}
217
218impl NestingProblem {
219 pub fn new(
221 geometries: Vec<Geometry2D>,
222 boundary: Boundary2D,
223 config: Config,
224 cancelled: Arc<AtomicBool>,
225 ) -> Self {
226 let mut instances = Vec::new();
228 let mut rotation_angles = Vec::new();
229
230 for (geom_idx, geom) in geometries.iter().enumerate() {
231 let angles = geom.rotations();
233 let angles = if angles.is_empty() { vec![0.0] } else { angles };
234 rotation_angles.push(angles);
235
236 for instance_num in 0..geom.quantity() {
238 instances.push(InstanceInfo {
239 geometry_idx: geom_idx,
240 instance_num,
241 });
242 }
243 }
244
245 let rotation_options = rotation_angles.iter().map(|a| a.len()).max().unwrap_or(1);
247
248 Self {
249 geometries,
250 boundary,
251 config,
252 instances,
253 rotation_angles,
254 rotation_options,
255 cancelled,
256 }
257 }
258
259 pub fn num_instances(&self) -> usize {
261 self.instances.len()
262 }
263
264 pub fn rotation_options(&self) -> usize {
266 self.rotation_options
267 }
268
269 pub fn decode(&self, chromosome: &NestingChromosome) -> (Vec<Placement<f64>>, f64, usize) {
271 let mut placements = Vec::new();
272 let mut placed_geometries: Vec<PlacedGeometry> = Vec::new();
273 let mut total_placed_area = 0.0;
274 let mut placed_count = 0;
275
276 let margin = self.config.margin;
277 let spacing = self.config.spacing;
278
279 let boundary_polygon = self.get_boundary_polygon_with_margin(margin);
281
282 let sample_step = self.compute_sample_step();
284
285 for &instance_idx in chromosome.order.iter() {
287 if self.cancelled.load(Ordering::Relaxed) {
288 break;
289 }
290
291 if instance_idx >= self.instances.len() {
292 continue;
293 }
294
295 let info = &self.instances[instance_idx];
296 let geom = &self.geometries[info.geometry_idx];
297
298 let rotation_idx = chromosome.rotations.get(instance_idx).copied().unwrap_or(0);
300 let rotation_angle = self
301 .rotation_angles
302 .get(info.geometry_idx)
303 .and_then(|angles| angles.get(rotation_idx % angles.len()))
304 .copied()
305 .unwrap_or(0.0);
306
307 let ifp = match compute_ifp(&boundary_polygon, geom, rotation_angle) {
309 Ok(ifp) => ifp,
310 Err(_) => {
311 continue;
312 }
313 };
314
315 if ifp.is_empty() {
316 continue;
317 }
318
319 let mut nfps: Vec<Nfp> = Vec::new();
321 for placed in &placed_geometries {
322 let placed_exterior = placed.translated_exterior();
323 let placed_geom = Geometry2D::new(format!("_placed_{}", placed.geometry.id()))
324 .with_polygon(placed_exterior);
325
326 if let Ok(nfp) = compute_nfp(&placed_geom, geom, rotation_angle) {
327 let expanded = self.expand_nfp(&nfp, spacing);
328 nfps.push(expanded);
329 }
330 }
331
332 let ifp_shrunk = self.shrink_ifp(&ifp, spacing);
334
335 let nfp_refs: Vec<&Nfp> = nfps.iter().collect();
338 let placement_result = find_bottom_left_placement(&ifp_shrunk, &nfp_refs, sample_step);
339 if let Some((x, y)) = placement_result {
340 let geom_aabb = geom.aabb_at_rotation(rotation_angle);
342 let boundary_aabb = self.boundary.aabb();
343
344 if let Some((clamped_x, clamped_y)) =
345 clamp_placement_to_boundary(x, y, geom_aabb, boundary_aabb)
346 {
347 let was_clamped = (clamped_x - x).abs() > 1e-6 || (clamped_y - y).abs() > 1e-6;
350 if was_clamped {
351 if !verify_no_overlap(
353 geom,
354 (clamped_x, clamped_y),
355 rotation_angle,
356 &placed_geometries,
357 ) {
358 continue; }
360 }
361
362 let placement = Placement::new_2d(
363 geom.id().clone(),
364 info.instance_num,
365 clamped_x,
366 clamped_y,
367 rotation_angle,
368 );
369
370 placements.push(placement);
371 placed_geometries.push(PlacedGeometry::new(
372 geom.clone(),
373 (clamped_x, clamped_y),
374 rotation_angle,
375 ));
376 total_placed_area += geom.measure();
377 placed_count += 1;
378 }
379 }
380 }
381
382 let utilization = total_placed_area / self.boundary.measure();
383 (placements, utilization, placed_count)
384 }
385
386 fn get_boundary_polygon_with_margin(&self, margin: f64) -> Vec<(f64, f64)> {
388 let (b_min, b_max) = self.boundary.aabb();
389 vec![
390 (b_min[0] + margin, b_min[1] + margin),
391 (b_max[0] - margin, b_min[1] + margin),
392 (b_max[0] - margin, b_max[1] - margin),
393 (b_min[0] + margin, b_max[1] - margin),
394 ]
395 }
396
397 fn compute_sample_step(&self) -> f64 {
399 if self.geometries.is_empty() {
400 return 1.0;
401 }
402
403 let mut min_dim = f64::INFINITY;
404 for geom in &self.geometries {
405 let (g_min, g_max) = geom.aabb();
406 let width = g_max[0] - g_min[0];
407 let height = g_max[1] - g_min[1];
408 min_dim = min_dim.min(width).min(height);
409 }
410
411 (min_dim / 4.0).clamp(0.5, 10.0)
412 }
413
414 fn expand_nfp(&self, nfp: &Nfp, spacing: f64) -> Nfp {
416 expand_nfp(nfp, spacing)
417 }
418
419 fn shrink_ifp(&self, ifp: &Nfp, spacing: f64) -> Nfp {
421 shrink_ifp(ifp, spacing)
422 }
423}
424
425impl GaProblem for NestingProblem {
426 type Individual = NestingChromosome;
427
428 fn evaluate(&self, individual: &mut Self::Individual) {
429 let (_, utilization, placed_count) = self.decode(individual);
430 let fitness = nesting_fitness(placed_count, individual.total_count, utilization);
431 individual.set_fitness(fitness, placed_count);
432 }
433
434 fn initialize_population<R: Rng>(&self, size: usize, rng: &mut R) -> Vec<Self::Individual> {
435 (0..size)
436 .map(|_| {
437 NestingChromosome::random_with_options(
438 self.num_instances(),
439 self.rotation_options(),
440 rng,
441 )
442 })
443 .collect()
444 }
445
446 fn on_generation(
447 &self,
448 generation: u32,
449 best: &Self::Individual,
450 _population: &[Self::Individual],
451 ) {
452 log::debug!(
453 "GA Generation {}: fitness={:.4}, placed={}/{}",
454 generation,
455 best.fitness(),
456 best.placed_count,
457 best.total_count
458 );
459 }
460}
461
462pub fn run_ga_nesting(
464 geometries: &[Geometry2D],
465 boundary: &Boundary2D,
466 config: &Config,
467 ga_config: GaConfig,
468 cancelled: Arc<AtomicBool>,
469) -> SolveResult<f64> {
470 let problem = NestingProblem::new(
471 geometries.to_vec(),
472 boundary.clone(),
473 config.clone(),
474 cancelled.clone(),
475 );
476
477 let runner = GaRunner::new(ga_config, problem);
478
479 #[cfg(not(target_arch = "wasm32"))]
481 {
482 let cancel_handle = runner.cancel_handle();
483 let cancelled_clone = cancelled.clone();
484 std::thread::spawn(move || {
485 while !cancelled_clone.load(Ordering::Relaxed) {
486 std::thread::sleep(std::time::Duration::from_millis(100));
487 }
488 cancel_handle.store(true, Ordering::Relaxed);
489 });
490 }
491
492 let ga_result = runner.run();
493
494 let problem = NestingProblem::new(
496 geometries.to_vec(),
497 boundary.clone(),
498 config.clone(),
499 Arc::new(AtomicBool::new(false)),
500 );
501
502 let (placements, utilization, _placed_count) = problem.decode(&ga_result.best);
503
504 let mut unplaced = Vec::new();
506 let mut placed_ids: std::collections::HashSet<String> = std::collections::HashSet::new();
507 for p in &placements {
508 placed_ids.insert(p.geometry_id.clone());
509 }
510 for geom in geometries {
511 if !placed_ids.contains(geom.id()) {
512 unplaced.push(geom.id().clone());
513 }
514 }
515
516 let mut result = SolveResult::new();
517 result.placements = placements;
518 result.unplaced = unplaced;
519 result.boundaries_used = 1;
520 result.utilization = utilization;
521 result.computation_time_ms = ga_result.elapsed.as_millis() as u64;
522 result.generations = Some(ga_result.generations);
523 result.best_fitness = Some(ga_result.best.fitness());
524 result.fitness_history = Some(ga_result.history);
525 result.strategy = Some("GeneticAlgorithm".to_string());
526 result.cancelled = cancelled.load(Ordering::Relaxed);
527 result.target_reached = ga_result.target_reached;
528
529 result
530}
531
532pub fn run_ga_nesting_with_progress(
534 geometries: &[Geometry2D],
535 boundary: &Boundary2D,
536 config: &Config,
537 ga_config: GaConfig,
538 cancelled: Arc<AtomicBool>,
539 progress_callback: ProgressCallback,
540) -> SolveResult<f64> {
541 let total_items = geometries.iter().map(|g| g.quantity()).sum::<usize>();
542
543 let problem = NestingProblem::new(
544 geometries.to_vec(),
545 boundary.clone(),
546 config.clone(),
547 cancelled.clone(),
548 );
549
550 let runner = GaRunner::new(ga_config.clone(), problem);
551
552 #[cfg(not(target_arch = "wasm32"))]
554 {
555 let cancel_handle = runner.cancel_handle();
556 let cancelled_clone = cancelled.clone();
557 std::thread::spawn(move || {
558 while !cancelled_clone.load(Ordering::Relaxed) {
559 std::thread::sleep(std::time::Duration::from_millis(100));
560 }
561 cancel_handle.store(true, Ordering::Relaxed);
562 });
563 }
564
565 let max_generations = ga_config.max_generations;
567 let ga_result = runner.run_with_progress(move |ga_progress: GaProgress<f64>| {
568 let info = ProgressInfo::new()
569 .with_iteration(ga_progress.generation, max_generations)
570 .with_fitness(ga_progress.best_fitness)
571 .with_utilization(ga_progress.best_fitness) .with_items(0, total_items) .with_elapsed(ga_progress.elapsed.as_millis() as u64)
574 .with_phase("Genetic Algorithm".to_string());
575
576 let info = if !ga_progress.running {
577 info.finished()
578 } else {
579 info
580 };
581
582 progress_callback(info);
583 });
584
585 let problem = NestingProblem::new(
587 geometries.to_vec(),
588 boundary.clone(),
589 config.clone(),
590 Arc::new(AtomicBool::new(false)),
591 );
592
593 let (placements, utilization, _placed_count) = problem.decode(&ga_result.best);
594
595 let mut unplaced = Vec::new();
597 let mut placed_ids: std::collections::HashSet<String> = std::collections::HashSet::new();
598 for p in &placements {
599 placed_ids.insert(p.geometry_id.clone());
600 }
601 for geom in geometries {
602 if !placed_ids.contains(geom.id()) {
603 unplaced.push(geom.id().clone());
604 }
605 }
606
607 let mut result = SolveResult::new();
608 result.placements = placements;
609 result.unplaced = unplaced;
610 result.boundaries_used = 1;
611 result.utilization = utilization;
612 result.computation_time_ms = ga_result.elapsed.as_millis() as u64;
613 result.generations = Some(ga_result.generations);
614 result.best_fitness = Some(ga_result.best.fitness());
615 result.fitness_history = Some(ga_result.history);
616 result.strategy = Some("GeneticAlgorithm".to_string());
617 result.cancelled = cancelled.load(Ordering::Relaxed);
618 result.target_reached = ga_result.target_reached;
619
620 result
621}
622
623#[cfg(test)]
624mod tests {
625 use super::*;
626
627 #[test]
628 fn test_nesting_chromosome_crossover() {
629 let mut rng = rand::rng();
630 let parent1 = NestingChromosome::random_with_options(10, 4, &mut rng);
631 let parent2 = NestingChromosome::random_with_options(10, 4, &mut rng);
632
633 let child = parent1.order_crossover(&parent2, &mut rng);
634
635 assert_eq!(child.order.len(), 10);
637 let mut sorted = child.order.clone();
638 sorted.sort();
639 assert_eq!(sorted, (0..10).collect::<Vec<_>>());
640 }
641
642 #[test]
643 fn test_nesting_chromosome_mutation() {
644 let mut rng = rand::rng();
645 let mut chromosome = NestingChromosome::random_with_options(10, 4, &mut rng);
646
647 chromosome.swap_mutate(&mut rng);
648
649 let mut sorted = chromosome.order.clone();
651 sorted.sort();
652 assert_eq!(sorted, (0..10).collect::<Vec<_>>());
653 }
654
655 #[test]
656 fn test_ga_nesting_basic() {
657 let geometries = vec![
658 Geometry2D::rectangle("R1", 20.0, 10.0).with_quantity(2),
659 Geometry2D::rectangle("R2", 15.0, 15.0).with_quantity(2),
660 ];
661
662 let boundary = Boundary2D::rectangle(100.0, 50.0);
663 let config = Config::default();
664 let ga_config = GaConfig::default()
665 .with_population_size(20)
666 .with_max_generations(10);
667
668 let result = run_ga_nesting(
669 &geometries,
670 &boundary,
671 &config,
672 ga_config,
673 Arc::new(AtomicBool::new(false)),
674 );
675
676 assert!(result.utilization > 0.0);
677 assert!(!result.placements.is_empty());
678 }
679
680 #[test]
681 fn test_ga_nesting_all_placed() {
682 let geometries = vec![Geometry2D::rectangle("R1", 20.0, 20.0).with_quantity(4)];
683
684 let boundary = Boundary2D::rectangle(100.0, 100.0);
685 let config = Config::default();
686 let ga_config = GaConfig::default()
687 .with_population_size(30)
688 .with_max_generations(20);
689
690 let result = run_ga_nesting(
691 &geometries,
692 &boundary,
693 &config,
694 ga_config,
695 Arc::new(AtomicBool::new(false)),
696 );
697
698 assert_eq!(result.placements.len(), 4);
700 assert!(result.unplaced.is_empty());
701 }
702
703 #[test]
704 fn test_ga_nesting_with_rotation() {
705 let geometries = vec![Geometry2D::rectangle("R1", 30.0, 10.0)
707 .with_quantity(3)
708 .with_rotations(vec![0.0, 90.0])];
709
710 let boundary = Boundary2D::rectangle(50.0, 50.0);
711 let config = Config::default();
712 let ga_config = GaConfig::default()
713 .with_population_size(30)
714 .with_max_generations(20);
715
716 let result = run_ga_nesting(
717 &geometries,
718 &boundary,
719 &config,
720 ga_config,
721 Arc::new(AtomicBool::new(false)),
722 );
723
724 assert!(result.utilization > 0.0);
725 assert!(!result.placements.is_empty());
727 }
728
729 #[test]
730 fn test_nesting_problem_decode() {
731 let geometries = vec![Geometry2D::rectangle("R1", 20.0, 10.0).with_quantity(2)];
732
733 let boundary = Boundary2D::rectangle(100.0, 50.0);
734 let config = Config::default();
735 let cancelled = Arc::new(AtomicBool::new(false));
736
737 let problem = NestingProblem::new(geometries, boundary, config, cancelled);
738
739 assert_eq!(problem.num_instances(), 2);
740
741 let chromosome = NestingChromosome::new(2, 1);
743 let (placements, utilization, placed_count) = problem.decode(&chromosome);
744
745 assert_eq!(placed_count, 2);
746 assert_eq!(placements.len(), 2);
747 assert!(utilization > 0.0);
748 }
749}