1use super::*;
3
4pub(crate) fn is_bin_packing_name(name: &str) -> bool {
5 [
6 name,
7 name.strip_prefix("gecode_").unwrap_or(""),
8 name.strip_prefix("fzn_").unwrap_or(""),
9 ]
10 .contains(&"gecode_bin_packing_load")
11}
12
13#[cfg(feature = "prepare")]
14pub(crate) fn bin_packing_signature() -> Vec<prepare::ArgumentType> {
15 vec![
16 prepare::ArgumentType {
17 kind: ValueType::Integer,
18 is_array: true,
19 is_var: true,
20 },
21 prepare::ArgumentType {
22 kind: ValueType::Integer,
23 is_array: true,
24 is_var: true,
25 },
26 prepare::ArgumentType {
27 kind: ValueType::Integer,
28 is_array: true,
29 is_var: false,
30 },
31 prepare::ArgumentType {
32 kind: ValueType::Integer,
33 is_array: false,
34 is_var: false,
35 },
36 ]
37}
38
39#[derive(Debug, Serialize, Deserialize)]
40pub(crate) struct BinPacking {
41 pub loads: Vec<Operand>,
42 pub bins: Vec<Operand>,
43 #[serde(
44 serialize_with = "serialize_integers",
45 deserialize_with = "deserialize_integers"
46 )]
47 pub weights: Vec<i64>,
48 #[serde(
49 serialize_with = "serialize_integer",
50 deserialize_with = "deserialize_integer"
51 )]
52 pub minimum: i64,
53}
54
55#[derive(Clone, Debug, Serialize)]
56pub struct PackingItem {
57 pub position: usize,
58 pub bin: Option<ArgumentValue>,
59 #[serde(serialize_with = "serialize_integer")]
60 pub weight: i64,
61}
62#[derive(Clone, Debug, Serialize)]
63pub struct PackingLoad {
64 pub position: usize,
65 #[serde(serialize_with = "serialize_integer")]
66 pub bin: i64,
67 pub supplied: Option<ArgumentValue>,
68 #[serde(serialize_with = "serialize_optional_integer")]
69 pub calculated: Option<i64>,
70 pub items: Vec<usize>,
71}
72#[derive(Clone, Debug, Serialize)]
73#[serde(tag = "kind", rename_all = "snake_case")]
74pub enum BinPackingFault {
75 Bin {
76 position: usize,
77 #[serde(serialize_with = "serialize_integer")]
78 value: i64,
79 },
80 Load {
81 position: usize,
82 },
83}
84#[derive(Clone, Debug, Serialize)]
85pub struct BinPackingFinding {
86 #[serde(serialize_with = "serialize_integer")]
87 pub minimum: i64,
88 pub items: Vec<PackingItem>,
89 pub loads: Vec<PackingLoad>,
90 pub faults: Vec<BinPackingFault>,
91}
92
93impl BinPacking {
94 pub fn validate(&self) -> Result<(), &'static str> {
95 if self.bins.len() != self.weights.len() {
96 return Err("bin and weight arrays must have equal lengths");
97 }
98 if self.weights.iter().any(|&weight| weight < 0) {
99 return Err("bin weights must be fixed nonnegative integers");
100 }
101 if !self.loads.is_empty()
102 && i64::try_from(self.loads.len() - 1)
103 .ok()
104 .and_then(|last| self.minimum.checked_add(last))
105 .is_none()
106 {
107 return Err("bin label interval exceeds i64");
108 }
109 Ok(())
110 }
111 pub fn operands(&self) -> Vec<&Operand> {
112 self.loads.iter().chain(&self.bins).collect()
113 }
114 fn bin_index(&self, bin: i64) -> Option<usize> {
115 bin.checked_sub(self.minimum)
116 .and_then(|offset| usize::try_from(offset).ok())
117 .filter(|&offset| offset < self.loads.len())
118 }
119 fn bin_faults(&self, values: &[Option<Value>]) -> Vec<BinPackingFault> {
120 self.bins
121 .iter()
122 .enumerate()
123 .filter_map(|(i, bin)| {
124 let value = bin.value(values)?.integer();
125 self.bin_index(value)
126 .is_none()
127 .then_some(BinPackingFault::Bin {
128 position: i + 1,
129 value,
130 })
131 })
132 .collect()
133 }
134 pub fn required_operands(&self, values: &[Option<Value>]) -> Vec<&Operand> {
135 if self.bin_faults(values).is_empty() {
136 self.operands()
137 } else {
138 Vec::new()
139 }
140 }
141 fn calculate(
142 &self,
143 values: &[Option<Value>],
144 constraint: &Constraint,
145 ) -> Result<Option<Vec<i64>>, CheckError> {
146 let mut loads = vec![0_i64; self.loads.len()];
147 for (bin, &weight) in self.bins.iter().zip(&self.weights) {
148 let Some(bin) = bin.value(values) else {
149 return Ok(None);
150 };
151 if let Some(position) = self.bin_index(bin.integer()) {
152 loads[position] = loads[position].checked_add(weight).ok_or_else(|| {
153 constraint.execution_error("checked i64 overflow in bin load sum")
154 })?;
155 }
156 }
157 Ok(Some(loads))
158 }
159 pub fn can_define(&self, output: usize) -> bool {
160 let matches =
161 |operand: &Operand| matches!(operand, Operand::Variable(slot) if *slot == output);
162 self.loads.iter().any(matches) && !self.bins.iter().any(matches)
163 }
164 pub fn derive(
165 &self,
166 output: usize,
167 values: &[Option<Value>],
168 constraint: &Constraint,
169 ) -> Result<Option<Value>, CheckError> {
170 let Some(loads) = self.calculate(values, constraint)? else {
171 return Ok(None);
172 };
173 let mut result = None;
174 for (operand, load) in self.loads.iter().zip(loads) {
175 if matches!(operand, Operand::Variable(slot) if *slot == output) {
176 if result.is_some_and(|prior| prior != load) {
177 return Ok(None);
178 }
179 result = Some(load);
180 }
181 }
182 Ok(result.map(Value::Integer))
183 }
184}
185impl PreparedModel {
186 pub(crate) fn evaluate_packing(
187 &self,
188 constraint: &Constraint,
189 packing: &BinPacking,
190 values: &[Option<Value>],
191 ) -> Result<Option<ViolationKind>, CheckError> {
192 let mut faults = packing.bin_faults(values);
193 let calculated = packing.calculate(values, constraint)?;
194 for (i, load) in packing.loads.iter().enumerate() {
195 if let (Some(supplied), Some(calculated)) = (load.value(values), &calculated)
196 && supplied.integer() != calculated[i]
197 {
198 faults.push(BinPackingFault::Load { position: i + 1 });
199 }
200 }
201 if faults.is_empty() {
202 return Ok(None);
203 }
204 let loads = packing
205 .loads
206 .iter()
207 .enumerate()
208 .map(|(i, load)| {
209 let supplied = self.argument(load, values);
210 let calculated = calculated.as_ref().map(|loads| loads[i]);
211 let bin = packing.minimum + i64::try_from(i).expect("validated bin interval");
212 PackingLoad {
213 position: i + 1,
214 bin,
215 supplied,
216 calculated,
217 items: packing
218 .bins
219 .iter()
220 .enumerate()
221 .filter_map(|(j, operand)| {
222 (operand.value(values) == Some(&Value::Integer(bin))).then_some(j + 1)
223 })
224 .collect(),
225 }
226 })
227 .collect();
228 Ok(Some(ViolationKind::BinPacking {
229 finding: BinPackingFinding {
230 minimum: packing.minimum,
231 items: packing
232 .bins
233 .iter()
234 .zip(&packing.weights)
235 .enumerate()
236 .map(|(i, (bin, &weight))| PackingItem {
237 position: i + 1,
238 bin: self.argument(bin, values),
239 weight,
240 })
241 .collect(),
242 loads,
243 faults,
244 },
245 }))
246 }
247}