problemreductions/models/misc/
subset_product.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
11use crate::traits::Problem;
12use num_bigint::{BigUint, ToBigUint};
13use num_traits::{One, Zero};
14use serde::{Deserialize, Serialize};
15
16inventory::submit! {
17 ProblemSchemaEntry {
18 name: "SubsetProduct",
19 display_name: "Subset Product",
20 aliases: &[],
21 dimensions: &[],
22 category: crate::registry::ProblemCategory::Misc,
23 module_path: module_path!(),
24 description: "Find a subset of positive integers whose product equals exactly a target value",
25 fields: &[
26 FieldInfo { name: "sizes", type_name: "Vec<BigUint>", description: "Positive integer sizes s(a) for each element" },
27 FieldInfo { name: "target", type_name: "BigUint", description: "Target product B" },
28 ],
29 }
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
54#[serde(try_from = "SubsetProductData")]
55pub struct SubsetProduct {
56 #[serde(with = "super::biguint_serde::decimal_biguint_vec")]
57 sizes: Vec<BigUint>,
58 #[serde(with = "super::biguint_serde::decimal_biguint")]
59 target: BigUint,
60}
61
62#[derive(Deserialize)]
63struct SubsetProductData {
64 #[serde(with = "super::biguint_serde::decimal_biguint_vec")]
65 sizes: Vec<BigUint>,
66 #[serde(with = "super::biguint_serde::decimal_biguint")]
67 target: BigUint,
68}
69
70impl TryFrom<SubsetProductData> for SubsetProduct {
71 type Error = crate::registry::ConstructionError;
72 fn try_from(data: SubsetProductData) -> Result<Self, Self::Error> {
73 if data.sizes.iter().any(BigUint::is_zero) {
74 return Err("all sizes must be positive (> 0)".into());
75 }
76 if data.target.is_zero() {
77 return Err("SubsetProduct target must be positive".into());
78 }
79 Ok(Self {
80 sizes: data.sizes,
81 target: data.target,
82 })
83 }
84}
85
86impl SubsetProduct {
87 pub fn new<S, T>(sizes: Vec<S>, target: T) -> Self
93 where
94 S: ToBigUint,
95 T: ToBigUint,
96 {
97 Self::try_new(sizes, target).unwrap_or_else(|error| panic!("{error}"))
98 }
99
100 fn try_new<S, T>(sizes: Vec<S>, target: T) -> Result<Self, crate::registry::ConstructionError>
101 where
102 S: ToBigUint,
103 T: ToBigUint,
104 {
105 let sizes = sizes
106 .into_iter()
107 .map(|size| size.to_biguint().ok_or("all sizes must be positive (> 0)"))
108 .collect::<Result<Vec<_>, _>>()?;
109 let target = target
110 .to_biguint()
111 .ok_or("SubsetProduct target must be nonnegative")?;
112 SubsetProductData { sizes, target }.try_into()
113 }
114
115 #[cfg(test)]
117 pub(crate) fn new_unchecked(sizes: Vec<BigUint>, target: BigUint) -> Self {
118 Self { sizes, target }
119 }
120
121 pub fn sizes(&self) -> &[BigUint] {
123 &self.sizes
124 }
125
126 pub fn target(&self) -> &BigUint {
128 &self.target
129 }
130
131 pub fn num_elements(&self) -> usize {
133 self.sizes.len()
134 }
135}
136
137impl Problem for SubsetProduct {
138 const NAME: &'static str = "SubsetProduct";
139 type Solution = Vec<bool>;
140 type Value = crate::types::Or;
141
142 crate::problem_parameters![("num_elements", num_elements),];
143
144 fn variant() -> Vec<(&'static str, &'static str)> {
145 crate::variant_params![]
146 }
147
148 fn evaluate(
149 &self,
150 config: &Self::Solution,
151 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
152 Ok({
153 crate::types::Or({
154 if config.len() != self.num_elements() {
155 return Err(crate::traits::EvaluationError::InvalidConfiguration(
156 "subset-selection length does not match the elements".into(),
157 ));
158 }
159 let mut product = BigUint::one();
160 for (i, &x) in config.iter().enumerate() {
161 if x {
162 product *= &self.sizes[i];
163 if product > self.target {
164 return Ok(crate::types::Or(false));
165 }
166 }
167 }
168 product == self.target
169 })
170 })
171 }
172}
173
174impl crate::solvers::BruteForceProblem for SubsetProduct {
175 fn dimensions(&self) -> Vec<usize> {
176 vec![2; self.num_elements()]
177 }
178}
179
180crate::declare_variants! {
181 default SubsetProduct => "2^num_elements",
182}
183
184crate::register_brute_force! {
185 SubsetProduct decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
186}
187
188#[cfg(feature = "example-db")]
189pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
190 vec![crate::example_db::specs::ModelExampleSpec {
192 id: "subset_product",
193 instance: Box::new(SubsetProduct::new(vec![2u32, 3, 5, 7, 6, 10], 210u32)),
194 optimal_config: serde_json::json!(vec![true, true, true, true, false, false]),
195 optimal_value: serde_json::json!(true),
196 }]
197}
198
199#[cfg(test)]
200#[path = "../../unit_tests/models/misc/subset_product.rs"]
201mod tests;