465 lines
15 KiB
Rust
465 lines
15 KiB
Rust
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<StaticVar> = [("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::<StaticVar>::from([("x", 1)]).contains(&Mono::from([("x", 2)])));
|
|
|
|
// Missing variable in self means not contained
|
|
assert!(!Mono::<StaticVar>::from([("x", 1), ("y", 1)]).contains(&Mono::from([("x", 2)])));
|
|
assert!(!Mono::<StaticVar>::from([("x", 1)]).contains(&Mono::from([("x", 1), ("y", 1)])));
|
|
}
|
|
|
|
#[test]
|
|
fn test_mono_mul() {
|
|
// Same variable: exponents add
|
|
let a: Mono<StaticVar> = [("x", 2)].into();
|
|
let b: Mono<StaticVar> = [("x", 3)].into();
|
|
assert_eq!(a * b, Mono::from([("x", 5)]));
|
|
|
|
// Disjoint variables: both appear in result
|
|
let a: Mono<StaticVar> = [("x", 2)].into();
|
|
let b: Mono<StaticVar> = [("y", 3)].into();
|
|
assert_eq!(a * b, Mono::from([("x", 2), ("y", 3)]));
|
|
|
|
// Mixed: shared and disjoint variables
|
|
let a: Mono<StaticVar> = [("x", 1), ("y", 2)].into();
|
|
let b: Mono<StaticVar> = [("y", 1), ("z", 3)].into();
|
|
assert_eq!(a * b, Mono::from([("x", 1), ("y", 3), ("z", 3)]));
|
|
|
|
// Commutativity
|
|
let a: Mono<StaticVar> = [("x", 2), ("z", 1)].into();
|
|
let b: Mono<StaticVar> = [("y", 3)].into();
|
|
assert_eq!(a.clone() * b.clone(), b * a);
|
|
|
|
// Multiply by constant monomial (empty term vec = 1)
|
|
let a: Mono<StaticVar> = [("x", 4)].into();
|
|
let one: Mono<StaticVar> = Mono { term: vec![] };
|
|
assert_eq!(a.clone() * one, a);
|
|
}
|
|
|
|
#[test]
|
|
fn test_poly_add() {
|
|
// Distinct monomials are collected as separate terms
|
|
let a: Poly<StaticVar> = [(1, [("x", 2)]), (2, [("y", 1)])].into();
|
|
let b: Poly<StaticVar> = [(3, [("z", 1)])].into();
|
|
let expected: Poly<StaticVar> = [(1, [("x", 2)]), (2, [("y", 1)]), (3, [("z", 1)])].into();
|
|
assert_eq!(a + b, expected);
|
|
|
|
// Coefficients of matching monomials are summed
|
|
let a: Poly<StaticVar> = [(2, [("x", 1)])].into();
|
|
let b: Poly<StaticVar> = [(3, [("x", 1)])].into();
|
|
let expected: Poly<StaticVar> = [(5, [("x", 1)])].into();
|
|
assert_eq!(a + b, expected);
|
|
|
|
// Terms that cancel sum to zero are dropped
|
|
let a: Poly<StaticVar> = [(1, [("x", 1)])].into();
|
|
let b: Poly<StaticVar> = [(-1, [("x", 1)])].into();
|
|
let expected: Poly<StaticVar> = 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<StaticVar> = [(3, [("x", 2)])].into();
|
|
let b: Poly<StaticVar> = [(1, [("y", 1)])].into();
|
|
let expected: Poly<StaticVar> = [(3, [("x", 2)]), (-1, [("y", 1)])].into();
|
|
assert_eq!(a - b, expected);
|
|
|
|
// Coefficients of matching monomials are subtracted
|
|
let a: Poly<StaticVar> = [(5, [("x", 1)])].into();
|
|
let b: Poly<StaticVar> = [(3, [("x", 1)])].into();
|
|
let expected: Poly<StaticVar> = [(2, [("x", 1)])].into();
|
|
assert_eq!(a - b, expected);
|
|
|
|
// Subtracting equal polynomials yields zero
|
|
let a: Poly<StaticVar> = [(4, [("x", 2)]), (1, [("y", 1)])].into();
|
|
let b: Poly<StaticVar> = [(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<StaticVar> = [("x", 2)].into();
|
|
let xy: Mono<StaticVar> = [("x", 1), ("y", 1)].into();
|
|
let y2: Mono<StaticVar> = [("y", 2)].into();
|
|
let x: Mono<StaticVar> = [("x", 1)].into();
|
|
let one: Mono<StaticVar> = 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<StaticVar> = [("x", 2)].into();
|
|
let b: Mono<StaticVar> = [("x", 1)].into();
|
|
assert_eq!(a.div(&b), Mono::from([("x", 1)]));
|
|
|
|
// x²y / xy = x
|
|
let a: Mono<StaticVar> = [("x", 2), ("y", 1)].into();
|
|
let b: Mono<StaticVar> = [("x", 1), ("y", 1)].into();
|
|
assert_eq!(a.div(&b), Mono::from([("x", 1)]));
|
|
|
|
// x²y / y = x²
|
|
let a: Mono<StaticVar> = [("x", 2), ("y", 1)].into();
|
|
let b: Mono<StaticVar> = [("y", 1)].into();
|
|
assert_eq!(a.div(&b), Mono::from([("x", 2)]));
|
|
|
|
// x / x = 1
|
|
let a: Mono<StaticVar> = [("x", 1)].into();
|
|
let b: Mono<StaticVar> = [("x", 1)].into();
|
|
assert_eq!(a.div(&b), Mono { term: vec![] });
|
|
}
|
|
|
|
#[test]
|
|
fn test_mono_lcm() {
|
|
// lcm(x², xy) = x²y
|
|
let a: Mono<StaticVar> = [("x", 2)].into();
|
|
let b: Mono<StaticVar> = [("x", 1), ("y", 1)].into();
|
|
assert_eq!(a.lcm(&b), Mono::from([("x", 2), ("y", 1)]));
|
|
|
|
// lcm(x, y) = xy (disjoint)
|
|
let a: Mono<StaticVar> = [("x", 1)].into();
|
|
let b: Mono<StaticVar> = [("y", 1)].into();
|
|
assert_eq!(a.lcm(&b), Mono::from([("x", 1), ("y", 1)]));
|
|
|
|
// lcm(x², x²) = x² (identical)
|
|
let a: Mono<StaticVar> = [("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<StaticVar> = [(1, [("x", 2)])].into();
|
|
let g: Poly<StaticVar> = [(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<StaticVar> = [
|
|
(1i32, Mono::from([("x", 2u32)])),
|
|
(1i32, Mono::from([("y", 1u32)])),
|
|
]
|
|
.into_iter()
|
|
.collect();
|
|
let g: Poly<StaticVar> = [
|
|
(1i32, Mono::from([("x", 1u32), ("y", 1u32)])),
|
|
(1i32, Mono::from([("z", 1u32)])),
|
|
]
|
|
.into_iter()
|
|
.collect();
|
|
let expected: Poly<StaticVar> = [
|
|
(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<StaticVar> = [(2, [("x", 1)]), (1, [("y", 1)])].into();
|
|
let g: Poly<StaticVar> = [(3, [("x", 1)]), (1, [("z", 1)])].into();
|
|
let expected: Poly<StaticVar> = [(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<StaticVar> = [(4, [("x", 1)])].into();
|
|
let g: Poly<StaticVar> = [(6, [("x", 1)])].into();
|
|
assert!(f.s_poly(&g).is_zero());
|
|
}
|
|
|
|
fn make_const_poly(c: i32) -> Poly<StaticVar> {
|
|
Poly {
|
|
mono: [(Mono { term: vec![] }, c)].into_iter().collect(),
|
|
}
|
|
}
|
|
|
|
fn verify_div_rem(
|
|
f: Poly<StaticVar>,
|
|
g: &Poly<StaticVar>,
|
|
d: u32,
|
|
q: Poly<StaticVar>,
|
|
r: Poly<StaticVar>,
|
|
) {
|
|
// 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<StaticVar> = [(1, [("x", 3)])].into();
|
|
let g: Poly<StaticVar> = [(1, [("x", 2)])].into();
|
|
let expected_q: Poly<StaticVar> = [(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<StaticVar> = [
|
|
(1i32, Mono::from([("x", 3u32)])),
|
|
(1i32, Mono::from([("x", 2u32), ("y", 1u32)])),
|
|
]
|
|
.into_iter()
|
|
.collect();
|
|
let g: Poly<StaticVar> = [(1, [("x", 2)])].into();
|
|
let expected_q: Poly<StaticVar> = [(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<StaticVar> = [(1, [("x", 3)]), (1, [("y", 1)])].into();
|
|
let g: Poly<StaticVar> = [(1, [("x", 2)])].into();
|
|
let expected_q: Poly<StaticVar> = [(1, [("x", 1)])].into();
|
|
let expected_r: Poly<StaticVar> = [(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<StaticVar> = [(3, [("x", 2)])].into();
|
|
let g: Poly<StaticVar> = [(2, [("x", 1)])].into();
|
|
let expected_q: Poly<StaticVar> = [(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<StaticVar> = [(6, [("x", 1), ("y", 1)])].into();
|
|
let g = make_const_poly(2);
|
|
let expected_q: Poly<StaticVar> = [(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<StaticVar> = [
|
|
(1i32, Mono::from([("x", 2u32)])),
|
|
(1i32, Mono::from([("x", 1u32), ("y", 1u32)])),
|
|
]
|
|
.into_iter()
|
|
.collect();
|
|
let g: Poly<StaticVar> = [(1, [("x", 1)]), (1, [("y", 1)])].into();
|
|
let expected_q: Poly<StaticVar> = [(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<StaticVar> = [(1, [("x", 2)])].into();
|
|
let _basis = groebner_basis(vec![f]);
|
|
|
|
// Ideal (x³ - x², x² - x): gcd is x² - x
|
|
let f: Poly<StaticVar> = [
|
|
(1i32, Mono::from([("x", 3u32)])),
|
|
(-1i32, Mono::from([("x", 2u32)])),
|
|
]
|
|
.into_iter()
|
|
.collect();
|
|
let g: Poly<StaticVar> = [
|
|
(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<StaticVar> = [
|
|
(1i32, Mono::from([("x", 2u32), ("y", 1u32)])),
|
|
(-1i32, Mono::from([("x", 1u32)])),
|
|
]
|
|
.into_iter()
|
|
.collect();
|
|
let g: Poly<StaticVar> = [
|
|
(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<StaticVar> = [(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<StaticVar> = [(1, [("x", 1)])].into();
|
|
let y: Poly<StaticVar> = [(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<StaticVar> = [
|
|
(1i32, Mono::from([("x", 2u32)])),
|
|
(1i32, Mono::from([("y", 1u32)])),
|
|
]
|
|
.into_iter()
|
|
.collect();
|
|
let g: Poly<StaticVar> = [(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<StaticVar> = [
|
|
(1i32, Mono::from([("x", 2u32)])),
|
|
(1i32, Mono::from([("y", 1u32)])),
|
|
]
|
|
.into_iter()
|
|
.collect();
|
|
let g: Poly<StaticVar> = [(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<StaticVar> = [(1, [("x", 2)])].into();
|
|
let g: Poly<StaticVar> = [(1, [("y", 1)])].into();
|
|
let ideal = Ideal::new(vec![f.clone(), g.clone()]);
|
|
assert_eq!(ideal.generators().len(), 2);
|
|
let ideal: Ideal<StaticVar, Generators> = [f.clone(), g.clone()].into_iter().collect();
|
|
assert_eq!(ideal.generators().len(), 2);
|
|
|
|
// Construction via From / .into()
|
|
let ideal: Ideal<StaticVar, Generators> = vec![f.clone(), g.clone()].into();
|
|
assert_eq!(ideal.generators().len(), 2);
|
|
|
|
// Display: <x², y>
|
|
let ideal = Ideal::new(vec![f, g]);
|
|
assert_eq!(ideal.to_string(), "<x\u{00B2}, y>");
|
|
|
|
// groebner_basis transitions state and result satisfies the criterion
|
|
let f: Poly<StaticVar> = [
|
|
(1i32, Mono::from([("x", 2u32), ("y", 1u32)])),
|
|
(-1i32, Mono::from([("x", 1u32)])),
|
|
]
|
|
.into_iter()
|
|
.collect();
|
|
let g: Poly<StaticVar> = [
|
|
(1i32, Mono::from([("x", 1u32), ("y", 2u32)])),
|
|
(-1i32, Mono::from([("y", 1u32)])),
|
|
]
|
|
.into_iter()
|
|
.collect();
|
|
let ideal: Ideal<StaticVar, Generators> = [f, g].into_iter().collect();
|
|
let gb: Ideal<StaticVar, GroebnerBasis> = 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<StaticVar> = [
|
|
(1i32, Mono::from([("x", 3u32)])),
|
|
(-2i32, Mono::from([("x", 1u32), ("y", 1u32)])),
|
|
]
|
|
.into_iter()
|
|
.collect();
|
|
|
|
// f2 = x²y - 2y² + x
|
|
let f2: Poly<StaticVar> = [
|
|
(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<StaticVar> = [
|
|
(-1i32, Mono::from([("x", 1u32)])),
|
|
(2i32, Mono::from([("y", 2u32)])),
|
|
]
|
|
.into_iter()
|
|
.collect();
|
|
|
|
// -4y³
|
|
let neg_4y3: Poly<StaticVar> = [(-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())
|
|
);
|
|
}
|
|
}
|