#![allow(dead_code)]
use crate::polynomial::Polynomial;
#[allow(unused_imports)]
use crate::prelude::*;
use core::cmp::Ordering;
use num_rational::BigRational;
use num_traits::{One, Signed, Zero};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IsolatingInterval {
pub lower: BigRational,
pub upper: BigRational,
}
impl IsolatingInterval {
pub fn new(lower: BigRational, upper: BigRational) -> Result<Self, AlgebraicError> {
if lower > upper {
return Err(AlgebraicError::InvalidInterval);
}
Ok(Self { lower, upper })
}
pub fn contains(&self, value: &BigRational) -> bool {
value >= &self.lower && value <= &self.upper
}
pub fn midpoint(&self) -> BigRational {
(&self.lower + &self.upper) / BigRational::from_integer(2.into())
}
pub fn width(&self) -> BigRational {
&self.upper - &self.lower
}
pub fn is_point(&self) -> bool {
self.lower == self.upper
}
}
#[derive(Debug, Clone)]
pub struct AlgebraicNumber {
pub minimal_poly: Polynomial,
pub interval: IsolatingInterval,
sign: Option<i8>, }
impl AlgebraicNumber {
pub fn new(
minimal_poly: Polynomial,
interval: IsolatingInterval,
) -> Result<Self, AlgebraicError> {
Ok(Self {
minimal_poly,
interval,
sign: None,
})
}
pub fn from_rational(r: BigRational) -> Self {
let var = 0;
let poly = Polynomial::linear(&[(BigRational::one(), var)], -r.clone());
Self {
minimal_poly: poly,
interval: IsolatingInterval {
lower: r.clone(),
upper: r,
},
sign: None,
}
}
pub fn is_rational(&self) -> bool {
self.minimal_poly.degree(0) == 1 || self.interval.is_point()
}
pub fn to_rational(&self) -> Option<BigRational> {
if self.interval.is_point() {
Some(self.interval.lower.clone())
} else if self.minimal_poly.degree(0) == 1 {
Some(self.interval.midpoint())
} else {
None
}
}
pub fn refine(&mut self) -> Result<(), AlgebraicError> {
if self.interval.is_point() {
return Ok(()); }
let mid = self.interval.midpoint();
let var = 0;
let mid_value = self.minimal_poly.eval_horner(var, &mid);
if mid_value.is_zero() {
self.interval = IsolatingInterval {
lower: mid.clone(),
upper: mid,
};
} else {
let lower_value = self.minimal_poly.eval_horner(var, &self.interval.lower);
if lower_value.is_zero() {
self.interval = IsolatingInterval {
lower: self.interval.lower.clone(),
upper: self.interval.lower.clone(),
};
} else if (lower_value.is_positive() && mid_value.is_negative())
|| (lower_value.is_negative() && mid_value.is_positive())
{
self.interval.upper = mid;
} else {
self.interval.lower = mid;
}
}
Ok(())
}
pub fn refine_to_precision(&mut self, epsilon: &BigRational) -> Result<(), AlgebraicError> {
while self.interval.width() > *epsilon && !self.interval.is_point() {
self.refine()?;
}
Ok(())
}
pub fn sign(&mut self) -> Result<i8, AlgebraicError> {
if let Some(s) = self.sign {
return Ok(s);
}
while self.interval.lower.is_negative() && self.interval.upper.is_positive() {
self.refine()?;
}
let sign = if self.interval.upper.is_negative() {
-1
} else if self.interval.lower.is_positive() {
1
} else if self.interval.is_point() && self.interval.lower.is_zero() {
0
} else {
return Err(AlgebraicError::SignUndetermined);
};
self.sign = Some(sign);
Ok(sign)
}
pub fn compare(&mut self, other: &mut AlgebraicNumber) -> Result<Ordering, AlgebraicError> {
if self.interval.upper < other.interval.lower {
return Ok(Ordering::Less);
}
if self.interval.lower > other.interval.upper {
return Ok(Ordering::Greater);
}
let max_iterations = 100;
for _ in 0..max_iterations {
if self.interval.upper < other.interval.lower {
return Ok(Ordering::Less);
}
if self.interval.lower > other.interval.upper {
return Ok(Ordering::Greater);
}
if self.interval == other.interval && self.minimal_poly == other.minimal_poly {
return Ok(Ordering::Equal);
}
self.refine()?;
other.refine()?;
}
Err(AlgebraicError::ComparisonFailed)
}
pub fn neg(&self) -> Self {
let var = 0;
let neg_var_poly = -Polynomial::from_var(var);
let negated_poly = self.minimal_poly.substitute(var, &neg_var_poly);
Self {
minimal_poly: negated_poly,
interval: IsolatingInterval {
lower: -self.interval.upper.clone(),
upper: -self.interval.lower.clone(),
},
sign: self.sign.map(|s| -s),
}
}
pub fn abs(&self) -> Self {
if self.interval.lower.is_negative() && self.interval.upper.is_positive() {
self.clone()
} else if self.interval.upper.is_negative() {
self.neg()
} else {
self.clone()
}
}
}
#[derive(Debug, Clone)]
pub struct AlgebraicConfig {
pub max_refinements: usize,
pub target_precision: BigRational,
pub enable_caching: bool,
}
impl Default for AlgebraicConfig {
fn default() -> Self {
Self {
max_refinements: 100,
target_precision: BigRational::from_integer(1.into())
/ BigRational::from_integer(1000000.into()), enable_caching: true,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct AlgebraicStats {
pub refinements: u64,
pub sign_evaluations: u64,
pub comparisons: u64,
pub cache_hits: u64,
}
pub struct AlgebraicManager {
config: AlgebraicConfig,
stats: AlgebraicStats,
cache: FxHashMap<String, AlgebraicNumber>,
}
impl AlgebraicManager {
pub fn new(config: AlgebraicConfig) -> Self {
Self {
config,
stats: AlgebraicStats::default(),
cache: FxHashMap::default(),
}
}
pub fn default_config() -> Self {
Self::new(AlgebraicConfig::default())
}
pub fn create(
&mut self,
poly: Polynomial,
interval: IsolatingInterval,
) -> Result<AlgebraicNumber, AlgebraicError> {
AlgebraicNumber::new(poly, interval)
}
pub fn from_rational(&mut self, r: BigRational) -> AlgebraicNumber {
AlgebraicNumber::from_rational(r)
}
pub fn refine_to_precision(&mut self, alg: &mut AlgebraicNumber) -> Result<(), AlgebraicError> {
alg.refine_to_precision(&self.config.target_precision)?;
self.stats.refinements += 1;
Ok(())
}
pub fn stats(&self) -> &AlgebraicStats {
&self.stats
}
pub fn reset_stats(&mut self) {
self.stats = AlgebraicStats::default();
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AlgebraicError {
InvalidInterval,
NoRootInInterval,
MultipleRootsInInterval,
SignUndetermined,
ComparisonFailed,
ArithmeticFailed,
}
impl core::fmt::Display for AlgebraicError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
AlgebraicError::InvalidInterval => write!(f, "invalid interval"),
AlgebraicError::NoRootInInterval => write!(f, "no root in interval"),
AlgebraicError::MultipleRootsInInterval => write!(f, "multiple roots in interval"),
AlgebraicError::SignUndetermined => write!(f, "sign undetermined"),
AlgebraicError::ComparisonFailed => write!(f, "comparison failed"),
AlgebraicError::ArithmeticFailed => write!(f, "arithmetic operation failed"),
}
}
}
impl core::error::Error for AlgebraicError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_isolating_interval() {
let interval = IsolatingInterval::new(
BigRational::from_integer(0.into()),
BigRational::from_integer(2.into()),
)
.expect("valid interval");
assert_eq!(interval.midpoint(), BigRational::from_integer(1.into()));
assert!(!interval.is_point());
}
#[test]
fn test_from_rational() {
let r = BigRational::from_integer(3.into());
let alg = AlgebraicNumber::from_rational(r.clone());
assert!(alg.is_rational());
assert_eq!(alg.to_rational(), Some(r));
}
#[test]
fn test_negation() {
let r = BigRational::from_integer(5.into());
let alg = AlgebraicNumber::from_rational(r);
let neg_alg = alg.neg();
assert_eq!(
neg_alg.to_rational(),
Some(BigRational::from_integer((-5).into()))
);
}
#[test]
fn test_manager() {
let manager = AlgebraicManager::default_config();
assert_eq!(manager.stats().refinements, 0);
}
}