Skip to main content

logicaffeine_base/
puiseux.rs

1//! The real closure of the hyperreals: real algebraic Puiseux series in `dt`, exactly.
2//!
3//! The hyperreals a program builds with the field operations are the rational functions of `dt` over
4//! the real algebraic numbers ([`Hyperreal`]); the real closure of that ordered field adds a root to
5//! every polynomial that changes sign. Its elements are the real Puiseux series algebraic over it —
6//! `√dt = dt^(1/2)`, `√(1 + dt) = 1 + dt/2 − dt²/8 + …` — and every one is held exactly, as a
7//! [`Puiseux`]: a squarefree polynomial it is a root of, with coefficients rational functions of
8//! `s = dt^(1/q)`, and the branch of that polynomial's real roots it is.
9//!
10//! **Branches.** The real roots of a polynomial over `K_s` are found by Newton's polygon: an edge of
11//! the lower convex hull of the points `(i, v(p_i))` of slope `−γ` holds the roots of valuation `γ`,
12//! and the real roots `c` of the edge's characteristic polynomial their leading coefficients. A
13//! fractional `γ` refines `s` to a root of itself; a simple `c` separates its root from every other,
14//! and a repeated one is followed into the polynomial `s^(-v)·P(s^γ·(c + z))` until its roots
15//! separate. A separated branch has one root of positive valuation left, so each further term is
16//! `−lc(p₀)/lc(p₁)·s^(v₀ − v₁)`, computed only as far as a comparison, a selection or a printed digit
17//! asks.
18//!
19//! **Operations.** `α + β` is a root of `Res_z(P(z), Q(y − z))`, `α·β` of `Res_z(P(z), z^n·Q(y/z))`,
20//! `α^k` of `Res_z(P(z), y − z^k)` and `ⁿ√α` of `P(y^n)`, each resultant interpolated in `y` at
21//! integer points from resultants over `K_s`. The result is the one root whose expansion agrees with
22//! the operation applied to the operands' expansions through the order at which that polynomial's
23//! roots all differ — so every decision is exact, and two values are equal exactly when their
24//! difference is the zero root of its polynomial.
25//!
26//! **One form for a value.** A value that is a rational function of a root of `dt` is held as that
27//! function ([`Closed::Field`] when it is one of `dt` itself), found by Padé reconstruction from its
28//! expansion with the degree bounds Gauss's lemma gives and checked exactly. It prints as that
29//! function; a value with a power that is one prints as a radical, `√(1 + dt)`, `2·∛(1 + dt)`; any
30//! other prints by its two leading terms and the order of the rest, `2√2 - (√2/16)·dt^2 + O(dt^3)`.
31
32use std::cmp::Ordering;
33use std::fmt;
34use std::hash::{Hash, Hasher};
35use std::sync::{Arc, Mutex, MutexGuard, OnceLock};
36
37use crate::algebraic::{negative_root, Algebraic};
38use crate::hyperreal::{Hyperreal, PowerError};
39use crate::numeric::Rational;
40
41fn zero() -> Hyperreal {
42    Hyperreal::from_i64(0)
43}
44
45fn constant(c: &Algebraic) -> Hyperreal {
46    Hyperreal::from_algebraic(c.clone())
47}
48
49fn power_of_s(k: i64) -> Hyperreal {
50    Hyperreal::monomial(Algebraic::from_i64(1), k)
51}
52
53fn gcd_i64(a: i64, b: i64) -> i64 {
54    if b == 0 {
55        a.abs()
56    } else {
57        gcd_i64(b, a % b)
58    }
59}
60
61fn lcm_u32(a: u32, b: u32) -> u32 {
62    a / gcd_i64(i64::from(a), i64::from(b)) as u32 * b
63}
64
65// ── polynomials over K_s ─────────────────────────────────────────────────────────────────────
66
67/// A polynomial in `y` whose coefficients are rational functions of `s`, lowest power first, with
68/// no trailing zero.
69#[derive(Clone, Debug, PartialEq, Eq, Hash)]
70struct KPoly {
71    c: Vec<Hyperreal>,
72}
73
74impl KPoly {
75    fn from(c: Vec<Hyperreal>) -> KPoly {
76        let mut c = c;
77        while c.last().is_some_and(Hyperreal::is_zero) {
78            c.pop();
79        }
80        KPoly { c }
81    }
82
83    fn is_zero(&self) -> bool {
84        self.c.is_empty()
85    }
86
87    fn degree(&self) -> Option<usize> {
88        self.c.len().checked_sub(1)
89    }
90
91    fn coeff(&self, i: usize) -> Hyperreal {
92        self.c.get(i).cloned().unwrap_or_else(zero)
93    }
94
95    /// `y − k`.
96    fn linear(k: &Hyperreal) -> KPoly {
97        KPoly::from(vec![k.neg(), Hyperreal::from_i64(1)])
98    }
99
100    fn add(&self, o: &KPoly) -> KPoly {
101        let n = self.c.len().max(o.c.len());
102        KPoly::from((0..n).map(|i| self.coeff(i).add(&o.coeff(i))).collect())
103    }
104
105    fn mul(&self, o: &KPoly) -> KPoly {
106        if self.is_zero() || o.is_zero() {
107            return KPoly { c: Vec::new() };
108        }
109        let mut out = vec![zero(); self.c.len() + o.c.len() - 1];
110        for (i, a) in self.c.iter().enumerate() {
111            if a.is_zero() {
112                continue;
113            }
114            for (j, b) in o.c.iter().enumerate() {
115                if !b.is_zero() {
116                    out[i + j] = out[i + j].add(&a.mul(b));
117                }
118            }
119        }
120        KPoly::from(out)
121    }
122
123    fn scale(&self, k: &Hyperreal) -> KPoly {
124        KPoly::from(self.c.iter().map(|a| a.mul(k)).collect())
125    }
126
127    fn monic(&self) -> KPoly {
128        match self.c.last() {
129            None => self.clone(),
130            Some(lc) => self.scale(&lc.recip().expect("a leading coefficient is nonzero")),
131        }
132    }
133
134    fn div_rem(&self, d: &KPoly) -> (KPoly, KPoly) {
135        let dd = d.degree().expect("the divisor is nonzero");
136        let inv = d.c[dd].recip().expect("a leading coefficient is nonzero");
137        if self.c.len() <= dd {
138            return (KPoly { c: Vec::new() }, self.clone());
139        }
140        let mut r = self.c.clone();
141        let mut q = vec![zero(); r.len() - dd];
142        for k in (0..q.len()).rev() {
143            let coef = r[k + dd].mul(&inv);
144            if !coef.is_zero() {
145                for (j, dj) in d.c.iter().enumerate() {
146                    r[k + j] = r[k + j].sub(&coef.mul(dj));
147                }
148            }
149            q[k] = coef;
150        }
151        r.truncate(dd);
152        (KPoly::from(q), KPoly::from(r))
153    }
154
155    fn gcd(&self, o: &KPoly) -> KPoly {
156        let (mut a, mut b) = (self.monic(), o.monic());
157        while !b.is_zero() {
158            let r = a.div_rem(&b).1;
159            a = b;
160            b = r.monic();
161        }
162        a
163    }
164
165    fn derivative(&self) -> KPoly {
166        KPoly::from(
167            self.c
168                .iter()
169                .enumerate()
170                .skip(1)
171                .map(|(i, a)| a.mul(&Hyperreal::from_i64(i as i64)))
172                .collect(),
173        )
174    }
175
176    /// The polynomial with each repeated root once, monic.
177    fn squarefree(&self) -> KPoly {
178        let g = self.gcd(&self.derivative());
179        if g.degree().unwrap_or(0) == 0 {
180            return self.monic();
181        }
182        self.div_rem(&g).0.monic()
183    }
184
185    fn eval(&self, y: &Hyperreal) -> Hyperreal {
186        self.c.iter().rev().fold(zero(), |acc, a| acc.mul(y).add(a))
187    }
188
189    /// `P(y + a)`.
190    fn shift(&self, a: &Hyperreal) -> KPoly {
191        let mut out = KPoly { c: Vec::new() };
192        let step = KPoly::from(vec![a.clone(), Hyperreal::from_i64(1)]);
193        for c in self.c.iter().rev() {
194            out = out.mul(&step).add(&KPoly::from(vec![c.clone()]));
195        }
196        out
197    }
198
199    /// `P(k·y)`.
200    fn stretch(&self, k: &Hyperreal) -> KPoly {
201        let mut power = Hyperreal::from_i64(1);
202        let mut out = Vec::with_capacity(self.c.len());
203        for c in &self.c {
204            out.push(c.mul(&power));
205            power = power.mul(k);
206        }
207        KPoly::from(out)
208    }
209
210    /// `P(−y)`.
211    fn neg_var(&self) -> KPoly {
212        KPoly::from(
213            self.c
214                .iter()
215                .enumerate()
216                .map(|(i, c)| if i % 2 == 1 { c.neg() } else { c.clone() })
217                .collect(),
218        )
219    }
220
221    /// `P(y^n)`.
222    fn spread(&self, n: u32) -> KPoly {
223        let mut out = vec![zero(); self.c.len().saturating_sub(1) * n as usize + 1];
224        for (i, c) in self.c.iter().enumerate() {
225            out[i * n as usize] = c.clone();
226        }
227        KPoly::from(out)
228    }
229
230    /// The same polynomial over `K_{s'}`, `s = s'^m`.
231    fn ramify(&self, m: u32) -> KPoly {
232        KPoly {
233            c: self.c.iter().map(|c| c.ramify(m)).collect(),
234        }
235    }
236
237    /// The polynomial with its root `0` divided out.
238    fn without_zero_root(&self) -> KPoly {
239        let k = self.c.iter().position(|c| !c.is_zero()).unwrap_or(0);
240        KPoly::from(self.c[k..].to_vec())
241    }
242
243    /// The resultant `lc^{deg o}·Π o(α)` over the roots `α` of `self`, by Euclid's algorithm over
244    /// the field `K_s`.
245    fn resultant(&self, o: &KPoly) -> Hyperreal {
246        let (Some(m), Some(n)) = (self.degree(), o.degree()) else {
247            return zero();
248        };
249        let power = |h: &Hyperreal, k: usize| h.pow(k as i32).expect("a resultant's power is small");
250        if n == 0 {
251            return power(&o.c[0], m);
252        }
253        if m == 0 {
254            return power(&self.c[0], n);
255        }
256        let r = self.div_rem(o).1;
257        let Some(dr) = r.degree() else {
258            return zero();
259        };
260        let v = power(&o.c[n], m - dr).mul(&o.resultant(&r));
261        if (m * n) % 2 == 1 {
262            v.neg()
263        } else {
264            v
265        }
266    }
267
268    /// The polynomial in `y` of degree at most `degree` whose value at each integer `j ≤ degree` is
269    /// `at(j)`, by Newton's divided differences over `K_s`.
270    fn interpolate(degree: usize, at: impl Fn(i64) -> Hyperreal) -> KPoly {
271        let nodes: Vec<i64> = (0..=degree as i64).collect();
272        let mut values: Vec<Hyperreal> = nodes.iter().map(|j| at(*j)).collect();
273        let n = nodes.len();
274        for j in 1..n {
275            for i in (j..n).rev() {
276                let gap = Hyperreal::from_i64(nodes[i] - nodes[i - j]);
277                values[i] = values[i].sub(&values[i - 1]).div(&gap).expect("the nodes are distinct");
278            }
279        }
280        let mut out = KPoly::from(vec![values[n - 1].clone()]);
281        for i in (0..n - 1).rev() {
282            let factor = KPoly::from(vec![Hyperreal::from_i64(-nodes[i]), Hyperreal::from_i64(1)]);
283            out = out.mul(&factor).add(&KPoly::from(vec![values[i].clone()]));
284        }
285        out
286    }
287
288    /// `Res_z(P(z), Q(y − z))`: its roots are the sums of a root of each.
289    fn sum_poly(p: &KPoly, q: &KPoly) -> KPoly {
290        let degree = p.degree().unwrap_or(0) * q.degree().unwrap_or(0);
291        let reflected = q.neg_var();
292        KPoly::interpolate(degree, |j| p.resultant(&reflected.shift(&Hyperreal::from_i64(-j))))
293    }
294
295    /// `Res_z(P(z), z^n·Q(y/z))` for `P` and `Q` without the root `0`: its roots are the products of
296    /// a root of each.
297    fn product_poly(p: &KPoly, q: &KPoly) -> KPoly {
298        let n = q.degree().unwrap_or(0);
299        let degree = p.degree().unwrap_or(0) * n;
300        KPoly::interpolate(degree, |j| {
301            let j = Hyperreal::from_i64(j);
302            let mut c = vec![zero(); n + 1];
303            let mut power = Hyperreal::from_i64(1);
304            for i in 0..=n {
305                c[n - i] = q.coeff(i).mul(&power);
306                power = power.mul(&j);
307            }
308            p.resultant(&KPoly::from(c))
309        })
310    }
311
312    /// `Res_z(P(z), y − z^k)`: its roots are the `k`-th powers of the roots of `P`.
313    fn power_poly(p: &KPoly, k: u32) -> KPoly {
314        let degree = p.degree().unwrap_or(0);
315        KPoly::interpolate(degree, |j| {
316            let mut c = vec![zero(); k as usize + 1];
317            c[0] = Hyperreal::from_i64(j);
318            c[k as usize] = Hyperreal::from_i64(-1);
319            p.resultant(&KPoly::from(c))
320        })
321    }
322}
323
324// ── branches: the real roots by Newton's polygon ────────────────────────────────────────────
325
326/// One real root of a polynomial over `K_s`: `y = Σ terms + s^at·z`, where `z` is the one root of
327/// positive valuation of `rest` the branch follows — or `y` is exactly the sum of its terms.
328#[derive(Clone, Debug)]
329struct Branch {
330    /// `s^q = dt` for the branch's own `s`.
331    q: u32,
332    /// `(exponent in s, coefficient)`, increasing exponents, every coefficient nonzero.
333    terms: Vec<(i64, Algebraic)>,
334    at: i64,
335    rest: KPoly,
336    /// Whether the root is exactly the sum of its terms.
337    exact: bool,
338}
339
340impl Branch {
341    /// The same branch over `s'`, `s = s'^m`.
342    fn ramified(&self, m: u32) -> Branch {
343        Branch {
344            q: self.q * m,
345            terms: self.terms.iter().map(|(e, c)| (e * i64::from(m), c.clone())).collect(),
346            at: self.at * i64::from(m),
347            rest: self.rest.ramify(m),
348            exact: self.exact,
349        }
350    }
351
352    /// The exponent of a term in `dt`'s own units.
353    fn in_dt(&self, e: i64) -> Rational {
354        Rational::from_ratio_i64(e, i64::from(self.q)).expect("q is positive")
355    }
356
357    /// One more term of a separated branch.
358    fn extend(&mut self) {
359        if self.exact {
360            return;
361        }
362        let (c0, c1) = (self.rest.coeff(0), self.rest.coeff(1));
363        if c0.is_zero() {
364            self.exact = true;
365            return;
366        }
367        let v0 = c0.valuation().expect("nonzero");
368        let v1 = c1.valuation().expect("a separated branch's linear coefficient is nonzero");
369        let gamma = v0 - v1;
370        assert!(gamma > 0, "a separated branch's next term lies past its last");
371        let lead = c0
372            .leading_coefficient()
373            .expect("nonzero")
374            .neg()
375            .div(&c1.leading_coefficient().expect("nonzero"))
376            .expect("nonzero");
377        self.rest = descend(&self.rest, gamma, &lead);
378        self.at += gamma;
379        self.terms.push((self.at, lead));
380    }
381
382    /// Every term of exponent at most `e`, in `dt`'s units.
383    fn through(&mut self, e: &Rational) -> Series {
384        while !self.exact && self.in_dt(self.at) < *e {
385            self.extend();
386        }
387        self.terms
388            .iter()
389            .map(|(k, c)| (self.in_dt(*k), c.clone()))
390            .filter(|(k, _)| k <= e)
391            .collect()
392    }
393
394    /// The exponent of the last term found, in `dt`'s units; `None` for the zero root.
395    fn last(&self) -> Option<Rational> {
396        self.terms.last().map(|(k, _)| self.in_dt(*k))
397    }
398
399    /// The coefficient of `s^k`: every term through `s^at` is known, the rest of the root lying past it.
400    fn coefficient(&mut self, k: i64) -> Algebraic {
401        while !self.exact && self.at < k {
402            self.extend();
403        }
404        self.terms
405            .iter()
406            .find(|(e, _)| *e == k)
407            .map_or_else(|| Algebraic::from_i64(0), |(_, c)| c.clone())
408    }
409}
410
411/// `s^(-v)·P(s^γ·(c + z))`, `v` the least valuation of the coefficients of `P(s^γ·y)`.
412fn descend(p: &KPoly, gamma: i64, c: &Algebraic) -> KPoly {
413    let stretched = p.stretch(&power_of_s(gamma));
414    let v = stretched
415        .c
416        .iter()
417        .filter_map(Hyperreal::valuation)
418        .min()
419        .expect("a nonzero polynomial");
420    stretched.shift(&constant(c)).scale(&power_of_s(-v))
421}
422
423/// The lower convex hull of points of increasing first coordinate.
424fn lower_hull(points: &[(i64, i64)]) -> Vec<(i64, i64)> {
425    let mut hull: Vec<(i64, i64)> = Vec::new();
426    for &p in points {
427        while hull.len() >= 2 {
428            let (o, a) = (hull[hull.len() - 2], hull[hull.len() - 1]);
429            let cross = (a.0 - o.0) * (p.1 - o.1) - (a.1 - o.1) * (p.0 - o.0);
430            if cross <= 0 {
431                hull.pop();
432            } else {
433                break;
434            }
435        }
436        hull.push(p);
437    }
438    hull
439}
440
441/// The real roots of the squarefree `p` over `K_s`, `s^q = dt`, each followed until it separates.
442fn branches(p: &KPoly, q: u32) -> Vec<Branch> {
443    let root = Branch {
444        q,
445        terms: Vec::new(),
446        at: 0,
447        rest: p.clone(),
448        exact: false,
449    };
450    split(root, true)
451}
452
453/// The roots `z` of `b.rest` — of any valuation at the `top`, of positive valuation below it — each
454/// as a branch of `b`.
455fn split(b: Branch, top: bool) -> Vec<Branch> {
456    let mut out = Vec::new();
457    let mut rest = b.rest.clone();
458    if rest.coeff(0).is_zero() {
459        out.push(Branch {
460            exact: true,
461            ..b.clone()
462        });
463        rest = rest.without_zero_root();
464    }
465    let points: Vec<(i64, i64)> = rest
466        .c
467        .iter()
468        .enumerate()
469        .filter_map(|(i, c)| c.valuation().map(|v| (i as i64, v)))
470        .collect();
471    for edge in lower_hull(&points).windows(2) {
472        let ((i0, v0), (i1, v1)) = (edge[0], edge[1]);
473        let (num, den) = (v0 - v1, i1 - i0);
474        if !top && num <= 0 {
475            continue;
476        }
477        let g = gcd_i64(num, den).max(1);
478        let (a, d) = (num / g, den / g);
479        let phi: Vec<Algebraic> = (i0..=i1)
480            .map(|i| match points.iter().find(|(j, _)| *j == i) {
481                Some(&(_, v)) if (v - v0) * den == -num * (i - i0) => {
482                    rest.coeff(i as usize).leading_coefficient().expect("nonzero")
483                }
484                _ => Algebraic::from_i64(0),
485            })
486            .collect();
487        for (c, multiplicity) in Algebraic::real_roots(&phi) {
488            if c.is_zero() {
489                continue;
490            }
491            let mut child = Branch {
492                rest: rest.clone(),
493                exact: false,
494                ..b.clone()
495            };
496            if d > 1 {
497                child = child.ramified(d as u32);
498            }
499            child.rest = descend(&child.rest, a, &c);
500            child.at += a;
501            child.terms.push((child.at, c));
502            if multiplicity == 1 {
503                out.push(child);
504            } else {
505                out.extend(split(child, false));
506            }
507        }
508    }
509    out
510}
511
512// ── series with rational exponents ───────────────────────────────────────────────────────────
513
514/// A truncated Puiseux series: `(exponent in dt, coefficient)`, increasing exponents, every
515/// coefficient nonzero.
516type Series = Vec<(Rational, Algebraic)>;
517
518fn series_add(a: &Series, b: &Series) -> Series {
519    let mut out: Series = Vec::new();
520    let (mut i, mut j) = (0, 0);
521    while i < a.len() || j < b.len() {
522        let take_a = j >= b.len() || (i < a.len() && a[i].0 < b[j].0);
523        let take_b = i >= a.len() || (j < b.len() && b[j].0 < a[i].0);
524        if take_a {
525            out.push(a[i].clone());
526            i += 1;
527        } else if take_b {
528            out.push(b[j].clone());
529            j += 1;
530        } else {
531            let c = a[i].1.add(&b[j].1);
532            if !c.is_zero() {
533                out.push((a[i].0.clone(), c));
534            }
535            i += 1;
536            j += 1;
537        }
538    }
539    out
540}
541
542fn series_neg(a: &Series) -> Series {
543    a.iter().map(|(e, c)| (e.clone(), c.neg())).collect()
544}
545
546/// The product's terms of exponent at most `e`.
547fn series_mul(a: &Series, b: &Series, e: &Rational) -> Series {
548    let mut out: Series = Vec::new();
549    for (ea, ca) in a {
550        for (eb, cb) in b {
551            let k = ea.add(eb);
552            if k > *e {
553                continue;
554            }
555            out = series_add(&out, &vec![(k, ca.mul(cb))]);
556        }
557    }
558    out
559}
560
561/// `Σ_k w_k·u^k` through exponent `e`, for `u` of positive exponents and `w_k` the weights `weight`
562/// gives in turn.
563fn series_compose(u: &Series, e: &Rational, mut weight: impl FnMut(usize) -> Algebraic) -> Series {
564    let mut out: Series = Vec::new();
565    let mut power: Series = vec![(Rational::zero(), Algebraic::from_i64(1))];
566    let mut k = 0;
567    while !power.is_empty() {
568        let w = weight(k);
569        if !w.is_zero() {
570            out = series_add(&out, &power.iter().map(|(x, c)| (x.clone(), c.mul(&w))).collect());
571        }
572        power = series_mul(&power, u, e);
573        k += 1;
574    }
575    out
576}
577
578/// `a = c·dt^v·(1 + u)`: the leading term and `u`, every term `u` needs for `a` through exponent `e`.
579fn series_split(a: &Series) -> (Rational, Algebraic, Series) {
580    let (v, c) = a[0].clone();
581    let inv = c.recip().expect("a leading coefficient is nonzero");
582    let u = a[1..].iter().map(|(x, k)| (x.sub(&v), k.mul(&inv))).collect();
583    (v, c, u)
584}
585
586fn shift_series(a: &Series, by: &Rational, scale: &Algebraic) -> Series {
587    a.iter().map(|(x, c)| (x.add(by), c.mul(scale))).collect()
588}
589
590/// `1/a` through exponent `e`, `a` nonzero and known through `e + 2·v(a)`.
591fn series_recip(a: &Series, e: &Rational) -> Series {
592    let (v, c, u) = series_split(a);
593    let reach = e.add(&v);
594    let minus_u = series_neg(&u);
595    let body = series_compose(&minus_u, &reach, |_| Algebraic::from_i64(1));
596    shift_series(&body, &v.negated(), &c.recip().expect("nonzero"))
597}
598
599/// The real `n`-th root of `a` through exponent `e` — the positive one for even `n` — or the reason
600/// there is none.
601fn series_root(a: &Series, n: u32, e: &Rational) -> Result<Series, String> {
602    let (v, c, u) = series_split(a);
603    let lead = c.nth_root(n).ok_or_else(|| negative_root(n))?;
604    let order = v.div(&Rational::from_i64(i64::from(n))).expect("n is positive");
605    let reach = e.sub(&order);
606    let alpha = Rational::from_ratio_i64(1, i64::from(n)).expect("n is positive");
607    let mut binomial = Rational::one();
608    let body = series_compose(&u, &reach, |k| {
609        if k > 0 {
610            let k = Rational::from_i64(k as i64);
611            binomial = binomial.mul(&alpha.sub(&k.sub(&Rational::one()))).div(&k).expect("k ≥ 1");
612        }
613        Algebraic::from_rational(binomial.clone())
614    });
615    Ok(shift_series(&body, &order, &lead))
616}
617
618// ── the values ───────────────────────────────────────────────────────────────────────────────
619
620/// A real algebraic Puiseux series in `dt`: a root of `poly` (squarefree, over `K_s`, `s^q = dt`),
621/// picked by its branch. See the module documentation.
622#[derive(Clone)]
623pub struct Puiseux {
624    poly: KPoly,
625    q: u32,
626    /// The rational function of `s` the value is, when `poly` has degree one.
627    function: Option<Hyperreal>,
628    /// The branch the value follows. A rational function `h` of a root of `dt` is the one root of
629    /// `y − h` and answers its terms from `h`, so its branch is followed only when an operation with a
630    /// value that is no such function asks for it.
631    picked: Arc<OnceLock<Picked>>,
632}
633
634/// A branch and the order at which every real root of its polynomial differs from every other, in
635/// `dt`'s units.
636struct Picked {
637    branch: Mutex<Branch>,
638    separation: Rational,
639}
640
641/// Why a power of a value of the real closure is refused: the hyperreal's own refusal, its degree
642/// counted in the root `dt^(1/q)` the value is a rational function of.
643#[derive(Clone, Debug, PartialEq, Eq)]
644pub struct PowerRefusal {
645    pub error: PowerError,
646    pub q: u32,
647}
648
649impl PowerRefusal {
650    /// The refusal, the infinitesimal written `name`: `the power would have degree 5000 in √dt; …`.
651    pub fn describe(&self, name: &str) -> String {
652        self.error.describe(&power_text(name, &ratio(1, self.q)))
653    }
654}
655
656/// A value of the real closure in its one form: a hyperreal when it is a rational function of `dt`,
657/// otherwise a Puiseux series that is no such function.
658#[derive(Clone, Debug)]
659pub enum Closed {
660    Field(Hyperreal),
661    Series(Puiseux),
662}
663
664/// How far a polynomial's branches must be followed to tell them all apart.
665fn separation_of(bs: &[Branch]) -> Rational {
666    bs.iter().filter_map(Branch::last).max().unwrap_or_else(Rational::zero)
667}
668
669impl Puiseux {
670    /// The root of `poly` over `K_s` whose expansion agrees with `target` through the order that
671    /// tells `poly`'s real roots apart.
672    fn select(poly: KPoly, q: u32, target: &dyn Fn(&Rational) -> Series) -> Puiseux {
673        let poly = poly.squarefree();
674        let bs = branches(&poly, q);
675        let separation = separation_of(&bs);
676        let want = target(&separation);
677        let chosen = bs
678            .into_iter()
679            .find_map(|mut b| (b.through(&separation) == want).then_some(b))
680            .expect("the operation's value is a real root of its polynomial");
681        Puiseux::picked_as(poly, q, chosen, separation)
682    }
683
684    fn picked_as(poly: KPoly, q: u32, branch: Branch, separation: Rational) -> Puiseux {
685        let function = (poly.degree() == Some(1))
686            .then(|| poly.coeff(0).neg().div(&poly.coeff(1)).expect("degree one"));
687        Puiseux {
688            poly,
689            q,
690            function,
691            picked: Arc::new(OnceLock::from(Picked {
692                branch: Mutex::new(branch),
693                separation,
694            })),
695        }
696    }
697
698    /// The hyperreal `h` (a rational function of `s`, `s^q = dt`) as the root of `y − h`.
699    fn of_field(h: &Hyperreal, q: u32) -> Puiseux {
700        Puiseux {
701            poly: KPoly::linear(h),
702            q,
703            function: Some(h.clone()),
704            picked: Arc::new(OnceLock::new()),
705        }
706    }
707
708    /// The branch, followed now when the value was made as a rational function of a root of `dt`.
709    fn picked(&self) -> &Picked {
710        self.picked.get_or_init(|| {
711            assert_eq!(self.poly.degree(), Some(1), "only the root of `y − h` is picked when first asked for");
712            let bs = branches(&self.poly, self.q);
713            let separation = separation_of(&bs);
714            let branch = bs.into_iter().next().expect("y − h has its root");
715            Picked {
716                branch: Mutex::new(branch),
717                separation,
718            }
719        })
720    }
721
722    fn branch(&self) -> MutexGuard<'_, Branch> {
723        self.picked().branch.lock().expect("unpoisoned")
724    }
725
726    /// The order at which every real root of `poly` differs from every other: for the root of
727    /// `y − h`, the order of its leading term.
728    fn separation(&self) -> Rational {
729        match self.function {
730            Some(_) => self.leading().map_or_else(Rational::zero, |(v, _)| v),
731            None => self.picked().separation.clone(),
732        }
733    }
734
735    /// Every term of exponent at most `e`, in `dt`'s units.
736    fn through(&self, e: &Rational) -> Series {
737        let (Some(h), q) = (&self.function, self.q) else {
738            return self.branch().through(e);
739        };
740        let Some(v) = h.valuation() else {
741            return Vec::new();
742        };
743        let top = e.mul(&Rational::from_i64(i64::from(q))).floor().to_i64().expect("a small order");
744        (v..=top)
745            .filter_map(|k| {
746                let c = h.laurent_coefficient(k);
747                (!c.is_zero()).then(|| (Rational::from_ratio_i64(k, i64::from(q)).expect("q is positive"), c))
748            })
749            .collect()
750    }
751
752    /// The `q` whose root of `dt` the expansion is a series in: every exponent is a multiple of `1/q`.
753    /// The root of `y − h` follows `h`'s own root of `dt`; a fractional slope of Newton's polygon
754    /// refines it only for a polynomial of higher degree.
755    pub fn ramification(&self) -> u32 {
756        match self.function {
757            Some(_) => self.q,
758            None => self.branch().q,
759        }
760    }
761
762    /// The coefficient of `dt^(k/q)`, `q` the [`Puiseux::ramification`].
763    pub fn coefficient(&self, k: i64) -> Algebraic {
764        match &self.function {
765            Some(h) => h.laurent_coefficient(k),
766            None => self.branch().coefficient(k),
767        }
768    }
769
770    /// The polynomial the value is a root of, its coefficients rational functions of `dt^(1/q)`, with
771    /// that `q`.
772    pub(crate) fn annihilator(&self) -> (Vec<Hyperreal>, u32) {
773        (self.poly.c.clone(), self.q)
774    }
775
776    /// The rational function of `dt^(1/q)` the value is, with that `q`, when it is one: a value in
777    /// its one form ([`Closed`]) that is one is held as the root of `y − h`.
778    pub fn rational_function(&self) -> Option<(Hyperreal, u32)> {
779        self.function.clone().map(|h| (h, self.q))
780    }
781
782    /// The exponent of the leading term in units of `dt^(1/q)`, `q` the [`Puiseux::ramification`].
783    pub fn order(&self) -> i64 {
784        match &self.function {
785            Some(h) => h.valuation().expect("a series value is nonzero"),
786            None => self.branch().terms.first().expect("a series value is nonzero").0,
787        }
788    }
789
790    /// The first term; `None` for zero.
791    fn leading(&self) -> Option<(Rational, Algebraic)> {
792        if let Some(h) = &self.function {
793            let v = h.valuation()?;
794            let c = h.leading_coefficient()?;
795            return Some((Rational::from_ratio_i64(v, i64::from(self.q)).expect("q is positive"), c));
796        }
797        let b = self.branch();
798        b.terms.first().map(|(k, c)| (b.in_dt(*k), c.clone()))
799    }
800
801    fn signum(&self) -> i32 {
802        self.leading().map_or(0, |(_, c)| c.signum())
803    }
804
805    /// The polynomial over `K_{s'}`, `s' = dt^(1/q')` for `q'` a multiple of `q`.
806    fn poly_over(&self, q: u32) -> KPoly {
807        self.poly.ramify(q / self.q)
808    }
809
810    fn plus(&self, o: &Puiseux) -> Puiseux {
811        let q = lcm_u32(self.q, o.q);
812        let poly = KPoly::sum_poly(&self.poly_over(q), &o.poly_over(q));
813        Puiseux::select(poly, q, &|e| series_add(&self.through(e), &o.through(e)))
814    }
815
816    /// `−α` is a root of `P(−y)` whose branch is `α`'s with every sign turned.
817    fn negated(&self) -> Puiseux {
818        if let Some(h) = &self.function {
819            return Puiseux::of_field(&h.neg(), self.q);
820        }
821        let b = self.branch().clone();
822        let branch = Branch {
823            terms: b.terms.iter().map(|(k, c)| (*k, c.neg())).collect(),
824            rest: b.rest.neg_var(),
825            ..b
826        };
827        Puiseux::picked_as(self.poly.neg_var(), self.q, branch, self.separation())
828    }
829
830    /// The product of two nonzero values.
831    fn times(&self, o: &Puiseux) -> Puiseux {
832        let q = lcm_u32(self.q, o.q);
833        let (va, _) = self.leading().expect("nonzero");
834        let (vb, _) = o.leading().expect("nonzero");
835        let poly = KPoly::product_poly(&self.poly_over(q).without_zero_root(), &o.poly_over(q).without_zero_root());
836        Puiseux::select(poly, q, &|e| series_mul(&self.through(&e.sub(&vb)), &o.through(&e.sub(&va)), e))
837    }
838
839    /// The reciprocal of a nonzero value: a root of `P` reversed.
840    fn inverse(&self) -> Puiseux {
841        let (v, _) = self.leading().expect("nonzero");
842        let mut reversed = self.poly.without_zero_root().c;
843        reversed.reverse();
844        let target = |e: &Rational| series_recip(&self.through(&e.add(&v).add(&v)), e);
845        Puiseux::select(KPoly::from(reversed), self.q, &target)
846    }
847
848    /// `α^k` for a nonzero `α` and `k ≥ 1`. Each partial product is cut at the order the factors still
849    /// to come could lower it to, `v` apiece, so a value of negative valuation loses no term it needs.
850    fn power(&self, k: u32) -> Puiseux {
851        let (v, _) = self.leading().expect("nonzero");
852        let target = |e: &Rational| {
853            let base = self.through(&e.sub(&v.mul(&Rational::from_i64(i64::from(k) - 1))));
854            let mut acc: Series = vec![(Rational::zero(), Algebraic::from_i64(1))];
855            for done in 1..=k {
856                let to_come = Rational::from_i64(i64::from(k - done));
857                acc = series_mul(&acc, &base, &e.sub(&v.mul(&to_come)));
858            }
859            acc
860        };
861        Puiseux::select(KPoly::power_poly(&self.poly, k), self.q, &target)
862    }
863
864    /// The real `n`-th root of a nonzero `α` — the positive one for even `n` — or the reason there
865    /// is none.
866    fn nth_root(&self, n: u32) -> Result<Puiseux, String> {
867        let (v, c) = self.leading().expect("nonzero");
868        if n % 2 == 0 && c.signum() < 0 {
869            return Err(negative_root(n));
870        }
871        let order = v.div(&Rational::from_i64(i64::from(n))).expect("n is positive");
872        let target = |e: &Rational| {
873            series_root(&self.through(&e.sub(&order).add(&v)), n, e).expect("the sign was checked")
874        };
875        Ok(Puiseux::select(self.poly.spread(n), self.q, &target))
876    }
877
878    /// The value as the rational function of a root of `dt` it is, when it is one, with that root's
879    /// `q`. A branch that ends is the sum of its terms; otherwise the candidate is the Padé
880    /// approximant of the expansion in the branch's own root of `dt`, within the degrees Gauss's
881    /// lemma bounds a root of the monic `poly` by — its denominator dividing the least common multiple
882    /// `L` of the coefficients' denominators, its numerator `L·p₀` — kept only when it is a root of
883    /// `poly` that agrees with the value through `separation`.
884    fn field_value(&self) -> Option<(Hyperreal, u32)> {
885        if let Some(h) = &self.function {
886            return Some((h.clone(), self.q));
887        }
888        let (qb, exact, terms) = {
889            let b = self.branch();
890            (b.q, b.exact, b.terms.clone())
891        };
892        if exact {
893            let sum = terms.iter().fold(zero(), |acc, (k, c)| acc.add(&Hyperreal::monomial(c.clone(), *k)));
894            return Some((sum, qb));
895        }
896        let poly = self.poly_over(qb).monic();
897        let degrees: Vec<(usize, usize)> = poly.c.iter().map(Hyperreal::degrees).collect();
898        let lcm_bound: usize = degrees.iter().map(|(_, d)| d).sum();
899        let (n0, d0) = degrees[0];
900        let num_bound = (lcm_bound + n0).checked_sub(d0)?;
901        let q = Rational::from_i64(i64::from(qb));
902        let (v, _) = self.leading()?;
903        let v_s = v.mul(&q);
904        let v_s = v_s.to_bigint().filter(|_| v_s.is_integer())?.to_i64()?;
905        let (l, m) = if v_s >= 0 {
906            (num_bound.checked_sub(usize::try_from(v_s).ok()?)?, lcm_bound)
907        } else {
908            (num_bound, lcm_bound.checked_sub(usize::try_from(-v_s).ok()?)?)
909        };
910        let reach = Rational::from_i64(v_s + (l + m) as i64).div(&q).expect("q is positive");
911        let mut series = vec![Algebraic::from_i64(0); l + m + 1];
912        for (e, c) in self.through(&reach) {
913            let e_s = e.mul(&q);
914            if !e_s.is_integer() {
915                return None;
916            }
917            let k = e_s.to_bigint()?.to_i64()? - v_s;
918            series[usize::try_from(k).ok()?] = c;
919        }
920        let candidate = Hyperreal::pade(&series, l, m)?.mul(&power_of_s(v_s));
921        if !poly.eval(&candidate).is_zero() {
922            return None;
923        }
924        let check = Puiseux::of_field(&candidate, qb);
925        let separation = self.separation();
926        (check.through(&separation) == self.through(&separation)).then_some((candidate, qb))
927    }
928
929    /// The value in its one form.
930    pub(crate) fn canonical(self) -> Closed {
931        match self.field_value() {
932            Some((h, q)) => Closed::of_field(h, q),
933            None => Closed::Series(self),
934        }
935    }
936
937    /// The rational function of `s` with `s`'s powers written in `dt`, a radical, or the leading
938    /// terms. See the module documentation.
939    fn render(&self, name: &str) -> String {
940        let q = self.q;
941        if let Some(h) = &self.function {
942            return h.render_powers(&|k| power_text(name, &ratio(k, q)));
943        }
944        if let Some(text) = self.radical(name) {
945            return text;
946        }
947        let mut b = self.branch();
948        while b.terms.len() < 3 && !b.exact {
949            b.extend();
950        }
951        let terms: Vec<(Rational, Algebraic)> = b.terms.iter().map(|(k, c)| (b.in_dt(*k), c.clone())).collect();
952        drop(b);
953        let mut out = String::new();
954        for (i, (e, c)) in terms.iter().take(2).enumerate() {
955            let t = term_text(name, c, e);
956            match (i, t.strip_prefix('-')) {
957                (0, _) => out.push_str(&t),
958                (_, Some(rest)) => {
959                    out.push_str(" - ");
960                    out.push_str(rest);
961                }
962                (_, None) => {
963                    out.push_str(" + ");
964                    out.push_str(&t);
965                }
966            }
967        }
968        if let Some((next, _)) = terms.get(2) {
969            out.push_str(&format!(" + {}", remainder(name, &remainder_order(&terms[..2], next))));
970        }
971        out
972    }
973
974    /// `F·ⁿ√κ'` for the least `n` whose power of the value is a rational function `κ` of a root of
975    /// `dt`: the `n`-th powers of `κ`'s polynomial factors and a constant that is an `n`-th power taken
976    /// out into `F` ([`Hyperreal::root_split`]). A value whose power has a constant numerator prints as
977    /// one over the radical of the reciprocal: `1/∛(3 + dt)`.
978    fn radical(&self, name: &str) -> Option<String> {
979        let degree = self.poly.degree().unwrap_or(0) as u32;
980        let negative = self.signum() < 0;
981        for n in 2..=degree.max(2) {
982            let Some((kappa, q)) = self.power(n).field_value() else {
983                continue;
984            };
985            let powers = |k: usize| power_text(name, &ratio(k, q));
986            let (kappa, inverted) = if kappa.numerator_is_constant() && kappa.degrees().1 > 0 {
987                (kappa.recip()?, true)
988            } else {
989                (kappa, false)
990            };
991            let (outside, inside) = kappa.root_split(n)?;
992            let outside = if n % 2 == 0 && negative { outside.neg() } else { outside };
993            let radicand = inside.render_powers(&powers);
994            let radicand = if radicand.contains([' ', '/', '·', '^']) {
995                format!("({radicand})")
996            } else {
997                radicand
998            };
999            let sign = radical_sign(n);
1000            let factor = outside.render_powers(&powers);
1001            let (minus, factor) = match factor.strip_prefix('-') {
1002                Some(rest) if !rest.contains(' ') => ("-", rest.to_string()),
1003                _ => ("", factor),
1004            };
1005            let factor = if factor.contains([' ', '/']) { format!("({factor})") } else { factor };
1006            let root = if factor == "1" {
1007                format!("{sign}{radicand}")
1008            } else {
1009                format!("{factor}·{sign}{radicand}")
1010            };
1011            return Some(if inverted {
1012                let root = if root.contains('·') { format!("({root})") } else { root };
1013                format!("{minus}1/{root}")
1014            } else {
1015                format!("{minus}{root}")
1016            });
1017        }
1018        None
1019    }
1020}
1021
1022/// The order the rest of an expansion is written `O(·)` of, after the terms `shown`: the order after
1023/// the last one on the grid their exponents lie on, as a germ's rest is written — or the next term's
1024/// own order when that comes sooner, the expansion running on a finer grid. Both are the value's own,
1025/// so the bound reads the same whatever route reached the value, and it always holds.
1026fn remainder_order(shown: &[(Rational, Algebraic)], next: &Rational) -> Rational {
1027    let exponents: Vec<Rational> = shown.iter().map(|(e, _)| e.clone()).collect();
1028    remainder_after(&exponents, next)
1029}
1030
1031/// [`remainder_order`] for the exponents of the terms shown, whatever their coefficients.
1032pub(crate) fn remainder_after(shown: &[Rational], next: &Rational) -> Rational {
1033    let grid = shown
1034        .iter()
1035        .map(|e| e.denominator().to_i64().expect("a small denominator"))
1036        .fold(1i64, |l, d| l / gcd_i64(l, d) * d);
1037    let after = shown
1038        .last()
1039        .map(|e| e.add(&Rational::from_ratio_i64(1, grid).expect("grid > 0")))
1040        .expect("terms are shown");
1041    if *next < after {
1042        next.clone()
1043    } else {
1044        after
1045    }
1046}
1047
1048/// `k/q` as a rational.
1049fn ratio(k: usize, q: u32) -> Rational {
1050    Rational::from_ratio_i64(k as i64, i64::from(q)).expect("q is positive")
1051}
1052
1053impl Closed {
1054    /// A hyperreal of the rational functions of `dt`.
1055    pub fn from_hyperreal(h: Hyperreal) -> Closed {
1056        Closed::Field(h)
1057    }
1058
1059    /// The rational function `h` of `s = dt^(1/q)` in its one form: of the least root of `dt` it is
1060    /// a function of, and a hyperreal when that is `dt` itself.
1061    pub(crate) fn of_field(h: Hyperreal, q: u32) -> Closed {
1062        let mut h = h;
1063        let mut q = q;
1064        for p in 2..=q {
1065            while q % p == 0 {
1066                match h.unramify(p) {
1067                    Some(r) => {
1068                        h = r;
1069                        q /= p;
1070                    }
1071                    None => break,
1072                }
1073            }
1074        }
1075        if q == 1 {
1076            Closed::Field(h)
1077        } else {
1078            Closed::Series(Puiseux::of_field(&h, q))
1079        }
1080    }
1081
1082    /// The standard number the value is, when it has no infinitesimal part.
1083    pub fn as_standard(&self) -> Option<Algebraic> {
1084        match self {
1085            Closed::Field(h) => h.as_standard(),
1086            Closed::Series(_) => None,
1087        }
1088    }
1089
1090    /// The exponent of the leading term in `dt` and its coefficient; `None` for zero.
1091    pub fn leading(&self) -> Option<(Rational, Algebraic)> {
1092        match self {
1093            Closed::Field(h) => Some((Rational::from_i64(h.valuation()?), h.leading_coefficient()?)),
1094            Closed::Series(p) => p.leading(),
1095        }
1096    }
1097
1098    /// The value as the rational function of `dt^(1/q)` it is, with that `q`, when it is one.
1099    pub(crate) fn field(&self) -> Option<(Hyperreal, u32)> {
1100        match self {
1101            Closed::Field(h) => Some((h.clone(), 1)),
1102            Closed::Series(p) => p.rational_function(),
1103        }
1104    }
1105
1106    /// Both values as rational functions of the least root of `dt` both are functions of, when both
1107    /// are such functions: their arithmetic is then that field's own, with no resultant.
1108    fn on_one_grid(&self, o: &Closed) -> Option<(Hyperreal, Hyperreal, u32)> {
1109        let (a, qa) = self.field()?;
1110        let (b, qb) = o.field()?;
1111        let q = lcm_u32(qa, qb);
1112        Some((a.ramify(q / qa), b.ramify(q / qb), q))
1113    }
1114
1115    /// The value as a series over `K_s`.
1116    fn series(&self) -> Puiseux {
1117        match self {
1118            Closed::Field(h) => Puiseux::of_field(h, 1),
1119            Closed::Series(p) => p.clone(),
1120        }
1121    }
1122
1123    pub fn is_zero(&self) -> bool {
1124        matches!(self, Closed::Field(h) if h.is_zero())
1125    }
1126
1127    pub fn add(&self, o: &Closed) -> Closed {
1128        if let Some((a, b, q)) = self.on_one_grid(o) {
1129            return Closed::of_field(a.add(&b), q);
1130        }
1131        self.series().plus(&o.series()).canonical()
1132    }
1133
1134    pub fn neg(&self) -> Closed {
1135        match self {
1136            Closed::Field(h) => Closed::Field(h.neg()),
1137            Closed::Series(p) => Closed::Series(p.negated()),
1138        }
1139    }
1140
1141    pub fn sub(&self, o: &Closed) -> Closed {
1142        self.add(&o.neg())
1143    }
1144
1145    pub fn mul(&self, o: &Closed) -> Closed {
1146        if let Some((a, b, q)) = self.on_one_grid(o) {
1147            return Closed::of_field(a.mul(&b), q);
1148        }
1149        if self.is_zero() || o.is_zero() {
1150            return Closed::Field(zero());
1151        }
1152        self.series().times(&o.series()).canonical()
1153    }
1154
1155    /// The reciprocal; `None` for zero. The reciprocal of a value that is no rational function of a
1156    /// root of `dt` is none either.
1157    pub fn recip(&self) -> Option<Closed> {
1158        match self {
1159            Closed::Field(h) => h.recip().map(Closed::Field),
1160            Closed::Series(p) if p.poly.degree() == Some(1) => p.clone().canonical_recip(),
1161            Closed::Series(p) => Some(Closed::Series(p.inverse())),
1162        }
1163    }
1164
1165    pub fn div(&self, o: &Closed) -> Option<Closed> {
1166        Some(self.mul(&o.recip()?))
1167    }
1168
1169    /// `self^k`; refused for zero to a negative power and, on a rational function of a root of
1170    /// `dt`, for a power past [`crate::hyperreal::MAX_POWER_DEGREE`] in that root.
1171    pub fn pow(&self, k: i32) -> Result<Closed, PowerRefusal> {
1172        if k < 0 {
1173            let inverse = self.recip().ok_or(PowerRefusal {
1174                error: PowerError::ZeroToNegative,
1175                q: 1,
1176            })?;
1177            return inverse.pow(-k);
1178        }
1179        if k == 0 {
1180            return Ok(Closed::Field(Hyperreal::from_i64(1)));
1181        }
1182        if let Some((h, q)) = self.field() {
1183            let power = h.pow(k).map_err(|error| PowerRefusal { error, q })?;
1184            return Ok(Closed::of_field(power, q));
1185        }
1186        match self {
1187            Closed::Field(_) => unreachable!("a hyperreal is a rational function of dt"),
1188            Closed::Series(p) => Ok(p.power(k as u32).canonical()),
1189        }
1190    }
1191
1192    /// The real `n`-th root — the positive one for even `n` — or the reason there is none.
1193    pub fn root(&self, n: u32) -> Result<Closed, String> {
1194        if n == 1 || self.is_zero() {
1195            return Ok(self.clone());
1196        }
1197        Ok(self.series().nth_root(n)?.canonical())
1198    }
1199
1200    /// `−1`, `0` or `1`: the sign of the leading coefficient.
1201    pub fn signum(&self) -> i32 {
1202        match self {
1203            Closed::Field(h) => h.signum(),
1204            Closed::Series(p) => p.signum(),
1205        }
1206    }
1207
1208    /// The order. The terms both expansions hold through the orders that separate their own
1209    /// polynomials' roots decide it at the first place they differ; expansions that agree that far are
1210    /// ordered by the sign of the difference, which is the zero root of its polynomial exactly when the
1211    /// two are equal.
1212    pub fn compare(&self, o: &Closed) -> Ordering {
1213        if let Some((a, b, _)) = self.on_one_grid(o) {
1214            return a.cmp(&b);
1215        }
1216        let (a, b) = (self.series(), o.series());
1217        let depth = a.separation().max(b.separation());
1218        let differ = series_add(&a.through(&depth), &series_neg(&b.through(&depth)));
1219        if let Some((_, c)) = differ.first() {
1220            return if c.signum() < 0 { Ordering::Less } else { Ordering::Greater };
1221        }
1222        match a.plus(&b.negated()).signum() {
1223            -1 => Ordering::Less,
1224            0 => Ordering::Equal,
1225            _ => Ordering::Greater,
1226        }
1227    }
1228
1229    /// The exponent of the leading term in `dt`; `None` for zero.
1230    pub fn valuation(&self) -> Option<Rational> {
1231        match self {
1232            Closed::Field(h) => h.valuation().map(Rational::from_i64),
1233            Closed::Series(p) => p.leading().map(|(v, _)| v),
1234        }
1235    }
1236
1237    pub fn is_infinitesimal(&self) -> bool {
1238        self.valuation().is_none_or(|v| v.is_positive())
1239    }
1240
1241    pub fn is_limited(&self) -> bool {
1242        self.valuation().is_none_or(|v| !v.is_negative())
1243    }
1244
1245    /// The standard real infinitely close to a limited value; `None` for an infinite one.
1246    pub fn standard_part(&self) -> Option<Algebraic> {
1247        match self {
1248            Closed::Field(h) => h.standard_part(),
1249            Closed::Series(p) => {
1250                let (v, c) = p.leading().expect("a series value is nonzero");
1251                if v.is_positive() {
1252                    Some(Algebraic::from_i64(0))
1253                } else if v.is_zero() {
1254                    Some(c)
1255                } else {
1256                    None
1257                }
1258            }
1259        }
1260    }
1261
1262    /// The value with the infinitesimal written `name`.
1263    pub fn render(&self, name: &str) -> String {
1264        match self {
1265            Closed::Field(h) => h.render(name),
1266            Closed::Series(p) => p.render(name),
1267        }
1268    }
1269}
1270
1271impl Puiseux {
1272    /// The reciprocal of a rational function of a root of `dt`, in its one form.
1273    fn canonical_recip(self) -> Option<Closed> {
1274        let (h, q) = self.field_value()?;
1275        Some(Closed::of_field(h.recip()?, q))
1276    }
1277}
1278
1279fn radical_sign(n: u32) -> String {
1280    match n {
1281        2 => "√".to_string(),
1282        3 => "∛".to_string(),
1283        4 => "∜".to_string(),
1284        n => {
1285            let digits: String = n
1286                .to_string()
1287                .chars()
1288                .map(|d| ['⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'][d.to_digit(10).expect("a digit") as usize])
1289                .collect();
1290            format!("{digits}√")
1291        }
1292    }
1293}
1294
1295/// `dt^e` written: `dt`, `dt^2`, `√dt`, `∛dt`, `dt^(3/2)`.
1296pub(crate) fn power_text(name: &str, e: &Rational) -> String {
1297    let one = Rational::one();
1298    if *e == one {
1299        return name.to_string();
1300    }
1301    if e.is_integer() {
1302        return format!("{name}^{e}");
1303    }
1304    let half = Rational::from_ratio_i64(1, 2).expect("2 > 0");
1305    let third = Rational::from_ratio_i64(1, 3).expect("3 > 0");
1306    if *e == half {
1307        return format!("√{name}");
1308    }
1309    if *e == third {
1310        return format!("∛{name}");
1311    }
1312    format!("{name}^({e})")
1313}
1314
1315/// What is left of an expansion from `dt^e` on: `O(dt^2)`, `O(√dt)`, and `O(1)` from the standard order.
1316pub(crate) fn remainder(name: &str, e: &Rational) -> String {
1317    if e.is_zero() {
1318        "O(1)".to_string()
1319    } else {
1320        format!("O({})", power_text(name, e))
1321    }
1322}
1323
1324fn term_text(name: &str, c: &Algebraic, e: &Rational) -> String {
1325    expansion_term(name, &c.to_string(), e)
1326}
1327
1328/// One term `c·dt^e` of an expansion, its coefficient printed `text`: `dt`, `2·√dt`, `-(1/2)·dt^2`,
1329/// `e^2·dt`, `3/dt`. A coefficient that is a sum or a quotient is parenthesized, and the sign of one
1330/// that is not a sum stands outside.
1331pub(crate) fn expansion_term(name: &str, text: &str, e: &Rational) -> String {
1332    if e.is_zero() {
1333        return text.to_string();
1334    }
1335    let (sign, magnitude) = match text.strip_prefix('-') {
1336        Some(m) if !top_level(m, &[' ']) => ("-", m),
1337        _ => ("", text),
1338    };
1339    let magnitude = if top_level(magnitude, &[' ', '/']) {
1340        format!("({magnitude})")
1341    } else {
1342        magnitude.to_string()
1343    };
1344    if e.is_negative() {
1345        return format!("{sign}{magnitude}/{}", power_text(name, &e.negated()));
1346    }
1347    let power = power_text(name, e);
1348    if magnitude == "1" {
1349        format!("{sign}{power}")
1350    } else {
1351        format!("{sign}{magnitude}·{power}")
1352    }
1353}
1354
1355/// Whether `text` holds one of `marks` outside every parenthesis.
1356pub(crate) fn top_level(text: &str, marks: &[char]) -> bool {
1357    let mut depth = 0i32;
1358    for ch in text.chars() {
1359        match ch {
1360            '(' => depth += 1,
1361            ')' => depth -= 1,
1362            ch if depth == 0 && marks.contains(&ch) => return true,
1363            _ => {}
1364        }
1365    }
1366    false
1367}
1368
1369impl fmt::Debug for Puiseux {
1370    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1371        write!(f, "Puiseux {{ q: {}, degree: {:?}, leading: {:?} }}", self.q, self.poly.degree(), self.leading())
1372    }
1373}
1374
1375impl PartialEq for Closed {
1376    fn eq(&self, o: &Self) -> bool {
1377        match (self, o) {
1378            (Closed::Field(a), Closed::Field(b)) => a == b,
1379            (Closed::Series(_), Closed::Series(_)) => self.compare(o) == Ordering::Equal,
1380            _ => false,
1381        }
1382    }
1383}
1384
1385impl Eq for Closed {}
1386
1387impl Hash for Closed {
1388    /// Equal values have one form, and two equal series one expansion: the field value itself, or a
1389    /// series by its leading term.
1390    fn hash<H: Hasher>(&self, state: &mut H) {
1391        match self {
1392            Closed::Field(h) => {
1393                0u8.hash(state);
1394                h.hash(state);
1395            }
1396            Closed::Series(p) => {
1397                1u8.hash(state);
1398                let (e, c) = p.leading().expect("a series value is nonzero");
1399                e.hash(state);
1400                c.hash(state);
1401            }
1402        }
1403    }
1404}
1405
1406impl crate::exact_codec::ExactCodec for Puiseux {
1407    /// `q`, the polynomial's coefficients lowest power first, and the root's terms through the order
1408    /// that tells the polynomial's roots apart, from which a reader picks the same root.
1409    fn write(&self, out: &mut Vec<u8>) {
1410        (u64::from(self.q)).write(out);
1411        crate::exact_codec::write_list(&self.poly.c, out);
1412        let terms = self.through(&self.separation());
1413        crate::exact_codec::write_len(terms.len(), out);
1414        for (e, c) in &terms {
1415            e.write(out);
1416            c.write(out);
1417        }
1418    }
1419
1420    fn read(buf: &[u8], pos: &mut usize) -> Option<Self> {
1421        use crate::exact_codec::{read_len, MAX_WIRE_DEGREE};
1422        let q = u32::try_from(u64::read(buf, pos)?).ok().filter(|q| *q > 0)?;
1423        let poly = KPoly::from(crate::exact_codec::read_list(buf, pos)?);
1424        if poly.degree().unwrap_or(0) == 0 || poly.squarefree().degree() != poly.degree() {
1425            return None;
1426        }
1427        let count = read_len(buf, pos, MAX_WIRE_DEGREE)?;
1428        let mut want: Series = Vec::with_capacity(count);
1429        for _ in 0..count {
1430            want.push((Rational::read(buf, pos)?, Algebraic::read(buf, pos)?));
1431        }
1432        let bs = branches(&poly, q);
1433        let separation = separation_of(&bs);
1434        let chosen = bs.into_iter().find_map(|mut b| (b.through(&separation) == want).then_some(b))?;
1435        Some(Puiseux::picked_as(poly, q, chosen, separation))
1436    }
1437}
1438
1439impl Closed {
1440    /// The bytes of the value's one form.
1441    pub fn encode(&self, out: &mut Vec<u8>) {
1442        use crate::exact_codec::ExactCodec;
1443        match self {
1444            Closed::Field(h) => {
1445                out.push(0);
1446                h.write(out);
1447            }
1448            Closed::Series(p) => {
1449                out.push(1);
1450                p.write(out);
1451            }
1452        }
1453    }
1454
1455    pub fn decode(buf: &[u8], pos: &mut usize) -> Option<Closed> {
1456        use crate::exact_codec::{read_byte, ExactCodec};
1457        match read_byte(buf, pos)? {
1458            0 => Some(Closed::Field(Hyperreal::read(buf, pos)?)),
1459            1 => Some(Puiseux::read(buf, pos)?.canonical()),
1460            _ => None,
1461        }
1462    }
1463}
1464
1465#[cfg(test)]
1466#[path = "../../logicaffeine_tests/unit/logicaffeine_base/puiseux/tests.rs"]
1467mod tests;