problemreductions/models/algebraic/
simultaneous_incongruences.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
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 category: crate::registry::ProblemCategory::Algebraic,
19 module_path: module_path!(),
20 description: "Decide whether there exists x with x ≢ aᵢ (mod bᵢ) for all i",
21 fields: &[
22 FieldInfo {
23 name: "pairs",
24 type_name: "Vec<(i64, i64)>",
25 description: "Pairs (aᵢ, bᵢ) with bᵢ > 0 and 1 ≤ aᵢ ≤ bᵢ",
26 },
27 ],
28 }
29}
30
31#[derive(Debug, Clone, Serialize)]
52pub struct SimultaneousIncongruences {
53 pairs: Vec<(i64, i64)>,
55}
56
57fn gcd(mut a: i64, mut b: i64) -> i64 {
58 while b != 0 {
59 let t = b;
60 b = a % b;
61 a = t;
62 }
63 a
64}
65
66impl SimultaneousIncongruences {
67 fn validate_inputs(pairs: &[(i64, i64)]) -> Result<(), crate::registry::ConstructionError> {
68 for (i, &(a, b)) in pairs.iter().enumerate() {
69 if b <= 0 {
70 return Err(format!("Modulus b at index {i} must be positive (got b={b})").into());
71 }
72 if a <= 0 {
73 return Err(format!("Residue a at index {i} must be at least 1 (got a=0)").into());
74 }
75 if a > b {
76 return Err(format!(
77 "Residue a ({a}) must not exceed modulus b ({b}) at index {i}"
78 )
79 .into());
80 }
81 }
82 pairs.iter().try_fold(1i64, |lcm, &(_, modulus)| {
83 (lcm / gcd(lcm, modulus))
84 .checked_mul(modulus)
85 .ok_or_else(|| "Least common multiple of moduli exceeds i64 range".to_string())
86 })?;
87 Ok(())
88 }
89
90 pub fn new(pairs: Vec<(i64, i64)>) -> Result<Self, crate::registry::ConstructionError> {
93 Self::validate_inputs(&pairs)?;
94 Ok(Self { pairs })
95 }
96
97 pub fn num_pairs(&self) -> usize {
99 self.pairs.len()
100 }
101
102 pub fn pairs(&self) -> &[(i64, i64)] {
104 &self.pairs
105 }
106
107 pub fn lcm_moduli(&self) -> i64 {
109 self.pairs.iter().fold(1i64, |lcm, &(_, modulus)| {
110 (lcm / gcd(lcm, modulus)) * modulus
111 })
112 }
113}
114
115#[derive(Deserialize)]
116struct SimultaneousIncongruencesData {
117 pairs: Vec<(i64, i64)>,
118}
119
120impl<'de> Deserialize<'de> for SimultaneousIncongruences {
121 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
122 where
123 D: Deserializer<'de>,
124 {
125 let data = SimultaneousIncongruencesData::deserialize(deserializer)?;
126 Self::new(data.pairs).map_err(D::Error::custom)
127 }
128}
129
130impl Problem for SimultaneousIncongruences {
131 const NAME: &'static str = "SimultaneousIncongruences";
132 type Solution = i64;
133 type Value = Or;
134
135 crate::problem_parameters![("num_pairs", num_pairs),];
136
137 fn variant() -> Vec<(&'static str, &'static str)> {
138 crate::variant_params![]
139 }
140
141 fn evaluate(&self, solution: &Self::Solution) -> Result<Or, crate::traits::EvaluationError> {
142 Ok({
143 Or(self.pairs.iter().all(|&(a, b)| solution % b != a % b))
145 })
146 }
147}
148
149impl crate::solvers::BruteForceProblem for SimultaneousIncongruences {
150 fn dimensions(&self) -> Vec<usize> {
151 let lcm = usize::try_from(self.lcm_moduli()).expect("validated positive LCM fits usize");
152 vec![lcm]
153 }
154}
155
156crate::declare_variants! {
157 default SimultaneousIncongruences => "num_pairs",
158}
159
160crate::register_brute_force! {
161 SimultaneousIncongruences decode |_, indices: Vec<usize>| i64::try_from(indices[0]).expect("enumerated incongruence value fits i64"),
162}
163
164#[cfg(feature = "example-db")]
165pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
166 vec![crate::example_db::specs::ModelExampleSpec {
167 id: "simultaneous_incongruences",
168 instance: Box::new(
169 SimultaneousIncongruences::new(vec![(2, 2), (1, 3), (2, 5), (3, 7)]).unwrap(),
170 ),
171 optimal_config: serde_json::json!(5),
173 optimal_value: serde_json::json!(true),
174 }]
175}
176
177#[cfg(test)]
178#[path = "../../unit_tests/models/algebraic/simultaneous_incongruences.rs"]
179mod tests;