use crate::boundary::Boundary3D;
use crate::geometry::Geometry3D;
use crate::packing_utils::{
build_instances, build_unplaced_list, layer_place_items, packing_fitness, InstanceInfo,
PlacementItem,
};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use u_nesting_core::brkga::{BrkgaConfig, BrkgaProblem, BrkgaRunner, RandomKeyChromosome};
use u_nesting_core::solver::Config;
use u_nesting_core::SolveResult;
pub struct BrkgaPackingProblem {
geometries: Vec<Geometry3D>,
boundary: Boundary3D,
config: Config,
instances: Vec<InstanceInfo>,
cancelled: Arc<AtomicBool>,
}
impl BrkgaPackingProblem {
pub fn new(
geometries: Vec<Geometry3D>,
boundary: Boundary3D,
config: Config,
cancelled: Arc<AtomicBool>,
) -> Self {
let instances = build_instances(&geometries);
Self {
geometries,
boundary,
config,
instances,
cancelled,
}
}
pub fn num_instances(&self) -> usize {
self.instances.len()
}
pub fn decode(
&self,
chromosome: &RandomKeyChromosome,
) -> (Vec<u_nesting_core::Placement<f64>>, f64, usize) {
let n = self.instances.len();
if n == 0 || chromosome.len() < n {
return (Vec::new(), 0.0, 0);
}
let order = chromosome.decode_as_permutation();
let order: Vec<usize> = order.into_iter().take(n).collect();
let items: Vec<PlacementItem> = order
.iter()
.map(|&instance_idx| {
let orientation_key_idx = n + instance_idx;
let orientation_idx = if orientation_key_idx < chromosome.len() {
let orient_count = self
.instances
.get(instance_idx)
.map(|i| i.orientation_count)
.unwrap_or(1);
chromosome.decode_as_discrete(orientation_key_idx, orient_count)
} else {
0
};
PlacementItem {
instance_idx,
orientation_idx,
}
})
.collect();
let result = layer_place_items(
&items,
&self.instances,
&self.geometries,
&self.boundary,
&self.config,
&self.cancelled,
);
(result.placements, result.utilization, result.placed_count)
}
}
impl BrkgaProblem for BrkgaPackingProblem {
fn num_keys(&self) -> usize {
self.instances.len() * 2
}
fn evaluate(&self, chromosome: &mut RandomKeyChromosome) {
let (_, utilization, placed_count) = self.decode(chromosome);
let fitness = packing_fitness(placed_count, self.instances.len(), utilization);
chromosome.set_fitness(fitness);
}
fn on_generation(
&self,
generation: u32,
best: &RandomKeyChromosome,
_population: &[RandomKeyChromosome],
) {
log::debug!(
"BRKGA 3D Packing Gen {}: fitness={:.4}",
generation,
best.fitness()
);
}
}
pub fn run_brkga_packing(
geometries: &[Geometry3D],
boundary: &Boundary3D,
config: &Config,
brkga_config: BrkgaConfig,
cancelled: Arc<AtomicBool>,
) -> SolveResult<f64> {
let problem = BrkgaPackingProblem::new(
geometries.to_vec(),
boundary.clone(),
config.clone(),
cancelled.clone(),
);
let runner = BrkgaRunner::with_cancellation(brkga_config, problem, cancelled.clone());
let brkga_result = runner.run();
let problem = BrkgaPackingProblem::new(
geometries.to_vec(),
boundary.clone(),
config.clone(),
Arc::new(AtomicBool::new(false)),
);
let (placements, utilization, _placed_count) = problem.decode(&brkga_result.best);
let unplaced = build_unplaced_list(&placements, geometries);
let mut result = SolveResult::new();
result.placements = placements;
result.unplaced = unplaced;
result.boundaries_used = 1;
result.utilization = utilization;
result.computation_time_ms = brkga_result.elapsed.as_millis() as u64;
result.generations = Some(brkga_result.generations);
result.best_fitness = Some(brkga_result.best.fitness());
result.fitness_history = Some(brkga_result.history);
result.strategy = Some("BRKGA".to_string());
result.cancelled = cancelled.load(Ordering::Relaxed);
result.target_reached = brkga_result.target_reached;
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_brkga_packing_basic() {
let geometries = vec![
Geometry3D::new("B1", 20.0, 20.0, 20.0).with_quantity(2),
Geometry3D::new("B2", 15.0, 15.0, 15.0).with_quantity(2),
];
let boundary = Boundary3D::new(100.0, 80.0, 50.0);
let config = Config::default();
let brkga_config = BrkgaConfig::default()
.with_population_size(30)
.with_max_generations(20);
let result = run_brkga_packing(
&geometries,
&boundary,
&config,
brkga_config,
Arc::new(AtomicBool::new(false)),
);
assert!(result.utilization > 0.0);
assert!(!result.placements.is_empty());
assert_eq!(result.strategy, Some("BRKGA".to_string()));
}
#[test]
fn test_brkga_packing_all_placed() {
let geometries = vec![Geometry3D::new("B1", 20.0, 20.0, 20.0).with_quantity(4)];
let boundary = Boundary3D::new(100.0, 100.0, 100.0);
let config = Config::default();
let brkga_config = BrkgaConfig::default()
.with_population_size(30)
.with_max_generations(30);
let result = run_brkga_packing(
&geometries,
&boundary,
&config,
brkga_config,
Arc::new(AtomicBool::new(false)),
);
assert_eq!(result.placements.len(), 4);
assert!(result.unplaced.is_empty());
}
#[test]
fn test_brkga_packing_with_orientations() {
use crate::geometry::OrientationConstraint;
let geometries = vec![Geometry3D::new("B1", 50.0, 10.0, 10.0)
.with_quantity(3)
.with_orientation(OrientationConstraint::Any)];
let boundary = Boundary3D::new(60.0, 60.0, 60.0);
let config = Config::default();
let brkga_config = BrkgaConfig::default()
.with_population_size(30)
.with_max_generations(30);
let result = run_brkga_packing(
&geometries,
&boundary,
&config,
brkga_config,
Arc::new(AtomicBool::new(false)),
);
assert!(result.utilization > 0.0);
assert!(!result.placements.is_empty());
}
#[test]
fn test_brkga_problem_decode() {
use rand::rngs::StdRng;
use rand::SeedableRng;
let geometries = vec![Geometry3D::new("B1", 10.0, 10.0, 10.0).with_quantity(2)];
let boundary = Boundary3D::new(500.0, 500.0, 500.0); let config = Config::default();
let cancelled = Arc::new(AtomicBool::new(false));
let problem = BrkgaPackingProblem::new(geometries, boundary, config, cancelled);
assert_eq!(problem.num_instances(), 2);
assert_eq!(problem.num_keys(), 4);
let mut rng = StdRng::seed_from_u64(42);
let chromosome = RandomKeyChromosome::random(problem.num_keys(), &mut rng);
let (placements, utilization, placed_count) = problem.decode(&chromosome);
assert!(
placed_count >= 1,
"Expected at least 1 placement but got {}",
placed_count
);
assert_eq!(placements.len(), placed_count);
if placed_count > 0 {
assert!(utilization > 0.0);
}
}
#[test]
fn test_brkga_packing_mass_constraint() {
let geometries = vec![Geometry3D::new("B1", 20.0, 20.0, 20.0)
.with_quantity(10)
.with_mass(100.0)];
let boundary = Boundary3D::new(100.0, 100.0, 100.0).with_max_mass(350.0);
let config = Config::default();
let brkga_config = BrkgaConfig::default()
.with_population_size(30)
.with_max_generations(20);
let result = run_brkga_packing(
&geometries,
&boundary,
&config,
brkga_config,
Arc::new(AtomicBool::new(false)),
);
assert!(result.placements.len() <= 3);
}
}