use super::buchberger::{groebner_basis, is_groebner_basis}; use super::flat::{Mono, Poly, lex_cmp}; use super::ideal::{Generators, GroebnerBasis, Ideal}; use super::var::StaticVar; #[test] fn test_mono_contains() { let a: Mono = [("x", 2), ("y", 1)].into(); // Lower exponent of same variable is contained assert!(a.contains(&Mono::from([("x", 1)]))); // Higher exponent of same variable is not contained assert!(!a.contains(&Mono::from([("x", 3)]))); // Identical monomial is contained assert!(a.contains(&Mono::from([("x", 2), ("y", 1)]))); // Variable absent from self is not contained assert!(!a.contains(&Mono::from([("x", 2), ("z", 1)]))); // Subset of variables with lower exponents is contained assert!(a.contains(&Mono::from([("x", 1), ("y", 1)]))); // Single variable with exact exponent is contained assert!(a.contains(&Mono::from([("x", 2)]))); // Insufficient exponent in self means not contained assert!(!Mono::::from([("x", 1)]).contains(&Mono::from([("x", 2)]))); // Missing variable in self means not contained assert!(!Mono::::from([("x", 1), ("y", 1)]).contains(&Mono::from([("x", 2)]))); assert!(!Mono::::from([("x", 1)]).contains(&Mono::from([("x", 1), ("y", 1)]))); } #[test] fn test_mono_mul() { // Same variable: exponents add let a: Mono = [("x", 2)].into(); let b: Mono = [("x", 3)].into(); assert_eq!(a * b, Mono::from([("x", 5)])); // Disjoint variables: both appear in result let a: Mono = [("x", 2)].into(); let b: Mono = [("y", 3)].into(); assert_eq!(a * b, Mono::from([("x", 2), ("y", 3)])); // Mixed: shared and disjoint variables let a: Mono = [("x", 1), ("y", 2)].into(); let b: Mono = [("y", 1), ("z", 3)].into(); assert_eq!(a * b, Mono::from([("x", 1), ("y", 3), ("z", 3)])); // Commutativity let a: Mono = [("x", 2), ("z", 1)].into(); let b: Mono = [("y", 3)].into(); assert_eq!(a.clone() * b.clone(), b * a); // Multiply by constant monomial (empty term vec = 1) let a: Mono = [("x", 4)].into(); let one: Mono = Mono { term: vec![] }; assert_eq!(a.clone() * one, a); } #[test] fn test_poly_add() { // Distinct monomials are collected as separate terms let a: Poly = [(1, [("x", 2)]), (2, [("y", 1)])].into(); let b: Poly = [(3, [("z", 1)])].into(); let expected: Poly = [(1, [("x", 2)]), (2, [("y", 1)]), (3, [("z", 1)])].into(); assert_eq!(a + b, expected); // Coefficients of matching monomials are summed let a: Poly = [(2, [("x", 1)])].into(); let b: Poly = [(3, [("x", 1)])].into(); let expected: Poly = [(5, [("x", 1)])].into(); assert_eq!(a + b, expected); // Terms that cancel sum to zero are dropped let a: Poly = [(1, [("x", 1)])].into(); let b: Poly = [(-1, [("x", 1)])].into(); let expected: Poly = Poly::default(); assert_eq!(a + b, expected); } #[test] fn test_poly_sub() { // Distinct monomials are collected as separate terms with negated rhs coefficients let a: Poly = [(3, [("x", 2)])].into(); let b: Poly = [(1, [("y", 1)])].into(); let expected: Poly = [(3, [("x", 2)]), (-1, [("y", 1)])].into(); assert_eq!(a - b, expected); // Coefficients of matching monomials are subtracted let a: Poly = [(5, [("x", 1)])].into(); let b: Poly = [(3, [("x", 1)])].into(); let expected: Poly = [(2, [("x", 1)])].into(); assert_eq!(a - b, expected); // Subtracting equal polynomials yields zero let a: Poly = [(4, [("x", 2)]), (1, [("y", 1)])].into(); let b: Poly = [(4, [("x", 2)]), (1, [("y", 1)])].into(); assert_eq!(a - b, Poly::default()); } #[test] fn test_lex_cmp() { use std::cmp::Ordering; let x2: Mono = [("x", 2)].into(); let xy: Mono = [("x", 1), ("y", 1)].into(); let y2: Mono = [("y", 2)].into(); let x: Mono = [("x", 1)].into(); let one: Mono = Mono { term: vec![] }; // x² > xy (x exponent 2 vs 1) assert_eq!(lex_cmp(&x2, &xy), Ordering::Greater); // x > y² (x has higher priority) assert_eq!(lex_cmp(&x, &y2), Ordering::Greater); // xy > y² (x present in xy but not y²) assert_eq!(lex_cmp(&xy, &y2), Ordering::Greater); // 1 < x assert_eq!(lex_cmp(&one, &x), Ordering::Less); // reflexive assert_eq!(lex_cmp(&x2, &x2), Ordering::Equal); } #[test] fn test_mono_div() { // x² / x = x let a: Mono = [("x", 2)].into(); let b: Mono = [("x", 1)].into(); assert_eq!(a.div(&b), Mono::from([("x", 1)])); // x²y / xy = x let a: Mono = [("x", 2), ("y", 1)].into(); let b: Mono = [("x", 1), ("y", 1)].into(); assert_eq!(a.div(&b), Mono::from([("x", 1)])); // x²y / y = x² let a: Mono = [("x", 2), ("y", 1)].into(); let b: Mono = [("y", 1)].into(); assert_eq!(a.div(&b), Mono::from([("x", 2)])); // x / x = 1 let a: Mono = [("x", 1)].into(); let b: Mono = [("x", 1)].into(); assert_eq!(a.div(&b), Mono { term: vec![] }); } #[test] fn test_mono_lcm() { // lcm(x², xy) = x²y let a: Mono = [("x", 2)].into(); let b: Mono = [("x", 1), ("y", 1)].into(); assert_eq!(a.lcm(&b), Mono::from([("x", 2), ("y", 1)])); // lcm(x, y) = xy (disjoint) let a: Mono = [("x", 1)].into(); let b: Mono = [("y", 1)].into(); assert_eq!(a.lcm(&b), Mono::from([("x", 1), ("y", 1)])); // lcm(x², x²) = x² (identical) let a: Mono = [("x", 2)].into(); assert_eq!(a.lcm(&a.clone()), a); } #[test] fn test_s_poly() { // S(x², xy) = 0: S-poly of two monomials always vanishes let f: Poly = [(1, [("x", 2)])].into(); let g: Poly = [(1, [("x", 1), ("y", 1)])].into(); assert!(f.s_poly(&g).is_zero()); // f = x² + y, g = xy + z (lex: LM(f)=x², LM(g)=xy) // L = x²y, t_f = y, t_g = x, d = gcd(1,1) = 1 // S = y*(x²+y) - x*(xy+z) = x²y + y² - x²y - xz = y² - xz let f: Poly = [ (1i32, Mono::from([("x", 2u32)])), (1i32, Mono::from([("y", 1u32)])), ] .into_iter() .collect(); let g: Poly = [ (1i32, Mono::from([("x", 1u32), ("y", 1u32)])), (1i32, Mono::from([("z", 1u32)])), ] .into_iter() .collect(); let expected: Poly = [ (1i32, Mono::from([("y", 2u32)])), (-1i32, Mono::from([("x", 1u32), ("z", 1u32)])), ] .into_iter() .collect(); assert_eq!(f.s_poly(&g), expected); // f = 2x + y, g = 3x + z (same LM=x, d=gcd(2,3)=1) // S = (3/1)*f - (2/1)*g = 3(2x+y) - 2(3x+z) = 3y - 2z let f: Poly = [(2, [("x", 1)]), (1, [("y", 1)])].into(); let g: Poly = [(3, [("x", 1)]), (1, [("z", 1)])].into(); let expected: Poly = [(3, [("y", 1)]), (-2, [("z", 1)])].into(); assert_eq!(f.s_poly(&g), expected); // f = 4x, g = 6x (d = gcd(4,6) = 2) // S = (6/2)*x*4x - (4/2)*x*6x = 3*4x - 2*6x = 12x - 12x = 0 let f: Poly = [(4, [("x", 1)])].into(); let g: Poly = [(6, [("x", 1)])].into(); assert!(f.s_poly(&g).is_zero()); } fn make_const_poly(c: i32) -> Poly { Poly { mono: [(Mono { term: vec![] }, c)].into_iter().collect(), } } fn verify_div_rem( f: Poly, g: &Poly, d: u32, q: Poly, r: Poly, ) { // lc(g)^d * f == q * g + r let (_, lc_g) = g.leading_term_lex().unwrap(); let lhs = lc_g.pow(d) * f; let rhs = q * g.clone() + r; assert_eq!(lhs, rhs); } #[test] fn test_div_rem() { // x³ / x² = x, r=0, d=0 let f: Poly = [(1, [("x", 3)])].into(); let g: Poly = [(1, [("x", 2)])].into(); let expected_q: Poly = [(1, [("x", 1)])].into(); let (d, q, r) = f.clone().div_rem(&g); assert_eq!(q, expected_q); assert!(r.is_zero()); verify_div_rem(f, &g, d, q, r); // (x³ + x²y) / x² = x + y, r=0 // f = x²(x + y), g = x² let f: Poly = [ (1i32, Mono::from([("x", 3u32)])), (1i32, Mono::from([("x", 2u32), ("y", 1u32)])), ] .into_iter() .collect(); let g: Poly = [(1, [("x", 2)])].into(); let expected_q: Poly = [(1, [("x", 1)]), (1, [("y", 1)])].into(); let (d, q, r) = f.clone().div_rem(&g); assert_eq!(q, expected_q); assert!(r.is_zero()); verify_div_rem(f, &g, d, q, r); // (x³ + y) / x² = x, r=y // LT(x³) divisible by x², LT(y) is not let f: Poly = [(1, [("x", 3)]), (1, [("y", 1)])].into(); let g: Poly = [(1, [("x", 2)])].into(); let expected_q: Poly = [(1, [("x", 1)])].into(); let expected_r: Poly = [(1, [("y", 1)])].into(); let (d, q, r) = f.clone().div_rem(&g); assert_eq!(q, expected_q); assert_eq!(r, expected_r); verify_div_rem(f, &g, d, q, r); // 3x² / (2x): 2 ∤ 3, needs pseudo-division // 2¹ · 3x² = 3x · 2x => d=1, q=3x, r=0 let f: Poly = [(3, [("x", 2)])].into(); let g: Poly = [(2, [("x", 1)])].into(); let expected_q: Poly = [(3, [("x", 1)])].into(); let (d, q, r) = f.clone().div_rem(&g); assert_eq!(d, 1); assert_eq!(q, expected_q); assert!(r.is_zero()); verify_div_rem(f, &g, d, q, r); // 6xy / 2 = 3xy, r=0, d=0 let f: Poly = [(6, [("x", 1), ("y", 1)])].into(); let g = make_const_poly(2); let expected_q: Poly = [(3, [("x", 1), ("y", 1)])].into(); let (d, q, r) = f.clone().div_rem(&g); assert_eq!(d, 0); assert_eq!(q, expected_q); assert!(r.is_zero()); verify_div_rem(f, &g, d, q, r); // (x² + xy) / (x + y) = x, r=0 // f = x(x + y), g = x + y let f: Poly = [ (1i32, Mono::from([("x", 2u32)])), (1i32, Mono::from([("x", 1u32), ("y", 1u32)])), ] .into_iter() .collect(); let g: Poly = [(1, [("x", 1)]), (1, [("y", 1)])].into(); let expected_q: Poly = [(1, [("x", 1)])].into(); let (d, q, r) = f.clone().div_rem(&g); assert_eq!(q, expected_q); assert!(r.is_zero()); verify_div_rem(f, &g, d, q, r); } #[test] fn test_groebner() { // Ideal (x²) — already a Gröbner basis let f: Poly = [(1, [("x", 2)])].into(); let _basis = groebner_basis(vec![f]); // Ideal (x³ - x², x² - x): gcd is x² - x let f: Poly = [ (1i32, Mono::from([("x", 3u32)])), (-1i32, Mono::from([("x", 2u32)])), ] .into_iter() .collect(); let g: Poly = [ (1i32, Mono::from([("x", 2u32)])), (-1i32, Mono::from([("x", 1u32)])), ] .into_iter() .collect(); let basis = groebner_basis(vec![f, g]); assert!(is_groebner_basis(&basis)); // Classic example: I = (x²y - x, xy² - y) let f: Poly = [ (1i32, Mono::from([("x", 2u32), ("y", 1u32)])), (-1i32, Mono::from([("x", 1u32)])), ] .into_iter() .collect(); let g: Poly = [ (1i32, Mono::from([("x", 1u32), ("y", 2u32)])), (-1i32, Mono::from([("y", 1u32)])), ] .into_iter() .collect(); let basis = groebner_basis(vec![f, g]); assert!(is_groebner_basis(&basis)); } #[test] fn test_is_groebner_basis() { // {x} is a GB: only one element, no pairs to check. let f: Poly = [(1, [("x", 1)])].into(); assert!(is_groebner_basis(&[f])); // {x, y} is a GB: S(x, y) = y*x - x*y = 0. let x: Poly = [(1, [("x", 1)])].into(); let y: Poly = [(1, [("y", 1)])].into(); assert!(is_groebner_basis(&[x, y])); // {x² + y, xy} is NOT a GB: // S(x²+y, xy) = y*(x²+y) - x*(xy) = y² ≠ 0 mod the set. let f: Poly = [ (1i32, Mono::from([("x", 2u32)])), (1i32, Mono::from([("y", 1u32)])), ] .into_iter() .collect(); let g: Poly = [(1i32, Mono::from([("x", 1u32), ("y", 1u32)]))] .into_iter() .collect(); assert!(!is_groebner_basis(&[f, g])); // After running groebner_basis, the result must always pass. let f: Poly = [ (1i32, Mono::from([("x", 2u32)])), (1i32, Mono::from([("y", 1u32)])), ] .into_iter() .collect(); let g: Poly = [(1i32, Mono::from([("x", 1u32), ("y", 1u32)]))] .into_iter() .collect(); let basis = groebner_basis(vec![f, g]); assert!(is_groebner_basis(&basis)); } #[test] fn test_ideal() { // Construction from Vec and iterator let f: Poly = [(1, [("x", 2)])].into(); let g: Poly = [(1, [("y", 1)])].into(); let ideal = Ideal::new(vec![f.clone(), g.clone()]); assert_eq!(ideal.generators().len(), 2); let ideal: Ideal = [f.clone(), g.clone()].into_iter().collect(); assert_eq!(ideal.generators().len(), 2); // Construction via From / .into() let ideal: Ideal = vec![f.clone(), g.clone()].into(); assert_eq!(ideal.generators().len(), 2); // Display: let ideal = Ideal::new(vec![f, g]); assert_eq!(ideal.to_string(), ""); // groebner_basis transitions state and result satisfies the criterion let f: Poly = [ (1i32, Mono::from([("x", 2u32), ("y", 1u32)])), (-1i32, Mono::from([("x", 1u32)])), ] .into_iter() .collect(); let g: Poly = [ (1i32, Mono::from([("x", 1u32), ("y", 2u32)])), (-1i32, Mono::from([("y", 1u32)])), ] .into_iter() .collect(); let ideal: Ideal = [f, g].into_iter().collect(); let gb: Ideal = ideal.groebner_basis(); assert!(is_groebner_basis(gb.generators())); } #[test] fn test_groebner_sagemath() { // I = (x³ - 2xy, x²y - 2y² + x) ⊆ k[x, y] // grobner basis: {4y³, x - 2y²} // f1 = x³ - 2xy let f1: Poly = [ (1i32, Mono::from([("x", 3u32)])), (-2i32, Mono::from([("x", 1u32), ("y", 1u32)])), ] .into_iter() .collect(); // f2 = x²y - 2y² + x let f2: Poly = [ (1i32, Mono::from([("x", 2u32), ("y", 1u32)])), (-2i32, Mono::from([("y", 2u32)])), (1i32, Mono::from([("x", 1u32)])), ] .into_iter() .collect(); let gb = Ideal::new(vec![f1.clone(), f2.clone()]).groebner_basis(); assert!(is_groebner_basis(gb.generators())); assert_eq!(gb.generators().len(), 2); // -x + 2y² let neg_x_plus_2y2: Poly = [ (-1i32, Mono::from([("x", 1u32)])), (2i32, Mono::from([("y", 2u32)])), ] .into_iter() .collect(); // -4y³ let neg_4y3: Poly = [(-4i32, Mono::from([("y", 3u32)]))].into_iter().collect(); let expected = [neg_x_plus_2y2, neg_4y3]; for e in &expected { assert!( gb.generators() .iter() .any(|a| *a == *e || *a == -1i32 * e.clone()) ); } }