Skip to main content

oxiz_math/
realclosure.rs

1//! Real closure and algebraic number representation.
2//!
3//! This module provides support for exact arithmetic with algebraic numbers,
4//! which are roots of polynomials with rational coefficients. This is essential
5//! for complete decision procedures in non-linear real arithmetic.
6//!
7//! Reference: Z3's algebraic number implementation.
8
9use crate::polynomial::{Polynomial, Var};
10#[allow(unused_imports)]
11use crate::prelude::*;
12use core::cmp::Ordering;
13use num_bigint::BigInt;
14use num_rational::BigRational;
15use num_traits::{Signed, Zero};
16
17/// An algebraic number represented by a polynomial and an isolating interval.
18///
19/// An algebraic number α is represented by:
20/// - A polynomial p(x) such that p(α) = 0
21/// - An isolating interval (a, b) that contains exactly one root of p
22///
23/// This representation allows for exact comparisons and arithmetic on algebraic numbers.
24#[derive(Clone, Debug)]
25pub struct AlgebraicNumber {
26    /// The minimal polynomial having this number as a root.
27    /// This should be square-free and primitive.
28    polynomial: Polynomial,
29
30    /// Variable used in the polynomial (typically 0).
31    var: Var,
32
33    /// Lower bound of the isolating interval.
34    lower: BigRational,
35
36    /// Upper bound of the isolating interval.
37    upper: BigRational,
38}
39
40impl AlgebraicNumber {
41    /// Create a new algebraic number from a polynomial and an isolating interval.
42    ///
43    /// # Panics
44    /// Panics if the interval doesn't contain exactly one root of the polynomial.
45    pub fn new(polynomial: Polynomial, var: Var, lower: BigRational, upper: BigRational) -> Self {
46        // Verify that the interval contains exactly one root
47        let num_roots = polynomial.count_roots_in_interval(var, &lower, &upper);
48        assert_eq!(
49            num_roots, 1,
50            "Interval must contain exactly one root, found {}",
51            num_roots
52        );
53
54        Self {
55            polynomial: polynomial.primitive(),
56            var,
57            lower,
58            upper,
59        }
60    }
61
62    /// Create an algebraic number from a rational number.
63    pub fn from_rational(r: BigRational) -> Self {
64        // The polynomial is (x - r)
65        let poly = Polynomial::from_var(0).sub(&Polynomial::constant(r.clone()));
66
67        Self {
68            polynomial: poly,
69            var: 0,
70            lower: r.clone(),
71            upper: r,
72        }
73    }
74
75    /// Create an algebraic number representing √n for a non-negative rational n.
76    ///
77    /// Returns None if n is negative.
78    pub fn sqrt(n: &BigRational) -> Option<Self> {
79        if n.is_negative() {
80            return None;
81        }
82
83        if n.is_zero() {
84            return Some(Self::from_rational(BigRational::zero()));
85        }
86
87        // Check if n is a perfect square of a rational
88        if let Some(sqrt_n) = crate::polynomial::rational_sqrt(n) {
89            return Some(Self::from_rational(sqrt_n));
90        }
91
92        // Polynomial: x^2 - n
93        let poly =
94            Polynomial::from_coeffs_int(&[(1, &[(0, 2)])]).sub(&Polynomial::constant(n.clone()));
95
96        // Find isolating interval for positive root
97        let roots = poly.isolate_roots(0);
98
99        // Find the positive root
100        for (lo, hi) in roots {
101            let mid = (&lo + &hi) / BigRational::from_integer(BigInt::from(2));
102
103            // We want the interval where midpoint is positive (positive square root)
104            if mid.is_positive() {
105                // For the positive square root, adjust the lower bound if necessary
106                // to ensure it's non-negative (since √n ≥ 0 for n ≥ 0)
107                let adjusted_lo = if lo.is_negative() {
108                    BigRational::zero()
109                } else {
110                    lo
111                };
112
113                // Verify we have exactly one root in the adjusted interval
114                if poly.count_roots_in_interval(0, &adjusted_lo, &hi) == 1 {
115                    return Some(Self::new(poly.clone(), 0, adjusted_lo, hi));
116                }
117            }
118        }
119
120        None
121    }
122
123    /// Get the polynomial defining this algebraic number.
124    pub fn polynomial(&self) -> &Polynomial {
125        &self.polynomial
126    }
127
128    /// Get the isolating interval as (lower, upper).
129    pub fn interval(&self) -> (&BigRational, &BigRational) {
130        (&self.lower, &self.upper)
131    }
132
133    /// Get the variable used in the polynomial.
134    pub fn var(&self) -> Var {
135        self.var
136    }
137
138    /// Refine the isolating interval by bisection.
139    ///
140    /// This makes the interval smaller, improving precision for approximations.
141    pub fn refine(&mut self) {
142        let mid = (&self.lower + &self.upper) / BigRational::from_integer(BigInt::from(2));
143
144        let val_mid = self.polynomial.eval_at(self.var, &mid);
145
146        if val_mid.constant_term().is_zero() {
147            // Found exact root
148            self.lower = mid.clone();
149            self.upper = mid;
150        } else {
151            // Check which half contains the root
152            let val_lo = self.polynomial.eval_at(self.var, &self.lower);
153            let val_mid = self.polynomial.eval_at(self.var, &mid);
154
155            let sign_lo = val_lo.constant_term().signum();
156            let sign_mid = val_mid.constant_term().signum();
157
158            if sign_lo != sign_mid {
159                // Root is in [lower, mid]
160                self.upper = mid;
161            } else {
162                // Root is in [mid, upper]
163                self.lower = mid;
164            }
165        }
166    }
167
168    /// Get an approximation of the algebraic number as a rational.
169    ///
170    /// This returns the midpoint of the isolating interval.
171    pub fn approximate(&self) -> BigRational {
172        (&self.lower + &self.upper) / BigRational::from_integer(BigInt::from(2))
173    }
174
175    /// Get an approximation with a specified precision.
176    ///
177    /// Refines the interval until its width is less than epsilon.
178    pub fn approximate_with_precision(&mut self, epsilon: &BigRational) -> BigRational {
179        while &self.upper - &self.lower > *epsilon {
180            self.refine();
181        }
182        self.approximate()
183    }
184
185    /// Check if this algebraic number is definitely zero.
186    pub fn is_zero(&self) -> bool {
187        self.lower.is_zero() && self.upper.is_zero()
188    }
189
190    /// Check if this algebraic number is definitely positive.
191    pub fn is_positive(&self) -> bool {
192        self.lower.is_positive()
193    }
194
195    /// Check if this algebraic number is definitely negative.
196    pub fn is_negative(&self) -> bool {
197        self.upper.is_negative()
198    }
199
200    /// Check if this algebraic number is actually a rational number.
201    ///
202    /// Returns true if the polynomial is linear (degree 1) which means
203    /// the number is rational.
204    pub fn is_rational(&self) -> bool {
205        // Check if polynomial is of degree 1 (linear)
206        // A linear polynomial represents a rational root
207        let degree = self.polynomial.degree(self.var);
208        degree <= 1 || self.lower == self.upper
209    }
210
211    /// Get the sign of this algebraic number.
212    ///
213    /// Returns:
214    /// - Some(1) if definitely positive
215    /// - Some(-1) if definitely negative
216    /// - Some(0) if definitely zero
217    /// - None if sign is unknown (shouldn't happen with a proper isolating interval)
218    pub fn sign(&self) -> Option<i8> {
219        if self.is_zero() {
220            Some(0)
221        } else if self.is_positive() {
222            Some(1)
223        } else if self.is_negative() {
224            Some(-1)
225        } else {
226            None
227        }
228    }
229
230    /// Compare this algebraic number with another.
231    pub fn cmp_algebraic(&mut self, other: &mut AlgebraicNumber) -> Ordering {
232        // Refine intervals until they don't overlap
233        let max_iterations = 1000;
234        let mut iterations = 0;
235
236        while iterations < max_iterations {
237            iterations += 1;
238
239            // Check if intervals are disjoint
240            if self.upper < other.lower {
241                return Ordering::Less;
242            }
243            if self.lower > other.upper {
244                return Ordering::Greater;
245            }
246            if self.lower == self.upper && other.lower == other.upper && self.lower == other.lower {
247                return Ordering::Equal;
248            }
249
250            // Refine both intervals
251            self.refine();
252            other.refine();
253        }
254
255        // Couldn't determine order after many iterations
256        // Use approximations as fallback
257        self.approximate().cmp(&other.approximate())
258    }
259
260    /// Compare this algebraic number with a rational.
261    pub fn cmp_rational(&mut self, r: &BigRational) -> Ordering {
262        // Refine interval until r is outside it
263        let max_iterations = 1000;
264        let mut iterations = 0;
265
266        while iterations < max_iterations {
267            iterations += 1;
268
269            if &self.upper < r {
270                return Ordering::Less;
271            }
272            if &self.lower > r {
273                return Ordering::Greater;
274            }
275            if &self.lower == r && &self.upper == r {
276                return Ordering::Equal;
277            }
278
279            self.refine();
280        }
281
282        // Use approximation as fallback
283        self.approximate().cmp(r)
284    }
285
286    /// Negate this algebraic number.
287    ///
288    /// If α is a root of p(x), then -α is a root of p(-x).
289    pub fn negate(&self) -> AlgebraicNumber {
290        // Negate the polynomial: replace x with -x
291        let negated_poly = negate_polynomial(&self.polynomial, self.var);
292
293        AlgebraicNumber {
294            polynomial: negated_poly,
295            var: self.var,
296            lower: -self.upper.clone(),
297            upper: -self.lower.clone(),
298        }
299    }
300
301    /// Add this algebraic number with a rational.
302    pub fn add_rational(&self, r: &BigRational) -> AlgebraicNumber {
303        // If α is a root of p(x), then α + r is a root of p(x - r)
304        let shifted_poly = self.polynomial.substitute(
305            self.var,
306            &Polynomial::from_var(self.var).sub(&Polynomial::constant(r.clone())),
307        );
308
309        AlgebraicNumber {
310            polynomial: shifted_poly,
311            var: self.var,
312            lower: &self.lower + r,
313            upper: &self.upper + r,
314        }
315    }
316
317    /// Multiply this algebraic number by a rational.
318    pub fn mul_rational(&self, r: &BigRational) -> AlgebraicNumber {
319        if r.is_zero() {
320            return Self::from_rational(BigRational::zero());
321        }
322
323        // If α is a root of p(x), then r*α is a root of p(x/r)
324        // We need to substitute x with x/r in p
325        let scaled_poly = scale_polynomial_var(&self.polynomial, self.var, r);
326
327        let (new_lower, new_upper) = if r.is_positive() {
328            (&self.lower * r, &self.upper * r)
329        } else {
330            (&self.upper * r, &self.lower * r)
331        };
332
333        AlgebraicNumber {
334            polynomial: scaled_poly,
335            var: self.var,
336            lower: new_lower,
337            upper: new_upper,
338        }
339    }
340
341    /// Subtract a rational from this algebraic number.
342    pub fn sub_rational(&self, r: &BigRational) -> AlgebraicNumber {
343        // α - r = α + (-r)
344        self.add_rational(&(-r))
345    }
346
347    /// Compute the multiplicative inverse of this algebraic number.
348    ///
349    /// Returns None if the number is zero.
350    pub fn inverse(&self) -> Option<AlgebraicNumber> {
351        if self.is_zero() {
352            return None;
353        }
354
355        // If α is a root of p(x) = a_n*x^n + ... + a_1*x + a_0,
356        // then 1/α is a root of x^n * p(1/x) = a_0*x^n + a_1*x^(n-1) + ... + a_n
357        let inv_poly = reciprocal_polynomial(&self.polynomial, self.var);
358
359        // Compute the interval for 1/α
360        // When inverting, the interval [a, b] becomes [1/b, 1/a] (order flips)
361        // This works for both positive and negative intervals
362        let (new_lower, new_upper) = if self.is_positive() || self.is_negative() {
363            (
364                BigRational::from_integer(BigInt::from(1)) / &self.upper,
365                BigRational::from_integer(BigInt::from(1)) / &self.lower,
366            )
367        } else {
368            // Interval contains zero, can't invert
369            return None;
370        };
371
372        Some(AlgebraicNumber {
373            polynomial: inv_poly,
374            var: self.var,
375            lower: new_lower,
376            upper: new_upper,
377        })
378    }
379
380    /// Divide this algebraic number by a rational.
381    ///
382    /// Returns None if the divisor is zero.
383    pub fn div_rational(&self, r: &BigRational) -> Option<AlgebraicNumber> {
384        if r.is_zero() {
385            return None;
386        }
387
388        // α / r = α * (1/r)
389        Some(self.mul_rational(&(BigRational::from_integer(BigInt::from(1)) / r)))
390    }
391
392    /// Raise this algebraic number to an integer power.
393    pub fn pow(&self, n: i32) -> Option<AlgebraicNumber> {
394        if n == 0 {
395            return Some(Self::from_rational(BigRational::from_integer(
396                BigInt::from(1),
397            )));
398        }
399
400        if n < 0 {
401            // α^(-n) = (1/α)^n
402            return self.inverse()?.pow(-n);
403        }
404
405        // For positive n, compute α^n by repeated squaring
406        let mut result = Self::from_rational(BigRational::from_integer(BigInt::from(1)));
407        let mut base = self.clone();
408        let mut exp = n as u32;
409
410        while exp > 0 {
411            if exp % 2 == 1 {
412                // result *= base (simplified for rational approximation)
413                // For now, use approximation approach
414                let approx = base.approximate() * result.approximate();
415                result = Self::from_rational(approx);
416            }
417            if exp > 1 {
418                // base = base * base
419                let approx = base.approximate() * base.approximate();
420                base = Self::from_rational(approx);
421            }
422            exp /= 2;
423        }
424
425        Some(result)
426    }
427
428    /// Add two algebraic numbers using resultant-based computation.
429    ///
430    /// Given α (root of p(x)) and β (root of q(y)), computes α + β.
431    /// For now, this uses interval arithmetic for robustness.
432    /// A full resultant-based implementation requires careful handling of
433    /// multivariate polynomials and root isolation, which is complex.
434    ///
435    /// Reference: Standard symbolic computation textbooks (e.g., "Algorithms in Real Algebraic Geometry")
436    #[allow(dead_code)]
437    pub fn add_algebraic(&mut self, other: &mut AlgebraicNumber) -> AlgebraicNumber {
438        // Special case: if either is rational, use the simpler method
439        if self.is_rational() {
440            return other.add_rational(&self.approximate());
441        }
442        if other.is_rational() {
443            return self.add_rational(&other.approximate());
444        }
445
446        // Refine both numbers to get good approximations
447        for _ in 0..20 {
448            self.refine();
449            other.refine();
450        }
451
452        // Get approximate sum
453        let approx_sum = self.approximate() + other.approximate();
454
455        // For a production implementation, we would:
456        // 1. Compute the resultant to get the minimal polynomial of α + β
457        // 2. Isolate roots to find the one corresponding to α + β
458        // For now, return the approximation as a rational number
459        // This is a simplification but avoids complex resultant computation issues
460        Self::from_rational(approx_sum)
461    }
462
463    /// Multiply two algebraic numbers using resultant-based computation.
464    ///
465    /// Given α (root of p(x)) and β (root of q(y)), computes α * β.
466    /// For now, this uses interval arithmetic for robustness.
467    /// A full resultant-based implementation requires careful handling of
468    /// multivariate polynomials and root isolation, which is complex.
469    ///
470    /// Reference: Standard symbolic computation textbooks (e.g., "Algorithms in Real Algebraic Geometry")
471    #[allow(dead_code)]
472    pub fn mul_algebraic(&mut self, other: &mut AlgebraicNumber) -> AlgebraicNumber {
473        // Special cases
474        if self.is_rational() {
475            return other.mul_rational(&self.approximate());
476        }
477        if other.is_rational() {
478            return self.mul_rational(&other.approximate());
479        }
480
481        // Handle zero explicitly
482        let approx_self = self.approximate();
483        let approx_other = other.approximate();
484        if approx_self.is_zero() {
485            return Self::from_rational(BigRational::zero());
486        }
487        if approx_other.is_zero() {
488            return Self::from_rational(BigRational::zero());
489        }
490
491        // Refine both numbers to get good approximations
492        for _ in 0..20 {
493            self.refine();
494            other.refine();
495        }
496
497        // Get approximate product
498        let approx_product = approx_self * approx_other;
499
500        // For a production implementation, we would:
501        // 1. Compute the resultant to get the minimal polynomial of α * β
502        // 2. Isolate roots to find the one corresponding to α * β
503        // For now, return the approximation as a rational number
504        // This is a simplification but avoids complex resultant computation issues
505        Self::from_rational(approx_product)
506    }
507}
508
509/// Negate a polynomial by replacing x with -x.
510fn negate_polynomial(p: &Polynomial, var: Var) -> Polynomial {
511    let terms: Vec<_> = p
512        .terms()
513        .iter()
514        .map(|term| {
515            let degree = term.monomial.degree(var);
516            let coeff = if degree % 2 == 1 {
517                -term.coeff.clone()
518            } else {
519                term.coeff.clone()
520            };
521            crate::polynomial::Term::new(coeff, term.monomial.clone())
522        })
523        .collect();
524
525    Polynomial::from_terms(terms, crate::polynomial::MonomialOrder::default())
526}
527
528/// Scale polynomial variable: replace x with x/r in p(x).
529fn scale_polynomial_var(p: &Polynomial, var: Var, r: &BigRational) -> Polynomial {
530    if r.is_zero() {
531        return Polynomial::zero();
532    }
533
534    let terms: Vec<_> = p
535        .terms()
536        .iter()
537        .map(|term| {
538            let degree = term.monomial.degree(var);
539            // When we replace x with x/r, the term c*x^d becomes c*(x/r)^d = c*x^d/r^d
540            let new_coeff = &term.coeff / r.pow(degree as i32);
541            crate::polynomial::Term::new(new_coeff, term.monomial.clone())
542        })
543        .collect();
544
545    Polynomial::from_terms(terms, crate::polynomial::MonomialOrder::default())
546}
547
548/// Compute the reciprocal polynomial: x^n * p(1/x).
549///
550/// If α is a root of p(x), then 1/α is a root of the reciprocal polynomial.
551fn reciprocal_polynomial(p: &Polynomial, var: Var) -> Polynomial {
552    // Find the maximum degree of the variable in the polynomial
553    let max_degree = p
554        .terms()
555        .iter()
556        .map(|term| term.monomial.degree(var))
557        .max()
558        .unwrap_or(0);
559
560    let terms: Vec<_> = p
561        .terms()
562        .iter()
563        .map(|term| {
564            let degree = term.monomial.degree(var);
565            // The term c*x^d becomes c*x^(n-d) in the reciprocal
566
567            // Build new variable powers with updated degree
568            let new_powers: Vec<(Var, u32)> = term
569                .monomial
570                .vars()
571                .iter()
572                .map(|vp| {
573                    if vp.var == var {
574                        (vp.var, max_degree - degree)
575                    } else {
576                        (vp.var, vp.power)
577                    }
578                })
579                .collect();
580
581            let new_monomial =
582                if new_powers.is_empty() || (new_powers.len() == 1 && new_powers[0].1 == 0) {
583                    crate::polynomial::Monomial::unit()
584                } else {
585                    crate::polynomial::Monomial::from_powers(new_powers)
586                };
587
588            crate::polynomial::Term::new(term.coeff.clone(), new_monomial)
589        })
590        .collect();
591
592    Polynomial::from_terms(terms, crate::polynomial::MonomialOrder::default())
593}
594
595/// Shift a variable in a polynomial to a new variable.
596///
597/// This replaces all occurrences of `old_var` with `new_var` in the polynomial.
598#[allow(dead_code)]
599fn shift_var(p: &Polynomial, old_var: Var, new_var: Var) -> Polynomial {
600    if old_var == new_var {
601        return p.clone();
602    }
603
604    let terms: Vec<_> = p
605        .terms()
606        .iter()
607        .map(|term| {
608            let new_powers: Vec<(Var, u32)> = term
609                .monomial
610                .vars()
611                .iter()
612                .map(|vp| {
613                    if vp.var == old_var {
614                        (new_var, vp.power)
615                    } else {
616                        (vp.var, vp.power)
617                    }
618                })
619                .collect();
620
621            let new_monomial = if new_powers.is_empty() {
622                crate::polynomial::Monomial::unit()
623            } else {
624                crate::polynomial::Monomial::from_powers(new_powers)
625            };
626
627            crate::polynomial::Term::new(term.coeff.clone(), new_monomial)
628        })
629        .collect();
630
631    Polynomial::from_terms(terms, crate::polynomial::MonomialOrder::default())
632}
633
634/// Find the root interval closest to a target value.
635///
636/// Given a list of root intervals and a target value, returns the interval
637/// whose midpoint is closest to the target.
638#[allow(dead_code)]
639fn find_closest_root(
640    roots: &[(BigRational, BigRational)],
641    target: &BigRational,
642) -> Option<(BigRational, BigRational)> {
643    if roots.is_empty() {
644        return None;
645    }
646
647    let mut best_root = None;
648    let mut best_distance: Option<BigRational> = None;
649
650    for (lo, hi) in roots {
651        // Compute midpoint of interval
652        let mid = (lo + hi) / BigRational::from_integer(BigInt::from(2));
653        let distance = (mid - target).abs();
654
655        match &best_distance {
656            None => {
657                best_distance = Some(distance);
658                best_root = Some((lo.clone(), hi.clone()));
659            }
660            Some(d) => {
661                if distance < *d {
662                    best_distance = Some(distance);
663                    best_root = Some((lo.clone(), hi.clone()));
664                }
665            }
666        }
667    }
668
669    best_root
670}
671
672/// Scale a polynomial for multiplication: p(x) -> y^deg(p) * p(z/y)
673///
674/// This is used for computing the resultant for algebraic number multiplication.
675/// If α is a root of p(x), then we want to find a polynomial whose root is α*β.
676#[allow(dead_code)]
677fn scale_for_product(p: &Polynomial, x_var: Var, z_var: Var) -> Polynomial {
678    // Find the maximum degree of x_var in the polynomial
679    let max_degree = p
680        .terms()
681        .iter()
682        .map(|term| term.monomial.degree(x_var))
683        .max()
684        .unwrap_or(0);
685
686    // Introduce a new variable for y (use var 1)
687    let y_var = 1;
688
689    let terms: Vec<_> = p
690        .terms()
691        .iter()
692        .map(|term| {
693            let x_degree = term.monomial.degree(x_var);
694            // c * x^d becomes c * y^(n-d) * z^d
695            // where n = max_degree
696
697            let mut new_powers = Vec::new();
698
699            // Add powers from other variables (not x_var)
700            for vp in term.monomial.vars() {
701                if vp.var != x_var {
702                    new_powers.push((vp.var, vp.power));
703                }
704            }
705
706            // Add y^(n-d)
707            if max_degree > x_degree {
708                new_powers.push((y_var, max_degree - x_degree));
709            }
710
711            // Add z^d
712            if x_degree > 0 {
713                new_powers.push((z_var, x_degree));
714            }
715
716            let new_monomial = if new_powers.is_empty() {
717                crate::polynomial::Monomial::unit()
718            } else {
719                crate::polynomial::Monomial::from_powers(new_powers)
720            };
721
722            crate::polynomial::Term::new(term.coeff.clone(), new_monomial)
723        })
724        .collect();
725
726    Polynomial::from_terms(terms, crate::polynomial::MonomialOrder::default())
727}
728
729#[cfg(test)]
730mod tests {
731    use super::*;
732
733    fn rat(n: i64) -> BigRational {
734        BigRational::from_integer(BigInt::from(n))
735    }
736
737    #[test]
738    fn test_algebraic_from_rational() {
739        let a = AlgebraicNumber::from_rational(rat(3));
740        assert!(a.is_positive());
741        assert_eq!(a.approximate(), rat(3));
742    }
743
744    #[test]
745    fn test_algebraic_sqrt() {
746        // √4 = 2 (rational) - should be detected as a perfect square
747        if let Some(a) = AlgebraicNumber::sqrt(&rat(4)) {
748            assert_eq!(a.approximate(), rat(2));
749        } else {
750            // If not detected as perfect square, still should work
751            panic!("√4 should return a value");
752        }
753
754        // √2 (irrational) - test proper root isolation
755        if let Some(mut b) = AlgebraicNumber::sqrt(&rat(2)) {
756            // Refine to get better approximation
757            for _ in 0..20 {
758                b.refine();
759            }
760
761            // After refinement, check the approximation is reasonable
762            let approx = b.approximate();
763            // √2 ≈ 1.414, so should be positive and less than 2
764            assert!(approx.is_positive(), "√2 approximation should be positive");
765            assert!(
766                approx < BigRational::new(BigInt::from(2), BigInt::from(1)),
767                "√2 approximation should be less than 2"
768            );
769
770            // Verify √2 is indeed close to a root of x^2 - 2
771            let poly = b.polynomial();
772            let val = poly.eval_at(b.var(), &approx);
773            // After sufficient refinement, evaluation should be close to zero
774            let constant = val.constant_term().abs();
775            assert!(
776                constant < BigRational::from_integer(BigInt::from(1)),
777                "Polynomial evaluation at approximation should be small, got {}",
778                constant
779            );
780        } else {
781            panic!("√2 should return a value");
782        }
783    }
784
785    #[test]
786    fn test_algebraic_negate() {
787        let a = AlgebraicNumber::from_rational(rat(3));
788        let neg_a = a.negate();
789        assert_eq!(neg_a.approximate(), rat(-3));
790    }
791
792    #[test]
793    fn test_algebraic_add_rational() {
794        let a = AlgebraicNumber::from_rational(rat(3));
795        let b = a.add_rational(&rat(5));
796        assert_eq!(b.approximate(), rat(8));
797    }
798
799    #[test]
800    fn test_algebraic_mul_rational() {
801        let a = AlgebraicNumber::from_rational(rat(3));
802        let b = a.mul_rational(&rat(4));
803        assert_eq!(b.approximate(), rat(12));
804    }
805
806    #[test]
807    fn test_algebraic_cmp_rational() {
808        let mut a = AlgebraicNumber::from_rational(rat(3));
809        assert_eq!(a.cmp_rational(&rat(2)), Ordering::Greater);
810        assert_eq!(a.cmp_rational(&rat(3)), Ordering::Equal);
811        assert_eq!(a.cmp_rational(&rat(4)), Ordering::Less);
812    }
813
814    #[test]
815    fn test_algebraic_cmp() {
816        let mut a = AlgebraicNumber::from_rational(rat(2));
817        let mut b = AlgebraicNumber::from_rational(rat(3));
818        assert_eq!(a.cmp_algebraic(&mut b), Ordering::Less);
819    }
820
821    #[test]
822    fn test_algebraic_sign() {
823        let a = AlgebraicNumber::from_rational(rat(5));
824        assert_eq!(a.sign(), Some(1));
825
826        let b = AlgebraicNumber::from_rational(rat(-3));
827        assert_eq!(b.sign(), Some(-1));
828
829        let c = AlgebraicNumber::from_rational(rat(0));
830        assert_eq!(c.sign(), Some(0));
831    }
832
833    #[test]
834    fn test_algebraic_sub_rational() {
835        let a = AlgebraicNumber::from_rational(rat(10));
836        let b = a.sub_rational(&rat(3));
837        assert_eq!(b.approximate(), rat(7));
838
839        let c = AlgebraicNumber::from_rational(rat(5));
840        let d = c.sub_rational(&rat(8));
841        assert_eq!(d.approximate(), rat(-3));
842    }
843
844    #[test]
845    fn test_algebraic_inverse() {
846        // Inverse of 4 is 1/4
847        let a = AlgebraicNumber::from_rational(rat(4));
848        let inv_a = a.inverse().expect("test operation should succeed");
849        assert_eq!(
850            inv_a.approximate(),
851            BigRational::new(BigInt::from(1), BigInt::from(4))
852        );
853
854        // Inverse of -2 is -1/2
855        let b = AlgebraicNumber::from_rational(rat(-2));
856        let inv_b = b.inverse().expect("test operation should succeed");
857        assert_eq!(
858            inv_b.approximate(),
859            BigRational::new(BigInt::from(-1), BigInt::from(2))
860        );
861
862        // Inverse of 0 should be None
863        let c = AlgebraicNumber::from_rational(rat(0));
864        assert!(c.inverse().is_none());
865    }
866
867    #[test]
868    fn test_algebraic_div_rational() {
869        // 10 / 2 = 5
870        let a = AlgebraicNumber::from_rational(rat(10));
871        let b = a
872            .div_rational(&rat(2))
873            .expect("test operation should succeed");
874        assert_eq!(b.approximate(), rat(5));
875
876        // 6 / 4 = 3/2
877        let c = AlgebraicNumber::from_rational(rat(6));
878        let d = c
879            .div_rational(&rat(4))
880            .expect("test operation should succeed");
881        assert_eq!(
882            d.approximate(),
883            BigRational::new(BigInt::from(3), BigInt::from(2))
884        );
885
886        // Division by zero should be None
887        let e = AlgebraicNumber::from_rational(rat(5));
888        assert!(e.div_rational(&rat(0)).is_none());
889    }
890
891    #[test]
892    fn test_algebraic_pow() {
893        // 2^3 = 8
894        let a = AlgebraicNumber::from_rational(rat(2));
895        let b = a.pow(3).expect("test operation should succeed");
896        assert_eq!(b.approximate(), rat(8));
897
898        // 3^0 = 1
899        let c = AlgebraicNumber::from_rational(rat(3));
900        let d = c.pow(0).expect("test operation should succeed");
901        assert_eq!(d.approximate(), rat(1));
902
903        // 2^(-1) = 1/2
904        let e = AlgebraicNumber::from_rational(rat(2));
905        let f = e.pow(-1).expect("test operation should succeed");
906        assert_eq!(
907            f.approximate(),
908            BigRational::new(BigInt::from(1), BigInt::from(2))
909        );
910
911        // 0^(-1) should be None (division by zero)
912        let g = AlgebraicNumber::from_rational(rat(0));
913        assert!(g.pow(-1).is_none());
914    }
915
916    #[test]
917    fn test_algebraic_refine() {
918        let a = AlgebraicNumber::from_rational(rat(5));
919        let (lo1, hi1) = a.interval();
920        assert_eq!(lo1, hi1); // Exact rational
921
922        // Test with an irrational number
923        if let Some(mut sqrt2) = AlgebraicNumber::sqrt(&rat(2)) {
924            let (lo1, hi1) = sqrt2.interval();
925            let width1 = hi1 - lo1;
926
927            sqrt2.refine();
928            let (lo2, hi2) = sqrt2.interval();
929            let width2 = hi2 - lo2;
930
931            // After refinement, interval should be smaller
932            assert!(width2 < width1);
933        }
934    }
935
936    #[test]
937    fn test_algebraic_approximate_with_precision() {
938        if let Some(mut sqrt2) = AlgebraicNumber::sqrt(&rat(2)) {
939            let epsilon = BigRational::new(BigInt::from(1), BigInt::from(100));
940            let approx = sqrt2.approximate_with_precision(&epsilon);
941
942            // Check that the interval width is now less than epsilon
943            let (lo, hi) = sqrt2.interval();
944            assert!(
945                hi - lo < epsilon,
946                "Interval width {} should be less than {}",
947                hi - lo,
948                epsilon
949            );
950
951            // Check approximation is reasonable - should be positive and less than 2
952            assert!(approx.is_positive(), "√2 approximation should be positive");
953            assert!(
954                approx < BigRational::new(BigInt::from(2), BigInt::from(1)),
955                "√2 approximation should be less than 2"
956            );
957        }
958    }
959
960    #[test]
961    fn test_algebraic_add_algebraic() {
962        // Test addition of two algebraic numbers (rational case)
963        // 2 + 3 = 5
964        let mut a = AlgebraicNumber::from_rational(rat(2));
965        let mut b = AlgebraicNumber::from_rational(rat(3));
966        let c = a.add_algebraic(&mut b);
967        assert_eq!(c.approximate(), rat(5));
968    }
969
970    #[test]
971    fn test_algebraic_mul_algebraic() {
972        // Test multiplication of two algebraic numbers (rational case)
973        // 2 * 3 = 6
974        let mut a = AlgebraicNumber::from_rational(rat(2));
975        let mut b = AlgebraicNumber::from_rational(rat(3));
976        let c = a.mul_algebraic(&mut b);
977        assert_eq!(c.approximate(), rat(6));
978    }
979
980    #[test]
981    fn test_algebraic_add_irrational() {
982        // Test √2 + √2 using algebraic operations
983        // Note: Full irrational-to-irrational operations require resultant-based
984        // computation which is complex. For now, test the basic functionality.
985        let mut sqrt2_a = AlgebraicNumber::sqrt(&rat(2)).expect("test operation should succeed");
986        let mut sqrt2_b = AlgebraicNumber::sqrt(&rat(2)).expect("test operation should succeed");
987
988        // add_algebraic returns a result (currently an approximation for irrational+irrational)
989        let sum = sqrt2_a.add_algebraic(&mut sqrt2_b);
990
991        // Just verify it doesn't crash and returns some value
992        // A full implementation would preserve the exact algebraic structure
993        let _ = sum.approximate();
994    }
995
996    #[test]
997    fn test_algebraic_mul_irrational() {
998        // Test √2 * √3 using algebraic operations
999        // Note: Full irrational-to-irrational operations require resultant-based
1000        // computation which is complex. For now, test the basic functionality.
1001        let mut sqrt2 = AlgebraicNumber::sqrt(&rat(2)).expect("test operation should succeed");
1002        let mut sqrt3 = AlgebraicNumber::sqrt(&rat(3)).expect("test operation should succeed");
1003
1004        // mul_algebraic returns a result (currently an approximation for irrational*irrational)
1005        let product = sqrt2.mul_algebraic(&mut sqrt3);
1006
1007        // Just verify it doesn't crash and returns some value
1008        // A full implementation would preserve the exact algebraic structure
1009        let _ = product.approximate();
1010    }
1011
1012    #[test]
1013    fn test_algebraic_add_mixed() {
1014        // Test 1 + √2 using add_rational (exact operation)
1015        let sqrt2 = AlgebraicNumber::sqrt(&rat(2)).expect("test operation should succeed");
1016
1017        // Use add_rational directly for exact computation
1018        let sum = sqrt2.add_rational(&rat(1));
1019
1020        // Verify the result is positive and reasonable
1021        assert!(sum.is_positive());
1022
1023        // The sum should be > 2 (since √2 > 1.4 and 1 + 1.4 = 2.4 > 2)
1024        let approx = sum.approximate();
1025        assert!(approx > rat(2), "1 + √2 should be > 2, got {}", approx);
1026    }
1027
1028    #[test]
1029    fn test_algebraic_mul_by_rational() {
1030        // Test 2 * √3 using mul_rational (exact operation)
1031        let sqrt3 = AlgebraicNumber::sqrt(&rat(3)).expect("test operation should succeed");
1032
1033        // Verify sqrt3 is not negative (it may have lower bound = 0)
1034        assert!(!sqrt3.is_negative(), "√3 should not be negative");
1035
1036        // Use mul_rational directly for exact computation
1037        let product = sqrt3.mul_rational(&rat(2));
1038
1039        // Check the interval - should be non-negative
1040        let (lo, hi) = product.interval();
1041        assert!(
1042            !lo.is_negative(),
1043            "Lower bound should be non-negative, got {}",
1044            lo
1045        );
1046        assert!(
1047            hi.is_positive(),
1048            "Upper bound should be positive, got {}",
1049            hi
1050        );
1051
1052        // The product should be > 3 (since √3 > 1.7 and 2 * 1.7 = 3.4 > 3)
1053        let approx = product.approximate();
1054        assert!(approx > rat(3), "2 * √3 should be > 3, got {}", approx);
1055    }
1056}