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_integer::Integer;
15use num_rational::BigRational;
16use num_traits::{One, Signed, Zero};
17
18/// An algebraic number represented by a polynomial and an isolating interval.
19///
20/// An algebraic number α is represented by:
21/// - A polynomial p(x) such that p(α) = 0
22/// - An isolating interval (a, b) that contains exactly one root of p
23///
24/// This representation allows for exact comparisons and arithmetic on algebraic numbers.
25#[derive(Clone, Debug)]
26pub struct AlgebraicNumber {
27    /// The minimal polynomial having this number as a root.
28    /// This should be square-free and primitive.
29    polynomial: Polynomial,
30
31    /// Variable used in the polynomial (typically 0).
32    var: Var,
33
34    /// Lower bound of the isolating interval.
35    lower: BigRational,
36
37    /// Upper bound of the isolating interval.
38    upper: BigRational,
39}
40
41impl AlgebraicNumber {
42    /// Create a new algebraic number from a polynomial and an isolating interval.
43    ///
44    /// # Panics
45    /// Panics if the interval doesn't contain exactly one root of the polynomial.
46    pub fn new(polynomial: Polynomial, var: Var, lower: BigRational, upper: BigRational) -> Self {
47        // Verify that the interval contains exactly one root
48        let num_roots = polynomial.count_roots_in_interval(var, &lower, &upper);
49        assert_eq!(
50            num_roots, 1,
51            "Interval must contain exactly one root, found {}",
52            num_roots
53        );
54
55        Self {
56            polynomial: polynomial.primitive(),
57            var,
58            lower,
59            upper,
60        }
61    }
62
63    /// Create an algebraic number from a rational number.
64    pub fn from_rational(r: BigRational) -> Self {
65        // The polynomial is (x - r)
66        let poly = Polynomial::from_var(0).sub(&Polynomial::constant(r.clone()));
67
68        Self {
69            polynomial: poly,
70            var: 0,
71            lower: r.clone(),
72            upper: r,
73        }
74    }
75
76    /// Create an algebraic number representing √n for a non-negative rational n.
77    ///
78    /// Returns None if n is negative.
79    pub fn sqrt(n: &BigRational) -> Option<Self> {
80        if n.is_negative() {
81            return None;
82        }
83
84        if n.is_zero() {
85            return Some(Self::from_rational(BigRational::zero()));
86        }
87
88        // Check if n is a perfect square of a rational
89        if let Some(sqrt_n) = crate::polynomial::rational_sqrt(n) {
90            return Some(Self::from_rational(sqrt_n));
91        }
92
93        // Polynomial: x^2 - n
94        let poly =
95            Polynomial::from_coeffs_int(&[(1, &[(0, 2)])]).sub(&Polynomial::constant(n.clone()));
96
97        // Find isolating interval for positive root
98        let roots = poly.isolate_roots(0);
99
100        // Find the positive root
101        for (lo, hi) in roots {
102            let mid = (&lo + &hi) / BigRational::from_integer(BigInt::from(2));
103
104            // We want the interval where midpoint is positive (positive square root)
105            if mid.is_positive() {
106                // For the positive square root, adjust the lower bound if necessary
107                // to ensure it's non-negative (since √n ≥ 0 for n ≥ 0)
108                let adjusted_lo = if lo.is_negative() {
109                    BigRational::zero()
110                } else {
111                    lo
112                };
113
114                // Verify we have exactly one root in the adjusted interval
115                if poly.count_roots_in_interval(0, &adjusted_lo, &hi) == 1 {
116                    return Some(Self::new(poly.clone(), 0, adjusted_lo, hi));
117                }
118            }
119        }
120
121        None
122    }
123
124    /// Get the polynomial defining this algebraic number.
125    pub fn polynomial(&self) -> &Polynomial {
126        &self.polynomial
127    }
128
129    /// Get the isolating interval as (lower, upper).
130    pub fn interval(&self) -> (&BigRational, &BigRational) {
131        (&self.lower, &self.upper)
132    }
133
134    /// Get the variable used in the polynomial.
135    pub fn var(&self) -> Var {
136        self.var
137    }
138
139    /// Refine the isolating interval by bisection.
140    ///
141    /// This makes the interval smaller, improving precision for approximations.
142    pub fn refine(&mut self) {
143        let mid = (&self.lower + &self.upper) / BigRational::from_integer(BigInt::from(2));
144
145        let val_mid = self.polynomial.eval_at(self.var, &mid);
146
147        if val_mid.constant_term().is_zero() {
148            // Found exact root
149            self.lower = mid.clone();
150            self.upper = mid;
151        } else {
152            // Check which half contains the root
153            let val_lo = self.polynomial.eval_at(self.var, &self.lower);
154            let val_mid = self.polynomial.eval_at(self.var, &mid);
155
156            let sign_lo = val_lo.constant_term().signum();
157            let sign_mid = val_mid.constant_term().signum();
158
159            if sign_lo != sign_mid {
160                // Root is in [lower, mid]
161                self.upper = mid;
162            } else {
163                // Root is in [mid, upper]
164                self.lower = mid;
165            }
166        }
167    }
168
169    /// Get an approximation of the algebraic number as a rational.
170    ///
171    /// This returns the midpoint of the isolating interval.
172    pub fn approximate(&self) -> BigRational {
173        (&self.lower + &self.upper) / BigRational::from_integer(BigInt::from(2))
174    }
175
176    /// Get an approximation with a specified precision.
177    ///
178    /// Refines the interval until its width is less than epsilon.
179    pub fn approximate_with_precision(&mut self, epsilon: &BigRational) -> BigRational {
180        while &self.upper - &self.lower > *epsilon {
181            self.refine();
182        }
183        self.approximate()
184    }
185
186    /// Check if this algebraic number is definitely zero.
187    pub fn is_zero(&self) -> bool {
188        self.lower.is_zero() && self.upper.is_zero()
189    }
190
191    /// Check if this algebraic number is definitely positive.
192    pub fn is_positive(&self) -> bool {
193        self.lower.is_positive()
194    }
195
196    /// Check if this algebraic number is definitely negative.
197    pub fn is_negative(&self) -> bool {
198        self.upper.is_negative()
199    }
200
201    /// Check if this algebraic number is actually a rational number.
202    ///
203    /// Returns true if the polynomial is linear (degree 1) which means
204    /// the number is rational.
205    pub fn is_rational(&self) -> bool {
206        // Check if polynomial is of degree 1 (linear)
207        // A linear polynomial represents a rational root
208        let degree = self.polynomial.degree(self.var);
209        degree <= 1 || self.lower == self.upper
210    }
211
212    /// Get the sign of this algebraic number.
213    ///
214    /// Returns:
215    /// - Some(1) if definitely positive
216    /// - Some(-1) if definitely negative
217    /// - Some(0) if definitely zero
218    /// - None if sign is unknown (shouldn't happen with a proper isolating interval)
219    pub fn sign(&self) -> Option<i8> {
220        if self.is_zero() {
221            Some(0)
222        } else if self.is_positive() {
223            Some(1)
224        } else if self.is_negative() {
225            Some(-1)
226        } else {
227            None
228        }
229    }
230
231    /// Compare this algebraic number with another.
232    pub fn cmp_algebraic(&mut self, other: &mut AlgebraicNumber) -> Ordering {
233        // Refine intervals until they don't overlap
234        let max_iterations = 1000;
235        let mut iterations = 0;
236
237        while iterations < max_iterations {
238            iterations += 1;
239
240            // Check if intervals are disjoint
241            if self.upper < other.lower {
242                return Ordering::Less;
243            }
244            if self.lower > other.upper {
245                return Ordering::Greater;
246            }
247            if self.lower == self.upper && other.lower == other.upper && self.lower == other.lower {
248                return Ordering::Equal;
249            }
250
251            // Refine both intervals
252            self.refine();
253            other.refine();
254        }
255
256        // Couldn't determine order after many iterations
257        // Use approximations as fallback
258        self.approximate().cmp(&other.approximate())
259    }
260
261    /// Compare this algebraic number with a rational.
262    pub fn cmp_rational(&mut self, r: &BigRational) -> Ordering {
263        // Refine interval until r is outside it
264        let max_iterations = 1000;
265        let mut iterations = 0;
266
267        while iterations < max_iterations {
268            iterations += 1;
269
270            if &self.upper < r {
271                return Ordering::Less;
272            }
273            if &self.lower > r {
274                return Ordering::Greater;
275            }
276            if &self.lower == r && &self.upper == r {
277                return Ordering::Equal;
278            }
279
280            self.refine();
281        }
282
283        // Use approximation as fallback
284        self.approximate().cmp(r)
285    }
286
287    /// Negate this algebraic number.
288    ///
289    /// If α is a root of p(x), then -α is a root of p(-x).
290    pub fn negate(&self) -> AlgebraicNumber {
291        // Negate the polynomial: replace x with -x
292        let negated_poly = negate_polynomial(&self.polynomial, self.var);
293
294        AlgebraicNumber {
295            polynomial: negated_poly,
296            var: self.var,
297            lower: -self.upper.clone(),
298            upper: -self.lower.clone(),
299        }
300    }
301
302    /// Add this algebraic number with a rational.
303    pub fn add_rational(&self, r: &BigRational) -> AlgebraicNumber {
304        // If α is a root of p(x), then α + r is a root of p(x - r)
305        let shifted_poly = self.polynomial.substitute(
306            self.var,
307            &Polynomial::from_var(self.var).sub(&Polynomial::constant(r.clone())),
308        );
309
310        AlgebraicNumber {
311            polynomial: shifted_poly,
312            var: self.var,
313            lower: &self.lower + r,
314            upper: &self.upper + r,
315        }
316    }
317
318    /// Multiply this algebraic number by a rational.
319    pub fn mul_rational(&self, r: &BigRational) -> AlgebraicNumber {
320        if r.is_zero() {
321            return Self::from_rational(BigRational::zero());
322        }
323
324        // If α is a root of p(x), then r*α is a root of p(x/r)
325        // We need to substitute x with x/r in p
326        let scaled_poly = scale_polynomial_var(&self.polynomial, self.var, r);
327
328        let (new_lower, new_upper) = if r.is_positive() {
329            (&self.lower * r, &self.upper * r)
330        } else {
331            (&self.upper * r, &self.lower * r)
332        };
333
334        AlgebraicNumber {
335            polynomial: scaled_poly,
336            var: self.var,
337            lower: new_lower,
338            upper: new_upper,
339        }
340    }
341
342    /// Subtract a rational from this algebraic number.
343    pub fn sub_rational(&self, r: &BigRational) -> AlgebraicNumber {
344        // α - r = α + (-r)
345        self.add_rational(&(-r))
346    }
347
348    /// Compute the multiplicative inverse of this algebraic number.
349    ///
350    /// Returns None if the number is zero.
351    pub fn inverse(&self) -> Option<AlgebraicNumber> {
352        if self.is_zero() {
353            return None;
354        }
355
356        // If α is a root of p(x) = a_n*x^n + ... + a_1*x + a_0,
357        // then 1/α is a root of x^n * p(1/x) = a_0*x^n + a_1*x^(n-1) + ... + a_n
358        let inv_poly = reciprocal_polynomial(&self.polynomial, self.var);
359
360        // Compute the interval for 1/α
361        // When inverting, the interval [a, b] becomes [1/b, 1/a] (order flips)
362        // This works for both positive and negative intervals
363        let (new_lower, new_upper) = if self.is_positive() || self.is_negative() {
364            (
365                BigRational::from_integer(BigInt::from(1)) / &self.upper,
366                BigRational::from_integer(BigInt::from(1)) / &self.lower,
367            )
368        } else {
369            // Interval contains zero, can't invert
370            return None;
371        };
372
373        Some(AlgebraicNumber {
374            polynomial: inv_poly,
375            var: self.var,
376            lower: new_lower,
377            upper: new_upper,
378        })
379    }
380
381    /// Divide this algebraic number by a rational.
382    ///
383    /// Returns None if the divisor is zero.
384    pub fn div_rational(&self, r: &BigRational) -> Option<AlgebraicNumber> {
385        if r.is_zero() {
386            return None;
387        }
388
389        // α / r = α * (1/r)
390        Some(self.mul_rational(&(BigRational::from_integer(BigInt::from(1)) / r)))
391    }
392
393    /// Raise this algebraic number to an integer power.
394    pub fn pow(&self, n: i32) -> Option<AlgebraicNumber> {
395        if n == 0 {
396            return Some(Self::from_rational(BigRational::from_integer(
397                BigInt::from(1),
398            )));
399        }
400
401        if n < 0 {
402            // α^(-n) = (1/α)^n
403            return self.inverse()?.pow(-n);
404        }
405
406        // For positive n, compute α^n by repeated squaring
407        let mut result = Self::from_rational(BigRational::from_integer(BigInt::from(1)));
408        let mut base = self.clone();
409        let mut exp = n as u32;
410
411        while exp > 0 {
412            if exp % 2 == 1 {
413                // result *= base (simplified for rational approximation)
414                // For now, use approximation approach
415                let approx = base.approximate() * result.approximate();
416                result = Self::from_rational(approx);
417            }
418            if exp > 1 {
419                // base = base * base
420                let approx = base.approximate() * base.approximate();
421                base = Self::from_rational(approx);
422            }
423            exp /= 2;
424        }
425
426        Some(result)
427    }
428
429    /// Add two algebraic numbers exactly, using a resultant-based construction.
430    ///
431    /// Given α (a root of `p`) and β (a root of `q`), the sum `α + β` is a root
432    /// of `R(z) = Res_y(p(y), q(z − y))`, whose real roots are exactly the sums
433    /// `αᵢ + βⱼ` over all roots `αᵢ` of `p` and `βⱼ` of `q`. We compute `R`
434    /// exactly via [`Polynomial::resultant`] (a genuine bivariate resultant),
435    /// make it square-free, then isolate the single root that matches the sum
436    /// of the operand intervals — refining both operand brackets as needed to
437    /// disambiguate. If that root is rational (a *degeneration* such as
438    /// `(1+√2) + (1−√2) = 2`) the result collapses to an exact rational.
439    ///
440    /// Unlike the former implementation, the returned number is a *true*
441    /// algebraic number (exact defining polynomial + isolating interval), not a
442    /// finite rational approximation of an irrational value.
443    ///
444    /// Reference: "Algorithms in Real Algebraic Geometry" (Basu, Pollack,
445    /// Roy) — resultant of `p(y)` and `q(z − y)` for the sum of algebraic
446    /// numbers.
447    pub fn add_algebraic(&mut self, other: &mut AlgebraicNumber) -> AlgebraicNumber {
448        // If either operand is rational, the exact `*_rational` shift applies.
449        if self.is_rational() {
450            return other.add_rational(&self.approximate());
451        }
452        if other.is_rational() {
453            return self.add_rational(&other.approximate());
454        }
455
456        // p(y): self's minimal polynomial rewritten in the elimination
457        // variable Y_VAR; q(z − y): other's polynomial with its variable
458        // replaced by (z − y) in the result/elimination variables.
459        let p_y = self
460            .polynomial
461            .substitute(self.var, &Polynomial::from_var(Y_VAR));
462        let z_minus_y = Polynomial::from_var(Z_VAR).sub(&Polynomial::from_var(Y_VAR));
463        let q_shift = other.polynomial.substitute(other.var, &z_minus_y);
464
465        // R(z) = Res_y(p(y), q(z − y)); roots are the pairwise sums αᵢ + βⱼ.
466        let r = p_y.resultant(&q_shift, Y_VAR).square_free();
467
468        combine_via_resultant(self, other, r, Z_VAR, false)
469    }
470
471    /// Multiply two algebraic numbers exactly, using a resultant-based
472    /// construction.
473    ///
474    /// Given α (a root of `p`) and β (a root of `q` with `deg q = m`), the
475    /// product `α · β` is a root of `R(z) = Res_y(p(y), yᵐ · q(z / y))`, whose
476    /// real roots are exactly the pairwise products `αᵢ · βⱼ` (with `βⱼ ≠ 0`).
477    /// As in [`Self::add_algebraic`], `R` is computed exactly, made square-free,
478    /// and the single root matching the product of the operand intervals is
479    /// isolated (refining as needed); a rational product (e.g. `√2 · √2 = 2`)
480    /// collapses to an exact rational.
481    ///
482    /// Reference: "Algorithms in Real Algebraic Geometry" (Basu, Pollack,
483    /// Roy) — resultant of `p(y)` and `yᵐ q(z/y)` for the product of algebraic
484    /// numbers.
485    pub fn mul_algebraic(&mut self, other: &mut AlgebraicNumber) -> AlgebraicNumber {
486        if self.is_rational() {
487            return other.mul_rational(&self.approximate());
488        }
489        if other.is_rational() {
490            return self.mul_rational(&other.approximate());
491        }
492        // A zero factor makes the product zero. Neither operand is rational
493        // here (0 is rational), so this is a defensive guard only.
494        if self.is_zero() || other.is_zero() {
495            return Self::from_rational(BigRational::zero());
496        }
497
498        let p_y = self
499            .polynomial
500            .substitute(self.var, &Polynomial::from_var(Y_VAR));
501        // q*(y, z) = yᵐ · q(z / y): the reversed/homogenized form of `other`
502        // (see [`scale_for_product`]), living in vars {Y_VAR, Z_VAR}.
503        let q_star = scale_for_product(&other.polynomial, other.var, Z_VAR);
504
505        // R(z) = Res_y(p(y), q*(y, z)); roots are the pairwise products αᵢ · βⱼ.
506        let r = p_y.resultant(&q_star, Y_VAR).square_free();
507
508        combine_via_resultant(self, other, r, Z_VAR, true)
509    }
510}
511
512/// Result variable (`z`) for resultant-based algebraic arithmetic — matches the
513/// `var = 0` convention used by [`AlgebraicNumber::from_rational`] and
514/// [`AlgebraicNumber::sqrt`].
515const Z_VAR: Var = 0;
516/// Elimination variable (`y`) for resultant-based algebraic arithmetic.
517const Y_VAR: Var = 1;
518
519/// Isolate the root of `r_poly` (square-free, univariate in `z_var`) that
520/// corresponds to combining `a` and `b` (sum if `!is_product`, product
521/// otherwise).
522///
523/// The combined value `α ∘ β` lies **strictly** inside the interval-arithmetic
524/// combination `[lo, hi]` of the operand brackets (because the operands are
525/// irrational here, so each strictly brackets its value). We refine both
526/// operand brackets until `[lo, hi]` is a genuine isolating interval for
527/// `r_poly` — exactly one root inside, and neither endpoint itself a root — and
528/// then build the algebraic number directly from that bracket. This deliberately
529/// avoids `isolate_roots`' independently-computed brackets, whose endpoints can
530/// coincide with a *different* root of `r_poly` (e.g. the root `0` of `z³ − 8z`
531/// for `√2 + √2`), which would corrupt later sign-based refinement.
532///
533/// Convergence: once `[lo, hi]` is narrower than the distance from the true
534/// root to the nearest other root of `r_poly`, the count is 1 and the endpoints
535/// (within that distance of the true root) cannot equal another root — so the
536/// loop terminates in a small number of steps.
537fn combine_via_resultant(
538    a: &mut AlgebraicNumber,
539    b: &mut AlgebraicNumber,
540    r_poly: Polynomial,
541    z_var: Var,
542    is_product: bool,
543) -> AlgebraicNumber {
544    for _ in 0..300 {
545        let (lo, hi) = combined_interval(a, b, is_product);
546
547        // The bracket only isolates a root cleanly when neither endpoint is
548        // itself a root of `r_poly`.
549        let lo_is_root = r_poly.eval_at(z_var, &lo).constant_term().is_zero();
550        let hi_is_root = r_poly.eval_at(z_var, &hi).constant_term().is_zero();
551
552        if !lo_is_root && !hi_is_root && r_poly.count_roots_in_interval(z_var, &lo, &hi) == 1 {
553            // Rational degeneration (e.g. (1+√2)+(1−√2) = 2): collapse to an
554            // exact rational when the isolated root is rational.
555            if let Some(root) = rational_root_in(&r_poly, z_var, &lo, &hi) {
556                return AlgebraicNumber::from_rational(root);
557            }
558            return AlgebraicNumber::new(r_poly, z_var, lo, hi);
559        }
560
561        a.refine();
562        b.refine();
563    }
564
565    // Unreachable for well-formed operands (the loop converges long before the
566    // cap). Fall back to the exact rational of the numeric estimate only if the
567    // isolation genuinely never converged, rather than fabricating an interval.
568    AlgebraicNumber::from_rational(combined_point(a, b, is_product))
569}
570
571/// Combined interval [lo, hi] of `a` and `b`: the interval-arithmetic sum
572/// (`is_product == false`) or product (`is_product == true`) of their brackets.
573fn combined_interval(
574    a: &AlgebraicNumber,
575    b: &AlgebraicNumber,
576    is_product: bool,
577) -> (BigRational, BigRational) {
578    if is_product {
579        product_bounds(&a.lower, &a.upper, &b.lower, &b.upper)
580    } else {
581        (&a.lower + &b.lower, &a.upper + &b.upper)
582    }
583}
584
585/// Numeric midpoint estimate of the combined value (for the non-convergent
586/// fallback only).
587fn combined_point(a: &AlgebraicNumber, b: &AlgebraicNumber, is_product: bool) -> BigRational {
588    if is_product {
589        a.approximate() * b.approximate()
590    } else {
591        a.approximate() + b.approximate()
592    }
593}
594
595/// Interval-arithmetic product of `[a_lo, a_hi] · [b_lo, b_hi]`: the min and
596/// max over the four endpoint products.
597fn product_bounds(
598    a_lo: &BigRational,
599    a_hi: &BigRational,
600    b_lo: &BigRational,
601    b_hi: &BigRational,
602) -> (BigRational, BigRational) {
603    let corners = [a_lo * b_lo, a_lo * b_hi, a_hi * b_lo, a_hi * b_hi];
604    let mut lo = corners[0].clone();
605    let mut hi = corners[0].clone();
606    for c in &corners[1..] {
607        if *c < lo {
608            lo = c.clone();
609        }
610        if *c > hi {
611            hi = c.clone();
612        }
613    }
614    (lo, hi)
615}
616
617/// Detect a rational root of `poly` (univariate in `var`) lying **strictly
618/// inside** the open interval `(lo, hi)`, via the rational-root theorem.
619///
620/// Returns `Some(r)` when a rational `r ∈ (lo, hi)` satisfies `poly(r) = 0`.
621/// Strict interior matters: `(lo, hi)` is the *open* isolating interval whose
622/// single interior root we are naming; a root sitting exactly on an endpoint
623/// (e.g. the root `0` of `z³ − 8z` at the boundary `lo = 0`) is a *different*
624/// root and must not be returned.
625///
626/// The divisor enumeration is bounded: when the (integer-cleared) leading and
627/// constant coefficients are large or zero, detection is skipped and `None` is
628/// returned — the caller then keeps the exact algebraic-number representation,
629/// which is still correct, just not simplified to a rational literal.
630fn rational_root_in(
631    poly: &Polynomial,
632    var: Var,
633    lo: &BigRational,
634    hi: &BigRational,
635) -> Option<BigRational> {
636    let deg = poly.degree(var);
637    if deg == 0 {
638        return None;
639    }
640
641    // Integer-cleared coefficients a_0 .. a_deg.
642    let coeffs: Vec<BigRational> = (0..=deg).map(|k| poly.univ_coeff(var, k)).collect();
643    let mut den_lcm = BigInt::one();
644    for c in &coeffs {
645        den_lcm = den_lcm.lcm(c.denom());
646    }
647    let int_coeffs: Vec<BigInt> = coeffs
648        .iter()
649        .map(|c| c.numer() * (&den_lcm / c.denom()))
650        .collect();
651
652    let a0 = &int_coeffs[0];
653    let an = &int_coeffs[deg as usize];
654
655    // A zero constant term means `0` is a root; only report it when it lies
656    // strictly inside the bracket. (Nonzero rational roots of `poly / z` are
657    // not enumerated in this degenerate case — the exact algebraic form is
658    // kept instead.)
659    if a0.is_zero() {
660        let zero = BigRational::zero();
661        if lo < &zero && &zero < hi {
662            return Some(zero);
663        }
664        return None;
665    }
666
667    // Only enumerate divisors when |a0| and |an| are small enough to bound work.
668    let a0_i64 = i64::try_from(a0.abs()).ok().filter(|&x| x <= 1_000_000)?;
669    let an_i64 = i64::try_from(an.abs()).ok().filter(|&x| x <= 1_000_000)?;
670
671    let num_divs = divisors_i64(a0_i64);
672    let den_divs = divisors_i64(an_i64);
673
674    for &p in &num_divs {
675        for &q in &den_divs {
676            for sign in [1i64, -1i64] {
677                let cand = BigRational::new(BigInt::from(sign * p), BigInt::from(q));
678                if &cand <= lo || &cand >= hi {
679                    continue;
680                }
681                if poly.eval_at(var, &cand).constant_term().is_zero() {
682                    return Some(cand);
683                }
684            }
685        }
686    }
687    None
688}
689
690/// Positive divisors of `|n|` (with `n != 0`).
691fn divisors_i64(n: i64) -> Vec<i64> {
692    let n = n.abs();
693    let mut divs = Vec::new();
694    let mut d = 1i64;
695    while d.saturating_mul(d) <= n {
696        if n % d == 0 {
697            divs.push(d);
698            if d != n / d {
699                divs.push(n / d);
700            }
701        }
702        d += 1;
703    }
704    divs
705}
706
707/// Negate a polynomial by replacing x with -x.
708fn negate_polynomial(p: &Polynomial, var: Var) -> Polynomial {
709    let terms: Vec<_> = p
710        .terms()
711        .iter()
712        .map(|term| {
713            let degree = term.monomial.degree(var);
714            let coeff = if degree % 2 == 1 {
715                -term.coeff.clone()
716            } else {
717                term.coeff.clone()
718            };
719            crate::polynomial::Term::new(coeff, term.monomial.clone())
720        })
721        .collect();
722
723    Polynomial::from_terms(terms, crate::polynomial::MonomialOrder::default())
724}
725
726/// Scale polynomial variable: replace x with x/r in p(x).
727fn scale_polynomial_var(p: &Polynomial, var: Var, r: &BigRational) -> Polynomial {
728    if r.is_zero() {
729        return Polynomial::zero();
730    }
731
732    let terms: Vec<_> = p
733        .terms()
734        .iter()
735        .map(|term| {
736            let degree = term.monomial.degree(var);
737            // When we replace x with x/r, the term c*x^d becomes c*(x/r)^d = c*x^d/r^d
738            let new_coeff = &term.coeff / r.pow(degree as i32);
739            crate::polynomial::Term::new(new_coeff, term.monomial.clone())
740        })
741        .collect();
742
743    Polynomial::from_terms(terms, crate::polynomial::MonomialOrder::default())
744}
745
746/// Compute the reciprocal polynomial: x^n * p(1/x).
747///
748/// If α is a root of p(x), then 1/α is a root of the reciprocal polynomial.
749fn reciprocal_polynomial(p: &Polynomial, var: Var) -> Polynomial {
750    // Find the maximum degree of the variable in the polynomial
751    let max_degree = p
752        .terms()
753        .iter()
754        .map(|term| term.monomial.degree(var))
755        .max()
756        .unwrap_or(0);
757
758    let terms: Vec<_> = p
759        .terms()
760        .iter()
761        .map(|term| {
762            let degree = term.monomial.degree(var);
763            // The term c*x^d becomes c*x^(n-d) in the reciprocal
764
765            // Build new variable powers with updated degree
766            let new_powers: Vec<(Var, u32)> = term
767                .monomial
768                .vars()
769                .iter()
770                .map(|vp| {
771                    if vp.var == var {
772                        (vp.var, max_degree - degree)
773                    } else {
774                        (vp.var, vp.power)
775                    }
776                })
777                .collect();
778
779            let new_monomial =
780                if new_powers.is_empty() || (new_powers.len() == 1 && new_powers[0].1 == 0) {
781                    crate::polynomial::Monomial::unit()
782                } else {
783                    crate::polynomial::Monomial::from_powers(new_powers)
784                };
785
786            crate::polynomial::Term::new(term.coeff.clone(), new_monomial)
787        })
788        .collect();
789
790    Polynomial::from_terms(terms, crate::polynomial::MonomialOrder::default())
791}
792
793/// Shift a variable in a polynomial to a new variable.
794///
795/// This replaces all occurrences of `old_var` with `new_var` in the polynomial.
796#[allow(dead_code)]
797fn shift_var(p: &Polynomial, old_var: Var, new_var: Var) -> Polynomial {
798    if old_var == new_var {
799        return p.clone();
800    }
801
802    let terms: Vec<_> = p
803        .terms()
804        .iter()
805        .map(|term| {
806            let new_powers: Vec<(Var, u32)> = term
807                .monomial
808                .vars()
809                .iter()
810                .map(|vp| {
811                    if vp.var == old_var {
812                        (new_var, vp.power)
813                    } else {
814                        (vp.var, vp.power)
815                    }
816                })
817                .collect();
818
819            let new_monomial = if new_powers.is_empty() {
820                crate::polynomial::Monomial::unit()
821            } else {
822                crate::polynomial::Monomial::from_powers(new_powers)
823            };
824
825            crate::polynomial::Term::new(term.coeff.clone(), new_monomial)
826        })
827        .collect();
828
829    Polynomial::from_terms(terms, crate::polynomial::MonomialOrder::default())
830}
831
832/// Scale a polynomial for multiplication: `q(w) -> y^deg(q) * q(z/y)`.
833///
834/// Used to compute the resultant for algebraic-number multiplication: if β is a
835/// root of `q(w)`, then the roots of `Res_y(p(y), y^deg(q) q(z/y))` are the
836/// products `αᵢ · βⱼ`. The elimination variable `y` is [`Y_VAR`]; `z_var` is
837/// the result variable. A term `c · w^d` becomes `c · y^(m−d) · z^d` where
838/// `m = deg(q)`.
839fn scale_for_product(p: &Polynomial, x_var: Var, z_var: Var) -> Polynomial {
840    // Find the maximum degree of x_var in the polynomial
841    let max_degree = p
842        .terms()
843        .iter()
844        .map(|term| term.monomial.degree(x_var))
845        .max()
846        .unwrap_or(0);
847
848    // The homogenizing variable `y` is the shared elimination variable.
849    let y_var = Y_VAR;
850
851    let terms: Vec<_> = p
852        .terms()
853        .iter()
854        .map(|term| {
855            let x_degree = term.monomial.degree(x_var);
856            // c * x^d becomes c * y^(n-d) * z^d
857            // where n = max_degree
858
859            let mut new_powers = Vec::new();
860
861            // Add powers from other variables (not x_var)
862            for vp in term.monomial.vars() {
863                if vp.var != x_var {
864                    new_powers.push((vp.var, vp.power));
865                }
866            }
867
868            // Add y^(n-d)
869            if max_degree > x_degree {
870                new_powers.push((y_var, max_degree - x_degree));
871            }
872
873            // Add z^d
874            if x_degree > 0 {
875                new_powers.push((z_var, x_degree));
876            }
877
878            let new_monomial = if new_powers.is_empty() {
879                crate::polynomial::Monomial::unit()
880            } else {
881                crate::polynomial::Monomial::from_powers(new_powers)
882            };
883
884            crate::polynomial::Term::new(term.coeff.clone(), new_monomial)
885        })
886        .collect();
887
888    Polynomial::from_terms(terms, crate::polynomial::MonomialOrder::default())
889}
890
891#[cfg(test)]
892mod tests {
893    use super::*;
894
895    fn rat(n: i64) -> BigRational {
896        BigRational::from_integer(BigInt::from(n))
897    }
898
899    #[test]
900    fn test_algebraic_from_rational() {
901        let a = AlgebraicNumber::from_rational(rat(3));
902        assert!(a.is_positive());
903        assert_eq!(a.approximate(), rat(3));
904    }
905
906    #[test]
907    fn test_algebraic_sqrt() {
908        // √4 = 2 (rational) - should be detected as a perfect square
909        if let Some(a) = AlgebraicNumber::sqrt(&rat(4)) {
910            assert_eq!(a.approximate(), rat(2));
911        } else {
912            // If not detected as perfect square, still should work
913            panic!("√4 should return a value");
914        }
915
916        // √2 (irrational) - test proper root isolation
917        if let Some(mut b) = AlgebraicNumber::sqrt(&rat(2)) {
918            // Refine to get better approximation
919            for _ in 0..20 {
920                b.refine();
921            }
922
923            // After refinement, check the approximation is reasonable
924            let approx = b.approximate();
925            // √2 ≈ 1.414, so should be positive and less than 2
926            assert!(approx.is_positive(), "√2 approximation should be positive");
927            assert!(
928                approx < BigRational::new(BigInt::from(2), BigInt::from(1)),
929                "√2 approximation should be less than 2"
930            );
931
932            // Verify √2 is indeed close to a root of x^2 - 2
933            let poly = b.polynomial();
934            let val = poly.eval_at(b.var(), &approx);
935            // After sufficient refinement, evaluation should be close to zero
936            let constant = val.constant_term().abs();
937            assert!(
938                constant < BigRational::from_integer(BigInt::from(1)),
939                "Polynomial evaluation at approximation should be small, got {}",
940                constant
941            );
942        } else {
943            panic!("√2 should return a value");
944        }
945    }
946
947    #[test]
948    fn test_algebraic_negate() {
949        let a = AlgebraicNumber::from_rational(rat(3));
950        let neg_a = a.negate();
951        assert_eq!(neg_a.approximate(), rat(-3));
952    }
953
954    #[test]
955    fn test_algebraic_add_rational() {
956        let a = AlgebraicNumber::from_rational(rat(3));
957        let b = a.add_rational(&rat(5));
958        assert_eq!(b.approximate(), rat(8));
959    }
960
961    #[test]
962    fn test_algebraic_mul_rational() {
963        let a = AlgebraicNumber::from_rational(rat(3));
964        let b = a.mul_rational(&rat(4));
965        assert_eq!(b.approximate(), rat(12));
966    }
967
968    #[test]
969    fn test_algebraic_cmp_rational() {
970        let mut a = AlgebraicNumber::from_rational(rat(3));
971        assert_eq!(a.cmp_rational(&rat(2)), Ordering::Greater);
972        assert_eq!(a.cmp_rational(&rat(3)), Ordering::Equal);
973        assert_eq!(a.cmp_rational(&rat(4)), Ordering::Less);
974    }
975
976    #[test]
977    fn test_algebraic_cmp() {
978        let mut a = AlgebraicNumber::from_rational(rat(2));
979        let mut b = AlgebraicNumber::from_rational(rat(3));
980        assert_eq!(a.cmp_algebraic(&mut b), Ordering::Less);
981    }
982
983    #[test]
984    fn test_algebraic_sign() {
985        let a = AlgebraicNumber::from_rational(rat(5));
986        assert_eq!(a.sign(), Some(1));
987
988        let b = AlgebraicNumber::from_rational(rat(-3));
989        assert_eq!(b.sign(), Some(-1));
990
991        let c = AlgebraicNumber::from_rational(rat(0));
992        assert_eq!(c.sign(), Some(0));
993    }
994
995    #[test]
996    fn test_algebraic_sub_rational() {
997        let a = AlgebraicNumber::from_rational(rat(10));
998        let b = a.sub_rational(&rat(3));
999        assert_eq!(b.approximate(), rat(7));
1000
1001        let c = AlgebraicNumber::from_rational(rat(5));
1002        let d = c.sub_rational(&rat(8));
1003        assert_eq!(d.approximate(), rat(-3));
1004    }
1005
1006    #[test]
1007    fn test_algebraic_inverse() {
1008        // Inverse of 4 is 1/4
1009        let a = AlgebraicNumber::from_rational(rat(4));
1010        let inv_a = a.inverse().expect("test operation should succeed");
1011        assert_eq!(
1012            inv_a.approximate(),
1013            BigRational::new(BigInt::from(1), BigInt::from(4))
1014        );
1015
1016        // Inverse of -2 is -1/2
1017        let b = AlgebraicNumber::from_rational(rat(-2));
1018        let inv_b = b.inverse().expect("test operation should succeed");
1019        assert_eq!(
1020            inv_b.approximate(),
1021            BigRational::new(BigInt::from(-1), BigInt::from(2))
1022        );
1023
1024        // Inverse of 0 should be None
1025        let c = AlgebraicNumber::from_rational(rat(0));
1026        assert!(c.inverse().is_none());
1027    }
1028
1029    #[test]
1030    fn test_algebraic_div_rational() {
1031        // 10 / 2 = 5
1032        let a = AlgebraicNumber::from_rational(rat(10));
1033        let b = a
1034            .div_rational(&rat(2))
1035            .expect("test operation should succeed");
1036        assert_eq!(b.approximate(), rat(5));
1037
1038        // 6 / 4 = 3/2
1039        let c = AlgebraicNumber::from_rational(rat(6));
1040        let d = c
1041            .div_rational(&rat(4))
1042            .expect("test operation should succeed");
1043        assert_eq!(
1044            d.approximate(),
1045            BigRational::new(BigInt::from(3), BigInt::from(2))
1046        );
1047
1048        // Division by zero should be None
1049        let e = AlgebraicNumber::from_rational(rat(5));
1050        assert!(e.div_rational(&rat(0)).is_none());
1051    }
1052
1053    #[test]
1054    fn test_algebraic_pow() {
1055        // 2^3 = 8
1056        let a = AlgebraicNumber::from_rational(rat(2));
1057        let b = a.pow(3).expect("test operation should succeed");
1058        assert_eq!(b.approximate(), rat(8));
1059
1060        // 3^0 = 1
1061        let c = AlgebraicNumber::from_rational(rat(3));
1062        let d = c.pow(0).expect("test operation should succeed");
1063        assert_eq!(d.approximate(), rat(1));
1064
1065        // 2^(-1) = 1/2
1066        let e = AlgebraicNumber::from_rational(rat(2));
1067        let f = e.pow(-1).expect("test operation should succeed");
1068        assert_eq!(
1069            f.approximate(),
1070            BigRational::new(BigInt::from(1), BigInt::from(2))
1071        );
1072
1073        // 0^(-1) should be None (division by zero)
1074        let g = AlgebraicNumber::from_rational(rat(0));
1075        assert!(g.pow(-1).is_none());
1076    }
1077
1078    #[test]
1079    fn test_algebraic_refine() {
1080        let a = AlgebraicNumber::from_rational(rat(5));
1081        let (lo1, hi1) = a.interval();
1082        assert_eq!(lo1, hi1); // Exact rational
1083
1084        // Test with an irrational number
1085        if let Some(mut sqrt2) = AlgebraicNumber::sqrt(&rat(2)) {
1086            let (lo1, hi1) = sqrt2.interval();
1087            let width1 = hi1 - lo1;
1088
1089            sqrt2.refine();
1090            let (lo2, hi2) = sqrt2.interval();
1091            let width2 = hi2 - lo2;
1092
1093            // After refinement, interval should be smaller
1094            assert!(width2 < width1);
1095        }
1096    }
1097
1098    #[test]
1099    fn test_algebraic_approximate_with_precision() {
1100        if let Some(mut sqrt2) = AlgebraicNumber::sqrt(&rat(2)) {
1101            let epsilon = BigRational::new(BigInt::from(1), BigInt::from(100));
1102            let approx = sqrt2.approximate_with_precision(&epsilon);
1103
1104            // Check that the interval width is now less than epsilon
1105            let (lo, hi) = sqrt2.interval();
1106            assert!(
1107                hi - lo < epsilon,
1108                "Interval width {} should be less than {}",
1109                hi - lo,
1110                epsilon
1111            );
1112
1113            // Check approximation is reasonable - should be positive and less than 2
1114            assert!(approx.is_positive(), "√2 approximation should be positive");
1115            assert!(
1116                approx < BigRational::new(BigInt::from(2), BigInt::from(1)),
1117                "√2 approximation should be less than 2"
1118            );
1119        }
1120    }
1121
1122    #[test]
1123    fn test_algebraic_add_algebraic() {
1124        // Test addition of two algebraic numbers (rational case)
1125        // 2 + 3 = 5
1126        let mut a = AlgebraicNumber::from_rational(rat(2));
1127        let mut b = AlgebraicNumber::from_rational(rat(3));
1128        let c = a.add_algebraic(&mut b);
1129        assert_eq!(c.approximate(), rat(5));
1130    }
1131
1132    #[test]
1133    fn test_algebraic_mul_algebraic() {
1134        // Test multiplication of two algebraic numbers (rational case)
1135        // 2 * 3 = 6
1136        let mut a = AlgebraicNumber::from_rational(rat(2));
1137        let mut b = AlgebraicNumber::from_rational(rat(3));
1138        let c = a.mul_algebraic(&mut b);
1139        assert_eq!(c.approximate(), rat(6));
1140    }
1141
1142    #[test]
1143    fn test_algebraic_add_irrational() {
1144        // √2 + √2 = 2√2 = √8 (root of x² - 8). The sum is a genuine algebraic
1145        // number whose defining polynomial vanishes at 2√2, not a rational
1146        // approximation.
1147        let mut sqrt2_a = AlgebraicNumber::sqrt(&rat(2)).expect("test operation should succeed");
1148        let mut sqrt2_b = AlgebraicNumber::sqrt(&rat(2)).expect("test operation should succeed");
1149
1150        let mut sum = sqrt2_a.add_algebraic(&mut sqrt2_b);
1151
1152        // The result must be irrational (2√2), i.e. not a rational collapse.
1153        assert!(!sum.is_rational(), "√2 + √2 = 2√2 is irrational");
1154
1155        // Its defining polynomial shares the factor x² - 8 (roots ±2√2).
1156        let x2_minus_8 = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (-8, &[])]);
1157        let g = sum.polynomial().gcd_univariate(&x2_minus_8);
1158        assert!(
1159            g.degree(sum.var()) >= 1,
1160            "sum's polynomial must share the root 2√2 (root of x²-8)"
1161        );
1162
1163        // And its interval brackets 2√2 ≈ 2.8284 after refinement.
1164        for _ in 0..40 {
1165            sum.refine();
1166        }
1167        let approx = sum.approximate();
1168        assert!(
1169            approx > rat(2) && approx < rat(3),
1170            "2√2 ≈ 2.83, got {approx}"
1171        );
1172    }
1173
1174    #[test]
1175    fn test_algebraic_mul_irrational() {
1176        // √2 · √3 = √6 (root of x² - 6): an exact algebraic number, not an
1177        // approximation.
1178        let mut sqrt2 = AlgebraicNumber::sqrt(&rat(2)).expect("test operation should succeed");
1179        let mut sqrt3 = AlgebraicNumber::sqrt(&rat(3)).expect("test operation should succeed");
1180
1181        let mut product = sqrt2.mul_algebraic(&mut sqrt3);
1182
1183        assert!(!product.is_rational(), "√2 · √3 = √6 is irrational");
1184
1185        let x2_minus_6 = Polynomial::from_coeffs_int(&[(1, &[(0, 2)]), (-6, &[])]);
1186        let g = product.polynomial().gcd_univariate(&x2_minus_6);
1187        assert!(
1188            g.degree(product.var()) >= 1,
1189            "product's polynomial must share the root √6 (root of x²-6)"
1190        );
1191
1192        for _ in 0..40 {
1193            product.refine();
1194        }
1195        let approx = product.approximate();
1196        assert!(
1197            approx > rat(2) && approx < rat(3),
1198            "√6 ≈ 2.45, got {approx}"
1199        );
1200    }
1201
1202    #[test]
1203    fn test_algebraic_add_mixed() {
1204        // Test 1 + √2 using add_rational (exact operation)
1205        let sqrt2 = AlgebraicNumber::sqrt(&rat(2)).expect("test operation should succeed");
1206
1207        // Use add_rational directly for exact computation
1208        let sum = sqrt2.add_rational(&rat(1));
1209
1210        // Verify the result is positive and reasonable
1211        assert!(sum.is_positive());
1212
1213        // The sum should be > 2 (since √2 > 1.4 and 1 + 1.4 = 2.4 > 2)
1214        let approx = sum.approximate();
1215        assert!(approx > rat(2), "1 + √2 should be > 2, got {}", approx);
1216    }
1217
1218    #[test]
1219    fn test_algebraic_mul_by_rational() {
1220        // Test 2 * √3 using mul_rational (exact operation)
1221        let sqrt3 = AlgebraicNumber::sqrt(&rat(3)).expect("test operation should succeed");
1222
1223        // Verify sqrt3 is not negative (it may have lower bound = 0)
1224        assert!(!sqrt3.is_negative(), "√3 should not be negative");
1225
1226        // Use mul_rational directly for exact computation
1227        let product = sqrt3.mul_rational(&rat(2));
1228
1229        // Check the interval - should be non-negative
1230        let (lo, hi) = product.interval();
1231        assert!(
1232            !lo.is_negative(),
1233            "Lower bound should be non-negative, got {}",
1234            lo
1235        );
1236        assert!(
1237            hi.is_positive(),
1238            "Upper bound should be positive, got {}",
1239            hi
1240        );
1241
1242        // The product should be > 3 (since √3 > 1.7 and 2 * 1.7 = 3.4 > 3)
1243        let approx = product.approximate();
1244        assert!(approx > rat(3), "2 * √3 should be > 3, got {}", approx);
1245    }
1246}