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 if spec.weights.len() != spec.values.len() {
88 return Err("weights and values must have the same length"
89 .to_string()
90 .into());
91 }
92 if spec.capacity < 0 {
93 return Err("capacity must be non-negative".to_string().into());
94 }
95 if let Some((index, weight)) = spec
96 .weights
97 .iter()
98 .enumerate()
99 .find(|(_, weight)| **weight < 0)
100 {
101 return Err(format!("weight[{index}] must be non-negative, got {weight}").into());
102 }
103 if let Some((index, value)) = spec
104 .values
105 .iter()
106 .enumerate()
107 .find(|(_, value)| **value < 0)
108 {
109 return Err(format!("value[{index}] must be non-negative, got {value}").into());
110 }
111 let precedences = spec.precedences.unwrap_or_default();
112 let num_items = spec.weights.len();
113 if let Some(&(pred, succ)) = precedences
114 .iter()
115 .find(|&&(pred, succ)| pred >= num_items || succ >= num_items)
116 {
117 return Err(format!(
118 "precedence ({pred}, {succ}) is out of range for {num_items} items"
119 )
120 .into());
121 }
122 let predecessors = Self::compute_predecessors(&precedences, num_items);
123 if let Some(item) = predecessors
124 .iter()
125 .enumerate()
126 .find_map(|(item, preds)| preds.contains(&item).then_some(item))
127 {
128 return Err(format!("precedences contain a cycle involving item {item}").into());
129 }
130 Ok(Self::new(
131 spec.weights,
132 spec.values,
133 precedences,
134 spec.capacity,
135 ))
136 }
137}
138
139impl Serialize for PartiallyOrderedKnapsack {
140 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
141 PartiallyOrderedKnapsackRaw {
142 weights: self.weights.clone(),
143 values: self.values.clone(),
144 precedences: self.precedences.clone(),
145 capacity: self.capacity,
146 }
147 .serialize(serializer)
148 }
149}
150
151impl<'de> Deserialize<'de> for PartiallyOrderedKnapsack {
152 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
153 let raw = PartiallyOrderedKnapsackRaw::deserialize(deserializer)?;
154 Ok(Self::new(
155 raw.weights,
156 raw.values,
157 raw.precedences,
158 raw.capacity,
159 ))
160 }
161}
162
163impl PartiallyOrderedKnapsack {
164 pub fn new(
177 weights: Vec<i64>,
178 values: Vec<i64>,
179 precedences: Vec<(usize, usize)>,
180 capacity: i64,
181 ) -> Self {
182 assert_eq!(
183 weights.len(),
184 values.len(),
185 "weights and values must have the same length"
186 );
187 assert!(capacity >= 0, "capacity must be non-negative");
188 for (i, &w) in weights.iter().enumerate() {
189 assert!(w >= 0, "weight[{i}] must be non-negative, got {w}");
190 }
191 for (i, &v) in values.iter().enumerate() {
192 assert!(v >= 0, "value[{i}] must be non-negative, got {v}");
193 }
194 let n = weights.len();
195 for &(a, b) in &precedences {
196 assert!(a < n, "precedence index {a} out of bounds (n={n})");
197 assert!(b < n, "precedence index {b} out of bounds (n={n})");
198 }
199 let predecessors = Self::compute_predecessors(&precedences, n);
200 for (i, preds) in predecessors.iter().enumerate() {
202 assert!(
203 !preds.contains(&i),
204 "precedences contain a cycle involving item {i}"
205 );
206 }
207 Self {
208 weights,
209 values,
210 precedences,
211 capacity,
212 predecessors,
213 }
214 }
215
216 fn compute_predecessors(precedences: &[(usize, usize)], n: usize) -> Vec<Vec<usize>> {
218 let mut reachable = vec![vec![false; n]; n];
219 for &(a, b) in precedences {
220 reachable[a][b] = true;
221 }
222 for k in 0..n {
223 for i in 0..n {
224 for j in 0..n {
225 if reachable[i][k] && reachable[k][j] {
226 reachable[i][j] = true;
227 }
228 }
229 }
230 }
231 (0..n)
232 .map(|b| (0..n).filter(|&a| reachable[a][b]).collect())
233 .collect()
234 }
235
236 pub fn weights(&self) -> &[i64] {
238 &self.weights
239 }
240
241 pub fn values(&self) -> &[i64] {
243 &self.values
244 }
245
246 pub fn precedences(&self) -> &[(usize, usize)] {
248 &self.precedences
249 }
250
251 pub fn capacity(&self) -> i64 {
253 self.capacity
254 }
255
256 pub fn num_items(&self) -> usize {
258 self.weights.len()
259 }
260
261 pub fn num_precedences(&self) -> usize {
263 self.precedences.len()
264 }
265
266 fn is_downward_closed(&self, config: &[bool]) -> bool {
271 for (b, preds) in self.predecessors.iter().enumerate() {
272 if config[b] {
273 for &a in preds {
274 if !config[a] {
275 return false;
276 }
277 }
278 }
279 }
280 true
281 }
282}
283
284impl Problem for PartiallyOrderedKnapsack {
285 const NAME: &'static str = "PartiallyOrderedKnapsack";
286 type Solution = Vec<bool>;
287 type Value = Max<i64>;
288
289 crate::problem_parameters![
290 ("num_items", num_items),
291 ("num_precedences", num_precedences),
292 ];
293
294 fn variant() -> Vec<(&'static str, &'static str)> {
295 crate::variant_params![]
296 }
297
298 fn evaluate(
299 &self,
300 config: &Self::Solution,
301 ) -> Result<Max<i64>, crate::traits::EvaluationError> {
302 Ok({
303 if config.len() != self.num_items() {
304 return Err(crate::traits::EvaluationError::InvalidConfiguration(
305 "item-selection length does not match the instance".into(),
306 ));
307 }
308 if !self.is_downward_closed(config) {
310 return Ok(Max(None));
311 }
312 let total_weight = config
314 .iter()
315 .enumerate()
316 .filter(|(_, &x)| x)
317 .map(|(i, _)| self.weights[i])
318 .try_fold(0_i64, |total, weight| {
319 total.checked_add(weight).ok_or_else(|| {
320 crate::traits::EvaluationError::IntegerOverflow(
321 "summing selected partially ordered knapsack weights".into(),
322 )
323 })
324 })?;
325 if total_weight > self.capacity {
326 return Ok(Max(None));
327 }
328 let total_value = config
330 .iter()
331 .enumerate()
332 .filter(|(_, &x)| x)
333 .map(|(i, _)| self.values[i])
334 .try_fold(0_i64, |total, value| {
335 total.checked_add(value).ok_or_else(|| {
336 crate::traits::EvaluationError::IntegerOverflow(
337 "summing selected partially ordered knapsack values".into(),
338 )
339 })
340 })?;
341 Max(Some(total_value))
342 })
343 }
344}
345
346impl crate::solvers::BruteForceProblem for PartiallyOrderedKnapsack {
347 fn dimensions(&self) -> Vec<usize> {
348 vec![2; self.num_items()]
349 }
350}
351
352crate::declare_variants! {
353 default PartiallyOrderedKnapsack => "2^num_items" create PartiallyOrderedKnapsackCreateSpec,
354}
355
356crate::register_brute_force! {
357 PartiallyOrderedKnapsack decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
358}
359
360#[cfg(feature = "example-db")]
361pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
362 vec![crate::example_db::specs::ModelExampleSpec {
363 id: "partially_ordered_knapsack",
364 instance: Box::new(PartiallyOrderedKnapsack::new(
365 vec![2, 3, 4, 1, 2, 3],
366 vec![3, 2, 5, 4, 3, 8],
367 vec![(0, 2), (0, 3), (1, 4), (3, 5), (4, 5)],
368 11,
369 )),
370 optimal_config: serde_json::json!(vec![true, true, false, true, true, true]),
371 optimal_value: serde_json::json!(20),
372 }]
373}
374
375#[cfg(test)]
376#[path = "../../unit_tests/models/misc/partially_ordered_knapsack.rs"]
377mod tests;