problemreductions/models/set/
integer_knapsack.rs1use crate::registry::ConstructionError;
7use crate::registry::{FieldInfo, ProblemSchemaEntry};
8use crate::solvers::BruteForceProblem as _;
9use crate::traits::Problem;
10use crate::types::Max;
11use serde::{Deserialize, Serialize};
12
13inventory::submit! {
14 ProblemSchemaEntry {
15 name: "IntegerKnapsack",
16 display_name: "Integer Knapsack",
17 aliases: &[],
18 dimensions: &[],
19 category: crate::registry::ProblemCategory::Set,
20 module_path: module_path!(),
21 description: "Select items with integer multiplicities to maximize total value subject to capacity constraint",
22 fields: &[
23 FieldInfo { name: "sizes", type_name: "Vec<i64>", description: "Positive item sizes s(u)" },
24 FieldInfo { name: "values", type_name: "Vec<i64>", description: "Positive item values v(u)" },
25 FieldInfo { name: "capacity", type_name: "i64", description: "Nonnegative knapsack capacity B" },
26 ],
27 }
28}
29
30#[derive(Debug, Clone, Serialize)]
54#[serde(into = "RawIntegerKnapsack")]
55pub struct IntegerKnapsack {
56 sizes: Vec<i64>,
57 values: Vec<i64>,
58 capacity: i64,
59}
60
61impl IntegerKnapsack {
62 pub fn new(
65 sizes: Vec<i64>,
66 values: Vec<i64>,
67 capacity: i64,
68 ) -> Result<Self, ConstructionError> {
69 Self::try_from(RawIntegerKnapsack {
70 sizes,
71 values,
72 capacity,
73 })
74 }
75
76 pub fn sizes(&self) -> &[i64] {
78 &self.sizes
79 }
80
81 pub fn values(&self) -> &[i64] {
83 &self.values
84 }
85
86 pub fn capacity(&self) -> i64 {
88 self.capacity
89 }
90
91 pub fn num_items(&self) -> usize {
93 self.sizes.len()
94 }
95}
96
97impl Problem for IntegerKnapsack {
98 const NAME: &'static str = "IntegerKnapsack";
99 type Solution = Vec<usize>;
100 type Value = Max<i64>;
101
102 crate::problem_parameters![("capacity", capacity), ("num_items", num_items),];
103
104 fn variant() -> Vec<(&'static str, &'static str)> {
105 crate::variant_params![]
106 }
107
108 fn evaluate(
109 &self,
110 config: &Self::Solution,
111 ) -> Result<Max<i64>, crate::traits::EvaluationError> {
112 Ok({
113 if config.len() != self.num_items() {
114 return Err(crate::traits::EvaluationError::InvalidConfiguration(
115 "multiplicity-vector length does not match the items".into(),
116 ));
117 }
118 let dims = self.dimensions();
119 if config
120 .iter()
121 .zip(&dims)
122 .any(|(&count, &dimension)| count >= dimension)
123 {
124 return Err(crate::traits::EvaluationError::InvalidConfiguration(
125 "multiplicity vector contains an out-of-range count".into(),
126 ));
127 }
128 let total_size = config
129 .iter()
130 .enumerate()
131 .try_fold(0_i64, |total, (i, &c)| {
132 let count = i64::try_from(c).map_err(|_| {
133 crate::traits::EvaluationError::IntegerOverflow(
134 "converting knapsack item count to i64".into(),
135 )
136 })?;
137 let contribution = count.checked_mul(self.sizes[i]).ok_or_else(|| {
138 crate::traits::EvaluationError::IntegerOverflow(
139 "multiplying knapsack item count by size".into(),
140 )
141 })?;
142 total.checked_add(contribution).ok_or_else(|| {
143 crate::traits::EvaluationError::IntegerOverflow(
144 "summing knapsack item sizes".into(),
145 )
146 })
147 })?;
148 if total_size > self.capacity {
149 return Ok(Max(None));
150 }
151 let total_value = config
152 .iter()
153 .enumerate()
154 .try_fold(0_i64, |total, (i, &c)| {
155 let count = i64::try_from(c).map_err(|_| {
156 crate::traits::EvaluationError::IntegerOverflow(
157 "converting knapsack item count to i64".into(),
158 )
159 })?;
160 let contribution = count.checked_mul(self.values[i]).ok_or_else(|| {
161 crate::traits::EvaluationError::IntegerOverflow(
162 "multiplying knapsack item count by value".into(),
163 )
164 })?;
165 total.checked_add(contribution).ok_or_else(|| {
166 crate::traits::EvaluationError::IntegerOverflow(
167 "summing knapsack item values".into(),
168 )
169 })
170 })?;
171 Max(Some(total_value))
172 })
173 }
174}
175
176impl crate::solvers::BruteForceProblem for IntegerKnapsack {
177 fn dimensions(&self) -> Vec<usize> {
178 self.sizes
179 .iter()
180 .map(|&s| {
181 let dimension = i128::from(self.capacity) / i128::from(s) + 1;
182 usize::try_from(dimension)
183 .expect("validated integer-knapsack dimension must fit usize")
184 })
185 .collect()
186 }
187}
188
189crate::declare_variants! {
190 default IntegerKnapsack => "(capacity + 1)^num_items",
191}
192
193crate::register_brute_force! {
194 IntegerKnapsack,
195}
196
197#[derive(Deserialize, Serialize)]
199struct RawIntegerKnapsack {
200 sizes: Vec<i64>,
201 values: Vec<i64>,
202 capacity: i64,
203}
204
205impl From<IntegerKnapsack> for RawIntegerKnapsack {
206 fn from(ik: IntegerKnapsack) -> Self {
207 RawIntegerKnapsack {
208 sizes: ik.sizes,
209 values: ik.values,
210 capacity: ik.capacity,
211 }
212 }
213}
214
215impl TryFrom<RawIntegerKnapsack> for IntegerKnapsack {
216 type Error = ConstructionError;
217
218 fn try_from(raw: RawIntegerKnapsack) -> Result<Self, Self::Error> {
219 if raw.sizes.len() != raw.values.len() {
220 return Err(ConstructionError::Conversion(format!(
221 "sizes and values must have the same length, got {} and {}",
222 raw.sizes.len(),
223 raw.values.len()
224 )));
225 }
226 if let Some(&s) = raw.sizes.iter().find(|&&s| s <= 0) {
227 return Err(ConstructionError::Conversion(format!(
228 "expected positive sizes, got {s}"
229 )));
230 }
231 if let Some(&v) = raw.values.iter().find(|&&v| v <= 0) {
232 return Err(ConstructionError::Conversion(format!(
233 "expected positive values, got {v}"
234 )));
235 }
236 if raw.capacity < 0 {
237 return Err(ConstructionError::Conversion(format!(
238 "expected nonnegative capacity, got {}",
239 raw.capacity
240 )));
241 }
242 for &size in &raw.sizes {
243 let dimension = i128::from(raw.capacity) / i128::from(size) + 1;
244 usize::try_from(dimension).map_err(|_| {
245 ConstructionError::IntegerOverflow(format!(
246 "knapsack dimension for capacity {} and item size {size} does not fit usize",
247 raw.capacity
248 ))
249 })?;
250 }
251 Ok(IntegerKnapsack {
252 sizes: raw.sizes,
253 values: raw.values,
254 capacity: raw.capacity,
255 })
256 }
257}
258
259impl<'de> Deserialize<'de> for IntegerKnapsack {
260 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
261 where
262 D: serde::Deserializer<'de>,
263 {
264 let raw = RawIntegerKnapsack::deserialize(deserializer)?;
265 IntegerKnapsack::try_from(raw).map_err(serde::de::Error::custom)
266 }
267}
268
269#[cfg(feature = "example-db")]
270pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
271 vec![crate::example_db::specs::ModelExampleSpec {
274 id: "integer-knapsack",
275 instance: Box::new(
276 IntegerKnapsack::new(vec![3, 4, 5, 2, 7], vec![4, 5, 7, 3, 9], 15).unwrap(),
277 ),
278 optimal_config: serde_json::json!(vec![0, 0, 1, 5, 0]),
279 optimal_value: serde_json::json!(22),
280 }]
281}
282
283#[cfg(test)]
284#[path = "../../unit_tests/models/set/integer_knapsack.rs"]
285mod tests;