jagua_rs/probs/spp/entities/
problem.rs

1use crate::entities::{Instance, Layout, PItemKey};
2use crate::geometry::DTransformation;
3use crate::probs::spp::entities::strip::Strip;
4use crate::probs::spp::entities::{SPInstance, SPSolution};
5use crate::probs::spp::util::assertions::problem_matches_solution;
6use itertools::Itertools;
7use std::time::Instant;
8
9/// Modifiable counterpart of [`SPInstance`]: items can be placed and removed, strip can be extended or fitted.
10#[derive(Clone)]
11pub struct SPProblem {
12    pub instance: SPInstance,
13    pub strip: Strip,
14    pub layout: Layout,
15    pub item_demand_qtys: Vec<usize>,
16}
17
18impl SPProblem {
19    pub fn new(instance: SPInstance) -> Self {
20        let item_demand_qtys = instance.items.iter().map(|(_, qty)| *qty).collect_vec();
21        let strip = instance.base_strip;
22        let layout = Layout::new(strip.into());
23
24        Self {
25            instance,
26            strip,
27            layout,
28            item_demand_qtys,
29        }
30    }
31
32    /// Modifies the width of the strip in the back, keeping the front fixed.
33    pub fn change_strip_width(&mut self, new_width: f32) {
34        self.strip.set_width(new_width);
35        self.layout.swap_container(self.strip.into());
36    }
37
38    /// Shrinks the strip to the minimum width that fits all items.
39    pub fn fit_strip(&mut self) {
40        let feasible_before = self.layout.is_feasible();
41
42        //Find the rightmost item in the strip and add some tolerance (avoiding false collision positives)
43        let item_x_max = self
44            .layout
45            .placed_items
46            .values()
47            .map(|pi| pi.shape.bbox.x_max)
48            .max_by(|a, b| a.partial_cmp(b).unwrap())
49            .unwrap()
50            * 1.00001;
51
52        // add the shape offset if any, the strip needs to be at least `offset` wider than the items
53        let fitted_width = item_x_max + self.strip.shape_modify_config.offset.unwrap_or(0.0);
54
55        self.change_strip_width(fitted_width);
56        debug_assert!(feasible_before == self.layout.is_feasible());
57    }
58
59    /// Places an item according to the given `SPPlacement` in the problem.
60    pub fn place_item(&mut self, placement: SPPlacement) -> PItemKey {
61        self.register_included_item(placement.item_id);
62        let item = self.instance.item(placement.item_id);
63
64        self.layout.place_item(item, placement.d_transf)
65    }
66
67    /// Removes a placed item from the strip. Returns the placement of the item.
68    /// Set `commit_instantly` to false if there's a high chance that this modification will be reverted.
69    pub fn remove_item(&mut self, pkey: PItemKey, commit_instant: bool) -> SPPlacement {
70        let pi = self.layout.remove_item(pkey, commit_instant);
71        self.deregister_included_item(pi.item_id);
72
73        SPPlacement {
74            item_id: pi.item_id,
75            d_transf: pi.d_transf,
76        }
77    }
78
79    /// Creates a snapshot of the current state of the problem as a [`SPSolution`].
80    pub fn save(&mut self) -> SPSolution {
81        let solution = SPSolution {
82            layout_snapshot: self.layout.save(),
83            strip: self.strip,
84            time_stamp: Instant::now(),
85        };
86
87        debug_assert!(problem_matches_solution(self, &solution));
88
89        solution
90    }
91
92    /// Restores the state of the problem to the given [`SPSolution`].
93    pub fn restore(&mut self, solution: &SPSolution) {
94        if self.strip == solution.strip {
95            // the strip is the same, restore the layout
96            self.layout.restore(&solution.layout_snapshot);
97        } else {
98            // the strip has changed, rebuild the layout
99            self.layout = Layout::from_snapshot(&solution.layout_snapshot);
100            self.strip = solution.strip;
101        }
102
103        //Restore the item demands
104        {
105            self.item_demand_qtys
106                .iter_mut()
107                .enumerate()
108                .for_each(|(id, qty)| *qty = self.instance.item_qty(id));
109
110            self.layout
111                .placed_items()
112                .iter()
113                .for_each(|(_, pi)| self.item_demand_qtys[pi.item_id] -= 1);
114        }
115        debug_assert!(problem_matches_solution(self, solution));
116    }
117
118    fn register_included_item(&mut self, item_id: usize) {
119        self.item_demand_qtys[item_id] -= 1;
120    }
121
122    fn deregister_included_item(&mut self, item_id: usize) {
123        self.item_demand_qtys[item_id] += 1;
124    }
125
126    pub fn density(&self) -> f32 {
127        self.layout.density(&self.instance)
128    }
129
130    pub fn strip_width(&self) -> f32 {
131        self.strip.width
132    }
133}
134
135/// Represents a placement of an item in the strip packing problem.
136#[derive(Debug, Clone, Copy)]
137pub struct SPPlacement {
138    pub item_id: usize,
139    pub d_transf: DTransformation,
140}