problemreductions/models/algebraic/
quadratic_diophantine_equations.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
10use crate::traits::Problem;
11use crate::types::Or;
12use num_bigint::{BigUint, ToBigUint};
13use num_traits::{One, Zero};
14use serde::de::Error as _;
15use serde::{Deserialize, Deserializer, Serialize};
16
17inventory::submit! {
18 ProblemSchemaEntry {
19 name: "QuadraticDiophantineEquations",
20 display_name: "Quadratic Diophantine Equations",
21 aliases: &["QDE"],
22 dimensions: &[],
23 category: crate::registry::ProblemCategory::Algebraic,
24 module_path: module_path!(),
25 description: "Decide whether ax^2 + by = c has a solution in positive integers x, y",
26 fields: &[
27 FieldInfo { name: "a", type_name: "BigUint", description: "Coefficient of x^2" },
28 FieldInfo { name: "b", type_name: "BigUint", description: "Coefficient of y" },
29 FieldInfo { name: "c", type_name: "BigUint", description: "Right-hand side constant" },
30 ],
31 }
32}
33
34#[derive(Debug, Clone, Serialize)]
42pub struct QuadraticDiophantineEquations {
43 #[serde(with = "crate::models::misc::biguint_serde::decimal_biguint")]
45 a: BigUint,
46 #[serde(with = "crate::models::misc::biguint_serde::decimal_biguint")]
48 b: BigUint,
49 #[serde(with = "crate::models::misc::biguint_serde::decimal_biguint")]
51 c: BigUint,
52}
53
54fn bit_length(value: &BigUint) -> usize {
55 if value.is_zero() {
56 0
57 } else {
58 let bytes = value.to_bytes_be();
59 let msb = *bytes.first().expect("nonzero BigUint has bytes");
60 8 * (bytes.len() - 1) + (8 - msb.leading_zeros() as usize)
61 }
62}
63
64impl QuadraticDiophantineEquations {
65 fn validate_inputs(
66 a: &BigUint,
67 b: &BigUint,
68 c: &BigUint,
69 ) -> Result<(), crate::registry::ConstructionError> {
70 if a.is_zero() {
71 return Err("Coefficient a must be positive".to_string().into());
72 }
73 if b.is_zero() {
74 return Err("Coefficient b must be positive".to_string().into());
75 }
76 if c.is_zero() {
77 return Err("Right-hand side c must be positive".to_string().into());
78 }
79 Ok(())
80 }
81
82 fn isqrt(n: &BigUint) -> BigUint {
83 if n.is_zero() {
84 return BigUint::zero();
85 }
86
87 let mut low = BigUint::zero();
88 let mut high = BigUint::one() << bit_length(n).div_ceil(2);
89
90 while low < high {
91 let mid = (&low + &high + BigUint::one()) >> 1usize;
92 if &mid * &mid <= *n {
93 low = mid;
94 } else {
95 high = mid - BigUint::one();
96 }
97 }
98
99 low
100 }
101
102 pub fn try_new<A, B, C>(a: A, b: B, c: C) -> Result<Self, crate::registry::ConstructionError>
105 where
106 A: ToBigUint,
107 B: ToBigUint,
108 C: ToBigUint,
109 {
110 let a = a
111 .to_biguint()
112 .ok_or_else(|| "Coefficient a must be nonnegative".to_string())?;
113 let b = b
114 .to_biguint()
115 .ok_or_else(|| "Coefficient b must be nonnegative".to_string())?;
116 let c = c
117 .to_biguint()
118 .ok_or_else(|| "Right-hand side c must be nonnegative".to_string())?;
119 Self::validate_inputs(&a, &b, &c)?;
120 Ok(Self { a, b, c })
121 }
122
123 pub fn new<A, B, C>(a: A, b: B, c: C) -> Self
129 where
130 A: ToBigUint,
131 B: ToBigUint,
132 C: ToBigUint,
133 {
134 Self::try_new(a, b, c).unwrap_or_else(|msg| panic!("{msg}"))
135 }
136
137 pub fn a(&self) -> &BigUint {
139 &self.a
140 }
141
142 pub fn b(&self) -> &BigUint {
144 &self.b
145 }
146
147 pub fn c(&self) -> &BigUint {
149 &self.c
150 }
151
152 pub fn bit_length_a(&self) -> usize {
154 bit_length(&self.a)
155 }
156
157 pub fn bit_length_b(&self) -> usize {
159 bit_length(&self.b)
160 }
161
162 pub fn bit_length_c(&self) -> usize {
164 bit_length(&self.c)
165 }
166
167 fn max_x(&self) -> BigUint {
168 if self.c < self.a {
169 return BigUint::zero();
170 }
171 Self::isqrt(&(&self.c / &self.a))
172 }
173
174 fn witness_bit_length(&self) -> usize {
175 let max_x = self.max_x();
176 if max_x.is_zero() {
177 0
178 } else {
179 bit_length(&max_x)
180 }
181 }
182
183 pub fn check_x(&self, x: &BigUint) -> Option<BigUint> {
187 if x.is_zero() {
188 return None;
189 }
190
191 let ax2 = &self.a * x * x;
192 if ax2 >= self.c {
193 return None;
194 }
195
196 let remainder = &self.c - ax2;
197 if (&remainder % &self.b) != BigUint::zero() {
198 return None;
199 }
200
201 let y = remainder / &self.b;
202 if y.is_zero() {
203 return None;
204 }
205
206 Some(y)
207 }
208}
209
210#[derive(Deserialize)]
211struct QuadraticDiophantineEquationsData {
212 #[serde(with = "crate::models::misc::biguint_serde::decimal_biguint")]
213 a: BigUint,
214 #[serde(with = "crate::models::misc::biguint_serde::decimal_biguint")]
215 b: BigUint,
216 #[serde(with = "crate::models::misc::biguint_serde::decimal_biguint")]
217 c: BigUint,
218}
219
220impl<'de> Deserialize<'de> for QuadraticDiophantineEquations {
221 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
222 where
223 D: Deserializer<'de>,
224 {
225 let data = QuadraticDiophantineEquationsData::deserialize(deserializer)?;
226 Self::try_new(data.a, data.b, data.c).map_err(D::Error::custom)
227 }
228}
229
230impl Problem for QuadraticDiophantineEquations {
231 const NAME: &'static str = "QuadraticDiophantineEquations";
232 type Solution = BigUint;
233 type Value = Or;
234
235 crate::problem_parameters![
236 ("bit_length_a", bit_length_a),
237 ("bit_length_b", bit_length_b),
238 ("bit_length_c", bit_length_c),
239 ];
240
241 fn variant() -> Vec<(&'static str, &'static str)> {
242 crate::variant_params![]
243 }
244
245 fn evaluate(&self, x: &Self::Solution) -> Result<Or, crate::traits::EvaluationError> {
246 Ok({
247 if x.is_zero() || x > &self.max_x() {
248 return Ok(Or(false));
249 }
250
251 Or(self.check_x(x).is_some())
252 })
253 }
254}
255
256impl crate::solvers::BruteForceProblem for QuadraticDiophantineEquations {
257 fn dimensions(&self) -> Vec<usize> {
258 let num_bits = self.witness_bit_length();
259 if num_bits == 0 {
260 Vec::new()
261 } else {
262 vec![2; num_bits]
263 }
264 }
265}
266
267crate::declare_variants! {
268 default QuadraticDiophantineEquations => "2^bit_length_c",
269}
270
271crate::register_brute_force! {
272 QuadraticDiophantineEquations decode |_: &QuadraticDiophantineEquations, indices: Vec<usize>| indices.into_iter().enumerate().fold(BigUint::zero(), |value, (bit, set)| if set == 0 { value } else { value + (BigUint::one() << bit) }),
273}
274
275#[cfg(feature = "example-db")]
276pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
277 let instance = QuadraticDiophantineEquations::new(3u32, 5u32, 53u32);
278 let optimal_config = BigUint::from(1u32);
279
280 vec![crate::example_db::specs::ModelExampleSpec {
281 id: "quadratic_diophantine_equations",
282 instance: Box::new(instance),
283 optimal_config: serde_json::to_value(optimal_config)
284 .expect("solution serialization must succeed"),
285 optimal_value: serde_json::json!(true),
286 }]
287}
288
289#[cfg(test)]
290#[path = "../../unit_tests/models/algebraic/quadratic_diophantine_equations.rs"]
291mod tests;