1use jagua_rs::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::{NoFilter, QualityZoneFilter};
7use jagua_rs::entities::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_idx in
43 item_placement_order(self.instance.items.iter().map(|(item, _)| item.as_ref()))
44 {
45 let item = self.instance.item(item_idx);
46 'inner: while self.problem.item_demand_qtys()[item_idx] > 0 {
48 let placement = search_layouts(
50 &self.problem,
51 item,
52 &self.config,
53 &mut self.rng,
54 &mut self.sample_counter,
55 );
56
57 match placement {
58 Some(i_opt) => {
59 let l_index = self.problem.place_item(i_opt);
60 info!(
61 "[LBF] placing item {}/{} with id {} at [{}] in Layout {:?}",
62 self.problem.item_placed_qtys().sum::<usize>(),
63 self.instance.total_item_qty(),
64 i_opt.item_idx,
65 i_opt.d_transf,
66 l_index
67 );
68 #[allow(clippy::absurd_extreme_comparisons)]
69 if self.problem.item_placed_qtys().sum::<usize>() >= ITEM_LIMIT {
70 break 'outer;
71 }
72 }
73 None => break 'inner, }
75 }
76 }
77
78 let solution = self.problem.save();
79
80 info!(
81 "[LBF] optimization finished in {:.3}ms ({} samples)",
82 start.elapsed().as_secs_f64() * 1000.0,
83 self.sample_counter.separate_with_commas()
84 );
85
86 info!(
87 "[LBF] solution contains {} items with a density of {:.3}%",
88 solution
89 .layout_snapshots()
90 .values()
91 .map(|ls| ls.placed_items().len())
92 .sum::<usize>(),
93 solution.density() * 100.0
94 );
95 solution
96 }
97}
98
99fn search_layouts(
100 problem: &BPProblem,
101 item: &Item,
102 config: &LBFConfig,
103 rng: &mut impl Rng,
104 sample_counter: &mut usize,
105) -> Option<BPPlacement> {
106 let open_layouts = problem.layouts().keys().map(BPLayoutType::Open);
108 let bins_with_stock =
109 problem
110 .bin_stock_qtys()
111 .iter()
112 .enumerate()
113 .filter_map(|(bin_id, qty)| match *qty > 0 {
114 true => Some(BPLayoutType::Closed { bin_id }),
115 false => None,
116 });
117
118 for layout_id in open_layouts.chain(bins_with_stock) {
120 debug!("searching in layout {layout_id:?}");
121 let cde = match layout_id {
122 BPLayoutType::Open(lkey) => problem.layouts()[lkey].cde(),
123 BPLayoutType::Closed { bin_id } => problem.instance().bins[bin_id]
124 .container
125 .base_cde()
126 .as_ref(),
127 };
128
129 let placement = match &item.min_quality() {
130 None => search(cde, item, config, rng, sample_counter, &NoFilter),
131 Some(min_quality) => {
132 let filter = QualityZoneFilter {
133 min_quality: *min_quality,
134 };
135 search(cde, item, config, rng, sample_counter, &filter)
136 }
137 };
138
139 if let Some((d_transf, _)) = placement {
140 return Some(BPPlacement {
141 layout_id,
142 item_idx: item.idx(),
143 d_transf,
144 });
145 }
146 }
147 None
148}