problemreductions/models/misc/
partially_ordered_knapsack.rs1use crate::registry::{CreateSpec, ProblemSchemaEntry};
8use crate::traits::Problem;
9use crate::types::Max;
10use serde::{Deserialize, Serialize};
11
12inventory::submit! {
13 ProblemSchemaEntry {
14 name: "PartiallyOrderedKnapsack",
15 display_name: "Partially Ordered Knapsack",
16 aliases: &["POK"],
17 dimensions: &[],
18 category: crate::registry::ProblemCategory::Misc,
19 module_path: module_path!(),
20 description: "Select items to maximize total value subject to precedence constraints and weight capacity",
21 fields: PartiallyOrderedKnapsackCreateSpec::FIELDS,
22 }
23}
24
25#[derive(Serialize, Deserialize)]
57struct PartiallyOrderedKnapsackRaw {
58 weights: Vec<i64>,
59 values: Vec<i64>,
60 precedences: Vec<(usize, usize)>,
61 capacity: i64,
62}
63
64#[derive(Debug, Clone)]
65pub struct PartiallyOrderedKnapsack {
66 weights: Vec<i64>,
67 values: Vec<i64>,
68 precedences: Vec<(usize, usize)>,
69 capacity: i64,
70 predecessors: Vec<Vec<usize>>,
73}
74
75#[derive(Debug, Deserialize, crate::CreateSpec)]
76struct PartiallyOrderedKnapsackCreateSpec {
77 weights: Vec<i64>,
78 values: Vec<i64>,
79 precedences: Option<Vec<(usize, usize)>>,
80 capacity: i64,
81}
82
83impl TryFrom<PartiallyOrderedKnapsackCreateSpec> for PartiallyOrderedKnapsack {
84 type Error = crate::registry::ConstructionError;
85
86 fn try_from(spec: PartiallyOrderedKnapsackCreateSpec) -> Result<Self, Self::Error> {
87 Self::try_new(
88 spec.weights,
89 spec.values,
90 spec.precedences.unwrap_or_default(),
91 spec.capacity,
92 )
93 }
94}
95
96impl Serialize for PartiallyOrderedKnapsack {
97 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
98 PartiallyOrderedKnapsackRaw {
99 weights: self.weights.clone(),
100 values: self.values.clone(),
101 precedences: self.precedences.clone(),
102 capacity: self.capacity,
103 }
104 .serialize(serializer)
105 }
106}
107
108impl<'de> Deserialize<'de> for PartiallyOrderedKnapsack {
109 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
110 let raw = PartiallyOrderedKnapsackRaw::deserialize(deserializer)?;
111 Self::try_new(raw.weights, raw.values, raw.precedences, raw.capacity)
112 .map_err(serde::de::Error::custom)
113 }
114}
115
116impl PartiallyOrderedKnapsack {
117 pub fn new(
130 weights: Vec<i64>,
131 values: Vec<i64>,
132 precedences: Vec<(usize, usize)>,
133 capacity: i64,
134 ) -> Self {
135 Self::try_new(weights, values, precedences, capacity)
136 .unwrap_or_else(|error| panic!("{error}"))
137 }
138
139 fn try_new(
140 weights: Vec<i64>,
141 values: Vec<i64>,
142 precedences: Vec<(usize, usize)>,
143 capacity: i64,
144 ) -> Result<Self, crate::registry::ConstructionError> {
145 if weights.len() != values.len() {
146 return Err("weights and values must have the same length".into());
147 }
148 if capacity < 0 {
149 return Err("capacity must be non-negative".into());
150 }
151 for (i, &w) in weights.iter().enumerate() {
152 if w < 0 {
153 return Err(format!("weight[{i}] must be non-negative, got {w}").into());
154 }
155 }
156 for (i, &v) in values.iter().enumerate() {
157 if v < 0 {
158 return Err(format!("value[{i}] must be non-negative, got {v}").into());
159 }
160 }
161 let n = weights.len();
162 for &(a, b) in &precedences {
163 if a >= n {
164 return Err(format!("precedence index {a} out of bounds (n={n})").into());
165 }
166 if b >= n {
167 return Err(format!("precedence index {b} out of bounds (n={n})").into());
168 }
169 }
170 let predecessors = Self::compute_predecessors(&precedences, n);
171 for (i, preds) in predecessors.iter().enumerate() {
173 if preds.contains(&i) {
174 return Err(format!("precedences contain a cycle involving item {i}").into());
175 }
176 }
177 Ok(Self {
178 weights,
179 values,
180 precedences,
181 capacity,
182 predecessors,
183 })
184 }
185
186 fn compute_predecessors(precedences: &[(usize, usize)], n: usize) -> Vec<Vec<usize>> {
188 let mut reachable = vec![vec![false; n]; n];
189 for &(a, b) in precedences {
190 reachable[a][b] = true;
191 }
192 for k in 0..n {
193 for i in 0..n {
194 for j in 0..n {
195 if reachable[i][k] && reachable[k][j] {
196 reachable[i][j] = true;
197 }
198 }
199 }
200 }
201 (0..n)
202 .map(|b| (0..n).filter(|&a| reachable[a][b]).collect())
203 .collect()
204 }
205
206 pub fn weights(&self) -> &[i64] {
208 &self.weights
209 }
210
211 pub fn values(&self) -> &[i64] {
213 &self.values
214 }
215
216 pub fn precedences(&self) -> &[(usize, usize)] {
218 &self.precedences
219 }
220
221 pub fn capacity(&self) -> i64 {
223 self.capacity
224 }
225
226 pub fn max_weight_bits(&self) -> u64 {
228 crate::types::max_numeric_magnitude_bits(
229 self.weights
230 .iter()
231 .copied()
232 .chain(std::iter::once(self.capacity)),
233 )
234 }
235
236 pub fn num_items(&self) -> usize {
238 self.weights.len()
239 }
240
241 pub fn num_precedences(&self) -> usize {
243 self.precedences.len()
244 }
245
246 fn is_downward_closed(&self, config: &[bool]) -> bool {
251 for (b, preds) in self.predecessors.iter().enumerate() {
252 if config[b] {
253 for &a in preds {
254 if !config[a] {
255 return false;
256 }
257 }
258 }
259 }
260 true
261 }
262}
263
264impl Problem for PartiallyOrderedKnapsack {
265 const NAME: &'static str = "PartiallyOrderedKnapsack";
266 type Solution = Vec<bool>;
267 type Value = Max<i64>;
268
269 crate::problem_parameters![
270 ("max_weight_bits", max_weight_bits),
271 ("num_items", num_items),
272 ("num_precedences", num_precedences),
273 ];
274
275 fn variant() -> Vec<(&'static str, &'static str)> {
276 crate::variant_params![]
277 }
278
279 fn evaluate(
280 &self,
281 config: &Self::Solution,
282 ) -> Result<Max<i64>, crate::traits::EvaluationError> {
283 Ok({
284 if config.len() != self.num_items() {
285 return Err(crate::traits::EvaluationError::InvalidConfiguration(
286 "item-selection length does not match the instance".into(),
287 ));
288 }
289 if !self.is_downward_closed(config) {
291 return Ok(Max(None));
292 }
293 let total_weight = config
295 .iter()
296 .enumerate()
297 .filter(|(_, &x)| x)
298 .map(|(i, _)| self.weights[i])
299 .try_fold(0_i64, |total, weight| {
300 total.checked_add(weight).ok_or_else(|| {
301 crate::traits::EvaluationError::IntegerOverflow(
302 "summing selected partially ordered knapsack weights".into(),
303 )
304 })
305 })?;
306 if total_weight > self.capacity {
307 return Ok(Max(None));
308 }
309 let total_value = config
311 .iter()
312 .enumerate()
313 .filter(|(_, &x)| x)
314 .map(|(i, _)| self.values[i])
315 .try_fold(0_i64, |total, value| {
316 total.checked_add(value).ok_or_else(|| {
317 crate::traits::EvaluationError::IntegerOverflow(
318 "summing selected partially ordered knapsack values".into(),
319 )
320 })
321 })?;
322 Max(Some(total_value))
323 })
324 }
325}
326
327impl crate::solvers::BruteForceProblem for PartiallyOrderedKnapsack {
328 fn dimensions(&self) -> Vec<usize> {
329 vec![2; self.num_items()]
330 }
331}
332
333crate::declare_variants! {
334 default PartiallyOrderedKnapsack => "2^num_items" create PartiallyOrderedKnapsackCreateSpec,
335}
336
337crate::register_brute_force! {
338 PartiallyOrderedKnapsack decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
339}
340
341#[cfg(feature = "example-db")]
342pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
343 vec![crate::example_db::specs::ModelExampleSpec {
344 id: "partially_ordered_knapsack",
345 instance: Box::new(PartiallyOrderedKnapsack::new(
346 vec![2, 3, 4, 1, 2, 3],
347 vec![3, 2, 5, 4, 3, 8],
348 vec![(0, 2), (0, 3), (1, 4), (3, 5), (4, 5)],
349 11,
350 )),
351 optimal_config: serde_json::json!(vec![true, true, false, true, true, true]),
352 optimal_value: serde_json::json!(20),
353 }]
354}
355
356#[cfg(test)]
357#[path = "../../unit_tests/models/misc/partially_ordered_knapsack.rs"]
358mod tests;