1use crate::boundary::Boundary3D;
7use crate::geometry::Geometry3D;
8use rand::prelude::*;
9use std::sync::atomic::{AtomicBool, Ordering};
10use std::sync::Arc;
11use u_nesting_core::ga::{GaConfig, GaProblem, GaRunner, Individual};
12use u_nesting_core::geometry::{Boundary, Geometry};
13use u_nesting_core::solver::Config;
14use u_nesting_core::{Placement, SolveResult};
15
16#[derive(Debug, Clone)]
18pub struct PackingChromosome {
19 pub order: Vec<usize>,
21 pub orientations: Vec<usize>,
23 fitness: f64,
25 placed_count: usize,
27 total_count: usize,
29}
30
31impl PackingChromosome {
32 pub fn new(num_instances: usize) -> Self {
34 Self {
35 order: (0..num_instances).collect(),
36 orientations: vec![0; num_instances],
37 fitness: f64::NEG_INFINITY,
38 placed_count: 0,
39 total_count: num_instances,
40 }
41 }
42
43 pub fn random_with_options<R: Rng>(
45 num_instances: usize,
46 orientation_counts: &[usize],
47 rng: &mut R,
48 ) -> Self {
49 let mut order: Vec<usize> = (0..num_instances).collect();
50 order.shuffle(rng);
51
52 let orientations: Vec<usize> = orientation_counts
53 .iter()
54 .map(|&count| rng.gen_range(0..count.max(1)))
55 .collect();
56
57 Self {
58 order,
59 orientations,
60 fitness: f64::NEG_INFINITY,
61 placed_count: 0,
62 total_count: num_instances,
63 }
64 }
65
66 pub fn set_fitness(&mut self, fitness: f64, placed_count: usize) {
68 self.fitness = fitness;
69 self.placed_count = placed_count;
70 }
71
72 pub fn order_crossover<R: Rng>(
74 &self,
75 other: &Self,
76 orientation_counts: &[usize],
77 rng: &mut R,
78 ) -> Self {
79 let n = self.order.len();
80 if n < 2 {
81 return self.clone();
82 }
83
84 let (mut p1, mut p2) = (rng.gen_range(0..n), rng.gen_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 orientations: Vec<usize> = self
115 .orientations
116 .iter()
117 .zip(&other.orientations)
118 .enumerate()
119 .map(|(i, (a, b))| {
120 let max_orient = orientation_counts.get(i).copied().unwrap_or(1);
121 let chosen = if rng.gen() { *a } else { *b };
122 chosen % max_orient.max(1)
123 })
124 .collect();
125
126 Self {
127 order: child_order,
128 orientations,
129 fitness: f64::NEG_INFINITY,
130 placed_count: 0,
131 total_count: self.total_count,
132 }
133 }
134
135 pub fn swap_mutate<R: Rng>(&mut self, rng: &mut R) {
137 if self.order.len() < 2 {
138 return;
139 }
140
141 let i = rng.gen_range(0..self.order.len());
142 let j = rng.gen_range(0..self.order.len());
143 self.order.swap(i, j);
144 self.fitness = f64::NEG_INFINITY;
145 }
146
147 pub fn orientation_mutate<R: Rng>(&mut self, orientation_counts: &[usize], rng: &mut R) {
149 if self.orientations.is_empty() {
150 return;
151 }
152
153 let idx = rng.gen_range(0..self.orientations.len());
154 let max_orient = orientation_counts.get(idx).copied().unwrap_or(1);
155 if max_orient > 1 {
156 self.orientations[idx] = rng.gen_range(0..max_orient);
157 self.fitness = f64::NEG_INFINITY;
158 }
159 }
160
161 pub fn inversion_mutate<R: Rng>(&mut self, rng: &mut R) {
163 let n = self.order.len();
164 if n < 2 {
165 return;
166 }
167
168 let (mut p1, mut p2) = (rng.gen_range(0..n), rng.gen_range(0..n));
169 if p1 > p2 {
170 std::mem::swap(&mut p1, &mut p2);
171 }
172
173 self.order[p1..=p2].reverse();
174 self.fitness = f64::NEG_INFINITY;
175 }
176}
177
178impl Individual for PackingChromosome {
179 type Fitness = f64;
180
181 fn fitness(&self) -> f64 {
182 self.fitness
183 }
184
185 fn random<R: Rng>(_rng: &mut R) -> Self {
186 Self::new(0)
188 }
189
190 fn crossover<R: Rng>(&self, other: &Self, rng: &mut R) -> Self {
191 let fake_counts = vec![6; self.orientations.len()];
193 self.order_crossover(other, &fake_counts, rng)
194 }
195
196 fn mutate<R: Rng>(&mut self, rng: &mut R) {
197 let r: f64 = rng.gen();
199 if r < 0.5 {
200 self.swap_mutate(rng);
201 } else if r < 0.8 {
202 self.inversion_mutate(rng);
203 } else {
204 let fake_counts = vec![6; self.orientations.len()];
206 self.orientation_mutate(&fake_counts, rng);
207 }
208 }
209}
210
211#[derive(Debug, Clone)]
213struct InstanceInfo {
214 geometry_idx: usize,
216 instance_num: usize,
218 orientation_count: usize,
220}
221
222pub struct PackingProblem {
224 geometries: Vec<Geometry3D>,
226 boundary: Boundary3D,
228 config: Config,
230 instances: Vec<InstanceInfo>,
232 orientation_counts: Vec<usize>,
234 cancelled: Arc<AtomicBool>,
236}
237
238impl PackingProblem {
239 pub fn new(
241 geometries: Vec<Geometry3D>,
242 boundary: Boundary3D,
243 config: Config,
244 cancelled: Arc<AtomicBool>,
245 ) -> Self {
246 let mut instances = Vec::new();
248 let mut orientation_counts = Vec::new();
249
250 for (geom_idx, geom) in geometries.iter().enumerate() {
251 let orient_count = geom.allowed_orientations().len();
252
253 for instance_num in 0..geom.quantity() {
254 instances.push(InstanceInfo {
255 geometry_idx: geom_idx,
256 instance_num,
257 orientation_count: orient_count,
258 });
259 orientation_counts.push(orient_count);
260 }
261 }
262
263 Self {
264 geometries,
265 boundary,
266 config,
267 instances,
268 orientation_counts,
269 cancelled,
270 }
271 }
272
273 pub fn num_instances(&self) -> usize {
275 self.instances.len()
276 }
277
278 pub fn orientation_counts(&self) -> &[usize] {
280 &self.orientation_counts
281 }
282
283 pub fn decode(&self, chromosome: &PackingChromosome) -> (Vec<Placement<f64>>, f64, usize) {
285 let mut placements = Vec::new();
286
287 let margin = self.config.margin;
288 let spacing = self.config.spacing;
289
290 let bound_max_x = self.boundary.width() - margin;
291 let bound_max_y = self.boundary.depth() - margin;
292 let bound_max_z = self.boundary.height() - margin;
293
294 let mut current_x = margin;
296 let mut current_y = margin;
297 let mut current_z = margin;
298 let mut row_depth = 0.0_f64;
299 let mut layer_height = 0.0_f64;
300
301 let mut total_placed_volume = 0.0;
302 let mut total_placed_mass = 0.0;
303 let mut placed_count = 0;
304
305 for &instance_idx in &chromosome.order {
307 if self.cancelled.load(Ordering::Relaxed) {
308 break;
309 }
310
311 if instance_idx >= self.instances.len() {
312 continue;
313 }
314
315 let info = &self.instances[instance_idx];
316 let geom = &self.geometries[info.geometry_idx];
317
318 let orientation_idx = chromosome
320 .orientations
321 .get(instance_idx)
322 .copied()
323 .unwrap_or(0);
324 let orientation_idx = orientation_idx % info.orientation_count.max(1);
325
326 let dims = geom.dimensions_for_orientation(orientation_idx);
328 let g_width = dims.x;
329 let g_depth = dims.y;
330 let g_height = dims.z;
331
332 if let (Some(max_mass), Some(item_mass)) = (self.boundary.max_mass(), geom.mass()) {
334 if total_placed_mass + item_mass > max_mass {
335 continue;
336 }
337 }
338
339 if current_x + g_width > bound_max_x {
341 current_x = margin;
343 current_y += row_depth + spacing;
344 row_depth = 0.0;
345 }
346
347 if current_y + g_depth > bound_max_y {
349 current_x = margin;
351 current_y = margin;
352 current_z += layer_height + spacing;
353 row_depth = 0.0;
354 layer_height = 0.0;
355 }
356
357 if current_z + g_height > bound_max_z {
359 continue;
360 }
361
362 let placement = Placement::new_3d(
364 geom.id().clone(),
365 info.instance_num,
366 current_x,
367 current_y,
368 current_z,
369 0.0,
370 0.0,
371 0.0, );
373
374 placements.push(placement);
375 total_placed_volume += geom.measure();
376 if let Some(mass) = geom.mass() {
377 total_placed_mass += mass;
378 }
379 placed_count += 1;
380
381 current_x += g_width + spacing;
383 row_depth = row_depth.max(g_depth);
384 layer_height = layer_height.max(g_height);
385 }
386
387 let utilization = total_placed_volume / self.boundary.measure();
388 (placements, utilization, placed_count)
389 }
390}
391
392impl GaProblem for PackingProblem {
393 type Individual = PackingChromosome;
394
395 fn evaluate(&self, individual: &mut Self::Individual) {
396 let (_, utilization, placed_count) = self.decode(individual);
397
398 let placement_ratio = placed_count as f64 / individual.total_count.max(1) as f64;
400 let fitness = placement_ratio * 100.0 + utilization * 10.0;
401
402 individual.set_fitness(fitness, placed_count);
403 }
404
405 fn initialize_population<R: Rng>(&self, size: usize, rng: &mut R) -> Vec<Self::Individual> {
406 (0..size)
407 .map(|_| {
408 PackingChromosome::random_with_options(
409 self.num_instances(),
410 &self.orientation_counts,
411 rng,
412 )
413 })
414 .collect()
415 }
416
417 fn on_generation(
418 &self,
419 generation: u32,
420 best: &Self::Individual,
421 _population: &[Self::Individual],
422 ) {
423 log::debug!(
424 "GA 3D Packing Gen {}: fitness={:.4}, placed={}/{}",
425 generation,
426 best.fitness(),
427 best.placed_count,
428 best.total_count
429 );
430 }
431}
432
433pub fn run_ga_packing(
435 geometries: &[Geometry3D],
436 boundary: &Boundary3D,
437 config: &Config,
438 ga_config: GaConfig,
439 cancelled: Arc<AtomicBool>,
440) -> SolveResult<f64> {
441 let problem = PackingProblem::new(
442 geometries.to_vec(),
443 boundary.clone(),
444 config.clone(),
445 cancelled.clone(),
446 );
447
448 let runner = GaRunner::new(ga_config, problem);
449
450 let cancel_handle = runner.cancel_handle();
452 let cancelled_clone = cancelled.clone();
453 std::thread::spawn(move || {
454 while !cancelled_clone.load(Ordering::Relaxed) {
455 std::thread::sleep(std::time::Duration::from_millis(100));
456 }
457 cancel_handle.store(true, Ordering::Relaxed);
458 });
459
460 let ga_result = runner.run();
461
462 let problem = PackingProblem::new(
464 geometries.to_vec(),
465 boundary.clone(),
466 config.clone(),
467 Arc::new(AtomicBool::new(false)),
468 );
469
470 let (placements, utilization, _placed_count) = problem.decode(&ga_result.best);
471
472 let mut unplaced = Vec::new();
474 let mut placed_ids: std::collections::HashSet<String> = std::collections::HashSet::new();
475 for p in &placements {
476 placed_ids.insert(p.geometry_id.clone());
477 }
478 for geom in geometries {
479 if !placed_ids.contains(geom.id()) {
480 unplaced.push(geom.id().clone());
481 }
482 }
483
484 let mut result = SolveResult::new();
485 result.placements = placements;
486 result.unplaced = unplaced;
487 result.boundaries_used = 1;
488 result.utilization = utilization;
489 result.computation_time_ms = ga_result.elapsed.as_millis() as u64;
490 result.generations = Some(ga_result.generations);
491 result.best_fitness = Some(ga_result.best.fitness());
492 result.fitness_history = Some(ga_result.history);
493 result.strategy = Some("GeneticAlgorithm".to_string());
494 result.cancelled = cancelled.load(Ordering::Relaxed);
495 result.target_reached = ga_result.target_reached;
496
497 result
498}
499
500#[cfg(test)]
501mod tests {
502 use super::*;
503
504 #[test]
505 fn test_packing_chromosome_crossover() {
506 let mut rng = thread_rng();
507 let orientation_counts = vec![6, 6, 6, 6, 6];
508 let parent1 = PackingChromosome::random_with_options(5, &orientation_counts, &mut rng);
509 let parent2 = PackingChromosome::random_with_options(5, &orientation_counts, &mut rng);
510
511 let child = parent1.order_crossover(&parent2, &orientation_counts, &mut rng);
512
513 assert_eq!(child.order.len(), 5);
515 let mut sorted = child.order.clone();
516 sorted.sort();
517 assert_eq!(sorted, (0..5).collect::<Vec<_>>());
518
519 for (i, &orient) in child.orientations.iter().enumerate() {
521 assert!(orient < orientation_counts[i]);
522 }
523 }
524
525 #[test]
526 fn test_packing_chromosome_mutation() {
527 let mut rng = thread_rng();
528 let orientation_counts = vec![6, 6, 6, 6, 6];
529 let mut chromosome =
530 PackingChromosome::random_with_options(5, &orientation_counts, &mut rng);
531
532 chromosome.swap_mutate(&mut rng);
533
534 let mut sorted = chromosome.order.clone();
536 sorted.sort();
537 assert_eq!(sorted, (0..5).collect::<Vec<_>>());
538 }
539
540 #[test]
541 fn test_ga_packing_basic() {
542 let geometries = vec![
543 Geometry3D::new("B1", 20.0, 20.0, 20.0).with_quantity(2),
544 Geometry3D::new("B2", 15.0, 15.0, 15.0).with_quantity(2),
545 ];
546
547 let boundary = Boundary3D::new(100.0, 80.0, 50.0);
548 let config = Config::default();
549 let ga_config = GaConfig::default()
550 .with_population_size(20)
551 .with_max_generations(10);
552
553 let result = run_ga_packing(
554 &geometries,
555 &boundary,
556 &config,
557 ga_config,
558 Arc::new(AtomicBool::new(false)),
559 );
560
561 assert!(result.utilization > 0.0);
562 assert!(!result.placements.is_empty());
563 }
564
565 #[test]
566 fn test_ga_packing_all_placed() {
567 let geometries = vec![Geometry3D::new("B1", 20.0, 20.0, 20.0).with_quantity(4)];
568
569 let boundary = Boundary3D::new(100.0, 100.0, 100.0);
570 let config = Config::default();
571 let ga_config = GaConfig::default()
572 .with_population_size(30)
573 .with_max_generations(20);
574
575 let result = run_ga_packing(
576 &geometries,
577 &boundary,
578 &config,
579 ga_config,
580 Arc::new(AtomicBool::new(false)),
581 );
582
583 assert_eq!(result.placements.len(), 4);
585 assert!(result.unplaced.is_empty());
586 }
587
588 #[test]
589 fn test_ga_packing_with_orientations() {
590 use crate::geometry::OrientationConstraint;
591 let geometries = vec![Geometry3D::new("B1", 50.0, 10.0, 10.0)
593 .with_quantity(3)
594 .with_orientation(OrientationConstraint::Any)];
595
596 let boundary = Boundary3D::new(60.0, 60.0, 60.0);
597 let config = Config::default();
598 let ga_config = GaConfig::default()
599 .with_population_size(30)
600 .with_max_generations(20);
601
602 let result = run_ga_packing(
603 &geometries,
604 &boundary,
605 &config,
606 ga_config,
607 Arc::new(AtomicBool::new(false)),
608 );
609
610 assert!(result.utilization > 0.0);
611 assert!(!result.placements.is_empty());
612 }
613
614 #[test]
615 fn test_packing_problem_decode() {
616 let geometries = vec![Geometry3D::new("B1", 20.0, 20.0, 20.0).with_quantity(2)];
617
618 let boundary = Boundary3D::new(100.0, 100.0, 100.0);
619 let config = Config::default();
620 let cancelled = Arc::new(AtomicBool::new(false));
621
622 let problem = PackingProblem::new(geometries, boundary, config, cancelled);
623
624 assert_eq!(problem.num_instances(), 2);
625
626 let chromosome = PackingChromosome::new(2);
628 let (placements, utilization, placed_count) = problem.decode(&chromosome);
629
630 assert_eq!(placed_count, 2);
631 assert_eq!(placements.len(), 2);
632 assert!(utilization > 0.0);
633 }
634
635 #[test]
636 fn test_ga_packing_mass_constraint() {
637 let geometries = vec![Geometry3D::new("B1", 20.0, 20.0, 20.0)
638 .with_quantity(10)
639 .with_mass(100.0)];
640
641 let boundary = Boundary3D::new(100.0, 100.0, 100.0).with_max_mass(350.0);
642 let config = Config::default();
643 let ga_config = GaConfig::default()
644 .with_population_size(20)
645 .with_max_generations(10);
646
647 let result = run_ga_packing(
648 &geometries,
649 &boundary,
650 &config,
651 ga_config,
652 Arc::new(AtomicBool::new(false)),
653 );
654
655 assert!(result.placements.len() <= 3);
657 }
658}