Skip to main content

logicaffeine_base/
poly.rs

1//! Exact univariate polynomials over ℚ — the algebra under the real algebraic numbers.
2//!
3//! A [`Poly`] is a little-endian vector of exact [`Rational`] coefficients with no trailing zeros
4//! (the zero polynomial is empty), so equal polynomials are structurally equal and `Eq`/`Hash` are
5//! derived. Beside the ring operations it carries what exact real-root arithmetic needs:
6//!
7//! - **Sturm chains** on the squarefree part count the distinct real roots in any half-open
8//!   interval `(lo, hi]`, and bisection on those counts isolates every real root in a
9//!   [`RootInterval`] holding exactly one;
10//! - **resultants** by the Euclidean identity `res(f, g) = (−1)^{mn}·lc(g)^{m − deg r}·res(g, r)`,
11//!   `r = f mod g`, which is how the polynomial of a sum or product of two roots is built;
12//! - **factorization over ℤ** (Zassenhaus): Yun's squarefree decomposition, Berlekamp's algorithm
13//!   modulo a small prime, linear Hensel lifting past the Mignotte bound `2·|lc|·2ⁿ·‖f‖₁`, and
14//!   recombination of lifted factors by trial division — so a root's defining polynomial can always
15//!   be cut down to its minimal polynomial.
16//!
17//! Everything is exact and deterministic: no floating point, and the prime and every split are
18//! chosen by a fixed order, so every run (and every tier mirroring this file) factors identically.
19
20use crate::numeric::{BigInt, Rational};
21use std::cmp::Ordering;
22use std::fmt;
23
24/// A polynomial over ℚ: `c[i]` is the coefficient of `xⁱ`. INVARIANT: no trailing zero.
25#[derive(Clone, PartialEq, Eq, Hash, Debug)]
26pub struct Poly {
27    c: Vec<Rational>,
28}
29
30/// One isolated real root. `lo == hi` is the root itself (a rational root the bisection landed
31/// on); otherwise the root is the only root in the open interval `(lo, hi)` and neither end is a
32/// root, so the squarefree part changes sign across it.
33#[derive(Clone, PartialEq, Eq, Debug)]
34pub struct RootInterval {
35    pub lo: Rational,
36    pub hi: Rational,
37}
38
39fn trimmed(mut c: Vec<Rational>) -> Vec<Rational> {
40    while c.last().is_some_and(Rational::is_zero) {
41        c.pop();
42    }
43    c
44}
45
46fn sign(r: &Rational) -> i32 {
47    if r.is_zero() {
48        0
49    } else if r.is_negative() {
50        -1
51    } else {
52        1
53    }
54}
55
56fn half_of_sum(a: &Rational, b: &Rational) -> Rational {
57    a.add(b).mul(&Rational::from_ratio_i64(1, 2).expect("2 is nonzero"))
58}
59
60impl Poly {
61    pub fn zero() -> Poly {
62        Poly { c: Vec::new() }
63    }
64
65    pub fn one() -> Poly {
66        Poly {
67            c: vec![Rational::one()],
68        }
69    }
70
71    pub fn x() -> Poly {
72        Poly {
73            c: vec![Rational::zero(), Rational::one()],
74        }
75    }
76
77    pub fn constant(c: Rational) -> Poly {
78        Poly { c: trimmed(vec![c]) }
79    }
80
81    /// From little-endian coefficients (`c[i]` multiplies `xⁱ`); trailing zeros are dropped.
82    pub fn from_coeffs(c: Vec<Rational>) -> Poly {
83        Poly { c: trimmed(c) }
84    }
85
86    pub fn from_i64s(c: &[i64]) -> Poly {
87        Self::from_coeffs(c.iter().map(|&x| Rational::from_i64(x)).collect())
88    }
89
90    pub fn from_bigints(c: &[BigInt]) -> Poly {
91        Self::from_coeffs(c.iter().cloned().map(Rational::from_bigint).collect())
92    }
93
94    pub fn coeffs(&self) -> &[Rational] {
95        &self.c
96    }
97
98    /// The coefficient of `xⁱ` (zero past the degree).
99    pub fn coeff(&self, i: usize) -> Rational {
100        self.c.get(i).cloned().unwrap_or_else(Rational::zero)
101    }
102
103    /// `None` for the zero polynomial.
104    pub fn degree(&self) -> Option<usize> {
105        self.c.len().checked_sub(1)
106    }
107
108    pub fn is_zero(&self) -> bool {
109        self.c.is_empty()
110    }
111
112    pub fn leading(&self) -> Option<&Rational> {
113        self.c.last()
114    }
115
116    pub fn add(&self, o: &Poly) -> Poly {
117        let n = self.c.len().max(o.c.len());
118        Self::from_coeffs((0..n).map(|i| self.coeff(i).add(&o.coeff(i))).collect())
119    }
120
121    pub fn sub(&self, o: &Poly) -> Poly {
122        let n = self.c.len().max(o.c.len());
123        Self::from_coeffs((0..n).map(|i| self.coeff(i).sub(&o.coeff(i))).collect())
124    }
125
126    pub fn neg(&self) -> Poly {
127        Poly {
128            c: self.c.iter().map(Rational::negated).collect(),
129        }
130    }
131
132    pub fn mul(&self, o: &Poly) -> Poly {
133        if self.is_zero() || o.is_zero() {
134            return Poly::zero();
135        }
136        let mut out = vec![Rational::zero(); self.c.len() + o.c.len() - 1];
137        for (i, a) in self.c.iter().enumerate() {
138            if a.is_zero() {
139                continue;
140            }
141            for (j, b) in o.c.iter().enumerate() {
142                if b.is_zero() {
143                    continue;
144                }
145                out[i + j] = out[i + j].add(&a.mul(b));
146            }
147        }
148        Self::from_coeffs(out)
149    }
150
151    pub fn scale(&self, k: &Rational) -> Poly {
152        Self::from_coeffs(self.c.iter().map(|a| a.mul(k)).collect())
153    }
154
155    pub fn pow(&self, mut e: u32) -> Poly {
156        let mut base = self.clone();
157        let mut acc = Poly::one();
158        while e > 0 {
159            if e & 1 == 1 {
160                acc = acc.mul(&base);
161            }
162            e >>= 1;
163            if e > 0 {
164                base = base.mul(&base);
165            }
166        }
167        acc
168    }
169
170    /// Euclidean division over ℚ: `self = q·d + r` with `deg r < deg d`. `None` when `d` is zero.
171    pub fn div_rem(&self, d: &Poly) -> Option<(Poly, Poly)> {
172        let dd = d.degree()?;
173        let lc_inv = d.c[dd].recip()?;
174        if self.c.len() <= dd {
175            return Some((Poly::zero(), self.clone()));
176        }
177        let mut r = self.c.clone();
178        let mut q = vec![Rational::zero(); r.len() - dd];
179        for k in (0..q.len()).rev() {
180            let coef = r[k + dd].mul(&lc_inv);
181            if !coef.is_zero() {
182                for (j, dj) in d.c.iter().enumerate() {
183                    r[k + j] = r[k + j].sub(&coef.mul(dj));
184                }
185            }
186            q[k] = coef;
187        }
188        r.truncate(dd);
189        Some((Self::from_coeffs(q), Self::from_coeffs(r)))
190    }
191
192    /// The monic greatest common divisor; `gcd(0, 0) = 0`.
193    pub fn gcd(&self, o: &Poly) -> Poly {
194        let (mut a, mut b) = (self.monic(), o.monic());
195        while !b.is_zero() {
196            let r = a.div_rem(&b).expect("b is nonzero").1;
197            a = b;
198            b = r.monic();
199        }
200        a
201    }
202
203    /// `self` divided by its leading coefficient; zero stays zero.
204    pub fn monic(&self) -> Poly {
205        match self.leading() {
206            None => Poly::zero(),
207            Some(lc) => self.scale(&lc.recip().expect("the leading coefficient is nonzero")),
208        }
209    }
210
211    pub fn derivative(&self) -> Poly {
212        Self::from_coeffs(
213            self.c
214                .iter()
215                .enumerate()
216                .skip(1)
217                .map(|(i, a)| a.mul(&Rational::from_i64(i as i64)))
218                .collect(),
219        )
220    }
221
222    /// Horner evaluation at an exact rational point.
223    pub fn eval(&self, x: &Rational) -> Rational {
224        self.c
225            .iter()
226            .rev()
227            .fold(Rational::zero(), |acc, a| acc.mul(x).add(a))
228    }
229
230    /// `self(inner(x))`.
231    pub fn compose(&self, inner: &Poly) -> Poly {
232        self.c.iter().rev().fold(Poly::zero(), |acc, a| {
233            acc.mul(inner).add(&Poly::constant(a.clone()))
234        })
235    }
236
237    /// `x^deg · self(1/x)`: the polynomial whose roots are the reciprocals of the nonzero roots.
238    pub fn reverse(&self) -> Poly {
239        let mut c = self.c.clone();
240        c.reverse();
241        Self::from_coeffs(c)
242    }
243
244    /// `self(−x)`: the polynomial whose roots are the negated roots.
245    pub fn neg_var(&self) -> Poly {
246        Poly {
247            c: self
248                .c
249                .iter()
250                .enumerate()
251                .map(|(i, a)| if i % 2 == 1 { a.negated() } else { a.clone() })
252                .collect(),
253        }
254    }
255
256    /// The monic polynomial with the same roots, each once: `self / gcd(self, self′)`.
257    pub fn squarefree_part(&self) -> Poly {
258        if self.is_zero() {
259            return Poly::zero();
260        }
261        let g = self.gcd(&self.derivative());
262        self.div_rem(&g).expect("the gcd of a nonzero polynomial is nonzero").0.monic()
263    }
264
265    /// The integer polynomial with the same roots: denominators cleared, content divided out,
266    /// leading coefficient positive. Empty for the zero polynomial.
267    pub fn primitive_int(&self) -> Vec<BigInt> {
268        if self.is_zero() {
269            return Vec::new();
270        }
271        let den = self
272            .c
273            .iter()
274            .fold(BigInt::from_i64(1), |l, r| lcm(&l, r.denominator()));
275        let ints: Vec<BigInt> = self
276            .c
277            .iter()
278            .map(|r| {
279                let scale = den.div_rem(r.denominator()).expect("denominators are nonzero").0;
280                r.numerator().mul(&scale)
281            })
282            .collect();
283        z_primitive(&ints)
284    }
285
286    /// Cauchy's bound `1 + max |aᵢ/aₙ|`: every complex root lies strictly inside it.
287    pub fn root_bound(&self) -> Rational {
288        let Some(n) = self.degree() else {
289            return Rational::one();
290        };
291        let lc = self.c[n].abs();
292        let max = self.c[..n]
293            .iter()
294            .map(|a| a.abs().div(&lc).expect("the leading coefficient is nonzero"))
295            .max()
296            .unwrap_or_else(Rational::zero);
297        Rational::one().add(&max)
298    }
299
300    /// The number of distinct real roots.
301    pub fn count_real_roots(&self) -> usize {
302        match self.degree() {
303            None | Some(0) => 0,
304            Some(_) => {
305                let chain = self.squarefree_part().sturm_chain();
306                variations_at_neg_inf(&chain) - variations_at_pos_inf(&chain)
307            }
308        }
309    }
310
311    /// The number of distinct real roots in the half-open interval `(lo, hi]`.
312    pub fn count_real_roots_in(&self, lo: &Rational, hi: &Rational) -> usize {
313        match self.degree() {
314            None | Some(0) => 0,
315            Some(_) if lo >= hi => 0,
316            Some(_) => {
317                let chain = self.squarefree_part().sturm_chain();
318                variations_at(&chain, lo) - variations_at(&chain, hi)
319            }
320        }
321    }
322
323    /// Every distinct real root, isolated, in increasing order.
324    pub fn isolate_real_roots(&self) -> Vec<RootInterval> {
325        match self.degree() {
326            None | Some(0) => Vec::new(),
327            Some(_) => {
328                let s = self.squarefree_part();
329                let chain = s.sturm_chain();
330                let hi = s.root_bound();
331                let lo = hi.negated();
332                let n = variations_at(&chain, &lo) - variations_at(&chain, &hi);
333                let mut out = Vec::new();
334                isolate(&s, &chain, lo, hi, n, &mut out);
335                out
336            }
337        }
338    }
339
340    /// Halve an isolating interval of one of `self`'s roots, keeping the root inside. An exact
341    /// root stays as it is; a midpoint that is the root becomes the exact interval.
342    pub fn refine(&self, iv: &RootInterval) -> RootInterval {
343        if iv.lo == iv.hi {
344            return iv.clone();
345        }
346        let s = self.squarefree_part();
347        let mid = half_of_sum(&iv.lo, &iv.hi);
348        let at_mid = sign(&s.eval(&mid));
349        if at_mid == 0 {
350            RootInterval {
351                lo: mid.clone(),
352                hi: mid,
353            }
354        } else if at_mid == sign(&s.eval(&iv.lo)) {
355            RootInterval {
356                lo: mid,
357                hi: iv.hi.clone(),
358            }
359        } else {
360            RootInterval {
361                lo: iv.lo.clone(),
362                hi: mid,
363            }
364        }
365    }
366
367    /// The resultant `lc(self)^{deg o} · ∏ o(α)` over the roots `α` of `self`; zero exactly when
368    /// the two share a root, and zero when either is the zero polynomial.
369    pub fn resultant(&self, o: &Poly) -> Rational {
370        let (Some(m), Some(n)) = (self.degree(), o.degree()) else {
371            return Rational::zero();
372        };
373        if n == 0 {
374            return o.c[0].pow(m as i32).expect("a nonzero constant has every power");
375        }
376        if m == 0 {
377            return self.c[0].pow(n as i32).expect("a nonzero constant has every power");
378        }
379        let r = self.div_rem(o).expect("o is nonzero").1;
380        let Some(dr) = r.degree() else {
381            return Rational::zero();
382        };
383        let lc = o.c[n]
384            .pow((m - dr) as i32)
385            .expect("a nonzero leading coefficient has every power");
386        let v = lc.mul(&o.resultant(&r));
387        if (m * n) % 2 == 1 {
388            v.negated()
389        } else {
390            v
391        }
392    }
393
394    /// The factorization over ℚ into irreducible primitive integer polynomials (positive leading
395    /// coefficient) with multiplicities, in [`Poly::canonical_order`]. The constant content is
396    /// dropped; a constant or zero polynomial has no factors.
397    pub fn factor_over_q(&self) -> Vec<(Poly, u32)> {
398        match self.degree() {
399            None | Some(0) => return Vec::new(),
400            Some(_) => {}
401        }
402        let mut out = Vec::new();
403        for (part, mult) in self.squarefree_decomposition() {
404            for g in factor_squarefree_primitive(part.primitive_int()) {
405                out.push((Poly::from_bigints(&g), mult));
406            }
407        }
408        out.sort_by(|a, b| Poly::canonical_order(&a.0, &b.0));
409        out
410    }
411
412    /// Irreducible over ℚ: positive degree and no proper factorization.
413    pub fn is_irreducible(&self) -> bool {
414        if self.degree().unwrap_or(0) == 0 {
415            return false;
416        }
417        let fs = self.factor_over_q();
418        fs.len() == 1 && fs[0].1 == 1
419    }
420
421    /// The total order factors are listed in: by degree, then by coefficients from the highest
422    /// power down.
423    pub fn canonical_order(a: &Poly, b: &Poly) -> Ordering {
424        a.degree().cmp(&b.degree()).then_with(|| {
425            for i in (0..a.c.len()).rev() {
426                match a.c[i].cmp(&b.c[i]) {
427                    Ordering::Equal => continue,
428                    other => return other,
429                }
430            }
431            Ordering::Equal
432        })
433    }
434
435    /// Yun's algorithm: `self = c · ∏ aᵢ^i` with each `aᵢ` monic, squarefree and pairwise coprime;
436    /// the constant parts are omitted.
437    fn squarefree_decomposition(&self) -> Vec<(Poly, u32)> {
438        let f = self.monic();
439        let fp = f.derivative();
440        let a0 = f.gcd(&fp);
441        let mut b = f.div_rem(&a0).expect("gcd is nonzero").0;
442        let c = fp.div_rem(&a0).expect("gcd is nonzero").0;
443        let mut d = c.sub(&b.derivative());
444        let mut i = 1u32;
445        let mut out = Vec::new();
446        while b.degree().unwrap_or(0) > 0 {
447            let a = b.gcd(&d);
448            if a.degree().unwrap_or(0) > 0 {
449                out.push((a.clone(), i));
450            }
451            b = b.div_rem(&a).expect("gcd is nonzero").0;
452            let c_next = d.div_rem(&a).expect("gcd is nonzero").0;
453            d = c_next.sub(&b.derivative());
454            i += 1;
455        }
456        out
457    }
458
459    /// The Sturm chain `p, p′, −rem(p, p′), …`, each member scaled by a positive constant to keep
460    /// its coefficients small (a positive scale leaves every sign, so every count, unchanged).
461    fn sturm_chain(&self) -> Vec<Poly> {
462        let mut chain = vec![self.clone()];
463        let d = self.derivative();
464        if d.is_zero() {
465            return chain;
466        }
467        chain.push(d);
468        loop {
469            let n = chain.len();
470            let r = chain[n - 2].div_rem(&chain[n - 1]).expect("chain members are nonzero").1;
471            if r.is_zero() {
472                return chain;
473            }
474            let lc = r.leading().expect("r is nonzero").abs();
475            chain.push(r.neg().scale(&lc.recip().expect("lc is nonzero")));
476        }
477    }
478}
479
480fn sign_changes(signs: impl Iterator<Item = i32>) -> usize {
481    let mut prev = 0;
482    let mut n = 0;
483    for s in signs {
484        if s == 0 {
485            continue;
486        }
487        if prev != 0 && s != prev {
488            n += 1;
489        }
490        prev = s;
491    }
492    n
493}
494
495fn variations_at(chain: &[Poly], x: &Rational) -> usize {
496    sign_changes(chain.iter().map(|p| sign(&p.eval(x))))
497}
498
499fn variations_at_pos_inf(chain: &[Poly]) -> usize {
500    sign_changes(chain.iter().map(|p| sign(p.leading().expect("chain members are nonzero"))))
501}
502
503fn variations_at_neg_inf(chain: &[Poly]) -> usize {
504    sign_changes(chain.iter().map(|p| {
505        let s = sign(p.leading().expect("chain members are nonzero"));
506        if p.degree().unwrap_or(0) % 2 == 1 {
507            -s
508        } else {
509            s
510        }
511    }))
512}
513
514/// Bisect `(lo, hi)`, which holds `n` roots of the squarefree `s` and has no root at either end.
515/// A midpoint that lands on a root is recorded exactly, and the bisection steps around it by a
516/// margin that leaves it alone in its window, so every open interval it reports has nonzero ends.
517fn isolate(
518    s: &Poly,
519    chain: &[Poly],
520    lo: Rational,
521    hi: Rational,
522    n: usize,
523    out: &mut Vec<RootInterval>,
524) {
525    if n == 0 {
526        return;
527    }
528    if n == 1 {
529        out.push(RootInterval { lo, hi });
530        return;
531    }
532    let mid = half_of_sum(&lo, &hi);
533    if !s.eval(&mid).is_zero() {
534        let left = variations_at(chain, &lo) - variations_at(chain, &mid);
535        isolate(s, chain, lo, mid.clone(), left, out);
536        isolate(s, chain, mid, hi, n - left, out);
537        return;
538    }
539    let quarter = Rational::from_ratio_i64(1, 4).expect("4 is nonzero");
540    let half = Rational::from_ratio_i64(1, 2).expect("2 is nonzero");
541    let mut delta = hi.sub(&lo).mul(&quarter);
542    let (a, b) = loop {
543        let a = mid.sub(&delta);
544        let b = mid.add(&delta);
545        if !s.eval(&a).is_zero()
546            && !s.eval(&b).is_zero()
547            && variations_at(chain, &a) - variations_at(chain, &b) == 1
548        {
549            break (a, b);
550        }
551        delta = delta.mul(&half);
552    };
553    let left = variations_at(chain, &lo) - variations_at(chain, &a);
554    isolate(s, chain, lo, a, left, out);
555    out.push(RootInterval {
556        lo: mid.clone(),
557        hi: mid,
558    });
559    isolate(s, chain, b, hi, n - left - 1, out);
560}
561
562impl fmt::Display for Poly {
563    /// Descending powers: `x^3 - x - 1`, `-x^2 + 1`, `x^2 - (1/3)x + 1/2`.
564    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
565        if self.is_zero() {
566            return write!(f, "0");
567        }
568        let mut first = true;
569        for i in (0..self.c.len()).rev() {
570            let c = &self.c[i];
571            if c.is_zero() {
572                continue;
573            }
574            let neg = c.is_negative();
575            if first {
576                if neg {
577                    write!(f, "-")?;
578                }
579            } else {
580                write!(f, "{}", if neg { " - " } else { " + " })?;
581            }
582            first = false;
583            let a = c.abs();
584            let coef = if i == 0 || a.is_integer() {
585                if i > 0 && a == Rational::one() {
586                    String::new()
587                } else {
588                    a.to_string()
589                }
590            } else {
591                format!("({a})")
592            };
593            match i {
594                0 => write!(f, "{coef}")?,
595                1 => write!(f, "{coef}x")?,
596                _ => write!(f, "{coef}x^{i}")?,
597            }
598        }
599        Ok(())
600    }
601}
602
603// ── integer polynomials ───────────────────────────────────────────────────────────────────────
604
605fn lcm(a: &BigInt, b: &BigInt) -> BigInt {
606    let g = a.gcd(b);
607    a.div_rem(&g).expect("gcd of nonzero is nonzero").0.mul(b).abs()
608}
609
610fn z_trim(mut v: Vec<BigInt>) -> Vec<BigInt> {
611    while v.last().is_some_and(BigInt::is_zero) {
612        v.pop();
613    }
614    v
615}
616
617fn z_add(a: &[BigInt], b: &[BigInt]) -> Vec<BigInt> {
618    let n = a.len().max(b.len());
619    let zero = BigInt::zero();
620    z_trim(
621        (0..n)
622            .map(|i| a.get(i).unwrap_or(&zero).add(b.get(i).unwrap_or(&zero)))
623            .collect(),
624    )
625}
626
627fn z_sub(a: &[BigInt], b: &[BigInt]) -> Vec<BigInt> {
628    let n = a.len().max(b.len());
629    let zero = BigInt::zero();
630    z_trim(
631        (0..n)
632            .map(|i| a.get(i).unwrap_or(&zero).sub(b.get(i).unwrap_or(&zero)))
633            .collect(),
634    )
635}
636
637fn z_mul(a: &[BigInt], b: &[BigInt]) -> Vec<BigInt> {
638    if a.is_empty() || b.is_empty() {
639        return Vec::new();
640    }
641    let mut out = vec![BigInt::zero(); a.len() + b.len() - 1];
642    for (i, x) in a.iter().enumerate() {
643        if x.is_zero() {
644            continue;
645        }
646        for (j, y) in b.iter().enumerate() {
647            out[i + j] = out[i + j].add(&x.mul(y));
648        }
649    }
650    z_trim(out)
651}
652
653fn z_scale(a: &[BigInt], k: &BigInt) -> Vec<BigInt> {
654    z_trim(a.iter().map(|x| x.mul(k)).collect())
655}
656
657fn mod_nonneg(x: &BigInt, m: &BigInt) -> BigInt {
658    let r = x.div_rem(m).expect("the modulus is nonzero").1;
659    if r.is_negative() {
660        r.add(m)
661    } else {
662        r
663    }
664}
665
666fn zm_reduce(a: &[BigInt], m: &BigInt) -> Vec<BigInt> {
667    z_trim(a.iter().map(|x| mod_nonneg(x, m)).collect())
668}
669
670/// The representative of `x mod m` in `(−m/2, m/2]`.
671fn symmetric(x: &BigInt, m: &BigInt) -> BigInt {
672    let r = mod_nonneg(x, m);
673    if r.add(&r) > *m {
674        r.sub(m)
675    } else {
676        r
677    }
678}
679
680/// Divide out the content and make the leading coefficient positive.
681fn z_primitive(a: &[BigInt]) -> Vec<BigInt> {
682    let a = z_trim(a.to_vec());
683    let Some(lead) = a.last() else {
684        return a;
685    };
686    let content = a.iter().fold(BigInt::zero(), |g, x| g.gcd(x));
687    let content = if lead.is_negative() {
688        content.negated()
689    } else {
690        content
691    };
692    a.iter()
693        .map(|x| x.div_rem(&content).expect("content of a nonzero polynomial is nonzero").0)
694        .collect()
695}
696
697/// `f / g` over ℤ when `g` divides `f` exactly with an integer quotient.
698fn z_div_exact(f: &[BigInt], g: &[BigInt]) -> Option<Vec<BigInt>> {
699    let dg = g.len().checked_sub(1)?;
700    if f.len() <= dg {
701        return if f.is_empty() { Some(Vec::new()) } else { None };
702    }
703    let lc = &g[dg];
704    let mut r = f.to_vec();
705    let mut q = vec![BigInt::zero(); r.len() - dg];
706    for k in (0..q.len()).rev() {
707        let (coef, rem) = r[k + dg].div_rem(lc).expect("lc is nonzero");
708        if !rem.is_zero() {
709            return None;
710        }
711        if !coef.is_zero() {
712            for (j, gj) in g.iter().enumerate() {
713                r[k + j] = r[k + j].sub(&coef.mul(gj));
714            }
715        }
716        q[k] = coef;
717    }
718    if r[..dg].iter().all(BigInt::is_zero) {
719        Some(z_trim(q))
720    } else {
721        None
722    }
723}
724
725fn bigint_mod_inverse(a: &BigInt, m: &BigInt) -> BigInt {
726    let (mut r0, mut r1) = (mod_nonneg(a, m), m.clone());
727    let (mut s0, mut s1) = (BigInt::from_i64(1), BigInt::zero());
728    while !r1.is_zero() {
729        let (q, r) = r0.div_rem(&r1).expect("r1 is nonzero");
730        r0 = std::mem::replace(&mut r1, r);
731        let s2 = s0.sub(&q.mul(&s1));
732        s0 = std::mem::replace(&mut s1, s2);
733    }
734    mod_nonneg(&s0, m)
735}
736
737// ── polynomials over GF(p), p a small prime ──────────────────────────────────────────────────
738
739fn fp_trim(mut v: Vec<u64>) -> Vec<u64> {
740    while v.last() == Some(&0) {
741        v.pop();
742    }
743    v
744}
745
746fn pow_mod(mut b: u64, mut e: u64, p: u64) -> u64 {
747    let mut acc = 1u64;
748    b %= p;
749    while e > 0 {
750        if e & 1 == 1 {
751            acc = acc * b % p;
752        }
753        b = b * b % p;
754        e >>= 1;
755    }
756    acc
757}
758
759fn fp_inv(a: u64, p: u64) -> u64 {
760    pow_mod(a, p - 2, p)
761}
762
763fn modp(x: &BigInt, p: u64) -> u64 {
764    mod_nonneg(x, &BigInt::from_u64(p))
765        .to_u64()
766        .expect("a residue below p fits u64")
767}
768
769fn to_fp(f: &[BigInt], p: u64) -> Vec<u64> {
770    fp_trim(f.iter().map(|c| modp(c, p)).collect())
771}
772
773fn from_fp(v: &[u64]) -> Vec<BigInt> {
774    v.iter().map(|&x| BigInt::from_u64(x)).collect()
775}
776
777fn fp_add(a: &[u64], b: &[u64], p: u64) -> Vec<u64> {
778    let n = a.len().max(b.len());
779    fp_trim(
780        (0..n)
781            .map(|i| (a.get(i).copied().unwrap_or(0) + b.get(i).copied().unwrap_or(0)) % p)
782            .collect(),
783    )
784}
785
786fn fp_sub(a: &[u64], b: &[u64], p: u64) -> Vec<u64> {
787    let n = a.len().max(b.len());
788    fp_trim(
789        (0..n)
790            .map(|i| (a.get(i).copied().unwrap_or(0) + p - b.get(i).copied().unwrap_or(0)) % p)
791            .collect(),
792    )
793}
794
795fn fp_mul(a: &[u64], b: &[u64], p: u64) -> Vec<u64> {
796    if a.is_empty() || b.is_empty() {
797        return Vec::new();
798    }
799    let mut out = vec![0u64; a.len() + b.len() - 1];
800    for (i, &x) in a.iter().enumerate() {
801        if x == 0 {
802            continue;
803        }
804        for (j, &y) in b.iter().enumerate() {
805            out[i + j] = (out[i + j] + x * y) % p;
806        }
807    }
808    fp_trim(out)
809}
810
811fn fp_scale(a: &[u64], k: u64, p: u64) -> Vec<u64> {
812    fp_trim(a.iter().map(|&x| x * k % p).collect())
813}
814
815fn fp_divrem(a: &[u64], b: &[u64], p: u64) -> (Vec<u64>, Vec<u64>) {
816    let db = b.len() - 1;
817    if a.len() <= db {
818        return (Vec::new(), a.to_vec());
819    }
820    let inv = fp_inv(b[db], p);
821    let mut r = a.to_vec();
822    let mut q = vec![0u64; r.len() - db];
823    for k in (0..q.len()).rev() {
824        let coef = r[k + db] * inv % p;
825        if coef != 0 {
826            for (j, &bj) in b.iter().enumerate() {
827                r[k + j] = (r[k + j] + p - coef * bj % p) % p;
828            }
829        }
830        q[k] = coef;
831    }
832    r.truncate(db);
833    (fp_trim(q), fp_trim(r))
834}
835
836fn fp_monic(a: &[u64], p: u64) -> Vec<u64> {
837    match a.last() {
838        None => Vec::new(),
839        Some(&lc) => fp_scale(a, fp_inv(lc, p), p),
840    }
841}
842
843fn fp_gcd(a: &[u64], b: &[u64], p: u64) -> Vec<u64> {
844    let (mut a, mut b) = (fp_monic(a, p), fp_monic(b, p));
845    while !b.is_empty() {
846        let r = fp_divrem(&a, &b, p).1;
847        a = b;
848        b = fp_monic(&r, p);
849    }
850    a
851}
852
853/// `(g, s, t)` with `s·a + t·b = g`, `g` the monic gcd.
854fn fp_ext_gcd(a: &[u64], b: &[u64], p: u64) -> (Vec<u64>, Vec<u64>, Vec<u64>) {
855    let (mut r0, mut r1) = (a.to_vec(), b.to_vec());
856    let (mut s0, mut s1) = (vec![1u64], Vec::new());
857    let (mut t0, mut t1) = (Vec::new(), vec![1u64]);
858    while !r1.is_empty() {
859        let (q, r) = fp_divrem(&r0, &r1, p);
860        r0 = std::mem::replace(&mut r1, r);
861        let s2 = fp_sub(&s0, &fp_mul(&q, &s1, p), p);
862        s0 = std::mem::replace(&mut s1, s2);
863        let t2 = fp_sub(&t0, &fp_mul(&q, &t1, p), p);
864        t0 = std::mem::replace(&mut t1, t2);
865    }
866    let inv = fp_inv(*r0.last().expect("gcd of nonzero polynomials is nonzero"), p);
867    (fp_scale(&r0, inv, p), fp_scale(&s0, inv, p), fp_scale(&t0, inv, p))
868}
869
870fn fp_derivative(a: &[u64], p: u64) -> Vec<u64> {
871    fp_trim(
872        a.iter()
873            .enumerate()
874            .skip(1)
875            .map(|(i, &x)| x * (i as u64 % p) % p)
876            .collect(),
877    )
878}
879
880/// `base^e mod f`.
881fn fp_powmod(base: &[u64], mut e: u64, f: &[u64], p: u64) -> Vec<u64> {
882    let mut acc = vec![1u64];
883    let mut b = fp_divrem(base, f, p).1;
884    while e > 0 {
885        if e & 1 == 1 {
886            acc = fp_divrem(&fp_mul(&acc, &b, p), f, p).1;
887        }
888        e >>= 1;
889        if e > 0 {
890            b = fp_divrem(&fp_mul(&b, &b, p), f, p).1;
891        }
892    }
893    acc
894}
895
896/// A basis of the null space of `a` over GF(p), by reduction to row echelon form.
897fn nullspace_mod_p(mut a: Vec<Vec<u64>>, p: u64) -> Vec<Vec<u64>> {
898    let rows = a.len();
899    let cols = a[0].len();
900    let mut pivot_cols = Vec::new();
901    let mut r = 0;
902    for c in 0..cols {
903        if r == rows {
904            break;
905        }
906        let Some(pr) = (r..rows).find(|&i| a[i][c] != 0) else {
907            continue;
908        };
909        a.swap(r, pr);
910        let inv = fp_inv(a[r][c], p);
911        for x in a[r].iter_mut() {
912            *x = *x * inv % p;
913        }
914        for i in 0..rows {
915            if i != r && a[i][c] != 0 {
916                let f = a[i][c];
917                let pivot = a[r].clone();
918                for (x, y) in a[i].iter_mut().zip(&pivot) {
919                    *x = (*x + p - f * y % p) % p;
920                }
921            }
922        }
923        pivot_cols.push(c);
924        r += 1;
925    }
926    (0..cols)
927        .filter(|c| !pivot_cols.contains(c))
928        .map(|fc| {
929            let mut v = vec![0u64; cols];
930            v[fc] = 1;
931            for (ri, &pc) in pivot_cols.iter().enumerate() {
932                v[pc] = (p - a[ri][fc]) % p;
933            }
934            v
935        })
936        .collect()
937}
938
939/// Berlekamp's algorithm: the monic irreducible factors of a monic squarefree `f` over GF(p).
940/// The fixed space of the Frobenius `v ↦ vᵖ` on `GF(p)[x]/f` has dimension equal to the factor
941/// count; gcds of `f` with `v − s` over its basis split `f` completely.
942fn berlekamp(f: &[u64], p: u64) -> Vec<Vec<u64>> {
943    let n = f.len() - 1;
944    let xp = fp_powmod(&[0, 1], p, f, p);
945    let mut rows = Vec::with_capacity(n);
946    let mut cur = vec![1u64];
947    for _ in 0..n {
948        let mut row = cur.clone();
949        row.resize(n, 0);
950        rows.push(row);
951        cur = fp_divrem(&fp_mul(&cur, &xp, p), f, p).1;
952    }
953    let mut a = vec![vec![0u64; n]; n];
954    for (i, row) in rows.iter().enumerate() {
955        for (j, &q) in row.iter().enumerate() {
956            a[j][i] = if i == j { (q + p - 1) % p } else { q };
957        }
958    }
959    let basis = nullspace_mod_p(a, p);
960    let r = basis.len();
961    let mut factors = vec![f.to_vec()];
962    'split: for v in &basis {
963        let v = fp_trim(v.clone());
964        if v.len() <= 1 {
965            continue;
966        }
967        for s in 0..p {
968            let mut shifted = v.clone();
969            shifted[0] = (shifted[0] + p - s) % p;
970            let shifted = fp_trim(shifted);
971            let mut next = Vec::with_capacity(factors.len() + 1);
972            for g in factors.drain(..) {
973                if g.len() <= 2 {
974                    next.push(g);
975                    continue;
976                }
977                let h = fp_gcd(&g, &shifted, p);
978                if h.len() > 1 && h.len() < g.len() {
979                    let rest = fp_monic(&fp_divrem(&g, &h, p).0, p);
980                    next.push(h);
981                    next.push(rest);
982                } else {
983                    next.push(g);
984                }
985            }
986            factors = next;
987            if factors.len() == r {
988                break 'split;
989            }
990        }
991    }
992    factors.sort();
993    factors
994}
995
996/// Lift `f ≡ g₀·h₀ (mod p)` (`g₀` monic, coprime to `h₀`) to `f ≡ g·h (mod pᵏ)`, one factor of
997/// `p` per step: with `s·g₀ + t·h₀ ≡ 1`, the error `e = (f − g·h)/m` is absorbed by
998/// `δg = t·e mod g₀` and `δh = s·e + (t·e div g₀)·h₀`, since `δg·h₀ + δh·g₀ ≡ e (mod p)`.
999fn hensel_pair(
1000    f: &[BigInt],
1001    g0: &[u64],
1002    h0: &[u64],
1003    p: u64,
1004    k: u32,
1005) -> (Vec<BigInt>, Vec<BigInt>) {
1006    let (_, s, t) = fp_ext_gcd(g0, h0, p);
1007    let pb = BigInt::from_u64(p);
1008    let mut m = pb.clone();
1009    let mut g = from_fp(g0);
1010    let mut h = from_fp(h0);
1011    for _ in 1..k {
1012        let diff = z_sub(f, &z_mul(&g, &h));
1013        let e = fp_trim(
1014            diff.iter()
1015                .map(|c| {
1016                    let (q, r) = c.div_rem(&m).expect("m is nonzero");
1017                    debug_assert!(r.is_zero(), "f ≡ g·h (mod m) makes the error divisible by m");
1018                    modp(&q, p)
1019                })
1020                .collect(),
1021        );
1022        let te = fp_mul(&t, &e, p);
1023        let (q, dg) = fp_divrem(&te, g0, p);
1024        let dh = fp_add(&fp_mul(&s, &e, p), &fp_mul(&q, h0, p), p);
1025        let mp = m.mul(&pb);
1026        g = zm_reduce(&z_add(&g, &z_scale(&from_fp(&dg), &m)), &mp);
1027        h = zm_reduce(&z_add(&h, &z_scale(&from_fp(&dh), &m)), &mp);
1028        m = mp;
1029    }
1030    (g, h)
1031}
1032
1033/// Lift every factor of `f ≡ lc(f)·∏ gᵢ (mod p)` to a monic factor modulo `pᵏ`, peeling one
1034/// factor at a time off the lifted cofactor.
1035fn hensel_multi(f: &[BigInt], mods: &[Vec<u64>], p: u64, k: u32, pk: &BigInt) -> Vec<Vec<BigInt>> {
1036    let mut out = Vec::with_capacity(mods.len());
1037    let mut target = f.to_vec();
1038    for g0 in &mods[..mods.len() - 1] {
1039        let (h0, rem) = fp_divrem(&to_fp(&target, p), g0, p);
1040        debug_assert!(rem.is_empty(), "every modular factor divides the target");
1041        let (g, h) = hensel_pair(&target, g0, &h0, p, k);
1042        out.push(g);
1043        target = h;
1044    }
1045    let inv = bigint_mod_inverse(target.last().expect("the cofactor is nonzero"), pk);
1046    out.push(zm_reduce(&z_scale(&target, &inv), pk));
1047    out
1048}
1049
1050/// Advance `idx` (a strictly increasing `s`-subset of `0..n`) to the next subset in lexicographic
1051/// order; `false` when it was the last.
1052fn next_combination(idx: &mut [usize], n: usize) -> bool {
1053    let s = idx.len();
1054    for i in (0..s).rev() {
1055        if idx[i] < n - s + i {
1056            idx[i] += 1;
1057            for j in i + 1..s {
1058                idx[j] = idx[j - 1] + 1;
1059            }
1060            return true;
1061        }
1062    }
1063    false
1064}
1065
1066/// Zassenhaus recombination: a true factor of `f` is, modulo `pᵏ`, `lc(f)` times a product of a
1067/// subset of the lifted factors; past the Mignotte bound its symmetric representative is the
1068/// factor itself (up to content), which trial division confirms.
1069fn recombine(mut f: Vec<BigInt>, mut lifted: Vec<Vec<BigInt>>, pk: &BigInt) -> Vec<Vec<BigInt>> {
1070    let mut found = Vec::new();
1071    let mut s = 1;
1072    while 2 * s <= lifted.len() {
1073        let n = lifted.len();
1074        let mut idx: Vec<usize> = (0..s).collect();
1075        let mut hit = None;
1076        loop {
1077            let lc = f.last().expect("f has positive degree").clone();
1078            let mut cand = vec![lc];
1079            for &i in &idx {
1080                cand = zm_reduce(&z_mul(&cand, &lifted[i]), pk);
1081            }
1082            let cand = z_primitive(&z_trim(cand.iter().map(|c| symmetric(c, pk)).collect()));
1083            if let Some(q) = z_div_exact(&f, &cand) {
1084                hit = Some((cand, q));
1085                break;
1086            }
1087            if !next_combination(&mut idx, n) {
1088                break;
1089            }
1090        }
1091        match hit {
1092            Some((g, q)) => {
1093                found.push(g);
1094                f = z_primitive(&q);
1095                for &i in idx.iter().rev() {
1096                    lifted.remove(i);
1097                }
1098            }
1099            None => s += 1,
1100        }
1101    }
1102    if f.len() > 1 {
1103        found.push(z_primitive(&f));
1104    }
1105    found
1106}
1107
1108fn is_prime(n: u64) -> bool {
1109    n >= 2 && (2..).take_while(|d| d * d <= n).all(|d| !n.is_multiple_of(d))
1110}
1111
1112/// Factor a squarefree primitive integer polynomial into its irreducible factors over ℤ.
1113fn factor_squarefree_primitive(f: Vec<BigInt>) -> Vec<Vec<BigInt>> {
1114    let n = f.len() - 1;
1115    if n <= 1 {
1116        return vec![f];
1117    }
1118    let lc = f.last().expect("positive degree").clone();
1119    let mut best: Option<(u64, Vec<Vec<u64>>)> = None;
1120    let mut admissible = 0;
1121    for p in (3u64..).step_by(2).filter(|&p| is_prime(p)) {
1122        if modp(&lc, p) == 0 {
1123            continue;
1124        }
1125        let fm = fp_monic(&to_fp(&f, p), p);
1126        if fp_gcd(&fm, &fp_derivative(&fm, p), p).len() != 1 {
1127            continue;
1128        }
1129        let factors = berlekamp(&fm, p);
1130        if factors.len() == 1 {
1131            return vec![f];
1132        }
1133        if best.as_ref().is_none_or(|(_, b)| factors.len() < b.len()) {
1134            best = Some((p, factors));
1135        }
1136        admissible += 1;
1137        if admissible == 5 {
1138            break;
1139        }
1140    }
1141    let (p, mods) = best.expect("a squarefree polynomial has admissible primes");
1142    let l1 = f.iter().fold(BigInt::zero(), |a, c| a.add(&c.abs()));
1143    let bound = lc.abs().mul(&l1).shl_bits(n as u64 + 1);
1144    let pb = BigInt::from_u64(p);
1145    let mut pk = pb.clone();
1146    let mut k = 1u32;
1147    while pk <= bound {
1148        pk = pk.mul(&pb);
1149        k += 1;
1150    }
1151    let lifted = hensel_multi(&f, &mods, p, k, &pk);
1152    recombine(f, lifted, &pk)
1153}
1154
1155impl crate::exact_codec::ExactCodec for Poly {
1156    /// The coefficients, lowest power first.
1157    fn write(&self, out: &mut Vec<u8>) {
1158        crate::exact_codec::write_list(&self.c, out);
1159    }
1160
1161    fn read(buf: &[u8], pos: &mut usize) -> Option<Self> {
1162        Some(Poly::from_coeffs(crate::exact_codec::read_list(buf, pos)?))
1163    }
1164}
1165
1166#[cfg(test)]
1167#[path = "../../logicaffeine_tests/unit/logicaffeine_base/poly/tests.rs"]
1168mod tests;