problemreductions/models/misc/
partition.rs1use crate::registry::{ConstructionError, FieldInfo, ProblemSchemaEntry};
8use crate::traits::Problem;
9use serde::{Deserialize, Serialize};
10
11inventory::submit! {
12 ProblemSchemaEntry {
13 name: "Partition",
14 display_name: "Partition",
15 aliases: &[],
16 dimensions: &[],
17 category: crate::registry::ProblemCategory::Misc,
18 module_path: module_path!(),
19 description: "Determine whether a multiset of positive integers can be partitioned into two subsets of equal sum",
20 fields: &[
21 FieldInfo { name: "sizes", type_name: "Vec<i64>", description: "Positive integer size for each element" },
22 ],
23 }
24}
25
26#[derive(Debug, Clone, Serialize)]
49pub struct Partition {
50 sizes: Vec<i64>,
51}
52
53impl Partition {
54 pub fn new(sizes: Vec<i64>) -> Result<Self, ConstructionError> {
57 if sizes.is_empty() {
58 return Err(ConstructionError::Conversion(
59 "Partition requires at least one element".into(),
60 ));
61 }
62 if sizes.iter().any(|&size| size <= 0) {
63 return Err(ConstructionError::Conversion(
64 "all Partition sizes must be positive".into(),
65 ));
66 }
67 sizes
68 .iter()
69 .try_fold(0i64, |sum, &size| sum.checked_add(size))
70 .ok_or_else(|| ConstructionError::IntegerOverflow("summing Partition sizes".into()))?;
71 Ok(Self { sizes })
72 }
73
74 pub fn sizes(&self) -> &[i64] {
76 &self.sizes
77 }
78
79 pub fn num_elements(&self) -> usize {
81 self.sizes.len()
82 }
83
84 pub fn max_numeric_magnitude_bits(&self) -> u64 {
86 crate::types::max_numeric_magnitude_bits(self.sizes.iter().copied())
87 }
88
89 pub fn total_sum(&self) -> i64 {
91 self.sizes.iter().sum()
92 }
93}
94
95impl<'de> Deserialize<'de> for Partition {
96 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
97 where
98 D: serde::Deserializer<'de>,
99 {
100 #[derive(Deserialize)]
101 struct Raw {
102 sizes: Vec<i64>,
103 }
104
105 let raw = Raw::deserialize(deserializer)?;
106 Self::new(raw.sizes).map_err(serde::de::Error::custom)
107 }
108}
109
110impl Problem for Partition {
111 const NAME: &'static str = "Partition";
112 type Solution = Vec<bool>;
113 type Value = crate::types::Or;
114
115 crate::problem_parameters![
116 ("max_numeric_magnitude_bits", max_numeric_magnitude_bits),
117 ("num_elements", num_elements),
118 ("total_sum", total_sum),
119 ];
120
121 fn variant() -> Vec<(&'static str, &'static str)> {
122 crate::variant_params![]
123 }
124
125 fn evaluate(
126 &self,
127 config: &Self::Solution,
128 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
129 Ok({
130 crate::types::Or({
131 if config.len() != self.num_elements() {
132 return Err(crate::traits::EvaluationError::InvalidConfiguration(
133 "partition selection length does not match the elements".into(),
134 ));
135 }
136 let selected_sum: i64 = config
137 .iter()
138 .enumerate()
139 .filter(|(_, &x)| x)
140 .map(|(i, _)| self.sizes[i])
141 .sum();
142 selected_sum == self.total_sum() - selected_sum
143 })
144 })
145 }
146}
147
148impl crate::solvers::BruteForceProblem for Partition {
149 fn dimensions(&self) -> Vec<usize> {
150 vec![2; self.num_elements()]
151 }
152}
153
154crate::declare_variants! {
155 default Partition => "2^(num_elements / 2)",
156}
157
158crate::register_brute_force! {
159 Partition decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
160}
161
162#[cfg(feature = "example-db")]
163pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
164 vec![crate::example_db::specs::ModelExampleSpec {
165 id: "partition",
166 instance: Box::new(Partition::new(vec![3, 1, 1, 2, 2, 1]).unwrap()),
167 optimal_config: serde_json::json!(vec![true, false, false, true, false, false]),
168 optimal_value: serde_json::json!(true),
169 }]
170}
171
172#[cfg(test)]
173#[path = "../../unit_tests/models/misc/partition.rs"]
174mod tests;