Matrix factorizations (spec layer) #
This file provides real, shape-indexed reference implementations of the two exact, finite matrix factorizations used by Gaussian processes, kernel ridge regression, PCA, and least squares:
choleskySpec: Cholesky factorization $A=LL^\mathsf{T}$ (lower-triangular $L$), proved for matrices with positive executable Cholesky pivots.qrSpec: QR factorization $A=QR$ via classical Gram–Schmidt; under positive executable $R$ pivots, $Q$ has orthonormal columns and $R$ is upper-triangular.
It also provides the linear solves that ride on the Cholesky factor:
triSolveLowerFnandtriSolveUpperFn: forward and back triangular substitution;cholSolveFn: solve $Ax=b$ from a Cholesky factor of $A$;solveRidgeSpec: the Tikhonov / kernel-ridge solve $(K+\gamma I)x=b$.
Verification scope #
The verified contribution is the factorizations: choleskySpec / qrSpec come with
reconstruction and structural theorems (IsCholesky / IsQR, lower- and upper-triangularity,
orthonormality) in NN.Proofs.Tensor.Basic.Factorizations*, under their stated positive-pivot
success hypotheses. The triangular- and ridge-solve helpers above (triSolveLowerFn,
triSolveUpperFn, cholSolveFn, solveRidgeSpec) are executable APIs only: their
correctness has not been proved. They follow the standard substitution formulas over the
readable function representation
and are exercised by #eval examples, but should not be read as carrying a verified-correctness
guarantee.
Intent / tradeoffs #
Like the rest of the spec layer (determinantSpec, inverseSpec?, matMulSpec), these prioritize
mathematical clarity and shape safety over performance, and are intended for small/medium
matrices and proof-oriented reference code. For large-scale numerics, use array-backed runtime
kernels.
Internally the algorithms are written over the plain function representation
Fin n → Fin n → α (matrices) and Fin n → α (vectors), then wrapped back into TorchLean.Tensor
at the boundary. This keeps the numerical formulas readable and keeps later correctness proofs
working on ordinary functions rather than on nested Tensor matches.
Boundary conversions between TorchLean.Tensor and plain functions #
View a matrix tensor as a function Fin m → Fin n → α.
Instances For
View a vector tensor as a function Fin n → α.
Instances For
Small numeric helpers on the function representation #
Cholesky factorization #
For an input whose executable Cholesky pivots are positive, compute the lower-triangular L with
$A=LL^\mathsf{T}$. Symmetric positive-definiteness is the standard sufficient condition, but the
theorem in this file family is stated against the executable positive-pivot success condition.
The columns are computed left to right. Column j uses only columns 0 .. j-1:
- diagonal: $L_{jj}=\sqrt{A_{jj}-\sum_{k<j}L_{jk}^2}$
- below: $L_{ij}=(A_{ij}-\sum_{k<j}L_{ik}L_{jk})/L_{jj}$ for $i>j$
- above: $L_{ij}=0$ for $i<j$
Trust boundary: the @[implemented_by] performance hooks #
Several defs here (choleskyColsFn, cholSolveFn, solveRidgeFn) carry an @[implemented_by]
attribute. The clean closure form is what the correctness proofs reason about; the internal
companion is a strict, array-backed rewrite that the compiler runs instead, so #eval stays fast
(the closure form re-evaluates prefixes exponentially in the interpreter).
This substitution is a trusted runtime boundary. Compiled #eval and runtime code execute the
internal array-backed body while the proofs constrain the clean closure body. The two transcribe the
same recurrence, and the
numeric examples in NN/Examples/Factorization exercise the compiled path, but a future equivalence
theorem should discharge this boundary explicitly. Anything proved about choleskyFn/solveRidgeFn
therefore transfers to #eval output only modulo this
unverified hook.
Shared strict Cholesky recurrence used by factorization and ridge solving.
Each column is materialized into an Array α, so a back-reference L[i,k]
is an O(1) lookup rather than a closure that re-evaluates the whole prefix. The closure form below
is mathematically clean (and is what the proofs reason about), but reading the full factor L from
it re-evaluates columns exponentially, which is ruinous in the interpreter (#eval). It is
intended to compute the same factor strictly; this equivalence is trusted, not proved (see the
trust-boundary note above), with the numeric examples ($A=LL^\mathsf{T}$ and ridge-solve residual
$\approx0$) as evidence rather than a proof.
Instances For
The list of columns of the Cholesky factor L, as length-n vectors, computed left to right.
Element j of the result is column j of L. Built by a left fold so that when column j is
formed, cols already holds columns 0 .. j-1.
The runtime implementation is Internal.choleskyCols (strict arrays); the closure form here is the
one used by the correctness proofs. The two are intended to compute the same factor. Their
equivalence remains part of the trust boundary described above.
Instances For
Cholesky factorization candidate for A, returning a lower-triangular factor. Over ℝ, the proved
reconstruction theorem assumes symmetry and positive executable Cholesky pivots.
PyTorch analogue: torch.linalg.cholesky(A).
Instances For
Triangular solves and the kernel-ridge (Tikhonov) linear solve #
Once $A$ is factored as $A=LL^\mathsf{T}$ (Cholesky), the linear system $Ax=b$ is solved by two
triangular substitutions: forward-solve $Lz=b$, then back-solve $L^\mathsf{T}x=z$. Each substitution
visits the unknowns in an order such that, when row i is reached, every unknown it depends on has
already been computed; the accumulator acc holds those values and 0 everywhere else, so the dot
dotFn (row i) acc is exactly the required partial sum (the not-yet-solved and structurally-zero
terms drop out).
Forward substitution: solve $Ly=b$ for a lower-triangular $L$ with nonzero diagonal.
Unknowns are visited $0,1,\ldots,n-1$; when row $i$ is reached, acc holds
$y_0,\ldots,y_{i-1}$ (and $0$ elsewhere), so
$\mathtt{dotFn}(L_i,\mathtt{acc})=\sum_{k<i}L_{ik}y_k$ by lower-triangularity.
Instances For
Back substitution: solve $Ux=y$ for an upper-triangular $U$ with nonzero diagonal.
Unknowns are visited $n-1,\ldots,1,0$; when row $i$ is reached, acc holds
$x_{i+1},\ldots,x_{n-1}$ (and $0$ elsewhere), so
$\mathtt{dotFn}(U_i,\mathtt{acc})=\sum_{k>i}U_{ik}x_k$ by upper-triangularity.
Instances For
Shared forward and backward substitution loops, given a constant-time factor entry reader.
Internal.cholSolve supplies a materialized row array; Internal.solveRidge supplies the
materialized columns from the shared Cholesky recurrence. The closure form below (triSolveUpperFn
over triSolveLowerFn) is mathematically clean, and is what the correctness proofs reason about,
but reads the Function.update accumulator chain on every step, which is ruinous in the interpreter
(#eval) when L is itself an unmaterialized closure (e.g. choleskyFn of a kernel matrix). It is
intended to compute the same solution strictly; this equivalence is trusted, not proved (see
the trust-boundary note above), with the numeric examples (the ridge residual $\approx0$) as
evidence rather than a proof.
Instances For
Solve $Ax=b$ given a Cholesky factor $L$ of $A$ (so $A=LL^\mathsf{T}$): forward-solve $Lz=b$, then back-solve $L^\mathsf{T}x=z$.
The runtime implementation is Internal.cholSolve (strict arrays); the closure form here is the
one used by the correctness proofs. Their equivalence remains part of the trust boundary described
above.
Instances For
Strict, array-backed runtime implementation of solveRidgeFn (registered via @[implemented_by]).
It factors $K+\gamma I=LL^\mathsf{T}$ and runs both triangular substitutions entirely over Arrays,
so no step materializes the deep Fin n → α closures the functional definition builds; those
re-evaluate columns / the substitution accumulator exponentially, which is ruinous in the
interpreter (#eval). Intended to be the same linear solve; this equivalence is trusted, not
proved (see the trust-boundary note above), with the numeric examples (residual $(K+\gamma I)x-b\approx0$) as evidence rather than a proof.
Instances For
The Tikhonov-regularized (kernel-ridge) solve $(K+\gamma I)x=b$, via the Cholesky factorization of $K+\gamma I$.
The runtime implementation is Internal.solveRidge (strict arrays); the closure form here is built
from the choleskyFn and triSolve* definitions used by the correctness proofs. Their equivalence
remains part of the trust boundary described above.
Instances For
Tensor-level kernel-ridge solve: $(K+\gamma I)x=b$.
PyTorch analogue: torch.linalg.solve(K + gamma * I, b) (specialized to the SPD Cholesky path).
Instances For
QR factorization (classical Gram–Schmidt) #
For $A\in\mathbb{R}^{m\times n}$, compute classical Gram–Schmidt factors. Under positive executable $R$ pivots, the proved real theorem gives $A=QR$, with $Q$ having orthonormal columns and $R$ upper-triangular. This uses classical Gram–Schmidt: each $r_{kj}=q_k^\mathsf{T}a_j$ is the inner product against the original column $a_j$, and all projections are subtracted in a single pass (modified Gram–Schmidt would instead dot each $q_k$ against the running residual). In exact real arithmetic the two coincide; the classical form is what the recurrence below implements and what the reconstruction proof matches.
Internal state for the Gram–Schmidt fold: computed Q columns and R columns so far.
Orthonormal
Qcolumns produced so far (each of lengthm).Rcolumns produced so far (each of lengthn, upper-triangular).
Instances For
The Q factor candidate of the QR factorization of A. Its columns are proved orthonormal
under positive executable R pivots.
Instances For
The R factor (upper-triangular) of the QR factorization of A.
Instances For
QR factorization candidate of $A\in\mathbb{R}^{m\times n}$ via classical Gram–Schmidt. Over ℝ,
the full $A=QR$, orthonormal-column, and upper-triangular specification is proved under positive
executable $R$ pivots.
PyTorch analogue: torch.linalg.qr(A).
Instances For
The first component of the QR pair is the orthonormal factor.
The second component is the upper-triangular factor.
The pair is returned as a plain product rather than a structure, so these two projections are what callers rewrite with instead of destructuring it.