use alloc::vec;
use alloc::vec::Vec;
use puremp::Rational;
fn zero() -> Rational {
Rational::from_integer(0.into())
}
fn one() -> Rational {
Rational::from_integer(1.into())
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UPoly {
coeffs: Vec<Rational>,
}
impl UPoly {
pub fn zero() -> UPoly {
UPoly { coeffs: Vec::new() }
}
pub fn constant(c: Rational) -> UPoly {
UPoly::from_coeffs(vec![c])
}
pub fn x() -> UPoly {
UPoly::from_coeffs(vec![zero(), one()])
}
pub fn from_coeffs(mut coeffs: Vec<Rational>) -> UPoly {
while coeffs.last().is_some_and(|c| c.is_zero()) {
coeffs.pop();
}
UPoly { coeffs }
}
pub fn coeffs(&self) -> &[Rational] {
&self.coeffs
}
pub fn is_zero(&self) -> bool {
self.coeffs.is_empty()
}
pub fn degree(&self) -> usize {
self.coeffs.len().saturating_sub(1)
}
pub fn lead(&self) -> Rational {
self.coeffs.last().cloned().unwrap_or_else(zero)
}
pub fn eval(&self, x: &Rational) -> Rational {
let mut acc = zero();
for c in self.coeffs.iter().rev() {
acc = acc.mul(x).add(c);
}
acc
}
pub fn sign_at(&self, x: &Rational) -> i32 {
self.eval(x).signum()
}
pub fn deriv(&self) -> UPoly {
if self.coeffs.len() <= 1 {
return UPoly::zero();
}
let mut c = Vec::with_capacity(self.coeffs.len() - 1);
for (i, coeff) in self.coeffs.iter().enumerate().skip(1) {
c.push(coeff.mul(&Rational::from_integer((i as i64).into())));
}
UPoly::from_coeffs(c)
}
pub fn scale(&self, s: &Rational) -> UPoly {
if s.is_zero() {
return UPoly::zero();
}
UPoly::from_coeffs(self.coeffs.iter().map(|c| c.mul(s)).collect())
}
pub fn neg(&self) -> UPoly {
UPoly::from_coeffs(self.coeffs.iter().map(|c| c.neg()).collect())
}
pub fn add(&self, other: &UPoly) -> UPoly {
let n = self.coeffs.len().max(other.coeffs.len());
let mut c = vec![zero(); n];
for (i, a) in self.coeffs.iter().enumerate() {
c[i] = c[i].add(a);
}
for (i, b) in other.coeffs.iter().enumerate() {
c[i] = c[i].add(b);
}
UPoly::from_coeffs(c)
}
pub fn sub(&self, other: &UPoly) -> UPoly {
let n = self.coeffs.len().max(other.coeffs.len());
let mut c = vec![zero(); n];
for (i, a) in self.coeffs.iter().enumerate() {
c[i] = c[i].add(a);
}
for (i, b) in other.coeffs.iter().enumerate() {
c[i] = c[i].sub(b);
}
UPoly::from_coeffs(c)
}
pub fn mul(&self, other: &UPoly) -> UPoly {
if self.is_zero() || other.is_zero() {
return UPoly::zero();
}
let mut c = vec![zero(); self.coeffs.len() + other.coeffs.len() - 1];
for (i, a) in self.coeffs.iter().enumerate() {
for (j, b) in other.coeffs.iter().enumerate() {
c[i + j] = c[i + j].add(&a.mul(b));
}
}
UPoly::from_coeffs(c)
}
pub fn pow(&self, n: u32) -> UPoly {
let mut acc = UPoly::constant(one());
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 rem(&self, divisor: &UPoly) -> UPoly {
debug_assert!(!divisor.is_zero());
let mut r = self.clone();
let d_deg = divisor.degree();
let d_lead = divisor.lead();
while !r.is_zero() && r.degree() >= d_deg {
let shift = r.degree() - d_deg;
let factor = r.lead().div(&d_lead);
let mut sub = vec![zero(); shift + divisor.coeffs.len()];
for (i, c) in divisor.coeffs.iter().enumerate() {
sub[i + shift] = c.mul(&factor);
}
r = r.sub(&UPoly::from_coeffs(sub));
}
r
}
pub fn div_rem(&self, divisor: &UPoly) -> (UPoly, UPoly) {
debug_assert!(!divisor.is_zero());
let mut r = self.clone();
let d_deg = divisor.degree();
let d_lead = divisor.lead();
let mut q = vec![zero(); self.degree().saturating_sub(d_deg) + 1];
while !r.is_zero() && r.degree() >= d_deg {
let shift = r.degree() - d_deg;
let factor = r.lead().div(&d_lead);
q[shift] = factor.clone();
let mut sub = vec![zero(); shift + divisor.coeffs.len()];
for (i, c) in divisor.coeffs.iter().enumerate() {
sub[i + shift] = c.mul(&factor);
}
r = r.sub(&UPoly::from_coeffs(sub));
}
(UPoly::from_coeffs(q), r)
}
pub fn div_exact(&self, divisor: &UPoly) -> UPoly {
self.div_rem(divisor).0
}
pub fn gcd(&self, other: &UPoly) -> UPoly {
let mut a = self.clone();
let mut b = other.clone();
while !b.is_zero() {
let r = a.rem(&b);
a = b;
b = r;
}
a.monic()
}
pub fn monic(&self) -> UPoly {
if self.is_zero() {
return self.clone();
}
let lead = self.lead();
self.scale(&lead.recip())
}
pub fn squarefree(&self) -> UPoly {
if self.degree() == 0 {
return self.clone();
}
let g = self.gcd(&self.deriv());
if g.degree() == 0 {
return self.monic();
}
self.div_exact(&g)
}
pub fn root_bound(&self) -> Rational {
if self.degree() == 0 {
return one();
}
let lead = self.lead().abs();
let mut m = zero();
for c in &self.coeffs[..self.coeffs.len() - 1] {
let ratio = c.abs().div(&lead);
if ratio > m {
m = ratio;
}
}
m.add(&one())
}
}
pub fn sturm_chain(p: &UPoly) -> Vec<UPoly> {
let mut chain = vec![p.clone(), p.deriv()];
while !chain.last().unwrap().is_zero() {
let n = chain.len();
let r = chain[n - 2].rem(&chain[n - 1]);
if r.is_zero() {
break;
}
chain.push(r.neg());
}
chain
}
pub fn variations(chain: &[UPoly], x: &Rational) -> usize {
let mut last = 0i32;
let mut count = 0;
for s in chain {
let sg = s.sign_at(x);
if sg == 0 {
continue;
}
if last != 0 && sg != last {
count += 1;
}
last = sg;
}
count
}
pub fn root_count(chain: &[UPoly], a: &Rational, b: &Rational) -> i64 {
variations(chain, a) as i64 - variations(chain, b) as i64
}
pub fn isolate_roots(p: &UPoly) -> Vec<(Rational, Rational)> {
let sf = p.squarefree();
if sf.degree() == 0 {
return Vec::new();
}
let chain = sturm_chain(&sf);
let m = sf.root_bound();
let two = Rational::from_integer(2.into());
let mut out = Vec::new();
let mut stack = vec![(m.neg(), m)];
let mut guard = 0;
while let Some((a, b)) = stack.pop() {
guard += 1;
if guard > 200_000 {
break;
}
let n = root_count(&chain, &a, &b);
if n <= 0 {
continue;
}
if n == 1 {
out.push((a, b));
continue;
}
let mut mid = a.add(&b).div(&two);
let step = b.sub(&a).div(&Rational::from_integer(1024.into()));
let mut tries = 0;
while sf.sign_at(&mid) == 0 && tries < 2048 {
mid = mid.add(&step);
tries += 1;
}
stack.push((a, mid.clone()));
stack.push((mid, b));
}
out.sort_by(|x, y| x.0.cmp(&y.0));
out
}
#[cfg(test)]
mod tests {
use super::*;
fn r(n: i64) -> Rational {
Rational::from_integer(n.into())
}
fn p(cs: &[i64]) -> UPoly {
UPoly::from_coeffs(cs.iter().map(|&c| r(c)).collect())
}
#[test]
fn arithmetic_and_eval() {
let a = p(&[1, 2, 1]); assert_eq!(a.eval(&r(2)), r(9));
assert_eq!(a.degree(), 2);
let b = p(&[-1, 1]); assert_eq!(a.mul(&b), p(&[-1, -1, 1, 1])); assert_eq!(a.deriv(), p(&[2, 2])); }
#[test]
fn gcd_and_squarefree() {
let poly = p(&[2, -3, 0, 1]); let sf = poly.squarefree();
assert_eq!(sf.degree(), 2);
assert_eq!(sf.eval(&r(1)), r(0));
assert_eq!(sf.eval(&r(-2)), r(0));
assert!(!sf.eval(&r(0)).is_zero());
}
#[test]
fn div_rem_exact() {
let a = p(&[-1, 0, 1]); let b = p(&[-1, 1]); let (q, rem) = a.div_rem(&b);
assert_eq!(q, p(&[1, 1])); assert!(rem.is_zero());
}
#[test]
fn isolates_roots() {
let poly = p(&[0, -2, 0, 1]);
let roots = isolate_roots(&poly);
assert_eq!(roots.len(), 3);
assert!(roots.iter().any(|(a, b)| a < &r(0) && b > &r(0)));
assert!(
roots
.iter()
.any(|(a, b)| a < &r(2) && b > &r(1) && a > &r(0))
);
}
#[test]
fn sturm_counts_roots() {
let poly = p(&[-2, 0, 1]);
let chain = sturm_chain(&poly);
assert_eq!(root_count(&chain, &r(-10), &r(10)), 2);
assert_eq!(root_count(&chain, &r(0), &r(10)), 1);
}
}