problemreductions/models/algebraic/
simultaneous_incongruences.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry, ProblemSizeFieldEntry};
7use crate::traits::Problem;
8use crate::types::Or;
9use serde::de::Error as _;
10use serde::{Deserialize, Deserializer, Serialize};
11
12inventory::submit! {
13 ProblemSchemaEntry {
14 name: "SimultaneousIncongruences",
15 display_name: "Simultaneous Incongruences",
16 aliases: &[],
17 dimensions: &[],
18 module_path: module_path!(),
19 description: "Decide whether there exists x with x ≢ aᵢ (mod bᵢ) for all i",
20 fields: &[
21 FieldInfo {
22 name: "pairs",
23 type_name: "Vec<(u64, u64)>",
24 description: "Pairs (aᵢ, bᵢ) with bᵢ > 0 and 1 ≤ aᵢ ≤ bᵢ",
25 },
26 ],
27 }
28}
29
30inventory::submit! {
31 ProblemSizeFieldEntry {
32 name: "SimultaneousIncongruences",
33 fields: &["num_pairs"],
34 }
35}
36
37#[derive(Debug, Clone, Serialize)]
58pub struct SimultaneousIncongruences {
59 pairs: Vec<(u64, u64)>,
61}
62
63pub(crate) const MAX_LCM: u128 = 1_000_000;
66
67fn lcm128(a: u128, b: u128) -> u128 {
68 if a == 0 || b == 0 {
69 return 0;
70 }
71 let g = gcd128(a, b);
72 (a / g).saturating_mul(b).min(MAX_LCM)
74}
75
76fn gcd128(mut a: u128, mut b: u128) -> u128 {
77 while b != 0 {
78 let t = b;
79 b = a % b;
80 a = t;
81 }
82 a
83}
84
85impl SimultaneousIncongruences {
86 fn validate_inputs(pairs: &[(u64, u64)]) -> Result<(), String> {
87 for (i, &(a, b)) in pairs.iter().enumerate() {
88 if b == 0 {
89 return Err(format!("Modulus b at index {i} must be positive (got b=0)"));
90 }
91 if a == 0 {
92 return Err(format!(
93 "Residue a at index {i} must be at least 1 (got a=0)"
94 ));
95 }
96 if a > b {
97 return Err(format!(
98 "Residue a ({a}) must not exceed modulus b ({b}) at index {i}"
99 ));
100 }
101 }
102 Ok(())
103 }
104
105 pub fn new(pairs: Vec<(u64, u64)>) -> Result<Self, String> {
108 Self::validate_inputs(&pairs)?;
109 Ok(Self { pairs })
110 }
111
112 pub fn num_pairs(&self) -> usize {
114 self.pairs.len()
115 }
116
117 pub fn pairs(&self) -> &[(u64, u64)] {
119 &self.pairs
120 }
121
122 pub fn lcm_moduli(&self) -> u64 {
124 if self.pairs.is_empty() {
125 return 1;
126 }
127 let lcm = self
128 .pairs
129 .iter()
130 .fold(1u128, |acc, &(_, b)| lcm128(acc, b as u128));
131 lcm as u64
132 }
133}
134
135#[derive(Deserialize)]
136struct SimultaneousIncongruencesData {
137 pairs: Vec<(u64, u64)>,
138}
139
140impl<'de> Deserialize<'de> for SimultaneousIncongruences {
141 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
142 where
143 D: Deserializer<'de>,
144 {
145 let data = SimultaneousIncongruencesData::deserialize(deserializer)?;
146 Self::new(data.pairs).map_err(D::Error::custom)
147 }
148}
149
150impl Problem for SimultaneousIncongruences {
151 const NAME: &'static str = "SimultaneousIncongruences";
152 type Value = Or;
153
154 fn variant() -> Vec<(&'static str, &'static str)> {
155 crate::variant_params![]
156 }
157
158 fn dims(&self) -> Vec<usize> {
159 let lcm = self.lcm_moduli() as usize;
160 vec![lcm]
161 }
162
163 fn evaluate(&self, config: &[usize]) -> Or {
164 if config.len() != 1 {
165 return Or(false);
166 }
167 let x = config[0] as u64;
168 Or(self.pairs.iter().all(|&(a, b)| x % b != a % b))
170 }
171}
172
173crate::declare_variants! {
174 default SimultaneousIncongruences => "num_pairs",
175}
176
177#[cfg(feature = "example-db")]
178pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
179 vec![crate::example_db::specs::ModelExampleSpec {
180 id: "simultaneous_incongruences",
181 instance: Box::new(
182 SimultaneousIncongruences::new(vec![(2, 2), (1, 3), (2, 5), (3, 7)]).unwrap(),
183 ),
184 optimal_config: vec![5],
186 optimal_value: serde_json::json!(true),
187 }]
188}
189
190#[cfg(test)]
191#[path = "../../unit_tests/models/algebraic/simultaneous_incongruences.rs"]
192mod tests;