use crate::scalar::Rat;
use std::collections::BTreeMap;
#[derive(Clone, Debug)]
pub struct Poly {
pub(crate) terms: BTreeMap<Vec<u8>, Rat>,
pub(crate) num_vars: usize,
}
impl Poly {
pub fn zero(num_vars: usize) -> Self {
Poly {
terms: BTreeMap::new(),
num_vars,
}
}
pub fn num_vars(&self) -> usize {
self.num_vars
}
pub fn terms_iter(&self) -> impl Iterator<Item = (&Vec<u8>, &Rat)> {
self.terms.iter()
}
pub fn constant(r: Rat, num_vars: usize) -> Self {
if r.is_zero() {
return Self::zero(num_vars);
}
let mut terms = BTreeMap::new();
terms.insert(vec![0u8; num_vars], r);
Poly { terms, num_vars }
}
pub fn variable(i: usize, num_vars: usize) -> Self {
let mut exp = vec![0u8; num_vars];
exp[i] = 1;
let mut terms = BTreeMap::new();
terms.insert(exp, Rat::ONE);
Poly { terms, num_vars }
}
pub fn is_zero(&self) -> bool {
self.terms.is_empty()
}
pub fn add_term(&mut self, exp: Vec<u8>, coeff: Rat) {
if coeff.is_zero() {
return;
}
let entry = self.terms.entry(exp).or_insert(Rat::ZERO);
*entry += coeff;
self.clean();
}
pub fn from_term(exp: Vec<u8>, coeff: Rat, num_vars: usize) -> Self {
let mut p = Self::zero(num_vars);
if !coeff.is_zero() {
p.terms.insert(exp, coeff);
}
p
}
pub fn coefficient_of_var(&self, var: usize) -> Rat {
for (exp, &coeff) in &self.terms {
if exp[var] == 1 {
let others: u8 = exp
.iter()
.enumerate()
.filter(|&(i, _)| i != var)
.map(|(_, &e)| e)
.sum();
if others == 0 {
return coeff;
}
}
}
Rat::ZERO
}
pub fn max_degree(&self) -> usize {
self.terms
.keys()
.map(|exp| exp.iter().map(|&e| e as usize).sum::<usize>())
.max()
.unwrap_or(0)
}
fn clean(&mut self) {
self.terms.retain(|_, c| !c.is_zero());
}
fn total_degree(exp: &[u8]) -> u16 {
exp.iter().map(|&e| e as u16).sum()
}
fn cmp_grlex(a: &[u8], b: &[u8]) -> std::cmp::Ordering {
let da = Self::total_degree(a);
let db = Self::total_degree(b);
match da.cmp(&db) {
std::cmp::Ordering::Equal => {
for i in (0..a.len()).rev() {
match a[i].cmp(&b[i]) {
std::cmp::Ordering::Equal => continue,
std::cmp::Ordering::Less => return std::cmp::Ordering::Greater,
std::cmp::Ordering::Greater => return std::cmp::Ordering::Less,
}
}
std::cmp::Ordering::Equal
}
other => other,
}
}
pub fn leading_monomial(&self) -> Option<&Vec<u8>> {
self.terms.keys().max_by(|a, b| Self::cmp_grlex(a, b))
}
pub fn leading_coefficient(&self) -> Rat {
self.leading_monomial()
.and_then(|m| self.terms.get(m))
.copied()
.unwrap_or(Rat::ZERO)
}
pub fn leading_term(&self) -> Option<(Vec<u8>, Rat)> {
self.leading_monomial().map(|m| (m.clone(), self.terms[m]))
}
pub fn make_monic(&mut self) {
let lc = self.leading_coefficient();
if lc.is_zero() || lc == Rat::ONE {
return;
}
let inv = lc.recip();
for c in self.terms.values_mut() {
*c *= inv;
}
}
pub fn monomial_divides(a: &[u8], b: &[u8]) -> bool {
a.iter().zip(b.iter()).all(|(&ai, &bi)| ai <= bi)
}
fn monomial_quotient(a: &[u8], b: &[u8]) -> Vec<u8> {
a.iter().zip(b.iter()).map(|(&ai, &bi)| bi - ai).collect()
}
fn monomial_lcm(a: &[u8], b: &[u8]) -> Vec<u8> {
a.iter()
.zip(b.iter())
.map(|(&ai, &bi)| ai.max(bi))
.collect()
}
pub fn mul_monomial(&self, exp: &[u8], coeff: Rat) -> Self {
let mut result = Poly::zero(self.num_vars);
for (e, c) in &self.terms {
let new_exp: Vec<u8> = e.iter().zip(exp.iter()).map(|(&a, &b)| a + b).collect();
result.terms.insert(new_exp, *c * coeff);
}
result.clean();
result
}
pub fn s_polynomial(&self, other: &Self) -> Self {
let (lt_a, lc_a) = match self.leading_term() {
Some(t) => t,
None => return Poly::zero(self.num_vars),
};
let (lt_b, lc_b) = match other.leading_term() {
Some(t) => t,
None => return Poly::zero(self.num_vars),
};
let lcm = Self::monomial_lcm(<_a, <_b);
let qa = Self::monomial_quotient(<_a, &lcm);
let qb = Self::monomial_quotient(<_b, &lcm);
let left = self.mul_monomial(&qa, lc_b);
let right = other.mul_monomial(&qb, lc_a);
left.sub(&right)
}
pub fn reduce_by(&self, divisor: &Self) -> Self {
let (div_lm, div_lc) = match divisor.leading_term() {
Some(t) => t,
None => return self.clone(),
};
let mut remainder = Poly::zero(self.num_vars);
let mut current = self.clone();
while !current.is_zero() {
let (cur_lm, cur_lc) = match current.leading_term() {
Some(t) => t,
None => break,
};
if Self::monomial_divides(&div_lm, &cur_lm) {
let q_exp = Self::monomial_quotient(&div_lm, &cur_lm);
let q_coeff = cur_lc / div_lc;
let subtrahend = divisor.mul_monomial(&q_exp, q_coeff);
current = current.sub(&subtrahend);
} else {
remainder.terms.insert(cur_lm.clone(), cur_lc);
current.terms.remove(&cur_lm);
}
}
remainder.clean();
remainder
}
pub fn eval(&self, values: &[Rat]) -> Rat {
let mut result = Rat::ZERO;
for (exp, coeff) in &self.terms {
let mut term = *coeff;
for (i, &e) in exp.iter().enumerate() {
for _ in 0..e {
term *= values[i];
}
}
result += term;
}
result
}
}
impl Poly {
pub fn add(&self, other: &Self) -> Self {
let mut result = self.clone();
for (exp, coeff) in &other.terms {
let entry = result.terms.entry(exp.clone()).or_insert(Rat::ZERO);
*entry += *coeff;
}
result.clean();
result
}
pub fn sub(&self, other: &Self) -> Self {
let mut result = self.clone();
for (exp, coeff) in &other.terms {
let entry = result.terms.entry(exp.clone()).or_insert(Rat::ZERO);
*entry -= *coeff;
}
result.clean();
result
}
pub fn mul(&self, other: &Self) -> Self {
let mut result = Poly::zero(self.num_vars);
for (ea, ca) in &self.terms {
for (eb, cb) in &other.terms {
let exp: Vec<u8> = ea.iter().zip(eb.iter()).map(|(&a, &b)| a + b).collect();
let entry = result.terms.entry(exp).or_insert(Rat::ZERO);
*entry += *ca * *cb;
}
}
result.clean();
result
}
pub fn neg(&self) -> Self {
let mut result = self.clone();
for c in result.terms.values_mut() {
*c = -*c;
}
result
}
pub fn scale(&self, r: Rat) -> Self {
if r.is_zero() {
return Poly::zero(self.num_vars);
}
let mut result = self.clone();
for c in result.terms.values_mut() {
*c *= r;
}
result.clean();
result
}
}
impl PartialEq for Poly {
fn eq(&self, other: &Self) -> bool {
self.terms == other.terms
}
}
impl Eq for Poly {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zero_poly() {
let p = Poly::zero(3);
assert!(p.is_zero());
}
#[test]
fn constant() {
let p = Poly::constant(Rat::from(5), 3);
assert!(!p.is_zero());
assert_eq!(p.eval(&[Rat::ZERO, Rat::ZERO, Rat::ZERO]), Rat::from(5));
}
#[test]
fn variable() {
let x = Poly::variable(0, 3);
assert_eq!(x.eval(&[Rat::from(7), Rat::ZERO, Rat::ZERO]), Rat::from(7));
}
#[test]
fn add_polys() {
let x = Poly::variable(0, 2);
let y = Poly::variable(1, 2);
let sum = x.add(&y);
assert_eq!(sum.eval(&[Rat::from(3), Rat::from(4)]), Rat::from(7));
}
#[test]
fn mul_polys() {
let n = 1;
let x = Poly::variable(0, n);
let one = Poly::constant(Rat::ONE, n);
let xp1 = x.add(&one);
let xm1 = x.sub(&one);
let product = xp1.mul(&xm1);
assert_eq!(product.eval(&[Rat::from(3)]), Rat::from(8));
assert_eq!(product.eval(&[Rat::from(1)]), Rat::ZERO);
}
#[test]
fn leading_term_grlex() {
let mut p = Poly::zero(2);
p.terms.insert(vec![2, 1], Rat::from(3)); p.terms.insert(vec![1, 2], Rat::from(2)); let (lm, lc) = p.leading_term().unwrap();
assert_eq!(lm, vec![2, 1]);
assert_eq!(lc, Rat::from(3));
}
#[test]
fn s_polynomial() {
let n = 2;
let f = {
let mut p = Poly::zero(n);
p.terms.insert(vec![2, 0], Rat::ONE); p.terms.insert(vec![0, 0], -Rat::ONE); p
};
let g = {
let mut p = Poly::zero(n);
p.terms.insert(vec![1, 1], Rat::ONE); p.terms.insert(vec![0, 0], -Rat::ONE); p
};
let s = f.s_polynomial(&g);
assert_eq!(s.eval(&[Rat::from(5), Rat::from(3)]), Rat::from(2)); }
#[test]
fn reduce_by_simple() {
let n = 1;
let x_sq = {
let mut p = Poly::zero(n);
p.terms.insert(vec![2], Rat::ONE);
p
};
let x_minus_1 = {
let mut p = Poly::zero(n);
p.terms.insert(vec![1], Rat::ONE);
p.terms.insert(vec![0], -Rat::ONE);
p
};
let rem = x_sq.reduce_by(&x_minus_1);
assert_eq!(rem.eval(&[Rat::from(99)]), Rat::ONE);
}
#[test]
fn monomial_divides() {
assert!(Poly::monomial_divides(&vec![1, 0], &vec![2, 1])); assert!(!Poly::monomial_divides(&vec![0, 2], &vec![1, 1])); }
#[test]
fn eval_multivariate() {
let mut p = Poly::zero(2);
p.terms.insert(vec![2, 0], Rat::ONE);
p.terms.insert(vec![1, 1], Rat::from(2));
p.terms.insert(vec![0, 2], Rat::ONE);
assert_eq!(p.eval(&[Rat::from(3), Rat::from(4)]), Rat::from(49)); }
#[test]
fn neg_poly() {
let x = Poly::variable(0, 1);
let nx = x.neg();
assert_eq!(nx.eval(&[Rat::from(5)]), Rat::from(-5));
}
#[test]
fn make_monic() {
let mut p = Poly::zero(1);
p.terms.insert(vec![2], Rat::from(3)); p.terms.insert(vec![0], Rat::from(6)); p.make_monic();
assert_eq!(p.leading_coefficient(), Rat::ONE);
assert_eq!(p.eval(&[Rat::ZERO]), Rat::from(2));
}
}