Kronecker packing of dense polynomials #
Sendov.qrow computes the coefficients of (g₀ + g₁t + g₂t²) ^ k as a List (List ℤ).
That is correct but the kernel evaluates list recursion by interpretation, which costs
Θ(k³) interpreted steps: measured, k = 24 takes two minutes and k = 32 over twenty.
Kernel Nat arithmetic, by contrast, is GMP-backed and fast — a 4000-digit multiplication
and a 50th power are milliseconds. So the fix is to change the representation, not the
algorithm: pack a dense polynomial into a single natural number by evaluating it at a base
b = 2 ^ B larger than every coefficient. Multiplication of polynomials then becomes
multiplication of naturals, and the whole recurrence collapses to base ^ k, one
exponentiation. Measured on the real shapes, k = 46 drops from hours to about half a
second.
This file provides the arithmetic core: evaluation Sendov.npev, the digit extraction
Sendov.unpackN, and the round trip Sendov.unpackN_npev saying that packing is faithful
exactly when every coefficient is below the base. The bound hypothesis is what the caller
must supply, and it is the only genuinely new obligation the packing introduces.
Everything here is about ℕ; signed coefficients are handled downstream by splitting a
ℤ-polynomial into its positive and negative parts.
Main statements #
Sendov.npev_cons,Sendov.npev_append: how evaluation behaves;Sendov.unpackN_npev:unpackN b p.length (npev p b) = pfor coefficients belowb.
Evaluate a dense ℕ-polynomial (lowest degree first) at b, by Horner's rule. This is
the packing map: npev p b is the single natural number representing p.
Equations
- Sendov.npev [] x✝ = 0
- Sendov.npev (a :: p) x✝ = a + x✝ * Sendov.npev p x✝
Instances For
Recover the first m coefficients of a packed polynomial.
Equations
- Sendov.unpackN b 0 x✝ = []
- Sendov.unpackN b m.succ x✝ = x✝ % b :: Sendov.unpackN b m (x✝ / b)
Instances For
Packing is faithful. If every coefficient is smaller than the base, evaluation at the base loses nothing: the coefficients can be read back off by division with remainder.
This is the standard Kronecker-substitution correctness statement, and the hypothesis
∀ a ∈ p, a < b is exactly the overflow condition the caller must discharge.
Packing is a ring homomorphism #
Addition of dense ℕ-polynomials.
Equations
- Sendov.paddN [] x✝ = x✝
- Sendov.paddN (a :: p) [] = a :: p
- Sendov.paddN (a :: p) (c :: q) = (a + c) :: Sendov.paddN p q
Instances For
Multiplication of dense ℕ-polynomials.
Equations
- Sendov.pmulN [] x✝ = []
- Sendov.pmulN (a :: p) x✝ = Sendov.paddN (List.map (fun (c : ℕ) => a * c) x✝) (0 :: Sendov.pmulN p x✝)
Instances For
The k-th power of a dense ℕ-polynomial.
Equations
- Sendov.ppowN p 0 = [1]
- Sendov.ppowN p k.succ = Sendov.pmulN p (Sendov.ppowN p k)
Instances For
The overflow bound #
Kronecker substitution. The coefficients of p ^ k are recovered from the single
natural number (npev p b) ^ k, provided the base b exceeds every coefficient of p ^ k
— for which (npev p 1) ^ k < b suffices, npev p 1 being the sum of the coefficients.
This is the statement that lets the kernel replace Θ(k³) interpreted list steps by one
GMP-backed exponentiation.
Signed coefficients #
The recurrence's middle coefficient g₁ = -2Dc is negative, so the polynomials that have to
be packed have coefficients in ℤ. Packing is still evaluation at the base — the same
homomorphism — but reading the coefficients back needs balanced digits: the representative
of x % b taken in (-b/2, b/2) rather than [0, b). The overflow condition becomes
2 * |c| < b.
Evaluate a dense ℤ-polynomial at an integer. This is the signed packing map.
Equations
- Sendov.pevZ [] x✝ = 0
- Sendov.pevZ (a :: p) x✝ = a + x✝ * Sendov.pevZ p x✝
Instances For
Recover the first m balanced digits of a packed ℤ-polynomial.
Equations
- Sendov.unpackZ b 0 x✝ = []
- Sendov.unpackZ b m.succ x✝ = Sendov.bdig b x✝ :: Sendov.unpackZ b m ((x✝ - Sendov.bdig b x✝) / b)