use alloc::vec;
use alloc::vec::Vec;
use core::cmp::Ordering;
use puremp::Rational;
pub type Var = u32;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Monomial {
powers: Vec<(Var, u32)>,
}
impl Monomial {
pub fn one() -> Monomial {
Monomial { powers: Vec::new() }
}
pub fn var(v: Var) -> Monomial {
Monomial {
powers: vec![(v, 1)],
}
}
pub fn from_powers(pairs: &[(Var, u32)]) -> Monomial {
let mut powers: Vec<(Var, u32)> = Vec::new();
for &(v, e) in pairs {
if e == 0 {
continue;
}
match powers.iter().position(|(pv, _)| *pv == v) {
Some(i) => powers[i].1 += e,
None => powers.push((v, e)),
}
}
powers.sort_by_key(|&(v, _)| v);
Monomial { powers }
}
pub fn is_one(&self) -> bool {
self.powers.is_empty()
}
pub fn total_degree(&self) -> u32 {
self.powers.iter().map(|&(_, e)| e).sum()
}
pub fn degree_of(&self, v: Var) -> u32 {
self.powers
.iter()
.find(|&&(pv, _)| pv == v)
.map_or(0, |&(_, e)| e)
}
pub fn vars(&self) -> impl Iterator<Item = Var> + '_ {
self.powers.iter().map(|&(v, _)| v)
}
pub fn mul(&self, other: &Monomial) -> Monomial {
let mut powers = self.powers.clone();
for &(v, e) in &other.powers {
match powers.iter().position(|(pv, _)| *pv == v) {
Some(i) => powers[i].1 += e,
None => powers.push((v, e)),
}
}
powers.sort_by_key(|&(v, _)| v);
Monomial { powers }
}
fn eval(&self, assign: &dyn Fn(Var) -> Rational) -> Rational {
let mut acc = Rational::from_integer(1.into());
for &(v, e) in &self.powers {
acc = acc.mul(&assign(v).pow(e as i32));
}
acc
}
fn grlex_cmp(&self, other: &Monomial) -> Ordering {
match self.total_degree().cmp(&other.total_degree()) {
Ordering::Equal => self.powers.cmp(&other.powers),
ord => ord,
}
}
pub fn checked_div(&self, other: &Monomial) -> Option<Monomial> {
let mut powers: Vec<(Var, u32)> = Vec::new();
for &(v, e) in &self.powers {
let oe = other.degree_of(v);
if oe > e {
return None;
}
if e - oe > 0 {
powers.push((v, e - oe));
}
}
for &(v, oe) in &other.powers {
if oe > 0 && self.degree_of(v) == 0 {
return None;
}
}
Some(Monomial { powers })
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Polynomial {
terms: Vec<(Rational, Monomial)>,
}
impl Polynomial {
pub fn zero() -> Polynomial {
Polynomial { terms: Vec::new() }
}
pub fn constant(c: Rational) -> Polynomial {
if c.is_zero() {
Polynomial::zero()
} else {
Polynomial {
terms: vec![(c, Monomial::one())],
}
}
}
pub fn var(v: Var) -> Polynomial {
Polynomial {
terms: vec![(Rational::from_integer(1.into()), Monomial::var(v))],
}
}
pub fn from_terms(terms: Vec<(Rational, Monomial)>) -> Polynomial {
let mut p = Polynomial { terms: Vec::new() };
for (c, m) in terms {
p.add_term(c, m);
}
p.canonicalize();
p
}
fn add_term(&mut self, c: Rational, m: Monomial) {
if c.is_zero() {
return;
}
match self.terms.iter_mut().find(|(_, tm)| *tm == m) {
Some(slot) => {
slot.0 = slot.0.add(&c);
}
None => self.terms.push((c, m)),
}
}
fn canonicalize(&mut self) {
self.terms.retain(|(c, _)| !c.is_zero());
self.terms.sort_by(|(_, a), (_, b)| b.grlex_cmp(a)); }
pub fn is_zero(&self) -> bool {
self.terms.is_empty()
}
pub fn is_constant(&self) -> bool {
self.terms.is_empty() || (self.terms.len() == 1 && self.terms[0].1.is_one())
}
pub fn as_constant(&self) -> Option<Rational> {
match self.terms.as_slice() {
[] => Some(Rational::from_integer(0.into())),
[(c, m)] if m.is_one() => Some(c.clone()),
_ => None,
}
}
pub fn num_terms(&self) -> usize {
self.terms.len()
}
pub fn terms(&self) -> &[(Rational, Monomial)] {
&self.terms
}
pub fn total_degree(&self) -> u32 {
self.terms
.iter()
.map(|(_, m)| m.total_degree())
.max()
.unwrap_or(0)
}
pub fn degree_of(&self, v: Var) -> u32 {
self.terms
.iter()
.map(|(_, m)| m.degree_of(v))
.max()
.unwrap_or(0)
}
pub fn coeff_of_var(&self, v: Var, k: u32) -> Polynomial {
let terms = self
.terms
.iter()
.filter(|(_, m)| m.degree_of(v) == k)
.map(|(c, m)| {
let powers: Vec<(Var, u32)> = m
.vars()
.filter(|&x| x != v)
.map(|x| (x, m.degree_of(x)))
.collect();
(c.clone(), Monomial::from_powers(&powers))
})
.collect();
Polynomial::from_terms(terms)
}
pub fn is_linear(&self) -> bool {
self.total_degree() <= 1
}
pub fn deriv_var(&self, v: Var) -> Polynomial {
let terms = self
.terms
.iter()
.filter(|(_, m)| m.degree_of(v) >= 1)
.map(|(c, m)| {
let d = m.degree_of(v);
let powers: Vec<(Var, u32)> = m
.vars()
.map(|x| {
if x == v {
(x, d - 1)
} else {
(x, m.degree_of(x))
}
})
.collect();
(
c.mul(&Rational::from_integer((d as i64).into())),
Monomial::from_powers(&powers),
)
})
.collect();
Polynomial::from_terms(terms)
}
pub fn neg(&self) -> Polynomial {
Polynomial {
terms: self
.terms
.iter()
.map(|(c, m)| (c.neg(), m.clone()))
.collect(),
}
}
pub fn add(&self, other: &Polynomial) -> Polynomial {
let mut p = self.clone();
for (c, m) in &other.terms {
p.add_term(c.clone(), m.clone());
}
p.canonicalize();
p
}
pub fn sub(&self, other: &Polynomial) -> Polynomial {
self.add(&other.neg())
}
pub fn mul(&self, other: &Polynomial) -> Polynomial {
let mut p = Polynomial { terms: Vec::new() };
for (ca, ma) in &self.terms {
for (cb, mb) in &other.terms {
p.add_term(ca.mul(cb), ma.mul(mb));
}
}
p.canonicalize();
p
}
pub fn div_exact(&self, divisor: &Polynomial) -> Option<Polynomial> {
debug_assert!(!divisor.is_zero());
let (dc, dm) = divisor.terms[0].clone(); let mut rem = self.clone();
let mut quot = Polynomial::zero();
while let Some((rc, rm)) = rem.terms.first().cloned() {
let qm = rm.checked_div(&dm)?; let qc = rc.div(&dc);
let qterm = Polynomial {
terms: vec![(qc, qm)],
};
quot = quot.add(&qterm);
rem = rem.sub(&divisor.mul(&qterm));
}
Some(quot)
}
pub fn scale(&self, c: &Rational) -> Polynomial {
if c.is_zero() {
return Polynomial::zero();
}
Polynomial {
terms: self
.terms
.iter()
.map(|(tc, m)| (tc.mul(c), m.clone()))
.collect(),
}
}
pub fn pow(&self, n: u32) -> Polynomial {
let mut acc = Polynomial::constant(Rational::from_integer(1.into()));
let mut base = self.clone();
let mut e = n;
while e > 0 {
if e & 1 == 1 {
acc = acc.mul(&base);
}
e >>= 1;
if e > 0 {
base = base.mul(&base);
}
}
acc
}
pub fn eval(&self, assign: &dyn Fn(Var) -> Rational) -> Rational {
let mut acc = Rational::from_integer(0.into());
for (c, m) in &self.terms {
acc = acc.add(&c.mul(&m.eval(assign)));
}
acc
}
pub fn vars(&self) -> Vec<Var> {
let mut vs: Vec<Var> = self.terms.iter().flat_map(|(_, m)| m.vars()).collect();
vs.sort_unstable();
vs.dedup();
vs
}
}
#[cfg(test)]
mod tests {
use super::*;
fn r(n: i64) -> Rational {
Rational::from_integer(n.into())
}
fn rat(n: i64, d: i64) -> Rational {
Rational::new(n.into(), d.into())
}
#[test]
fn difference_of_squares() {
let x = Polynomial::var(0);
let one = Polynomial::constant(r(1));
let lhs = x.add(&one).mul(&x.sub(&one));
let rhs = x.mul(&x).sub(&one);
assert_eq!(lhs, rhs);
assert_eq!(lhs.total_degree(), 2);
}
#[test]
fn square_of_sum() {
let x = Polynomial::var(0);
let y = Polynomial::var(1);
let expanded = x.add(&y).pow(2);
let manual = Polynomial::from_terms(vec![
(r(1), Monomial::from_powers(&[(0, 2)])),
(r(2), Monomial::from_powers(&[(0, 1), (1, 1)])),
(r(1), Monomial::from_powers(&[(1, 2)])),
]);
assert_eq!(expanded, manual);
}
#[test]
fn eval_matches_factored_form() {
let x = Polynomial::var(0);
let y = Polynomial::var(1);
let p = x
.scale(&r(2))
.sub(&y.scale(&r(3)))
.add(&Polynomial::constant(r(1)))
.mul(&x.add(&y));
let samples = [
(3i64, 5i64),
(-2, 7),
(11, -4),
(1, 1),
(-9, -9),
(0, 6),
(8, 0),
];
for (a, b) in samples {
let want = {
let a = r(a);
let b = r(b);
let two_a2 = a.mul(&a).mul(&r(2));
let ab = a.mul(&b);
let three_b2 = b.mul(&b).mul(&r(3));
two_a2.sub(&ab).sub(&three_b2).add(&a).add(&b)
};
let got = p.eval(&|v| if v == 0 { r(a) } else { r(b) });
assert_eq!(got, want, "mismatch at ({a},{b})");
}
}
#[test]
fn scaling_and_zero() {
let x = Polynomial::var(0);
assert!(x.scale(&r(0)).is_zero());
assert_eq!(x.scale(&rat(1, 2)).scale(&r(2)), x);
assert!(x.sub(&x).is_zero());
}
#[test]
fn constant_recognition() {
let c = Polynomial::constant(rat(7, 3));
assert!(c.is_constant());
assert_eq!(c.as_constant(), Some(rat(7, 3)));
assert!(Polynomial::var(0).as_constant().is_none());
assert_eq!(Polynomial::zero().as_constant(), Some(r(0)));
}
#[test]
fn degree_queries() {
let p = Polynomial::from_terms(vec![
(r(5), Monomial::from_powers(&[(0, 3), (1, 1)])),
(r(1), Monomial::from_powers(&[(1, 2)])),
]);
assert_eq!(p.total_degree(), 4);
assert_eq!(p.degree_of(0), 3);
assert_eq!(p.degree_of(1), 2);
assert_eq!(p.degree_of(2), 0);
assert!(!p.is_linear());
assert_eq!(p.vars(), vec![0, 1]);
}
}