problemreductions/models/algebraic/
quadratic_congruences.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry, ProblemSizeFieldEntry};
12use crate::traits::Problem;
13use crate::types::Or;
14use num_bigint::{BigUint, ToBigUint};
15use num_traits::{One, Zero};
16use serde::de::Error as _;
17use serde::{Deserialize, Deserializer, Serialize};
18
19inventory::submit! {
20 ProblemSchemaEntry {
21 name: "QuadraticCongruences",
22 display_name: "Quadratic Congruences",
23 aliases: &[],
24 dimensions: &[],
25 module_path: module_path!(),
26 description: "Decide whether x² ≡ a (mod b) has a solution for x in {1, ..., c-1}",
27 fields: &[
28 FieldInfo { name: "a", type_name: "BigUint", description: "a" },
29 FieldInfo { name: "b", type_name: "BigUint", description: "b" },
30 FieldInfo { name: "c", type_name: "BigUint", description: "c" },
31 ],
32 }
33}
34
35inventory::submit! {
36 ProblemSizeFieldEntry {
37 name: "QuadraticCongruences",
38 fields: &["bit_length_a", "bit_length_b", "bit_length_c"],
39 }
40}
41
42#[derive(Debug, Clone, Serialize)]
51pub struct QuadraticCongruences {
52 #[serde(with = "crate::models::misc::biguint_serde::decimal_biguint")]
54 a: BigUint,
55 #[serde(with = "crate::models::misc::biguint_serde::decimal_biguint")]
57 b: BigUint,
58 #[serde(with = "crate::models::misc::biguint_serde::decimal_biguint")]
60 c: BigUint,
61}
62
63fn bit_length(value: &BigUint) -> usize {
64 if value.is_zero() {
65 0
66 } else {
67 let bytes = value.to_bytes_be();
68 let msb = *bytes.first().expect("nonzero BigUint has bytes");
69 8 * (bytes.len() - 1) + (8 - msb.leading_zeros() as usize)
70 }
71}
72
73impl QuadraticCongruences {
74 fn validate_inputs(a: &BigUint, b: &BigUint, c: &BigUint) -> Result<(), String> {
75 if b.is_zero() {
76 return Err("Modulus b must be positive".to_string());
77 }
78 if c.is_zero() {
79 return Err("Bound c must be positive".to_string());
80 }
81 if a >= b {
82 return Err(format!("Residue a ({a}) must be less than modulus b ({b})"));
83 }
84 Ok(())
85 }
86
87 pub fn try_new<A, B, C>(a: A, b: B, c: C) -> Result<Self, String>
90 where
91 A: ToBigUint,
92 B: ToBigUint,
93 C: ToBigUint,
94 {
95 let a = a
96 .to_biguint()
97 .ok_or_else(|| "Residue a must be nonnegative".to_string())?;
98 let b = b
99 .to_biguint()
100 .ok_or_else(|| "Modulus b must be nonnegative".to_string())?;
101 let c = c
102 .to_biguint()
103 .ok_or_else(|| "Bound c must be nonnegative".to_string())?;
104 Self::validate_inputs(&a, &b, &c)?;
105 Ok(Self { a, b, c })
106 }
107
108 pub fn new<A, B, C>(a: A, b: B, c: C) -> Self
114 where
115 A: ToBigUint,
116 B: ToBigUint,
117 C: ToBigUint,
118 {
119 Self::try_new(a, b, c).unwrap_or_else(|msg| panic!("{msg}"))
120 }
121
122 pub fn a(&self) -> &BigUint {
124 &self.a
125 }
126
127 pub fn b(&self) -> &BigUint {
129 &self.b
130 }
131
132 pub fn c(&self) -> &BigUint {
134 &self.c
135 }
136
137 pub fn bit_length_a(&self) -> usize {
139 bit_length(&self.a)
140 }
141
142 pub fn bit_length_b(&self) -> usize {
144 bit_length(&self.b)
145 }
146
147 pub fn bit_length_c(&self) -> usize {
149 bit_length(&self.c)
150 }
151
152 fn witness_bit_length(&self) -> usize {
153 if self.c <= BigUint::one() {
154 0
155 } else {
156 bit_length(&(&self.c - BigUint::one()))
157 }
158 }
159
160 pub fn encode_witness(&self, x: &BigUint) -> Option<Vec<usize>> {
162 if x.is_zero() || x >= &self.c {
163 return None;
164 }
165
166 let num_bits = self.witness_bit_length();
167 let mut remaining = x.clone();
168 let mut config = Vec::with_capacity(num_bits);
169
170 for _ in 0..num_bits {
171 config.push(if (&remaining & BigUint::one()).is_zero() {
172 0
173 } else {
174 1
175 });
176 remaining >>= 1usize;
177 }
178
179 if remaining.is_zero() {
180 Some(config)
181 } else {
182 None
183 }
184 }
185
186 pub fn decode_witness(&self, config: &[usize]) -> Option<BigUint> {
188 if config.len() != self.witness_bit_length() || config.iter().any(|&digit| digit > 1) {
189 return None;
190 }
191
192 let mut value = BigUint::zero();
193 let mut weight = BigUint::one();
194 for &digit in config {
195 if digit == 1 {
196 value += &weight;
197 }
198 weight <<= 1usize;
199 }
200 Some(value)
201 }
202}
203
204#[derive(Deserialize)]
205struct QuadraticCongruencesData {
206 #[serde(with = "crate::models::misc::biguint_serde::decimal_biguint")]
207 a: BigUint,
208 #[serde(with = "crate::models::misc::biguint_serde::decimal_biguint")]
209 b: BigUint,
210 #[serde(with = "crate::models::misc::biguint_serde::decimal_biguint")]
211 c: BigUint,
212}
213
214impl<'de> Deserialize<'de> for QuadraticCongruences {
215 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
216 where
217 D: Deserializer<'de>,
218 {
219 let data = QuadraticCongruencesData::deserialize(deserializer)?;
220 Self::try_new(data.a, data.b, data.c).map_err(D::Error::custom)
221 }
222}
223
224impl Problem for QuadraticCongruences {
225 const NAME: &'static str = "QuadraticCongruences";
226 type Value = Or;
227
228 fn variant() -> Vec<(&'static str, &'static str)> {
229 crate::variant_params![]
230 }
231
232 fn dims(&self) -> Vec<usize> {
233 let num_bits = self.witness_bit_length();
234 if num_bits == 0 {
235 Vec::new()
236 } else {
237 vec![2; num_bits]
238 }
239 }
240
241 fn evaluate(&self, config: &[usize]) -> Or {
242 let Some(x) = self.decode_witness(config) else {
243 return Or(false);
244 };
245
246 if x.is_zero() || x >= *self.c() {
247 return Or(false);
248 }
249
250 let satisfies = (&x * &x) % self.b() == self.a().clone();
251 Or(satisfies)
252 }
253}
254
255crate::declare_variants! {
256 default QuadraticCongruences => "2^bit_length_c",
257}
258
259#[cfg(feature = "example-db")]
260pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
261 let instance = QuadraticCongruences::new(4u32, 15u32, 10u32);
262 let optimal_config = instance
263 .encode_witness(&BigUint::from(2u32))
264 .expect("x=2 should be a valid canonical witness");
265
266 vec![crate::example_db::specs::ModelExampleSpec {
267 id: "quadratic_congruences",
268 instance: Box::new(instance),
269 optimal_config,
270 optimal_value: serde_json::json!(true),
271 }]
272}
273
274#[cfg(test)]
275#[path = "../../unit_tests/models/algebraic/quadratic_congruences.rs"]
276mod tests;