problemreductions/models/misc/
knapsack.rs1use crate::registry::{CreateSpec, ProblemSchemaEntry};
7use crate::traits::Problem;
8use crate::types::Max;
9use serde::{Deserialize, Serialize};
10
11inventory::submit! {
12 ProblemSchemaEntry {
13 name: "Knapsack",
14 display_name: "Knapsack",
15 aliases: &[],
16 dimensions: &[],
17 category: crate::registry::ProblemCategory::Misc,
18 module_path: module_path!(),
19 description: "Select items to maximize total value subject to weight capacity constraint",
20 fields: KnapsackCreateSpec::FIELDS,
21 }
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct Knapsack {
48 #[serde(deserialize_with = "nonnegative_i64_vec::deserialize")]
49 weights: Vec<i64>,
50 #[serde(deserialize_with = "nonnegative_i64_vec::deserialize")]
51 values: Vec<i64>,
52 #[serde(deserialize_with = "nonnegative_i64::deserialize")]
53 capacity: i64,
54}
55
56#[derive(Debug, Deserialize, crate::CreateSpec)]
57struct KnapsackCreateSpec {
58 weights: Option<Vec<i64>>,
60 values: Vec<i64>,
62 capacity: i64,
64}
65impl TryFrom<KnapsackCreateSpec> for Knapsack {
66 type Error = crate::registry::ConstructionError;
67 fn try_from(spec: KnapsackCreateSpec) -> Result<Self, Self::Error> {
68 let count = spec.values.len();
69 let weights = spec.weights.unwrap_or_else(|| vec![1; count]);
70 if weights.len() != count {
71 return Err("weights length must equal values length".to_string().into());
72 }
73 if weights.iter().any(|&value| value < 0)
74 || spec.values.iter().any(|&value| value < 0)
75 || spec.capacity < 0
76 {
77 return Err("weights, values, and capacity must be nonnegative"
78 .to_string()
79 .into());
80 }
81 Ok(Self::new(weights, spec.values, spec.capacity))
82 }
83}
84
85impl Knapsack {
86 pub fn new(weights: Vec<i64>, values: Vec<i64>, capacity: i64) -> Self {
92 assert_eq!(
93 weights.len(),
94 values.len(),
95 "weights and values must have the same length"
96 );
97 assert!(
98 weights.iter().all(|&weight| weight >= 0),
99 "Knapsack weights must be nonnegative"
100 );
101 assert!(
102 values.iter().all(|&value| value >= 0),
103 "Knapsack values must be nonnegative"
104 );
105 assert!(capacity >= 0, "Knapsack capacity must be nonnegative");
106 Self {
107 weights,
108 values,
109 capacity,
110 }
111 }
112
113 pub fn weights(&self) -> &[i64] {
115 &self.weights
116 }
117
118 pub fn values(&self) -> &[i64] {
120 &self.values
121 }
122
123 pub fn capacity(&self) -> i64 {
125 self.capacity
126 }
127
128 pub fn num_items(&self) -> usize {
130 self.weights.len()
131 }
132
133 pub fn num_slack_bits(&self) -> usize {
138 if self.capacity == 0 {
139 1
140 } else {
141 self.capacity.ilog2() as usize + 1
142 }
143 }
144}
145
146impl Problem for Knapsack {
147 const NAME: &'static str = "Knapsack";
148 type Solution = Vec<bool>;
149 type Value = Max<i64>;
150
151 crate::problem_parameters![("capacity", capacity), ("num_items", num_items),];
152
153 fn variant() -> Vec<(&'static str, &'static str)> {
154 crate::variant_params![]
155 }
156
157 fn evaluate(
158 &self,
159 config: &Self::Solution,
160 ) -> Result<Max<i64>, crate::traits::EvaluationError> {
161 Ok({
162 if config.len() != self.num_items() {
163 return Err(crate::traits::EvaluationError::InvalidConfiguration(
164 "item-selection length does not match the instance".into(),
165 ));
166 }
167 let total_weight = config
168 .iter()
169 .enumerate()
170 .filter(|(_, &x)| x)
171 .map(|(i, _)| self.weights[i])
172 .try_fold(0_i64, |total, weight| {
173 total.checked_add(weight).ok_or_else(|| {
174 crate::traits::EvaluationError::IntegerOverflow(
175 "summing selected knapsack weights".into(),
176 )
177 })
178 })?;
179 if total_weight > self.capacity {
180 return Ok(Max(None));
181 }
182 let total_value = config
183 .iter()
184 .enumerate()
185 .filter(|(_, &x)| x)
186 .map(|(i, _)| self.values[i])
187 .try_fold(0_i64, |total, value| {
188 total.checked_add(value).ok_or_else(|| {
189 crate::traits::EvaluationError::IntegerOverflow(
190 "summing selected knapsack values".into(),
191 )
192 })
193 })?;
194 Max(Some(total_value))
195 })
196 }
197}
198
199impl crate::solvers::BruteForceProblem for Knapsack {
200 fn dimensions(&self) -> Vec<usize> {
201 vec![2; self.num_items()]
202 }
203}
204
205crate::declare_variants! {
206 default Knapsack => "2^(num_items / 2)" create KnapsackCreateSpec,
207}
208
209crate::register_brute_force! {
210 Knapsack decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
211}
212
213mod nonnegative_i64 {
214 use serde::de::Error;
215 use serde::{Deserialize, Deserializer};
216
217 pub fn deserialize<'de, D>(deserializer: D) -> Result<i64, D::Error>
218 where
219 D: Deserializer<'de>,
220 {
221 let value = i64::deserialize(deserializer)?;
222 if value < 0 {
223 return Err(D::Error::custom(format!(
224 "expected nonnegative integer, got {value}"
225 )));
226 }
227 Ok(value)
228 }
229}
230
231mod nonnegative_i64_vec {
232 use serde::de::Error;
233 use serde::{Deserialize, Deserializer};
234
235 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<i64>, D::Error>
236 where
237 D: Deserializer<'de>,
238 {
239 let values = Vec::<i64>::deserialize(deserializer)?;
240 if let Some(value) = values.iter().copied().find(|value| *value < 0) {
241 return Err(D::Error::custom(format!(
242 "expected nonnegative integers, got {value}"
243 )));
244 }
245 Ok(values)
246 }
247}
248
249#[cfg(feature = "example-db")]
250pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
251 vec![crate::example_db::specs::ModelExampleSpec {
254 id: "knapsack",
255 instance: Box::new(Knapsack::new(vec![2, 3, 4, 5], vec![3, 4, 5, 7], 7)),
256 optimal_config: serde_json::json!(vec![true, false, false, true]),
257 optimal_value: serde_json::json!(10),
258 }]
259}
260
261#[cfg(test)]
262#[path = "../../unit_tests/models/misc/knapsack.rs"]
263mod tests;