use num_bigint::BigInt;
use num_rational::BigRational;
use num_traits::{One, Signed, Zero};
use rustc_hash::FxHashMap;
use smallvec::SmallVec;
use std::cmp::Ordering;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::ops::{Add, Mul, Neg, Sub};
pub type Var = u32;
pub const NULL_VAR: Var = u32::MAX;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct VarPower {
pub var: Var,
pub power: u32,
}
impl VarPower {
#[inline]
pub fn new(var: Var, power: u32) -> Self {
Self { var, power }
}
}
impl PartialOrd for VarPower {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for VarPower {
fn cmp(&self, other: &Self) -> Ordering {
self.var.cmp(&other.var)
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct Monomial {
vars: SmallVec<[VarPower; 4]>,
total_degree: u32,
hash: u64,
}
impl Monomial {
#[inline]
pub fn unit() -> Self {
Self {
vars: SmallVec::new(),
total_degree: 0,
hash: 0,
}
}
#[inline]
pub fn from_var(var: Var) -> Self {
Self::from_var_power(var, 1)
}
pub fn from_var_power(var: Var, power: u32) -> Self {
if power == 0 {
return Self::unit();
}
let mut vars = SmallVec::new();
vars.push(VarPower::new(var, power));
Self {
total_degree: power,
hash: compute_monomial_hash(&vars),
vars,
}
}
pub fn from_powers(powers: impl IntoIterator<Item = (Var, u32)>) -> Self {
let mut var_powers: FxHashMap<Var, u32> = FxHashMap::default();
for (var, power) in powers {
if power > 0 {
*var_powers.entry(var).or_insert(0) += power;
}
}
let mut vars: SmallVec<[VarPower; 4]> = var_powers
.into_iter()
.filter(|(_, p)| *p > 0)
.map(|(v, p)| VarPower::new(v, p))
.collect();
vars.sort_by_key(|vp| vp.var);
let total_degree = vars.iter().map(|vp| vp.power).sum();
let hash = compute_monomial_hash(&vars);
Self {
vars,
total_degree,
hash,
}
}
#[inline]
pub fn is_unit(&self) -> bool {
self.vars.is_empty()
}
#[inline]
pub fn total_degree(&self) -> u32 {
self.total_degree
}
#[inline]
pub fn num_vars(&self) -> usize {
self.vars.len()
}
#[inline]
pub fn vars(&self) -> &[VarPower] {
&self.vars
}
pub fn degree(&self, var: Var) -> u32 {
self.vars
.iter()
.find(|vp| vp.var == var)
.map(|vp| vp.power)
.unwrap_or(0)
}
pub fn max_var(&self) -> Var {
self.vars.last().map(|vp| vp.var).unwrap_or(NULL_VAR)
}
#[inline]
pub fn is_univariate(&self) -> bool {
self.vars.len() <= 1
}
#[inline]
pub fn is_linear(&self) -> bool {
self.total_degree <= 1
}
pub fn mul(&self, other: &Monomial) -> Monomial {
if self.is_unit() {
return other.clone();
}
if other.is_unit() {
return self.clone();
}
let mut vars: SmallVec<[VarPower; 4]> = SmallVec::new();
let mut i = 0;
let mut j = 0;
while i < self.vars.len() && j < other.vars.len() {
match self.vars[i].var.cmp(&other.vars[j].var) {
Ordering::Less => {
vars.push(self.vars[i]);
i += 1;
}
Ordering::Greater => {
vars.push(other.vars[j]);
j += 1;
}
Ordering::Equal => {
vars.push(VarPower::new(
self.vars[i].var,
self.vars[i].power + other.vars[j].power,
));
i += 1;
j += 1;
}
}
}
vars.extend_from_slice(&self.vars[i..]);
vars.extend_from_slice(&other.vars[j..]);
let total_degree = self.total_degree + other.total_degree;
let hash = compute_monomial_hash(&vars);
Monomial {
vars,
total_degree,
hash,
}
}
pub fn div(&self, other: &Monomial) -> Option<Monomial> {
if other.is_unit() {
return Some(self.clone());
}
let mut vars: SmallVec<[VarPower; 4]> = SmallVec::new();
let mut j = 0;
for vp in &self.vars {
if j < other.vars.len() && other.vars[j].var == vp.var {
if vp.power < other.vars[j].power {
return None;
}
let new_power = vp.power - other.vars[j].power;
if new_power > 0 {
vars.push(VarPower::new(vp.var, new_power));
}
j += 1;
} else if j < other.vars.len() && other.vars[j].var < vp.var {
return None;
} else {
vars.push(*vp);
}
}
if j < other.vars.len() {
return None;
}
let total_degree = vars.iter().map(|vp| vp.power).sum();
let hash = compute_monomial_hash(&vars);
Some(Monomial {
vars,
total_degree,
hash,
})
}
pub fn gcd(&self, other: &Monomial) -> Monomial {
if self.is_unit() || other.is_unit() {
return Monomial::unit();
}
let mut vars: SmallVec<[VarPower; 4]> = SmallVec::new();
let mut i = 0;
let mut j = 0;
while i < self.vars.len() && j < other.vars.len() {
match self.vars[i].var.cmp(&other.vars[j].var) {
Ordering::Less => {
i += 1;
}
Ordering::Greater => {
j += 1;
}
Ordering::Equal => {
let min_power = self.vars[i].power.min(other.vars[j].power);
if min_power > 0 {
vars.push(VarPower::new(self.vars[i].var, min_power));
}
i += 1;
j += 1;
}
}
}
let total_degree = vars.iter().map(|vp| vp.power).sum();
let hash = compute_monomial_hash(&vars);
Monomial {
vars,
total_degree,
hash,
}
}
pub fn pow(&self, n: u32) -> Monomial {
if n == 0 {
return Monomial::unit();
}
if n == 1 {
return self.clone();
}
let vars: SmallVec<[VarPower; 4]> = self
.vars
.iter()
.map(|vp| VarPower::new(vp.var, vp.power * n))
.collect();
let total_degree = self.total_degree * n;
let hash = compute_monomial_hash(&vars);
Monomial {
vars,
total_degree,
hash,
}
}
pub fn lex_cmp(&self, other: &Monomial) -> Ordering {
let mut i = 0;
let mut j = 0;
while i < self.vars.len() && j < other.vars.len() {
match self.vars[i].var.cmp(&other.vars[j].var) {
Ordering::Less => return Ordering::Greater,
Ordering::Greater => return Ordering::Less,
Ordering::Equal => match self.vars[i].power.cmp(&other.vars[j].power) {
Ordering::Equal => {
i += 1;
j += 1;
}
ord => return ord,
},
}
}
if i < self.vars.len() {
Ordering::Greater
} else if j < other.vars.len() {
Ordering::Less
} else {
Ordering::Equal
}
}
pub fn grlex_cmp(&self, other: &Monomial) -> Ordering {
match self.total_degree.cmp(&other.total_degree) {
Ordering::Equal => self.lex_cmp(other),
ord => ord,
}
}
pub fn grevlex_cmp(&self, other: &Monomial) -> Ordering {
match self.total_degree.cmp(&other.total_degree) {
Ordering::Equal => {
let mut i = self.vars.len();
let mut j = other.vars.len();
while i > 0 && j > 0 {
i -= 1;
j -= 1;
match self.vars[i].var.cmp(&other.vars[j].var) {
Ordering::Less => return Ordering::Less,
Ordering::Greater => return Ordering::Greater,
Ordering::Equal => match self.vars[i].power.cmp(&other.vars[j].power) {
Ordering::Equal => {}
Ordering::Less => return Ordering::Greater,
Ordering::Greater => return Ordering::Less,
},
}
}
if i > 0 {
Ordering::Less
} else if j > 0 {
Ordering::Greater
} else {
Ordering::Equal
}
}
ord => ord,
}
}
}
fn compute_monomial_hash(vars: &[VarPower]) -> u64 {
use std::collections::hash_map::DefaultHasher;
let mut hasher = DefaultHasher::new();
for vp in vars {
vp.hash(&mut hasher);
}
hasher.finish()
}
fn gcd_bigint(mut a: BigInt, mut b: BigInt) -> BigInt {
while !b.is_zero() {
let t = &a % &b;
a = b;
b = t;
}
a.abs()
}
fn count_sign_variations(seq: &[Polynomial], var: Var, point: &BigRational) -> usize {
let mut signs = Vec::new();
for poly in seq {
let val = poly.eval_at(var, point);
let c = val.constant_term();
if !c.is_zero() {
signs.push(if c.is_positive() { 1 } else { -1 });
}
}
let mut variations = 0;
for i in 1..signs.len() {
if signs[i] != signs[i - 1] {
variations += 1;
}
}
variations
}
fn cauchy_root_bound(poly: &Polynomial, var: Var) -> BigRational {
if poly.is_zero() {
return BigRational::one();
}
let deg = poly.degree(var);
if deg == 0 {
return BigRational::one();
}
let lc = poly.univ_coeff(var, deg);
if lc.is_zero() {
return BigRational::one();
}
let lc_abs = lc.abs();
let mut max_ratio = BigRational::zero();
for k in 0..deg {
let coeff = poly.univ_coeff(var, k);
if !coeff.is_zero() {
let ratio = coeff.abs() / &lc_abs;
if ratio > max_ratio {
max_ratio = ratio;
}
}
}
BigRational::one() + max_ratio
}
fn fujiwara_root_bound(poly: &Polynomial, var: Var) -> BigRational {
if poly.is_zero() {
return BigRational::one();
}
let deg = poly.degree(var);
if deg == 0 {
return BigRational::one();
}
let lc = poly.univ_coeff(var, deg);
if lc.is_zero() {
return BigRational::one();
}
let lc_abs = lc.abs();
let mut max_val = BigRational::zero();
for k in 0..deg {
let coeff = poly.univ_coeff(var, k);
if !coeff.is_zero() {
let ratio = coeff.abs() / &lc_abs;
let exp = deg - k;
let approx_root = rational_nth_root_approx(&ratio, exp);
if approx_root > max_val {
max_val = approx_root;
}
}
}
BigRational::from_integer(BigInt::from(2)) * max_val
}
fn lagrange_positive_root_bound(poly: &Polynomial, var: Var) -> BigRational {
if poly.is_zero() {
return BigRational::one();
}
let deg = poly.degree(var);
if deg == 0 {
return BigRational::one();
}
let lc = poly.univ_coeff(var, deg);
if lc.is_zero() {
return BigRational::one();
}
let lc_abs = lc.abs();
let mut max_val = BigRational::zero();
let lc_positive = lc.is_positive();
for k in 0..deg {
let coeff = poly.univ_coeff(var, k);
if !coeff.is_zero() && coeff.is_positive() != lc_positive {
let ratio = coeff.abs() / &lc_abs;
let exp = deg - k;
let approx_root = rational_nth_root_approx(&ratio, exp);
if approx_root > max_val {
max_val = approx_root;
}
}
}
if max_val.is_zero() {
BigRational::one()
} else {
max_val
}
}
fn rational_nth_root_approx(target: &BigRational, n: u32) -> BigRational {
use crate::rational::pow_uint;
if n == 0 {
return BigRational::one();
}
if n == 1 {
return target.clone();
}
if target.is_zero() {
return BigRational::zero();
}
let mut low = BigRational::zero();
let mut high = target.clone() + BigRational::one();
for _ in 0..100 {
let mid = (&low + &high) / BigRational::from_integer(BigInt::from(2));
let mid_pow_n = pow_uint(&mid, n);
if &mid_pow_n == target {
return mid;
}
if mid_pow_n < *target {
low = mid;
} else {
high = mid.clone();
}
let diff = &high - &low;
if diff < BigRational::new(BigInt::one(), BigInt::from(1000000)) {
return high;
}
}
high
}
fn count_coefficient_sign_variations(poly: &Polynomial, var: Var) -> usize {
if poly.is_zero() {
return 0;
}
let deg = poly.degree(var);
if deg == 0 {
return 0;
}
let mut coeffs = Vec::new();
for k in 0..=deg {
let coeff = poly.univ_coeff(var, k);
if !coeff.is_zero() {
coeffs.push(coeff);
}
}
let mut variations = 0;
for i in 1..coeffs.len() {
if coeffs[i].is_positive() != coeffs[i - 1].is_positive() {
variations += 1;
}
}
variations
}
fn descartes_positive_roots(poly: &Polynomial, var: Var) -> (usize, usize) {
let variations = count_coefficient_sign_variations(poly, var);
let lower = variations % 2;
(lower, variations)
}
fn descartes_negative_roots(poly: &Polynomial, var: Var) -> (usize, usize) {
let deg = poly.degree(var);
let mut neg_poly_terms = Vec::new();
for k in 0..=deg {
let coeff = poly.univ_coeff(var, k);
if !coeff.is_zero() {
let adjusted_coeff = if k % 2 == 1 { -coeff } else { coeff };
if k == 0 {
neg_poly_terms.push(Term::constant(adjusted_coeff));
} else {
neg_poly_terms.push(Term::new(adjusted_coeff, Monomial::from_var_power(var, k)));
}
}
}
let neg_poly = Polynomial::from_terms(neg_poly_terms, poly.order);
descartes_positive_roots(&neg_poly, var)
}
impl Hash for Monomial {
fn hash<H: Hasher>(&self, state: &mut H) {
self.hash.hash(state);
}
}
impl fmt::Debug for Monomial {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_unit() {
write!(f, "1")
} else {
for (i, vp) in self.vars.iter().enumerate() {
if i > 0 {
write!(f, "*")?;
}
if vp.power == 1 {
write!(f, "x{}", vp.var)?;
} else {
write!(f, "x{}^{}", vp.var, vp.power)?;
}
}
Ok(())
}
}
}
impl fmt::Display for Monomial {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self, f)
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct Term {
pub coeff: BigRational,
pub monomial: Monomial,
}
impl Term {
#[inline]
pub fn new(coeff: BigRational, monomial: Monomial) -> Self {
Self { coeff, monomial }
}
#[inline]
pub fn constant(c: BigRational) -> Self {
Self::new(c, Monomial::unit())
}
#[inline]
pub fn from_var(var: Var) -> Self {
Self::new(BigRational::one(), Monomial::from_var(var))
}
#[inline]
pub fn is_zero(&self) -> bool {
self.coeff.is_zero()
}
#[inline]
pub fn is_constant(&self) -> bool {
self.monomial.is_unit()
}
}
impl fmt::Debug for Term {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.monomial.is_unit() {
write!(f, "{}", self.coeff)
} else if self.coeff.is_one() {
write!(f, "{:?}", self.monomial)
} else if self.coeff == -BigRational::one() {
write!(f, "-{:?}", self.monomial)
} else {
write!(f, "{}*{:?}", self.coeff, self.monomial)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum MonomialOrder {
Lex,
#[default]
GrLex,
GRevLex,
}
impl MonomialOrder {
pub fn compare(&self, a: &Monomial, b: &Monomial) -> Ordering {
match self {
MonomialOrder::Lex => a.lex_cmp(b),
MonomialOrder::GrLex => a.grlex_cmp(b),
MonomialOrder::GRevLex => a.grevlex_cmp(b),
}
}
}
#[derive(Clone)]
pub struct Polynomial {
terms: Vec<Term>,
order: MonomialOrder,
}
impl Polynomial {
#[inline]
pub fn zero() -> Self {
Self {
terms: Vec::new(),
order: MonomialOrder::default(),
}
}
#[inline]
pub fn one() -> Self {
Self::constant(BigRational::one())
}
pub fn constant(c: BigRational) -> Self {
if c.is_zero() {
Self::zero()
} else {
Self {
terms: vec![Term::constant(c)],
order: MonomialOrder::default(),
}
}
}
pub fn from_var(var: Var) -> Self {
Self {
terms: vec![Term::from_var(var)],
order: MonomialOrder::default(),
}
}
pub fn from_var_power(var: Var, power: u32) -> Self {
if power == 0 {
Self::one()
} else {
Self {
terms: vec![Term::new(
BigRational::one(),
Monomial::from_var_power(var, power),
)],
order: MonomialOrder::default(),
}
}
}
pub fn from_terms(terms: impl IntoIterator<Item = Term>, order: MonomialOrder) -> Self {
let mut poly = Self {
terms: terms.into_iter().filter(|t| !t.is_zero()).collect(),
order,
};
poly.normalize();
poly
}
pub fn from_coeffs_int(coeffs: &[(i64, &[(Var, u32)])]) -> Self {
let terms: Vec<Term> = coeffs
.iter()
.map(|(c, powers)| {
Term::new(
BigRational::from_integer(BigInt::from(*c)),
Monomial::from_powers(powers.iter().copied()),
)
})
.collect();
Self::from_terms(terms, MonomialOrder::default())
}
pub fn linear(coeffs: &[(BigRational, Var)], constant: BigRational) -> Self {
let mut terms: Vec<Term> = coeffs
.iter()
.filter(|(c, _)| !c.is_zero())
.map(|(c, v)| Term::new(c.clone(), Monomial::from_var(*v)))
.collect();
if !constant.is_zero() {
terms.push(Term::constant(constant));
}
Self::from_terms(terms, MonomialOrder::default())
}
pub fn univariate(var: Var, coeffs: &[BigRational]) -> Self {
let terms: Vec<Term> = coeffs
.iter()
.enumerate()
.filter(|(_, c)| !c.is_zero())
.map(|(i, c)| Term::new(c.clone(), Monomial::from_var_power(var, i as u32)))
.collect();
Self::from_terms(terms, MonomialOrder::default())
}
#[inline]
pub fn is_zero(&self) -> bool {
self.terms.is_empty()
}
#[inline]
pub fn is_constant(&self) -> bool {
self.terms.len() == 1 && self.terms[0].monomial.is_unit()
}
pub fn constant_value(&self) -> BigRational {
if self.is_zero() {
BigRational::zero()
} else if self.is_constant() {
self.terms[0].coeff.clone()
} else {
BigRational::zero()
}
}
pub fn is_one(&self) -> bool {
self.terms.len() == 1 && self.terms[0].monomial.is_unit() && self.terms[0].coeff.is_one()
}
pub fn is_univariate(&self) -> bool {
if self.terms.is_empty() {
return true;
}
let mut var: Option<Var> = None;
for term in &self.terms {
for vp in term.monomial.vars() {
match var {
None => var = Some(vp.var),
Some(v) if v != vp.var => return false,
_ => {}
}
}
}
true
}
pub fn is_linear(&self) -> bool {
self.terms.iter().all(|t| t.monomial.is_linear())
}
#[inline]
pub fn num_terms(&self) -> usize {
self.terms.len()
}
#[inline]
pub fn terms(&self) -> &[Term] {
&self.terms
}
pub fn total_degree(&self) -> u32 {
self.terms
.iter()
.map(|t| t.monomial.total_degree())
.max()
.unwrap_or(0)
}
pub fn degree(&self, var: Var) -> u32 {
self.terms
.iter()
.map(|t| t.monomial.degree(var))
.max()
.unwrap_or(0)
}
pub fn max_var(&self) -> Var {
self.terms
.iter()
.map(|t| t.monomial.max_var())
.filter(|&v| v != NULL_VAR)
.max()
.unwrap_or(NULL_VAR)
}
pub fn vars(&self) -> Vec<Var> {
let mut vars: Vec<Var> = self
.terms
.iter()
.flat_map(|t| t.monomial.vars().iter().map(|vp| vp.var))
.collect();
vars.sort_unstable();
vars.dedup();
vars
}
#[inline]
pub fn leading_term(&self) -> Option<&Term> {
self.terms.first()
}
pub fn leading_coeff(&self) -> BigRational {
self.terms
.first()
.map(|t| t.coeff.clone())
.unwrap_or_else(BigRational::zero)
}
pub fn leading_monomial(&self) -> Option<&Monomial> {
self.terms.first().map(|t| &t.monomial)
}
pub fn constant_term(&self) -> BigRational {
self.terms
.iter()
.find(|t| t.monomial.is_unit())
.map(|t| t.coeff.clone())
.unwrap_or_else(BigRational::zero)
}
pub fn univ_coeff(&self, var: Var, k: u32) -> BigRational {
for term in &self.terms {
if term.monomial.degree(var) == k && term.monomial.num_vars() <= 1 {
return term.coeff.clone();
}
}
BigRational::zero()
}
pub fn coeff(&self, var: Var, k: u32) -> Polynomial {
let terms: Vec<Term> = self
.terms
.iter()
.filter(|t| t.monomial.degree(var) == k)
.map(|t| {
let new_mon = if let Some(m) = t.monomial.div(&Monomial::from_var_power(var, k)) {
m
} else {
Monomial::unit()
};
Term::new(t.coeff.clone(), new_mon)
})
.collect();
Polynomial::from_terms(terms, self.order)
}
pub fn leading_coeff_wrt(&self, var: Var) -> Polynomial {
let d = self.degree(var);
self.coeff(var, d)
}
fn normalize(&mut self) {
if self.terms.is_empty() {
return;
}
let order = self.order;
self.terms
.sort_by(|a, b| order.compare(&b.monomial, &a.monomial));
let mut i = 0;
while i < self.terms.len() {
let mut j = i + 1;
while j < self.terms.len() && self.terms[j].monomial == self.terms[i].monomial {
let coeff = self.terms[j].coeff.clone();
self.terms[i].coeff += coeff;
j += 1;
}
if j > i + 1 {
self.terms.drain((i + 1)..j);
}
i += 1;
}
self.terms.retain(|t| !t.coeff.is_zero());
}
pub fn neg(&self) -> Polynomial {
Polynomial {
terms: self
.terms
.iter()
.map(|t| Term::new(-t.coeff.clone(), t.monomial.clone()))
.collect(),
order: self.order,
}
}
pub fn add(&self, other: &Polynomial) -> Polynomial {
let mut terms: Vec<Term> = self.terms.clone();
terms.extend(other.terms.iter().cloned());
Polynomial::from_terms(terms, self.order)
}
pub fn sub(&self, other: &Polynomial) -> Polynomial {
self.add(&other.neg())
}
pub fn scale(&self, c: &BigRational) -> Polynomial {
if c.is_zero() {
return Polynomial::zero();
}
if c.is_one() {
return self.clone();
}
Polynomial {
terms: self
.terms
.iter()
.map(|t| Term::new(&t.coeff * c, t.monomial.clone()))
.collect(),
order: self.order,
}
}
pub fn mul(&self, other: &Polynomial) -> Polynomial {
if self.is_zero() || other.is_zero() {
return Polynomial::zero();
}
if self.is_univariate()
&& other.is_univariate()
&& self.max_var() == other.max_var()
&& self.terms.len() >= 16
&& other.terms.len() >= 16
{
return self.mul_karatsuba(other);
}
let mut terms: Vec<Term> = Vec::with_capacity(self.terms.len() * other.terms.len());
for t1 in &self.terms {
for t2 in &other.terms {
terms.push(Term::new(
&t1.coeff * &t2.coeff,
t1.monomial.mul(&t2.monomial),
));
}
}
Polynomial::from_terms(terms, self.order)
}
fn mul_karatsuba(&self, other: &Polynomial) -> Polynomial {
debug_assert!(self.is_univariate() && other.is_univariate());
debug_assert!(self.max_var() == other.max_var());
let var = self.max_var();
let deg1 = self.degree(var);
let deg2 = other.degree(var);
if deg1 <= 8 || deg2 <= 8 {
let mut terms: Vec<Term> = Vec::with_capacity(self.terms.len() * other.terms.len());
for t1 in &self.terms {
for t2 in &other.terms {
terms.push(Term::new(
&t1.coeff * &t2.coeff,
t1.monomial.mul(&t2.monomial),
));
}
}
return Polynomial::from_terms(terms, self.order);
}
let split = deg1.max(deg2).div_ceil(2);
let (low0, high0) = self.split_at_degree(var, split);
let (low1, high1) = other.split_at_degree(var, split);
let z0 = low0.mul_karatsuba(&low1); let z2 = high0.mul_karatsuba(&high1);
let sum0 = Polynomial::add(&low0, &high0);
let sum1 = Polynomial::add(&low1, &high1);
let z1_temp = sum0.mul_karatsuba(&sum1);
let z1 = Polynomial::sub(&Polynomial::sub(&z1_temp, &z0), &z2);
let x_split = Monomial::from_var_power(var, split);
let x_2split = Monomial::from_var_power(var, 2 * split);
let term1 = z1.mul_monomial(&x_split);
let term2 = z2.mul_monomial(&x_2split);
Polynomial::add(&Polynomial::add(&z0, &term1), &term2)
}
fn split_at_degree(&self, var: Var, deg: u32) -> (Polynomial, Polynomial) {
let mut low_terms = Vec::new();
let mut high_terms = Vec::new();
for term in &self.terms {
let term_deg = term.monomial.degree(var);
if term_deg < deg {
low_terms.push(term.clone());
} else {
if let Some(new_mon) = term.monomial.div(&Monomial::from_var_power(var, deg)) {
high_terms.push(Term::new(term.coeff.clone(), new_mon));
}
}
}
let low = if low_terms.is_empty() {
Polynomial::zero()
} else {
Polynomial::from_terms(low_terms, self.order)
};
let high = if high_terms.is_empty() {
Polynomial::zero()
} else {
Polynomial::from_terms(high_terms, self.order)
};
(low, high)
}
pub fn mul_monomial(&self, m: &Monomial) -> Polynomial {
if m.is_unit() {
return self.clone();
}
Polynomial {
terms: self
.terms
.iter()
.map(|t| Term::new(t.coeff.clone(), t.monomial.mul(m)))
.collect(),
order: self.order,
}
}
pub fn pow(&self, k: u32) -> Polynomial {
if k == 0 {
return Polynomial::one();
}
if k == 1 {
return self.clone();
}
if self.is_zero() {
return Polynomial::zero();
}
let mut result = Polynomial::one();
let mut base = self.clone();
let mut exp = k;
while exp > 0 {
if exp & 1 == 1 {
result = Polynomial::mul(&result, &base);
}
base = Polynomial::mul(&base, &base);
exp >>= 1;
}
result
}
pub fn derivative(&self, var: Var) -> Polynomial {
let terms: Vec<Term> = self
.terms
.iter()
.filter_map(|t| {
let d = t.monomial.degree(var);
if d == 0 {
return None;
}
let new_coeff = &t.coeff * BigRational::from_integer(BigInt::from(d));
let new_mon = if d == 1 {
t.monomial
.div(&Monomial::from_var(var))
.unwrap_or_else(Monomial::unit)
} else {
let new_powers: Vec<(Var, u32)> = t
.monomial
.vars()
.iter()
.map(|vp| {
if vp.var == var {
(vp.var, vp.power - 1)
} else {
(vp.var, vp.power)
}
})
.filter(|(_, p)| *p > 0)
.collect();
Monomial::from_powers(new_powers)
};
Some(Term::new(new_coeff, new_mon))
})
.collect();
Polynomial::from_terms(terms, self.order)
}
pub fn nth_derivative(&self, var: Var, n: u32) -> Polynomial {
if n == 0 {
return self.clone();
}
let mut result = self.clone();
for _ in 0..n {
result = result.derivative(var);
if result.is_zero() {
break;
}
}
result
}
pub fn gradient(&self) -> Vec<Polynomial> {
let vars = self.vars();
if vars.is_empty() {
return vec![];
}
let max_var = *vars.iter().max().unwrap();
let mut grad = Vec::new();
for var in 0..=max_var {
grad.push(self.derivative(var));
}
grad
}
pub fn hessian(&self) -> Vec<Vec<Polynomial>> {
let vars = self.vars();
if vars.is_empty() {
return vec![];
}
let max_var = *vars.iter().max().unwrap();
let n = (max_var + 1) as usize;
let mut hessian = vec![vec![Polynomial::zero(); n]; n];
for i in 0..=max_var {
for j in 0..=max_var {
let first_deriv = self.derivative(i);
let second_deriv = first_deriv.derivative(j);
hessian[i as usize][j as usize] = second_deriv;
}
}
hessian
}
pub fn jacobian(polys: &[Polynomial]) -> Vec<Vec<Polynomial>> {
if polys.is_empty() {
return vec![];
}
let max_var = polys.iter().flat_map(|p| p.vars()).max().unwrap_or(0);
let n_vars = (max_var + 1) as usize;
let mut jacobian = Vec::with_capacity(polys.len());
for poly in polys {
let mut row = Vec::with_capacity(n_vars);
for var in 0..=max_var {
row.push(poly.derivative(var));
}
jacobian.push(row);
}
jacobian
}
pub fn integrate(&self, var: Var) -> Polynomial {
let terms: Vec<Term> = self
.terms
.iter()
.map(|t| {
let d = t.monomial.degree(var);
let new_power = d + 1;
let new_coeff = &t.coeff / BigRational::from_integer(BigInt::from(new_power));
let new_powers: Vec<(Var, u32)> = if d == 0 {
let mut powers = t
.monomial
.vars()
.iter()
.map(|vp| (vp.var, vp.power))
.collect::<Vec<_>>();
powers.push((var, 1));
powers.sort_by_key(|(v, _)| *v);
powers
} else {
t.monomial
.vars()
.iter()
.map(|vp| {
if vp.var == var {
(vp.var, vp.power + 1)
} else {
(vp.var, vp.power)
}
})
.collect()
};
let new_mon = Monomial::from_powers(new_powers);
Term::new(new_coeff, new_mon)
})
.collect();
Polynomial::from_terms(terms, self.order)
}
pub fn definite_integral(
&self,
var: Var,
lower: &BigRational,
upper: &BigRational,
) -> Option<BigRational> {
let antideriv = self.integrate(var);
let mut upper_assignment = rustc_hash::FxHashMap::default();
upper_assignment.insert(var, upper.clone());
let upper_val = antideriv.eval(&upper_assignment);
let mut lower_assignment = rustc_hash::FxHashMap::default();
lower_assignment.insert(var, lower.clone());
let lower_val = antideriv.eval(&lower_assignment);
Some(upper_val - lower_val)
}
pub fn find_critical_points(&self, var: Var) -> Vec<(BigRational, BigRational)> {
let deriv = self.derivative(var);
deriv.isolate_roots(var)
}
pub fn trapezoidal_rule(
&self,
var: Var,
lower: &BigRational,
upper: &BigRational,
n: u32,
) -> BigRational {
if n == 0 {
return BigRational::zero();
}
let h = (upper - lower) / BigRational::from_integer(BigInt::from(n));
let mut sum = BigRational::zero();
let mut assignment = rustc_hash::FxHashMap::default();
assignment.insert(var, lower.clone());
sum += &self.eval(&assignment) / BigRational::from_integer(BigInt::from(2));
assignment.insert(var, upper.clone());
sum += &self.eval(&assignment) / BigRational::from_integer(BigInt::from(2));
for i in 1..n {
let x_i = lower + &h * BigRational::from_integer(BigInt::from(i));
assignment.insert(var, x_i);
sum += &self.eval(&assignment);
}
sum * h
}
pub fn simpsons_rule(
&self,
var: Var,
lower: &BigRational,
upper: &BigRational,
n: u32,
) -> BigRational {
if n == 0 {
return BigRational::zero();
}
let n = if n % 2 == 1 { n + 1 } else { n };
let h = (upper - lower) / BigRational::from_integer(BigInt::from(n));
let mut sum = BigRational::zero();
let mut assignment = rustc_hash::FxHashMap::default();
assignment.insert(var, lower.clone());
sum += &self.eval(&assignment);
assignment.insert(var, upper.clone());
sum += &self.eval(&assignment);
for i in (1..n).step_by(2) {
let x_i = lower + &h * BigRational::from_integer(BigInt::from(i));
assignment.insert(var, x_i);
sum += BigRational::from_integer(BigInt::from(4)) * &self.eval(&assignment);
}
for i in (2..n).step_by(2) {
let x_i = lower + &h * BigRational::from_integer(BigInt::from(i));
assignment.insert(var, x_i);
sum += BigRational::from_integer(BigInt::from(2)) * &self.eval(&assignment);
}
sum * h / BigRational::from_integer(BigInt::from(3))
}
pub fn eval_at(&self, var: Var, value: &BigRational) -> Polynomial {
let terms: Vec<Term> = self
.terms
.iter()
.map(|t| {
let d = t.monomial.degree(var);
if d == 0 {
t.clone()
} else {
let new_coeff = &t.coeff * value.pow(d as i32);
let new_mon = t
.monomial
.div(&Monomial::from_var_power(var, d))
.unwrap_or_else(Monomial::unit);
Term::new(new_coeff, new_mon)
}
})
.collect();
Polynomial::from_terms(terms, self.order)
}
pub fn eval(&self, assignment: &FxHashMap<Var, BigRational>) -> BigRational {
let mut result = BigRational::zero();
for term in &self.terms {
let mut val = term.coeff.clone();
for vp in term.monomial.vars() {
if let Some(v) = assignment.get(&vp.var) {
val *= v.pow(vp.power as i32);
} else {
panic!("Variable x{} not in assignment", vp.var);
}
}
result += val;
}
result
}
pub fn eval_horner(&self, var: Var, value: &BigRational) -> BigRational {
if self.is_zero() {
return BigRational::zero();
}
if !self.is_univariate() || self.max_var() != var {
let result = self.eval_at(var, value);
if result.is_constant() {
return result.constant_value();
}
panic!("Polynomial is not univariate in variable x{}", var);
}
let deg = self.degree(var);
if deg == 0 {
return self.constant_value();
}
let mut coeffs = vec![BigRational::zero(); (deg + 1) as usize];
for k in 0..=deg {
coeffs[k as usize] = self.univ_coeff(var, k);
}
let mut result = coeffs[deg as usize].clone();
for k in (0..deg).rev() {
result = &result * value + &coeffs[k as usize];
}
result
}
pub fn substitute(&self, var: Var, replacement: &Polynomial) -> Polynomial {
let mut result = Polynomial::zero();
for term in &self.terms {
let d = term.monomial.degree(var);
if d == 0 {
result = Polynomial::add(
&result,
&Polynomial::from_terms(vec![term.clone()], self.order),
);
} else {
let remainder = term
.monomial
.div(&Monomial::from_var_power(var, d))
.unwrap_or_else(Monomial::unit);
let coeff_poly = Polynomial::from_terms(
vec![Term::new(term.coeff.clone(), remainder)],
self.order,
);
let rep_pow = replacement.pow(d);
result = Polynomial::add(&result, &Polynomial::mul(&coeff_poly, &rep_pow));
}
}
result
}
pub fn integer_content(&self) -> BigInt {
if self.terms.is_empty() {
return BigInt::one();
}
let mut gcd: Option<BigInt> = None;
for term in &self.terms {
let num = term.coeff.numer().clone();
gcd = Some(match gcd {
None => num.abs(),
Some(g) => gcd_bigint(g, num.abs()),
});
}
gcd.unwrap_or_else(BigInt::one)
}
pub fn primitive(&self) -> Polynomial {
let content = self.integer_content();
if content.is_one() {
return self.clone();
}
let c = BigRational::from_integer(content);
self.scale(&(BigRational::one() / c))
}
pub fn gcd_univariate(&self, other: &Polynomial) -> Polynomial {
if self.is_zero() {
return other.primitive();
}
if other.is_zero() {
return self.primitive();
}
let var = self.max_var().max(other.max_var());
if var == NULL_VAR {
return Polynomial::one();
}
let mut a = self.primitive();
let mut b = other.primitive();
if a.degree(var) < b.degree(var) {
std::mem::swap(&mut a, &mut b);
}
let mut iter_count = 0;
let max_iters = a.degree(var) as usize + b.degree(var) as usize + 10;
while !b.is_zero() && iter_count < max_iters {
iter_count += 1;
let r = a.pseudo_remainder(&b, var);
a = b;
b = if r.is_zero() { r } else { r.primitive() };
}
a.primitive()
}
pub fn pseudo_remainder(&self, divisor: &Polynomial, var: Var) -> Polynomial {
if divisor.is_zero() {
panic!("Division by zero polynomial");
}
if self.is_zero() {
return Polynomial::zero();
}
let deg_a = self.degree(var);
let deg_b = divisor.degree(var);
if deg_a < deg_b {
return self.clone();
}
let lc_b = divisor.univ_coeff(var, deg_b);
let mut r = self.clone();
let max_iters = (deg_a - deg_b + 2) as usize;
let mut iters = 0;
while !r.is_zero() && r.degree(var) >= deg_b && iters < max_iters {
iters += 1;
let deg_r = r.degree(var);
let lc_r = r.univ_coeff(var, deg_r);
let shift = deg_r - deg_b;
r = r.scale(&lc_b);
let subtractor = divisor
.scale(&lc_r)
.mul_monomial(&Monomial::from_var_power(var, shift));
r = Polynomial::sub(&r, &subtractor);
}
r
}
pub fn pseudo_div_univariate(&self, divisor: &Polynomial) -> (Polynomial, Polynomial) {
if divisor.is_zero() {
panic!("Division by zero polynomial");
}
if self.is_zero() {
return (Polynomial::zero(), Polynomial::zero());
}
let var = self.max_var().max(divisor.max_var());
if var == NULL_VAR {
return (Polynomial::zero(), self.clone());
}
let deg_a = self.degree(var);
let deg_b = divisor.degree(var);
if deg_a < deg_b {
return (Polynomial::zero(), self.clone());
}
let lc_b = divisor.univ_coeff(var, deg_b);
let mut q = Polynomial::zero();
let mut r = self.clone();
let max_iters = (deg_a - deg_b + 2) as usize;
let mut iters = 0;
while !r.is_zero() && r.degree(var) >= deg_b && iters < max_iters {
iters += 1;
let deg_r = r.degree(var);
let lc_r = r.univ_coeff(var, deg_r);
let shift = deg_r - deg_b;
let term = Polynomial::from_terms(
vec![Term::new(
lc_r.clone(),
Monomial::from_var_power(var, shift),
)],
self.order,
);
q = q.scale(&lc_b);
q = Polynomial::add(&q, &term);
r = r.scale(&lc_b);
let subtractor = Polynomial::mul(divisor, &term);
r = Polynomial::sub(&r, &subtractor);
}
(q, r)
}
pub fn subresultant_prs(&self, other: &Polynomial, var: Var) -> Vec<Polynomial> {
if self.is_zero() || other.is_zero() {
return vec![];
}
let mut prs = Vec::new();
let mut a = self.clone();
let mut b = other.clone();
if a.degree(var) < b.degree(var) {
std::mem::swap(&mut a, &mut b);
}
prs.push(a.clone());
prs.push(b.clone());
let mut g = Polynomial::one();
let mut h = Polynomial::one();
let max_iters = a.degree(var) as usize + b.degree(var) as usize + 10;
let mut iter_count = 0;
while !b.is_zero() && iter_count < max_iters {
iter_count += 1;
let delta = a.degree(var) as i32 - b.degree(var) as i32;
if delta < 0 {
break;
}
let prem = a.pseudo_remainder(&b, var);
if prem.is_zero() {
break;
}
let normalized = if delta == 0 {
prem.scale(&(BigRational::one() / &h.constant_value()))
} else {
let g_pow = g.constant_value().pow(delta);
let divisor = &g_pow * &h.constant_value();
prem.scale(&(BigRational::one() / divisor))
};
let lc_b = b.leading_coeff_wrt(var);
g = lc_b;
h = if delta == 0 {
Polynomial::one()
} else {
let g_val = g.constant_value();
let h_val = h.constant_value();
let new_h = g_val.pow(delta) / h_val.pow(delta - 1);
Polynomial::constant(new_h)
};
a = b;
b = normalized.primitive(); prs.push(b.clone());
}
prs
}
pub fn resultant(&self, other: &Polynomial, var: Var) -> Polynomial {
if self.is_zero() || other.is_zero() {
return Polynomial::zero();
}
let deg_p = self.degree(var);
let deg_q = other.degree(var);
if deg_p == 0 {
return self.pow(deg_q);
}
if deg_q == 0 {
return other.pow(deg_p);
}
let mut a = self.clone();
let mut b = other.clone();
let mut g = Polynomial::one();
let mut h = Polynomial::one();
let mut sign = if (deg_p & 1 == 1) && (deg_q & 1 == 1) {
-1i32
} else {
1i32
};
let max_iters = (deg_p + deg_q) * 10;
let mut iter_count = 0;
while !b.is_zero() && iter_count < max_iters {
iter_count += 1;
let delta = a.degree(var) as i32 - b.degree(var) as i32;
if delta < 0 {
std::mem::swap(&mut a, &mut b);
if (a.degree(var) & 1 == 1) && (b.degree(var) & 1 == 1) {
sign = -sign;
}
continue;
}
let (_, r) = a.pseudo_div_univariate(&b);
if r.is_zero() {
if b.degree(var) > 0 {
return Polynomial::zero();
} else {
let d = a.degree(var);
return b.pow(d);
}
}
a = b;
let g_pow = g.pow((delta + 1) as u32);
let h_pow = h.pow(delta as u32);
b = r;
if delta > 0 {
let denom = Polynomial::mul(&g_pow, &h_pow);
b = b.primitive();
let _ = denom; }
g = a.leading_coeff_wrt(var);
let g_delta = g.pow(delta as u32);
let h_new = if delta == 0 {
h.clone()
} else if delta == 1 {
g.clone()
} else {
g_delta
};
h = h_new;
}
if sign < 0 { a.neg() } else { a }
}
pub fn discriminant(&self, var: Var) -> Polynomial {
let deriv = self.derivative(var);
self.resultant(&deriv, var)
}
pub fn is_definitely_positive(&self) -> bool {
if self.terms.is_empty() {
return false;
}
self.terms
.iter()
.all(|t| t.coeff.is_positive() && t.monomial.vars().iter().all(|vp| vp.power % 2 == 0))
}
pub fn is_definitely_negative(&self) -> bool {
self.neg().is_definitely_positive()
}
pub fn make_monic(&self) -> Polynomial {
if self.is_zero() {
return self.clone();
}
let lc = self.leading_coeff();
if lc.is_one() {
return self.clone();
}
self.scale(&(BigRational::one() / lc))
}
pub fn square_free(&self) -> Polynomial {
if self.is_zero() || self.is_constant() {
return self.clone();
}
let var = self.max_var();
if var == NULL_VAR {
return self.clone();
}
let deriv = self.derivative(var);
if deriv.is_zero() {
return self.clone();
}
let g = self.gcd_univariate(&deriv);
if g.is_constant() {
self.primitive()
} else {
let (q, r) = self.pseudo_div_univariate(&g);
if r.is_zero() {
q.primitive().square_free()
} else {
self.primitive()
}
}
}
pub fn sturm_sequence(&self, var: Var) -> Vec<Polynomial> {
if self.is_zero() || self.degree(var) == 0 {
return vec![self.clone()];
}
let mut seq = Vec::new();
seq.push(self.clone());
seq.push(self.derivative(var));
let max_iterations = self.degree(var) as usize + 5;
let mut iterations = 0;
while !seq.last().unwrap().is_zero() && iterations < max_iterations {
iterations += 1;
let n = seq.len();
let rem = seq[n - 2].pseudo_remainder(&seq[n - 1], var);
if rem.is_zero() {
break;
}
seq.push(rem.neg());
}
seq
}
pub fn count_roots_in_interval(&self, var: Var, a: &BigRational, b: &BigRational) -> usize {
if self.is_zero() {
return 0;
}
let sturm_seq = self.sturm_sequence(var);
if sturm_seq.is_empty() {
return 0;
}
let var_a = count_sign_variations(&sturm_seq, var, a);
let var_b = count_sign_variations(&sturm_seq, var, b);
var_a.saturating_sub(var_b)
}
pub fn cauchy_bound(&self, var: Var) -> BigRational {
cauchy_root_bound(self, var)
}
pub fn fujiwara_bound(&self, var: Var) -> BigRational {
fujiwara_root_bound(self, var)
}
pub fn lagrange_positive_bound(&self, var: Var) -> BigRational {
lagrange_positive_root_bound(self, var)
}
pub fn isolate_roots(&self, var: Var) -> Vec<(BigRational, BigRational)> {
if self.is_zero() || self.is_constant() {
return vec![];
}
let p = self.square_free();
if p.is_constant() {
return vec![];
}
let bound = cauchy_root_bound(&p, var);
let (_pos_lower, pos_upper) = descartes_positive_roots(&p, var);
let (_neg_lower, neg_upper) = descartes_negative_roots(&p, var);
let mut intervals = Vec::new();
let mut queue = Vec::new();
if pos_upper > 0 {
queue.push((BigRational::zero(), bound.clone()));
}
if neg_upper > 0 {
queue.push((-bound, BigRational::zero()));
}
let max_iterations = 1000;
let mut iterations = 0;
while let Some((a, b)) = queue.pop() {
iterations += 1;
if iterations > max_iterations {
break;
}
let num_roots = p.count_roots_in_interval(var, &a, &b);
if num_roots == 0 {
continue;
} else if num_roots == 1 {
intervals.push((a, b));
} else {
let mid = (&a + &b) / BigRational::from_integer(BigInt::from(2));
let val_mid = p.eval_at(var, &mid);
if val_mid.constant_term().is_zero() {
intervals.push((mid.clone(), mid.clone()));
let left_roots = p.count_roots_in_interval(var, &a, &mid);
let right_roots = p.count_roots_in_interval(var, &mid, &b);
if left_roots > 0 {
queue.push((a, mid.clone()));
}
if right_roots > 0 {
queue.push((mid, b));
}
} else {
queue.push((a, mid.clone()));
queue.push((mid, b));
}
}
}
intervals
}
pub fn newton_raphson(
&self,
var: Var,
initial: BigRational,
lower: BigRational,
upper: BigRational,
max_iterations: usize,
tolerance: &BigRational,
) -> Option<BigRational> {
use num_traits::{Signed, Zero};
let derivative = self.derivative(var);
let mut x = initial;
for _ in 0..max_iterations {
let mut assignment = FxHashMap::default();
assignment.insert(var, x.clone());
let fx = self.eval(&assignment);
if fx.abs() < *tolerance {
return Some(x);
}
let fpx = derivative.eval(&assignment);
if fpx.is_zero() {
return None;
}
let x_new = x - (fx / fpx);
if x_new < lower || x_new > upper {
return None;
}
x = x_new;
}
Some(x)
}
pub fn refine_roots(
&self,
var: Var,
intervals: &[(BigRational, BigRational)],
max_iterations: usize,
tolerance: &BigRational,
) -> Vec<BigRational> {
intervals
.iter()
.filter_map(|(lower, upper)| {
let initial = (lower + upper) / BigRational::from_integer(BigInt::from(2));
self.newton_raphson(
var,
initial,
lower.clone(),
upper.clone(),
max_iterations,
tolerance,
)
})
.collect()
}
pub fn taylor_expansion(&self, var: Var, point: &BigRational, degree: u32) -> Polynomial {
use crate::rational::factorial;
let mut result = Polynomial::zero();
let mut derivative = self.clone();
for k in 0..=degree {
let mut assignment = FxHashMap::default();
assignment.insert(var, point.clone());
let coeff_at_point = derivative.eval(&assignment);
let factorial_k = factorial(k);
let taylor_coeff = coeff_at_point / BigRational::from_integer(factorial_k);
let shifted_term = if k == 0 {
Polynomial::constant(taylor_coeff)
} else {
let x_poly = Polynomial::from_var(var);
let point_poly = Polynomial::constant(point.clone());
let mut power = x_poly - point_poly;
for _ in 1..k {
let x_poly = Polynomial::from_var(var);
let point_poly = Polynomial::constant(point.clone());
power = power * (x_poly - point_poly);
}
power * Polynomial::constant(taylor_coeff)
};
result = result + shifted_term;
if k < degree {
derivative = derivative.derivative(var);
}
}
result
}
pub fn maclaurin_expansion(&self, var: Var, degree: u32) -> Polynomial {
use num_bigint::BigInt;
self.taylor_expansion(var, &BigRational::from_integer(BigInt::from(0)), degree)
}
pub fn bisection(
&self,
var: Var,
lower: BigRational,
upper: BigRational,
max_iterations: usize,
tolerance: &BigRational,
) -> Option<BigRational> {
use num_traits::{Signed, Zero};
let mut a = lower;
let mut b = upper;
let mut assignment_a = FxHashMap::default();
assignment_a.insert(var, a.clone());
let fa = self.eval(&assignment_a);
let mut assignment_b = FxHashMap::default();
assignment_b.insert(var, b.clone());
let fb = self.eval(&assignment_b);
if fa.is_zero() {
return Some(a);
}
if fb.is_zero() {
return Some(b);
}
if (fa.is_positive() && fb.is_positive()) || (fa.is_negative() && fb.is_negative()) {
return None;
}
for _ in 0..max_iterations {
if (&b - &a).abs() < *tolerance {
return Some((&a + &b) / BigRational::from_integer(BigInt::from(2)));
}
let mid = (&a + &b) / BigRational::from_integer(BigInt::from(2));
let mut assignment_mid = FxHashMap::default();
assignment_mid.insert(var, mid.clone());
let fmid = self.eval(&assignment_mid);
if fmid.is_zero() {
return Some(mid);
}
let mut assignment_a = FxHashMap::default();
assignment_a.insert(var, a.clone());
let fa = self.eval(&assignment_a);
if (fa.is_positive() && fmid.is_negative()) || (fa.is_negative() && fmid.is_positive())
{
b = mid;
} else {
a = mid;
}
}
Some((&a + &b) / BigRational::from_integer(BigInt::from(2)))
}
pub fn secant(
&self,
var: Var,
x0: BigRational,
x1: BigRational,
max_iterations: usize,
tolerance: &BigRational,
) -> Option<BigRational> {
use num_traits::{Signed, Zero};
let mut x_prev = x0;
let mut x_curr = x1;
for _ in 0..max_iterations {
let mut assignment_prev = FxHashMap::default();
assignment_prev.insert(var, x_prev.clone());
let f_prev = self.eval(&assignment_prev);
let mut assignment_curr = FxHashMap::default();
assignment_curr.insert(var, x_curr.clone());
let f_curr = self.eval(&assignment_curr);
if f_curr.abs() < *tolerance {
return Some(x_curr);
}
let denom = &f_curr - &f_prev;
if denom.is_zero() {
return None;
}
let x_new = &x_curr - &f_curr * (&x_curr - &x_prev) / denom;
x_prev = x_curr;
x_curr = x_new;
}
Some(x_curr)
}
pub fn sign_at(&self, var_signs: &FxHashMap<Var, i8>) -> Option<i8> {
if self.is_zero() {
return Some(0);
}
if self.is_constant() {
let c = &self.terms[0].coeff;
return Some(if c.is_positive() {
1
} else if c.is_negative() {
-1
} else {
0
});
}
let mut all_positive = true;
let mut all_negative = true;
for term in &self.terms {
let mut term_sign: i8 = if term.coeff.is_positive() {
1
} else if term.coeff.is_negative() {
-1
} else {
continue;
};
for vp in term.monomial.vars() {
if let Some(&s) = var_signs.get(&vp.var) {
if s == 0 {
term_sign = 0;
break;
}
if vp.power % 2 == 1 {
term_sign *= s;
}
} else {
return None;
}
}
if term_sign > 0 {
all_negative = false;
} else if term_sign < 0 {
all_positive = false;
}
}
if all_positive && !all_negative {
Some(1)
} else if all_negative && !all_positive {
Some(-1)
} else {
None
}
}
pub fn is_irreducible(&self, var: Var) -> bool {
if self.is_zero() || self.is_constant() {
return false;
}
let deg = self.degree(var);
if deg == 0 {
return false;
}
if deg == 1 {
return true;
}
let sf = self.square_free();
if sf.degree(var) < deg {
return false; }
if deg == 2 {
return self.is_irreducible_quadratic(var);
}
true
}
fn is_irreducible_quadratic(&self, var: Var) -> bool {
let deg = self.degree(var);
if deg != 2 {
return false;
}
let a = self.univ_coeff(var, 2);
let b = self.univ_coeff(var, 1);
let c = self.univ_coeff(var, 0);
let discriminant = &b * &b - (BigRational::from_integer(BigInt::from(4)) * &a * &c);
if discriminant.is_negative() {
return true; }
let num_sqrt = is_perfect_square(discriminant.numer());
let den_sqrt = is_perfect_square(discriminant.denom());
!(num_sqrt && den_sqrt)
}
pub fn factor(&self, var: Var) -> Vec<(Polynomial, u32)> {
if self.is_zero() {
return vec![];
}
if self.is_constant() {
return vec![(self.clone(), 1)];
}
let deg = self.degree(var);
if deg == 0 {
return vec![(self.clone(), 1)];
}
let p = self.primitive().make_monic();
if deg == 1 {
return vec![(p, 1)];
}
if deg == 2 {
return self.factor_quadratic(var);
}
self.factor_square_free(var)
}
fn factor_quadratic(&self, var: Var) -> Vec<(Polynomial, u32)> {
let deg = self.degree(var);
if deg != 2 {
return vec![(self.primitive(), 1)];
}
let p = self.primitive().make_monic();
let a = p.univ_coeff(var, 2);
let b = p.univ_coeff(var, 1);
let c = p.univ_coeff(var, 0);
let discriminant = &b * &b - (BigRational::from_integer(BigInt::from(4)) * &a * &c);
let num_sqrt = integer_sqrt(discriminant.numer());
let den_sqrt = integer_sqrt(discriminant.denom());
if num_sqrt.is_none() || den_sqrt.is_none() {
return vec![(p, 1)];
}
let disc_sqrt = BigRational::new(num_sqrt.unwrap(), den_sqrt.unwrap());
let two_a = BigRational::from_integer(BigInt::from(2)) * &a;
let root1 = (-&b + &disc_sqrt) / &two_a;
let root2 = (-&b - disc_sqrt) / two_a;
let factor1 = Polynomial::from_terms(
vec![
Term::new(BigRational::one(), Monomial::from_var(var)),
Term::new(-root1, Monomial::unit()),
],
MonomialOrder::Lex,
);
let factor2 = Polynomial::from_terms(
vec![
Term::new(BigRational::one(), Monomial::from_var(var)),
Term::new(-root2, Monomial::unit()),
],
MonomialOrder::Lex,
);
vec![(factor1, 1), (factor2, 1)]
}
fn factor_square_free(&self, var: Var) -> Vec<(Polynomial, u32)> {
if self.is_zero() || self.is_constant() {
return vec![(self.clone(), 1)];
}
let mut result = Vec::new();
let p = self.primitive();
let mut multiplicity = 1u32;
let deriv = p.derivative(var);
if deriv.is_zero() {
return vec![(p, 1)];
}
let gcd = p.gcd_univariate(&deriv);
if gcd.is_constant() {
if p.is_irreducible(var) {
return vec![(p.make_monic(), 1)];
} else {
return vec![(p.make_monic(), 1)];
}
}
let (quo, rem) = p.pseudo_div_univariate(&gcd);
if !rem.is_zero() {
return vec![(p, 1)];
}
let mut u = quo.primitive();
let (v_quo, v_rem) = deriv.pseudo_div_univariate(&gcd);
if v_rem.is_zero() {
let v = v_quo.primitive();
let mut w = Polynomial::sub(&v, &u.derivative(var));
while !w.is_zero() && !w.is_constant() {
let y = u.gcd_univariate(&w);
if !y.is_constant() {
result.push((y.make_monic(), multiplicity));
let (u_new, u_rem) = u.pseudo_div_univariate(&y);
if u_rem.is_zero() {
u = u_new.primitive();
}
let (w_new, w_rem) = w.pseudo_div_univariate(&y);
if w_rem.is_zero() {
w = Polynomial::sub(&w_new.primitive(), &u.derivative(var));
} else {
break;
}
} else {
break;
}
multiplicity += 1;
}
if !u.is_constant() {
result.push((u.make_monic(), multiplicity));
}
}
if result.is_empty() {
result.push((p.make_monic(), 1));
}
result
}
pub fn content(&self) -> BigRational {
if self.terms.is_empty() {
return BigRational::one();
}
let mut num_gcd: Option<BigInt> = None;
let mut den_lcm: Option<BigInt> = None;
for term in &self.terms {
let coeff_num = term.coeff.numer().clone().abs();
let coeff_den = term.coeff.denom().clone();
num_gcd = Some(match num_gcd {
None => coeff_num,
Some(g) => gcd_bigint(g, coeff_num),
});
den_lcm = Some(match den_lcm {
None => coeff_den,
Some(l) => {
let gcd = gcd_bigint(l.clone(), coeff_den.clone());
(&l * &coeff_den) / gcd
}
});
}
BigRational::new(
num_gcd.unwrap_or_else(BigInt::one),
den_lcm.unwrap_or_else(BigInt::one),
)
}
pub fn compose(&self, var: Var, other: &Polynomial) -> Polynomial {
self.substitute(var, other)
}
pub fn lagrange_interpolate(
var: Var,
points: &[(BigRational, BigRational)],
) -> Option<Polynomial> {
if points.is_empty() {
return None;
}
if points.len() == 1 {
return Some(Polynomial::constant(points[0].1.clone()));
}
for i in 0..points.len() {
for j in i + 1..points.len() {
if points[i].0 == points[j].0 {
return None; }
}
}
let mut result = Polynomial::zero();
for i in 0..points.len() {
let (x_i, y_i) = &points[i];
let mut basis = Polynomial::one();
for (j, (x_j, _)) in points.iter().enumerate() {
if i == j {
continue;
}
let x_poly = Polynomial::from_var(var);
let const_poly = Polynomial::constant(x_j.clone());
let numerator = &x_poly - &const_poly;
let denominator = x_i - x_j;
if denominator.is_zero() {
return None; }
basis = &basis * &numerator;
basis = basis.scale(&(BigRational::one() / denominator));
}
let scaled_basis = basis.scale(y_i);
result = &result + &scaled_basis;
}
Some(result)
}
pub fn newton_interpolate(
var: Var,
points: &[(BigRational, BigRational)],
) -> Option<Polynomial> {
if points.is_empty() {
return None;
}
if points.len() == 1 {
return Some(Polynomial::constant(points[0].1.clone()));
}
for i in 0..points.len() {
for j in i + 1..points.len() {
if points[i].0 == points[j].0 {
return None;
}
}
}
let n = points.len();
let mut dd: Vec<Vec<BigRational>> = vec![vec![BigRational::zero(); n]; n];
for i in 0..n {
dd[i][0] = points[i].1.clone();
}
for j in 1..n {
for i in 0..n - j {
let numerator = &dd[i + 1][j - 1] - &dd[i][j - 1];
let denominator = &points[i + j].0 - &points[i].0;
if denominator.is_zero() {
return None;
}
dd[i][j] = numerator / denominator;
}
}
let mut result = Polynomial::constant(dd[0][0].clone());
let mut term = Polynomial::one();
for i in 1..n {
let x_poly = Polynomial::from_var(var);
let const_poly = Polynomial::constant(points[i - 1].0.clone());
let factor = &x_poly - &const_poly;
term = &term * &factor;
let scaled_term = term.scale(&dd[0][i]);
result = &result + &scaled_term;
}
Some(result)
}
pub fn chebyshev_first_kind(var: Var, n: u32) -> Polynomial {
match n {
0 => Polynomial::one(),
1 => Polynomial::from_var(var),
_ => {
let mut t_prev = Polynomial::one(); let mut t_curr = Polynomial::from_var(var);
for _ in 2..=n {
let x = Polynomial::from_var(var);
let two_x_t_curr = Polynomial::mul(&t_curr, &x)
.scale(&BigRational::from_integer(BigInt::from(2)));
let t_next = Polynomial::sub(&two_x_t_curr, &t_prev);
t_prev = t_curr;
t_curr = t_next;
}
t_curr
}
}
}
pub fn chebyshev_second_kind(var: Var, n: u32) -> Polynomial {
match n {
0 => Polynomial::one(),
1 => {
Polynomial::from_var(var).scale(&BigRational::from_integer(BigInt::from(2)))
}
_ => {
let mut u_prev = Polynomial::one(); let mut u_curr =
Polynomial::from_var(var).scale(&BigRational::from_integer(BigInt::from(2)));
for _ in 2..=n {
let x = Polynomial::from_var(var);
let two_x_u_curr = Polynomial::mul(&u_curr, &x)
.scale(&BigRational::from_integer(BigInt::from(2)));
let u_next = Polynomial::sub(&two_x_u_curr, &u_prev);
u_prev = u_curr;
u_curr = u_next;
}
u_curr
}
}
}
pub fn chebyshev_nodes(n: u32) -> Vec<BigRational> {
if n == 0 {
return vec![BigRational::zero()];
}
let mut nodes = Vec::with_capacity((n + 1) as usize);
for k in 0..=n {
let numerator = (2 * k + 1) as i64;
let denominator = (2 * n + 2) as i64;
let ratio = BigRational::new(BigInt::from(numerator), BigInt::from(denominator));
let node = BigRational::one() - ratio * BigRational::from_integer(BigInt::from(2));
nodes.push(node);
}
nodes
}
pub fn legendre(var: Var, n: u32) -> Polynomial {
match n {
0 => Polynomial::one(),
1 => Polynomial::from_var(var),
_ => {
let mut p_prev = Polynomial::one(); let mut p_curr = Polynomial::from_var(var);
for k in 1..n {
let x = Polynomial::from_var(var);
let coeff_2k_plus_1 = BigRational::from_integer(BigInt::from(2 * k + 1));
let term1 = Polynomial::mul(&p_curr, &x).scale(&coeff_2k_plus_1);
let coeff_k = BigRational::from_integer(BigInt::from(k));
let term2 = p_prev.scale(&coeff_k);
let numerator = Polynomial::sub(&term1, &term2);
let divisor = BigRational::from_integer(BigInt::from(k + 1));
let p_next = numerator.scale(&(BigRational::one() / divisor));
p_prev = p_curr;
p_curr = p_next;
}
p_curr
}
}
}
pub fn hermite(var: Var, n: u32) -> Polynomial {
match n {
0 => Polynomial::one(),
1 => {
Polynomial::from_var(var).scale(&BigRational::from_integer(BigInt::from(2)))
}
_ => {
let mut h_prev = Polynomial::one(); let mut h_curr =
Polynomial::from_var(var).scale(&BigRational::from_integer(BigInt::from(2)));
for k in 1..n {
let x = Polynomial::from_var(var);
let two_x_h = Polynomial::mul(&h_curr, &x)
.scale(&BigRational::from_integer(BigInt::from(2)));
let coeff_2k = BigRational::from_integer(BigInt::from(2 * k));
let term2 = h_prev.scale(&coeff_2k);
let h_next = Polynomial::sub(&two_x_h, &term2);
h_prev = h_curr;
h_curr = h_next;
}
h_curr
}
}
}
pub fn laguerre(var: Var, n: u32) -> Polynomial {
match n {
0 => Polynomial::one(),
1 => {
let one = Polynomial::one();
let x = Polynomial::from_var(var);
Polynomial::sub(&one, &x)
}
_ => {
let mut l_prev = Polynomial::one(); let mut l_curr = {
let one = Polynomial::one();
let x = Polynomial::from_var(var);
Polynomial::sub(&one, &x)
};
for k in 1..n {
let x = Polynomial::from_var(var);
let coeff_2k_plus_1 = BigRational::from_integer(BigInt::from(2 * k + 1));
let term1 = l_curr.scale(&coeff_2k_plus_1);
let term2 = Polynomial::mul(&l_curr, &x);
let combined = Polynomial::sub(&term1, &term2);
let coeff_k = BigRational::from_integer(BigInt::from(k));
let term3 = l_prev.scale(&coeff_k);
let numerator = Polynomial::sub(&combined, &term3);
let divisor = BigRational::from_integer(BigInt::from(k + 1));
let l_next = numerator.scale(&(BigRational::one() / divisor));
l_prev = l_curr;
l_curr = l_next;
}
l_curr
}
}
}
}
fn is_perfect_square(n: &BigInt) -> bool {
if n.is_negative() {
return false;
}
if n.is_zero() || n.is_one() {
return true;
}
let sqrt = integer_sqrt(n);
sqrt.is_some()
}
fn integer_sqrt(n: &BigInt) -> Option<BigInt> {
if n.is_negative() {
return None;
}
if n.is_zero() {
return Some(BigInt::zero());
}
if n.is_one() {
return Some(BigInt::one());
}
let mut x: BigInt = n.clone();
let mut y: BigInt = (&x + BigInt::one()) >> 1;
while y < x {
x = y.clone();
y = (&x + (n / &x)) >> 1;
}
if &x * &x == *n { Some(x) } else { None }
}
pub fn rational_sqrt(n: &BigRational) -> Option<BigRational> {
if n.is_negative() {
return None;
}
if n.is_zero() {
return Some(BigRational::zero());
}
let numer = n.numer();
let denom = n.denom();
let sqrt_numer = integer_sqrt(numer)?;
let sqrt_denom = integer_sqrt(denom)?;
Some(BigRational::new(sqrt_numer, sqrt_denom))
}
impl PartialEq for Polynomial {
fn eq(&self, other: &Self) -> bool {
if self.terms.len() != other.terms.len() {
return false;
}
self.terms.iter().zip(&other.terms).all(|(a, b)| a == b)
}
}
impl Eq for Polynomial {}
impl Default for Polynomial {
fn default() -> Self {
Self::zero()
}
}
impl fmt::Debug for Polynomial {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_zero() {
write!(f, "0")
} else {
for (i, term) in self.terms.iter().enumerate() {
if i == 0 {
write!(f, "{:?}", term)?;
} else if term.coeff.is_negative() {
write!(
f,
" - {:?}",
Term::new(-term.coeff.clone(), term.monomial.clone())
)?;
} else {
write!(f, " + {:?}", term)?;
}
}
Ok(())
}
}
}
impl fmt::Display for Polynomial {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self, f)
}
}
impl Neg for Polynomial {
type Output = Polynomial;
fn neg(self) -> Self::Output {
Polynomial::neg(&self)
}
}
impl Neg for &Polynomial {
type Output = Polynomial;
fn neg(self) -> Self::Output {
Polynomial::neg(self)
}
}
impl Add for Polynomial {
type Output = Polynomial;
fn add(self, rhs: Self) -> Self::Output {
Polynomial::add(&self, &rhs)
}
}
impl Add<&Polynomial> for &Polynomial {
type Output = Polynomial;
fn add(self, rhs: &Polynomial) -> Self::Output {
Polynomial::add(self, rhs)
}
}
impl Sub for Polynomial {
type Output = Polynomial;
fn sub(self, rhs: Self) -> Self::Output {
Polynomial::sub(&self, &rhs)
}
}
impl Sub<&Polynomial> for &Polynomial {
type Output = Polynomial;
fn sub(self, rhs: &Polynomial) -> Self::Output {
Polynomial::sub(self, rhs)
}
}
impl Mul for Polynomial {
type Output = Polynomial;
fn mul(self, rhs: Self) -> Self::Output {
Polynomial::mul(&self, &rhs)
}
}
impl Mul<&Polynomial> for &Polynomial {
type Output = Polynomial;
fn mul(self, rhs: &Polynomial) -> Self::Output {
Polynomial::mul(self, rhs)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn rat(n: i64) -> BigRational {
BigRational::from_integer(BigInt::from(n))
}
#[test]
fn test_monomial_unit() {
let m = Monomial::unit();
assert!(m.is_unit());
assert_eq!(m.total_degree(), 0);
}
#[test]
fn test_monomial_from_var() {
let m = Monomial::from_var(0);
assert!(!m.is_unit());
assert_eq!(m.total_degree(), 1);
assert_eq!(m.degree(0), 1);
assert_eq!(m.degree(1), 0);
}
#[test]
fn test_monomial_mul() {
let m1 = Monomial::from_var_power(0, 2); let m2 = Monomial::from_var_power(0, 3); let m3 = m1.mul(&m2);
assert_eq!(m3.total_degree(), 5);
assert_eq!(m3.degree(0), 5);
}
#[test]
fn test_monomial_div() {
let m1 = Monomial::from_var_power(0, 5); let m2 = Monomial::from_var_power(0, 2); let m3 = m1.div(&m2).unwrap();
assert_eq!(m3.degree(0), 3);
assert!(m2.div(&m1).is_none());
}
#[test]
fn test_polynomial_zero() {
let p = Polynomial::zero();
assert!(p.is_zero());
assert_eq!(p.total_degree(), 0);
}
#[test]
fn test_polynomial_constant() {
let p = Polynomial::constant(rat(5));
assert!(p.is_constant());
assert_eq!(p.constant_term(), rat(5));
}
#[test]
fn test_polynomial_add() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (1, &[])]);
let q = Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (2, &[])]);
let r = Polynomial::add(&p, &q);
assert_eq!(r.num_terms(), 2);
assert_eq!(r.univ_coeff(0, 1), rat(2));
assert_eq!(r.constant_term(), rat(3));
}
#[test]
fn test_polynomial_mul() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (1, &[])]);
let q = Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (-1, &[])]);
let r = Polynomial::mul(&p, &q);
assert_eq!(r.univ_coeff(0, 2), rat(1));
assert_eq!(r.univ_coeff(0, 1), rat(0));
assert_eq!(r.constant_term(), rat(-1));
}
#[test]
fn test_polynomial_mul_karatsuba() {
let mut p_coeffs = Vec::new();
for i in 0..=20 {
p_coeffs.push((1, vec![(0, i)]));
}
let p = Polynomial::from_coeffs_int(
&p_coeffs
.iter()
.map(|(c, v)| (*c, v.as_slice()))
.collect::<Vec<_>>(),
);
let q = Polynomial::from_coeffs_int(&[(1, &[(0, 20)]), (-1, &[])]);
let result = p.mul(q);
assert_eq!(result.degree(0), 40);
assert_eq!(result.univ_coeff(0, 40), rat(1));
assert_eq!(result.constant_term(), rat(-1));
assert_eq!(result.univ_coeff(0, 20), rat(0)); }
#[test]
fn test_polynomial_mul_karatsuba_correctness() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (2, &[(0, 1)]), (1, &[])]);
let p10 = p.pow(10);
let q = Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (-1, &[])]);
let result = p10.mul(q);
assert_eq!(result.degree(0), 21);
assert_eq!(result.univ_coeff(0, 21), rat(1));
assert!(!result.is_zero());
}
#[test]
fn test_polynomial_mul_karatsuba_simple() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 16)]), (1, &[])]);
let q = Polynomial::from_coeffs_int(&[(1, &[(0, 16)]), (-1, &[])]);
let result = p.mul(q);
assert_eq!(result.degree(0), 32);
assert_eq!(result.univ_coeff(0, 32), rat(1));
assert_eq!(result.constant_term(), rat(-1));
assert_eq!(result.univ_coeff(0, 16), rat(0)); }
#[test]
fn test_polynomial_derivative() {
let p = Polynomial::from_coeffs_int(&[
(1, &[(0, 3)]),
(2, &[(0, 2)]),
(1, &[(0, 1)]),
(1, &[]),
]);
let dp = p.derivative(0);
assert_eq!(dp.univ_coeff(0, 2), rat(3));
assert_eq!(dp.univ_coeff(0, 1), rat(4));
assert_eq!(dp.constant_term(), rat(1));
}
#[test]
fn test_polynomial_nth_derivative() {
let p = Polynomial::from_coeffs_int(&[
(1, &[(0, 4)]),
(2, &[(0, 3)]),
(3, &[(0, 2)]),
(4, &[(0, 1)]),
(5, &[]),
]);
let d0 = p.nth_derivative(0, 0);
assert_eq!(d0.univ_coeff(0, 4), rat(1));
assert_eq!(d0.constant_term(), rat(5));
let d1 = p.nth_derivative(0, 1);
assert_eq!(d1.univ_coeff(0, 3), rat(4));
assert_eq!(d1.univ_coeff(0, 2), rat(6));
assert_eq!(d1.univ_coeff(0, 1), rat(6));
assert_eq!(d1.constant_term(), rat(4));
let d2 = p.nth_derivative(0, 2);
assert_eq!(d2.univ_coeff(0, 2), rat(12));
assert_eq!(d2.univ_coeff(0, 1), rat(12));
assert_eq!(d2.constant_term(), rat(6));
let d3 = p.nth_derivative(0, 3);
assert_eq!(d3.univ_coeff(0, 1), rat(24));
assert_eq!(d3.constant_term(), rat(12));
let d4 = p.nth_derivative(0, 4);
assert_eq!(d4.constant_term(), rat(24));
let d5 = p.nth_derivative(0, 5);
assert!(d5.is_zero());
let d10 = p.nth_derivative(0, 10);
assert!(d10.is_zero());
}
#[test]
fn test_polynomial_integrate() {
let p = Polynomial::from_coeffs_int(&[(3, &[(0, 2)])]);
let integral = p.integrate(0);
assert_eq!(integral.univ_coeff(0, 3), rat(1));
assert!(integral.constant_term().is_zero());
let deriv = integral.derivative(0);
assert_eq!(deriv, p);
}
#[test]
fn test_polynomial_integrate_constant() {
let p = Polynomial::from_coeffs_int(&[(5, &[])]);
let integral = p.integrate(0);
assert_eq!(integral.univ_coeff(0, 1), rat(5));
assert!(integral.constant_term().is_zero());
}
#[test]
fn test_polynomial_integrate_complex() {
let p = Polynomial::from_coeffs_int(&[
(4, &[(0, 3)]),
(6, &[(0, 2)]),
(6, &[(0, 1)]),
(4, &[]),
]);
let integral = p.integrate(0);
assert_eq!(integral.univ_coeff(0, 4), rat(1));
assert_eq!(integral.univ_coeff(0, 3), rat(2));
assert_eq!(integral.univ_coeff(0, 2), rat(3));
assert_eq!(integral.univ_coeff(0, 1), rat(4));
assert!(integral.constant_term().is_zero());
let deriv = integral.derivative(0);
assert_eq!(deriv, p);
}
#[test]
fn test_polynomial_integrate_multivariate() {
let p = Polynomial::from_coeffs_int(&[(2, &[(0, 1), (1, 1)])]);
let integral = p.integrate(0);
let mut assignment = rustc_hash::FxHashMap::default();
assignment.insert(0, rat(3)); assignment.insert(1, rat(2));
assert_eq!(p.eval(&assignment), rat(12));
assert_eq!(integral.eval(&assignment), rat(18));
let deriv = integral.derivative(0);
assert_eq!(deriv, p);
}
#[test]
fn test_polynomial_integrate_then_differentiate_roundtrip() {
let p = Polynomial::from_coeffs_int(&[
(1, &[(0, 4)]),
(2, &[(0, 3)]),
(3, &[(0, 2)]),
(4, &[(0, 1)]),
(5, &[]),
]);
let integral = p.integrate(0);
let deriv = integral.derivative(0);
assert_eq!(deriv, p);
}
#[test]
fn test_polynomial_definite_integral() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 2)])]);
let result = p.definite_integral(0, &rat(0), &rat(2)).unwrap();
assert_eq!(result, BigRational::new(BigInt::from(8), BigInt::from(3)));
}
#[test]
fn test_polynomial_definite_integral_constant() {
let p = Polynomial::from_coeffs_int(&[(5, &[])]);
let result = p.definite_integral(0, &rat(1), &rat(3)).unwrap();
assert_eq!(result, rat(10));
}
#[test]
fn test_polynomial_definite_integral_complex() {
let p = Polynomial::from_coeffs_int(&[
(1, &[(0, 3)]),
(2, &[(0, 2)]),
(3, &[(0, 1)]),
(4, &[]),
]);
let result = p.definite_integral(0, &rat(0), &rat(1)).unwrap();
assert_eq!(result, BigRational::new(BigInt::from(77), BigInt::from(12)));
}
#[test]
fn test_polynomial_find_critical_points() {
let p = Polynomial::from_coeffs_int(&[
(1, &[(0, 3)]), (-3, &[(0, 1)]), ]);
let critical_points = p.find_critical_points(0);
assert_eq!(critical_points.len(), 2);
let has_negative_one = critical_points
.iter()
.any(|(lower, upper)| lower <= &rat(-1) && &rat(-1) <= upper);
let has_positive_one = critical_points
.iter()
.any(|(lower, upper)| lower <= &rat(1) && &rat(1) <= upper);
assert!(has_negative_one);
assert!(has_positive_one);
}
#[test]
fn test_polynomial_find_critical_points_quadratic() {
let p = Polynomial::from_coeffs_int(&[
(1, &[(0, 2)]), (-4, &[(0, 1)]), (3, &[]), ]);
let critical_points = p.find_critical_points(0);
assert_eq!(critical_points.len(), 1);
let (lower, upper) = &critical_points[0];
assert!(lower <= &rat(2) && &rat(2) <= upper);
}
#[test]
fn test_polynomial_trapezoidal_rule() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 2)])]);
let approx_10 = p.trapezoidal_rule(0, &rat(0), &rat(1), 10);
let exact = BigRational::new(BigInt::from(1), BigInt::from(3));
let error_10 = (&approx_10 - &exact).abs();
assert!(error_10 < BigRational::new(BigInt::from(1), BigInt::from(10)));
let approx_100 = p.trapezoidal_rule(0, &rat(0), &rat(1), 100);
let error_100 = (&approx_100 - &exact).abs();
assert!(error_100 < BigRational::new(BigInt::from(1), BigInt::from(100)));
assert!(error_100 < error_10);
}
#[test]
fn test_polynomial_simpsons_rule() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 2)])]);
let approx = p.simpsons_rule(0, &rat(0), &rat(1), 10);
let exact = BigRational::new(BigInt::from(1), BigInt::from(3));
let error = (&approx - &exact).abs();
assert!(error < BigRational::new(BigInt::from(1), BigInt::from(1000)));
}
#[test]
fn test_polynomial_simpsons_vs_trapezoidal() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 3)])]);
let exact = rat(4);
let trap_approx = p.trapezoidal_rule(0, &rat(0), &rat(2), 20);
let simp_approx = p.simpsons_rule(0, &rat(0), &rat(2), 20);
let trap_error = (&trap_approx - &exact).abs();
let simp_error = (&simp_approx - &exact).abs();
assert!(simp_error <= trap_error);
}
#[test]
fn test_numerical_integration_consistency() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 4)]), (2, &[(0, 3)]), (3, &[(0, 2)])]);
let symbolic = p.definite_integral(0, &rat(0), &rat(1)).unwrap();
let numerical_trap = p.trapezoidal_rule(0, &rat(0), &rat(1), 1000);
let numerical_simp = p.simpsons_rule(0, &rat(0), &rat(1), 1000);
let trap_diff = (&numerical_trap - &symbolic).abs();
let simp_diff = (&numerical_simp - &symbolic).abs();
assert!(trap_diff < BigRational::new(BigInt::from(1), BigInt::from(100)));
assert!(simp_diff < BigRational::new(BigInt::from(1), BigInt::from(10000)));
}
#[test]
fn test_polynomial_multivariate() {
let p = Polynomial::from_coeffs_int(&[
(1, &[(0, 1), (1, 1)]),
(1, &[(0, 1)]),
(1, &[(1, 1)]),
(1, &[]),
]);
assert!(!p.is_univariate());
assert!(!p.is_linear()); assert_eq!(p.degree(0), 1);
assert_eq!(p.degree(1), 1);
assert_eq!(p.total_degree(), 2);
let q = Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (1, &[(1, 1)]), (1, &[])]);
assert!(!q.is_univariate());
assert!(q.is_linear());
assert_eq!(q.total_degree(), 1);
}
#[test]
fn test_polynomial_pow() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (1, &[])]);
let p2 = p.pow(2);
assert_eq!(p2.univ_coeff(0, 2), rat(1));
assert_eq!(p2.univ_coeff(0, 1), rat(2));
assert_eq!(p2.constant_term(), rat(1));
}
#[test]
fn test_polynomial_eval_at() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (2, &[(0, 1)]), (1, &[])]);
let r = p.eval_at(0, &rat(3));
assert!(r.is_constant());
assert_eq!(r.constant_term(), rat(16));
}
#[test]
fn test_polynomial_gcd() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (-1, &[])]);
let q = Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (-1, &[])]);
let g = p.gcd_univariate(&q);
assert_eq!(g.total_degree(), 1);
}
#[test]
fn test_monomial_ordering() {
let m1 = Monomial::from_powers([(0, 2), (1, 1)]); let m2 = Monomial::from_powers([(0, 1), (1, 2)]);
assert_eq!(m1.total_degree(), 3);
assert_eq!(m2.total_degree(), 3);
assert_eq!(m1.grlex_cmp(&m2), Ordering::Greater);
}
#[test]
fn test_sturm_sequence() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (-2, &[])]);
let seq = p.sturm_sequence(0);
assert!(seq.len() >= 2);
assert_eq!(seq[0], p);
assert_eq!(seq[1].degree(0), 1);
}
#[test]
fn test_root_bounds_cauchy() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (-2, &[])]);
let bound = p.cauchy_bound(0);
assert_eq!(bound, rat(3));
assert!(bound > rat(2));
}
#[test]
fn test_root_bounds_fujiwara() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (-2, &[])]);
let bound = p.fujiwara_bound(0);
let _cauchy = p.cauchy_bound(0);
assert!(bound > rat(2));
assert!(bound >= rat(1));
}
#[test]
fn test_root_bounds_lagrange() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (-5, &[(0, 1)]), (6, &[])]);
let bound = p.lagrange_positive_bound(0);
assert!(bound >= rat(3));
assert!(bound <= rat(10));
}
#[test]
fn test_root_bounds_comparison() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 3)]), (-7, &[(0, 1)]), (6, &[])]);
let cauchy = p.cauchy_bound(0);
let fujiwara = p.fujiwara_bound(0);
assert!(cauchy >= rat(3));
assert!(fujiwara >= rat(3));
assert!(fujiwara > BigRational::zero());
}
#[test]
fn test_count_roots() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (-2, &[])]);
let count = p.count_roots_in_interval(0, &rat(-2), &rat(2));
assert_eq!(count, 2);
let count = p.count_roots_in_interval(0, &rat(0), &rat(2));
assert_eq!(count, 1);
let count = p.count_roots_in_interval(0, &rat(-2), &rat(0));
assert_eq!(count, 1);
}
#[test]
fn test_isolate_roots_simple() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (-3, &[(0, 1)]), (2, &[])]);
let roots = p.isolate_roots(0);
assert!(roots.len() >= 2);
let mut found_one = false;
let mut found_two = false;
for (a, b) in &roots {
assert!(a <= b); if a <= &rat(1) && &rat(1) <= b {
found_one = true;
}
if a <= &rat(2) && &rat(2) <= b {
found_two = true;
}
}
assert!(found_one, "Root at x=1 not found");
assert!(found_two, "Root at x=2 not found");
}
#[test]
fn test_square_free() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 2)])]);
let sf = p.square_free();
assert_eq!(sf.degree(0), 1);
}
#[test]
fn test_isolate_roots_quadratic() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (-5, &[])]);
let roots = p.isolate_roots(0);
assert_eq!(roots.len(), 2);
for (a, b) in &roots {
assert!(a <= b);
let val_a = p.eval_at(0, a).constant_term();
let val_b = p.eval_at(0, b).constant_term();
if !val_a.is_zero() && !val_b.is_zero() {
let sign_change = (val_a.is_positive() && val_b.is_negative())
|| (val_a.is_negative() && val_b.is_positive());
assert!(sign_change || a == b);
}
}
}
#[test]
fn test_factor_linear() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (-1, &[])]);
let factors = p.factor(0);
assert_eq!(factors.len(), 1);
assert_eq!(factors[0].1, 1);
}
#[test]
fn test_factor_quadratic_factorable() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (-3, &[(0, 1)]), (2, &[])]);
let factors = p.factor(0);
assert_eq!(factors.len(), 2);
for (f, mult) in &factors {
assert_eq!(f.degree(0), 1);
assert_eq!(*mult, 1);
}
}
#[test]
fn test_factor_quadratic_irreducible() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (1, &[])]);
let factors = p.factor(0);
assert_eq!(factors.len(), 1);
assert_eq!(factors[0].0.degree(0), 2);
}
#[test]
fn test_factor_quadratic_perfect_square() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (-2, &[(0, 1)]), (1, &[])]);
let factors = p.factor(0);
assert!(!factors.is_empty());
}
#[test]
fn test_is_irreducible_linear() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (-1, &[])]);
assert!(p.is_irreducible(0));
}
#[test]
fn test_is_irreducible_quadratic_true() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (1, &[])]);
assert!(p.is_irreducible(0));
}
#[test]
fn test_is_irreducible_quadratic_false() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (-4, &[])]);
assert!(!p.is_irreducible(0));
}
#[test]
fn test_content() {
let p = Polynomial::from_coeffs_int(&[(2, &[(0, 2)]), (4, &[(0, 1)]), (6, &[])]);
let content = p.content();
assert_eq!(content, rat(2));
}
#[test]
fn test_integer_sqrt_perfect() {
assert_eq!(integer_sqrt(&BigInt::from(0)), Some(BigInt::from(0)));
assert_eq!(integer_sqrt(&BigInt::from(1)), Some(BigInt::from(1)));
assert_eq!(integer_sqrt(&BigInt::from(4)), Some(BigInt::from(2)));
assert_eq!(integer_sqrt(&BigInt::from(9)), Some(BigInt::from(3)));
assert_eq!(integer_sqrt(&BigInt::from(16)), Some(BigInt::from(4)));
assert_eq!(integer_sqrt(&BigInt::from(25)), Some(BigInt::from(5)));
assert_eq!(integer_sqrt(&BigInt::from(100)), Some(BigInt::from(10)));
}
#[test]
fn test_integer_sqrt_not_perfect() {
assert_eq!(integer_sqrt(&BigInt::from(2)), None);
assert_eq!(integer_sqrt(&BigInt::from(3)), None);
assert_eq!(integer_sqrt(&BigInt::from(5)), None);
assert_eq!(integer_sqrt(&BigInt::from(10)), None);
assert_eq!(integer_sqrt(&BigInt::from(15)), None);
}
#[test]
fn test_is_perfect_square() {
assert!(is_perfect_square(&BigInt::from(0)));
assert!(is_perfect_square(&BigInt::from(1)));
assert!(is_perfect_square(&BigInt::from(4)));
assert!(is_perfect_square(&BigInt::from(9)));
assert!(!is_perfect_square(&BigInt::from(2)));
assert!(!is_perfect_square(&BigInt::from(3)));
assert!(!is_perfect_square(&BigInt::from(-1)));
}
#[test]
fn test_resultant_simple() {
let p = Polynomial::from_var(0); let q = Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (-1, &[])]); let res = p.resultant(&q, 0);
assert!(!res.is_zero());
}
#[test]
fn test_resultant_common_factor() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (-1, &[])]); let q = Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (-1, &[])]); let res = p.resultant(&q, 0);
let _ = res; }
#[test]
fn test_discriminant_quadratic() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (-3, &[(0, 1)]), (2, &[])]);
let disc = p.discriminant(0);
assert!(!disc.is_zero());
}
#[test]
fn test_substitute_edge_cases() {
let p = Polynomial::zero();
let q = Polynomial::from_var(0);
let result = p.substitute(0, &q);
assert!(result.is_zero());
let p = Polynomial::constant(rat(5));
let q = Polynomial::from_var(1);
let result = p.substitute(0, &q);
assert_eq!(result.constant_term(), rat(5));
}
#[test]
fn test_polynomial_primitive_edge_cases() {
let p = Polynomial::zero();
let prim = p.primitive();
assert!(prim.is_zero());
let p = Polynomial::constant(rat(6));
let prim = p.primitive();
assert_eq!(prim.constant_term(), rat(1));
let p = Polynomial::from_coeffs_int(&[(2, &[(0, 1)]), (4, &[])]);
let prim = p.primitive();
assert!(prim.terms[0].coeff.abs() <= rat(2));
}
#[test]
fn test_polynomial_compose() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 2)])]); let q = Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (1, &[])]);
let composed = p.compose(0, &q);
let mut env = FxHashMap::default();
env.insert(0, rat(0));
assert_eq!(composed.eval(&env), rat(1));
env.insert(0, rat(1));
assert_eq!(composed.eval(&env), rat(4));
env.insert(0, rat(2));
assert_eq!(composed.eval(&env), rat(9)); }
#[test]
fn test_lagrange_interpolation_linear() {
let points = vec![(rat(0), rat(1)), (rat(1), rat(3))];
let poly = Polynomial::lagrange_interpolate(0, &points).unwrap();
let mut env = FxHashMap::default();
env.insert(0, rat(0));
assert_eq!(poly.eval(&env), rat(1));
env.insert(0, rat(1));
assert_eq!(poly.eval(&env), rat(3));
env.insert(0, rat(2));
assert_eq!(poly.eval(&env), rat(5)); }
#[test]
fn test_lagrange_interpolation_quadratic() {
let points = vec![(rat(0), rat(1)), (rat(1), rat(0)), (rat(2), rat(3))];
let poly = Polynomial::lagrange_interpolate(0, &points).unwrap();
let mut env = FxHashMap::default();
env.insert(0, rat(0));
assert_eq!(poly.eval(&env), rat(1));
env.insert(0, rat(1));
assert_eq!(poly.eval(&env), rat(0));
env.insert(0, rat(2));
assert_eq!(poly.eval(&env), rat(3));
}
#[test]
fn test_lagrange_interpolation_single_point() {
let points = vec![(rat(5), rat(7))];
let poly = Polynomial::lagrange_interpolate(0, &points).unwrap();
let mut env = FxHashMap::default();
env.insert(0, rat(0));
assert_eq!(poly.eval(&env), rat(7));
env.insert(0, rat(100));
assert_eq!(poly.eval(&env), rat(7));
}
#[test]
fn test_lagrange_interpolation_duplicate_x() {
let points = vec![(rat(1), rat(2)), (rat(1), rat(3))];
assert!(Polynomial::lagrange_interpolate(0, &points).is_none());
}
#[test]
fn test_newton_interpolation_linear() {
let points = vec![(rat(0), rat(1)), (rat(1), rat(3))];
let poly = Polynomial::newton_interpolate(0, &points).unwrap();
let mut env = FxHashMap::default();
env.insert(0, rat(0));
assert_eq!(poly.eval(&env), rat(1));
env.insert(0, rat(1));
assert_eq!(poly.eval(&env), rat(3));
env.insert(0, rat(2));
assert_eq!(poly.eval(&env), rat(5));
}
#[test]
fn test_newton_interpolation_quadratic() {
let points = vec![(rat(0), rat(1)), (rat(1), rat(0)), (rat(2), rat(3))];
let poly = Polynomial::newton_interpolate(0, &points).unwrap();
let mut env = FxHashMap::default();
env.insert(0, rat(0));
assert_eq!(poly.eval(&env), rat(1));
env.insert(0, rat(1));
assert_eq!(poly.eval(&env), rat(0));
env.insert(0, rat(2));
assert_eq!(poly.eval(&env), rat(3));
}
#[test]
fn test_lagrange_vs_newton() {
let points = vec![
(rat(0), rat(2)),
(rat(1), rat(-1)),
(rat(2), rat(4)),
(rat(3), rat(1)),
];
let lagrange = Polynomial::lagrange_interpolate(0, &points).unwrap();
let newton = Polynomial::newton_interpolate(0, &points).unwrap();
let mut env = FxHashMap::default();
for x in -5..10 {
let val = rat(x);
env.insert(0, val);
assert_eq!(
lagrange.eval(&env),
newton.eval(&env),
"Polynomials differ at x = {}",
x
);
}
}
#[test]
fn test_descartes_rule_positive() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (-1, &[])]);
let (lower, upper) = super::descartes_positive_roots(&p, 0);
assert_eq!(upper, 1);
assert_eq!(lower, 1);
}
#[test]
fn test_descartes_rule_no_positive_roots() {
let p = Polynomial::from_coeffs_int(&[(-1, &[(0, 2)]), (-1, &[])]);
let (lower, upper) = super::descartes_positive_roots(&p, 0);
assert_eq!(upper, 0);
assert_eq!(lower, 0);
}
#[test]
fn test_descartes_rule_multiple_variations() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 3)]), (-1, &[(0, 1)])]);
let (_lower, upper) = super::descartes_positive_roots(&p, 0);
assert_eq!(upper, 1);
}
#[test]
fn test_descartes_rule_negative_roots() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (-1, &[])]);
let (lower, upper) = super::descartes_negative_roots(&p, 0);
assert_eq!(upper, 1);
assert_eq!(lower, 1);
}
#[test]
fn test_isolate_roots_with_descartes() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 3)]), (-2, &[])]);
let roots = p.isolate_roots(0);
assert_eq!(roots.len(), 1);
assert!(roots[0].0.is_positive() || roots[0].0.is_zero());
assert!(roots[0].1.is_positive());
}
#[test]
fn test_isolate_roots_all_negative() {
let p = Polynomial::from_coeffs_int(&[(-1, &[(0, 2)]), (-1, &[])]);
let roots = p.isolate_roots(0);
assert_eq!(roots.len(), 0);
}
#[test]
fn test_subresultant_prs_simple() {
let p1 = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (-1, &[])]);
let p2 = Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (-1, &[])]);
let prs = p1.subresultant_prs(&p2, 0);
assert!(prs.len() >= 2);
assert_eq!(prs[0].degree(0), 2); assert_eq!(prs[1].degree(0), 1); }
#[test]
fn test_subresultant_prs_coprime() {
let p1 = Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (1, &[])]);
let p2 = Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (2, &[])]);
let prs = p1.subresultant_prs(&p2, 0);
assert!(prs.len() >= 2);
let last = prs.last().unwrap();
assert!(last.is_constant() || last.degree(0) == 0);
}
#[test]
fn test_subresultant_prs_with_gcd() {
let p1 = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (-1, &[])]);
let p2 = Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (-1, &[])]);
let gcd = p1.gcd_univariate(&p2);
assert_eq!(gcd.degree(0), 1);
}
#[test]
fn test_chebyshev_first_kind_t0() {
let t0 = Polynomial::chebyshev_first_kind(0, 0);
assert!(t0.is_constant());
assert_eq!(t0.constant_value(), rat(1));
}
#[test]
fn test_chebyshev_first_kind_t1() {
let t1 = Polynomial::chebyshev_first_kind(0, 1);
assert_eq!(t1.degree(0), 1);
let mut env = FxHashMap::default();
env.insert(0, rat(2));
assert_eq!(t1.eval(&env), rat(2));
}
#[test]
fn test_chebyshev_first_kind_t2() {
let t2 = Polynomial::chebyshev_first_kind(0, 2);
assert_eq!(t2.degree(0), 2);
let mut env = FxHashMap::default();
env.insert(0, rat(0));
assert_eq!(t2.eval(&env), rat(-1));
env.insert(0, rat(1));
assert_eq!(t2.eval(&env), rat(1));
}
#[test]
fn test_chebyshev_first_kind_t3() {
let t3 = Polynomial::chebyshev_first_kind(0, 3);
assert_eq!(t3.degree(0), 3);
let mut env = FxHashMap::default();
env.insert(0, rat(0));
assert_eq!(t3.eval(&env), rat(0));
env.insert(0, rat(1));
assert_eq!(t3.eval(&env), rat(1));
}
#[test]
fn test_chebyshev_second_kind_u0() {
let u0 = Polynomial::chebyshev_second_kind(0, 0);
assert!(u0.is_constant());
assert_eq!(u0.constant_value(), rat(1));
}
#[test]
fn test_chebyshev_second_kind_u1() {
let u1 = Polynomial::chebyshev_second_kind(0, 1);
assert_eq!(u1.degree(0), 1);
let mut env = FxHashMap::default();
env.insert(0, rat(2));
assert_eq!(u1.eval(&env), rat(4)); }
#[test]
fn test_chebyshev_second_kind_u2() {
let u2 = Polynomial::chebyshev_second_kind(0, 2);
assert_eq!(u2.degree(0), 2);
let mut env = FxHashMap::default();
env.insert(0, rat(0));
assert_eq!(u2.eval(&env), rat(-1));
env.insert(0, rat(1));
assert_eq!(u2.eval(&env), rat(3));
}
#[test]
fn test_chebyshev_nodes() {
let nodes = Polynomial::chebyshev_nodes(2);
assert_eq!(nodes.len(), 3);
for node in &nodes {
assert!(node >= &rat(-1));
assert!(node <= &rat(1));
}
}
#[test]
fn test_legendre_p0() {
let p0 = Polynomial::legendre(0, 0);
assert!(p0.is_constant());
assert_eq!(p0.constant_value(), rat(1));
}
#[test]
fn test_legendre_p1() {
let p1 = Polynomial::legendre(0, 1);
assert_eq!(p1.degree(0), 1);
let mut env = FxHashMap::default();
env.insert(0, rat(2));
assert_eq!(p1.eval(&env), rat(2));
}
#[test]
fn test_legendre_p2() {
let p2 = Polynomial::legendre(0, 2);
assert_eq!(p2.degree(0), 2);
let mut env = FxHashMap::default();
env.insert(0, rat(0));
assert_eq!(
p2.eval(&env),
BigRational::new(BigInt::from(-1), BigInt::from(2))
);
env.insert(0, rat(1));
assert_eq!(p2.eval(&env), rat(1));
}
#[test]
fn test_legendre_orthogonality() {
for n in 0..5 {
let p_n = Polynomial::legendre(0, n);
let mut env = FxHashMap::default();
env.insert(0, rat(1));
assert_eq!(p_n.eval(&env), rat(1), "P_{}(1) should be 1", n);
}
}
#[test]
fn test_hermite_h0() {
let h0 = Polynomial::hermite(0, 0);
assert!(h0.is_constant());
assert_eq!(h0.constant_value(), rat(1));
}
#[test]
fn test_hermite_h1() {
let h1 = Polynomial::hermite(0, 1);
assert_eq!(h1.degree(0), 1);
let mut env = FxHashMap::default();
env.insert(0, rat(3));
assert_eq!(h1.eval(&env), rat(6)); }
#[test]
fn test_hermite_h2() {
let h2 = Polynomial::hermite(0, 2);
assert_eq!(h2.degree(0), 2);
let mut env = FxHashMap::default();
env.insert(0, rat(0));
assert_eq!(h2.eval(&env), rat(-2));
env.insert(0, rat(1));
assert_eq!(h2.eval(&env), rat(2));
}
#[test]
fn test_hermite_h3() {
let h3 = Polynomial::hermite(0, 3);
assert_eq!(h3.degree(0), 3);
let mut env = FxHashMap::default();
env.insert(0, rat(0));
assert_eq!(h3.eval(&env), rat(0));
env.insert(0, rat(1));
assert_eq!(h3.eval(&env), rat(-4));
}
#[test]
fn test_laguerre_l0() {
let l0 = Polynomial::laguerre(0, 0);
assert!(l0.is_constant());
assert_eq!(l0.constant_value(), rat(1));
}
#[test]
fn test_laguerre_l1() {
let l1 = Polynomial::laguerre(0, 1);
assert_eq!(l1.degree(0), 1);
let mut env = FxHashMap::default();
env.insert(0, rat(0));
assert_eq!(l1.eval(&env), rat(1));
env.insert(0, rat(1));
assert_eq!(l1.eval(&env), rat(0));
}
#[test]
fn test_laguerre_l2() {
let l2 = Polynomial::laguerre(0, 2);
assert_eq!(l2.degree(0), 2);
let mut env = FxHashMap::default();
env.insert(0, rat(0));
assert_eq!(l2.eval(&env), rat(1));
env.insert(0, rat(2));
assert_eq!(l2.eval(&env), rat(-1));
}
#[test]
fn test_orthogonal_polynomials_degree() {
for n in 0..6 {
let cheb_t = Polynomial::chebyshev_first_kind(0, n);
let cheb_u = Polynomial::chebyshev_second_kind(0, n);
let legendre = Polynomial::legendre(0, n);
let hermite = Polynomial::hermite(0, n);
let laguerre = Polynomial::laguerre(0, n);
assert_eq!(cheb_t.degree(0), n, "Chebyshev T_{} has wrong degree", n);
assert_eq!(cheb_u.degree(0), n, "Chebyshev U_{} has wrong degree", n);
assert_eq!(legendre.degree(0), n, "Legendre P_{} has wrong degree", n);
assert_eq!(hermite.degree(0), n, "Hermite H_{} has wrong degree", n);
assert_eq!(laguerre.degree(0), n, "Laguerre L_{} has wrong degree", n);
}
}
#[test]
fn test_horner_constant() {
let p = Polynomial::constant(rat(42));
let result = p.eval_horner(0, &rat(10));
assert_eq!(result, rat(42));
}
#[test]
fn test_horner_linear() {
let p = Polynomial::from_coeffs_int(&[(2, &[(0, 1)]), (3, &[])]);
let result = p.eval_horner(0, &rat(5));
assert_eq!(result, rat(13));
let result = p.eval_horner(0, &rat(0));
assert_eq!(result, rat(3));
}
#[test]
fn test_horner_quadratic() {
let p = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (2, &[(0, 1)]), (1, &[])]);
let result = p.eval_horner(0, &rat(3));
assert_eq!(result, rat(16));
let result = p.eval_horner(0, &rat(-1));
assert_eq!(result, rat(0));
}
#[test]
fn test_horner_cubic() {
let p = Polynomial::from_coeffs_int(&[
(1, &[(0, 3)]),
(-2, &[(0, 2)]),
(1, &[(0, 1)]),
(-5, &[]),
]);
let result = p.eval_horner(0, &rat(2));
assert_eq!(result, rat(-3));
let result = p.eval_horner(0, &rat(0));
assert_eq!(result, rat(-5));
}
#[test]
fn test_horner_vs_eval() {
let p = Polynomial::from_coeffs_int(&[
(1, &[(0, 4)]),
(-3, &[(0, 3)]),
(2, &[(0, 2)]),
(5, &[(0, 1)]),
(-7, &[]),
]);
for x in -5..=5 {
let val = rat(x);
let horner_result = p.eval_horner(0, &val);
let mut env = FxHashMap::default();
env.insert(0, val);
let eval_result = p.eval(&env);
assert_eq!(
horner_result, eval_result,
"Horner and eval differ at x = {}",
x
);
}
}
#[test]
fn test_horner_with_fractions() {
let p1 = Polynomial::from_coeffs_int(&[(1, &[(0, 2)])]);
let p2 = Polynomial::constant(BigRational::new(BigInt::from(1), BigInt::from(2)));
let p = Polynomial::sub(&p1, &p2);
let result = p.eval_horner(0, &BigRational::new(BigInt::from(1), BigInt::from(2)));
assert_eq!(result, BigRational::new(BigInt::from(-1), BigInt::from(4)));
}
#[test]
fn test_horner_zero_polynomial() {
let p = Polynomial::zero();
let result = p.eval_horner(0, &rat(100));
assert_eq!(result, rat(0));
}
#[test]
fn test_gradient_simple() {
let f = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (1, &[(1, 2)])]);
let grad = f.gradient();
assert_eq!(grad.len(), 2);
let mut env = FxHashMap::default();
env.insert(0, rat(3));
env.insert(1, rat(0));
assert_eq!(grad[0].eval(&env), rat(6));
env.insert(0, rat(0));
env.insert(1, rat(4));
assert_eq!(grad[1].eval(&env), rat(8)); }
#[test]
fn test_gradient_multivariate() {
let f = Polynomial::from_coeffs_int(&[
(1, &[(0, 2), (1, 1)]), (2, &[(0, 1), (1, 1)]), (1, &[(1, 2)]), ]);
let grad = f.gradient();
assert_eq!(grad.len(), 2);
let mut env = FxHashMap::default();
env.insert(0, rat(1));
env.insert(1, rat(2));
assert_eq!(grad[0].eval(&env), rat(8));
assert_eq!(grad[1].eval(&env), rat(7)); }
#[test]
fn test_gradient_constant() {
let f = Polynomial::constant(rat(5));
let grad = f.gradient();
assert_eq!(grad.len(), 0); }
#[test]
fn test_hessian_simple() {
let f = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (1, &[(1, 2)])]);
let hessian = f.hessian();
assert_eq!(hessian.len(), 2);
assert_eq!(hessian[0].len(), 2);
assert!(hessian[0][0].is_constant());
assert_eq!(hessian[0][0].constant_term(), rat(2));
assert!(hessian[1][1].is_constant());
assert_eq!(hessian[1][1].constant_term(), rat(2));
assert!(hessian[0][1].is_zero());
assert!(hessian[1][0].is_zero());
}
#[test]
fn test_hessian_with_cross_terms() {
let f = Polynomial::from_coeffs_int(&[
(1, &[(0, 2)]), (1, &[(0, 1), (1, 1)]), (1, &[(1, 2)]), ]);
let hessian = f.hessian();
assert_eq!(hessian.len(), 2);
assert_eq!(hessian[0][0].constant_term(), rat(2));
assert_eq!(hessian[1][1].constant_term(), rat(2));
assert_eq!(hessian[0][1].constant_term(), rat(1));
assert_eq!(hessian[1][0].constant_term(), rat(1));
}
#[test]
fn test_hessian_symmetry() {
let f = Polynomial::from_coeffs_int(&[
(1, &[(0, 2), (1, 1)]), (1, &[(0, 1), (1, 1), (2, 1)]), (1, &[(2, 3)]), ]);
let hessian = f.hessian();
assert_eq!(hessian.len(), 3);
for i in 0..3 {
for j in 0..3 {
assert_eq!(
hessian[i][j], hessian[j][i],
"Hessian not symmetric at ({}, {})",
i, j
);
}
}
}
#[test]
fn test_jacobian_simple() {
let f1 = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (1, &[(1, 1)])]);
let f2 = Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (1, &[(1, 2)])]);
let jacobian = Polynomial::jacobian(&[f1, f2]);
assert_eq!(jacobian.len(), 2); assert_eq!(jacobian[0].len(), 2);
let mut env = FxHashMap::default();
env.insert(0, rat(3));
env.insert(1, rat(0));
assert_eq!(jacobian[0][0].eval(&env), rat(6));
assert_eq!(jacobian[0][1].constant_term(), rat(1));
assert_eq!(jacobian[1][0].constant_term(), rat(1));
env.insert(0, rat(0));
env.insert(1, rat(5));
assert_eq!(jacobian[1][1].eval(&env), rat(10)); }
#[test]
fn test_jacobian_empty() {
let jacobian = Polynomial::jacobian(&[]);
assert!(jacobian.is_empty());
}
#[test]
fn test_jacobian_single_function() {
let f = Polynomial::from_coeffs_int(&[(1, &[(0, 1)]), (2, &[(1, 1)]), (3, &[(2, 1)])]);
let jacobian = Polynomial::jacobian(&[f]);
assert_eq!(jacobian.len(), 1);
assert_eq!(jacobian[0].len(), 3);
assert_eq!(jacobian[0][0].constant_term(), rat(1));
assert_eq!(jacobian[0][1].constant_term(), rat(2));
assert_eq!(jacobian[0][2].constant_term(), rat(3));
}
}