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)]
47#[serde(try_from = "KnapsackData")]
48pub struct Knapsack {
49 weights: Vec<i64>,
50 values: Vec<i64>,
51 capacity: i64,
52}
53
54#[derive(Deserialize)]
55struct KnapsackData {
56 weights: Vec<i64>,
57 values: Vec<i64>,
58 capacity: i64,
59}
60
61impl TryFrom<KnapsackData> for Knapsack {
62 type Error = crate::registry::ConstructionError;
63
64 fn try_from(data: KnapsackData) -> Result<Self, Self::Error> {
65 Self::try_new(data.weights, data.values, data.capacity)
66 }
67}
68
69#[derive(Debug, Deserialize, crate::CreateSpec)]
70struct KnapsackCreateSpec {
71 weights: Option<Vec<i64>>,
73 values: Vec<i64>,
75 capacity: i64,
77}
78impl TryFrom<KnapsackCreateSpec> for Knapsack {
79 type Error = crate::registry::ConstructionError;
80 fn try_from(spec: KnapsackCreateSpec) -> Result<Self, Self::Error> {
81 let weights = spec.weights.unwrap_or_else(|| vec![1; spec.values.len()]);
82 Self::try_new(weights, spec.values, spec.capacity)
83 }
84}
85
86impl Knapsack {
87 pub fn new(weights: Vec<i64>, values: Vec<i64>, capacity: i64) -> Self {
93 Self::try_new(weights, values, capacity).unwrap_or_else(|error| panic!("{error}"))
94 }
95
96 fn try_new(
97 weights: Vec<i64>,
98 values: Vec<i64>,
99 capacity: i64,
100 ) -> Result<Self, crate::registry::ConstructionError> {
101 if weights.len() != values.len() {
102 return Err("weights and values must have the same length".into());
103 }
104 if weights.iter().any(|&weight| weight < 0) {
105 return Err("Knapsack weights must be nonnegative".into());
106 }
107 if values.iter().any(|&value| value < 0) {
108 return Err("Knapsack values must be nonnegative".into());
109 }
110 if capacity < 0 {
111 return Err("Knapsack capacity must be nonnegative".into());
112 }
113 Ok(Self {
114 weights,
115 values,
116 capacity,
117 })
118 }
119
120 pub fn weights(&self) -> &[i64] {
122 &self.weights
123 }
124
125 pub fn values(&self) -> &[i64] {
127 &self.values
128 }
129
130 pub fn capacity(&self) -> i64 {
132 self.capacity
133 }
134
135 pub fn capacity_bits(&self) -> u64 {
137 crate::types::max_numeric_magnitude_bits([self.capacity])
138 }
139
140 pub fn num_items(&self) -> usize {
142 self.weights.len()
143 }
144
145 pub fn num_slack_bits(&self) -> usize {
150 usize::try_from(self.capacity_bits()).expect("capacity bit length fits usize")
151 }
152}
153
154impl Problem for Knapsack {
155 const NAME: &'static str = "Knapsack";
156 type Solution = Vec<bool>;
157 type Value = Max<i64>;
158
159 crate::problem_parameters![
160 ("capacity", capacity),
161 ("capacity_bits", capacity_bits),
162 ("num_items", num_items),
163 ];
164
165 fn variant() -> Vec<(&'static str, &'static str)> {
166 crate::variant_params![]
167 }
168
169 fn evaluate(
170 &self,
171 config: &Self::Solution,
172 ) -> Result<Max<i64>, crate::traits::EvaluationError> {
173 Ok({
174 if config.len() != self.num_items() {
175 return Err(crate::traits::EvaluationError::InvalidConfiguration(
176 "item-selection length does not match the instance".into(),
177 ));
178 }
179 let total_weight = config
180 .iter()
181 .enumerate()
182 .filter(|(_, &x)| x)
183 .map(|(i, _)| self.weights[i])
184 .try_fold(0_i64, |total, weight| {
185 total.checked_add(weight).ok_or_else(|| {
186 crate::traits::EvaluationError::IntegerOverflow(
187 "summing selected knapsack weights".into(),
188 )
189 })
190 })?;
191 if total_weight > self.capacity {
192 return Ok(Max(None));
193 }
194 let total_value = config
195 .iter()
196 .enumerate()
197 .filter(|(_, &x)| x)
198 .map(|(i, _)| self.values[i])
199 .try_fold(0_i64, |total, value| {
200 total.checked_add(value).ok_or_else(|| {
201 crate::traits::EvaluationError::IntegerOverflow(
202 "summing selected knapsack values".into(),
203 )
204 })
205 })?;
206 Max(Some(total_value))
207 })
208 }
209}
210
211impl crate::solvers::BruteForceProblem for Knapsack {
212 fn dimensions(&self) -> Vec<usize> {
213 vec![2; self.num_items()]
214 }
215}
216
217crate::declare_variants! {
218 default Knapsack => "2^(num_items / 2)" create KnapsackCreateSpec,
219}
220
221crate::register_brute_force! {
222 Knapsack decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
223}
224
225#[cfg(feature = "example-db")]
226pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
227 vec![crate::example_db::specs::ModelExampleSpec {
230 id: "knapsack",
231 instance: Box::new(Knapsack::new(vec![2, 3, 4, 5], vec![3, 4, 5, 7], 7)),
232 optimal_config: serde_json::json!(vec![true, false, false, true]),
233 optimal_value: serde_json::json!(10),
234 }]
235}
236
237#[cfg(test)]
238#[path = "../../unit_tests/models/misc/knapsack.rs"]
239mod tests;