use crate::polynomial::root_counting::Polynomial;
#[allow(unused_imports)]
use crate::prelude::*;
use core::cmp::Ordering;
use core::fmt;
use num_bigint::BigInt;
use num_rational::BigRational;
use num_traits::{Signed, Zero};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AlgebraicNumberError {
NonIsolatingInterval,
NoRootInInterval,
ZeroPolynomial,
InvalidOperation(String),
}
impl fmt::Display for AlgebraicNumberError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NonIsolatingInterval => write!(f, "Interval doesn't isolate a single root"),
Self::NoRootInInterval => write!(f, "No root in interval"),
Self::ZeroPolynomial => write!(f, "Polynomial is zero"),
Self::InvalidOperation(msg) => write!(f, "Invalid operation: {}", msg),
}
}
}
impl core::error::Error for AlgebraicNumberError {}
#[derive(Debug, Clone)]
pub struct AlgebraicNumber {
pub minimal_poly: Polynomial,
pub lower: BigRational,
pub upper: BigRational,
refinement_level: usize,
}
impl AlgebraicNumber {
pub fn new(
poly: Polynomial,
lower: BigRational,
upper: BigRational,
) -> Result<Self, AlgebraicNumberError> {
if poly.degree() == 0 && poly.coeffs.first().is_none_or(Zero::is_zero) {
return Err(AlgebraicNumberError::ZeroPolynomial);
}
if lower > upper {
return Err(AlgebraicNumberError::NonIsolatingInterval);
}
if lower == upper {
if poly.eval(&lower).is_zero() {
return Ok(Self {
minimal_poly: poly,
lower,
upper,
refinement_level: 0,
});
}
return Err(AlgebraicNumberError::NoRootInInterval);
}
let root_count = sturm_root_count(&poly, &lower, &upper);
if root_count != 1 {
return Err(AlgebraicNumberError::NonIsolatingInterval);
}
Ok(Self {
minimal_poly: poly,
lower,
upper,
refinement_level: 0,
})
}
pub fn from_rational(r: BigRational) -> Self {
let poly = Polynomial::new(vec![-r.clone(), BigRational::from(BigInt::from(1))]);
Self {
minimal_poly: poly,
lower: r.clone(),
upper: r,
refinement_level: 0,
}
}
pub fn is_rational(&self) -> bool {
self.minimal_poly.degree() == 1 || self.lower == self.upper
}
pub fn to_rational(&self) -> Option<BigRational> {
if self.is_rational() {
Some(self.lower.clone())
} else {
None
}
}
pub fn refine(&mut self) {
let mid = (&self.lower + &self.upper) / BigRational::from(BigInt::from(2));
let mid_value = self.minimal_poly.eval(&mid);
if mid_value.is_zero() {
self.lower = mid.clone();
self.upper = mid;
} else {
let lower_value = self.minimal_poly.eval(&self.lower);
if lower_value.signum() != mid_value.signum() {
self.upper = mid;
} else {
self.lower = mid;
}
}
self.refinement_level += 1;
}
pub fn refine_to_precision(&mut self, precision: &BigRational) {
while &self.upper - &self.lower > *precision {
self.refine();
}
}
pub fn interval_width(&self) -> BigRational {
&self.upper - &self.lower
}
pub fn midpoint(&self) -> BigRational {
(&self.lower + &self.upper) / BigRational::from(BigInt::from(2))
}
pub fn compare(&mut self, other: &mut AlgebraicNumber) -> Ordering {
loop {
if self.upper < other.lower {
return Ordering::Less;
}
if self.lower > other.upper {
return Ordering::Greater;
}
if self.minimal_poly == other.minimal_poly
&& self.lower == other.lower
&& self.upper == other.upper
{
return Ordering::Equal;
}
self.refine();
other.refine();
if self.refinement_level > 1000 {
return Ordering::Equal;
}
}
}
pub fn add(&self, other: &AlgebraicNumber) -> Result<AlgebraicNumber, AlgebraicNumberError> {
if self.is_rational() && other.is_rational() {
return Ok(AlgebraicNumber::from_rational(&self.lower + &other.lower));
}
Err(AlgebraicNumberError::InvalidOperation(
"General algebraic addition requires resultant computation (not yet implemented)"
.to_string(),
))
}
pub fn mul(&self, other: &AlgebraicNumber) -> Result<AlgebraicNumber, AlgebraicNumberError> {
if self.is_rational() && other.is_rational() {
return Ok(AlgebraicNumber::from_rational(&self.lower * &other.lower));
}
Err(AlgebraicNumberError::InvalidOperation(
"General algebraic multiplication requires resultant computation (not yet implemented)"
.to_string(),
))
}
pub fn negate(&self) -> AlgebraicNumber {
let neg_poly = poly_substitute_neg_x(&self.minimal_poly);
AlgebraicNumber {
minimal_poly: neg_poly,
lower: -&self.upper,
upper: -&self.lower,
refinement_level: self.refinement_level,
}
}
pub fn signum(&self) -> i32 {
if self.upper < BigRational::zero() {
-1
} else if self.lower > BigRational::zero() {
1
} else if self.lower == self.upper && self.lower.is_zero() {
0
} else {
let mut copy = self.clone();
copy.refine();
copy.signum()
}
}
}
fn poly_substitute_neg_x(poly: &Polynomial) -> Polynomial {
let coeffs: Vec<BigRational> = poly
.coeffs
.iter()
.enumerate()
.map(|(i, c)| if i % 2 == 0 { c.clone() } else { -c })
.collect();
Polynomial::new(coeffs)
}
fn sturm_root_count(poly: &Polynomial, lower: &BigRational, upper: &BigRational) -> usize {
let seq = build_sturm_sequence(poly);
let v_lower = sign_variations(&seq, lower);
let v_upper = sign_variations(&seq, upper);
(v_lower as isize - v_upper as isize).unsigned_abs()
}
fn build_sturm_sequence(poly: &Polynomial) -> Vec<Polynomial> {
let mut seq = vec![poly.clone(), poly.derivative()];
loop {
let n = seq.len();
let last = &seq[n - 1];
if last.degree() == 0 {
break;
}
let remainder = seq[n - 2].remainder(last);
let negated = Polynomial::new(remainder.coeffs.iter().map(|c| -c).collect());
if negated.degree() == 0 && negated.coeffs.first().is_none_or(Zero::is_zero) {
break;
}
seq.push(negated);
if seq.len() > 1000 {
break;
}
}
seq
}
fn sign_variations(seq: &[Polynomial], point: &BigRational) -> usize {
let signs: Vec<i32> = seq
.iter()
.map(|p| {
let val = p.eval(point);
if val.is_positive() {
1
} else if val.is_negative() {
-1
} else {
0
}
})
.filter(|&s| s != 0)
.collect();
signs.windows(2).filter(|w| w[0] != w[1]).count()
}
impl PartialEq for AlgebraicNumber {
fn eq(&self, other: &Self) -> bool {
let mut a = self.clone();
let mut b = other.clone();
matches!(a.compare(&mut b), Ordering::Equal)
}
}
impl Eq for AlgebraicNumber {}
impl PartialOrd for AlgebraicNumber {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for AlgebraicNumber {
fn cmp(&self, other: &Self) -> Ordering {
let mut a = self.clone();
let mut b = other.clone();
a.compare(&mut b)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn rat(n: i64) -> BigRational {
BigRational::from(BigInt::from(n))
}
#[test]
fn test_from_rational() {
let alg = AlgebraicNumber::from_rational(rat(42));
assert!(alg.is_rational());
assert_eq!(alg.to_rational(), Some(rat(42)));
}
#[test]
fn test_rational_arithmetic() {
let a = AlgebraicNumber::from_rational(rat(3));
let b = AlgebraicNumber::from_rational(rat(5));
let sum = a.add(&b).expect("test operation should succeed");
assert!(sum.is_rational());
assert_eq!(sum.to_rational(), Some(rat(8)));
let prod = a.mul(&b).expect("test operation should succeed");
assert!(prod.is_rational());
assert_eq!(prod.to_rational(), Some(rat(15)));
}
#[test]
fn test_negate() {
let a = AlgebraicNumber::from_rational(rat(5));
let neg_a = a.negate();
assert!(neg_a.is_rational());
assert_eq!(neg_a.to_rational(), Some(rat(-5)));
}
#[test]
fn test_compare() {
let a = AlgebraicNumber::from_rational(rat(3));
let b = AlgebraicNumber::from_rational(rat(5));
assert!(a < b);
assert!(b > a);
assert_eq!(a, a.clone());
}
#[test]
fn test_signum() {
let pos = AlgebraicNumber::from_rational(rat(5));
let neg = AlgebraicNumber::from_rational(rat(-3));
let zero = AlgebraicNumber::from_rational(rat(0));
assert_eq!(pos.signum(), 1);
assert_eq!(neg.signum(), -1);
assert_eq!(zero.signum(), 0);
}
#[test]
fn test_refine() {
let poly = Polynomial::new(vec![rat(-2), rat(0), rat(1)]);
let mut alg =
AlgebraicNumber::new(poly, rat(1), rat(2)).expect("test operation should succeed");
let initial_width = alg.interval_width();
alg.refine();
let refined_width = alg.interval_width();
assert!(refined_width < initial_width);
}
#[test]
fn test_interval_width() {
let alg = AlgebraicNumber::from_rational(rat(5));
assert_eq!(alg.interval_width(), rat(0));
}
#[test]
fn test_new_rejects_non_isolating_interval() {
let poly = Polynomial::new(vec![rat(-1), rat(0), rat(1)]);
let result = AlgebraicNumber::new(poly, rat(-2), rat(2));
assert!(result.is_err());
}
#[test]
fn test_new_accepts_isolating_interval() {
let poly = Polynomial::new(vec![rat(-2), rat(0), rat(1)]);
let result = AlgebraicNumber::new(poly, rat(1), rat(2));
assert!(result.is_ok());
}
#[test]
fn test_sturm_root_count() {
let poly = Polynomial::new(vec![rat(-2), rat(0), rat(1)]);
let count = sturm_root_count(&poly, &rat(1), &rat(2));
assert_eq!(count, 1);
}
#[test]
fn test_poly_substitute_neg_x() {
let poly = Polynomial::new(vec![rat(-2), rat(0), rat(1)]);
let neg = poly_substitute_neg_x(&poly);
assert_eq!(neg.eval(&rat(2)), poly.eval(&rat(2)));
let poly2 = Polynomial::new(vec![rat(0), rat(0), rat(0), rat(1)]);
let neg2 = poly_substitute_neg_x(&poly2);
assert_eq!(neg2.eval(&rat(2)), -poly2.eval(&rat(2)));
}
}