cheatah
Module

linalg

Benchmarks
Every measured table for linalg — with its stamp (commit, host, harness) and the commands that reproduce it — is on the linalg benchmarks page.

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 never slower than GLM anywhere the two 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.

Usage

import ndarray
import linalg              # links ndarray for you

let A = ndarray.reshape(ndarray.array([4.0, 1.0, 1.0, 3.0]), [2, 2])
let b = ndarray.array([1.0, 2.0])
let x = linalg.solve(A, b) # A·x = b
let d = linalg.det(A)

Functions

Every routine below returns a fresh result. Each also has an out-parameter overload (solve(out, A, b), svd(u, s, vh, A), …) in routines.hpp that writes into caller-owned storage: the products and reductions stay off the heap on contiguous operands, and the factorizations keep their private scratch.

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 via inv).

  • 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 — numpy's eigvalsh returns ascending — with eigenvector columns reordered to match.

import io
import ndarray
import linalg

# 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+0j

Norms & numbers

  • norm — L2 (vector) / Frobenius (matrix).

  • cond — 2-norm condition number.

  • det / slogdet — determinant (LU); slogdet is 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): 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 in doubles.

A build with no SIMD returns identical results, just slower — SIMD is never a correctness dependency. The model, and its compile-time-dispatch limitation, is in simd.hpp.

Performance

linalg goes head-to-head with NumPy, whose array ops dispatch to BLAS/LAPACK — hand-tuned, vectorized, often multi-threaded Fortran — and, in a separate single-core harness, with Eigen 3.4. Each function's Performance row on this page points at the generated tables; the full size-dependence, and the BLAS caveat that governs how to read it, is on the linalg benchmarks page.

cheatah does all of this on one core, by design — no hidden threads, nothing to tune. NumPy's and Eigen's remaining edges are the large blocked problems where threaded BLAS-3 kernels spread the work; the Performance guide has the 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

fn matmul · 2 overloads
Array< T > matmul(const Array< T > &a, const Array< T > &b)source#
void matmul(Array< T > &out, const Array< T > &a, const Array< T > &b)source#

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 and fills it through the out-parameter kernel — the host SIMD path, or a device shader when Array is a device container.

Template parameters
T

the element type (double / std::complex<double>).

Array

the container template.

Parameters
a

m×k matrix, or a B×m×k batch of matrices.

b

k×p matrix, or a B×k×p batch.

Returns

m×p product (or the B×m×p batch), an Array<T> of the same container and element.

Complexity

O(n³) (× B for a batch).

Allocation

allocates only the result; operands read in place (a strided host view packs once).

Concurrency

deliberately single-threaded (the fastest-per-core contract); parallelize across independent products in the caller.

Compile-run testLinalgCompileRun.Matmul
Performancenumeric — the honest baseline is NumPy/LAPACK, not a pure-Python loop; see the vs-NumPy table on the linalg benchmarks page · how measured
fn std::size_t vector_len(const A &a) source#

The 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.

Template parameters
A

the (located) container type.

Parameters
a

the operand whose vector length is wanted.

Returns

the element count of the flattened vector.

Complexity

O(1).

Allocation

none.

fn dot · 2 overloads
T dot(const Array< T > &a, const Array< T > &b)source#
void dot(T &out, const Array< T > &a, const Array< T > &b)source#

Dot product: 1-D inner product (vectors flattened) — the bilinear Σ aᵢbᵢ.

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).

Template parameters
T

the element type.

Array

the container template.

Parameters
a, b

same-length vectors.

Returns

Σ aᵢbᵢ as the scalar T.

Complexity

O(n).

Allocation

none for contiguous operands (read in place); a non-contiguous view packs once O(n).

Performancenumeric — the honest baseline is NumPy/LAPACK, not a pure-Python loop; see the vs-NumPy table on the linalg benchmarks page · how measured
fn vdot · 2 overloads
T vdot(const Array< T > &a, const Array< T > &b)source#
void vdot(T &out, const Array< T > &a, const Array< T > &b)source#

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); vdot(a, a) is ‖a‖².

Template parameters
T

the element type.

Array

the container template.

Parameters
a, b

same-length vectors.

Returns

Σ aᵢbᵢ (real) or Σ conj(aᵢ)·bᵢ (complex), as the scalar T.

Complexity

O(n).

Allocation

none for contiguous operands; a non-contiguous view packs once O(n).

Performancenumeric — the honest baseline is NumPy/LAPACK, not a pure-Python loop; see the vs-NumPy table on the linalg benchmarks page · how measured
fn inner · 2 overloads
T inner(const Array< T > &a, const Array< T > &b)source#
void inner(T &out, const Array< T > &a, const Array< T > &b)source#

Inner product of two vectors — the bilinear Σ aᵢbᵢ (numpy's inner; same as dot).

Template parameters
T

the element type.

Array

the container template.

Parameters
a, b

same-length vectors.

Returns

Σ aᵢbᵢ as the scalar T.

Complexity

O(n).

Allocation

none for contiguous operands; a non-contiguous view packs once O(n).

Compile-run testLinalgCompileRun.Inner
System testStdlibE2E.Linalg
Performancenumeric — the honest baseline is NumPy/LAPACK, not a pure-Python loop; see the vs-NumPy table on the linalg benchmarks page · how measured
fn trace · 2 overloads
T trace(const Array< T > &a)source#
void trace(T &out, const Array< T > &a)source#

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.

Template parameters
T

the element type.

Array

the container template.

Parameters
a

a 2-D matrix.

Returns

Σ aᵢᵢ as the scalar T.

Complexity

O(min(r, c)).

Allocation

none (strided diagonal read straight from the buffer).

Compile-run testLinalgCompileRun.Trace
Performancenumeric — the honest baseline is NumPy/LAPACK, not a pure-Python loop; see the vs-NumPy table on the linalg benchmarks page · how measured
fn outer · 2 overloads
Array< T > outer(const Array< T > &a, const Array< T > &b)source#
void outer(Array< T > &out, const Array< T > &a, const Array< T > &b)source#

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).

Parameters
a

length-n vector.

b

length-m vector.

Returns

n×m matrix aᵢbⱼ.

Complexity

O(n·m).

Allocation

allocates only the n×m result; operands read in place when contiguous.

Compile-run testLinalgCompileRun.Outer
System testStdlibE2E.Linalg
Performancenumeric — the honest baseline is NumPy/LAPACK, not a pure-Python loop; see the vs-NumPy table on the linalg benchmarks page · how measured
fn conj_transpose · 2 overloads
Array< T > conj_transpose(const Array< T > &a)source#
void conj_transpose(Array< T > &out, const Array< T > &a)source#

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.

Parameters
a

a 2-D matrix.

Returns

the c×r adjoint of an r×c input; throws on non-2-D input.

Complexity

O(r·c).

Allocation

allocates only the c×r result; a non-contiguous operand is packed once into scratch.

Performancenumeric — the honest baseline is NumPy/LAPACK, not a pure-Python loop; see the vs-NumPy table on the linalg benchmarks page · how measured
fn kron · 2 overloads
Array< T > kron(const Array< T > &a, const Array< T > &b)source#
void kron(Array< T > &out, const Array< T > &a, const Array< T > &b)source#

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.

Parameters
a

m×k matrix.

b

p×q matrix.

Returns

(m·p)×(k·q) block product.

Complexity

O(n⁴) in the output area.

Allocation

allocates only the (m·p)×(k·q) result; a non-contiguous operand packs once into scratch.

Compile-run testLinalgCompileRun.Kron
System testStdlibE2E.Linalg
Performancenumeric — the honest baseline is NumPy/LAPACK, not a pure-Python loop; see the vs-NumPy table on the linalg benchmarks page · how measured
fn dot< double, ndarray::basic_ndarray > · 2 overloads
template void dot< double, ndarray::basic_ndarray >(double &, const NDArray &, const NDArray &)source#
template double dot< double, ndarray::basic_ndarray >(const NDArray &, const NDArray &)source#
fn dot< Cplx, ndarray::basic_ndarray > · 2 overloads
template void dot< Cplx, ndarray::basic_ndarray >(Cplx &, const CNDArray &, const CNDArray &)source#
template Cplx dot< Cplx, ndarray::basic_ndarray >(const CNDArray &, const CNDArray &)source#
fn vdot< double, ndarray::basic_ndarray > · 2 overloads
template void vdot< double, ndarray::basic_ndarray >(double &, const NDArray &, const NDArray &)source#
template double vdot< double, ndarray::basic_ndarray >(const NDArray &, const NDArray &)source#
fn vdot< Cplx, ndarray::basic_ndarray > · 2 overloads
template void vdot< Cplx, ndarray::basic_ndarray >(Cplx &, const CNDArray &, const CNDArray &)source#
template Cplx vdot< Cplx, ndarray::basic_ndarray >(const CNDArray &, const CNDArray &)source#
fn inner< double, ndarray::basic_ndarray > · 2 overloads
template void inner< double, ndarray::basic_ndarray >(double &, const NDArray &, const NDArray &)source#
template double inner< double, ndarray::basic_ndarray >(const NDArray &, const NDArray &)source#
fn outer< double, ndarray::basic_ndarray > · 2 overloads
template void outer< double, ndarray::basic_ndarray >(NDArray &, const NDArray &, const NDArray &)source#
template NDArray outer< double, ndarray::basic_ndarray >(const NDArray &, const NDArray &)source#
fn matmul< double, ndarray::basic_ndarray > · 2 overloads
template void matmul< double, ndarray::basic_ndarray >(NDArray &, const NDArray &, const NDArray &)source#
template NDArray matmul< double, ndarray::basic_ndarray >(const NDArray &, const NDArray &)source#
fn matmul< Cplx, ndarray::basic_ndarray > · 2 overloads
template void matmul< Cplx, ndarray::basic_ndarray >(CNDArray &, const CNDArray &, const CNDArray &)source#
template CNDArray matmul< Cplx, ndarray::basic_ndarray >(const CNDArray &, const CNDArray &)source#
fn conj_transpose< Cplx, ndarray::basic_ndarray > · 2 overloads
template void conj_transpose< Cplx, ndarray::basic_ndarray >(CNDArray &, const CNDArray &)source#
template CNDArray conj_transpose< Cplx, ndarray::basic_ndarray >(const CNDArray &)source#
fn conj_transpose< double, ndarray::basic_ndarray > · 2 overloads
template void conj_transpose< double, ndarray::basic_ndarray >(NDArray &, const NDArray &)source#
template NDArray conj_transpose< double, ndarray::basic_ndarray >(const NDArray &)source#
fn matrix_power · 2 overloads
void matrix_power(Array< T > &out, const Array< T > &a, long long n)source#
Array< T > matrix_power(const Array< T > &a, long long n)source#

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|.

Parameters
a

square matrix.

n

exponent.

Returns

Aⁿ.

Complexity

O(n³·log|n|) by binary exponentiation.

Allocation

allocates a new NDArray result; the binary-exponentiation matmul steps allocate their own intermediates.

System testStdlibE2E.Linalg
Performancenumeric — the honest baseline is NumPy/LAPACK, not a pure-Python loop; see the vs-NumPy table on the linalg benchmarks page · how measured
fn kron< double, ndarray::basic_ndarray > · 2 overloads
template void kron< double, ndarray::basic_ndarray >(NDArray &, const NDArray &, const NDArray &)source#
template NDArray kron< double, ndarray::basic_ndarray >(const NDArray &, const NDArray &)source#
fn trace< double, ndarray::basic_ndarray > · 2 overloads
template void trace< double, ndarray::basic_ndarray >(double &, const NDArray &)source#
template double trace< double, ndarray::basic_ndarray >(const NDArray &)source#
fn norm · 2 overloads
void norm(T &out, const Array< T > &a)source#
T norm(const Array< T > &a)source#

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).

Parameters
a

vector or matrix.

Returns

√Σ xᵢ².

Complexity

O(n) for vectors / O(n²) for matrices.

Allocation

none for a contiguous operand (summed in place); a non-contiguous view packs once. Returns a double.

Compile-run testLinalgCompileRun.Norm
Performancenumeric — the honest baseline is NumPy/LAPACK, not a pure-Python loop; see the vs-NumPy table on the linalg benchmarks page · how measured
fn norm< double, ndarray::basic_ndarray > · 2 overloads
template void norm< double, ndarray::basic_ndarray >(double &, const NDArray &)source#
template double norm< double, ndarray::basic_ndarray >(const NDArray &)source#
fn solve · 2 overloads
void solve(Array< T > &out, const Array< T > &a, const Array< T > &b)source#
Array< T > solve(const Array< T > &a, const Array< T > &b)source#

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).

Parameters
a

square coefficient matrix.

b

right-hand-side vector.

Returns

solution x.

Complexity

O(n³).

Allocation

allocates a new NDArray result; the LU factorization allocates its own O(n²) scratch.

Compile-run testLinalgCompileRun.Solve
Performancenumeric — the honest baseline is NumPy/LAPACK, not a pure-Python loop; see the vs-NumPy table on the linalg benchmarks page · how measured
fn det · 2 overloads
void det(T &out, const Array< T > &a)source#
T det(const Array< T > &a)source#

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.

Parameters
a

square matrix.

Returns

det(A).

Complexity

O(n³).

Allocation

allocates scratch O(n²) for the factorization; returns a double.

Compile-run testLinalgCompileRun.Det
Performancenumeric — the honest baseline is NumPy/LAPACK, not a pure-Python loop; see the vs-NumPy table on the linalg benchmarks page · how measured
fn slogdet · 2 overloads
void slogdet(SLogDet &out, const Array< T > &a)source#
SLogDet slogdet(const Array< T > &a)source#

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.

Parameters
a

square matrix.

Returns

SLogDet.

Complexity

O(n³).

Allocation

allocates scratch O(n²) for the factorization (the struct members are plain doubles).

Compile-run testLinalgCompileRun.Slogdet
System testStdlibE2E.Linalg
Performancenumeric — the honest baseline is NumPy/LAPACK, not a pure-Python loop; see the vs-NumPy table on the linalg benchmarks page · how measured
fn inv · 2 overloads
void inv(Array< T > &out, const Array< T > &a)source#
Array< T > inv(const Array< T > &a)source#

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.

Parameters
a

square matrix.

Returns

A⁻¹.

Complexity

O(n³).

Allocation

allocates a new NDArray result; the LU factorization and the whole-identity back-solve allocate their own O(n²) scratch.

Compile-run testLinalgCompileRun.Inv
System testStdlibE2E.Linalg
Performancenumeric — the honest baseline is NumPy/LAPACK, not a pure-Python loop; see the vs-NumPy table on the linalg benchmarks page · how measured
fn solve< double, ndarray::basic_ndarray > · 2 overloads
template void solve< double, ndarray::basic_ndarray >(NDArray &, const NDArray &, const NDArray &)source#
template NDArray solve< double, ndarray::basic_ndarray >(const NDArray &, const NDArray &)source#
fn det< double, ndarray::basic_ndarray > · 2 overloads
template void det< double, ndarray::basic_ndarray >(double &, const NDArray &)source#
template double det< double, ndarray::basic_ndarray >(const NDArray &)source#
fn slogdet< double, ndarray::basic_ndarray > · 2 overloads
template void slogdet< double, ndarray::basic_ndarray >(SLogDet &, const NDArray &)source#
template SLogDet slogdet< double, ndarray::basic_ndarray >(const NDArray &)source#
fn inv< double, ndarray::basic_ndarray > · 2 overloads
template void inv< double, ndarray::basic_ndarray >(NDArray &, const NDArray &)source#
template NDArray inv< double, ndarray::basic_ndarray >(const NDArray &)source#
fn lstsq · 2 overloads
void lstsq(Array< T > &out, const Array< T > &a, const Array< T > &b)source#
Array< T > lstsq(const Array< T > &a, const Array< T > &b)source#

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.

Parameters
a

m×n matrix.

b

right-hand side.

Returns

minimizing x.

Complexity

iterative O(n³) via SVD.

Allocation

allocates a new NDArray result; plus the intermediate n×m pseudo-inverse and its SVD scratch.

Compile-run testLinalgCompileRun.Lstsq
System testStdlibE2E.Linalg
Performancenumeric — the honest baseline is NumPy/LAPACK, not a pure-Python loop; see the vs-NumPy table on the linalg benchmarks page · how measured
fn cholesky · 2 overloads
void cholesky(Array< T > &out, const Array< T > &a)source#
Array< T > cholesky(const Array< T > &a)source#

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.

Parameters
a

square SPD matrix.

Returns

lower-triangular L with A = L·Lᵀ.

Complexity

O(n³).

Allocation

allocates a new NDArray result; the factor is computed into O(n²) private scratch, then copied in.

System testStdlibE2E.Linalg
Performancenumeric — the honest baseline is NumPy/LAPACK, not a pure-Python loop; see the vs-NumPy table on the linalg benchmarks page · how measured
fn qr · 2 overloads
void qr(Array< T > &q, Array< T > &r, const Array< T > &a)source#
QR< Array< T > > qr(const Array< T > &a)source#

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.

Parameters
a

m×n matrix.

Returns

QR with q (m×n, orthonormal cols) and r (n×n, upper-triangular).

Complexity

O(m²·n) — n reflectors each applied to the full m×m Q; O(n³) when square.

Allocation

allocates both members; the factorization works in O(m²) private scratch (a full m×m Q workspace), then copies the reduced factors in.

Compile-run testLinalgCompileRun.Qr
System testStdlibE2E.Linalg
Performancenumeric — the honest baseline is NumPy/LAPACK, not a pure-Python loop; see the vs-NumPy table on the linalg benchmarks page · how measured
fn svd · 2 overloads
void svd(Array< T > &u, Array< T > &sv, Array< T > &vhh, const Array< T > &a)source#
SVD< Array< T > > svd(const Array< T > &a)source#

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.

Parameters
a

m×n matrix.

Returns

SVD with u (m×n), s (descending singular values), vh (n×n = Vᵀ).

Complexity

iterative O(m·n²); O(n³) when square.

Allocation

allocates all members; the Golub–Reinsch reduction allocates its own O(m·n) workspace.

Compile-run testLinalgCompileRun.Svd
System testStdlibE2E.Linalg
Performancenumeric — the honest baseline is NumPy/LAPACK, not a pure-Python loop; see the vs-NumPy table on the linalg benchmarks page · how measured
fn svdvals · 2 overloads
void svdvals(Array< T > &out, const Array< T > &a)source#
Array< T > svdvals(const Array< T > &a)source#

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 U/V Givens rotations in the QR sweep — so it beats the full decomposition by a margin that grows with n (see the linalg benchmark page). Accepts any shape (singular values of a and aᵀ coincide).

Parameters
a

m×n matrix.

Returns

length-min(m,n) vector of singular values, descending.

Complexity

iterative O(n³), but a large constant factor below svd.

Allocation

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).

Compile-run testLinalgCompileRun.Svdvals
System testStdlibE2E.Linalg
fn pinv · 2 overloads
void pinv(Array< T > &out, const Array< T > &a)source#
Array< T > pinv(const Array< T > &a)source#

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.

Parameters
a

m×n matrix.

Returns

n×m pseudo-inverse.

Complexity

iterative O(n³) via SVD.

Allocation

allocates a new NDArray result; the SVD and the assembly allocate their own O(m·n) scratch.

Compile-run testLinalgCompileRun.Pinv
System testStdlibE2E.Linalg
Performancenumeric — the honest baseline is NumPy/LAPACK, not a pure-Python loop; see the vs-NumPy table on the linalg benchmarks page · how measured
fn cond · 2 overloads
void cond(T &out, const Array< T > &a)source#
T cond(const Array< T > &a)source#

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).

Parameters
a

matrix.

Returns

condition number.

Complexity

iterative O(n³) via SVD.

Allocation

allocates scratch O(n²) for the factorization; returns a double.

Compile-run testLinalgCompileRun.Cond
System testStdlibE2E.Linalg
Performancenumeric — the honest baseline is NumPy/LAPACK, not a pure-Python loop; see the vs-NumPy table on the linalg benchmarks page · how measured
fn matrix_rank · 2 overloads
void matrix_rank(long long &out, const Array< T > &a)source#
long long matrix_rank(const Array< T > &a)source#

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.

Parameters
a

matrix.

Returns

rank.

Complexity

iterative O(n³) via SVD.

Allocation

allocates scratch O(n²) for the factorization.

System testStdlibE2E.Linalg
Performancenumeric — the honest baseline is NumPy/LAPACK, not a pure-Python loop; see the vs-NumPy table on the linalg benchmarks page · how measured
fn eigh · 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).

Parameters
a

square symmetric matrix.

Returns

Eig (real input) or EighC (complex Hermitian input): descending real values and matching eigenvector columns.

Complexity

iterative O(n³).

Allocation

allocates both members; the solver allocates its own O(n²) scratch (a complex Hermitian input first embeds into a 2n×2n real matrix).

Performancenumeric — the honest baseline is NumPy/LAPACK, not a pure-Python loop; see the vs-NumPy table on the linalg benchmarks page · how measured
fn eigvalsh · 2 overloads
void eigvalsh(Array< ndarray::real_base_t< T > > &out, const Array< T > &a)source#
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, so it runs well under eigh's cost at large n (see the linalg benchmark page); 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>>.

Template parameters
T

the element type (double or std::complex<double>).

Array

the container.

Parameters
a

square symmetric (real) / Hermitian (complex) matrix.

Returns

length-n vector of real eigenvalues.

Complexity

iterative O(n³).

Allocation

allocates a new result; the solver allocates its own O(n²) scratch (a complex Hermitian input first embeds into a 2n×2n real matrix).

Performancenumeric — the honest baseline is NumPy/LAPACK, not a pure-Python loop; see the vs-NumPy table on the linalg benchmarks page · how measured
fn eigvalsh< double, ndarray::basic_ndarray > · 2 overloads
template void eigvalsh< double, ndarray::basic_ndarray >(NDArray &, const NDArray &)source#
template NDArray eigvalsh< double, ndarray::basic_ndarray >(const NDArray &)source#
fn eigvalsh< Cplx, ndarray::basic_ndarray > · 2 overloads
template void eigvalsh< Cplx, ndarray::basic_ndarray >(NDArray &, const CNDArray &)source#
template NDArray eigvalsh< Cplx, ndarray::basic_ndarray >(const CNDArray &)source#
fn eigh< double, ndarray::basic_ndarray > · 2 overloads
template void eigh< double, ndarray::basic_ndarray >(NDArray &, NDArray &, const NDArray &)source#
template Eig< NDArray > eigh< double, ndarray::basic_ndarray >(const NDArray &)source#
fn eigh< Cplx, ndarray::basic_ndarray > · 2 overloads
template void eigh< Cplx, ndarray::basic_ndarray >(NDArray &, CNDArray &, const CNDArray &)source#
template EighC< NDArray, CNDArray > eigh< Cplx, ndarray::basic_ndarray >(const CNDArray &)source#
fn 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.

Parameters
a

square matrix.

Returns

EigC with complex values and matching complex eigenvector columns.

Complexity

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).

Allocation

allocates both members; plus O(n²) factorization scratch (on the non-symmetric path, a fresh complex n×n LU per eigenvalue).

Compile-run testLinalgCompileRun.Eig
System testStdlibE2E.Linalg
Performancenumeric — the honest baseline is NumPy/LAPACK, not a pure-Python loop; see the vs-NumPy table on the linalg benchmarks page · how measured
fn eigvals · 2 overloads
void eigvals(Array< ndarray::complex_of_t< T > > &out, const Array< T > &a)source#
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.

Parameters
a

square matrix.

Returns

length-n complex vector of eigenvalues.

Complexity

iterative O(n³).

Allocation

allocates a new CNDArray result; the iteration allocates its own O(n²) scratch.

Compile-run testLinalgCompileRun.Eigvals
System testStdlibE2E.Linalg
Performancenumeric — the honest baseline is NumPy/LAPACK, not a pure-Python loop; see the vs-NumPy table on the linalg benchmarks page · how measured
fn matrix_power< double, ndarray::basic_ndarray > · 2 overloads
template void matrix_power< double, ndarray::basic_ndarray >(NDArray &, const NDArray &, long long)source#
template NDArray matrix_power< double, ndarray::basic_ndarray >(const NDArray &, long long)source#
fn lstsq< double, ndarray::basic_ndarray > · 2 overloads
template void lstsq< double, ndarray::basic_ndarray >(NDArray &, const NDArray &, const NDArray &)source#
template NDArray lstsq< double, ndarray::basic_ndarray >(const NDArray &, const NDArray &)source#
fn cholesky< double, ndarray::basic_ndarray > · 2 overloads
template void cholesky< double, ndarray::basic_ndarray >(NDArray &, const NDArray &)source#
template NDArray cholesky< double, ndarray::basic_ndarray >(const NDArray &)source#
fn svdvals< double, ndarray::basic_ndarray > · 2 overloads
template void svdvals< double, ndarray::basic_ndarray >(NDArray &, const NDArray &)source#
template NDArray svdvals< double, ndarray::basic_ndarray >(const NDArray &)source#
fn pinv< double, ndarray::basic_ndarray > · 2 overloads
template void pinv< double, ndarray::basic_ndarray >(NDArray &, const NDArray &)source#
template NDArray pinv< double, ndarray::basic_ndarray >(const NDArray &)source#
fn cond< double, ndarray::basic_ndarray > · 2 overloads
template void cond< double, ndarray::basic_ndarray >(double &, const NDArray &)source#
template double cond< double, ndarray::basic_ndarray >(const NDArray &)source#
fn matrix_rank< double, ndarray::basic_ndarray > · 2 overloads
template void matrix_rank< double, ndarray::basic_ndarray >(long long &, const NDArray &)source#
template long long matrix_rank< double, ndarray::basic_ndarray >(const NDArray &)source#
fn qr< double, ndarray::basic_ndarray > · 2 overloads
template void qr< double, ndarray::basic_ndarray >(NDArray &, NDArray &, const NDArray &)source#
template QR< NDArray > qr< double, ndarray::basic_ndarray >(const NDArray &)source#
fn svd< double, ndarray::basic_ndarray > · 2 overloads
template void svd< double, ndarray::basic_ndarray >(NDArray &, NDArray &, NDArray &, const NDArray &)source#
template SVD< NDArray > svd< double, ndarray::basic_ndarray >(const NDArray &)source#
fn eig< double, ndarray::basic_ndarray > · 2 overloads
template void eig< double, ndarray::basic_ndarray >(CNDArray &, CNDArray &, const NDArray &)source#
template EigC< CNDArray > eig< double, ndarray::basic_ndarray >(const NDArray &)source#
fn eigvals< double, ndarray::basic_ndarray > · 2 overloads
template void eigvals< double, ndarray::basic_ndarray >(CNDArray &, const NDArray &)source#
template CNDArray eigvals< double, ndarray::basic_ndarray >(const NDArray &)source#
fn std::string simd_features() source#

Instruction sets this build targets, e.g.

"AVX2;FMA" (x86-64), "NEON" (ARM), or "scalar".

Reflects compile-time target flags (e.g. -march=native), not a runtime CPUID probe.

Returns

;-separated feature list.

Complexity

O(1).

Allocation

allocates the result string.

Performancequeries CPU SIMD support — not a hot path · how measured
fn int simd_lane_doubles() noexcept source#

Width, in doubles, of the widest SIMD lane this build targets.

1 = scalar, 2 = SSE2/NEON, 4 = AVX, 8 = AVX-512; useful for sizing blocked kernels.

Returns

lane width.

Complexity

O(1).

Allocation

none.

Performancequeries CPU SIMD width — not a hot path · how measured

Types

enum std::uint8_t Conj source#

Whether a reduction conjugates its first operand.

dot/inner are bilinear (Conj::None, Σ aᵢbᵢ); vdot is the conjugate-linear Hermitian inner product (Conj::Conjugate, Σ conj(aᵢ)·bᵢ). For a REAL element the conjugate is the identity, so both fold to the same code via if constexpr (is_complex_v<T> && mode == Conj::Conjugate).

type typename std::remove_cvref_t< A >::value_type element_t source#

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).

type typename location_of< std::remove_cvref_t< A > >::type location_t source#

location_t: shorthand for the location tag of A (its location_of ::type), with any reference/cv-qualification stripped from A first.

type std::complex< double > Cplx source#

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.

type 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.