1pub mod algebraic {
76 pub mod field;
77 pub mod lattice;
78 pub mod section2;
79 pub mod units;
80 pub mod window;
81}
82pub mod backend;
83pub mod certificate;
84pub mod classical;
85pub mod error;
86mod numeric;
87pub mod utils;
88
89use crate::algebraic::field::MultiQuadraticField;
90use crate::algebraic::section2::generate_native_multiquadratic_section2;
91pub use crate::certificate::{
92 CertificateVerificationReport, CertifiedPointSet, ClassicalCertificate,
93 ClassicalConstructionKind, ConstructionCertificate, FloatingAuditReport,
94 MultiquadraticCertificate, Point2,
95};
96use crate::classical::{generate_moser_spindle, generate_square_grid, generate_triangular_grid};
97pub use crate::error::{GenerationError, VerificationError};
98
99const DEFAULT_MAX_PRIME_EXPONENT: usize = 3;
100const DEFAULT_MAX_RADIUS_ATTEMPTS: usize = 15;
101const DEFAULT_INITIAL_RADIUS: f64 = 2.0;
102const DEFAULT_RADIUS_GROWTH: f64 = 1.5;
103const DEFAULT_CANDIDATE_MULTIPLIER: usize = 10;
104const DEFAULT_PROJECTION_TOLERANCE: f64 = 1e-7;
105const DEFAULT_UNIT_DISTANCE_TOLERANCE: f64 = 1e-4;
106
107#[derive(Clone, Debug)]
109pub struct MultiquadraticConfig {
110 generators: Vec<i64>,
111 split_prime: i64,
112 k: usize,
113 max_prime_exponent: usize,
114 max_radius_attempts: usize,
115 initial_radius: f64,
116 radius_growth: f64,
117 candidate_multiplier: usize,
118 projection_tolerance: f64,
119 unit_distance_tolerance: f64,
120}
121
122impl MultiquadraticConfig {
123 pub fn builder(
124 generators: Vec<i64>,
125 split_prime: i64,
126 k: usize,
127 ) -> MultiquadraticConfigBuilder {
128 MultiquadraticConfigBuilder {
129 generators,
130 split_prime,
131 k,
132 max_prime_exponent: DEFAULT_MAX_PRIME_EXPONENT,
133 max_radius_attempts: DEFAULT_MAX_RADIUS_ATTEMPTS,
134 initial_radius: DEFAULT_INITIAL_RADIUS,
135 radius_growth: DEFAULT_RADIUS_GROWTH,
136 candidate_multiplier: DEFAULT_CANDIDATE_MULTIPLIER,
137 projection_tolerance: DEFAULT_PROJECTION_TOLERANCE,
138 unit_distance_tolerance: DEFAULT_UNIT_DISTANCE_TOLERANCE,
139 }
140 }
141
142 pub fn new(generators: Vec<i64>, split_prime: i64, k: usize) -> Result<Self, GenerationError> {
144 let mut generators = generators;
145 if !generators.contains(&-1) {
146 generators.push(-1);
147 }
148 MultiQuadraticField::try_new(&generators)?;
149 validate_split_prime(split_prime)?;
150 validate_complete_splitting(&generators, split_prime)?;
151
152 Ok(Self {
153 generators,
154 split_prime,
155 k,
156 max_prime_exponent: DEFAULT_MAX_PRIME_EXPONENT,
157 max_radius_attempts: DEFAULT_MAX_RADIUS_ATTEMPTS,
158 initial_radius: DEFAULT_INITIAL_RADIUS,
159 radius_growth: DEFAULT_RADIUS_GROWTH,
160 candidate_multiplier: DEFAULT_CANDIDATE_MULTIPLIER,
161 projection_tolerance: DEFAULT_PROJECTION_TOLERANCE,
162 unit_distance_tolerance: DEFAULT_UNIT_DISTANCE_TOLERANCE,
163 })
164 }
165
166 pub fn generators(&self) -> &[i64] {
167 &self.generators
168 }
169
170 pub fn split_prime(&self) -> i64 {
171 self.split_prime
172 }
173
174 pub fn k(&self) -> usize {
175 self.k
176 }
177
178 pub fn max_prime_exponent(&self) -> usize {
179 self.max_prime_exponent
180 }
181
182 pub fn max_radius_attempts(&self) -> usize {
183 self.max_radius_attempts
184 }
185
186 pub fn initial_radius(&self) -> f64 {
187 self.initial_radius
188 }
189
190 pub fn radius_growth(&self) -> f64 {
191 self.radius_growth
192 }
193
194 pub fn candidate_multiplier(&self) -> usize {
195 self.candidate_multiplier
196 }
197
198 pub fn projection_tolerance(&self) -> f64 {
199 self.projection_tolerance
200 }
201
202 pub fn unit_distance_tolerance(&self) -> f64 {
203 self.unit_distance_tolerance
204 }
205
206 pub fn with_prime_search_limit(
207 mut self,
208 max_prime_exponent: usize,
209 ) -> Result<Self, GenerationError> {
210 validate_nonzero_usize("max_prime_exponent", max_prime_exponent)?;
211 self.max_prime_exponent = max_prime_exponent;
212 Ok(self)
213 }
214
215 pub fn with_radius_search(
216 mut self,
217 max_radius_attempts: usize,
218 initial_radius: f64,
219 radius_growth: f64,
220 ) -> Result<Self, GenerationError> {
221 validate_nonzero_usize("max_radius_attempts", max_radius_attempts)?;
222 validate_positive_f64("initial_radius", initial_radius)?;
223 if !radius_growth.is_finite() || radius_growth <= 1.0 {
224 return Err(GenerationError::InvalidSearchParameter {
225 parameter: "radius_growth",
226 reason: "expected a finite value greater than 1.0",
227 });
228 }
229 self.max_radius_attempts = max_radius_attempts;
230 self.initial_radius = initial_radius;
231 self.radius_growth = radius_growth;
232 Ok(self)
233 }
234
235 pub fn with_candidate_multiplier(
236 mut self,
237 candidate_multiplier: usize,
238 ) -> Result<Self, GenerationError> {
239 validate_nonzero_usize("candidate_multiplier", candidate_multiplier)?;
240 self.candidate_multiplier = candidate_multiplier;
241 Ok(self)
242 }
243
244 pub fn with_tolerances(
245 mut self,
246 projection_tolerance: f64,
247 unit_distance_tolerance: f64,
248 ) -> Result<Self, GenerationError> {
249 validate_positive_f64("projection_tolerance", projection_tolerance)?;
250 validate_positive_f64("unit_distance_tolerance", unit_distance_tolerance)?;
251 self.projection_tolerance = projection_tolerance;
252 self.unit_distance_tolerance = unit_distance_tolerance;
253 Ok(self)
254 }
255}
256
257#[derive(Clone, Debug)]
258pub struct MultiquadraticConfigBuilder {
259 generators: Vec<i64>,
260 split_prime: i64,
261 k: usize,
262 max_prime_exponent: usize,
263 max_radius_attempts: usize,
264 initial_radius: f64,
265 radius_growth: f64,
266 candidate_multiplier: usize,
267 projection_tolerance: f64,
268 unit_distance_tolerance: f64,
269}
270
271impl MultiquadraticConfigBuilder {
272 pub fn max_prime_exponent(mut self, value: usize) -> Self {
273 self.max_prime_exponent = value;
274 self
275 }
276
277 pub fn radius_search(mut self, attempts: usize, initial_radius: f64, growth: f64) -> Self {
278 self.max_radius_attempts = attempts;
279 self.initial_radius = initial_radius;
280 self.radius_growth = growth;
281 self
282 }
283
284 pub fn candidate_multiplier(mut self, value: usize) -> Self {
285 self.candidate_multiplier = value;
286 self
287 }
288
289 pub fn tolerances(mut self, projection_tolerance: f64, unit_distance_tolerance: f64) -> Self {
290 self.projection_tolerance = projection_tolerance;
291 self.unit_distance_tolerance = unit_distance_tolerance;
292 self
293 }
294
295 pub fn build(self) -> Result<MultiquadraticConfig, GenerationError> {
296 MultiquadraticConfig::new(self.generators, self.split_prime, self.k)?
297 .with_prime_search_limit(self.max_prime_exponent)?
298 .with_radius_search(
299 self.max_radius_attempts,
300 self.initial_radius,
301 self.radius_growth,
302 )?
303 .with_candidate_multiplier(self.candidate_multiplier)?
304 .with_tolerances(self.projection_tolerance, self.unit_distance_tolerance)
305 }
306}
307
308#[derive(Clone, Debug)]
310pub enum ConstructionType {
311 SquareGrid { rows: usize, cols: usize },
313 TriangularGrid,
315 MoserSpindle,
317 Multiquadratic(MultiquadraticConfig),
319}
320
321#[derive(Clone, Debug)]
330pub struct UnitDistanceSet {
331 construction: ConstructionType,
332}
333
334impl UnitDistanceSet {
335 pub fn square_grid(rows: usize, cols: usize) -> Self {
337 UnitDistanceSet {
338 construction: ConstructionType::SquareGrid { rows, cols },
339 }
340 }
341
342 pub fn triangular_grid() -> Self {
344 UnitDistanceSet {
345 construction: ConstructionType::TriangularGrid,
346 }
347 }
348
349 pub fn moser_spindle() -> Self {
351 UnitDistanceSet {
352 construction: ConstructionType::MoserSpindle,
353 }
354 }
355
356 pub fn try_new_multiquadratic(
376 generators: Vec<i64>,
377 split_prime: i64,
378 k: usize,
379 ) -> Result<Self, GenerationError> {
380 Ok(UnitDistanceSet {
381 construction: ConstructionType::Multiquadratic(MultiquadraticConfig::new(
382 generators,
383 split_prime,
384 k,
385 )?),
386 })
387 }
388
389 pub fn multiquadratic(config: MultiquadraticConfig) -> Self {
390 UnitDistanceSet {
391 construction: ConstructionType::Multiquadratic(config),
392 }
393 }
394
395 pub fn construction(&self) -> &ConstructionType {
396 &self.construction
397 }
398
399 pub fn generate(&self, n_target: usize) -> Result<Vec<[f64; 2]>, GenerationError> {
404 match &self.construction {
405 ConstructionType::SquareGrid { rows, cols } => Ok(generate_square_grid(*rows, *cols)),
406 ConstructionType::TriangularGrid => Ok(generate_triangular_grid(n_target)),
407 ConstructionType::MoserSpindle => Ok(generate_moser_spindle()),
408 ConstructionType::Multiquadratic(config) => {
409 Ok(generate_native_multiquadratic_section2(config, n_target)?.projected_points)
410 }
411 }
412 }
413
414 pub fn generate_points(&self, n_target: usize) -> Result<Vec<Point2>, GenerationError> {
415 Ok(self
416 .generate(n_target)?
417 .into_iter()
418 .map(Point2::from)
419 .collect())
420 }
421
422 pub fn generate_certified(
427 &self,
428 n_target: usize,
429 ) -> Result<CertifiedPointSet, GenerationError> {
430 Ok(match &self.construction {
431 ConstructionType::SquareGrid { rows, cols } => {
432 let points = generate_square_grid(*rows, *cols);
433 CertifiedPointSet::new_classical(
434 ClassicalConstructionKind::SquareGrid {
435 rows: *rows,
436 cols: *cols,
437 },
438 n_target,
439 points,
440 DEFAULT_UNIT_DISTANCE_TOLERANCE,
441 )
442 }
443 ConstructionType::TriangularGrid => {
444 let points = generate_triangular_grid(n_target);
445 CertifiedPointSet::new_classical(
446 ClassicalConstructionKind::TriangularGrid,
447 n_target,
448 points,
449 DEFAULT_UNIT_DISTANCE_TOLERANCE,
450 )
451 }
452 ConstructionType::MoserSpindle => {
453 let points = generate_moser_spindle();
454 CertifiedPointSet::new_classical(
455 ClassicalConstructionKind::MoserSpindle,
456 n_target,
457 points,
458 DEFAULT_UNIT_DISTANCE_TOLERANCE,
459 )
460 }
461 ConstructionType::Multiquadratic(config) => {
462 CertifiedPointSet::new_algebraic_from_section2(
463 generate_native_multiquadratic_section2(config, n_target)?,
464 )?
465 }
466 })
467 }
468}
469
470fn validate_split_prime(split_prime: i64) -> Result<(), GenerationError> {
471 if split_prime <= 2 || !is_prime(split_prime) {
472 return Err(GenerationError::InvalidSplitPrime { split_prime });
473 }
474 Ok(())
475}
476
477fn validate_nonzero_usize(parameter: &'static str, value: usize) -> Result<(), GenerationError> {
478 if value == 0 {
479 return Err(GenerationError::InvalidSearchParameter {
480 parameter,
481 reason: "expected a value greater than zero",
482 });
483 }
484 Ok(())
485}
486
487fn validate_positive_f64(parameter: &'static str, value: f64) -> Result<(), GenerationError> {
488 if !value.is_finite() || value <= 0.0 {
489 return Err(GenerationError::InvalidSearchParameter {
490 parameter,
491 reason: "expected a finite value greater than zero",
492 });
493 }
494 Ok(())
495}
496
497fn validate_complete_splitting(
498 generators: &[i64],
499 split_prime: i64,
500) -> Result<(), GenerationError> {
501 generators.iter().try_for_each(|&generator| {
502 if legendre_symbol(generator, split_prime) == 1 {
503 Ok(())
504 } else {
505 Err(GenerationError::PrimeNotSplit {
506 split_prime,
507 generator,
508 })
509 }
510 })
511}
512
513fn is_prime(n: i64) -> bool {
514 if n < 2 {
515 return false;
516 }
517 if n == 2 {
518 return true;
519 }
520 if n % 2 == 0 {
521 return false;
522 }
523 !(3..)
524 .step_by(2)
525 .take_while(|d| d * d <= n)
526 .any(|d| n % d == 0)
527}
528
529fn legendre_symbol(a: i64, p: i64) -> i64 {
530 let a = a.rem_euclid(p);
531 if a == 0 {
532 return 0;
533 }
534 let value = mod_pow(a, (p - 1) / 2, p);
535 if value == p - 1 { -1 } else { value }
536}
537
538fn mod_pow(mut base: i64, exp: i64, modulus: i64) -> i64 {
539 base = base.rem_euclid(modulus);
540 std::iter::successors(Some((base, exp)), |&(base, exp)| {
541 (exp / 2 > 0).then_some(((base * base).rem_euclid(modulus), exp / 2))
542 })
543 .filter(|&(_, exp)| exp % 2 == 1)
544 .fold(1, |result, (base, _)| (result * base).rem_euclid(modulus))
545}