problemreductions/models/misc/
numerical_matching_with_target_sums.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
10use crate::traits::Problem;
11use crate::types::Or;
12use serde::de::Error as _;
13use serde::{Deserialize, Deserializer, Serialize};
14
15inventory::submit! {
16 ProblemSchemaEntry {
17 name: "NumericalMatchingWithTargetSums",
18 display_name: "Numerical Matching with Target Sums",
19 aliases: &["NMTS"],
20 dimensions: &[],
21 category: crate::registry::ProblemCategory::Misc,
22 module_path: module_path!(),
23 description: "Partition X∪Y into m pairs (one from X, one from Y) with pair sums matching targets",
24 fields: &[
25 FieldInfo { name: "sizes_x", type_name: "Vec<i64>", description: "Integer sizes for each element of X" },
26 FieldInfo { name: "sizes_y", type_name: "Vec<i64>", description: "Integer sizes for each element of Y" },
27 FieldInfo { name: "targets", type_name: "Vec<i64>", description: "Target sums for each pair" },
28 ],
29 }
30}
31
32#[derive(Debug, Clone, Serialize)]
33pub struct NumericalMatchingWithTargetSums {
34 sizes_x: Vec<i64>,
35 sizes_y: Vec<i64>,
36 targets: Vec<i64>,
37}
38
39impl NumericalMatchingWithTargetSums {
40 fn validate_inputs(
41 sizes_x: &[i64],
42 sizes_y: &[i64],
43 targets: &[i64],
44 ) -> Result<(), crate::registry::ConstructionError> {
45 let m = sizes_x.len();
46 if m == 0 {
47 return Err(
48 "NumericalMatchingWithTargetSums requires at least one element per set".into(),
49 );
50 }
51 if sizes_y.len() != m {
52 return Err(
53 "NumericalMatchingWithTargetSums requires sizes_x and sizes_y to have the same length"
54 .into(),
55 );
56 }
57 if targets.len() != m {
58 return Err(
59 "NumericalMatchingWithTargetSums requires targets to have the same length as sizes_x"
60 .into(),
61 );
62 }
63 Ok(())
64 }
65
66 pub fn try_new(
67 sizes_x: Vec<i64>,
68 sizes_y: Vec<i64>,
69 targets: Vec<i64>,
70 ) -> Result<Self, crate::registry::ConstructionError> {
71 Self::validate_inputs(&sizes_x, &sizes_y, &targets)?;
72 Ok(Self {
73 sizes_x,
74 sizes_y,
75 targets,
76 })
77 }
78
79 pub fn new(sizes_x: Vec<i64>, sizes_y: Vec<i64>, targets: Vec<i64>) -> Self {
85 Self::try_new(sizes_x, sizes_y, targets).unwrap_or_else(|message| panic!("{message}"))
86 }
87
88 pub fn num_pairs(&self) -> usize {
90 self.sizes_x.len()
91 }
92
93 pub fn sizes_x(&self) -> &[i64] {
95 &self.sizes_x
96 }
97
98 pub fn sizes_y(&self) -> &[i64] {
100 &self.sizes_y
101 }
102
103 pub fn targets(&self) -> &[i64] {
105 &self.targets
106 }
107}
108
109#[derive(Deserialize)]
110struct NumericalMatchingWithTargetSumsData {
111 sizes_x: Vec<i64>,
112 sizes_y: Vec<i64>,
113 targets: Vec<i64>,
114}
115
116impl<'de> Deserialize<'de> for NumericalMatchingWithTargetSums {
117 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
118 where
119 D: Deserializer<'de>,
120 {
121 let data = NumericalMatchingWithTargetSumsData::deserialize(deserializer)?;
122 Self::try_new(data.sizes_x, data.sizes_y, data.targets).map_err(D::Error::custom)
123 }
124}
125
126impl Problem for NumericalMatchingWithTargetSums {
127 const NAME: &'static str = "NumericalMatchingWithTargetSums";
128 type Solution = Vec<usize>;
129 type Value = Or;
130
131 crate::problem_parameters![("num_pairs", num_pairs),];
132
133 fn variant() -> Vec<(&'static str, &'static str)> {
134 crate::variant_params![]
135 }
136
137 fn evaluate(&self, config: &Self::Solution) -> Result<Or, crate::traits::EvaluationError> {
138 Ok({
139 Or({
140 let m = self.num_pairs();
141 if config.len() != m {
142 return Err(crate::traits::EvaluationError::InvalidConfiguration(
143 "matching permutation length does not match the instance".into(),
144 ));
145 }
146
147 if config.iter().any(|&index| index >= m) {
148 return Err(crate::traits::EvaluationError::InvalidConfiguration(
149 "matching permutation contains an out-of-range index".into(),
150 ));
151 }
152
153 let mut used = vec![false; m];
155 for &idx in config {
156 if idx >= m || used[idx] {
157 return Ok(Or(false));
158 }
159 used[idx] = true;
160 }
161
162 let mut pair_sums: Vec<i64> = (0..m)
164 .map(|i| self.sizes_x[i] + self.sizes_y[config[i]])
165 .collect();
166 let mut sorted_targets = self.targets.clone();
167 pair_sums.sort();
168 sorted_targets.sort();
169 pair_sums == sorted_targets
170 })
171 })
172 }
173}
174
175impl crate::solvers::BruteForceProblem for NumericalMatchingWithTargetSums {
176 fn dimensions(&self) -> Vec<usize> {
177 let m = self.num_pairs();
178 vec![m; m]
179 }
180}
181
182crate::declare_variants! {
183 default NumericalMatchingWithTargetSums => "2^num_pairs",
184}
185
186crate::register_brute_force! {
187 NumericalMatchingWithTargetSums,
188}
189
190#[cfg(feature = "example-db")]
191pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
192 vec![crate::example_db::specs::ModelExampleSpec {
193 id: "numerical_matching_with_target_sums",
194 instance: Box::new(NumericalMatchingWithTargetSums::new(
195 vec![1, 4, 7],
196 vec![2, 5, 3],
197 vec![3, 7, 12],
198 )),
199 optimal_config: serde_json::json!(vec![0, 2, 1]),
200 optimal_value: serde_json::json!(true),
201 }]
202}
203
204#[cfg(test)]
205#[path = "../../unit_tests/models/misc/numerical_matching_with_target_sums.rs"]
206mod tests;