use crate::polynomial::{Monomial, MonomialOrder, Polynomial};
use num_rational::BigRational;
use num_traits::{One, Signed, Zero};
use rustc_hash::FxHashMap;
pub fn s_polynomial(f: &Polynomial, g: &Polynomial) -> Polynomial {
if f.is_zero() || g.is_zero() {
return Polynomial::zero();
}
let lm_f = f.leading_monomial();
let lm_g = g.leading_monomial();
if lm_f.is_none() || lm_g.is_none() {
return Polynomial::zero();
}
let lm_f = lm_f.unwrap();
let lm_g = lm_g.unwrap();
let lcm = monomial_lcm(lm_f, lm_g);
let coeff_f = f.leading_coeff();
let coeff_g = g.leading_coeff();
if coeff_f.is_zero() || coeff_g.is_zero() {
return Polynomial::zero();
}
let factor_f = if let Some(quotient) = lcm.div(lm_f) {
quotient
} else {
return Polynomial::zero();
};
let factor_g = if let Some(quotient) = lcm.div(lm_g) {
quotient
} else {
return Polynomial::zero();
};
let term1 = f
.mul_monomial(&factor_f)
.scale(&(BigRational::one() / &coeff_f));
let term2 = g
.mul_monomial(&factor_g)
.scale(&(BigRational::one() / &coeff_g));
term1.sub(&term2)
}
fn monomial_lcm(m1: &Monomial, m2: &Monomial) -> Monomial {
let mut powers = Vec::new();
let vars1 = m1.vars();
let vars2 = m2.vars();
let mut i = 0;
let mut j = 0;
while i < vars1.len() || j < vars2.len() {
if i >= vars1.len() {
powers.push((vars2[j].var, vars2[j].power));
j += 1;
} else if j >= vars2.len() || vars1[i].var < vars2[j].var {
powers.push((vars1[i].var, vars1[i].power));
i += 1;
} else if vars1[i].var > vars2[j].var {
powers.push((vars2[j].var, vars2[j].power));
j += 1;
} else {
let max_power = vars1[i].power.max(vars2[j].power);
powers.push((vars1[i].var, max_power));
i += 1;
j += 1;
}
}
Monomial::from_powers(powers)
}
pub fn reduce(f: &Polynomial, g_set: &[Polynomial]) -> Polynomial {
if f.is_zero() || g_set.is_empty() {
return f.clone();
}
let mut r = Polynomial::zero();
let mut p = f.clone();
let max_iterations = 1000;
let mut iterations = 0;
while !p.is_zero() && iterations < max_iterations {
iterations += 1;
let mut reduced = false;
for g in g_set {
if g.is_zero() {
continue;
}
let lm_p = p.leading_monomial();
let lm_g = g.leading_monomial();
if lm_p.is_none() || lm_g.is_none() {
continue;
}
let lm_p = lm_p.unwrap();
let lm_g = lm_g.unwrap();
if let Some(quotient_monomial) = lm_p.div(lm_g) {
let lc_p = p.leading_coeff();
let lc_g = g.leading_coeff();
if !lc_g.is_zero() {
let quotient_coeff = lc_p / lc_g;
let subtractor = g.mul_monomial("ient_monomial).scale("ient_coeff);
p = p.sub(&subtractor);
reduced = true;
break;
}
}
}
if !reduced {
if let Some(lt) = p.leading_term() {
r = r.add(&Polynomial::from_terms(
vec![lt.clone()],
MonomialOrder::default(),
));
let mut terms = p.terms().to_vec();
if !terms.is_empty() {
terms.remove(0);
}
p = Polynomial::from_terms(terms, MonomialOrder::default());
} else {
break;
}
}
}
r
}
pub fn grobner_basis(polynomials: &[Polynomial]) -> Vec<Polynomial> {
if polynomials.is_empty() {
return vec![];
}
let mut g: Vec<Polynomial> = polynomials
.iter()
.filter(|p| !p.is_zero())
.map(|p| p.primitive())
.collect();
if g.is_empty() {
return vec![];
}
let mut pairs = Vec::new();
for i in 0..g.len() {
for j in (i + 1)..g.len() {
pairs.push((i, j));
}
}
let max_iterations = 1000;
let mut iterations = 0;
while !pairs.is_empty() && iterations < max_iterations {
iterations += 1;
let (i, j) = pairs.pop().unwrap();
if i >= g.len() || j >= g.len() {
continue;
}
let s = s_polynomial(&g[i], &g[j]);
let s_reduced = reduce(&s, &g);
if !s_reduced.is_zero() {
let s_primitive = s_reduced.primitive();
let new_idx = g.len();
g.push(s_primitive);
for k in 0..new_idx {
pairs.push((k, new_idx));
}
}
}
interreduce(&g)
}
fn interreduce(basis: &[Polynomial]) -> Vec<Polynomial> {
let mut result = Vec::new();
for (i, p) in basis.iter().enumerate() {
if p.is_zero() {
continue;
}
let others: Vec<Polynomial> = basis
.iter()
.enumerate()
.filter(|(j, _)| *j != i)
.map(|(_, q)| q.clone())
.collect();
let p_reduced = reduce(p, &others);
if !p_reduced.is_zero() {
result.push(p_reduced.make_monic());
}
}
result
}
pub fn ideal_membership(f: &Polynomial, generators: &[Polynomial]) -> bool {
if f.is_zero() {
return true;
}
if generators.is_empty() {
return false;
}
let gb = grobner_basis(generators);
let reduced = reduce(f, &gb);
reduced.is_zero()
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct Signature {
index: usize,
monomial: Monomial,
}
impl Signature {
fn new(index: usize, monomial: Monomial) -> Self {
Self { index, monomial }
}
fn unit(index: usize) -> Self {
Self {
index,
monomial: Monomial::unit(),
}
}
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match other.index.cmp(&self.index) {
std::cmp::Ordering::Equal => {
self.monomial.grevlex_cmp(&other.monomial)
}
ord => ord,
}
}
}
#[derive(Clone, Debug)]
struct LabeledPoly {
polynomial: Polynomial,
signature: Signature,
}
impl LabeledPoly {
fn new(polynomial: Polynomial, signature: Signature) -> Self {
Self {
polynomial,
signature,
}
}
#[allow(dead_code)]
fn mul_monomial(&self, m: &Monomial) -> Self {
Self {
polynomial: self.polynomial.mul_monomial(m),
signature: Signature::new(self.signature.index, self.signature.monomial.mul(m)),
}
}
}
fn f5_criterion(sig: &Signature, basis: &[LabeledPoly]) -> bool {
for p in basis {
if p.signature.cmp(sig) != std::cmp::Ordering::Greater {
if let Some(lm) = p.polynomial.leading_monomial()
&& sig.monomial.div(lm).is_some()
{
return true;
}
}
}
false
}
pub fn grobner_basis_f5(polynomials: &[Polynomial]) -> Vec<Polynomial> {
if polynomials.is_empty() {
return vec![];
}
let polys: Vec<Polynomial> = polynomials
.iter()
.filter(|p| !p.is_zero())
.map(|p| p.primitive())
.collect();
if polys.is_empty() {
return vec![];
}
let mut basis: Vec<LabeledPoly> = Vec::new();
for (idx, poly) in polys.iter().enumerate() {
let labeled = LabeledPoly::new(poly.clone(), Signature::unit(idx));
basis.push(labeled);
let mut pairs = Vec::new();
let basis_len = basis.len();
for i in 0..basis_len {
for j in (i + 1)..basis_len {
if basis[i].signature.index == idx || basis[j].signature.index == idx {
pairs.push((i, j));
}
}
}
let max_iterations = 500;
let mut iterations = 0;
while !pairs.is_empty() && iterations < max_iterations {
iterations += 1;
let (i, j) = pairs.pop().unwrap();
if i >= basis.len() || j >= basis.len() {
continue;
}
let lm_i = basis[i].polynomial.leading_monomial();
let lm_j = basis[j].polynomial.leading_monomial();
if lm_i.is_none() || lm_j.is_none() {
continue;
}
let lm_i = lm_i.unwrap();
let lm_j = lm_j.unwrap();
let lcm = monomial_lcm(lm_i, lm_j);
let sig_i = if let Some(quot) = lcm.div(lm_i) {
Signature::new(basis[i].signature.index, quot)
} else {
continue;
};
let sig_j = if let Some(quot) = lcm.div(lm_j) {
Signature::new(basis[j].signature.index, quot)
} else {
continue;
};
if f5_criterion(&sig_i, &basis) || f5_criterion(&sig_j, &basis) {
continue;
}
let (s_poly, sig) = if sig_i.cmp(&sig_j) == std::cmp::Ordering::Greater {
(
s_polynomial(&basis[i].polynomial, &basis[j].polynomial),
sig_i,
)
} else {
(
s_polynomial(&basis[i].polynomial, &basis[j].polynomial),
sig_j,
)
};
if s_poly.is_zero() {
continue;
}
let basis_polys: Vec<Polynomial> =
basis.iter().map(|lp| lp.polynomial.clone()).collect();
let s_reduced = reduce(&s_poly, &basis_polys);
if !s_reduced.is_zero() {
let s_primitive = s_reduced.primitive();
let new_labeled = LabeledPoly::new(s_primitive, sig);
let new_idx = basis.len();
basis.push(new_labeled);
for k in 0..new_idx {
if basis[k].signature.index == idx || basis[new_idx].signature.index == idx {
pairs.push((k, new_idx));
}
}
}
}
}
let polys: Vec<Polynomial> = basis.iter().map(|lp| lp.polynomial.clone()).collect();
interreduce(&polys)
}
pub fn grobner_basis_f4(polynomials: &[Polynomial]) -> Vec<Polynomial> {
if polynomials.is_empty() {
return vec![];
}
let mut g: Vec<Polynomial> = polynomials
.iter()
.filter(|p| !p.is_zero())
.map(|p| p.primitive())
.collect();
if g.is_empty() {
return vec![];
}
let mut pairs = Vec::new();
for i in 0..g.len() {
for j in (i + 1)..g.len() {
pairs.push((i, j));
}
}
let max_iterations = 500;
let mut iterations = 0;
while !pairs.is_empty() && iterations < max_iterations {
iterations += 1;
let batch_size = pairs.len().min(5);
let mut batch = Vec::new();
for _ in 0..batch_size {
if let Some(pair) = pairs.pop() {
batch.push(pair);
}
}
let mut s_polys = Vec::new();
for (i, j) in batch {
if i >= g.len() || j >= g.len() {
continue;
}
let s = s_polynomial(&g[i], &g[j]);
if !s.is_zero() {
s_polys.push(s);
}
}
if s_polys.is_empty() {
continue;
}
let reduced_polys = f4_matrix_reduction(&s_polys, &g);
for s_reduced in reduced_polys {
if !s_reduced.is_zero() {
let s_primitive = s_reduced.primitive();
let new_idx = g.len();
g.push(s_primitive);
for k in 0..new_idx {
pairs.push((k, new_idx));
}
}
}
}
interreduce(&g)
}
fn f4_matrix_reduction(polys: &[Polynomial], basis: &[Polynomial]) -> Vec<Polynomial> {
if polys.is_empty() {
return vec![];
}
let mut monomials = Vec::new();
for p in polys.iter().chain(basis.iter()) {
for term in p.terms() {
let monomial = &term.monomial;
if !monomials.iter().any(|m: &Monomial| m == monomial) {
monomials.push(monomial.clone());
}
}
}
monomials.sort_by(|a, b| b.grevlex_cmp(a));
let mut matrix: Vec<Vec<BigRational>> = Vec::new();
for p in polys {
let row = polynomial_to_row(p, &monomials);
matrix.push(row);
}
for b in basis {
let row = polynomial_to_row(b, &monomials);
matrix.push(row);
}
gaussian_elimination(&mut matrix);
let mut result = Vec::new();
for row in matrix.iter().take(polys.len()) {
let p = row_to_polynomial(row, &monomials);
if !p.is_zero() {
result.push(p);
}
}
result
}
fn polynomial_to_row(poly: &Polynomial, monomials: &[Monomial]) -> Vec<BigRational> {
let mut row = vec![BigRational::zero(); monomials.len()];
for term in poly.terms() {
if let Some(idx) = monomials.iter().position(|m| m == &term.monomial) {
row[idx] = term.coeff.clone();
}
}
row
}
fn row_to_polynomial(row: &[BigRational], monomials: &[Monomial]) -> Polynomial {
use crate::polynomial::Term;
let mut terms = Vec::new();
for (i, coeff) in row.iter().enumerate() {
if !coeff.is_zero() && i < monomials.len() {
terms.push(Term {
coeff: coeff.clone(),
monomial: monomials[i].clone(),
});
}
}
Polynomial::from_terms(terms, MonomialOrder::default())
}
#[allow(clippy::needless_range_loop)]
fn gaussian_elimination(matrix: &mut [Vec<BigRational>]) {
if matrix.is_empty() {
return;
}
let rows = matrix.len();
let cols = matrix[0].len();
let mut pivot_row = 0;
for col in 0..cols {
let mut max_row = pivot_row;
for row in pivot_row..rows {
if matrix[row][col].abs() > matrix[max_row][col].abs() {
max_row = row;
}
}
if matrix[max_row][col].is_zero() {
continue;
}
if max_row != pivot_row {
matrix.swap(pivot_row, max_row);
}
let pivot_val = matrix[pivot_row][col].clone();
for row in (pivot_row + 1)..rows {
if !matrix[row][col].is_zero() {
let factor = matrix[row][col].clone() / &pivot_val;
for c in col..cols {
let subtractor = &factor * &matrix[pivot_row][c];
matrix[row][c] -= subtractor;
}
}
}
pivot_row += 1;
if pivot_row >= rows {
break;
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Relation {
Equal,
NotEqual,
Greater,
GreaterEqual,
Less,
LessEqual,
}
#[derive(Debug, Clone)]
pub struct PolynomialConstraint {
pub polynomial: Polynomial,
pub relation: Relation,
}
impl PolynomialConstraint {
pub fn new(polynomial: Polynomial, relation: Relation) -> Self {
Self {
polynomial,
relation,
}
}
pub fn equal(polynomial: Polynomial) -> Self {
Self::new(polynomial, Relation::Equal)
}
pub fn not_equal(polynomial: Polynomial) -> Self {
Self::new(polynomial, Relation::NotEqual)
}
pub fn greater(polynomial: Polynomial) -> Self {
Self::new(polynomial, Relation::Greater)
}
pub fn greater_equal(polynomial: Polynomial) -> Self {
Self::new(polynomial, Relation::GreaterEqual)
}
pub fn less(polynomial: Polynomial) -> Self {
Self::new(polynomial, Relation::Less)
}
pub fn less_equal(polynomial: Polynomial) -> Self {
Self::new(polynomial, Relation::LessEqual)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SatResult {
Sat,
Unsat,
Unknown,
}
#[derive(Debug, Clone)]
pub struct Model {
pub assignments: FxHashMap<u32, BigRational>,
}
impl Model {
pub fn new() -> Self {
Self {
assignments: FxHashMap::default(),
}
}
pub fn assign(&mut self, var: u32, value: BigRational) {
self.assignments.insert(var, value);
}
pub fn get(&self, var: u32) -> Option<&BigRational> {
self.assignments.get(&var)
}
pub fn eval(&self, poly: &Polynomial) -> BigRational {
poly.eval(&self.assignments)
}
}
impl Default for Model {
fn default() -> Self {
Self::new()
}
}
pub struct NraSolver {
equalities: Vec<Polynomial>,
inequalities: Vec<PolynomialConstraint>,
grobner_basis: Option<Vec<Polynomial>>,
basis_dirty: bool,
}
impl NraSolver {
pub fn new() -> Self {
Self {
equalities: Vec::new(),
inequalities: Vec::new(),
grobner_basis: None,
basis_dirty: false,
}
}
pub fn add_constraint(&mut self, constraint: PolynomialConstraint) {
match constraint.relation {
Relation::Equal => {
self.equalities.push(constraint.polynomial);
self.basis_dirty = true;
}
_ => {
self.inequalities.push(constraint);
}
}
}
pub fn add_equality(&mut self, polynomial: Polynomial) {
self.equalities.push(polynomial);
self.basis_dirty = true;
}
fn get_grobner_basis(&mut self) -> &Vec<Polynomial> {
if self.basis_dirty || self.grobner_basis.is_none() {
let gb = if self.equalities.is_empty() {
vec![]
} else {
grobner_basis_f5(&self.equalities)
};
self.grobner_basis = Some(gb);
self.basis_dirty = false;
}
self.grobner_basis.as_ref().unwrap()
}
pub fn check_equalities(&mut self) -> SatResult {
if self.equalities.is_empty() {
return SatResult::Sat;
}
let gb = self.get_grobner_basis();
for p in gb {
if p.is_constant() && !p.is_zero() {
return SatResult::Unsat;
}
}
SatResult::Sat
}
pub fn check_sat(&mut self) -> SatResult {
let eq_result = self.check_equalities();
if eq_result == SatResult::Unsat {
return SatResult::Unsat;
}
if !self.inequalities.is_empty() {
let gb = self.get_grobner_basis().clone();
for constraint in &self.inequalities {
let simplified = reduce(&constraint.polynomial, &gb);
if simplified.is_constant() || simplified.is_zero() {
let const_val = if simplified.is_zero() {
BigRational::zero()
} else {
simplified.constant_term()
};
let is_sat = match constraint.relation {
Relation::Equal => const_val.is_zero(),
Relation::NotEqual => !const_val.is_zero(),
Relation::Greater => const_val.is_positive(),
Relation::GreaterEqual => !const_val.is_negative(),
Relation::Less => const_val.is_negative(),
Relation::LessEqual => !const_val.is_positive(),
};
if !is_sat {
return SatResult::Unsat;
}
}
}
let has_complex_inequality = self.inequalities.iter().any(|c| {
let simplified = reduce(&c.polynomial, &gb);
!simplified.is_constant() && simplified.total_degree() > 1
});
if has_complex_inequality {
return SatResult::Unknown;
}
}
SatResult::Sat
}
pub fn simplify(&mut self, polynomial: &Polynomial) -> Polynomial {
if self.equalities.is_empty() {
return polynomial.clone();
}
let gb = self.get_grobner_basis();
reduce(polynomial, gb)
}
pub fn implies_zero(&mut self, polynomial: &Polynomial) -> bool {
if self.equalities.is_empty() {
return polynomial.is_zero();
}
let gb = self.get_grobner_basis();
let reduced = reduce(polynomial, gb);
reduced.is_zero()
}
pub fn get_model(&mut self) -> Option<Model> {
let eq_result = self.check_equalities();
if eq_result == SatResult::Unsat {
return None;
}
let gb = self.get_grobner_basis();
let mut model = Model::new();
for poly in gb {
if poly.is_constant() {
continue;
}
let vars = poly.vars();
if vars.len() == 1 {
let var = vars[0];
if poly.total_degree() == 1 {
if let Some(root) = solve_linear(poly) {
model.assign(var, root);
}
}
else if model.get(var).is_none() {
model.assign(var, BigRational::zero());
}
}
}
Some(model)
}
pub fn reset(&mut self) {
self.equalities.clear();
self.inequalities.clear();
self.grobner_basis = None;
self.basis_dirty = false;
}
pub fn num_equalities(&self) -> usize {
self.equalities.len()
}
pub fn num_inequalities(&self) -> usize {
self.inequalities.len()
}
}
impl Default for NraSolver {
fn default() -> Self {
Self::new()
}
}
fn solve_linear(poly: &Polynomial) -> Option<BigRational> {
if poly.total_degree() != 1 {
return None;
}
let terms = poly.terms();
if terms.is_empty() {
return None;
}
let mut constant = BigRational::zero();
let mut linear_coeff = BigRational::zero();
for term in terms {
if term.monomial.total_degree() == 0 {
constant = term.coeff.clone();
} else if term.monomial.total_degree() == 1 {
linear_coeff = term.coeff.clone();
}
}
if linear_coeff.is_zero() {
return None;
}
Some(-constant / linear_coeff)
}
#[cfg(test)]
mod tests {
use super::*;
use num_bigint::BigInt;
#[allow(dead_code)]
fn rat(n: i64) -> BigRational {
BigRational::from_integer(BigInt::from(n))
}
#[test]
fn test_monomial_lcm() {
let m1 = Monomial::from_var_power(0, 2);
let m2 = Monomial::from_var_power(0, 3);
let lcm = monomial_lcm(&m1, &m2);
assert_eq!(lcm.degree(0), 3);
let m1 = Monomial::from_powers([(0, 2), (1, 1)]);
let m2 = Monomial::from_powers([(0, 1), (1, 2)]);
let lcm = monomial_lcm(&m1, &m2);
assert_eq!(lcm.degree(0), 2);
assert_eq!(lcm.degree(1), 2);
}
#[test]
fn test_s_polynomial() {
let f = Polynomial::from_coeffs_int(&[(1, &[(0, 2)])]);
let g = Polynomial::from_coeffs_int(&[(1, &[(0, 1), (1, 1)])]);
let s = s_polynomial(&f, &g);
assert!(s.is_zero() || s.total_degree() <= 2);
}
#[test]
fn test_reduce() {
let f = Polynomial::from_coeffs_int(&[(1, &[(0, 2)])]);
let g = vec![Polynomial::from_coeffs_int(&[(1, &[(0, 1)])])];
let r = reduce(&f, &g);
assert!(r.is_zero());
}
#[test]
fn test_grobner_basis_simple() {
let f1 = Polynomial::from_var(0); let f2 = Polynomial::from_var(1);
let gb = grobner_basis(&[f1, f2]);
assert!(gb.len() >= 2);
}
#[test]
fn test_ideal_membership() {
let f = Polynomial::from_coeffs_int(&[(1, &[(0, 2)])]);
let generators = vec![Polynomial::from_var(0)];
assert!(ideal_membership(&f, &generators));
let f = Polynomial::from_var(1);
assert!(!ideal_membership(&f, &generators));
}
#[test]
fn test_ideal_membership_multivariate() {
let f = Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (1, &[(1, 1)])]);
let generators = vec![Polynomial::from_var(0), Polynomial::from_var(1)];
assert!(ideal_membership(&f, &generators));
}
#[test]
fn test_f4_simple() {
let f1 = Polynomial::from_var(0); let f2 = Polynomial::from_var(1);
let gb = grobner_basis_f4(&[f1, f2]);
assert!(gb.len() >= 2);
}
#[test]
fn test_f4_vs_buchberger() {
let f1 = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (-1, &[])]); let f2 = Polynomial::from_coeffs_int(&[(1, &[(0, 1), (1, 1)]), (-1, &[(1, 1)])]);
let gb_buchberger = grobner_basis(&[f1.clone(), f2.clone()]);
let gb_f4 = grobner_basis_f4(&[f1, f2]);
assert!(!gb_buchberger.is_empty());
assert!(!gb_f4.is_empty());
assert!(gb_buchberger.len() <= gb_f4.len() * 2);
assert!(gb_f4.len() <= gb_buchberger.len() * 2);
}
#[test]
fn test_gaussian_elimination() {
use num_bigint::BigInt;
let mut matrix = vec![
vec![
BigRational::from_integer(BigInt::from(2)),
BigRational::from_integer(BigInt::from(1)),
],
vec![
BigRational::from_integer(BigInt::from(1)),
BigRational::from_integer(BigInt::from(1)),
],
];
gaussian_elimination(&mut matrix);
assert_eq!(matrix[0][0], BigRational::from_integer(BigInt::from(2)));
assert_eq!(matrix[1][0], BigRational::from_integer(BigInt::from(0)));
}
#[test]
fn test_polynomial_row_conversion() {
let p = Polynomial::from_coeffs_int(&[(2, &[(0, 1)]), (3, &[(1, 1)])]);
let monomials = vec![
Monomial::from_var_power(0, 1),
Monomial::from_var_power(1, 1),
];
let row = polynomial_to_row(&p, &monomials);
assert_eq!(row.len(), 2);
let p2 = row_to_polynomial(&row, &monomials);
assert_eq!(p.total_degree(), p2.total_degree());
}
#[test]
fn test_f5_simple() {
let f1 = Polynomial::from_var(0); let f2 = Polynomial::from_var(1);
let gb = grobner_basis_f5(&[f1, f2]);
assert!(gb.len() >= 2);
}
#[test]
fn test_f5_vs_buchberger() {
let f1 = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (-1, &[])]); let f2 = Polynomial::from_coeffs_int(&[(1, &[(0, 1), (1, 1)]), (-1, &[(1, 1)])]);
let gb_buchberger = grobner_basis(&[f1.clone(), f2.clone()]);
let gb_f5 = grobner_basis_f5(&[f1, f2]);
assert!(!gb_buchberger.is_empty());
assert!(!gb_f5.is_empty());
assert!(gb_buchberger.len() <= gb_f5.len() * 2);
assert!(gb_f5.len() <= gb_buchberger.len() * 2);
}
#[test]
fn test_f5_vs_f4() {
let f1 = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (-1, &[(1, 2)])]); let f2 = Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (-1, &[(1, 1)])]);
let gb_f4 = grobner_basis_f4(&[f1.clone(), f2.clone()]);
let gb_f5 = grobner_basis_f5(&[f1, f2]);
assert!(!gb_f4.is_empty());
assert!(!gb_f5.is_empty());
}
#[test]
fn test_signature_ordering() {
let sig1 = Signature::unit(0);
let sig2 = Signature::unit(1);
assert_eq!(sig1.cmp(&sig2), std::cmp::Ordering::Greater);
}
#[test]
fn test_labeled_poly() {
let p = Polynomial::from_var(0);
let sig = Signature::unit(0);
let lp = LabeledPoly::new(p, sig);
let m = Monomial::from_var_power(1, 1);
let lp2 = lp.mul_monomial(&m);
assert_eq!(lp2.polynomial.total_degree(), 2);
}
#[test]
fn test_nra_solver_empty() {
let mut solver = NraSolver::new();
assert_eq!(solver.check_sat(), SatResult::Sat);
}
#[test]
fn test_nra_solver_simple_equality() {
let mut solver = NraSolver::new();
let x = Polynomial::from_var(0); solver.add_equality(x);
assert_eq!(solver.check_sat(), SatResult::Sat);
if let Some(model) = solver.get_model() {
assert_eq!(model.get(0), Some(&BigRational::zero()));
}
}
#[test]
fn test_nra_solver_linear_system() {
let mut solver = NraSolver::new();
let p1 = Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (1, &[(1, 1)])]);
let p2 = Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (-1, &[(1, 1)])]);
solver.add_equality(p1);
solver.add_equality(p2);
assert_eq!(solver.check_sat(), SatResult::Sat);
let model = solver.get_model();
assert!(model.is_some());
}
#[test]
fn test_nra_solver_inconsistent() {
let mut solver = NraSolver::new();
let one = Polynomial::constant(rat(1));
solver.add_equality(one);
assert_eq!(solver.check_sat(), SatResult::Unsat);
}
#[test]
fn test_nra_solver_inconsistent_system() {
let mut solver = NraSolver::new();
let p1 = Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (-1, &[])]);
let p2 = Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (-2, &[])]);
solver.add_equality(p1);
solver.add_equality(p2);
assert_eq!(solver.check_sat(), SatResult::Unsat);
}
#[test]
fn test_nra_solver_simplify() {
let mut solver = NraSolver::new();
let x = Polynomial::from_var(0);
solver.add_equality(x.clone());
let x_squared = Polynomial::from_coeffs_int(&[(1, &[(0, 2)])]);
let simplified = solver.simplify(&x_squared);
assert!(simplified.is_zero());
}
#[test]
fn test_nra_solver_implies_zero() {
let mut solver = NraSolver::new();
let x = Polynomial::from_var(0);
solver.add_equality(x.clone());
let x_squared = Polynomial::from_coeffs_int(&[(1, &[(0, 2)])]);
assert!(solver.implies_zero(&x_squared));
let y = Polynomial::from_var(1);
assert!(!solver.implies_zero(&y));
}
#[test]
fn test_nra_solver_reset() {
let mut solver = NraSolver::new();
let x = Polynomial::from_var(0);
solver.add_equality(x);
assert_eq!(solver.num_equalities(), 1);
solver.reset();
assert_eq!(solver.num_equalities(), 0);
assert_eq!(solver.check_sat(), SatResult::Sat);
}
#[test]
fn test_polynomial_constraint_creation() {
let p = Polynomial::from_var(0);
let eq = PolynomialConstraint::equal(p.clone());
assert_eq!(eq.relation, Relation::Equal);
let neq = PolynomialConstraint::not_equal(p.clone());
assert_eq!(neq.relation, Relation::NotEqual);
let gt = PolynomialConstraint::greater(p.clone());
assert_eq!(gt.relation, Relation::Greater);
let gte = PolynomialConstraint::greater_equal(p.clone());
assert_eq!(gte.relation, Relation::GreaterEqual);
let lt = PolynomialConstraint::less(p.clone());
assert_eq!(lt.relation, Relation::Less);
let lte = PolynomialConstraint::less_equal(p);
assert_eq!(lte.relation, Relation::LessEqual);
}
#[test]
fn test_model_operations() {
let mut model = Model::new();
model.assign(0, rat(2));
model.assign(1, rat(3));
assert_eq!(model.get(0), Some(&rat(2)));
assert_eq!(model.get(1), Some(&rat(3)));
let poly = Polynomial::from_coeffs_int(&[(2, &[(0, 1)]), (3, &[(1, 1)])]);
let result = model.eval(&poly);
assert_eq!(result, rat(13));
}
#[test]
fn test_solve_linear() {
let poly = Polynomial::from_coeffs_int(&[(2, &[(0, 1)]), (4, &[])]);
let solution = solve_linear(&poly);
assert_eq!(solution, Some(rat(-2)));
}
#[test]
fn test_solve_linear_simple() {
let poly = Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (-3, &[])]);
let solution = solve_linear(&poly);
assert_eq!(solution, Some(rat(3)));
}
#[test]
fn test_nra_solver_with_constraint_api() {
let mut solver = NraSolver::new();
let x = Polynomial::from_var(0);
let constraint = PolynomialConstraint::equal(x);
solver.add_constraint(constraint);
assert_eq!(solver.num_equalities(), 1);
assert_eq!(solver.check_sat(), SatResult::Sat);
}
#[test]
fn test_nra_solver_trivial_inequality_sat() {
let mut solver = NraSolver::new();
let one = Polynomial::constant(rat(1));
let constraint = PolynomialConstraint::greater(one);
solver.add_constraint(constraint);
assert_eq!(solver.check_sat(), SatResult::Sat);
}
#[test]
fn test_nra_solver_trivial_inequality_unsat() {
let mut solver = NraSolver::new();
let neg_one = Polynomial::constant(rat(-1));
let constraint = PolynomialConstraint::greater(neg_one);
solver.add_constraint(constraint);
assert_eq!(solver.check_sat(), SatResult::Unsat);
}
#[test]
fn test_nra_solver_inequality_after_simplification() {
let mut solver = NraSolver::new();
let p1 = Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (-2, &[])]);
solver.add_equality(p1);
let x = Polynomial::from_var(0);
let constraint = PolynomialConstraint::greater(x);
solver.add_constraint(constraint);
assert_eq!(solver.check_sat(), SatResult::Sat);
}
#[test]
fn test_nra_solver_not_equal_constraint() {
let mut solver = NraSolver::new();
let one = Polynomial::constant(rat(1));
let constraint = PolynomialConstraint::not_equal(one);
solver.add_constraint(constraint);
assert_eq!(solver.check_sat(), SatResult::Sat);
}
#[test]
fn test_nra_solver_not_equal_unsat() {
let mut solver = NraSolver::new();
let zero = Polynomial::zero();
let constraint = PolynomialConstraint::not_equal(zero);
solver.add_constraint(constraint);
assert_eq!(solver.check_sat(), SatResult::Unsat);
}
#[test]
fn test_nra_solver_less_equal() {
let mut solver = NraSolver::new();
let neg_one = Polynomial::constant(rat(-1));
let constraint = PolynomialConstraint::less_equal(neg_one);
solver.add_constraint(constraint);
assert_eq!(solver.check_sat(), SatResult::Sat);
}
#[test]
fn test_nra_solver_greater_equal() {
let mut solver = NraSolver::new();
let zero = Polynomial::zero();
let constraint = PolynomialConstraint::greater_equal(zero);
solver.add_constraint(constraint);
assert_eq!(solver.check_sat(), SatResult::Sat);
}
#[test]
fn test_nra_solver_complex_inequality() {
let mut solver = NraSolver::new();
let x_squared = Polynomial::from_coeffs_int(&[(1, &[(0, 2)])]);
let constraint = PolynomialConstraint::greater(x_squared);
solver.add_constraint(constraint);
assert_eq!(solver.check_sat(), SatResult::Unknown);
}
}