problemreductions/models/misc/
numerical_matching_with_target_sums.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry, ProblemSizeFieldEntry};
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 module_path: module_path!(),
22 description: "Partition X∪Y into m pairs (one from X, one from Y) with pair sums matching targets",
23 fields: &[
24 FieldInfo { name: "sizes_x", type_name: "Vec<i64>", description: "Integer sizes for each element of X" },
25 FieldInfo { name: "sizes_y", type_name: "Vec<i64>", description: "Integer sizes for each element of Y" },
26 FieldInfo { name: "targets", type_name: "Vec<i64>", description: "Target sums for each pair" },
27 ],
28 }
29}
30
31inventory::submit! {
32 ProblemSizeFieldEntry {
33 name: "NumericalMatchingWithTargetSums",
34 fields: &["num_pairs"],
35 }
36}
37
38#[derive(Debug, Clone, Serialize)]
39pub struct NumericalMatchingWithTargetSums {
40 sizes_x: Vec<i64>,
41 sizes_y: Vec<i64>,
42 targets: Vec<i64>,
43}
44
45impl NumericalMatchingWithTargetSums {
46 fn validate_inputs(sizes_x: &[i64], sizes_y: &[i64], targets: &[i64]) -> Result<(), String> {
47 let m = sizes_x.len();
48 if m == 0 {
49 return Err(
50 "NumericalMatchingWithTargetSums requires at least one element per set".to_string(),
51 );
52 }
53 if sizes_y.len() != m {
54 return Err(
55 "NumericalMatchingWithTargetSums requires sizes_x and sizes_y to have the same length"
56 .to_string(),
57 );
58 }
59 if targets.len() != m {
60 return Err(
61 "NumericalMatchingWithTargetSums requires targets to have the same length as sizes_x"
62 .to_string(),
63 );
64 }
65 Ok(())
66 }
67
68 pub fn try_new(
69 sizes_x: Vec<i64>,
70 sizes_y: Vec<i64>,
71 targets: Vec<i64>,
72 ) -> Result<Self, String> {
73 Self::validate_inputs(&sizes_x, &sizes_y, &targets)?;
74 Ok(Self {
75 sizes_x,
76 sizes_y,
77 targets,
78 })
79 }
80
81 pub fn new(sizes_x: Vec<i64>, sizes_y: Vec<i64>, targets: Vec<i64>) -> Self {
87 Self::try_new(sizes_x, sizes_y, targets).unwrap_or_else(|message| panic!("{message}"))
88 }
89
90 pub fn num_pairs(&self) -> usize {
92 self.sizes_x.len()
93 }
94
95 pub fn sizes_x(&self) -> &[i64] {
97 &self.sizes_x
98 }
99
100 pub fn sizes_y(&self) -> &[i64] {
102 &self.sizes_y
103 }
104
105 pub fn targets(&self) -> &[i64] {
107 &self.targets
108 }
109}
110
111#[derive(Deserialize)]
112struct NumericalMatchingWithTargetSumsData {
113 sizes_x: Vec<i64>,
114 sizes_y: Vec<i64>,
115 targets: Vec<i64>,
116}
117
118impl<'de> Deserialize<'de> for NumericalMatchingWithTargetSums {
119 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
120 where
121 D: Deserializer<'de>,
122 {
123 let data = NumericalMatchingWithTargetSumsData::deserialize(deserializer)?;
124 Self::try_new(data.sizes_x, data.sizes_y, data.targets).map_err(D::Error::custom)
125 }
126}
127
128impl Problem for NumericalMatchingWithTargetSums {
129 const NAME: &'static str = "NumericalMatchingWithTargetSums";
130 type Value = Or;
131
132 fn variant() -> Vec<(&'static str, &'static str)> {
133 crate::variant_params![]
134 }
135
136 fn dims(&self) -> Vec<usize> {
137 let m = self.num_pairs();
138 vec![m; m]
139 }
140
141 fn evaluate(&self, config: &[usize]) -> Or {
142 Or({
143 let m = self.num_pairs();
144 if config.len() != m {
145 return Or(false);
146 }
147
148 let mut used = vec![false; m];
150 for &idx in config {
151 if idx >= m || used[idx] {
152 return Or(false);
153 }
154 used[idx] = true;
155 }
156
157 let mut pair_sums: Vec<i64> = (0..m)
159 .map(|i| self.sizes_x[i] + self.sizes_y[config[i]])
160 .collect();
161 let mut sorted_targets = self.targets.clone();
162 pair_sums.sort();
163 sorted_targets.sort();
164 pair_sums == sorted_targets
165 })
166 }
167}
168
169crate::declare_variants! {
170 default NumericalMatchingWithTargetSums => "2^num_pairs",
171}
172
173#[cfg(feature = "example-db")]
174pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
175 vec![crate::example_db::specs::ModelExampleSpec {
176 id: "numerical_matching_with_target_sums",
177 instance: Box::new(NumericalMatchingWithTargetSums::new(
178 vec![1, 4, 7],
179 vec![2, 5, 3],
180 vec![3, 7, 12],
181 )),
182 optimal_config: vec![0, 2, 1],
183 optimal_value: serde_json::json!(true),
184 }]
185}
186
187#[cfg(test)]
188#[path = "../../unit_tests/models/misc/numerical_matching_with_target_sums.rs"]
189mod tests;