problemreductions/models/misc/
partially_ordered_knapsack.rs1use crate::registry::{FieldInfo, 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 module_path: module_path!(),
19 description: "Select items to maximize total value subject to precedence constraints and weight capacity",
20 fields: &[
21 FieldInfo { name: "weights", type_name: "Vec<i64>", description: "Item weights w(u) for each item" },
22 FieldInfo { name: "values", type_name: "Vec<i64>", description: "Item values v(u) for each item" },
23 FieldInfo { name: "precedences", type_name: "Vec<(usize, usize)>", description: "Precedence pairs (a, b) meaning a must be included before b" },
24 FieldInfo { name: "capacity", type_name: "i64", description: "Knapsack capacity B" },
25 ],
26 }
27}
28
29#[derive(Serialize, Deserialize)]
61struct PartiallyOrderedKnapsackRaw {
62 weights: Vec<i64>,
63 values: Vec<i64>,
64 precedences: Vec<(usize, usize)>,
65 capacity: i64,
66}
67
68#[derive(Debug, Clone)]
69pub struct PartiallyOrderedKnapsack {
70 weights: Vec<i64>,
71 values: Vec<i64>,
72 precedences: Vec<(usize, usize)>,
73 capacity: i64,
74 predecessors: Vec<Vec<usize>>,
77}
78
79impl Serialize for PartiallyOrderedKnapsack {
80 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
81 PartiallyOrderedKnapsackRaw {
82 weights: self.weights.clone(),
83 values: self.values.clone(),
84 precedences: self.precedences.clone(),
85 capacity: self.capacity,
86 }
87 .serialize(serializer)
88 }
89}
90
91impl<'de> Deserialize<'de> for PartiallyOrderedKnapsack {
92 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
93 let raw = PartiallyOrderedKnapsackRaw::deserialize(deserializer)?;
94 Ok(Self::new(
95 raw.weights,
96 raw.values,
97 raw.precedences,
98 raw.capacity,
99 ))
100 }
101}
102
103impl PartiallyOrderedKnapsack {
104 pub fn new(
117 weights: Vec<i64>,
118 values: Vec<i64>,
119 precedences: Vec<(usize, usize)>,
120 capacity: i64,
121 ) -> Self {
122 assert_eq!(
123 weights.len(),
124 values.len(),
125 "weights and values must have the same length"
126 );
127 assert!(capacity >= 0, "capacity must be non-negative");
128 for (i, &w) in weights.iter().enumerate() {
129 assert!(w >= 0, "weight[{i}] must be non-negative, got {w}");
130 }
131 for (i, &v) in values.iter().enumerate() {
132 assert!(v >= 0, "value[{i}] must be non-negative, got {v}");
133 }
134 let n = weights.len();
135 for &(a, b) in &precedences {
136 assert!(a < n, "precedence index {a} out of bounds (n={n})");
137 assert!(b < n, "precedence index {b} out of bounds (n={n})");
138 }
139 let predecessors = Self::compute_predecessors(&precedences, n);
140 for (i, preds) in predecessors.iter().enumerate() {
142 assert!(
143 !preds.contains(&i),
144 "precedences contain a cycle involving item {i}"
145 );
146 }
147 Self {
148 weights,
149 values,
150 precedences,
151 capacity,
152 predecessors,
153 }
154 }
155
156 fn compute_predecessors(precedences: &[(usize, usize)], n: usize) -> Vec<Vec<usize>> {
158 let mut reachable = vec![vec![false; n]; n];
159 for &(a, b) in precedences {
160 reachable[a][b] = true;
161 }
162 for k in 0..n {
163 for i in 0..n {
164 for j in 0..n {
165 if reachable[i][k] && reachable[k][j] {
166 reachable[i][j] = true;
167 }
168 }
169 }
170 }
171 (0..n)
172 .map(|b| (0..n).filter(|&a| reachable[a][b]).collect())
173 .collect()
174 }
175
176 pub fn weights(&self) -> &[i64] {
178 &self.weights
179 }
180
181 pub fn values(&self) -> &[i64] {
183 &self.values
184 }
185
186 pub fn precedences(&self) -> &[(usize, usize)] {
188 &self.precedences
189 }
190
191 pub fn capacity(&self) -> i64 {
193 self.capacity
194 }
195
196 pub fn num_items(&self) -> usize {
198 self.weights.len()
199 }
200
201 pub fn num_precedences(&self) -> usize {
203 self.precedences.len()
204 }
205
206 fn is_downward_closed(&self, config: &[usize]) -> bool {
211 for (b, preds) in self.predecessors.iter().enumerate() {
212 if config[b] == 1 {
213 for &a in preds {
214 if config[a] != 1 {
215 return false;
216 }
217 }
218 }
219 }
220 true
221 }
222}
223
224impl Problem for PartiallyOrderedKnapsack {
225 const NAME: &'static str = "PartiallyOrderedKnapsack";
226 type Value = Max<i64>;
227
228 fn variant() -> Vec<(&'static str, &'static str)> {
229 crate::variant_params![]
230 }
231
232 fn dims(&self) -> Vec<usize> {
233 vec![2; self.num_items()]
234 }
235
236 fn evaluate(&self, config: &[usize]) -> Max<i64> {
237 if config.len() != self.num_items() {
238 return Max(None);
239 }
240 if config.iter().any(|&v| v >= 2) {
241 return Max(None);
242 }
243 if !self.is_downward_closed(config) {
245 return Max(None);
246 }
247 let total_weight: i64 = config
249 .iter()
250 .enumerate()
251 .filter(|(_, &x)| x == 1)
252 .map(|(i, _)| self.weights[i])
253 .sum();
254 if total_weight > self.capacity {
255 return Max(None);
256 }
257 let total_value: i64 = config
259 .iter()
260 .enumerate()
261 .filter(|(_, &x)| x == 1)
262 .map(|(i, _)| self.values[i])
263 .sum();
264 Max(Some(total_value))
265 }
266}
267
268crate::declare_variants! {
269 default PartiallyOrderedKnapsack => "2^num_items",
270}
271
272#[cfg(feature = "example-db")]
273pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
274 vec![crate::example_db::specs::ModelExampleSpec {
275 id: "partially_ordered_knapsack",
276 instance: Box::new(PartiallyOrderedKnapsack::new(
277 vec![2, 3, 4, 1, 2, 3],
278 vec![3, 2, 5, 4, 3, 8],
279 vec![(0, 2), (0, 3), (1, 4), (3, 5), (4, 5)],
280 11,
281 )),
282 optimal_config: vec![1, 1, 0, 1, 1, 1],
283 optimal_value: serde_json::json!(20),
284 }]
285}
286
287#[cfg(test)]
288#[path = "../../unit_tests/models/misc/partially_ordered_knapsack.rs"]
289mod tests;