linalg
NumPy-style linear algebra on ndarray, with SIMD-accelerated contiguous kernels.
For the small-and-hot regime — a 3-D direction, a 4×4 transform built and consumed millions of times a second — reach for the sibling fixarray module: the same mathematics with the shape moved into the type (vec3f/mat4f, allocation-free, and faster than GLM across their whole overlap). linalg is the home of the heavy, shape-generic numerics below.
The routines mirror numpy.linalg and operate on ndarray::NDArray (2-D = matrix, 1-D = vector). The general eigensolvers eig/eigvals return a complex spectrum (CNDArray) — a real matrix can have complex conjugate eigenvalue pairs, e.g. a rotation has ±i — while the Hermitian solvers eigh/eigvalsh return a guaranteed-real spectrum (the same split as numpy). Kernels are compiled at -O3 -march=native so the hot loops auto-vectorize.
Usage
import linalg # auto-links ndarray
let x = linalg.solve(A, b) # A·x = b
let d = linalg.det(A)Functions
Every routine below returns a fresh array. Each also has an allocation-free out-parameter overload (solve(out, A, b), svd(u, s, vh, A), …) in routines.hpp for hot loops that reuse one scratch buffer per call — same math, caller-owned storage.
Products
dot/vdot/inner— vector dot product (Σ aᵢbᵢ).outer— outer product of two vectors → matrix.matmul— matrix multiply.matrix_power— integer matrix power Aⁿ (negative viainv).kron— Kronecker (block) product.
Decompositions
cholesky— lower-triangular L with A = L·Lᵀ (SPD only).qr— reduced QR via Householder reflections.svd— singular value decomposition (Golub–Reinsch: bidiagonalization + implicit QR).svdvals— singular values only (the SVD fast path, without forming U/Vᵀ).
Eigenvalues
eig/eigvals— general square matrix (complex spectrum + eigenvectors; Hessenberg + shifted QR for the values, inverse iteration for the vectors).eigh/eigvalsh— symmetric or complex Hermitian matrix (real spectrum; real eigenvectors for a real symmetric matrix, complex eigenvectors for a Hermitian one; Householder tridiagonalization + QL, via a real 2n embedding for Hermitian input).
All eigenvalue routines return the spectrum descending (note: numpy's eigvalsh returns ascending), with eigenvector columns reordered to match.
# A real rotation matrix has complex eigenvalues ±i:
let r = ndarray.reshape(ndarray.array([0.0, -1.0, 1.0, 0.0]), [2, 2])
io.print(ndarray.to_string(linalg.eigvals(r))) # [0+1j, 0-1j]Complex inner-product spaces
Vectors and matrices can be complex (ndarray.complex(re, im)), so the routines work over complex inner-product spaces — Hermitian operators, complex wavefunctions:
dot— bilinear product Σ aᵢbᵢ (complex, no conjugation; matches numpy).vdot— conjugate-linear Hermitian inner product ⟨a, b⟩ = Σ conj(aᵢ)·bᵢ (conjugates the first argument;vdot(a, a)is the real ‖a‖²).matmul— complex matrix multiply.conj_transpose— conjugate transpose (Hermitian adjoint) Aᴴ.
import io
import ndarray
import linalg
# A Hermitian operator H = [[2, 1+i], [1-i, 3]] — real eigenvalues 4, 1:
let re = ndarray.array([2.0, 1.0, 1.0, 3.0])
let im = ndarray.array([0.0, 1.0, -1.0, 0.0])
let H = ndarray.reshape(ndarray.complex(re, im), [2, 2])
io.print(ndarray.to_string(linalg.eigvalsh(H))) # [4, 1]
# Hermitian inner product ⟨a, a⟩ = ‖a‖² is real:
let a = ndarray.complex(ndarray.array([1.0, 3.0]), ndarray.array([2.0, -1.0]))
io.print(linalg.vdot(a, a)) # 15+0jNorms & numbers
norm— L2 (vector) / Frobenius (matrix).cond— 2-norm condition number.det/slogdet— determinant (LU);slogdetis overflow-safe.matrix_rank— numerical rank from SVD thresholding.trace— sum of the main diagonal.
Solving & inverses
solve— solve A·x = b via LU with partial pivoting.lstsq— least-squares solution min‖A·x − b‖.inv— matrix inverse (LU).pinv— Moore–Penrose pseudo-inverse (SVD, any shape).
SIMD
SIMD here is pure compiler auto-vectorization (no intrinsics): the kernels are contiguous, unit-stride loops compiled at -O3 -march=native. These functions only report the build's capability:
simd_features— instruction sets this build targets (e.g.AVX2;FMA,NEON,scalar).simd_lane_doubles— widest SIMD lane width indoubles.
On a build with no SIMD every routine returns identical results, just scalar / slower — SIMD is never a correctness dependency. The full model (and the compile-time-dispatch limitation) is documented in simd.hpp.
Performance vs NumPy
linalg goes head-to-head with NumPy, whose array ops dispatch to BLAS/LAPACK — hand-tuned, vectorized, often multi-threaded Fortran. The scripts/numpy_compare.py harness feeds the same fixed-seed, well-conditioned matrix to both, runs the same op many times with the result consumed, and checks the answers agree. Each function's Performance row above carries its own measurement; the full size-dependence:
What we compared against — read this before the numbers. NumPy's absolute speed, and where every crossover lands, depends far more on which BLAS it links than on which NumPy version it is. The generated stamp below records the resolved library rather than the version string, because "NumPy 1.26.4" does not identify a measurement and the library does.
On this machine that resolves to libblas.so.3 → the reference implementation, not OpenBLAS and not MKL. That matters for how much these wins are worth: reference BLAS is the unoptimized baseline, so a cheatah win over it is a win over untuned Fortran, not over the tuned kernels most NumPy installs actually use. A tuned BLAS would narrow the large-n rows and push the crossovers lower. The small-n wins are a different claim and survive either way — they come from cheatah having no per-call Python and dispatch overhead to pay, which no choice of BLAS changes.
The Eigen comparison is a separate measurement, in the native Google Benchmark harness (tests/benchmarks/eigen_compare_bench.cpp), where cheatah and Eigen 3.4 are both compiled C++ timed identically on one thread — an apples-to-apples per-core comparison.
Two harnesses, two tables — deliberately. These numbers used to share one table, with the cheatah column measured by numpy_compare.py (separate processes) sitting beside an Eigen column measured by Google Benchmark (compiled C++, one process), and a prose warning not to read across. A warning is a weaker fix than a structure: a reader who divides two adjacent columns gets a wrong answer no matter how the caption is worded. Each harness now publishes only what it measured, and neither table contains a column it did not produce.
Both are generated — see docs/performance.md for the methodology (striated, interleaved, medians with dispersion) and the reference machine.
vs NumPy
op | operand dimensions | cheatah | NumPy | winner | band |
|---|---|---|---|---|---|
| 4 | 0.09 | 0.79 | cheatah 8.3x | 7.20-8.48 |
| 16 | 0.54 | 2.24 | cheatah 4.1x | 3.56-4.29 |
| 32 | 3.22 | 13.43 | cheatah 4.2x | 3.94-4.60 |
| 64 | 25.60 | 116.37 | cheatah 4.5x | 4.30-4.71 |
| 96 | 90.65 | 318.41 | cheatah 3.5x | 3.38-3.63 |
| 4 | 0.22 | 2.45 | cheatah 11.0x | 10.03-11.49 |
| 16 | 1.06 | 4.34 | cheatah 4.1x | 3.81-4.35 |
| 32 | 3.69 | 10.35 | cheatah 2.8x | 2.55-2.85 |
| 64 | 18.02 | 50.82 | cheatah 2.8x | 2.72-3.10 |
| 4 | 0.11 | 2.05 | cheatah 22.6x | 15.44-28.51 |
| 16 | 0.81 | 3.76 | cheatah 4.7x | 3.94-5.39 |
| 32 | 2.80 | 9.58 | cheatah 3.4x | 3.18-3.53 |
| 64 | 14.17 | 47.05 | cheatah 3.4x | 3.22-3.51 |
| 4 | 0.26 | 2.18 | cheatah 8.2x | 7.66-9.08 |
| 16 | 1.56 | 6.35 | cheatah 4.1x | 3.18-4.40 |
| 32 | 6.97 | 25.27 | cheatah 3.6x | 3.51-3.65 |
| 64 | 46.20 | 142.12 | cheatah 3.1x | 2.95-3.51 |
| 2 | 0.22 | 2.01 | cheatah 9.2x | 8.95-9.74 |
| 3 | 0.48 | 2.35 | cheatah 4.9x | 4.67-5.01 |
| 4 | 0.62 | 2.66 | cheatah 4.3x | 3.88-4.37 |
| 6 | 1.43 | 3.49 | cheatah 2.4x | 2.34-2.48 |
| 8 | 1.91 | 4.20 | cheatah 2.2x | 2.10-2.23 |
| 16 | 6.73 | 9.20 | cheatah 1.4x | 1.32-1.39 |
| 32 | 26.01 | 25.98 | cheatah 1.0x | 0.95-1.04 |
| 64 | 106.80 | 149.84 | cheatah 1.4x | 1.26-1.43 |
| 64 | 0.02 | 0.62 | cheatah 34.4x | 28.33-46.46 |
| 1024 | 0.09 | 1.08 | cheatah 11.6x | 7.28-17.05 |
| 16384 | 2.59 | 8.16 | cheatah 3.1x | 2.77-3.54 |
| 64 | 0.19 | 0.88 | cheatah 4.7x | 3.07-4.97 |
| 1024 | 0.96 | 1.77 | cheatah 1.8x | 1.52-2.09 |
| 16384 | 14.58 | 14.66 | NumPy 1.0x | 0.95-1.04 |
| 64 | 0.21 | 1.05 | cheatah 5.0x | 3.83-5.62 |
| 1024 | 1.00 | 4.03 | cheatah 4.0x | 2.52-4.76 |
| 16384 | 13.52 | 48.89 | cheatah 3.6x | 3.40-3.92 |
| 64 | 0.22 | 1.11 | cheatah 5.0x | 4.08-6.13 |
| 1024 | 0.95 | 5.52 | cheatah 5.9x | 4.23-6.25 |
| 16384 | 13.69 | 89.32 | cheatah 6.5x | 5.68-6.84 |
| 64 | 0.11 | 0.62 | cheatah 5.5x | 4.58-6.05 |
| 16384 | 2.48 | 2.88 | cheatah 1.2x | 0.78-1.23 |
| 8 | 0.31 | 2.63 | cheatah 8.4x | 7.20-9.79 |
| 32 | 3.77 | 7.35 | cheatah 2.0x | 1.68-2.15 |
| 64 | 16.81 | 25.99 | cheatah 1.5x | 1.35-1.78 |
| 8 | 1.07 | 8.88 | cheatah 8.3x | 7.53-8.64 |
| 32 | 14.79 | 28.49 | cheatah 2.0x | 1.72-2.16 |
| 64 | 102.62 | 137.47 | cheatah 1.3x | 1.16-1.36 |
| 8 | 3.28 | 6.15 | cheatah 1.9x | 1.70-1.93 |
| 32 | 49.70 | 42.83 | NumPy 1.2x | 0.82-0.96 |
| 64 | 229.46 | 208.75 | NumPy 1.1x | 0.88-0.97 |
| 8 | 3.93 | 10.53 | cheatah 2.7x | 2.51-2.85 |
| 32 | 70.11 | 115.62 | cheatah 1.6x | 1.50-1.65 |
| 64 | 433.35 | 739.34 | cheatah 1.7x | 1.50-1.76 |
| 8 | 4.63 | 18.86 | cheatah 4.1x | 3.37-4.19 |
| 32 | 100.36 | 136.64 | cheatah 1.4x | 1.31-1.47 |
| 64 | 633.48 | 800.84 | cheatah 1.3x | 1.23-1.31 |
| 8 | 3.23 | 10.21 | cheatah 3.2x | 3.07-3.31 |
| 32 | 49.56 | 45.71 | NumPy 1.1x | 0.89-0.97 |
| 64 | 217.89 | 195.27 | NumPy 1.1x | 0.89-0.95 |
| 8 | 2.98 | 11.98 | cheatah 4.0x | 3.86-4.16 |
| 32 | 46.95 | 51.17 | cheatah 1.1x | 0.98-1.12 |
| 64 | 216.33 | 194.85 | NumPy 1.1x | 0.88-0.93 |
| 8 | 0.20 | 3.15 | cheatah 15.2x | 11.78-17.67 |
| 32 | 2.93 | 10.48 | cheatah 3.6x | 3.45-3.71 |
| 64 | 14.03 | 48.95 | cheatah 3.5x | 2.20-3.56 |
| 8 | 2.48 | 6.52 | cheatah 2.6x | 2.32-4.14 |
| 32 | 37.96 | 64.51 | cheatah 1.7x | 1.62-1.72 |
| 64 | 238.38 | 401.26 | cheatah 1.7x | 1.61-1.72 |
| 8 | 7.88 | 14.11 | cheatah 1.8x | 1.73-1.89 |
| 8 | 0.91 | 2.49 | cheatah 2.7x | 2.60-2.79 |
| 32 | 12.27 | 28.00 | cheatah 2.3x | 1.96-2.38 |
| 64 | 97.93 | 214.16 | cheatah 2.2x | 2.12-2.40 |
| 32 | 0.01 | 1.20 | cheatah 168.1x | 120.84-178.23 |
| 256 | 0.08 | 1.47 | cheatah 20.2x | 17.90-22.16 |
| 32 | 0.09 | 1.55 | cheatah 17.8x | 10.01-24.71 |
| 256 | 7.38 | 32.35 | cheatah 4.4x | 3.92-4.52 |
| 64 | 0.34 | 4.05 | cheatah 11.9x | 7.13-13.10 |
| 256 | 9.78 | 48.77 | cheatah 5.0x | 4.15-5.25 |
| 8 | 1.71 | 13.12 | cheatah 7.8x | 7.24-8.74 |
| 16 | 20.25 | 72.57 | cheatah 3.6x | 3.20-4.17 |
| 32 | 347.09 | 950.98 | cheatah 2.7x | 2.49-2.81 |
vs Eigen
case | cheatah | spread | vs | rival | spread | ratio | verdict |
|---|---|---|---|---|---|---|---|
BM_dot/16384 | 1.76 µs | ±140.45 ns IQR | eigen | 2.54 µs | ±29.23 ns IQR | 1.44x | faster |
BM_dot/64 | 10.18 ns | ±0.71 ns IQR | eigen | 6.69 ns | ±0.17 ns IQR | 0.66x | slower |
BM_inv/32 | 6.76 µs | ±1.35 µs IQR | eigen | 11.21 µs | ±438.60 ns IQR | 1.66x | faster |
BM_inv/64 | 38.55 µs | ±8.55 µs IQR | eigen | 67.44 µs | ±2.07 µs IQR | 1.75x | faster |
BM_matmul/32 | 3.17 µs | ±137.63 ns IQR | eigen | 3.80 µs | ±49.86 ns IQR | 1.20x | faster |
BM_matmul/96 | 87.96 µs | ±879.72 ns IQR | eigen | 94.93 µs | ±3.32 µs IQR | 1.08x | parity |
BM_solve/32 | 3.56 µs | ±118.70 ns IQR | eigen | 4.17 µs | ±151.63 ns IQR | 1.17x | faster |
BM_solve/64 | 16.47 µs | ±372.73 ns IQR | eigen | 19.97 µs | ±426.11 ns IQR | 1.21x | faster |
Tally (a difference counts only above 1.15x AND 0.25 ns) — vs eigen: 6 faster / 1 parity / 1 slower.
Loss vs eigen:
BM_dot/64— cheatah 10.18 ns vs 6.69 ns (1.52x slower)
After a focused optimization round (hunting a few recurring mistakes across every routine — a heap allocation in a hot predicate, single-accumulator reductions, a column-stride QR walk, and a result buffer that was zero-filled and then thrown away):
vs NumPy/LAPACK, cheatah now wins across nearly the whole library — products, the LU family, the SVD, the symmetric eigensolver,
outer,qr,kron, and largenorm(the last four previously lost). The handful still behind (svdvals/cond/matrix_rank~1.1×) are SVD-threshold queries where LAPACK's bidiagonal solver edges it.vs Eigen 3.4 on one core, cheatah matches or beats it on the bulk —
inv(≈1.7×),svd(≈1.6×),det/matmul/trace(≈1.3–1.4×),outer(≈1.2–1.3×, now that the result buffer is built uninitialized and moved in zero-copy),solve/eigvalsh/eigh/norm(≈1.1–1.2×). Eigen still leads on its blocked BLAS-3 kernels —qr(1.3–1.8×),cholesky(1.2×), and theeigheigenvector path (1.1×) — which we flag honestly rather than hide.
cheatah does all of this on one core, by design — single-threaded is a feature, not a shortfall (no hidden threads, no contention, nothing to tune). Both NumPy's and Eigen's remaining edges are the very large or blocked dense problems where threaded/BLAS-3 kernels spread the work — a different operating point. See the Performance guide for the full rationale.
Per-function docs (parameters, complexity, heap behavior) are in routines.hpp. Tested in ../tests/linalg_routines_test.cpp and ../tests/linalg_smoke_test.cpp; ASan + Valgrind clean via the QA gate (security/run-valgrind.sh).
Functions
matmul · 2 overloads
Matrix multiply — the allocating front.
Both operands are Array<T> (so host⊗device / element mixes fail to deduce and are compile errors); requires both to be 2-D with matching inner dimensions — or both 3-D for the BATCHED product [B,M,K] @ [B,K,N] → [B,M,N] (equal batch counts, strict: no broadcast batching). Allocates the result via Array<T>::uninitialized (no throwaway zero-fill) and fills it through the out-parameter kernel — the host SIMD path, or a device shader when Array is a device container (selected by concept at compile time).
T | the element type ( |
Array | the container template. |
a | m×k matrix, or a B×m×k batch of matrices. |
b | k×p matrix, or a B×k×p batch. |
m×p product (or the B×m×p batch), an Array<T> of the same container and element.
O(n³) (× B for a batch).
allocates only the result; operands read in place (a strided host view packs once).
deliberately single-threaded (the fastest-per-core contract); parallelize across independent products in the caller.
LinalgCompileRun.MatmulStdlibE2E.Linalg SystemApps.LinearSolveThe flattened length of a vector-shaped operand — 1-D, or 2-D with a size-1 row/column (throws otherwise).
Reads only host-resident shape metadata, so it is valid for ANY located container, device arrays included; the shared validation step of every vector front below.
A | the (located) container type. |
a | the operand whose vector length is wanted. |
the element count of the flattened vector.
O(1).
none.
LinalgRoutines.ProductsAndTracedot · 2 overloads
Dot product: 1-D inner product (vectors flattened) — the bilinear Σ aᵢbᵢ.
ONE two-layer template over the element T and container Array serving real, complex, host and — via a device extension — device operands. Flattens each operand to a vector (1-D, or 2-D with a size-1 row/column) and throws if either is not vector-shaped or the lengths differ. Both operands are Array<T> (the deduction firewall).
T | the element type; |
Array | the container template. |
a, b | same-length vectors. |
Σ aᵢbᵢ as the scalar T.
O(n).
none for contiguous operands (read in place); a non-contiguous view packs once O(n).
LinalgCompileRun.Dot LinalgCompileRun.ComplexDotStdlibE2E.Linalg StdlibE2E.LinalgComplexvdot · 2 overloads
Vector dot product.
For a REAL element this is the bilinear Σ aᵢbᵢ (identical to dot and inner); for a complex element it is the conjugate-linear Hermitian inner product ⟨a, b⟩ = Σ conj(aᵢ)·bᵢ (numpy's vdot, conjugating the first argument) — one two-layer template, the conjugation chosen at compile time by if constexpr. vdot(a, a) is the real ‖a‖².
T | the element type; |
Array | the container template. |
a, b | same-length vectors. |
Σ aᵢbᵢ (real) or Σ conj(aᵢ)·bᵢ (complex), as the scalar T.
O(n).
none for contiguous operands; a non-contiguous view packs once O(n).
LinalgCompileRun.Vdot LinalgCompileRun.ComplexVdotStdlibE2E.Linalg StdlibE2E.LinalgComplexinner · 2 overloads
Inner product of two vectors — the bilinear Σ aᵢbᵢ (numpy's inner; same as dot for flattened vectors).
One two-layer template over the element and container.
T | the element type; |
Array | the container template. |
a, b | same-length vectors. |
Σ aᵢbᵢ as the scalar T.
O(n).
none for contiguous operands; a non-contiguous view packs once O(n).
LinalgRoutines.VdotInnerOuterKronLinalgCompileRun.InnerStdlibE2E.Linalgtrace · 2 overloads
Trace: the sum of the matrix diagonal, as the scalar T.
Requires a 2-D matrix (throws otherwise); rectangular matrices sum min(r, c) diagonal entries.
T | the element type; |
Array | the container template. |
a | a 2-D matrix. |
Σ aᵢᵢ as the scalar T.
O(min(r, c)).
none (strided diagonal read straight from the buffer).
LinalgRoutines.ProductsAndTraceLinalgCompileRun.TraceStdlibE2E.Linalg SystemApps.LinearSolveouter · 2 overloads
Outer product of two vectors.
Flattens both operands to vectors and forms the full rank-1 matrix; any pair of vector lengths is accepted (no matching constraint). Allocates the result via Array<T>::uninitialized and fills it through the out-parameter kernel — the host SIMD path, or a device shader when Array is a device container (selected by concept).
a | length-n vector. |
b | length-m vector. |
n×m matrix aᵢbⱼ.
O(n·m).
allocates only the n×m result; operands read in place when contiguous.
LinalgRoutines.VdotInnerOuterKronLinalgCompileRun.OuterStdlibE2E.Linalgconj_transpose · 2 overloads
Conjugate transpose (Hermitian adjoint) Aᴴ: transpose, then conjugate every entry (a plain transpose for a real element — the conjugation is compiled out).
A matrix is Hermitian iff conj_transpose(A) == A.
a | a 2-D matrix. |
the c×r adjoint of an r×c input; throws on non-2-D input.
O(r·c).
allocates only the c×r result; a non-contiguous operand is packed once into scratch.
LinalgRoutines.ComplexProductsLinalgCompileRun.ConjTransposeStdlibE2E.LinalgComplexkron · 2 overloads
Kronecker product.
Requires both operands to be 2-D (throws otherwise) and replaces each entry of a with that scalar times the whole of b, giving the (m·p)×(k·q) block matrix; no dimension matching is needed.
a | m×k matrix. |
b | p×q matrix. |
(m·p)×(k·q) block product.
O(n⁴) in the output area.
allocates only the (m·p)×(k·q) result; a non-contiguous operand is packed once into scratch.
LinalgRoutines.VdotInnerOuterKronLinalgCompileRun.KronStdlibE2E.LinalgRequires both operands to be 2-D (throws otherwise) and replaces each entry of a with that scalar times the whole of b, giving the (m·p)×(k·q) block matrix; no dimension matching is needed.
a | m×k matrix. |
b | p×q matrix. |
(m·p)×(k·q) block product.
O(n⁴) in the output area.
allocates a new NDArray result; a non-contiguous operand is packed once into scratch.
LinalgRoutines.VdotInnerOuterKronLinalgCompileRun.KronStdlibE2E.Linalgdot< double, ndarray::basic_ndarray > · 2 overloads
dot< Cplx, ndarray::basic_ndarray > · 2 overloads
vdot< double, ndarray::basic_ndarray > · 2 overloads
vdot< Cplx, ndarray::basic_ndarray > · 2 overloads
inner< double, ndarray::basic_ndarray > · 2 overloads
outer< double, ndarray::basic_ndarray > · 2 overloads
matmul< double, ndarray::basic_ndarray > · 2 overloads
matmul< Cplx, ndarray::basic_ndarray > · 2 overloads
conj_transpose< Cplx, ndarray::basic_ndarray > · 2 overloads
conj_transpose< double, ndarray::basic_ndarray > · 2 overloads
matrix_power · 2 overloads
Integer matrix power Aⁿ (negative n via inv).
Requires a square matrix (throws otherwise); n == 0 returns the identity, and negative n first inverts a via inv (so it inherits inv's singular-matrix behavior) before raising to |n|.
a | square matrix. |
n | exponent. |
Aⁿ.
O(n³·log|n|) by binary exponentiation.
allocates a new NDArray result; the binary-exponentiation matmul steps allocate their own intermediates.
LinalgRoutines.MatrixPowerLinalgCompileRun.MatrixPowerStdlibE2E.Linalgkron< double, ndarray::basic_ndarray > · 2 overloads
trace< double, ndarray::basic_ndarray > · 2 overloads
norm · 2 overloads
Norm: L2 for vectors, Frobenius for matrices.
Dispatches on rank: 1-D (or lower) inputs get the Euclidean L2 norm, 2-D inputs the Frobenius norm; either way it is the square root of the sum of squared entries. Two-layer over the element and container like every routine (the scalar-out kernel norm(out, a) is the host/device seam; host double is the shipped instantiation).
a | vector or matrix. |
√Σ xᵢ².
O(n) for vectors / O(n²) for matrices.
none for a contiguous operand (summed in place); a non-contiguous view packs once. Returns a double.
LinalgRoutines.NormAndRankLinalgCompileRun.NormStdlibE2E.Linalg SystemApps.LinearSolvenorm< double, ndarray::basic_ndarray > · 2 overloads
solve · 2 overloads
Solve A·x = b via LU with partial pivoting.
Factorizes a once then does forward/back substitution against b; requires a square and b a vector of matching length (throws otherwise). A singular a does not throw but yields a garbage/overflowing solution (pivots are nudged off zero rather than detected).
a | square coefficient matrix. |
b | right-hand-side vector. |
solution x.
O(n³).
allocates a new NDArray result; the LU factorization allocates its own O(n²) scratch.
LinalgRoutines.SolveDetInvLinalgCompileRun.SolveStdlibE2E.Linalg SystemApps.LinearSolvedet · 2 overloads
Determinant via LU with partial pivoting.
Computes the product of the LU pivots times the permutation sign; requires a square matrix (throws otherwise). A singular matrix yields a determinant of (or extremely near) zero rather than an error.
a | square matrix. |
det(A).
O(n³).
allocates scratch O(n²) for the factorization; returns a double.
LinalgRoutines.SolveDetInvLinalgCompileRun.DetStdlibE2E.Linalg SystemApps.LinearSolveslogdet · 2 overloads
Sign and log|det| via LU (overflow-safe determinant).
Sums the logs of the absolute LU pivots (avoiding the over/underflow of a raw product) and tracks the sign from the pivot signs and permutation parity; requires a square matrix (throws otherwise). A singular matrix gives a hugely negative logabsdet rather than −infinity, since a zero pivot is nudged to a tiny value during factorization.
a | square matrix. |
SLogDet.
O(n³).
allocates scratch O(n²) for the factorization (the struct members are plain doubles).
LinalgRoutines.SlogdetAndCondLinalgCompileRun.SlogdetStdlibE2E.Linalginv · 2 overloads
Matrix inverse via LU with partial pivoting.
Factorizes a once and back-solves against each identity column; requires a square matrix (throws otherwise). A singular a does not throw but produces garbage/overflowing entries since zero pivots are nudged rather than detected.
a | square matrix. |
A⁻¹.
O(n³).
allocates a new NDArray result; the LU factorization and the whole-identity back-solve allocate their own O(n²) scratch.
LinalgRoutines.SolveDetInvLinalgCompileRun.InvStdlibE2E.Linalgsolve< double, ndarray::basic_ndarray > · 2 overloads
det< double, ndarray::basic_ndarray > · 2 overloads
slogdet< double, ndarray::basic_ndarray > · 2 overloads
inv< double, ndarray::basic_ndarray > · 2 overloads
lstsq · 2 overloads
Least-squares solution min‖A·x − b‖ (computed as pinv (a)·b).
Forms the Moore–Penrose pseudo-inverse via SVD and multiplies it by b, so it handles over- and under-determined systems and returns the minimum-norm solution for rank-deficient a; b must be conformable for the matmul step.
a | m×n matrix. |
b | right-hand side. |
minimizing x.
iterative O(n³) via SVD.
allocates a new NDArray result; plus the intermediate n×m pseudo-inverse and its SVD scratch.
LinalgRoutines.LstsqLinalgCompileRun.LstsqStdlibE2E.Linalgcholesky · 2 overloads
Cholesky factor of a symmetric positive-definite matrix (throws otherwise).
Requires a square matrix and computes L column by column reading only the lower triangle of a; if any pivot (the diagonal under the square root) is non-positive it throws "matrix is not positive-definite", which also catches non-SPD or non-symmetric input.
a | square SPD matrix. |
lower-triangular L with A = L·Lᵀ.
O(n³).
allocates a new NDArray result; the factor is computed into O(n²) private scratch, then copied in.
LinalgRoutines.CholeskyAndQRLinalgCompileRun.CholeskyStdlibE2E.Linalgqr · 2 overloads
Reduced QR via Householder reflections (requires rows ≥ cols).
Applies successive Householder reflectors to triangularize a, returning the thin/reduced factors; throws "qr requires rows >= cols" for wide matrices. Rank-deficient columns (zero pivot norm) are skipped, leaving the corresponding R entries zero.
a | m×n matrix. |
QR with q (m×n, orthonormal cols) and r (n×n, upper-triangular).
O(n³).
allocates both members; the factorization works in O(m²) private scratch (a full m×m Q workspace), then copies the reduced factors in.
LinalgRoutines.CholeskyAndQRLinalgCompileRun.QrStdlibE2E.Linalgsvd · 2 overloads
Singular value decomposition (Golub–Reinsch; requires rows ≥ cols).
Reduces a to upper-bidiagonal form by Householder reflections, then diagonalizes it with implicit-shift QR (accumulating U and V), and sorts the singular values descending — the world-standard dense SVD (what LAPACK's dgesvd reduces to). Throws "svd requires rows >= cols" for wide matrices (transpose first); singular values come out non-negative.
a | m×n matrix. |
SVD with u (m×n), s (descending singular values), vh (n×n = Vᵀ).
iterative O(n³).
allocates all members; the Golub–Reinsch reduction allocates its own O(m·n) workspace.
LinalgRoutines.SvdAndEighLinalgCompileRun.SvdStdlibE2E.Linalgsvdvals · 2 overloads
Singular values only (≈ numpy.linalg.svd(a, compute_uv=False) / svdvals).
Runs the same Golub–Reinsch reduction as svd but takes the values-only fast path — it never accumulates U or V, and skips the (dominant) U/V Givens rotations in the QR sweep — so it is several times faster than the full decomposition. Accepts any shape (singular values of a and aᵀ coincide).
a | m×n matrix. |
length-min(m,n) vector of singular values, descending.
iterative O(n³), but a large constant factor below svd.
allocates a new NDArray result; the reduction still allocates its O(m·n) workspace (the values-only path skips the U/V accumulation work and result copies, not the working buffers).
LinalgRoutines.SvdAndEighLinalgCompileRun.SvdvalsStdlibE2E.Linalgpinv · 2 overloads
Moore–Penrose pseudo-inverse via SVD (any shape).
Computes V·diag(1/σ)·Uᵀ from a Golub–Reinsch SVD, transposing wide matrices internally so any shape works; singular values at or below a size-scaled tolerance are dropped (treated as zero) so it stays well-defined for rank-deficient input.
a | m×n matrix. |
n×m pseudo-inverse.
iterative O(n³) via SVD.
allocates a new NDArray result; the SVD and the assembly allocate their own O(m·n) scratch.
LinalgCompileRun.PinvStdlibE2E.Linalgcond · 2 overloads
2-norm condition number σ_max/σ_min (∞ if singular).
Takes the ratio of largest to smallest singular value from a Golub–Reinsch SVD (transposing internally for wide matrices); returns +infinity when the smallest singular value is exactly zero (singular/rank-deficient).
a | matrix. |
condition number.
iterative O(n³) via SVD.
allocates scratch O(n²) for the factorization; returns a double.
LinalgRoutines.SlogdetAndCondLinalgCompileRun.CondStdlibE2E.Linalgmatrix_rank · 2 overloads
Numerical rank from SVD singular-value thresholding.
Counts singular values above a tolerance scaled by the largest singular value and the matrix size (the standard numpy-style threshold); accepts any shape, transposing wide matrices internally.
a | matrix. |
rank.
iterative O(n³) via SVD.
allocates scratch O(n²) for the factorization.
LinalgRoutines.NormAndRankLinalgCompileRun.MatrixRankStdlibE2E.Linalgeigh · 2 overloads
void eigh(Array< ndarray::real_base_t< T > > &values, Array< T > &vectors, const Array< T > &a)source#eigh_result_t< T, Array > eigh(const Array< T > &a)source#Eigen-decomposition of a symmetric matrix (Householder tridiagonalization + QL).
Reduces a to tridiagonal form by Householder reflections, then diagonalizes it with implicit-shift QL, returning real eigenvalues sorted descending with matching eigenvector columns; it reads the full matrix and assumes symmetry rather than checking it, so asymmetric input yields meaningless results. Throws on a non-square matrix (or if the QL iteration fails to converge).
a | square symmetric matrix. |
Eig with descending values and matching eigenvectors.
iterative O(n³).
allocates both members; the solver allocates its own O(n²) scratch (a complex Hermitian input first embeds into a 2n×2n real matrix).
LinalgCompileRun.Eigh LinalgCompileRun.EighComplexStdlibE2E.Linalg StdlibE2E.LinalgComplexeigvalsh · 2 overloads
Array< ndarray::real_base_t< T > > eigvalsh(const Array< T > &a)source#Eigenvalues of a symmetric matrix, descending (tridiagonal QL).
Same tridiagonalization + QL as eigh but skips the eigenvector accumulation entirely (the bulk of the work), so it is roughly twice as fast as eigh; assumes (does not verify) symmetry and throws on a non-square matrix. ONE two-layer template collapsing the former real and complex (Hermitian) overloads: a real element takes the symmetric path, a complex element the Hermitian path (if constexpr). The spectrum is always REAL, returned as Array<real_base_t<T>>.
T | the element type ( |
Array | the container. |
a | square symmetric (real) / Hermitian (complex) matrix. |
length-n vector of real eigenvalues.
iterative O(n³).
allocates a new result; the solver allocates its own O(n²) scratch (a complex Hermitian input first embeds into a 2n×2n real matrix).
LinalgCompileRun.Eigvalsh LinalgCompileRun.EigvalshComplexStdlibE2E.Linalg StdlibE2E.LinalgComplexeigvalsh< double, ndarray::basic_ndarray > · 2 overloads
eigvalsh< Cplx, ndarray::basic_ndarray > · 2 overloads
eigh< double, ndarray::basic_ndarray > · 2 overloads
eigh< Cplx, ndarray::basic_ndarray > · 2 overloads
eig · 2 overloads
void eig(Array< ndarray::complex_of_t< T > > &values, Array< ndarray::complex_of_t< T > > &vectors, const Array< T > &a)source#EigC< Array< ndarray::complex_of_t< T > > > eig(const Array< T > &a)source#Eigen-decomposition of a general square matrix (complex spectrum and eigenvectors).
For a symmetric a it delegates to eigh (promoted to complex with zero imaginary part); otherwise it uses Hessenberg reduction + shifted QR for the eigenvalues, then inverse iteration for each eigenvector. A real matrix with a complex conjugate pair (e.g. a rotation) yields those complex eigenvalues and eigenvectors rather than throwing. Throws on a non-square matrix or if the QR iteration fails to converge.
a | square matrix. |
EigC with complex values and matching complex eigenvector columns.
iterative O(n³) via Hessenberg + shifted QR for the eigenvalues; the general (non-symmetric) eigenvectors add O(n⁴) — one inverse iteration, each with its own O(n³) complex LU factorization, per eigenvalue (a symmetric a stays O(n³) via eigh, which accumulates the vectors in the QL sweep).
allocates both members; plus O(n²) factorization scratch (on the non-symmetric path, a fresh complex n×n LU per eigenvalue).
LinalgRoutines.GeneralEigLinalgCompileRun.EigStdlibE2E.Linalgeigvals · 2 overloads
Array< ndarray::complex_of_t< T > > eigvals(const Array< T > &a)source#Eigenvalues of a general square matrix (complex), descending.
Routes symmetric input through tridiagonal QL and everything else through Hessenberg + shifted QR, then sorts the result descending (by real part, then by imaginary part). A real matrix with a complex conjugate pair yields those complex eigenvalues rather than throwing. Throws on a non-square matrix or non-convergence of the QR iteration.
a | square matrix. |
length-n complex vector of eigenvalues.
iterative O(n³).
allocates a new CNDArray result; the iteration allocates its own O(n²) scratch.
LinalgRoutines.SvdAndEighLinalgCompileRun.EigvalsStdlibE2E.Linalgmatrix_power< double, ndarray::basic_ndarray > · 2 overloads
lstsq< double, ndarray::basic_ndarray > · 2 overloads
cholesky< double, ndarray::basic_ndarray > · 2 overloads
svdvals< double, ndarray::basic_ndarray > · 2 overloads
pinv< double, ndarray::basic_ndarray > · 2 overloads
cond< double, ndarray::basic_ndarray > · 2 overloads
matrix_rank< double, ndarray::basic_ndarray > · 2 overloads
qr< double, ndarray::basic_ndarray > · 2 overloads
svd< double, ndarray::basic_ndarray > · 2 overloads
eig< double, ndarray::basic_ndarray > · 2 overloads
eigvals< double, ndarray::basic_ndarray > · 2 overloads
Instruction sets this build targets, e.g.
"AVX2;FMA" (x86-64), "NEON" (ARM), or "scalar".
;-separated feature list.
O(1); reflects compile-time target flags (e.g. -march=native), not a runtime CPUID probe. Allocates the returned std::string.
O(1).
the returned feature string.
LinalgSmoke.SimdFeaturesReportedWidth, in doubles, of the widest SIMD lane this build targets (1 = scalar, 2 = SSE2/NEON, 4 = AVX, 8 = AVX-512).
lane width.
O(1).
none. Useful for sizing blocked kernels.
LinalgSmoke.SimdFeaturesReportedTypes
Whether a reduction conjugates its first operand.
element_t: the scalar an array-like stores — its nested value_type, with any reference/cv-qualification on A stripped first so const NDArray& and NDArray yield the same element type.
Undefined for a type with no value_type (which simply makes the concepts below unsatisfied for it, never a hard error).
location_t: shorthand for the location tag of A (its location_of ::type), with any reference/cv-qualification stripped from A first.
A complex scalar (std::complex<double>) — the element type of CNDArray and the return type of the complex inner products dot / vdot.
A complex array (basic_ndarray<std::complex<double>>) — what the general eigensolvers return, since a real matrix can have complex eigenvalues.
Prints element-wise as a+bj via cheatah::ndarray::to_string.
std::conditional_t< ndarray::is_complex_v< T >, EighC< Array< ndarray::real_base_t< T > >, Array< T > >, Eig< Array< T > > > eigh_result_t
source#
The result type of the unified eigh: for a real element, Eig<Array<T>> (real values + vectors); for a complex element, EighC<Array<real>, Array<T>> (real values, complex vectors).
eigh's return type differs per element, so it is expressed here (not auto) so the header declaration knows it without seeing the definition.
