1use std::time::Instant;
2
3use crate::ITEM_LIMIT;
4use crate::config::LBFConfig;
5use crate::opt::search::{item_placement_order, search};
6use jagua_rs::collision_detection::hazards::filter::NoHazardFilter;
7use jagua_rs::entities::{Instance, Item};
8use jagua_rs::probs::bpp::entities::{
9 BPInstance, BPLayoutType, BPPlacement, BPProblem, BPSolution,
10};
11use log::{debug, info};
12use rand::Rng;
13use rand::prelude::SmallRng;
14use thousands::Separable;
15
16pub struct LBFOptimizerBP {
18 pub instance: BPInstance,
19 pub problem: BPProblem,
20 pub config: LBFConfig,
21 pub rng: SmallRng,
23 pub sample_counter: usize,
24}
25
26impl LBFOptimizerBP {
27 pub fn new(instance: BPInstance, config: LBFConfig, rng: SmallRng) -> Self {
28 assert!(config.n_samples > 0);
29 let problem = BPProblem::new(instance.clone());
30 Self {
31 instance,
32 problem,
33 config,
34 rng,
35 sample_counter: 0,
36 }
37 }
38
39 pub fn solve(&mut self) -> BPSolution {
40 let start = Instant::now();
41
42 'outer: for item_id in item_placement_order(&self.instance) {
43 let item = self.instance.item(item_id);
44 'inner: while self.problem.item_demand_qtys[item_id] > 0 {
46 let placement = search_layouts(
48 &self.problem,
49 item,
50 &self.config,
51 &mut self.rng,
52 &mut self.sample_counter,
53 );
54
55 match placement {
56 Some(i_opt) => {
57 let l_index = self.problem.place_item(i_opt);
58 info!(
59 "[LBF] placing item {}/{} with id {} at [{}] in Layout {:?}",
60 self.problem.item_placed_qtys().sum::<usize>(),
61 self.instance.total_item_qty(),
62 i_opt.item_id,
63 i_opt.d_transf,
64 l_index
65 );
66 #[allow(clippy::absurd_extreme_comparisons)]
67 if self.problem.item_placed_qtys().sum::<usize>() >= ITEM_LIMIT {
68 break 'outer;
69 }
70 }
71 None => break 'inner, }
73 }
74 }
75
76 let solution = self.problem.save();
77
78 info!(
79 "[LBF] optimization finished in {:.3}ms ({} samples)",
80 start.elapsed().as_secs_f64() * 1000.0,
81 self.sample_counter.separate_with_commas()
82 );
83
84 info!(
85 "[LBF] solution contains {} items with a density of {:.3}%",
86 solution
87 .layout_snapshots
88 .values()
89 .map(|ls| ls.placed_items.len())
90 .sum::<usize>(),
91 solution.density(&self.instance) * 100.0
92 );
93 solution
94 }
95}
96
97fn search_layouts(
98 problem: &BPProblem,
99 item: &Item,
100 config: &LBFConfig,
101 rng: &mut impl Rng,
102 sample_counter: &mut usize,
103) -> Option<BPPlacement> {
104 let open_layouts = problem.layouts.keys().map(BPLayoutType::Open);
106 let bins_with_stock = problem
107 .bin_stock_qtys
108 .iter()
109 .enumerate()
110 .filter_map(|(bin_id, qty)| match *qty > 0 {
111 true => Some(BPLayoutType::Closed { bin_id }),
112 false => None,
113 });
114
115 for layout_id in open_layouts.chain(bins_with_stock) {
117 debug!("searching in layout {layout_id:?}");
118 let cde = match layout_id {
119 BPLayoutType::Open(lkey) => problem.layouts[lkey].cde(),
120 BPLayoutType::Closed { bin_id } => problem.instance.container(bin_id).base_cde.as_ref(),
121 };
122
123 let placement = match &item.hazard_filter {
124 None => search(cde, item, config, rng, sample_counter, &NoHazardFilter),
125 Some(hf) => search(cde, item, config, rng, sample_counter, hf),
126 };
127
128 if let Some((d_transf, _)) = placement {
129 return Some(BPPlacement {
130 layout_id,
131 item_id: item.id,
132 d_transf,
133 });
134 }
135 }
136 None
137}