11//! Quadratic Diophantine Equations problem implementation.
22//!
3- //! Given positive integers a, b, c, determine whether there exist
4- //! positive integers x, y such that ax² + by = c.
3+ //! Given positive integers `a`, `b`, and `c`, determine whether there exist
4+ //! positive integers `x`, `y` such that `a x^2 + b y = c`.
5+ //!
6+ //! The witness integer `x` is encoded as a little-endian binary vector so the
7+ //! model can represent large reductions without fixed-width overflow.
58
69use crate :: registry:: { FieldInfo , ProblemSchemaEntry , ProblemSizeFieldEntry } ;
710use crate :: traits:: Problem ;
811use crate :: types:: Or ;
12+ use num_bigint:: { BigUint , ToBigUint } ;
13+ use num_traits:: { One , Zero } ;
914use serde:: de:: Error as _;
1015use serde:: { Deserialize , Deserializer , Serialize } ;
1116
@@ -16,147 +21,245 @@ inventory::submit! {
1621 aliases: & [ "QDE" ] ,
1722 dimensions: & [ ] ,
1823 module_path: module_path!( ) ,
19- description: "Decide whether ax² + by = c has a solution in positive integers x, y" ,
24+ description: "Decide whether ax^2 + by = c has a solution in positive integers x, y" ,
2025 fields: & [
21- FieldInfo { name: "a" , type_name: "u64 " , description: "Coefficient of x² " } ,
22- FieldInfo { name: "b" , type_name: "u64 " , description: "Coefficient of y" } ,
23- FieldInfo { name: "c" , type_name: "u64 " , description: "Right-hand side constant" } ,
26+ FieldInfo { name: "a" , type_name: "BigUint " , description: "Coefficient of x^2 " } ,
27+ FieldInfo { name: "b" , type_name: "BigUint " , description: "Coefficient of y" } ,
28+ FieldInfo { name: "c" , type_name: "BigUint " , description: "Right-hand side constant" } ,
2429 ] ,
2530 }
2631}
2732
2833inventory:: submit! {
2934 ProblemSizeFieldEntry {
3035 name: "QuadraticDiophantineEquations" ,
31- fields: & [ "c " ] ,
36+ fields: & [ "bit_length_a" , "bit_length_b" , "bit_length_c "] ,
3237 }
3338}
3439
3540/// Quadratic Diophantine Equations problem.
3641///
37- /// Given positive integers a, b, c, determine whether there exist
38- /// positive integers x, y such that ax² + by = c.
39- ///
40- /// # Example
42+ /// Given positive integers `a`, `b`, and `c`, determine whether there exist
43+ /// positive integers `x`, `y` such that `a x^2 + b y = c`.
4144///
42- /// ```
43- /// use problemreductions::models::algebraic::QuadraticDiophantineEquations;
44- /// use problemreductions::{Problem, Solver, BruteForce};
45- ///
46- /// // a=3, b=5, c=53: x=1 gives y=10, x=4 gives y=1
47- /// let problem = QuadraticDiophantineEquations::new(3, 5, 53);
48- /// let solver = BruteForce::new();
49- /// let witness = solver.find_witness(&problem);
50- /// assert!(witness.is_some());
51- /// ```
45+ /// The configuration encodes `x` in little-endian binary:
46+ /// `config[i] in {0,1}` is the coefficient of `2^i`.
5247#[ derive( Debug , Clone , Serialize ) ]
5348pub struct QuadraticDiophantineEquations {
54- /// Coefficient of x².
55- a : u64 ,
49+ /// Coefficient of x^2.
50+ #[ serde( with = "crate::models::misc::biguint_serde::decimal_biguint" ) ]
51+ a : BigUint ,
5652 /// Coefficient of y.
57- b : u64 ,
53+ #[ serde( with = "crate::models::misc::biguint_serde::decimal_biguint" ) ]
54+ b : BigUint ,
5855 /// Right-hand side constant.
59- c : u64 ,
56+ #[ serde( with = "crate::models::misc::biguint_serde::decimal_biguint" ) ]
57+ c : BigUint ,
58+ }
59+
60+ fn bit_length ( value : & BigUint ) -> usize {
61+ if value. is_zero ( ) {
62+ 0
63+ } else {
64+ let bytes = value. to_bytes_be ( ) ;
65+ let msb = * bytes. first ( ) . expect ( "nonzero BigUint has bytes" ) ;
66+ 8 * ( bytes. len ( ) - 1 ) + ( 8 - msb. leading_zeros ( ) as usize )
67+ }
6068}
6169
6270impl QuadraticDiophantineEquations {
63- fn validate_inputs ( a : u64 , b : u64 , c : u64 ) -> Result < ( ) , String > {
64- if a == 0 {
71+ fn validate_inputs ( a : & BigUint , b : & BigUint , c : & BigUint ) -> Result < ( ) , String > {
72+ if a. is_zero ( ) {
6573 return Err ( "Coefficient a must be positive" . to_string ( ) ) ;
6674 }
67- if b == 0 {
75+ if b. is_zero ( ) {
6876 return Err ( "Coefficient b must be positive" . to_string ( ) ) ;
6977 }
70- if c == 0 {
78+ if c. is_zero ( ) {
7179 return Err ( "Right-hand side c must be positive" . to_string ( ) ) ;
7280 }
7381 Ok ( ( ) )
7482 }
7583
84+ fn isqrt ( n : & BigUint ) -> BigUint {
85+ if n. is_zero ( ) {
86+ return BigUint :: zero ( ) ;
87+ }
88+
89+ let mut low = BigUint :: zero ( ) ;
90+ let mut high = BigUint :: one ( ) << ( ( bit_length ( n) + 1 ) / 2 ) ;
91+
92+ while low < high {
93+ let mid = ( & low + & high + BigUint :: one ( ) ) >> 1usize ;
94+ if & mid * & mid <= * n {
95+ low = mid;
96+ } else {
97+ high = mid - BigUint :: one ( ) ;
98+ }
99+ }
100+
101+ low
102+ }
103+
76104 /// Create a new QuadraticDiophantineEquations instance, returning an error
77105 /// instead of panicking when inputs are invalid.
78- pub fn try_new ( a : u64 , b : u64 , c : u64 ) -> Result < Self , String > {
79- Self :: validate_inputs ( a, b, c) ?;
106+ pub fn try_new < A , B , C > ( a : A , b : B , c : C ) -> Result < Self , String >
107+ where
108+ A : ToBigUint ,
109+ B : ToBigUint ,
110+ C : ToBigUint ,
111+ {
112+ let a = a
113+ . to_biguint ( )
114+ . ok_or_else ( || "Coefficient a must be nonnegative" . to_string ( ) ) ?;
115+ let b = b
116+ . to_biguint ( )
117+ . ok_or_else ( || "Coefficient b must be nonnegative" . to_string ( ) ) ?;
118+ let c = c
119+ . to_biguint ( )
120+ . ok_or_else ( || "Right-hand side c must be nonnegative" . to_string ( ) ) ?;
121+ Self :: validate_inputs ( & a, & b, & c) ?;
80122 Ok ( Self { a, b, c } )
81123 }
82124
83125 /// Create a new QuadraticDiophantineEquations instance.
84126 ///
85127 /// # Panics
86128 ///
87- /// Panics if any of a, b, c is zero.
88- pub fn new ( a : u64 , b : u64 , c : u64 ) -> Self {
129+ /// Panics if any of `a`, `b`, `c` is zero.
130+ pub fn new < A , B , C > ( a : A , b : B , c : C ) -> Self
131+ where
132+ A : ToBigUint ,
133+ B : ToBigUint ,
134+ C : ToBigUint ,
135+ {
89136 Self :: try_new ( a, b, c) . unwrap_or_else ( |msg| panic ! ( "{msg}" ) )
90137 }
91138
92- /// Get the coefficient a (coefficient of x² ).
93- pub fn a ( & self ) -> u64 {
94- self . a
139+ /// Get the coefficient a (coefficient of x^2 ).
140+ pub fn a ( & self ) -> & BigUint {
141+ & self . a
95142 }
96143
97144 /// Get the coefficient b (coefficient of y).
98- pub fn b ( & self ) -> u64 {
99- self . b
145+ pub fn b ( & self ) -> & BigUint {
146+ & self . b
100147 }
101148
102149 /// Get the right-hand side constant c.
103- pub fn c ( & self ) -> u64 {
104- self . c
150+ pub fn c ( & self ) -> & BigUint {
151+ & self . c
152+ }
153+
154+ /// Number of bits needed to encode the coefficient a.
155+ pub fn bit_length_a ( & self ) -> usize {
156+ bit_length ( & self . a )
157+ }
158+
159+ /// Number of bits needed to encode the coefficient b.
160+ pub fn bit_length_b ( & self ) -> usize {
161+ bit_length ( & self . b )
105162 }
106163
107- /// Compute the integer square root of n (floor(sqrt(n))).
108- fn isqrt ( n : u64 ) -> u64 {
109- if n == 0 {
110- return 0 ;
164+ /// Number of bits needed to encode the constant c.
165+ pub fn bit_length_c ( & self ) -> usize {
166+ bit_length ( & self . c )
167+ }
168+
169+ fn max_x ( & self ) -> BigUint {
170+ if self . c < self . a {
171+ return BigUint :: zero ( ) ;
111172 }
112- let mut x = ( n as f64 ) . sqrt ( ) as u64 ;
113- // Correct for floating-point imprecision.
114- while x. saturating_mul ( x) > n {
115- x -= 1 ;
173+ Self :: isqrt ( & ( & self . c / & self . a ) )
174+ }
175+
176+ fn witness_bit_length ( & self ) -> usize {
177+ let max_x = self . max_x ( ) ;
178+ if max_x. is_zero ( ) {
179+ 0
180+ } else {
181+ bit_length ( & max_x)
182+ }
183+ }
184+
185+ /// Encode a candidate witness integer `x` as a little-endian binary configuration.
186+ pub fn encode_witness ( & self , x : & BigUint ) -> Option < Vec < usize > > {
187+ if x. is_zero ( ) || x > & self . max_x ( ) {
188+ return None ;
116189 }
117- while ( x + 1 ) . saturating_mul ( x + 1 ) <= n {
118- x += 1 ;
190+
191+ let num_bits = self . witness_bit_length ( ) ;
192+ let mut remaining = x. clone ( ) ;
193+ let mut config = Vec :: with_capacity ( num_bits) ;
194+
195+ for _ in 0 ..num_bits {
196+ config. push ( if ( & remaining & BigUint :: one ( ) ) . is_zero ( ) {
197+ 0
198+ } else {
199+ 1
200+ } ) ;
201+ remaining >>= 1usize ;
202+ }
203+
204+ if remaining. is_zero ( ) {
205+ Some ( config)
206+ } else {
207+ None
119208 }
120- x
121209 }
122210
123- /// Compute the maximum value of x (floor(sqrt(c/a))).
124- /// Returns 0 if c < a (no positive x is possible since x >= 1 requires a*1 <= c).
125- fn max_x ( & self ) -> u64 {
126- if self . c < self . a {
127- return 0 ;
211+ /// Decode a little-endian binary configuration into its candidate witness `x`.
212+ pub fn decode_witness ( & self , config : & [ usize ] ) -> Option < BigUint > {
213+ if config. len ( ) != self . witness_bit_length ( ) || config. iter ( ) . any ( |& digit| digit > 1 ) {
214+ return None ;
128215 }
129- Self :: isqrt ( self . c / self . a )
216+
217+ let mut value = BigUint :: zero ( ) ;
218+ let mut weight = BigUint :: one ( ) ;
219+ for & digit in config {
220+ if digit == 1 {
221+ value += & weight;
222+ }
223+ weight <<= 1usize ;
224+ }
225+ Some ( value)
130226 }
131227
132228 /// Check whether a given x yields a valid positive integer y.
133229 ///
134- /// Returns Some(y) if y is a positive integer, None otherwise.
135- pub fn check_x ( & self , x : u64 ) -> Option < u64 > {
136- if x == 0 {
230+ /// Returns ` Some(y)` if `y` is a positive integer, ` None` otherwise.
231+ pub fn check_x ( & self , x : & BigUint ) -> Option < BigUint > {
232+ if x. is_zero ( ) {
137233 return None ;
138234 }
139- let ax2 = self . a . checked_mul ( x) ?. checked_mul ( x) ?;
235+
236+ let ax2 = & self . a * x * x;
140237 if ax2 >= self . c {
141238 return None ;
142239 }
143- let remainder = self . c - ax2;
144- if !remainder. is_multiple_of ( self . b ) {
240+
241+ let remainder = & self . c - ax2;
242+ if ( & remainder % & self . b ) != BigUint :: zero ( ) {
145243 return None ;
146244 }
147- let y = remainder / self . b ;
148- if y == 0 {
245+
246+ let y = remainder / & self . b ;
247+ if y. is_zero ( ) {
149248 return None ;
150249 }
250+
151251 Some ( y)
152252 }
153253}
154254
155255#[ derive( Deserialize ) ]
156256struct QuadraticDiophantineEquationsData {
157- a : u64 ,
158- b : u64 ,
159- c : u64 ,
257+ #[ serde( with = "crate::models::misc::biguint_serde::decimal_biguint" ) ]
258+ a : BigUint ,
259+ #[ serde( with = "crate::models::misc::biguint_serde::decimal_biguint" ) ]
260+ b : BigUint ,
261+ #[ serde( with = "crate::models::misc::biguint_serde::decimal_biguint" ) ]
262+ c : BigUint ,
160263}
161264
162265impl < ' de > Deserialize < ' de > for QuadraticDiophantineEquations {
@@ -178,38 +281,42 @@ impl Problem for QuadraticDiophantineEquations {
178281 }
179282
180283 fn dims ( & self ) -> Vec < usize > {
181- let max_x = self . max_x ( ) as usize ;
182- if max_x == 0 {
183- // No valid x exists; return empty config space.
184- return vec ! [ 0 ] ;
284+ let num_bits = self . witness_bit_length ( ) ;
285+ if num_bits == 0 {
286+ Vec :: new ( )
287+ } else {
288+ vec ! [ 2 ; num_bits]
185289 }
186- // One variable: x ranges over {1, ..., max_x}.
187- // config[0] in {0, ..., max_x - 1} maps to x = config[0] + 1.
188- vec ! [ max_x]
189290 }
190291
191292 fn evaluate ( & self , config : & [ usize ] ) -> Or {
192- Or ( {
193- if config. len ( ) != 1 {
194- return Or ( false ) ;
195- }
196- let x = ( config[ 0 ] as u64 ) + 1 ; // 1-indexed
197- self . check_x ( x) . is_some ( )
198- } )
293+ let Some ( x) = self . decode_witness ( config) else {
294+ return Or ( false ) ;
295+ } ;
296+
297+ if x. is_zero ( ) || x > self . max_x ( ) {
298+ return Or ( false ) ;
299+ }
300+
301+ Or ( self . check_x ( & x) . is_some ( ) )
199302 }
200303}
201304
202305crate :: declare_variants! {
203- default QuadraticDiophantineEquations => "sqrt(c) " ,
306+ default QuadraticDiophantineEquations => "2^bit_length_c " ,
204307}
205308
206309#[ cfg( feature = "example-db" ) ]
207310pub ( crate ) fn canonical_model_example_specs ( ) -> Vec < crate :: example_db:: specs:: ModelExampleSpec > {
311+ let instance = QuadraticDiophantineEquations :: new ( 3u32 , 5u32 , 53u32 ) ;
312+ let optimal_config = instance
313+ . encode_witness ( & BigUint :: from ( 1u32 ) )
314+ . expect ( "x=1 should be a valid canonical witness" ) ;
315+
208316 vec ! [ crate :: example_db:: specs:: ModelExampleSpec {
209317 id: "quadratic_diophantine_equations" ,
210- instance: Box :: new( QuadraticDiophantineEquations :: new( 3 , 5 , 53 ) ) ,
211- // x=1 (config[0]=0) gives y=10: 3*1 + 5*10 = 53
212- optimal_config: vec![ 0 ] ,
318+ instance: Box :: new( instance) ,
319+ optimal_config,
213320 optimal_value: serde_json:: json!( true ) ,
214321 } ]
215322}
0 commit comments