Skip to content

Vectors & Linear Algebra

linaldb gives you two ways to do math on tensors outside of SELECT: functional keywords and infix operators. (For vector functions used inside SQL, such as COSINE_SIM and AVG_VEC, see Querying.)

  • ADD a b / SUBTRACT a b / MULTIPLY a b / DIVIDE a b: element-wise.
  • MATMUL a b: standard matrix multiplication.
  • TRANSPOSE a: swap dimensions of a matrix/tensor.
  • RESHAPE a TO [dims]: change shape without copying data.
  • FLATTEN a: convert a multidimensional tensor to a 1D vector.
  • NORMALIZE a: scale a vector to unit length (L2 norm).
  • SCALE a BY n: multiply all elements by scalar n.
  • STACK t1 t2 ...: combine tensors along axis 0.
  • SUM a / MEAN a / STDEV a / VARIANCE a / MEDIAN a: reduce to a true scalar (rank-0 tensor, shape []), so it correctly broadcasts against a longer vector in a later ADD/SUBTRACT/MULTIPLY/DIVIDE (e.g. v - SUM v) instead of being treated as a mismatched same-rank vector. VARIANCE is the population variance: literally STDEV a squared.
  • QUANTILE a AT p: the p-th quantile of a’s elements (p in [0.0, 1.0]; p=0.5 is the median, matching MEDIAN a). True scalar result.
  • COVARIANCE a WITH b: population covariance between two same-shape tensors, treating each as a flat sample. True scalar result.
  • COVARIANCE MATRIX a: feature covariance matrix of a (rows = samples, columns = features): a Matrix(k, k) for a Matrix(n, k) input. Uses the standard n - 1 (Bessel-corrected) normalization: a different statistic from COVARIANCE a WITH b’s n normalization, not a bug.
VECTOR v = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0]
LET var = VARIANCE v -- 4.0
LET med = MEDIAN v -- 4.5
LET p90 = QUANTILE v AT 0.9
VECTOR w = [1.0, 3.0, 5.0, 3.0, 1.0, -1.0, -3.0, -5.0]
LET cov = COVARIANCE v WITH w -- -8.25 (anti-correlated)
MATRIX samples = [[1.0, 2.0], [3.0, 4.0], [5.0, 8.0]] -- rows = samples, cols = features
LET cm = COVARIANCE MATRIX samples -- Matrix(2, 2)

Prefix LET with LAZY (either word order) to defer computation: the expression is stored as a computation graph and materialized only when SHOW is called:

LAZY LET trend = STDEV sensor_3d -- deferred
LET LAZY trend = STDEV sensor_3d -- identical alias
SHOW trend -- triggers materialization
LET result = (v_a + v_b) / 2.0
LET scaled = m_a * 10
  • CORRELATE a WITH b: Pearson correlation between two vectors.
  • SIMILARITY a WITH b: cosine similarity, [-1.0, 1.0].
  • DISTANCE a TO b: Euclidean distance between points.
  • FFT a [WINDOW HANN|HAMMING]: real-to-complex forward FFT. a is a rank-1 Vector(N); result is a Matrix(2, N/2+1) (row 0 real, row 1 imaginary; only non-negative frequencies, the standard real-input FFT optimization). The optional WINDOW clause applies a Hann or Hamming window to a before transforming, to reduce spectral leakage. Omitting it is the original unwindowed (rectangular) behavior. HANN/HAMMING are plain identifiers, not reserved keywords.
  • IFFT a: complex-to-real inverse FFT from a Matrix(2, M) spectrum. Assumes the original signal length was even (reconstructs length 2*(M-1)). Keep the original vector around if you need an exact odd-length round trip.
  • MAGNITUDE a: power/magnitude spectrum from a Matrix(2, M) spectrum: Vector(M), sqrt(re² + im²) per bin.
  • PSD a WINDOW n [HANN|HAMMING]: power spectral density via averaged periodograms. Splits a into non-overlapping n-sample chunks, averages each chunk’s power spectrum. The optional trailing HANN/HAMMING applies that window to each chunk before its FFT (omitting it is the original unwindowed behavior). Still simplified vs. textbook Welch’s method (no 50% chunk overlap, even with a window applied): good for noise-floor estimation, not research-grade PSD.
  • WHITEN a WITH b: flattens a’s noise spectrum against a PSD estimate b. b must have exactly a.len()/2+1 entries. The standard first step before matched filtering.
  • BANDPASS a FROM low_hz TO high_hz WITH RATE sample_rate: brick-wall bandpass filter (zeros FFT bins outside the range, inverse-transforms back). Simplified vs. a real filter design (IIR/FIR): a hard cutoff introduces ringing at sharp edges.
  • MATCHED_FILTER a WITH b: FFT-based cross-correlation, the standard detection statistic: the peak (by absolute value) marks the best-matching lag between a (data) and b (template). The peak lag is relative to b’s own reference point, not an absolute location in a. Computes circular correlation (wraps at buffer edges).
VECTOR signal = [0.0, 1.0, 0.0, -1.0, 0.0, 1.0, 0.0, -1.0]
LET spectrum = FFT signal
LET recovered = IFFT spectrum
LET mag = MAGNITUDE spectrum
LET noise_floor = PSD signal WINDOW 8
LET whitened = WHITEN signal WITH noise_floor
LET filtered = BANDPASS signal FROM 35.0 TO 350.0 WITH RATE 4096.0
LET correlation = MATCHED_FILTER whitened WITH template

A complex spectrum (FFT output, and EIGENVALUES_GENERAL below) is an ordinary Matrix(2, N) by convention (row 0 real, row 1 imaginary) rather than a collection of scalar Complex values, so SHOW, persistence, TRANSPOSE, and row indexing all already work on it unmodified. The scalar Complex type (see Data & Resources) is a separate, deliberately scalar-only concept: a genuine Tensor<Complex> is out of scope.

Built on nalgebra (pure Rust). Every fallible operator here errors loudly on a singular, non-square, or non-symmetric input: never a silent NaN.

Single-output (bind with a plain LET name = ...):

Operator Description
TRACE a Sum of the diagonal. a must be a square Matrix. True scalar result.
DETERMINANT a a must be square. 0.0 for a singular matrix is a legitimate result.
RANK a Numerical rank via SVD. True scalar, integer-valued.
INVERSE a a must be square. Errors if singular.
SOLVE a b Solves a x = b via LU with partial pivoting. Errors if a is singular.
LSTSQ a b Least-squares (minimum-norm) solve of a x = b for any shape of a (over-determined, under-determined, or square-but-singular) via the SVD-based Moore-Penrose pseudo-inverse. Never errors on non-square/singular input, unlike SOLVE: a deliberately distinct keyword, not one polymorphic operator.
EIGENVALUES a Real eigenvalues of a symmetric matrix only. Errors on a non-symmetric input rather than silently guessing.
EIGENVALUES_GENERAL a Eigenvalues of any square matrix, possibly complex (via Schur decomposition). Result: real Matrix(2, N) (row 0 real parts, row 1 imaginary parts) mirroring FFT’s complex-spectrum convention rather than a collection of scalar Complex values.
CHOLESKY a a = L·Lᵗ of a symmetric positive-definite matrix. Errors otherwise.
PCA a COMPONENTS k Projects a’s rows onto their top-k principal components (mean-centered, built on SVD).

Multi-output (bind with LET a, b[, c] = ...):

Operator Outputs
QR a Q, R
LU a P, L, U (square a only): P is included so P @ a == L @ U actually holds
EIGEN a eigenvalues (Vector), eigenvectors (Matrix, as columns); symmetric matrices only
EIGEN_GENERAL a eigenvalues (Vector), eigenvectors (Matrix, as columns); any square matrix, but scoped to the real-eigenvalue case only (nalgebra has no public general complex-eigenvector API). Errors loudly, pointing at EIGENVALUES_GENERAL, if the matrix’s eigenvalues turn out to be genuinely complex: never a silently wrong real approximation.
SVD a U, s (Vector of singular values), Vᵗ
MATRIX m = [[4, 7], [2, 6]]
LET tr = TRACE m -- 10.0
LET det = DETERMINANT m -- 10.0
LET inv = INVERSE m
VECTOR b = [4, 6]
LET x = SOLVE m b -- solves m @ x = b
MATRIX overdetermined = [[1, 1], [2, 1], [3, 1]]
VECTOR y = [2.1, 3.9, 6.05]
LET fit = LSTSQ overdetermined y -- least-squares fit; never errors on non-square/singular input
LET q, r = QR m
LET p, l, u = LU m
MATRIX sym = [[2, 1], [1, 2]]
LET vals, vecs = EIGEN sym -- symmetric matrices: real eigenvalues guaranteed
LET u, s, vt = SVD m
-- Non-symmetric matrices: eigenvalues may be complex
MATRIX rot = [[0, -1], [1, 0]]
LET eigs = EIGENVALUES_GENERAL rot -- Matrix(2,N): row 0 = real parts, row 1 = imaginary parts

LET a, b[, c] = <expr> binds more than one name from a single expression: only the decompositions above with more than one natural output support this. The number of names must match the expression’s real output count exactly; a mismatch is a clear error, not a silent truncation. Using a single-output operator with multi-output LET, or vice versa, is also a clear error pointing at the correct form.