use num_bigint::BigInt;
use num_rational::Ratio;
use num_traits::{One, Zero};
use std::collections::BTreeMap;
use std::fmt;
use std::ops;
pub trait MonomialOrd: 'static + Clone + Send + Sync + std::fmt::Debug {
fn cmp_exponents(a: &[u32], b: &[u32]) -> std::cmp::Ordering;
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct GrevLex;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Lex;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct GrLex;
impl MonomialOrd for GrevLex {
fn cmp_exponents(a: &[u32], b: &[u32]) -> std::cmp::Ordering {
let deg_a: u32 = a.iter().sum();
let deg_b: u32 = b.iter().sum();
deg_a.cmp(°_b).then_with(|| {
for (ai, bi) in a.iter().rev().zip(b.iter().rev()) {
match bi.cmp(ai) {
std::cmp::Ordering::Equal => continue,
other => return other,
}
}
std::cmp::Ordering::Equal
})
}
}
impl MonomialOrd for Lex {
fn cmp_exponents(a: &[u32], b: &[u32]) -> std::cmp::Ordering {
for (ai, bi) in a.iter().zip(b.iter()) {
match ai.cmp(bi) {
std::cmp::Ordering::Equal => continue,
other => return other,
}
}
std::cmp::Ordering::Equal
}
}
impl MonomialOrd for GrLex {
fn cmp_exponents(a: &[u32], b: &[u32]) -> std::cmp::Ordering {
let deg_a: u32 = a.iter().sum();
let deg_b: u32 = b.iter().sum();
deg_a.cmp(°_b).then_with(|| Lex::cmp_exponents(a, b))
}
}
#[derive(Clone, Debug)]
pub struct MonoKey<O: MonomialOrd> {
pub exponents: Vec<u32>,
_phantom: std::marker::PhantomData<O>,
}
impl<O: MonomialOrd> MonoKey<O> {
pub fn new(exponents: Vec<u32>) -> Self {
Self {
exponents,
_phantom: std::marker::PhantomData,
}
}
}
impl<O: MonomialOrd> PartialEq for MonoKey<O> {
fn eq(&self, other: &Self) -> bool {
self.exponents == other.exponents
}
}
impl<O: MonomialOrd> Eq for MonoKey<O> {}
impl<O: MonomialOrd> PartialOrd for MonoKey<O> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<O: MonomialOrd> Ord for MonoKey<O> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
O::cmp_exponents(&self.exponents, &other.exponents)
}
}
impl<O: MonomialOrd> std::hash::Hash for MonoKey<O> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.exponents.hash(state);
}
}
pub type Exponent = Vec<u32>;
#[derive(Clone, Debug)]
pub struct MultiPoly<O: MonomialOrd = GrevLex> {
num_vars: usize,
terms: BTreeMap<MonoKey<O>, Ratio<BigInt>>,
}
impl<O: MonomialOrd> PartialEq for MultiPoly<O> {
fn eq(&self, other: &Self) -> bool {
self.num_vars == other.num_vars && self.terms == other.terms
}
}
impl<O: MonomialOrd> Eq for MultiPoly<O> {}
fn rat(n: i64) -> Ratio<BigInt> {
Ratio::from_integer(BigInt::from(n))
}
pub fn monomial_lcm(a: &[u32], b: &[u32]) -> Vec<u32> {
a.iter()
.zip(b.iter())
.map(|(&ai, &bi)| ai.max(bi))
.collect()
}
pub fn monomial_divides(a: &[u32], b: &[u32]) -> bool {
a.iter().zip(b.iter()).all(|(&ai, &bi)| ai <= bi)
}
pub fn monomial_div(a: &[u32], b: &[u32]) -> Option<Vec<u32>> {
if !monomial_divides(a, b) {
return None;
}
Some(a.iter().zip(b.iter()).map(|(&ai, &bi)| bi - ai).collect())
}
pub fn monomial_mul(a: &[u32], b: &[u32]) -> Vec<u32> {
a.iter().zip(b.iter()).map(|(&ai, &bi)| ai + bi).collect()
}
pub fn monomial_coprime(a: &[u32], b: &[u32]) -> bool {
a.iter().zip(b.iter()).all(|(&ai, &bi)| ai == 0 || bi == 0)
}
impl<O: MonomialOrd> MultiPoly<O> {
pub fn zero(num_vars: usize) -> Self {
MultiPoly {
num_vars,
terms: BTreeMap::new(),
}
}
pub fn constant(num_vars: usize, c: Ratio<BigInt>) -> Self {
let mut p = Self::zero(num_vars);
if !c.is_zero() {
p.terms.insert(MonoKey::new(vec![0; num_vars]), c);
}
p
}
pub fn from_int(num_vars: usize, n: i64) -> Self {
Self::constant(num_vars, rat(n))
}
pub fn var(num_vars: usize, var_index: usize) -> Self {
assert!(
var_index < num_vars,
"var_index {var_index} out of range for {num_vars} variables"
);
let mut exp = vec![0u32; num_vars];
exp[var_index] = 1;
let mut terms = BTreeMap::new();
terms.insert(MonoKey::new(exp), Ratio::one());
MultiPoly { num_vars, terms }
}
pub fn monomial(c: Ratio<BigInt>, exponents: Exponent) -> Self {
let num_vars = exponents.len();
let mut p = Self::zero(num_vars);
if !c.is_zero() {
p.terms.insert(MonoKey::new(exponents), c);
}
p
}
fn insert_term(&mut self, exp: Vec<u32>, coeff: Ratio<BigInt>) {
if coeff.is_zero() {
return;
}
let key = MonoKey::new(exp);
let entry = self
.terms
.entry(key)
.or_insert_with(|| Ratio::from_integer(BigInt::from(0)));
*entry += coeff;
}
pub fn coeff(&self, exp: &[u32]) -> Option<&Ratio<BigInt>> {
if exp.len() != self.num_vars {
return None;
}
self.terms.get(&MonoKey::<O>::new(exp.to_vec()))
}
pub fn from_terms(num_vars: usize, terms: Vec<(Vec<u32>, Ratio<BigInt>)>) -> Option<Self> {
let mut p = Self::zero(num_vars);
for (exp, c) in terms {
if exp.len() != num_vars {
return None;
}
p.insert_term(exp, c);
}
p.prune();
Some(p)
}
pub fn map_coeffs(&self, mut f: impl FnMut(&Ratio<BigInt>) -> Ratio<BigInt>) -> Self {
let mut terms = BTreeMap::new();
for (k, c) in &self.terms {
let nc = f(c);
if !nc.is_zero() {
terms.insert(k.clone(), nc);
}
}
MultiPoly {
num_vars: self.num_vars,
terms,
}
}
fn prune(&mut self) {
self.terms.retain(|_, c| !c.is_zero());
}
fn assert_compatible(&self, other: &MultiPoly<O>) {
assert_eq!(
self.num_vars, other.num_vars,
"MultiPoly: incompatible variable counts ({} vs {})",
self.num_vars, other.num_vars
);
}
}
impl<O: MonomialOrd> MultiPoly<O> {
pub fn is_zero(&self) -> bool {
self.terms.is_empty()
}
pub fn num_vars(&self) -> usize {
self.num_vars
}
pub fn num_terms(&self) -> usize {
self.terms.len()
}
pub fn total_degree(&self) -> Option<u32> {
self.terms.keys().map(|k| k.exponents.iter().sum()).max()
}
pub fn degree_in(&self, var_index: usize) -> u32 {
assert!(
var_index < self.num_vars,
"var_index {var_index} out of range for {} variables",
self.num_vars
);
self.terms
.keys()
.map(|k| k.exponents[var_index])
.max()
.unwrap_or(0)
}
pub fn leading_term(&self) -> Option<(&[u32], &Ratio<BigInt>)> {
self.terms
.last_key_value()
.map(|(k, v)| (k.exponents.as_slice(), v))
}
pub fn leading_monomial(&self) -> Option<&[u32]> {
self.terms
.last_key_value()
.map(|(k, _)| k.exponents.as_slice())
}
pub fn leading_coeff(&self) -> Option<&Ratio<BigInt>> {
self.terms.last_key_value().map(|(_, v)| v)
}
pub fn terms(&self) -> impl Iterator<Item = (&[u32], &Ratio<BigInt>)> {
self.terms.iter().map(|(k, v)| (k.exponents.as_slice(), v))
}
pub fn convert_order<B: MonomialOrd>(&self) -> MultiPoly<B> {
let mut new_terms = BTreeMap::new();
for (key, coeff) in &self.terms {
new_terms.insert(MonoKey::<B>::new(key.exponents.clone()), coeff.clone());
}
MultiPoly {
num_vars: self.num_vars,
terms: new_terms,
}
}
}
impl<O: MonomialOrd> MultiPoly<O> {
pub fn eval(&self, values: &[Ratio<BigInt>]) -> Ratio<BigInt> {
assert_eq!(
values.len(),
self.num_vars,
"eval: expected {} values, got {}",
self.num_vars,
values.len()
);
let mut result = Ratio::from_integer(BigInt::from(0));
for (key, coeff) in &self.terms {
let mut term_val = coeff.clone();
for (i, &e) in key.exponents.iter().enumerate() {
if e > 0 {
term_val *= pow_ratio(&values[i], e);
}
}
result += term_val;
}
result
}
}
fn pow_ratio(base: &Ratio<BigInt>, exp: u32) -> Ratio<BigInt> {
let mut result = Ratio::one();
for _ in 0..exp {
result *= base;
}
result
}
impl<O: MonomialOrd> MultiPoly<O> {
pub fn partial_derivative(&self, var_index: usize) -> MultiPoly<O> {
assert!(
var_index < self.num_vars,
"partial_derivative: var_index {var_index} out of range for {} variables",
self.num_vars
);
let mut result = Self::zero(self.num_vars);
for (key, coeff) in &self.terms {
let e_i = key.exponents[var_index];
if e_i == 0 {
continue; }
let new_coeff = coeff * Ratio::from_integer(BigInt::from(e_i));
let mut new_exp = key.exponents.clone();
new_exp[var_index] -= 1;
result.terms.insert(MonoKey::new(new_exp), new_coeff);
}
result
}
pub fn substitute(&self, var_index: usize, value: &Ratio<BigInt>) -> MultiPoly<O> {
assert!(
var_index < self.num_vars,
"substitute: var_index {var_index} out of range for {} variables",
self.num_vars
);
assert!(
self.num_vars > 0,
"substitute: cannot reduce below 0 variables"
);
let new_num_vars = self.num_vars - 1;
let mut result = Self::zero(new_num_vars);
for (key, coeff) in &self.terms {
let e_i = key.exponents[var_index];
let val_pow = pow_ratio(value, e_i);
let new_coeff = coeff * val_pow;
if new_coeff.is_zero() {
continue;
}
let mut new_exp = Vec::with_capacity(new_num_vars);
for (j, &ej) in key.exponents.iter().enumerate() {
if j != var_index {
new_exp.push(ej);
}
}
result.insert_term(new_exp, new_coeff);
}
result.prune();
result
}
}
impl<O: MonomialOrd> MultiPoly<O> {
pub fn add(&self, other: &MultiPoly<O>) -> MultiPoly<O> {
self.assert_compatible(other);
let mut result = self.clone();
for (key, coeff) in &other.terms {
result.insert_term(key.exponents.clone(), coeff.clone());
}
result.prune();
result
}
pub fn sub(&self, other: &MultiPoly<O>) -> MultiPoly<O> {
self.assert_compatible(other);
let mut result = self.clone();
for (key, coeff) in &other.terms {
result.insert_term(key.exponents.clone(), -coeff.clone());
}
result.prune();
result
}
pub fn neg(&self) -> MultiPoly<O> {
let terms = self
.terms
.iter()
.map(|(k, c)| (k.clone(), -c.clone()))
.collect();
MultiPoly {
num_vars: self.num_vars,
terms,
}
}
pub fn mul(&self, other: &MultiPoly<O>) -> MultiPoly<O> {
self.assert_compatible(other);
let mut result = Self::zero(self.num_vars);
for (key_a, coeff_a) in &self.terms {
for (key_b, coeff_b) in &other.terms {
let new_coeff = coeff_a * coeff_b;
let new_exp: Vec<u32> = key_a
.exponents
.iter()
.zip(key_b.exponents.iter())
.map(|(&a, &b)| a + b)
.collect();
result.insert_term(new_exp, new_coeff);
}
}
result.prune();
result
}
pub fn scale(&self, c: &Ratio<BigInt>) -> MultiPoly<O> {
if c.is_zero() {
return Self::zero(self.num_vars);
}
let terms = self
.terms
.iter()
.map(|(k, coeff)| (k.clone(), coeff * c))
.collect();
MultiPoly {
num_vars: self.num_vars,
terms,
}
}
pub fn mul_monomial(&self, coeff: &Ratio<BigInt>, exp: &[u32]) -> Self {
if coeff.is_zero() {
return Self::zero(self.num_vars);
}
let mut result = BTreeMap::new();
for (key, c) in &self.terms {
let new_exp = monomial_mul(&key.exponents, exp);
let new_coeff = c * coeff;
if !new_coeff.is_zero() {
result.insert(MonoKey::new(new_exp), new_coeff);
}
}
MultiPoly {
num_vars: self.num_vars,
terms: result,
}
}
}
impl<O: MonomialOrd> MultiPoly<O> {
pub fn monic(&self) -> Self {
if self.is_zero() {
return self.clone();
}
let lc = self.leading_coeff().unwrap().clone();
self.scale(&(Ratio::one() / lc))
}
pub fn primitive_part_q(&self) -> Self {
if self.is_zero() {
return self.clone();
}
let mut denom_lcm = BigInt::one();
for (_, coeff) in self.terms() {
denom_lcm = num_integer::lcm(denom_lcm, coeff.denom().clone());
}
let scale_factor = Ratio::from_integer(denom_lcm);
let integer_poly = self.scale(&scale_factor);
let mut content = BigInt::zero();
for (_, coeff) in integer_poly.terms() {
content = num_integer::gcd(content, coeff.numer().clone());
}
if content.is_zero() || content.is_one() {
return integer_poly;
}
integer_poly.scale(&Ratio::new(BigInt::one(), content))
}
}
const HEUGCD_MAX_TRIES: usize = 6;
fn symmetric_mod(c: &BigInt, m: &BigInt) -> BigInt {
use num_integer::Integer;
let r = c.mod_floor(m);
if &r + &r > *m { r - m } else { r }
}
impl<O: MonomialOrd> MultiPoly<O> {
pub fn integer_content(&self) -> BigInt {
let mut g = BigInt::zero();
for (_, c) in self.terms() {
g = num_integer::gcd(g, c.numer().clone());
if g.is_one() {
break;
}
}
g
}
pub fn clear_denominators(&self) -> (BigInt, Self) {
let mut d = BigInt::one();
for (_, c) in self.terms() {
d = num_integer::lcm(d, c.denom().clone());
}
if d.is_one() {
return (d, self.clone());
}
let scaled = self.scale(&Ratio::from_integer(d.clone()));
(d, scaled)
}
fn max_norm(&self) -> BigInt {
let mut m = BigInt::zero();
for (_, c) in self.terms() {
let a = num_traits::Signed::abs(c.numer());
if a > m {
m = a;
}
}
m
}
fn leading_is_negative(&self) -> bool {
self.leading_coeff()
.is_some_and(num_traits::Signed::is_negative)
}
fn normalized_over_z(&self) -> Self {
let (_, z) = self.clear_denominators();
if z.leading_is_negative() { z.neg() } else { z }
}
pub fn gcd(a: &Self, b: &Self) -> Self {
if a.num_vars != b.num_vars {
return Self::from_int(a.num_vars, 1);
}
match (a.is_zero(), b.is_zero()) {
(true, true) => return Self::zero(a.num_vars),
(true, false) => return b.normalized_over_z(),
(false, true) => return a.normalized_over_z(),
(false, false) => {}
}
let (_, az) = a.clear_denominators();
let (_, bz) = b.clear_denominators();
match Self::heugcd_z(&az, &bz, 0) {
Some(h) => {
if h.leading_is_negative() {
h.neg()
} else {
h
}
}
None => Self::from_int(a.num_vars, 1),
}
}
pub fn lcm(a: &Self, b: &Self) -> Self {
if a.is_zero() || b.is_zero() {
return Self::zero(a.num_vars);
}
let g = Self::gcd(a, b);
let az = a.normalized_over_z();
let bz = b.normalized_over_z();
let prod = az.mul(&bz);
match prod.div_exact(&g) {
Some(l) => l,
None => prod,
}
}
fn heugcd_z(f: &Self, g: &Self, depth: usize) -> Option<Self> {
let nv = f.num_vars;
let cf = f.integer_content();
let cg = g.integer_content();
if cf.is_zero() || cg.is_zero() {
return None;
}
let c = num_integer::gcd(cf.clone(), cg.clone());
let inv_cf = Ratio::new(BigInt::one(), cf);
let inv_cg = Ratio::new(BigInt::one(), cg);
let f = f.scale(&inv_cf);
let g = g.scale(&inv_cg);
let c_rat = Ratio::from_integer(c);
if nv == 0 {
return Some(Self::constant(0, c_rat));
}
if f.total_degree() == Some(0) || g.total_degree() == Some(0) {
return Some(Self::constant(nv, c_rat));
}
if f == g || f == g.neg() {
return Some(f.scale(&c_rat));
}
if g.div_exact(&f).is_some() {
return Some(f.scale(&c_rat));
}
if f.div_exact(&g).is_some() {
return Some(g.scale(&c_rat));
}
if depth > nv + 1 {
return None;
}
let var = nv - 1;
let f_norm = f.max_norm();
let g_norm = g.max_norm();
let two = BigInt::from(2);
let mut xi: BigInt = &two * f_norm.min(g_norm) + BigInt::from(29);
for _ in 0..HEUGCD_MAX_TRIES {
if let Some(h) = Self::heugcd_attempt(&f, &g, var, &xi, depth) {
return Some(h.scale(&c_rat));
}
xi = Self::next_xi(&xi);
}
None
}
fn next_xi(xi: &BigInt) -> BigInt {
let root4 = xi.sqrt().sqrt().max(BigInt::from(2));
(BigInt::from(73794) * xi * root4) / BigInt::from(27011)
}
fn heugcd_attempt(f: &Self, g: &Self, var: usize, xi: &BigInt, depth: usize) -> Option<Self> {
let xi_rat = Ratio::from_integer(xi.clone());
let ff = f.substitute(var, &xi_rat);
let gg = g.substitute(var, &xi_rat);
if ff.is_zero() || gg.is_zero() {
return None;
}
let h = Self::heugcd_z(&ff, &gg, depth + 1)?;
let h = Self::interpolate_xi(&h, xi, var);
if h.is_zero() {
return None;
}
let content = h.integer_content();
if content.is_zero() {
return None;
}
let h = h.scale(&Ratio::new(BigInt::one(), content));
if f.div_exact(&h).is_some() && g.div_exact(&h).is_some() {
Some(h)
} else {
None
}
}
fn interpolate_xi(h: &Self, xi: &BigInt, var: usize) -> Self {
let nv = h.num_vars + 1;
let mut result = Self::zero(nv);
let mut rest = h.clone();
let mut i: u32 = 0;
let xi_rat = Ratio::from_integer(xi.clone());
while !rest.is_zero() {
if i > 4096 || rest.terms().any(|(_, c)| !c.is_integer()) {
return Self::zero(nv);
}
let digit = rest.map_coeffs(|c| Ratio::from_integer(symmetric_mod(c.numer(), xi)));
for (exp, c) in digit.terms() {
let mut e = Vec::with_capacity(nv);
e.extend_from_slice(&exp[..var]);
e.push(i);
e.extend_from_slice(&exp[var..]);
result.insert_term(e, c.clone());
}
rest = rest.sub(&digit).map_coeffs(|c| c / &xi_rat);
i += 1;
}
result.prune();
result
}
}
impl<O: MonomialOrd> MultiPoly<O> {
pub fn reduce(&self, divisors: &[&MultiPoly<O>]) -> MultiPoly<O> {
if self.is_zero() || divisors.is_empty() {
return self.clone();
}
let mut remainder = MultiPoly::zero(self.num_vars);
let mut p = self.clone();
while !p.is_zero() {
let mut divided = false;
let (lt_exp, lt_coeff) = p.leading_term().unwrap();
let lt_exp = lt_exp.to_vec();
let lt_coeff = lt_coeff.clone();
for divisor in divisors {
if divisor.is_zero() {
continue;
}
let (div_lt_exp, div_lt_coeff) = divisor.leading_term().unwrap();
if monomial_divides(div_lt_exp, <_exp) {
let quot_exp = monomial_div(div_lt_exp, <_exp).unwrap();
let quot_coeff = <_coeff / div_lt_coeff;
let subtrahend = divisor.mul_monomial("_coeff, "_exp);
p = p.sub(&subtrahend);
divided = true;
break;
}
}
if !divided {
remainder.insert_term(lt_exp.clone(), lt_coeff);
p.terms.remove(&MonoKey::<O>::new(lt_exp));
}
}
remainder
}
pub fn div_exact(&self, divisor: &MultiPoly<O>) -> Option<MultiPoly<O>> {
self.assert_compatible(divisor);
let (div_lt_exp, div_lt_coeff) = divisor.leading_term()?;
let div_lt_exp = div_lt_exp.to_vec();
let div_lt_coeff = div_lt_coeff.clone();
let mut quotient = MultiPoly::zero(self.num_vars);
let mut p = self.clone();
while let Some((lt_exp, lt_coeff)) = p.leading_term() {
let quot_exp = monomial_div(&div_lt_exp, lt_exp)?;
let quot_coeff = lt_coeff / &div_lt_coeff;
let subtrahend = divisor.mul_monomial("_coeff, "_exp);
quotient.insert_term(quot_exp, quot_coeff);
p = p.sub(&subtrahend);
}
Some(quotient)
}
pub fn monomial_content(&self) -> Vec<u32> {
let mut min: Option<Vec<u32>> = None;
for (exp, _) in self.terms() {
match &mut min {
None => min = Some(exp.to_vec()),
Some(m) => {
for (mi, &e) in m.iter_mut().zip(exp) {
*mi = (*mi).min(e);
}
}
}
}
min.unwrap_or_else(|| vec![0; self.num_vars])
}
pub fn variables_present(&self) -> Vec<usize> {
(0..self.num_vars)
.filter(|&i| self.terms.keys().any(|k| k.exponents[i] > 0))
.collect()
}
}
pub fn s_polynomial<O: MonomialOrd>(f: &MultiPoly<O>, g: &MultiPoly<O>) -> MultiPoly<O> {
assert_eq!(f.num_vars(), g.num_vars());
if f.is_zero() || g.is_zero() {
return MultiPoly::zero(f.num_vars());
}
let (lm_f, lc_f) = f.leading_term().unwrap();
let (lm_g, lc_g) = g.leading_term().unwrap();
let lcm = monomial_lcm(lm_f, lm_g);
let quot_f = monomial_div(lm_f, &lcm).unwrap();
let quot_g = monomial_div(lm_g, &lcm).unwrap();
let coeff_f = Ratio::one() / lc_f;
let coeff_g = Ratio::one() / lc_g;
let scaled_f = f.mul_monomial(&coeff_f, "_f);
let scaled_g = g.mul_monomial(&coeff_g, "_g);
scaled_f.sub(&scaled_g)
}
impl<O: MonomialOrd> fmt::Display for MultiPoly<O> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_zero() {
return write!(f, "0");
}
let mut first = true;
for (key, coeff) in self.terms.iter().rev() {
let exp = &key.exponents;
let is_constant = exp.iter().all(|&e| e == 0);
let coeff_is_one = *coeff == Ratio::one();
let coeff_is_neg_one = *coeff == -Ratio::<BigInt>::one();
let is_negative = coeff < &Ratio::from_integer(BigInt::from(0));
if first {
if is_constant {
write!(f, "{coeff}")?;
} else if coeff_is_one {
write!(f, "{}", format_monomial_vars(exp))?;
} else if coeff_is_neg_one {
write!(f, "-{}", format_monomial_vars(exp))?;
} else {
write!(f, "{}*{}", coeff, format_monomial_vars(exp))?;
}
} else if is_constant {
if is_negative {
write!(f, " - {}", -coeff.clone())?;
} else {
write!(f, " + {coeff}")?;
}
} else if coeff_is_one {
write!(f, " + {}", format_monomial_vars(exp))?;
} else if coeff_is_neg_one {
write!(f, " - {}", format_monomial_vars(exp))?;
} else if is_negative {
let pos = -coeff.clone();
write!(f, " - {}*{}", pos, format_monomial_vars(exp))?;
} else {
write!(f, " + {}*{}", coeff, format_monomial_vars(exp))?;
}
first = false;
}
Ok(())
}
}
fn format_monomial_vars(exp: &[u32]) -> String {
let mut parts = Vec::new();
for (i, &e) in exp.iter().enumerate() {
if e == 0 {
continue;
} else if e == 1 {
parts.push(format!("x{i}"));
} else {
parts.push(format!("x{i}^{e}"));
}
}
if parts.is_empty() {
"1".to_string()
} else {
parts.join("*")
}
}
impl<O: MonomialOrd> ops::Add for &MultiPoly<O> {
type Output = MultiPoly<O>;
fn add(self, rhs: &MultiPoly<O>) -> MultiPoly<O> {
MultiPoly::add(self, rhs)
}
}
impl<O: MonomialOrd> ops::Sub for &MultiPoly<O> {
type Output = MultiPoly<O>;
fn sub(self, rhs: &MultiPoly<O>) -> MultiPoly<O> {
MultiPoly::sub(self, rhs)
}
}
impl<O: MonomialOrd> ops::Mul for &MultiPoly<O> {
type Output = MultiPoly<O>;
fn mul(self, rhs: &MultiPoly<O>) -> MultiPoly<O> {
MultiPoly::mul(self, rhs)
}
}
impl<O: MonomialOrd> ops::Neg for &MultiPoly<O> {
type Output = MultiPoly<O>;
fn neg(self) -> MultiPoly<O> {
MultiPoly::neg(self)
}
}
impl<O: MonomialOrd> ops::Add for MultiPoly<O> {
type Output = MultiPoly<O>;
fn add(self, rhs: MultiPoly<O>) -> MultiPoly<O> {
MultiPoly::add(&self, &rhs)
}
}
impl<O: MonomialOrd> ops::Sub for MultiPoly<O> {
type Output = MultiPoly<O>;
fn sub(self, rhs: MultiPoly<O>) -> MultiPoly<O> {
MultiPoly::sub(&self, &rhs)
}
}
impl<O: MonomialOrd> ops::Mul for MultiPoly<O> {
type Output = MultiPoly<O>;
fn mul(self, rhs: MultiPoly<O>) -> MultiPoly<O> {
MultiPoly::mul(&self, &rhs)
}
}
impl<O: MonomialOrd> ops::Neg for MultiPoly<O> {
type Output = MultiPoly<O>;
fn neg(self) -> MultiPoly<O> {
MultiPoly::neg(&self)
}
}
impl<O: MonomialOrd> ops::Add<i64> for &MultiPoly<O> {
type Output = MultiPoly<O>;
fn add(self, rhs: i64) -> MultiPoly<O> {
let c = MultiPoly::from_int(self.num_vars(), rhs);
MultiPoly::add(self, &c)
}
}
impl<O: MonomialOrd> ops::Add<i64> for MultiPoly<O> {
type Output = MultiPoly<O>;
fn add(self, rhs: i64) -> MultiPoly<O> {
(&self) + rhs
}
}
impl<O: MonomialOrd> ops::Sub<i64> for &MultiPoly<O> {
type Output = MultiPoly<O>;
fn sub(self, rhs: i64) -> MultiPoly<O> {
let c = MultiPoly::from_int(self.num_vars(), rhs);
MultiPoly::sub(self, &c)
}
}
impl<O: MonomialOrd> ops::Sub<i64> for MultiPoly<O> {
type Output = MultiPoly<O>;
fn sub(self, rhs: i64) -> MultiPoly<O> {
(&self) - rhs
}
}
impl<O: MonomialOrd> ops::Mul<i64> for &MultiPoly<O> {
type Output = MultiPoly<O>;
fn mul(self, rhs: i64) -> MultiPoly<O> {
let c = Ratio::from_integer(BigInt::from(rhs));
self.scale(&c)
}
}
impl<O: MonomialOrd> ops::Mul<i64> for MultiPoly<O> {
type Output = MultiPoly<O>;
fn mul(self, rhs: i64) -> MultiPoly<O> {
(&self) * rhs
}
}
pub fn multipoly_vars<O: MonomialOrd>(num_vars: usize) -> Vec<MultiPoly<O>> {
(0..num_vars).map(|i| MultiPoly::var(num_vars, i)).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn grevlex_ordering() {
assert_eq!(
GrevLex::cmp_exponents(&[2, 0], &[1, 1]),
std::cmp::Ordering::Greater
);
assert_eq!(
GrevLex::cmp_exponents(&[1, 1], &[0, 2]),
std::cmp::Ordering::Greater
);
assert_eq!(
GrevLex::cmp_exponents(&[3, 0], &[1, 1]),
std::cmp::Ordering::Greater
);
}
#[test]
fn zero_is_zero() {
let z: MultiPoly<GrevLex> = MultiPoly::zero(3);
assert!(z.is_zero());
assert_eq!(z.num_terms(), 0);
assert_eq!(z.total_degree(), None);
}
#[test]
fn constant_round_trip() {
let c: MultiPoly<GrevLex> = MultiPoly::from_int(2, 42);
assert!(!c.is_zero());
assert_eq!(c.num_terms(), 1);
assert_eq!(c.total_degree(), Some(0));
assert_eq!(c.eval(&[rat(0), rat(0)]), rat(42));
}
fn vars2() -> (MultiPoly<GrevLex>, MultiPoly<GrevLex>) {
(MultiPoly::var(2, 0), MultiPoly::var(2, 1))
}
fn pow(p: &MultiPoly<GrevLex>, n: u32) -> MultiPoly<GrevLex> {
let mut acc = MultiPoly::from_int(p.num_vars(), 1);
for _ in 0..n {
acc = acc.mul(p);
}
acc
}
#[test]
fn gcd_coprime_is_one() {
let (x, y) = vars2();
let f = x.mul(&x).add(&y); let g = x.add(&y).add(&MultiPoly::from_int(2, 1)); assert_eq!(MultiPoly::gcd(&f, &g), MultiPoly::from_int(2, 1));
assert_eq!(MultiPoly::gcd(&x, &y), MultiPoly::from_int(2, 1));
}
#[test]
fn gcd_shared_linear_factor() {
let (x, y) = vars2();
let s = x.add(&y);
let d = x.sub(&y);
let f = s.mul(&d); let g = s.mul(&s); assert_eq!(MultiPoly::gcd(&f, &g), s);
assert_eq!(MultiPoly::gcd(&f.neg(), &g), s);
}
#[test]
fn gcd_three_variables() {
let x: MultiPoly<GrevLex> = MultiPoly::var(3, 0);
let y: MultiPoly<GrevLex> = MultiPoly::var(3, 1);
let z: MultiPoly<GrevLex> = MultiPoly::var(3, 2);
let h = x.mul(&y).add(&z).add(&MultiPoly::from_int(3, 1));
let f = h.mul(&x.sub(&z));
let g = h.mul(&y.mul(&y).add(&x));
assert_eq!(MultiPoly::gcd(&f, &g), h);
assert_eq!(MultiPoly::gcd(&g, &f), h);
}
#[test]
fn gcd_zero_handling() {
let (x, y) = vars2();
let f = x.mul(&y).scale(&rat(-4)); let z: MultiPoly<GrevLex> = MultiPoly::zero(2);
assert!(MultiPoly::gcd(&z, &z).is_zero());
assert_eq!(MultiPoly::gcd(&f, &z), x.mul(&y).scale(&rat(4)));
assert_eq!(MultiPoly::gcd(&z, &f), x.mul(&y).scale(&rat(4)));
}
#[test]
fn gcd_includes_integer_content() {
let (x, _y) = vars2();
let f = x.scale(&rat(6)); let g = x.mul(&x).scale(&rat(4)); assert_eq!(MultiPoly::gcd(&f, &g), x.scale(&rat(2)));
let twelve: MultiPoly<GrevLex> = MultiPoly::from_int(2, 12);
assert_eq!(
MultiPoly::gcd(&twelve, &MultiPoly::from_int(2, 18)),
MultiPoly::from_int(2, 6)
);
}
#[test]
fn gcd_clears_rational_denominators() {
let (x, y) = vars2();
let s = x.add(&y);
let half = Ratio::new(BigInt::from(1), BigInt::from(2));
let third = Ratio::new(BigInt::from(1), BigInt::from(3));
let f = s.mul(&x).scale(&half); let g = s.mul(&y).scale(&third); let h = MultiPoly::gcd(&f, &g);
assert!(f.div_exact(&h).is_some() && g.div_exact(&h).is_some());
assert_eq!(h, s);
}
#[test]
fn gcd_large_coefficients() {
let (x, y) = vars2();
let big = |s: &str| Ratio::from_integer(s.parse::<BigInt>().unwrap());
let h = x
.scale(&big("123456789012345678901234567890"))
.add(&y.scale(&big("987654321098765432109876543210")))
.add(&MultiPoly::from_int(2, 1));
let f = h.mul(&x.add(&MultiPoly::from_int(2, 7)));
let g = h.mul(&y.sub(&x.scale(&big("5555555555555555555"))));
assert_eq!(MultiPoly::gcd(&f, &g), h);
}
#[test]
fn gcd_first_evaluation_point_fails_then_retry_succeeds() {
let x: MultiPoly<GrevLex> = MultiPoly::var(1, 0);
let one = MultiPoly::from_int(1, 1);
let h = pow(&x.add(&one), 8);
let f = h.mul(&x.sub(&one)); let g = h.mul(&x.mul(&x).add(&one)); assert_eq!(f.max_norm(), BigInt::from(28));
assert_eq!(g.max_norm(), BigInt::from(112));
let xi0 = BigInt::from(85);
assert!(MultiPoly::heugcd_attempt(&f, &g, 0, &xi0, 0).is_none());
let xi1 = MultiPoly::<GrevLex>::next_xi(&xi0);
assert!(xi1 > BigInt::from(140), "next ξ = {xi1}");
assert_eq!(
MultiPoly::heugcd_attempt(&f, &g, 0, &xi1, 0),
Some(h.clone())
);
assert_eq!(MultiPoly::gcd(&f, &g), h);
}
#[test]
fn gcd_never_wrong_on_random_products() {
let (x, y) = vars2();
let mut seed: u64 = 0x2545_F491_4F6C_DD1D;
let mut next = || {
seed ^= seed << 13;
seed ^= seed >> 7;
seed ^= seed << 17;
(seed % 7) as i64 - 3
};
let mut rand_poly = || {
let mut p = MultiPoly::zero(2);
for ex in 0..3u32 {
for ey in 0..3u32 {
let c = next();
if c != 0 {
p = p.add(&MultiPoly::monomial(rat(c), vec![ex, ey]));
}
}
}
if p.is_zero() { x.add(&y) } else { p }
};
for _ in 0..12 {
let h = rand_poly();
let a = rand_poly();
let b = rand_poly();
let f = h.mul(&a);
let g = h.mul(&b);
let d = MultiPoly::gcd(&f, &g);
assert!(f.div_exact(&d).is_some(), "gcd does not divide f");
assert!(g.div_exact(&d).is_some(), "gcd does not divide g");
assert!(d.div_exact(&h).is_some(), "gcd {d} misses factor {h}");
}
}
#[test]
fn lcm_of_monomials() {
let (x, y) = vars2();
let f = x.mul(&y);
let g = y.mul(&y);
assert_eq!(MultiPoly::lcm(&f, &g), x.mul(&y).mul(&y));
assert!(MultiPoly::lcm(&f, &MultiPoly::zero(2)).is_zero());
}
#[test]
fn from_terms_and_map_coeffs() {
let p: MultiPoly<GrevLex> =
MultiPoly::from_terms(2, vec![(vec![1, 0], rat(2)), (vec![1, 0], rat(-2))]).unwrap();
assert!(p.is_zero());
let q: MultiPoly<GrevLex> = MultiPoly::from_terms(2, vec![(vec![2, 1], rat(3))]).unwrap();
assert_eq!(q.coeff(&[2, 1]), Some(&rat(3)));
assert_eq!(q.coeff(&[2]), None);
let doubled = q.map_coeffs(|c| c * rat(2));
assert_eq!(doubled.coeff(&[2, 1]), Some(&rat(6)));
let killed = q.map_coeffs(|_| rat(0));
assert!(killed.is_zero());
}
#[test]
fn integer_content_and_clear_denominators() {
let (x, y) = vars2();
let f = x.scale(&rat(6)).add(&y.scale(&rat(9)));
assert_eq!(f.integer_content(), BigInt::from(3));
let g = x
.scale(&Ratio::new(BigInt::from(1), BigInt::from(2)))
.add(&y.scale(&Ratio::new(BigInt::from(2), BigInt::from(3))));
let (d, gz) = g.clear_denominators();
assert_eq!(d, BigInt::from(6));
assert_eq!(gz, x.scale(&rat(3)).add(&y.scale(&rat(4))));
}
}