use ocas_core::FastHashMap as HashMap;
use ocas_domain::{Domain, EuclideanDomain, FiniteField, IntegerDomain};
use smallvec::SmallVec;
use crate::factor::multivariate::{bivariate_factor_fp, bivariate_factor_z};
pub trait MonomialOrder: Clone + PartialEq + Eq + std::fmt::Debug + Default {
fn cmp(&self, lhs: &[usize], rhs: &[usize]) -> std::cmp::Ordering;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Lex;
impl MonomialOrder for Lex {
fn cmp(&self, lhs: &[usize], rhs: &[usize]) -> std::cmp::Ordering {
lhs.cmp(rhs)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Grevlex;
impl MonomialOrder for Grevlex {
fn cmp(&self, 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, Default)]
pub struct Grlex;
impl MonomialOrder for Grlex {
fn cmp(&self, 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 WeightOrder {
weights: SmallVec<[i64; 4]>,
}
impl WeightOrder {
pub fn new(weights: SmallVec<[i64; 4]>) -> Self {
Self { weights }
}
pub fn from_slice(weights: &[i64]) -> Self {
Self {
weights: SmallVec::from_slice(weights),
}
}
}
impl Default for WeightOrder {
fn default() -> Self {
Self {
weights: smallvec::smallvec![1; 4],
}
}
}
impl MonomialOrder for WeightOrder {
fn cmp(&self, lhs: &[usize], rhs: &[usize]) -> std::cmp::Ordering {
let w_lhs: i64 = lhs
.iter()
.zip(self.weights.iter())
.map(|(&e, &w)| w * e as i64)
.sum();
let w_rhs: i64 = rhs
.iter()
.zip(self.weights.iter())
.map(|(&e, &w)| w * e as i64)
.sum();
w_rhs.cmp(&w_lhs)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MatrixOrder {
matrix: Vec<Vec<i64>>,
n_vars: usize,
}
impl MatrixOrder {
pub fn new(matrix: Vec<Vec<i64>>) -> Self {
let n_vars = matrix.len();
debug_assert!(matrix.iter().all(|row| row.len() == n_vars));
Self { matrix, n_vars }
}
pub fn elimination_order(elim_vars: usize, n_vars: usize) -> Self {
debug_assert!(elim_vars <= n_vars);
let mut matrix = vec![vec![0i64; n_vars]; n_vars];
#[allow(clippy::needless_range_loop)]
{
for i in 0..elim_vars {
for j in 0..n_vars {
if j <= i {
matrix[i][j] = (n_vars * n_vars + n_vars) as i64;
} else {
matrix[i][j] = 0;
}
}
}
for i in elim_vars..n_vars {
for j in 0..n_vars {
if j < elim_vars {
matrix[i][j] = 0;
} else {
matrix[i][j] = 1;
}
}
if i > elim_vars {
let rev_idx = n_vars - 1 - (i - elim_vars);
if rev_idx >= elim_vars {
matrix[i][rev_idx] += 1;
}
}
}
}
Self { matrix, n_vars }
}
}
impl Default for MatrixOrder {
fn default() -> Self {
Self {
matrix: vec![vec![1]],
n_vars: 1,
}
}
}
impl MonomialOrder for MatrixOrder {
fn cmp(&self, lhs: &[usize], rhs: &[usize]) -> std::cmp::Ordering {
for row in &self.matrix {
let w_lhs: i64 = lhs
.iter()
.zip(row.iter())
.map(|(&e, &w)| w * e as i64)
.sum();
let w_rhs: i64 = rhs
.iter()
.zip(row.iter())
.map(|(&e, &w)| w * e as i64)
.sum();
match w_lhs.cmp(&w_rhs) {
std::cmp::Ordering::Equal => continue,
ord => return ord,
}
}
std::cmp::Ordering::Equal
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SubOrder {
Lex,
Grevlex,
Grlex,
}
impl SubOrder {
fn cmp_block(&self, lhs: &[usize], rhs: &[usize]) -> std::cmp::Ordering {
match self {
SubOrder::Lex => lhs.cmp(rhs),
SubOrder::Grevlex => {
let deg_l: usize = lhs.iter().sum();
let deg_r: usize = rhs.iter().sum();
deg_r
.cmp(°_l)
.then_with(|| rhs.iter().rev().cmp(lhs.iter().rev()))
}
SubOrder::Grlex => {
let deg_l: usize = lhs.iter().sum();
let deg_r: usize = rhs.iter().sum();
deg_r.cmp(°_l).then_with(|| lhs.cmp(rhs))
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BlockOrder {
boundaries: SmallVec<[usize; 4]>,
orders: SmallVec<[SubOrder; 4]>,
}
impl BlockOrder {
pub fn new(boundaries: SmallVec<[usize; 4]>, orders: SmallVec<[SubOrder; 4]>) -> Self {
debug_assert_eq!(orders.len(), boundaries.len() + 1);
Self { boundaries, orders }
}
}
impl Default for BlockOrder {
fn default() -> Self {
Self {
boundaries: SmallVec::new(),
orders: smallvec::smallvec![SubOrder::Grevlex],
}
}
}
impl MonomialOrder for BlockOrder {
fn cmp(&self, lhs: &[usize], rhs: &[usize]) -> std::cmp::Ordering {
let mut start = 0;
for (i, &end) in self.boundaries.iter().enumerate() {
match self.orders[i].cmp_block(&lhs[start..end], &rhs[start..end]) {
std::cmp::Ordering::Equal => {}
ord => return ord,
}
start = end;
}
self.orders[self.boundaries.len()].cmp_block(&lhs[start..], &rhs[start..])
}
}
#[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,
pub order: O,
}
impl<D: Domain, O: MonomialOrder> SparseMultivariatePolynomial<D, O> {
pub fn new(domain: D, n_vars: usize) -> Self {
Self {
terms: HashMap::default(),
domain,
n_vars,
order: O::default(),
}
}
pub fn new_with_order(domain: D, n_vars: usize, order: O) -> Self {
Self {
terms: HashMap::default(),
domain,
n_vars,
order,
}
}
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, _)| self.order.cmp(a, b));
terms
}
pub fn leading_term(&self) -> Option<(&SmallVec<[usize; 4]>, &D::Element)> {
self.terms
.iter()
.max_by(|(a, _), (b, _)| self.order.cmp(a, b))
}
pub fn leading_monomial(&self) -> Option<&SmallVec<[usize; 4]>> {
self.terms.keys().max_by(|a, b| self.order.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(&rem_exp, &div_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)
}
pub fn coeff_of_var_pow(&self, var_index: usize, pow: usize) -> Self {
let mut result = Self::new(self.domain.clone(), self.n_vars);
for (exp, coeff) in &self.terms {
if exp.get(var_index).copied().unwrap_or(0) == pow {
let mut new_exp = exp.clone();
if var_index < new_exp.len() {
new_exp[var_index] = 0;
}
result.terms.insert(new_exp, coeff.clone());
}
}
result
}
pub fn leading_coeff_in(&self, var_index: usize) -> Self {
self.coeff_of_var_pow(var_index, self.degree_in(var_index))
}
pub fn derivative(&self, var_index: usize) -> Self {
let mut result = Self::new(self.domain.clone(), self.n_vars);
for (exp, coeff) in &self.terms {
let power = exp.get(var_index).copied().unwrap_or(0);
if power == 0 {
continue;
}
let mut new_exp = exp.clone();
new_exp[var_index] = power - 1;
let scalar = self.domain.cast_u64(power as u64);
let new_coeff = self.domain.mul(coeff, &scalar);
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
}
pub fn taylor_coefficients(&self, var_index: usize, a: &D::Element) -> Vec<Self> {
let d = self.degree_in(var_index);
let mut coeffs = vec![Self::new(self.domain.clone(), self.n_vars); d + 1];
for (exp, coeff) in &self.terms {
let e = exp.get(var_index).copied().unwrap_or(0);
let mut base_exp = exp.clone();
if var_index < base_exp.len() {
base_exp[var_index] = 0;
}
for (j, t_j) in coeffs.iter_mut().enumerate().take(e + 1) {
let binom = self.domain.cast_u64(binomial(e, j));
let a_pow = self.domain.pow(a, (e - j) as u64);
let contrib = self.domain.mul(coeff, &self.domain.mul(&binom, &a_pow));
let existing = t_j
.terms
.get(&base_exp)
.cloned()
.unwrap_or_else(|| self.domain.zero());
let sum = self.domain.add(&existing, &contrib);
if self.domain.is_zero(&sum) {
t_j.terms.remove(&base_exp);
} else {
t_j.terms.insert(base_exp.clone(), sum);
}
}
}
coeffs
}
pub fn drop_main_var(&self) -> Self {
debug_assert!(
self.terms_ref()
.keys()
.all(|e| e.first().copied().unwrap_or(0) == 0),
"drop_main_var: variable 0 must not occur"
);
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 {
if exp.first().copied().unwrap_or(0) != 0 {
continue;
}
let new_exp: SmallVec<[usize; 4]> = exp.iter().skip(1).copied().collect();
result.terms.insert(new_exp, coeff.clone());
}
result
}
pub fn embed_new_main(&self) -> Self {
let new_n_vars = self.n_vars + 1;
let mut result = Self::new(self.domain.clone(), new_n_vars);
for (exp, coeff) in &self.terms {
let mut new_exp = SmallVec::with_capacity(new_n_vars);
new_exp.push(0);
new_exp.extend(exp.iter().copied());
result.terms.insert(new_exp, coeff.clone());
}
result
}
pub fn drop_variable(&self, var_index: usize) -> Self {
assert!(
var_index < self.n_vars,
"var_index ({var_index}) must be < n_vars ({})",
self.n_vars
);
let new_n_vars = self.n_vars - 1;
let mut result = Self::new(self.domain.clone(), new_n_vars);
result.order = self.order.clone();
for (exp, coeff) in &self.terms {
if exp.get(var_index).copied().unwrap_or(0) != 0 {
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]);
}
}
result.terms.insert(new_exp, coeff.clone());
}
result
}
pub fn extend_vars(&self, new_n_vars: usize) -> Self {
assert!(
new_n_vars >= self.n_vars,
"new_n_vars ({new_n_vars}) must be >= self.n_vars ({})",
self.n_vars
);
if new_n_vars == self.n_vars {
return self.clone();
}
let mut result = Self::new(self.domain.clone(), new_n_vars);
result.order = self.order.clone();
for (exp, coeff) in &self.terms {
let mut new_exp = SmallVec::with_capacity(new_n_vars);
new_exp.extend(exp.iter().copied());
new_exp.resize(new_n_vars, 0);
result.terms.insert(new_exp, coeff.clone());
}
result
}
pub fn permute_variables(&self, perm: &[usize]) -> Self {
assert_eq!(perm.len(), self.n_vars, "perm must be a permutation");
let mut result = Self::new(self.domain.clone(), self.n_vars);
for (exp, coeff) in &self.terms {
let mut new_exp = SmallVec::with_capacity(self.n_vars);
for &p in perm {
new_exp.push(exp.get(p).copied().unwrap_or(0));
}
result.terms.insert(new_exp, coeff.clone());
}
result
}
pub fn checked_div_exact(&self, divisor: &Self) -> Option<Self> {
if divisor.is_zero() {
return None;
}
let (quot, rem) = self.div_rem_sparse(divisor);
if rem.is_zero() { Some(quot) } else { None }
}
pub fn eval_keep(&self, var_index: usize, value: &D::Element) -> Self {
let mut result = Self::new(self.domain.clone(), self.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 = exp.clone();
new_exp[var_index] = 0;
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
}
#[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| self.order.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)> {
if self.n_vars() >= 3 {
crate::factor::eez::multivariate_factor_z(self)
} else if self
.leading_coeff_in(0)
.terms_ref()
.keys()
.any(|e| e.iter().skip(1).any(|&d| d > 0))
{
crate::factor::eez::multivariate_factor_z(self)
} else {
bivariate_factor_z(self, 0, 1)
}
}
}
impl SparseMultivariatePolynomial<FiniteField, Lex> {
pub fn factor(&self) -> Vec<(Self, usize)> {
if self.n_vars() >= 3 {
crate::factor::eez::multivariate_factor_fp(self)
} else {
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)
}
pub(crate) fn binomial(n: usize, k: usize) -> u64 {
if k > n {
return 0;
}
if k == 0 || k == n {
return 1;
}
let k = k.min(n - k);
let mut num = 1u64;
let mut den = 1u64;
for i in 0..k {
num *= (n - i) as u64;
den *= (i + 1) as u64;
}
num / den
}
#[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]]);
}
}