Skip to main content

logicaffeine_base/
algebraic.rs

1//! Real algebraic numbers: exact roots of integer polynomials, with decidable equality and order.
2//!
3//! An [`Algebraic`] is the `index`-th real root (counting from 1 in increasing order) of its
4//! **minimal polynomial** — irreducible over ℚ, primitive over ℤ, positive leading coefficient —
5//! together with an isolating interval for that root. The pair (minimal polynomial, index) is the
6//! value's canonical name, so equality and hashing read only it and two routes to `√2` are the same
7//! value; the interval is a refinable cache that comparison, floor and printing narrow as needed.
8//! A rational is the degree-1 case, held with its exact point interval.
9//!
10//! Arithmetic builds a polynomial the result is known to be a root of, then cuts it down by
11//! factoring and picks the one irreducible factor with exactly one root inside an interval
12//! enclosure of the result, shrinking the enclosure until only one candidate root remains:
13//!
14//! - `α + β` is a root of `Res_y(m_α(y), m_β(x − y))`, `α·β` of `Res_y(m_α(y), y^{deg} m_β(x/y))`,
15//!   and `α^e` of `Res_y(m_α(y), x − y^e)` — each resultant interpolated from univariate
16//!   resultants at integer points, which is exact because the leading coefficients in `y` do not
17//!   depend on `x`;
18//! - a rational shift or scale, negation, and reciprocal transform the minimal polynomial
19//!   directly (they keep it irreducible), and `ᵏ√α` is a root of `m_α(x^k)`.
20//!
21//! Every decision is exact: the order of two distinct values is found by refining both intervals
22//! until they separate, which always happens because distinct numbers have a positive distance.
23
24use crate::numeric::{rational_from_f64_exact, rational_to_f64_nearest, BigInt, Rational};
25use crate::poly::{Poly, RootInterval};
26use std::cmp::Ordering;
27use std::fmt;
28use std::hash::{Hash, Hasher};
29
30/// A real algebraic number. See the module documentation.
31#[derive(Clone, Debug)]
32pub struct Algebraic {
33    /// Irreducible over ℚ, primitive over ℤ, positive leading coefficient.
34    min: Poly,
35    /// Which real root of `min`, counting from 1 in increasing order.
36    index: usize,
37    /// `lo == hi` for a rational; otherwise an open interval holding this root of `min` and no
38    /// other, with neither end a root.
39    iv: RootInterval,
40}
41
42/// `⌊√n⌋` for `n ≥ 0`, by Newton's method on integers.
43fn isqrt(n: &BigInt) -> BigInt {
44    if n.is_zero() {
45        return BigInt::zero();
46    }
47    let mut x = BigInt::from_i64(1).shl_bits(n.bit_len() / 2 + 1);
48    loop {
49        let y = x.add(&n.div_rem(&x).expect("x > 0").0).shr_bits(1).expect("nonnegative");
50        if y >= x {
51            return x;
52        }
53        x = y;
54    }
55}
56
57/// A quadratic irrational `a + b·√d`, `d` a positive integer that is no square.
58struct Quadratic {
59    a: Rational,
60    b: Rational,
61    d: BigInt,
62}
63
64fn half() -> Rational {
65    Rational::from_ratio_i64(1, 2).expect("2 is nonzero")
66}
67
68fn pow2_recip(bits: u32) -> Rational {
69    Rational::new(BigInt::from_i64(1), BigInt::from_i64(1).shl_bits(bits as u64))
70        .expect("a power of two is nonzero")
71}
72
73/// The largest integer `s ≥ 0` with `sᵏ ≤ n`, for `n ≥ 0`.
74fn integer_root_floor(n: &BigInt, k: u32) -> BigInt {
75    if n.is_zero() {
76        return BigInt::zero();
77    }
78    let top = n.bit_len() / k as u64 + 1;
79    let mut s = BigInt::zero();
80    for bit in (0..=top).rev() {
81        let t = s.add(&BigInt::from_i64(1).shl_bits(bit));
82        if t.pow(k) <= *n {
83            s = t;
84        }
85    }
86    s
87}
88
89/// Rational bounds `(l, u)` with `lᵏ ≤ x ≤ uᵏ` and `u − l ≤ 2^-bits`, for `x ≥ 0`.
90fn rational_root_bounds(x: &Rational, k: u32, bits: u32) -> (Rational, Rational) {
91    let scale = BigInt::from_i64(1).shl_bits(k as u64 * bits as u64);
92    let scaled = x.mul(&Rational::from_bigint(scale));
93    let lo_int = integer_root_floor(&scaled.floor(), k);
94    let ceil = scaled.ceil();
95    let mut hi_int = integer_root_floor(&ceil, k);
96    if hi_int.pow(k) < ceil {
97        hi_int = hi_int.add(&BigInt::from_i64(1));
98    }
99    let den = BigInt::from_i64(1).shl_bits(bits as u64);
100    (
101        Rational::new(lo_int, den.clone()).expect("nonzero"),
102        Rational::new(hi_int, den).expect("nonzero"),
103    )
104}
105
106/// `x^e` over a closed interval.
107fn interval_pow(lo: &Rational, hi: &Rational, e: u32) -> (Rational, Rational) {
108    let pl = lo.pow(e as i32).expect("nonnegative powers exist");
109    let ph = hi.pow(e as i32).expect("nonnegative powers exist");
110    if e % 2 == 1 || !lo.is_negative() {
111        (pl, ph)
112    } else if !hi.is_positive() {
113        (ph, pl)
114    } else {
115        (Rational::zero(), pl.max(ph))
116    }
117}
118
119fn interval_mul(a: &(Rational, Rational), b: &(Rational, Rational)) -> (Rational, Rational) {
120    let products = [a.0.mul(&b.0), a.0.mul(&b.1), a.1.mul(&b.0), a.1.mul(&b.1)];
121    let lo = products.iter().min().expect("four products").clone();
122    let hi = products.iter().max().expect("four products").clone();
123    (lo, hi)
124}
125
126/// `Res_y(p(y), q_at(x₀)(y))` as a polynomial in `x`, interpolated through `degree + 1` integer
127/// points by Newton's divided differences.
128pub(crate) fn resultant_in_y(p: &Poly, q_at: impl Fn(&Rational) -> Poly, degree: usize) -> Poly {
129    let xs: Vec<Rational> = (0..=degree as i64).map(Rational::from_i64).collect();
130    let mut d: Vec<Rational> = xs.iter().map(|x| p.resultant(&q_at(x))).collect();
131    for j in 1..=degree {
132        for i in (j..=degree).rev() {
133            let num = d[i].sub(&d[i - 1]);
134            let den = xs[i].sub(&xs[i - j]);
135            d[i] = num.div(&den).expect("interpolation points are distinct");
136        }
137    }
138    let mut poly = Poly::constant(d[degree].clone());
139    for j in (0..degree).rev() {
140        let factor = Poly::from_coeffs(vec![xs[j].negated(), Rational::one()]);
141        poly = poly.mul(&factor).add(&Poly::constant(d[j].clone()));
142    }
143    poly
144}
145
146/// The square-free decomposition `n = k²·d` (`n > 0`) by trial division with the primes below
147/// 10 000, then a perfect-square test of whatever cofactor remains.
148fn split_square(n: &BigInt) -> (BigInt, BigInt) {
149    let mut rest = n.clone();
150    let mut k = BigInt::from_i64(1);
151    let mut d = BigInt::from_i64(1);
152    let mut p = 2i64;
153    while p < 10_000 {
154        let pb = BigInt::from_i64(p);
155        if pb.mul(&pb) > rest {
156            break;
157        }
158        let mut e = 0u32;
159        loop {
160            let (q, r) = rest.div_rem(&pb).expect("p is nonzero");
161            if !r.is_zero() {
162                break;
163            }
164            rest = q;
165            e += 1;
166        }
167        k = k.mul(&pb.pow(e / 2));
168        if e % 2 == 1 {
169            d = d.mul(&pb);
170        }
171        p += if p == 2 { 1 } else { 2 };
172    }
173    let s = integer_root_floor(&rest, 2);
174    if s.mul(&s) == rest {
175        k = k.mul(&s);
176    } else {
177        d = d.mul(&rest);
178    }
179    (k, d)
180}
181
182impl Algebraic {
183    pub fn from_i64(n: i64) -> Algebraic {
184        Self::from_rational(Rational::from_i64(n))
185    }
186
187    pub fn from_rational(r: Rational) -> Algebraic {
188        let min = Poly::from_bigints(&[r.numerator().negated(), r.denominator().clone()]);
189        Algebraic {
190            min,
191            index: 1,
192            iv: RootInterval {
193                lo: r.clone(),
194                hi: r,
195            },
196        }
197    }
198
199    /// The exact value of a finite float. `None` for NaN and the infinities.
200    pub fn from_f64(f: f64) -> Option<Algebraic> {
201        rational_from_f64_exact(f).map(Self::from_rational)
202    }
203
204    /// The `k`-th real root of `p` (counting from 1 in increasing order, each distinct root once).
205    pub fn root_of(p: &Poly, k: usize) -> Option<Algebraic> {
206        if k == 0 || p.degree().unwrap_or(0) == 0 {
207            return None;
208        }
209        let roots = p.isolate_real_roots();
210        let iv = roots.get(k - 1)?;
211        if iv.lo == iv.hi {
212            return Some(Self::from_rational(iv.lo.clone()));
213        }
214        let factors: Vec<Poly> = p.factor_over_q().into_iter().map(|(g, _)| g).collect();
215        let g = factors
216            .into_iter()
217            .find(|g| g.count_real_roots_in(&iv.lo, &iv.hi) == 1)
218            .expect("the root lies on exactly one irreducible factor");
219        Some(Self::from_irreducible(g, iv.lo.clone(), iv.hi.clone()))
220    }
221
222    /// The root of the irreducible primitive `g` in the open interval `(lo, hi)`, which holds that
223    /// root and no other root of `g`, with neither end a root.
224    fn from_irreducible(g: Poly, lo: Rational, hi: Rational) -> Algebraic {
225        if g.degree() == Some(1) {
226            let r = g.coeff(0).negated().div(&g.coeff(1)).expect("degree 1");
227            return Self::from_rational(r);
228        }
229        let bound = g.root_bound();
230        let index = g.count_real_roots_in(&bound.negated(), &lo) + 1;
231        Algebraic {
232            min: g,
233            index,
234            iv: RootInterval { lo, hi },
235        }
236    }
237
238    /// The one root, among the roots of `candidate`, that `enclose` encloses: the irreducible
239    /// factors of `candidate` are tested against enclosures of increasing precision until exactly
240    /// one factor has exactly one root in the closed enclosure.
241    fn select(candidate: &Poly, enclose: impl Fn(u32) -> (Rational, Rational)) -> Algebraic {
242        let factors: Vec<Poly> = candidate.factor_over_q().into_iter().map(|(g, _)| g).collect();
243        let mut bits = 4u32;
244        loop {
245            let (lo, hi) = enclose(bits);
246            if lo == hi {
247                return Self::from_rational(lo);
248            }
249            let mut hits = factors.iter().filter_map(|g| {
250                let n = g.count_real_roots_in(&lo, &hi) + usize::from(g.eval(&lo).is_zero());
251                (n > 0).then_some((g, n))
252            });
253            if let (Some((g, 1)), None) = (hits.next(), hits.next()) {
254                if g.degree() == Some(1) {
255                    let r = g.coeff(0).negated().div(&g.coeff(1)).expect("degree 1");
256                    return Self::from_rational(r);
257                }
258                return Self::from_irreducible(g.clone(), lo, hi);
259            }
260            bits = bits.saturating_mul(2);
261        }
262    }
263
264    pub fn degree(&self) -> usize {
265        self.min.degree().expect("a minimal polynomial is nonzero")
266    }
267
268    pub fn minimal_polynomial(&self) -> &Poly {
269        &self.min
270    }
271
272    /// Which real root of the minimal polynomial this is, counting from 1 in increasing order.
273    pub fn root_index(&self) -> usize {
274        self.index
275    }
276
277    pub fn to_rational(&self) -> Option<Rational> {
278        (self.degree() == 1).then(|| self.iv.lo.clone())
279    }
280
281    pub fn is_zero(&self) -> bool {
282        self.degree() == 1 && self.iv.lo.is_zero()
283    }
284
285    pub fn signum(&self) -> i32 {
286        if let Some(r) = self.to_rational() {
287            return if r.is_zero() {
288                0
289            } else if r.is_negative() {
290                -1
291            } else {
292                1
293            };
294        }
295        let mut iv = self.iv.clone();
296        loop {
297            if !iv.lo.is_negative() {
298                return 1;
299            }
300            if !iv.hi.is_positive() {
301                return -1;
302            }
303            iv = self.bisect(&iv);
304        }
305    }
306
307    /// Halve this value's isolating interval.
308    fn bisect(&self, iv: &RootInterval) -> RootInterval {
309        if iv.lo == iv.hi {
310            return iv.clone();
311        }
312        let mid = iv.lo.add(&iv.hi).mul(&half());
313        let at_mid = self.min.eval(&mid);
314        if at_mid.is_zero() {
315            return RootInterval {
316                lo: mid.clone(),
317                hi: mid,
318            };
319        }
320        if at_mid.is_negative() == self.min.eval(&iv.lo).is_negative() {
321            RootInterval {
322                lo: mid,
323                hi: iv.hi.clone(),
324            }
325        } else {
326            RootInterval {
327                lo: iv.lo.clone(),
328                hi: mid,
329            }
330        }
331    }
332
333    /// A closed interval `[lo, hi]` holding the value, at most `2^-bits` wide; a rational encloses
334    /// itself exactly.
335    pub fn enclosure(&self, bits: u32) -> (Rational, Rational) {
336        let eps = pow2_recip(bits);
337        let mut iv = self.iv.clone();
338        while iv.hi.sub(&iv.lo) > eps {
339            iv = self.bisect(&iv);
340        }
341        (iv.lo, iv.hi)
342    }
343
344    pub fn neg(&self) -> Algebraic {
345        if let Some(r) = self.to_rational() {
346            return Self::from_rational(r.negated());
347        }
348        let min = Poly::from_bigints(&self.min.neg_var().primitive_int());
349        let real_roots = min.count_real_roots();
350        Algebraic {
351            index: real_roots + 1 - self.index,
352            iv: RootInterval {
353                lo: self.iv.hi.negated(),
354                hi: self.iv.lo.negated(),
355            },
356            min,
357        }
358    }
359
360    fn add_rational(&self, r: &Rational) -> Algebraic {
361        if let Some(s) = self.to_rational() {
362            return Self::from_rational(s.add(r));
363        }
364        let shifted = self
365            .min
366            .compose(&Poly::from_coeffs(vec![r.negated(), Rational::one()]));
367        Algebraic {
368            min: Poly::from_bigints(&shifted.primitive_int()),
369            index: self.index,
370            iv: RootInterval {
371                lo: self.iv.lo.add(r),
372                hi: self.iv.hi.add(r),
373            },
374        }
375    }
376
377    fn mul_rational(&self, r: &Rational) -> Algebraic {
378        if r.is_zero() {
379            return Self::from_i64(0);
380        }
381        if let Some(s) = self.to_rational() {
382            return Self::from_rational(s.mul(r));
383        }
384        let scaled = self.min.compose(&Poly::from_coeffs(vec![
385            Rational::zero(),
386            r.recip().expect("r is nonzero"),
387        ]));
388        let min = Poly::from_bigints(&scaled.primitive_int());
389        let (lo, hi) = (self.iv.lo.mul(r), self.iv.hi.mul(r));
390        if r.is_positive() {
391            Algebraic {
392                min,
393                index: self.index,
394                iv: RootInterval { lo, hi },
395            }
396        } else {
397            let real_roots = min.count_real_roots();
398            Algebraic {
399                min,
400                index: real_roots + 1 - self.index,
401                iv: RootInterval { lo: hi, hi: lo },
402            }
403        }
404    }
405
406    pub fn add(&self, o: &Algebraic) -> Algebraic {
407        if let Some(r) = o.to_rational() {
408            return self.add_rational(&r);
409        }
410        if let Some(r) = self.to_rational() {
411            return o.add_rational(&r);
412        }
413        if let Some((x, y)) = self.one_quadratic_field(o) {
414            return Self::from_quadratic(x.a.add(&y.a), x.b.add(&y.b), &x.d);
415        }
416        let mb = o.min.clone();
417        let candidate = resultant_in_y(
418            &self.min,
419            |x0| mb.compose(&Poly::from_coeffs(vec![x0.clone(), Rational::from_i64(-1)])),
420            self.degree() * o.degree(),
421        );
422        Self::select(&candidate, |bits| {
423            let (alo, ahi) = self.enclosure(bits + 1);
424            let (blo, bhi) = o.enclosure(bits + 1);
425            (alo.add(&blo), ahi.add(&bhi))
426        })
427    }
428
429    pub fn sub(&self, o: &Algebraic) -> Algebraic {
430        self.add(&o.neg())
431    }
432
433    pub fn mul(&self, o: &Algebraic) -> Algebraic {
434        if let Some(r) = o.to_rational() {
435            return self.mul_rational(&r);
436        }
437        if let Some(r) = self.to_rational() {
438            return o.mul_rational(&r);
439        }
440        if let Some((x, y)) = self.one_quadratic_field(o) {
441            let d = Rational::from_bigint(x.d.clone());
442            let a = x.a.mul(&y.a).add(&x.b.mul(&y.b).mul(&d));
443            let b = x.a.mul(&y.b).add(&y.a.mul(&x.b));
444            return Self::from_quadratic(a, b, &x.d);
445        }
446        let db = o.degree();
447        let mb = o.min.clone();
448        let candidate = resultant_in_y(
449            &self.min,
450            |x0| {
451                let mut c = vec![Rational::zero(); db + 1];
452                let mut xp = Rational::one();
453                for j in 0..=db {
454                    c[db - j] = mb.coeff(j).mul(&xp);
455                    xp = xp.mul(x0);
456                }
457                Poly::from_coeffs(c)
458            },
459            self.degree() * db,
460        );
461        Self::select(&candidate, |bits| {
462            let a = self.enclosure(bits + 4);
463            let b = o.enclosure(bits + 4);
464            interval_mul(&a, &b)
465        })
466    }
467
468    pub fn recip(&self) -> Option<Algebraic> {
469        if self.is_zero() {
470            return None;
471        }
472        if let Some(r) = self.to_rational() {
473            return Some(Self::from_rational(r.recip().expect("nonzero")));
474        }
475        let candidate = Poly::from_bigints(&self.min.reverse().primitive_int());
476        Some(Self::select(&candidate, |bits| {
477            let mut b = bits;
478            loop {
479                let (lo, hi) = self.enclosure(b);
480                if lo.is_positive() || hi.is_negative() {
481                    return (
482                        hi.recip().expect("0 is outside the enclosure"),
483                        lo.recip().expect("0 is outside the enclosure"),
484                    );
485                }
486                b += 4;
487            }
488        }))
489    }
490
491    pub fn div(&self, o: &Algebraic) -> Option<Algebraic> {
492        Some(self.mul(&o.recip()?))
493    }
494
495    /// `self^e`; `None` only for zero to a negative power.
496    pub fn pow(&self, e: i32) -> Option<Algebraic> {
497        if e < 0 {
498            return self.recip()?.pow(-e);
499        }
500        if e == 0 {
501            return Some(Self::from_i64(1));
502        }
503        if let Some(r) = self.to_rational() {
504            return Some(Self::from_rational(r.pow(e).expect("nonnegative power")));
505        }
506        let e = e as u32;
507        let candidate = resultant_in_y(
508            &self.min,
509            |x0| {
510                let mut c = vec![Rational::zero(); e as usize + 1];
511                c[0] = x0.clone();
512                c[e as usize] = Rational::from_i64(-1);
513                Poly::from_coeffs(c)
514            },
515            self.degree(),
516        );
517        Some(Self::select(&candidate, |bits| {
518            let (lo, hi) = self.enclosure(bits + 2 * e + 8);
519            interval_pow(&lo, &hi, e)
520        }))
521    }
522
523    /// The nonnegative square root; `None` for a negative number.
524    pub fn sqrt(&self) -> Option<Algebraic> {
525        self.nth_root(2)
526    }
527
528    /// The real `k`-th root: the nonnegative one for even `k` (`None` for a negative number), the
529    /// unique one for odd `k`. `None` for `k = 0`.
530    pub fn nth_root(&self, k: u32) -> Option<Algebraic> {
531        if k == 0 {
532            return None;
533        }
534        if k == 1 || self.is_zero() {
535            return Some(self.clone());
536        }
537        let negative = self.signum() < 0;
538        if negative {
539            if k.is_multiple_of(2) {
540                return None;
541            }
542            return Some(self.neg().nth_root(k)?.neg());
543        }
544        let mut c = vec![Rational::zero(); self.degree() * k as usize + 1];
545        for (i, a) in self.min.coeffs().iter().enumerate() {
546            c[i * k as usize] = a.clone();
547        }
548        let candidate = Poly::from_coeffs(c);
549        Some(Self::select(&candidate, |bits| {
550            let (lo, hi) = self.enclosure(bits * k + 4);
551            let lo = if lo.is_negative() { Rational::zero() } else { lo };
552            let (l, _) = rational_root_bounds(&lo, k, bits + 2);
553            let (_, u) = rational_root_bounds(&hi, k, bits + 2);
554            (l, u)
555        }))
556    }
557
558    /// The distinct real roots of `Σ coeffs[i]·x^i`, a polynomial with real algebraic coefficients,
559    /// in increasing order, each with its multiplicity; none for a constant.
560    ///
561    /// Every root is a root of the polynomial's norm over the rationals — the product of its
562    /// conjugates under every embedding of its coefficients, a tower of resultants against their
563    /// minimal polynomials ([`crate::elimination::Polynomial::resultant`]) — so the roots are found among the
564    /// real roots of the norm's irreducible factors, each kept when the polynomial vanishes there
565    /// exactly.
566    pub fn real_roots(coeffs: &[Algebraic]) -> Vec<(Algebraic, usize)> {
567        let mut c = coeffs.to_vec();
568        while c.last().is_some_and(Algebraic::is_zero) {
569            c.pop();
570        }
571        if c.len() <= 1 {
572            return Vec::new();
573        }
574        let rational: Option<Vec<Rational>> = c.iter().map(Algebraic::to_rational).collect();
575        let norm = match &rational {
576            Some(r) => Poly::from_coeffs(r.clone()),
577            None => norm_over_q(&c),
578        };
579        let mut out = Vec::new();
580        for (g, _) in norm.factor_over_q() {
581            for k in 1..=g.count_real_roots() {
582                let r = Self::root_of(&g, k).expect("an index within the real roots");
583                if rational.is_none() && !eval_at(&c, &r).is_zero() {
584                    continue;
585                }
586                let mut multiplicity = 1;
587                let mut d = derivative(&c);
588                while !d.is_empty() && eval_at(&d, &r).is_zero() {
589                    multiplicity += 1;
590                    d = derivative(&d);
591                }
592                out.push((r, multiplicity));
593            }
594        }
595        out.sort_by(|a, b| a.0.cmp(&b.0));
596        out
597    }
598
599    pub fn floor(&self) -> BigInt {
600        if let Some(r) = self.to_rational() {
601            return r.floor();
602        }
603        let mut iv = self.iv.clone();
604        loop {
605            let (fl, fh) = (iv.lo.floor(), iv.hi.floor());
606            if fl == fh {
607                return fl;
608            }
609            iv = self.bisect(&iv);
610        }
611    }
612
613    pub fn ceil(&self) -> BigInt {
614        if let Some(r) = self.to_rational() {
615            return r.ceil();
616        }
617        let mut iv = self.iv.clone();
618        loop {
619            let (cl, ch) = (iv.lo.ceil(), iv.hi.ceil());
620            if cl == ch {
621                return cl;
622            }
623            iv = self.bisect(&iv);
624        }
625    }
626
627    /// The nearest f64 (ties to even): the interval is refined until both ends round alike.
628    pub fn to_f64(&self) -> f64 {
629        if let Some(r) = self.to_rational() {
630            return rational_to_f64_nearest(&r);
631        }
632        let mut iv = self.iv.clone();
633        loop {
634            let (fl, fh) = (rational_to_f64_nearest(&iv.lo), rational_to_f64_nearest(&iv.hi));
635            if fl.to_bits() == fh.to_bits() {
636                return fl;
637            }
638            iv = self.bisect(&iv);
639        }
640    }
641
642    /// The radical form of a quadratic irrational: `(r ± s√d)/t`.
643    /// A quadratic irrational as `a + b·√d`, `d` the discriminant of its minimal polynomial
644    /// `c₂x² + c₁x + c₀` (`c₂ > 0`): of the two roots `(−c₁ ± √d)/(2c₂)`, the larger has `+`. `None` for
645    /// any other degree.
646    fn quadratic(&self) -> Option<Quadratic> {
647        if self.degree() != 2 {
648            return None;
649        }
650        let c = self.min.primitive_int();
651        let (c0, c1, c2) = (&c[0], &c[1], &c[2]);
652        let d = c1.mul(c1).sub(&BigInt::from_i64(4).mul(c2).mul(c0));
653        let two_c2 = Rational::from_bigint(c2.add(c2));
654        let a = Rational::from_bigint(c1.negated()).div(&two_c2).expect("c₂ is nonzero");
655        let b = Rational::one().div(&two_c2).expect("c₂ is nonzero");
656        let b = if self.index == 2 { b } else { b.negated() };
657        Some(Quadratic { a, b, d })
658    }
659
660    /// Both values as `a + b·√d` over one `d`, in either order, when both are quadratic irrationals of
661    /// one field: their discriminants' product is a square `r²`, so `√d₂ = (r/d₁)·√d₁`, and their sum
662    /// and product are that field's own, with no resultant. The smaller discriminant is the one kept.
663    fn one_quadratic_field(&self, o: &Algebraic) -> Option<(Quadratic, Quadratic)> {
664        let (x, y) = (self.quadratic()?, o.quadratic()?);
665        if x.d == y.d {
666            return Some((x, y));
667        }
668        let product = x.d.mul(&y.d);
669        let r = isqrt(&product);
670        if r.mul(&r) != product {
671            return None;
672        }
673        let (keep, other) = if x.d <= y.d { (x, y) } else { (y, x) };
674        let scale = Rational::from_bigint(r).div(&Rational::from_bigint(keep.d.clone())).expect("d is positive");
675        let moved = Quadratic {
676            a: other.a,
677            b: other.b.mul(&scale),
678            d: keep.d.clone(),
679        };
680        Some((keep, moved))
681    }
682
683    /// `a + b·√d` in its one form, `d` a positive integer that is no square: a rational when `b` is
684    /// zero, otherwise the root `a ± |b|·√d` of the irreducible `(x − a)² − b²·d`, the larger when `b`
685    /// is positive. With `s < √d < s + 1`, `a ± |b|·(s, s + 1)` holds that root and not the other,
686    /// which lies `2|b|·√d > |b|` away, and neither end is a root, both being rational.
687    fn from_quadratic(a: Rational, b: Rational, d: &BigInt) -> Algebraic {
688        if b.is_zero() {
689            return Self::from_rational(a);
690        }
691        let c0 = a.mul(&a).sub(&b.mul(&b).mul(&Rational::from_bigint(d.clone())));
692        let c1 = a.add(&a).negated();
693        let p = Poly::from_coeffs(vec![c0, c1, Rational::one()]);
694        let min = Poly::from_bigints(&p.primitive_int());
695        let larger = b.is_positive();
696        let m = if larger { b } else { b.negated() };
697        let s = Rational::from_bigint(isqrt(d));
698        let (near, far) = (m.mul(&s), m.mul(&s.add(&Rational::one())));
699        let (lo, hi) = if larger { (a.add(&near), a.add(&far)) } else { (a.sub(&far), a.sub(&near)) };
700        Algebraic {
701            min,
702            index: if larger { 2 } else { 1 },
703            iv: RootInterval { lo, hi },
704        }
705    }
706
707    fn fmt_quadratic(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
708        let c = self.min.primitive_int();
709        let (cc, b, a) = (&c[0], &c[1], &c[2]);
710        let four = BigInt::from_i64(4);
711        let disc = b.mul(b).sub(&four.mul(a).mul(cc));
712        let (k, d) = split_square(&disc);
713        let two_a = a.add(a);
714        let g = b.abs().gcd(&k).gcd(&two_a);
715        let r = b.negated().div_rem(&g).expect("g divides b").0;
716        let s = k.div_rem(&g).expect("g divides k").0;
717        let t = two_a.div_rem(&g).expect("g divides 2a").0;
718        let plus = self.index == 2;
719        let one = BigInt::from_i64(1);
720        let radical = if s == one {
721            format!("√{d}")
722        } else {
723            format!("{s}√{d}")
724        };
725        if r.is_zero() {
726            let sign = if plus { "" } else { "-" };
727            if t == one {
728                write!(f, "{sign}{radical}")
729            } else {
730                write!(f, "{sign}{radical}/{t}")
731            }
732        } else {
733            let op = if plus { "+" } else { "-" };
734            if t == one {
735                write!(f, "{r} {op} {radical}")
736            } else {
737                write!(f, "({r} {op} {radical})/{t}")
738            }
739        }
740    }
741}
742
743/// `Σ c[i]·x^i` at `x`, by Horner's rule.
744fn eval_at(c: &[Algebraic], x: &Algebraic) -> Algebraic {
745    c.iter().rev().fold(Algebraic::from_i64(0), |acc, a| acc.mul(x).add(a))
746}
747
748fn derivative(c: &[Algebraic]) -> Vec<Algebraic> {
749    c.iter()
750        .enumerate()
751        .skip(1)
752        .map(|(i, a)| a.mul_rational(&Rational::from_i64(i as i64)))
753        .collect()
754}
755
756/// The norm over ℚ of `Σ c[i]·x^i`: each distinct irrational coefficient is a variable `z_j`, and
757/// each `z_j` is eliminated against its minimal polynomial.
758fn norm_over_q(c: &[Algebraic]) -> Poly {
759    use crate::elimination::Polynomial;
760    let mut symbols: Vec<&Algebraic> = Vec::new();
761    for a in c {
762        if a.to_rational().is_none() && !symbols.contains(&a) {
763            symbols.push(a);
764        }
765    }
766    let mut f = Polynomial::zero();
767    for (i, a) in c.iter().enumerate() {
768        let coefficient = match a.to_rational() {
769            Some(r) => Polynomial::constant(r),
770            None => Polynomial::var(1 + symbols.iter().position(|s| *s == a).expect("collected")),
771        };
772        f = f.add(&coefficient.mul(&Polynomial::var(0).pow(i as u32)));
773    }
774    for (j, a) in symbols.iter().enumerate() {
775        let minimal = Polynomial::from_univariate(a.minimal_polynomial(), j + 1);
776        f = minimal.resultant(&f, j + 1);
777    }
778    f.to_univariate(0).expect("every coefficient eliminated")
779}
780
781impl PartialEq for Algebraic {
782    fn eq(&self, o: &Self) -> bool {
783        self.index == o.index && self.min == o.min
784    }
785}
786
787impl Eq for Algebraic {}
788
789impl Hash for Algebraic {
790    fn hash<H: Hasher>(&self, state: &mut H) {
791        self.min.hash(state);
792        self.index.hash(state);
793    }
794}
795
796impl Ord for Algebraic {
797    fn cmp(&self, o: &Self) -> Ordering {
798        if self == o {
799            return Ordering::Equal;
800        }
801        if let (Some(a), Some(b)) = (self.to_rational(), o.to_rational()) {
802            return a.cmp(&b);
803        }
804        let (mut a, mut b) = (self.iv.clone(), o.iv.clone());
805        loop {
806            if a.hi <= b.lo {
807                return Ordering::Less;
808            }
809            if b.hi <= a.lo {
810                return Ordering::Greater;
811            }
812            if a.hi.sub(&a.lo) >= b.hi.sub(&b.lo) {
813                a = self.bisect(&a);
814            } else {
815                b = o.bisect(&b);
816            }
817        }
818    }
819}
820
821impl PartialOrd for Algebraic {
822    fn partial_cmp(&self, o: &Self) -> Option<Ordering> {
823        Some(self.cmp(o))
824    }
825}
826
827impl fmt::Display for Algebraic {
828    /// A rational as a rational; a quadratic irrational in simplified radical form
829    /// (`(1 + √5)/2`, `3√2/4`); anything of higher degree as `root k of <minimal polynomial>`.
830    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
831        match self.degree() {
832            1 => write!(f, "{}", self.iv.lo),
833            2 => self.fmt_quadratic(f),
834            _ => write!(f, "root {} of {}", self.index, self.min),
835        }
836    }
837}
838
839impl crate::exact_codec::ExactCodec for Algebraic {
840    /// The minimal polynomial, then which of its real roots, counting from 1.
841    fn write(&self, out: &mut Vec<u8>) {
842        self.min.write(out);
843        (self.index as u64).write(out);
844    }
845
846    /// Refused unless the polynomial is primitive with a positive lead, of degree at most
847    /// [`crate::exact_codec::MAX_WIRE_DEGREE`], irreducible, and has that many real roots.
848    fn read(buf: &[u8], pos: &mut usize) -> Option<Self> {
849        let min = Poly::read(buf, pos)?;
850        let index = usize::try_from(u64::read(buf, pos)?).ok()?;
851        let degree = min.degree()?;
852        if degree == 0 || degree > crate::exact_codec::MAX_WIRE_DEGREE || index == 0 {
853            return None;
854        }
855        if Poly::from_bigints(&min.primitive_int()) != min || !min.is_irreducible() {
856            return None;
857        }
858        let value = Algebraic::root_of(&min, index)?;
859        (value.min == min && value.index == index).then_some(value)
860    }
861}
862
863/// Why the even `n`-th root of a negative number is refused, in the words every exact type uses.
864pub(crate) fn negative_root(n: u32) -> String {
865    if n == 2 {
866        "square root of a negative number".to_string()
867    } else {
868        format!("an even root ({n}th) of a negative number")
869    }
870}
871
872#[cfg(test)]
873#[path = "../../logicaffeine_tests/unit/logicaffeine_base/algebraic/tests.rs"]
874mod tests;