1#![allow(clippy::needless_range_loop)] #[allow(unused_imports)]
28use crate::prelude::*;
29use num_rational::BigRational;
30use num_traits::Zero;
31
32pub type Monomial = Vec<u32>;
34
35#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct Term {
38 pub coeff: BigRational,
40 pub monomial: Monomial,
42}
43
44impl Term {
45 pub fn new(coeff: BigRational, monomial: Monomial) -> Self {
47 Self { coeff, monomial }
48 }
49
50 pub fn constant(c: BigRational) -> Self {
52 Self {
53 coeff: c,
54 monomial: Vec::new(),
55 }
56 }
57}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct Polynomial {
62 pub terms: Vec<Term>,
64}
65
66impl Polynomial {
67 pub fn zero() -> Self {
69 Self { terms: Vec::new() }
70 }
71
72 pub fn is_zero(&self) -> bool {
74 self.terms.is_empty()
75 }
76
77 pub fn leading_monomial(&self) -> Option<&Monomial> {
79 self.terms.first().map(|t| &t.monomial)
80 }
81
82 pub fn leading_term(&self) -> Option<&Term> {
84 self.terms.first()
85 }
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90pub enum MonomialOrder {
91 Lex,
93 DegRevLex,
95 DegLex,
97}
98
99impl MonomialOrder {
100 pub fn compare(&self, a: &Monomial, b: &Monomial) -> core::cmp::Ordering {
102 match self {
103 MonomialOrder::Lex => self.lex_compare(a, b),
104 MonomialOrder::DegRevLex => self.deg_revlex_compare(a, b),
105 MonomialOrder::DegLex => self.deg_lex_compare(a, b),
106 }
107 }
108
109 fn lex_compare(&self, a: &Monomial, b: &Monomial) -> core::cmp::Ordering {
110 let max_len = a.len().max(b.len());
111 for i in 0..max_len {
112 let a_exp = a.get(i).copied().unwrap_or(0);
113 let b_exp = b.get(i).copied().unwrap_or(0);
114 match a_exp.cmp(&b_exp) {
115 core::cmp::Ordering::Equal => continue,
116 other => return other,
117 }
118 }
119 core::cmp::Ordering::Equal
120 }
121
122 fn deg_revlex_compare(&self, a: &Monomial, b: &Monomial) -> core::cmp::Ordering {
123 let a_deg: u32 = a.iter().sum();
124 let b_deg: u32 = b.iter().sum();
125
126 match a_deg.cmp(&b_deg) {
127 core::cmp::Ordering::Equal => {
128 let max_len = a.len().max(b.len());
130 for i in (0..max_len).rev() {
131 let a_exp = a.get(i).copied().unwrap_or(0);
132 let b_exp = b.get(i).copied().unwrap_or(0);
133 match a_exp.cmp(&b_exp) {
134 core::cmp::Ordering::Equal => continue,
135 other => return other,
136 }
137 }
138 core::cmp::Ordering::Equal
139 }
140 other => other,
141 }
142 }
143
144 fn deg_lex_compare(&self, a: &Monomial, b: &Monomial) -> core::cmp::Ordering {
145 let a_deg: u32 = a.iter().sum();
146 let b_deg: u32 = b.iter().sum();
147
148 match a_deg.cmp(&b_deg) {
149 core::cmp::Ordering::Equal => self.lex_compare(a, b),
150 other => other,
151 }
152 }
153}
154
155#[derive(Debug, Clone)]
157pub struct CriticalPair {
158 pub poly1: usize,
160 pub poly2: usize,
162 pub lcm: Monomial,
164}
165
166#[derive(Debug, Clone)]
168pub struct F4Config {
169 pub order: MonomialOrder,
171 pub max_iterations: u32,
173 pub optimize_matrix: bool,
175}
176
177impl Default for F4Config {
178 fn default() -> Self {
179 Self {
180 order: MonomialOrder::DegRevLex,
181 max_iterations: 1000,
182 optimize_matrix: true,
183 }
184 }
185}
186
187#[derive(Debug, Clone, Default)]
189pub struct F4Stats {
190 pub iterations: u64,
192 pub pairs_processed: u64,
194 pub matrix_reductions: u64,
196 pub basis_size: u64,
198 pub time_us: u64,
200}
201
202pub struct F4Algorithm {
204 config: F4Config,
205 stats: F4Stats,
206}
207
208impl F4Algorithm {
209 pub fn new() -> Self {
211 Self::with_config(F4Config::default())
212 }
213
214 pub fn with_config(config: F4Config) -> Self {
216 Self {
217 config,
218 stats: F4Stats::default(),
219 }
220 }
221
222 pub fn stats(&self) -> &F4Stats {
224 &self.stats
225 }
226
227 pub fn compute_basis(&mut self, generators: Vec<Polynomial>) -> Vec<Polynomial> {
229 #[cfg(feature = "std")]
230 let start = std::time::Instant::now();
231
232 if generators.is_empty() {
233 return Vec::new();
234 }
235
236 let mut basis = generators;
238 let mut critical_pairs = self.initialize_pairs(&basis);
239
240 for iteration in 0..self.config.max_iterations {
241 self.stats.iterations += 1;
242
243 if critical_pairs.is_empty() {
244 break;
245 }
246
247 let pairs_to_reduce = self.select_pairs(&mut critical_pairs);
249 if pairs_to_reduce.is_empty() {
250 break;
251 }
252
253 self.stats.pairs_processed += pairs_to_reduce.len() as u64;
254
255 let monomials = self.symbolic_preprocessing(&basis, &pairs_to_reduce);
257
258 let matrix = self.build_matrix(&basis, &pairs_to_reduce, &monomials);
260
261 let reduced = self.reduce_matrix(matrix);
263 self.stats.matrix_reductions += 1;
264
265 let new_polys = self.extract_polynomials(reduced, &monomials);
267
268 for poly in new_polys {
270 if !poly.is_zero() {
271 for i in 0..basis.len() {
273 if let Some(pair) = self.make_pair(i, basis.len(), &basis, &poly) {
274 critical_pairs.push(pair);
275 }
276 }
277
278 basis.push(poly);
279 }
280 }
281
282 if iteration % 10 == 0 {
283 basis = self.interreduce(basis);
285 }
286 }
287
288 basis = self.interreduce(basis);
290
291 self.stats.basis_size = basis.len() as u64;
292 #[cfg(feature = "std")]
293 {
294 self.stats.time_us += start.elapsed().as_micros() as u64;
295 }
296
297 basis
298 }
299
300 fn initialize_pairs(&self, basis: &[Polynomial]) -> Vec<CriticalPair> {
302 let mut pairs = Vec::new();
303
304 for i in 0..basis.len() {
305 for j in (i + 1)..basis.len() {
306 if let Some(pair) = self.make_pair(i, j, basis, &basis[j]) {
307 pairs.push(pair);
308 }
309 }
310 }
311
312 pairs
313 }
314
315 fn make_pair(
317 &self,
318 i: usize,
319 j: usize,
320 basis: &[Polynomial],
321 poly_j: &Polynomial,
322 ) -> Option<CriticalPair> {
323 let lm_i = basis.get(i)?.leading_monomial()?;
324 let lm_j = poly_j.leading_monomial()?;
325
326 let lcm = self.lcm_monomial(lm_i, lm_j);
327
328 Some(CriticalPair {
329 poly1: i,
330 poly2: j,
331 lcm,
332 })
333 }
334
335 fn lcm_monomial(&self, a: &Monomial, b: &Monomial) -> Monomial {
337 let max_len = a.len().max(b.len());
338 let mut lcm = vec![0; max_len];
339
340 for i in 0..max_len {
341 let a_exp = a.get(i).copied().unwrap_or(0);
342 let b_exp = b.get(i).copied().unwrap_or(0);
343 lcm[i] = a_exp.max(b_exp);
344 }
345
346 lcm
347 }
348
349 fn select_pairs(&self, pairs: &mut Vec<CriticalPair>) -> Vec<CriticalPair> {
351 if pairs.is_empty() {
352 return Vec::new();
353 }
354
355 let count = pairs.len().min(10);
358 pairs.drain(0..count).collect()
359 }
360
361 fn symbolic_preprocessing(
363 &self,
364 _basis: &[Polynomial],
365 _pairs: &[CriticalPair],
366 ) -> Vec<Monomial> {
367 Vec::new()
370 }
371
372 fn build_matrix(
374 &self,
375 _basis: &[Polynomial],
376 _pairs: &[CriticalPair],
377 _monomials: &[Monomial],
378 ) -> Vec<Vec<BigRational>> {
379 Vec::new()
382 }
383
384 fn reduce_matrix(&self, mut matrix: Vec<Vec<BigRational>>) -> Vec<Vec<BigRational>> {
386 if matrix.is_empty() {
387 return matrix;
388 }
389
390 let rows = matrix.len();
391 let cols = matrix.first().map(|r| r.len()).unwrap_or(0);
392
393 let mut pivot_row = 0;
394
395 for col in 0..cols {
396 let mut pivot = None;
398 for row in pivot_row..rows {
399 if !matrix[row][col].is_zero() {
400 pivot = Some(row);
401 break;
402 }
403 }
404
405 let Some(pivot_idx) = pivot else {
406 continue;
407 };
408
409 if pivot_idx != pivot_row {
411 matrix.swap(pivot_row, pivot_idx);
412 }
413
414 let pivot_val = matrix[pivot_row][col].clone();
416 if !pivot_val.is_zero() {
417 for entry in &mut matrix[pivot_row] {
418 *entry = entry.clone() / &pivot_val;
419 }
420 }
421
422 for row in 0..rows {
424 if row != pivot_row {
425 let factor = matrix[row][col].clone();
426 if !factor.is_zero() {
427 for c in 0..cols {
428 let sub_val = &matrix[pivot_row][c] * &factor;
429 matrix[row][c] = &matrix[row][c] - &sub_val;
430 }
431 }
432 }
433 }
434
435 pivot_row += 1;
436 if pivot_row >= rows {
437 break;
438 }
439 }
440
441 matrix
442 }
443
444 fn extract_polynomials(
446 &self,
447 _matrix: Vec<Vec<BigRational>>,
448 _monomials: &[Monomial],
449 ) -> Vec<Polynomial> {
450 Vec::new()
453 }
454
455 fn interreduce(&self, mut basis: Vec<Polynomial>) -> Vec<Polynomial> {
457 basis.retain(|p| !p.is_zero());
459 basis
460 }
461}
462
463impl Default for F4Algorithm {
464 fn default() -> Self {
465 Self::new()
466 }
467}
468
469#[cfg(test)]
470mod tests {
471 use super::*;
472 use num_bigint::BigInt;
473 use num_rational::BigRational;
474 use num_traits::One;
475
476 #[test]
477 fn test_f4_creation() {
478 let f4 = F4Algorithm::new();
479 assert_eq!(f4.stats().iterations, 0);
480 }
481
482 #[test]
483 fn test_monomial_order_lex() {
484 let order = MonomialOrder::Lex;
485
486 let m1 = vec![2, 1];
487 let m2 = vec![1, 2];
488
489 assert_eq!(order.compare(&m1, &m2), core::cmp::Ordering::Greater);
491 }
492
493 #[test]
494 fn test_monomial_order_degrevlex() {
495 let order = MonomialOrder::DegRevLex;
496
497 let m1 = vec![2, 1]; let m2 = vec![1, 1]; assert_eq!(order.compare(&m1, &m2), core::cmp::Ordering::Greater);
502 }
503
504 #[test]
505 fn test_lcm_monomial() {
506 let f4 = F4Algorithm::new();
507
508 let m1 = vec![2, 1, 0];
509 let m2 = vec![1, 3, 2];
510
511 let lcm = f4.lcm_monomial(&m1, &m2);
512
513 assert_eq!(lcm, vec![2, 3, 2]);
514 }
515
516 #[test]
517 fn test_polynomial_zero() {
518 let poly = Polynomial::zero();
519 assert!(poly.is_zero());
520 assert_eq!(poly.leading_monomial(), None);
521 }
522
523 #[test]
524 fn test_polynomial_leading() {
525 let term = Term::new(BigRational::from_integer(BigInt::from(1)), vec![1, 2]);
526 let poly = Polynomial {
527 terms: vec![term.clone()],
528 };
529
530 assert_eq!(poly.leading_monomial(), Some(&vec![1, 2]));
531 assert_eq!(poly.leading_term(), Some(&term));
532 }
533
534 #[test]
535 fn test_compute_basis_empty() {
536 let mut f4 = F4Algorithm::new();
537 let basis = f4.compute_basis(Vec::new());
538
539 assert_eq!(basis.len(), 0);
540 }
541
542 #[test]
543 fn test_gaussian_elimination() {
544 let f4 = F4Algorithm::new();
545
546 let matrix = vec![
548 vec![
549 BigRational::from_integer(BigInt::from(2)),
550 BigRational::from_integer(BigInt::from(4)),
551 ],
552 vec![
553 BigRational::from_integer(BigInt::from(1)),
554 BigRational::from_integer(BigInt::from(3)),
555 ],
556 ];
557
558 let reduced = f4.reduce_matrix(matrix);
559
560 assert_eq!(reduced.len(), 2);
562 }
563
564 #[test]
565 fn test_critical_pair() {
566 let f4 = F4Algorithm::new();
567
568 let poly1 = Polynomial {
569 terms: vec![Term::new(
570 BigRational::from_integer(BigInt::one()),
571 vec![2, 0],
572 )],
573 };
574
575 let poly2 = Polynomial {
576 terms: vec![Term::new(
577 BigRational::from_integer(BigInt::one()),
578 vec![0, 2],
579 )],
580 };
581
582 let basis = vec![poly1, poly2.clone()];
583 let pair = f4.make_pair(0, 1, &basis, &poly2);
584
585 assert!(pair.is_some());
586 if let Some(p) = pair {
587 assert_eq!(p.lcm, vec![2, 2]);
588 }
589 }
590}