use std::collections::HashMap;
use std::marker::PhantomData;
use ocas_domain::{Domain, EuclideanDomain, FiniteField, IntegerDomain};
use smallvec::SmallVec;
use crate::factor::multivariate::{bivariate_factor_fp, bivariate_factor_z};
pub trait MonomialOrder: Clone + Copy + PartialEq + Eq + std::fmt::Debug {
fn cmp(lhs: &[usize], rhs: &[usize]) -> std::cmp::Ordering;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Lex;
impl MonomialOrder for Lex {
fn cmp(lhs: &[usize], rhs: &[usize]) -> std::cmp::Ordering {
lhs.cmp(rhs)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Grevlex;
impl MonomialOrder for Grevlex {
fn cmp(lhs: &[usize], rhs: &[usize]) -> std::cmp::Ordering {
let deg_lhs: usize = lhs.iter().sum();
let deg_rhs: usize = rhs.iter().sum();
deg_rhs
.cmp(°_lhs)
.then_with(|| rhs.iter().rev().cmp(lhs.iter().rev()))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Grlex;
impl MonomialOrder for Grlex {
fn cmp(lhs: &[usize], rhs: &[usize]) -> std::cmp::Ordering {
let deg_lhs: usize = lhs.iter().sum();
let deg_rhs: usize = rhs.iter().sum();
deg_rhs.cmp(°_lhs).then_with(|| lhs.cmp(rhs))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SparseMultivariatePolynomial<D: Domain, O: MonomialOrder = Grevlex> {
terms: HashMap<SmallVec<[usize; 4]>, D::Element>,
domain: D,
n_vars: usize,
_marker: PhantomData<O>,
}
impl<D: Domain, O: MonomialOrder> SparseMultivariatePolynomial<D, O> {
pub fn new(domain: D, n_vars: usize) -> Self {
Self {
terms: HashMap::new(),
domain,
n_vars,
_marker: PhantomData,
}
}
pub fn from_terms(domain: D, n_vars: usize, terms: Vec<(Vec<usize>, D::Element)>) -> Self {
let mut poly = Self::new(domain, n_vars);
for (exp, coeff) in terms {
poly.set_term(exp, coeff);
}
poly
}
pub fn domain(&self) -> &D {
&self.domain
}
pub fn n_vars(&self) -> usize {
self.n_vars
}
pub fn n_terms(&self) -> usize {
self.terms.len()
}
pub fn is_zero(&self) -> bool {
self.terms.is_empty()
}
pub fn terms_ref(&self) -> &HashMap<SmallVec<[usize; 4]>, D::Element> {
&self.terms
}
pub fn set_term_external(&mut self, exp: Vec<usize>, coeff: D::Element) {
self.set_term(exp, coeff);
}
pub fn total_degree(&self) -> Option<usize> {
self.terms.keys().map(|e| e.iter().sum::<usize>()).max()
}
pub fn coeff(&self, exp: &[usize]) -> D::Element {
let key = Self::normalize_exp(exp, self.n_vars);
self.terms
.get(&key)
.cloned()
.unwrap_or_else(|| self.domain.zero())
}
fn set_term(&mut self, exp: Vec<usize>, coeff: D::Element) {
let key = Self::normalize_exp(&exp, self.n_vars);
if self.domain.is_zero(&coeff) {
self.terms.remove(&key);
} else {
self.terms.insert(key, coeff);
}
}
fn normalize_exp(exp: &[usize], n_vars: usize) -> SmallVec<[usize; 4]> {
let mut v = SmallVec::with_capacity(n_vars);
for i in 0..n_vars {
v.push(*exp.get(i).unwrap_or(&0));
}
v
}
pub fn zero(&self) -> Self {
Self::new(self.domain.clone(), self.n_vars)
}
pub fn one(&self) -> Self {
let mut poly = Self::new(self.domain.clone(), self.n_vars);
let mut exp = SmallVec::with_capacity(self.n_vars);
exp.resize(self.n_vars, 0);
poly.terms.insert(exp, self.domain.one());
poly
}
pub fn neg(&self) -> Self {
let mut poly = self.zero();
for (exp, coeff) in &self.terms {
poly.terms.insert(exp.clone(), self.domain.neg(coeff));
}
poly
}
pub fn add(&self, other: &Self) -> Self {
assert_eq!(
self.n_vars, other.n_vars,
"polynomials must have the same number of variables"
);
let mut poly = self.clone();
for (exp, coeff) in &other.terms {
let existing = poly
.terms
.get(exp)
.cloned()
.unwrap_or_else(|| poly.domain.zero());
let sum = poly.domain.add(&existing, coeff);
if poly.domain.is_zero(&sum) {
poly.terms.remove(exp);
} else {
poly.terms.insert(exp.clone(), sum);
}
}
poly
}
pub fn sub(&self, other: &Self) -> Self {
self.add(&other.neg())
}
pub fn mul_scalar(&self, scalar: &D::Element) -> Self {
if self.domain.is_zero(scalar) {
return self.zero();
}
let mut poly = self.zero();
for (exp, coeff) in &self.terms {
poly.terms
.insert(exp.clone(), self.domain.mul(coeff, scalar));
}
poly
}
pub fn mul(&self, other: &Self) -> Self {
assert_eq!(
self.n_vars, other.n_vars,
"polynomials must have the same number of variables"
);
if self.is_zero() || other.is_zero() {
return self.zero();
}
let mut poly = self.zero();
for (e1, c1) in &self.terms {
for (e2, c2) in &other.terms {
let mut exp = SmallVec::with_capacity(self.n_vars);
for i in 0..self.n_vars {
exp.push(e1[i] + e2[i]);
}
let prod = self.domain.mul(c1, c2);
let existing = poly
.terms
.get(&exp)
.cloned()
.unwrap_or_else(|| poly.domain.zero());
let sum = poly.domain.add(&existing, &prod);
if poly.domain.is_zero(&sum) {
poly.terms.remove(&exp);
} else {
poly.terms.insert(exp, sum);
}
}
}
poly
}
pub fn sorted_terms(&self) -> Vec<(&SmallVec<[usize; 4]>, &D::Element)> {
let mut terms: Vec<_> = self.terms.iter().collect();
terms.sort_by(|(a, _), (b, _)| O::cmp(a, b));
terms
}
pub fn leading_term(&self) -> Option<(&SmallVec<[usize; 4]>, &D::Element)> {
self.terms.iter().max_by(|(a, _), (b, _)| O::cmp(a, b))
}
pub fn leading_monomial(&self) -> Option<&SmallVec<[usize; 4]>> {
self.terms.keys().max_by(|a, b| O::cmp(a, b))
}
pub fn leading_coeff(&self) -> Option<&D::Element> {
let lm = self.leading_monomial()?;
self.terms.get(lm)
}
pub fn mul_monomial(&self, exp: &[usize]) -> Self {
assert_eq!(
exp.len(),
self.n_vars,
"exponent vector must have length {}",
self.n_vars
);
let mut poly = self.zero();
for (e, c) in &self.terms {
let mut new_exp = SmallVec::with_capacity(self.n_vars);
for i in 0..self.n_vars {
new_exp.push(e[i] + exp[i]);
}
poly.terms.insert(new_exp, c.clone());
}
poly
}
pub fn reduce(&self, basis: &[Self]) -> Self {
let mut remainder = self.clone();
let mut result = self.zero();
let basis_lts: Vec<_> = basis
.iter()
.filter_map(|g| g.leading_term().map(|(e, c)| (g, e.clone(), c.clone())))
.collect();
let max_iter = 10000;
for _ in 0..max_iter {
if remainder.is_zero() {
break;
}
let (rm, rc) = match remainder.leading_term() {
Some((e, c)) => (e.clone(), c.clone()),
None => break,
};
let mut reduced = false;
for (g, lm, lc) in &basis_lts {
if monomial_divides(&rm, lm) {
let qm: SmallVec<[usize; 4]> =
rm.iter().zip(lm.iter()).map(|(a, b)| a - b).collect();
let qc = match self.domain.div(&rc, lc) {
Some(q) => q,
None => break,
};
let sub = g.mul_monomial(&qm).mul_scalar(&qc);
remainder = remainder.sub(&sub);
reduced = true;
break;
}
}
if !reduced {
let key = rm;
let val = rc;
result.terms.insert(key.clone(), val);
remainder.terms.remove(&key);
}
}
result
}
pub fn spoly(&self, other: &Self) -> Self {
let (lm_f, lc_f) = match self.leading_term() {
Some(t) => (t.0.clone(), t.1.clone()),
None => return self.zero(),
};
let (lm_g, lc_g) = match other.leading_term() {
Some(t) => (t.0.clone(), t.1.clone()),
None => return self.zero(),
};
let lcm = monomial_lcm(&lm_f, &lm_g);
let m_f: SmallVec<[usize; 4]> = lcm.iter().zip(lm_f.iter()).map(|(a, b)| a - b).collect();
let m_g: SmallVec<[usize; 4]> = lcm.iter().zip(lm_g.iter()).map(|(a, b)| a - b).collect();
let term1 = self.mul_monomial(&m_f).mul_scalar(&lc_g);
let term2 = other.mul_monomial(&m_g).mul_scalar(&lc_f);
term1.sub(&term2)
}
pub fn content(&self) -> D::Element
where
D: EuclideanDomain,
{
if self.is_zero() {
return self.domain.zero();
}
let mut g = self.domain.zero();
for c in self.terms.values() {
g = self.domain.gcd(&g, c);
}
g
}
pub fn primitive_part(&self) -> Self
where
D: EuclideanDomain,
{
if self.is_zero() {
return self.clone();
}
let content = self.content();
if self.domain.is_one(&content) {
return self.clone();
}
let mut result = self.zero();
for (exp, c) in &self.terms {
let q = self.domain.div(c, &content).unwrap_or_else(|| c.clone());
result.terms.insert(exp.clone(), q);
}
result
}
pub fn div_exact(&self, divisor: &Self) -> Self {
if divisor.n_terms() <= 1 {
let const_val = divisor.coeff(&vec![0; divisor.n_vars]);
if self.domain.is_one(&const_val) {
return self.clone();
}
}
let (quot, rem) = self.div_rem_sparse(divisor);
debug_assert!(rem.is_zero(), "div_exact: division had non-zero remainder");
quot
}
fn div_rem_sparse(&self, divisor: &Self) -> (Self, Self) {
if divisor.is_zero() {
panic!("division by zero polynomial");
}
let (_, div_lm) = match divisor.leading_term() {
Some(t) => (t.0.clone(), t.1.clone()),
None => return (self.zero(), self.clone()),
};
let div_lc = div_lm;
let div_exp = divisor.leading_monomial().unwrap().clone();
let mut remainder = self.clone();
let mut quotient = self.zero();
while !remainder.is_zero() {
let (rem_exp, rem_lc) = match remainder.leading_term() {
Some(t) => (t.0.clone(), t.1.clone()),
None => break,
};
if !monomial_divides(&div_exp, &rem_exp) {
break;
}
let q_coeff = match self.domain.div(&rem_lc, &div_lc) {
Some(q) => q,
None => break,
};
let q_exp: SmallVec<[usize; 4]> = rem_exp
.iter()
.zip(div_exp.iter())
.map(|(a, b)| a - b)
.collect();
let existing = quotient
.terms
.get(&q_exp)
.cloned()
.unwrap_or_else(|| self.domain.zero());
let sum = self.domain.add(&existing, &q_coeff);
if self.domain.is_zero(&sum) {
quotient.terms.remove(&q_exp);
} else {
quotient.terms.insert(q_exp, sum);
}
let scaled = divisor.mul_monomial(
&remainder
.leading_monomial()
.unwrap()
.iter()
.zip(div_exp.iter())
.map(|(a, b)| a - b)
.collect::<SmallVec<[usize; 4]>>(),
);
let scaled = scaled.mul_scalar(&q_coeff);
remainder = remainder.sub(&scaled);
}
(quotient, remainder)
}
pub fn degree_in(&self, var_index: usize) -> usize {
self.terms
.keys()
.map(|e| e.get(var_index).copied().unwrap_or(0))
.max()
.unwrap_or(0)
}
#[inline]
pub fn max_exp(&self) -> Option<&SmallVec<[usize; 4]>> {
self.leading_monomial()
}
#[inline]
pub fn max_coeff(&self) -> Option<&D::Element> {
self.leading_coeff()
}
pub fn exponents_iter(&self) -> impl Iterator<Item = &SmallVec<[usize; 4]>> {
let mut sorted: Vec<_> = self.terms.keys().collect();
sorted.sort_by(|a, b| O::cmp(a, b));
sorted.into_iter()
}
pub fn make_monic_inplace(&mut self) -> bool {
if self.is_zero() {
return false;
}
let lc = self.leading_coeff().cloned().unwrap();
match self.domain.inv(&lc) {
Some(inv_lc) => {
for coeff in self.terms.values_mut() {
*coeff = self.domain.mul(coeff, &inv_lc);
}
true
}
None => false,
}
}
#[inline]
pub fn zero_with_capacity(&self, _cap: usize) -> Self {
self.zero()
}
pub fn append_monomial(&mut self, coeff: D::Element, exp: &[usize]) {
let key = Self::normalize_exp(exp, self.n_vars);
let existing = self
.terms
.get(&key)
.cloned()
.unwrap_or_else(|| self.domain.zero());
let sum = self.domain.add(&existing, &coeff);
if self.domain.is_zero(&sum) {
self.terms.remove(&key);
} else {
self.terms.insert(key, sum);
}
}
pub fn eval(&self, var_index: usize, value: &D::Element) -> Self {
let new_n_vars = self.n_vars.saturating_sub(1);
let mut result = Self::new(self.domain.clone(), new_n_vars);
for (exp, coeff) in &self.terms {
let power = self.domain.pow(value, exp[var_index] as u64);
let new_coeff = self.domain.mul(coeff, &power);
if self.domain.is_zero(&new_coeff) {
continue;
}
let mut new_exp = SmallVec::with_capacity(new_n_vars);
for i in 0..self.n_vars {
if i != var_index {
new_exp.push(exp[i]);
}
}
let existing = result
.terms
.get(&new_exp)
.cloned()
.unwrap_or_else(|| self.domain.zero());
let sum = self.domain.add(&existing, &new_coeff);
if self.domain.is_zero(&sum) {
result.terms.remove(&new_exp);
} else {
result.terms.insert(new_exp, sum);
}
}
result
}
}
impl SparseMultivariatePolynomial<IntegerDomain, Lex> {
pub fn factor(&self) -> Vec<(Self, usize)> {
bivariate_factor_z(self, 0, 1)
}
}
impl SparseMultivariatePolynomial<FiniteField, Lex> {
pub fn factor(&self) -> Vec<(Self, usize)> {
bivariate_factor_fp(self, 0, 1)
}
}
pub fn monomial_divides(a: &[usize], b: &[usize]) -> bool {
a.iter().zip(b.iter()).all(|(x, y)| x >= y)
}
pub fn monomial_lcm(a: &[usize], b: &[usize]) -> SmallVec<[usize; 4]> {
a.iter().zip(b.iter()).map(|(x, y)| *x.max(y)).collect()
}
pub fn monomial_are_coprime(a: &[usize], b: &[usize]) -> bool {
a.iter().zip(b.iter()).all(|(x, y)| *x == 0 || *y == 0)
}
#[cfg(test)]
mod tests {
use super::*;
use ocas_domain::{Integer, IntegerDomain, Rational, RationalDomain};
#[test]
fn sparse_create_and_coeff() {
let domain = IntegerDomain;
let p = SparseMultivariatePolynomial::<_, Lex>::from_terms(
domain,
2,
vec![
(vec![1, 0], Integer::from(2)),
(vec![0, 1], Integer::from(3)),
],
);
assert_eq!(p.coeff(&[1, 0]), Integer::from(2));
assert_eq!(p.coeff(&[0, 1]), Integer::from(3));
assert_eq!(p.coeff(&[0, 0]), Integer::from(0));
}
#[test]
fn sparse_total_degree() {
let domain = IntegerDomain;
let p = SparseMultivariatePolynomial::<_, Grevlex>::from_terms(
domain,
2,
vec![
(vec![2, 1], Integer::from(1)),
(vec![1, 0], Integer::from(1)),
],
);
assert_eq!(p.total_degree(), Some(3));
}
#[test]
fn sparse_add_and_sub() {
let domain = IntegerDomain;
let a = SparseMultivariatePolynomial::<_, Lex>::from_terms(
domain,
2,
vec![
(vec![1, 0], Integer::from(1)),
(vec![0, 1], Integer::from(2)),
],
);
let b = SparseMultivariatePolynomial::<_, Lex>::from_terms(
domain,
2,
vec![
(vec![1, 0], Integer::from(3)),
(vec![0, 0], Integer::from(4)),
],
);
let sum = a.add(&b);
assert_eq!(sum.coeff(&[1, 0]), Integer::from(4));
assert_eq!(sum.coeff(&[0, 1]), Integer::from(2));
assert_eq!(sum.coeff(&[0, 0]), Integer::from(4));
let diff = b.sub(&a);
assert_eq!(diff.coeff(&[1, 0]), Integer::from(2));
assert_eq!(diff.coeff(&[0, 1]), Integer::from(-2));
assert_eq!(diff.coeff(&[0, 0]), Integer::from(4));
}
#[test]
fn sparse_multiplication() {
let domain = RationalDomain;
let a = SparseMultivariatePolynomial::<_, Grevlex>::from_terms(
domain,
2,
vec![
(vec![1, 0], Rational::new(1, 1)),
(vec![0, 1], Rational::new(2, 1)),
],
);
let b = SparseMultivariatePolynomial::<_, Grevlex>::from_terms(
domain,
2,
vec![
(vec![1, 0], Rational::new(3, 1)),
(vec![0, 1], Rational::new(1, 1)),
],
);
let prod = a.mul(&b);
assert_eq!(prod.coeff(&[2, 0]), Rational::new(3, 1));
assert_eq!(prod.coeff(&[1, 1]), Rational::new(7, 1));
assert_eq!(prod.coeff(&[0, 2]), Rational::new(2, 1));
}
#[test]
fn sparse_sorted_terms_grevlex() {
let domain = IntegerDomain;
let p = SparseMultivariatePolynomial::<_, Grevlex>::from_terms(
domain,
2,
vec![
(vec![1, 0], Integer::from(1)),
(vec![2, 0], Integer::from(1)),
(vec![0, 1], Integer::from(1)),
],
);
let sorted = p.sorted_terms();
let exps: Vec<_> = sorted.into_iter().map(|(e, _)| e.to_vec()).collect();
assert_eq!(exps, vec![vec![2, 0], vec![0, 1], vec![1, 0]]);
}
}