jagua_rs/probs/bpp/entities/
problem.rs1use crate::Instant;
2use crate::entities::Layout;
3use crate::entities::{PItemKey, PlacedItem};
4use crate::geometry::DTransformation;
5use crate::probs::bpp::entities::BPInstance;
6use crate::probs::bpp::entities::BPSolution;
7use crate::probs::bpp::util::assertions::problem_matches_solution;
8use itertools::Itertools;
9use slotmap::{SecondaryMap, SlotMap, new_key_type};
10
11new_key_type! {
12 pub struct LayKey;
14}
15
16#[derive(Clone)]
18pub struct BPProblem {
19 pub(crate) instance: BPInstance,
20 pub(crate) layouts: SlotMap<LayKey, Layout>,
21 pub(crate) layout_bins: SecondaryMap<LayKey, usize>,
23 pub(crate) item_demand_qtys: Vec<usize>,
24 pub(crate) bin_stock_qtys: Vec<usize>,
25}
26
27impl BPProblem {
28 #[must_use]
29 pub fn new(instance: BPInstance) -> Self {
30 let item_demand_qtys = instance.items.iter().map(|(_, qty)| *qty).collect_vec();
31 let bin_stock_qtys = instance.bins.iter().map(|bin| bin.stock).collect_vec();
32
33 Self {
34 instance,
35 layouts: SlotMap::with_key(),
36 layout_bins: SecondaryMap::new(),
37 item_demand_qtys,
38 bin_stock_qtys,
39 }
40 }
41
42 pub fn remove_layout(&mut self, key: LayKey) {
44 self.deregister_layout(key);
45 }
46
47 pub fn place_item(&mut self, placement: BPPlacement) -> (LayKey, PItemKey) {
50 assert!(
51 self.item_demand_qtys[placement.item_idx] > 0,
52 "item demand exhausted"
53 );
54 let lkey = match placement.layout_id {
55 BPLayoutType::Open(lkey) => lkey,
56 BPLayoutType::Closed { bin_id } => {
57 let bin = &self.instance.bins[bin_id];
59 let layout = Layout::new(bin.container.clone());
60 self.register_layout(layout, bin_id)
61 }
62 };
63
64 let layout = &mut self.layouts[lkey];
65 let item = self.instance.item(placement.item_idx);
66 let pik = layout.place_item(item, placement.d_transf);
67
68 self.register_included_item(placement.item_idx);
69
70 (lkey, pik)
71 }
72
73 pub fn remove_item(&mut self, lkey: LayKey, pik: PItemKey) -> BPPlacement {
75 let pi = self.layouts[lkey].remove_item(pik);
76 self.deregister_included_item(pi.item.idx);
77 if self.layouts[lkey].is_empty() {
78 let bin_id = self.layout_bins[lkey];
80 self.deregister_layout(lkey);
81 BPPlacement::from_placed_item(BPLayoutType::Closed { bin_id }, &pi)
82 } else {
83 BPPlacement::from_placed_item(BPLayoutType::Open(lkey), &pi)
84 }
85 }
86
87 #[must_use]
89 pub fn save(&self) -> BPSolution {
90 let layout_snapshots = self
91 .layouts
92 .iter()
93 .map(|(lkey, l)| (lkey, l.save()))
94 .collect();
95
96 let solution = BPSolution {
97 layout_snapshots,
98 layout_bins: self.layout_bins.clone(),
99 time_stamp: Instant::now(),
100 };
101
102 debug_assert!(problem_matches_solution(self, &solution));
103
104 solution
105 }
106
107 pub fn restore(&mut self, solution: &BPSolution) -> bool {
111 let mut layout_keys_changed = false;
112 let mut layouts_to_remove = vec![];
113
114 for (lkey, layout) in &mut self.layouts {
117 match solution.layout_snapshots.get(lkey) {
118 Some(ls) => {
119 self.layout_bins[lkey] = solution.layout_bins[lkey];
120 if layout.restore(ls).is_err() {
122 *layout = Layout::from_snapshot(ls);
123 }
124 }
125 _ => {
126 layouts_to_remove.push(lkey);
127 }
128 }
129 }
130
131 for lkey in layouts_to_remove {
133 layout_keys_changed = true;
134 self.layouts.remove(lkey);
135 self.layout_bins.remove(lkey);
136 }
137
138 for (lkey, ls) in &solution.layout_snapshots {
140 if !self.layouts.contains_key(lkey) {
141 let new_key = self.layouts.insert(Layout::from_snapshot(ls));
142 self.layout_bins.insert(new_key, solution.layout_bins[lkey]);
143 layout_keys_changed = true;
144 }
145 }
146
147 {
149 self.item_demand_qtys
150 .iter_mut()
151 .enumerate()
152 .for_each(|(idx, demand)| {
153 *demand = self.instance.item_qty(idx);
154 });
155
156 self.bin_stock_qtys
157 .iter_mut()
158 .enumerate()
159 .for_each(|(id, stock)| {
160 *stock = self.instance.bin_qty(id);
161 });
162
163 self.layouts.iter().for_each(|(key, layout)| {
164 self.bin_stock_qtys[self.layout_bins[key]] -= 1;
165 layout
166 .placed_items
167 .values()
168 .for_each(|pi| self.item_demand_qtys[pi.item.idx] -= 1);
169 });
170 }
171
172 debug_assert!(problem_matches_solution(self, solution));
173 layout_keys_changed
174 }
175
176 #[must_use]
177 pub fn instance(&self) -> &BPInstance {
178 &self.instance
179 }
180
181 #[must_use]
182 pub fn layouts(&self) -> &SlotMap<LayKey, Layout> {
183 &self.layouts
184 }
185
186 #[must_use]
187 pub fn layout_bins(&self) -> &SecondaryMap<LayKey, usize> {
188 &self.layout_bins
189 }
190
191 #[must_use]
192 pub fn item_demand_qtys(&self) -> &[usize] {
193 &self.item_demand_qtys
194 }
195
196 #[must_use]
197 pub fn bin_stock_qtys(&self) -> &[usize] {
198 &self.bin_stock_qtys
199 }
200
201 #[must_use]
202 pub fn density(&self) -> f32 {
203 let total_bin_area = self
204 .layouts
205 .values()
206 .map(|l| l.container.area())
207 .sum::<f32>();
208
209 let total_item_area = self
210 .layouts
211 .values()
212 .map(Layout::placed_item_area)
213 .sum::<f32>();
214
215 total_item_area / total_bin_area
216 }
217
218 pub fn item_placed_qtys(&self) -> impl Iterator<Item = usize> {
219 self.item_demand_qtys
220 .iter()
221 .enumerate()
222 .map(|(i, demand)| self.instance.item_qty(i) - demand)
223 }
224
225 pub fn bin_used_qtys(&self) -> impl Iterator<Item = usize> {
226 self.bin_stock_qtys
227 .iter()
228 .enumerate()
229 .map(|(i, stock)| self.instance.bin_qty(i) - stock)
230 }
231
232 #[must_use]
234 pub fn bin_cost(&self) -> u64 {
235 self.bin_used_qtys()
236 .enumerate()
237 .map(|(id, qty)| self.instance.bins[id].cost * qty as u64)
238 .sum()
239 }
240
241 fn register_layout(&mut self, layout: Layout, bin_idx: usize) -> LayKey {
242 self.open_bin(bin_idx);
243 layout
244 .placed_items
245 .values()
246 .for_each(|pi| self.register_included_item(pi.item.idx));
247 let key = self.layouts.insert(layout);
248 self.layout_bins.insert(key, bin_idx);
249 key
250 }
251
252 fn deregister_layout(&mut self, key: LayKey) {
253 let layout = self.layouts.remove(key).expect("layout key not present");
254 let bin_idx = self
255 .layout_bins
256 .remove(key)
257 .expect("layout must have a source bin");
258 self.close_bin(bin_idx);
259 layout
260 .placed_items
261 .values()
262 .for_each(|pi| self.deregister_included_item(pi.item.idx));
263 }
264
265 fn register_included_item(&mut self, item_idx: usize) {
266 self.item_demand_qtys[item_idx] -= 1;
267 }
268
269 fn deregister_included_item(&mut self, item_idx: usize) {
270 self.item_demand_qtys[item_idx] += 1;
271 }
272
273 fn open_bin(&mut self, bin_id: usize) {
274 assert!(self.bin_stock_qtys[bin_id] > 0, "bin stock exhausted");
275 self.bin_stock_qtys[bin_id] -= 1;
276 }
277
278 fn close_bin(&mut self, bin_id: usize) {
279 self.bin_stock_qtys[bin_id] += 1;
280 }
281
282 #[must_use]
283 pub fn n_placed_items(&self) -> usize {
284 self.layouts.values().map(|l| l.placed_items.len()).sum()
285 }
286}
287
288#[derive(Clone, Debug, Copy)]
289pub struct BPPlacement {
291 pub layout_id: BPLayoutType,
293 pub item_idx: usize,
295 pub d_transf: DTransformation,
297}
298
299impl BPPlacement {
300 #[must_use]
301 pub fn from_placed_item(layout_id: BPLayoutType, placed_item: &PlacedItem) -> Self {
302 BPPlacement {
303 layout_id,
304 item_idx: placed_item.item.idx,
305 d_transf: placed_item.d_transf,
306 }
307 }
308}
309
310#[derive(Debug, Clone, Copy, PartialEq, Eq)]
312pub enum BPLayoutType {
313 Open(LayKey),
315 Closed { bin_id: usize },
317}