use alloc::vec::Vec;
use core::cmp::Ordering;
use core::fmt;
use crate::int::Int;
use crate::matrix::Matrix;
use crate::poly::Poly;
use crate::rational::Rational;
type Q = Rational;
type P = Poly<Rational>;
fn q_i64(v: i64) -> Q {
Rational::from_integer(Int::from_i64(v))
}
use crate::poly::sturm_count as count_roots;
fn squarefree(p: &P) -> P {
p.squarefree_part()
}
fn eval_sign(p: &P, x: &Q) -> i32 {
p.eval(x).signum()
}
fn sturm_chain(p: &P) -> Vec<P> {
p.sturm_chain()
}
fn companion(p: &P) -> Matrix<Q> {
let p = p.monic();
let m = p.degree().expect("companion: non-constant polynomial");
let mut mat = Matrix::zeros(m, m);
for i in 0..m {
mat.set(i, m - 1, p.coeff(i).neg()); }
for i in 1..m {
mat.set(i, i - 1, Rational::ONE); }
mat
}
fn kron(a: &Matrix<Q>, b: &Matrix<Q>) -> Matrix<Q> {
let (ar, ac) = (a.rows(), a.cols());
let (br, bc) = (b.rows(), b.cols());
let mut out = Matrix::zeros(ar * br, ac * bc);
for i in 0..ar {
for j in 0..ac {
let aij = a.get(i, j).clone();
for k in 0..br {
for l in 0..bc {
out.set(i * br + k, j * bc + l, aij.mul(b.get(k, l)));
}
}
}
}
out
}
fn trace(m: &Matrix<Q>) -> Q {
let mut t = Rational::ZERO;
for i in 0..m.rows() {
t = t.add(m.get(i, i));
}
t
}
fn charpoly(m: &Matrix<Q>) -> P {
let n = m.rows();
let mut coeffs = alloc::vec![Rational::ZERO; n + 1];
coeffs[n] = Rational::ONE;
let mut mk = Matrix::<Q>::identity(n); for k in 1..=n {
let amk = m.mul(&mk);
let ck = trace(&amk).div(&q_i64(k as i64)).neg(); coeffs[n - k] = ck.clone();
if k < n {
let ident = Matrix::<Q>::identity(n).scalar_mul(&ck);
mk = amk.add(&ident); }
}
Poly::new(coeffs)
}
#[derive(Clone)]
pub struct Algebraic {
poly: P, lo: Q,
hi: Q,
}
impl Algebraic {
pub fn from_rational(r: Rational) -> Algebraic {
let poly = Poly::new(alloc::vec![r.neg(), Rational::ONE]);
Algebraic {
poly,
lo: r.clone(),
hi: r,
}
}
#[inline]
pub fn from_int(n: Int) -> Algebraic {
Algebraic::from_rational(Rational::from_integer(n))
}
pub fn real_roots_of(poly: &Poly<Rational>) -> Vec<Algebraic> {
let sf = squarefree(poly);
poly.isolate_real_roots()
.into_iter()
.map(|(lo, hi)| Algebraic::new(sf.clone(), lo, hi))
.collect()
}
#[cfg(feature = "lattice")]
pub fn from_float(alpha: &crate::float::Float, max_degree: usize) -> Option<Algebraic> {
use crate::float::RoundingMode::Nearest;
let prec = alpha.precision();
let coeffs = crate::lattice::minimal_polynomial(alpha, max_degree, prec * 7 / 10)?;
if coeffs.len() == 2 {
return Some(Algebraic::from_rational(Rational::new(
coeffs[0].neg(),
coeffs[1].clone(),
)));
}
let poly = Poly::new(
coeffs
.iter()
.map(|c| Rational::from_integer(c.clone()))
.collect(),
);
let mut best: Option<(Algebraic, crate::float::Float)> = None;
for root in Algebraic::real_roots_of(&poly) {
let dist = root.to_float(prec, Nearest).sub(alpha, prec, Nearest).abs();
if best.as_ref().is_none_or(|(_, d)| dist < *d) {
best = Some((root, dist));
}
}
best.map(|(root, _)| root)
}
pub fn new(poly: P, lo: Q, hi: Q) -> Algebraic {
let poly = squarefree(&poly);
Algebraic { poly, lo, hi }.normalized()
}
fn normalized(self) -> Algebraic {
if self.lo == self.hi {
return self;
}
if eval_sign(&self.poly, &self.hi) == 0 {
let r = self.hi.clone();
return Algebraic::from_rational(r);
}
self.collapse_if_rational()
}
fn collapse_if_rational(self) -> Algebraic {
if self.lo == self.hi {
return self;
}
let deg = match self.poly.degree() {
Some(d) if d >= 1 => d,
_ => return self,
};
let mut lcm = Int::ONE;
for i in 0..=deg {
let d = self.poly.coeff(i).denominator().clone();
let g = lcm.gcd(&d);
lcm = lcm.div_trunc(&g).mul(&d);
}
let lead = &self.poly.coeff(deg);
let l_int = lead.numerator().mul(&lcm).div_trunc(lead.denominator());
let l_abs = l_int.abs();
if l_abs > Int::from_i64(1 << 20) {
return self;
}
let width = Rational::new(Int::ONE, l_abs.mul(&Int::from_i64(2)));
let mut a = self.clone();
a.refine_below(&width);
if a.lo == a.hi {
return Algebraic::from_rational(a.hi);
}
for q in l_abs.divisors() {
let qr = Rational::from_integer(q.clone());
let p = Rational::mul(&a.hi, &qr).floor(); let cand = Rational::new(p, q);
if cand > a.lo && cand <= a.hi && eval_sign(&self.poly, &cand) == 0 {
return Algebraic::from_rational(cand);
}
}
self
}
#[inline]
pub fn defining_polynomial(&self) -> &Poly<Rational> {
&self.poly
}
#[inline]
pub fn interval(&self) -> (&Rational, &Rational) {
(&self.lo, &self.hi)
}
#[inline]
pub fn is_rational(&self) -> bool {
self.lo == self.hi
}
pub fn refine(&mut self) {
if self.lo == self.hi {
return;
}
let mid = self.lo.add(&self.hi).div(&q_i64(2));
let sm = eval_sign(&self.poly, &mid);
if sm == 0 {
self.lo = mid.clone();
self.hi = mid;
return;
}
if sm == eval_sign(&self.poly, &self.hi) {
self.hi = mid;
} else {
self.lo = mid;
}
}
pub fn refine_below(&mut self, width: &Rational) {
while self.lo != self.hi && self.hi.sub(&self.lo) > *width {
self.refine();
}
}
pub fn signum(&self) -> i32 {
let zero = Rational::ZERO;
if eval_sign(&self.poly, &zero) == 0 && self.lo < zero && zero <= self.hi {
return 0;
}
let mut a = self.clone();
loop {
if a.is_rational() {
return a.lo.signum();
}
if a.lo.is_positive() {
return 1;
}
if a.hi.is_negative() {
return -1;
}
a.refine();
}
}
pub fn to_float(
&self,
precision: u64,
mode: crate::float::RoundingMode,
) -> crate::float::Float {
use crate::float::Float;
let mut a = self.clone();
for _ in 0..(precision + 64) {
let flo = Float::from_rational(&a.lo, precision, mode);
let fhi = Float::from_rational(&a.hi, precision, mode);
if flo == fhi {
return flo;
}
a.refine();
}
Float::from_rational(&a.lo.add(&a.hi).div(&q_i64(2)), precision, mode)
}
pub fn to_f64(&self) -> f64 {
self.to_float(53, crate::float::RoundingMode::Nearest)
.to_f64()
}
pub fn neg(&self) -> Algebraic {
let neg_poly = compose_neg(&self.poly);
Algebraic {
poly: squarefree(&neg_poly),
lo: self.hi.neg(),
hi: self.lo.neg(),
}
.normalized()
}
pub fn add(&self, rhs: &Algebraic) -> Algebraic {
if self.is_rational() {
return rhs.shift(&self.lo);
}
if rhs.is_rational() {
return self.shift(&rhs.lo);
}
let r = charpoly(&kron_sum(&companion(&self.poly), &companion(&rhs.poly)));
self.combine(rhs, &r, |a, b| (a.lo.add(&b.lo), a.hi.add(&b.hi)))
}
pub fn sub(&self, rhs: &Algebraic) -> Algebraic {
self.add(&rhs.neg())
}
pub fn mul(&self, rhs: &Algebraic) -> Algebraic {
if self.is_rational() {
return rhs.scale(&self.lo);
}
if rhs.is_rational() {
return self.scale(&rhs.lo);
}
let r = charpoly(&kron(&companion(&self.poly), &companion(&rhs.poly)));
self.combine(rhs, &r, |a, b| interval_mul(&a.lo, &a.hi, &b.lo, &b.hi))
}
pub fn recip(&self) -> Algebraic {
assert!(self.signum() != 0, "Algebraic::recip: reciprocal of zero");
if self.is_rational() {
return Algebraic::from_rational(self.lo.recip());
}
let mut a = self.clone();
while a.lo.signum() != a.hi.signum() || a.lo.is_zero() || a.hi.is_zero() {
a.refine();
}
let rev = reverse_poly(&a.poly);
let (nlo, nhi) = (a.hi.recip(), a.lo.recip());
Algebraic::new(rev, nlo, nhi)
}
pub fn div(&self, rhs: &Algebraic) -> Algebraic {
self.mul(&rhs.recip())
}
fn shift(&self, c: &Q) -> Algebraic {
if self.is_rational() {
return Algebraic::from_rational(self.lo.add(c));
}
Algebraic {
poly: squarefree(&compose_shift(&self.poly, c)),
lo: self.lo.add(c),
hi: self.hi.add(c),
}
.normalized()
}
fn scale(&self, c: &Q) -> Algebraic {
if c.is_zero() {
return Algebraic::from_rational(Rational::ZERO);
}
if self.is_rational() {
return Algebraic::from_rational(self.lo.mul(c));
}
let (lo, hi) = if c.is_positive() {
(self.lo.mul(c), self.hi.mul(c))
} else {
(self.hi.mul(c), self.lo.mul(c))
};
Algebraic {
poly: squarefree(&compose_scale(&self.poly, c)),
lo,
hi,
}
.normalized()
}
fn combine(
&self,
rhs: &Algebraic,
poly: &P,
interval_fn: impl Fn(&Algebraic, &Algebraic) -> (Q, Q),
) -> Algebraic {
let sf = squarefree(poly);
let chain = sturm_chain(&sf);
let mut a = self.clone();
let mut b = rhs.clone();
for _ in 0..4096 {
let (lo, hi) = interval_fn(&a, &b);
if lo < hi && count_roots(&chain, &lo, &hi) == 1 {
return Algebraic::new(sf, lo, hi);
}
a.refine();
b.refine();
}
panic!("Algebraic::combine: failed to isolate the result root");
}
}
fn interval_mul(a: &Q, b: &Q, c: &Q, d: &Q) -> (Q, Q) {
let ps = [a.mul(c), a.mul(d), b.mul(c), b.mul(d)];
let mut lo = ps[0].clone();
let mut hi = ps[0].clone();
for p in &ps[1..] {
if *p < lo {
lo = p.clone();
}
if *p > hi {
hi = p.clone();
}
}
(lo, hi)
}
fn compose_neg(p: &P) -> P {
let mut c = p.coeffs().to_vec();
for (i, coeff) in c.iter_mut().enumerate() {
if i % 2 == 1 {
*coeff = coeff.neg();
}
}
Poly::new(c)
}
fn compose_shift(p: &P, c: &Q) -> P {
let xmc = Poly::new(alloc::vec![c.neg(), Rational::ONE]); let mut acc = Poly::zero();
for coeff in p.coeffs().iter().rev() {
acc = acc.mul(&xmc).add(&Poly::constant(coeff.clone()));
}
acc
}
fn compose_scale(p: &P, c: &Q) -> P {
let n = p.degree().unwrap_or(0);
let mut out = Vec::with_capacity(n + 1);
let mut cpow = Rational::ONE; let cn = pow_q(c, n);
for i in 0..=n {
let factor = cn.div(&cpow); out.push(p.coeff(i).mul(&factor));
cpow = cpow.mul(c);
}
Poly::new(out)
}
fn reverse_poly(p: &P) -> P {
let mut c = p.coeffs().to_vec();
c.reverse();
Poly::new(c)
}
fn pow_q(c: &Q, n: usize) -> Q {
let mut acc = Rational::ONE;
for _ in 0..n {
acc = acc.mul(c);
}
acc
}
fn kron_sum(a: &Matrix<Q>, b: &Matrix<Q>) -> Matrix<Q> {
let m = a.rows();
let n = b.rows();
let left = kron(a, &Matrix::<Q>::identity(n));
let right = kron(&Matrix::<Q>::identity(m), b);
left.add(&right)
}
impl PartialEq for Algebraic {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl Eq for Algebraic {}
impl PartialOrd for Algebraic {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Algebraic {
fn cmp(&self, other: &Self) -> Ordering {
let mut a = self.clone();
let mut b = other.clone();
let g = squarefree(&a.poly.gcd(&b.poly));
let has_common = g.degree().unwrap_or(0) >= 1;
let g_chain = has_common.then(|| sturm_chain(&g));
for _ in 0..4096 {
if a.hi < b.lo {
return Ordering::Less;
}
if b.hi < a.lo {
return Ordering::Greater;
}
if let Some(chain) = &g_chain {
let ov_lo = if a.lo > b.lo {
a.lo.clone()
} else {
b.lo.clone()
};
let ov_hi = if a.hi < b.hi {
a.hi.clone()
} else {
b.hi.clone()
};
if ov_lo < ov_hi && count_roots(chain, &ov_lo, &ov_hi) >= 1 {
return Ordering::Equal;
}
}
a.refine();
b.refine();
}
Ordering::Equal
}
}
impl From<Rational> for Algebraic {
#[inline]
fn from(r: Rational) -> Algebraic {
Algebraic::from_rational(r)
}
}
impl From<Int> for Algebraic {
#[inline]
fn from(n: Int) -> Algebraic {
Algebraic::from_int(n)
}
}
impl fmt::Display for Algebraic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_rational() {
return fmt::Display::fmt(&self.lo, f);
}
write!(f, "root of {} in ({}, {})", self.poly, self.lo, self.hi)
}
}
impl fmt::Debug for Algebraic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Algebraic({self})")
}
}
macro_rules! alg_binop {
($tr:ident, $m:ident, $atr:ident, $am:ident) => {
impl core::ops::$tr for Algebraic {
type Output = Algebraic;
#[inline]
fn $m(self, rhs: Algebraic) -> Algebraic {
Algebraic::$m(&self, &rhs)
}
}
impl core::ops::$tr<&Algebraic> for &Algebraic {
type Output = Algebraic;
#[inline]
fn $m(self, rhs: &Algebraic) -> Algebraic {
Algebraic::$m(self, rhs)
}
}
impl core::ops::$atr for Algebraic {
#[inline]
fn $am(&mut self, rhs: Algebraic) {
*self = Algebraic::$m(self, &rhs);
}
}
};
}
alg_binop!(Add, add, AddAssign, add_assign);
alg_binop!(Sub, sub, SubAssign, sub_assign);
alg_binop!(Mul, mul, MulAssign, mul_assign);
alg_binop!(Div, div, DivAssign, div_assign);
impl core::ops::Neg for Algebraic {
type Output = Algebraic;
#[inline]
fn neg(self) -> Algebraic {
Algebraic::neg(&self)
}
}
impl Algebraic {
pub fn sqrt(&self) -> Algebraic {
assert!(self.signum() >= 0, "Algebraic::sqrt: negative radicand");
if self.signum() == 0 {
return Algebraic::from_rational(Rational::ZERO);
}
if self.is_rational()
&& let (Some(sn), Some(sd)) = (
self.lo.numerator().sqrt_exact(),
self.lo.denominator().sqrt_exact(),
)
{
return Algebraic::from_rational(Rational::new(sn, sd));
}
let sub = compose_square(&self.poly);
let sf = squarefree(&sub);
let chain = sturm_chain(&sf);
let mut a = self.clone();
for _ in 0..4096 {
let lo = rational_sqrt_floor(&a.lo);
let hi = rational_sqrt_ceil(&a.hi);
if lo < hi && count_roots(&chain, &lo, &hi) == 1 {
return Algebraic::new(sf, lo, hi);
}
a.refine();
}
panic!("Algebraic::sqrt: failed to isolate the root");
}
}
fn compose_square(p: &P) -> P {
let n = p.degree().unwrap_or(0);
let mut out = alloc::vec![Rational::ZERO; 2 * n + 1];
for i in 0..=n {
out[2 * i] = p.coeff(i);
}
Poly::new(out)
}
fn rational_sqrt_floor(q: &Q) -> Q {
let (lo, _) = rational_sqrt_bracket(q);
lo
}
fn rational_sqrt_ceil(q: &Q) -> Q {
let (_, hi) = rational_sqrt_bracket(q);
hi
}
fn rational_sqrt_bracket(q: &Q) -> (Q, Q) {
if q.signum() <= 0 {
return (Rational::ZERO, Rational::ZERO);
}
let mut lo = Rational::ZERO;
let mut hi = if *q > Rational::ONE {
q.clone()
} else {
Rational::ONE
};
let eps = Rational::new(Int::ONE, Int::ONE.mul_2k(32));
while hi.sub(&lo) > eps {
let mid = lo.add(&hi).div(&q_i64(2));
if mid.mul(&mid) <= *q {
lo = mid;
} else {
hi = mid;
}
}
(lo, hi)
}