Skip to content

Stdlib · extensions

linalg

linear algebra (graphics + dense + AI base)

extensions.md · 104 lines · 3 min read

use linalg

Not “gamedev linalg”; it is the mathematical library that underpins everything from graphics to AI. There are three layers: fixed types (graphics), Matrix[M,N]/dynamic (general dense), and BLAS-style dense algebra (multiplication, decompositions, solve). Dual backend (mirrors the multi-engine of regex): the portable own implementation is the floor, and the system BLAS/LAPACK is the opt-in accelerator (via the C-FFI we already have). Tier 1. Arbitrary-rank tensors and autograd/NN are left to the AI lib (tier 2/3) on top; here is the numerical foundation.

Size known at compile time; operations are by value, no allocation:

decl Vec2 { pub x: f32; pub y: f32 }
decl Vec3 { pub x: f32; pub y: f32; pub z: f32 }
decl Vec4 { pub x: f32; pub y: f32; pub z: f32; pub w: f32 }
decl Mat2 { ... } decl Mat3 { ... } decl Mat4 { ... }
decl Quat { pub x: f32; pub y: f32; pub z: f32; pub w: f32 } // quaternion
fn (a: Vec3) dot(b: Vec3) -> f32 // dot product (+ via operator where numeric)
fn (a: Vec3) cross(b: Vec3) -> Vec3 // cross product
fn (a: Vec3) length() -> f32
fn (a: Vec3) normalized() -> Vec3
fn (a: Vec3) add(b: Vec3) -> Vec3 // + also via operator (numeric)
fn (m: Mat4) mul(n: Mat4) -> Mat4
fn (m: Mat4) mul_vec(v: Vec4) -> Vec4
fn (m: Mat4) inverse() -> Optional[Mat4] // none if singular
fn (m: Mat4) transpose() -> Mat4
fn (q: Quat) mul(r: Quat) -> Quat
fn (q: Quat) to_mat4() -> Mat4
// common transformations (constructors):
fn Mat4.identity() -> Mat4
fn Mat4.translation(v: Vec3) -> Mat4
fn Mat4.rotation(axis: Vec3, angle: f32) -> Mat4
fn Mat4.scale(v: Vec3) -> Mat4
fn Mat4.perspective(fovy: f32, aspect: f32, near: f32, far: f32) -> Mat4
fn Mat4.look_at(eye: Vec3, center: Vec3, up: Vec3) -> Mat4

f32 generic where it makes sense: Vec3[T] over any float (f32/f64); the bare names (Vec3) are the f32 alias (the graphics case). In practice, alias Vec3 = Vec3f.

Dimensions at runtime; allocates via the context’s @mm. It is the general numerical base, and what AI pulls:

decl Matrix[T] { pub rows: usize; pub cols: usize; ... } // T = f32/f64
fn (Matrix[T]) zeros(rows: usize, cols: usize) -> Matrix[T]
fn (Matrix[T]) from(rows: usize, cols: usize, data: []T) -> Matrix[T] // row-major
fn (Matrix[T]) identity(n: usize) -> Matrix[T]
fn (m: Matrix[T]) get(i: usize, j: usize) -> Optional[T] // SAFE (none out of bounds)
@requires(i < m.rows && j < m.cols)
fn (m: Matrix[T]) at(i: usize, j: usize) -> T // contracted (no Optional)
fn (m: *Matrix[T]) set(i: usize, j: usize, v: T) -> error{OutOfBounds}
fn (m: Matrix[T]) mul(n: Matrix[T]) -> Result[Matrix[T], error{ShapeMismatch}] // matrix product
fn (m: Matrix[T]) add(n: Matrix[T]) -> Result[Matrix[T], error{ShapeMismatch}]
fn (m: Matrix[T]) transpose() -> Matrix[T]
fn (m: Matrix[T]) slice(r0: usize, r1: usize, c0: usize, c1: usize) -> Matrix[T] // submatrix (view)
fn (m: Matrix[T]) row(i: usize) -> []T // view
fn (m: Matrix[T]) col(j: usize) -> List[T]

Access is at/get (no [] overload), same as collections/containers: safe from the outside, with the raw index contained.

Dense algebra (decompositions): dual backend

Section titled “Dense algebra (decompositions): dual backend”

The decompositions and the solve, the heart of the dense part. Portable own one by default; BLAS/LAPACK opt-in when linked (the compiler chooses the backend at comptime according to how linalg is configured, the way arch.has_feature chooses the SIMD path):

fn (m: Matrix[T]) lu() -> Result[(l: Matrix[T], u: Matrix[T], p: Permutation), error{Singular}]
fn (m: Matrix[T]) qr() -> (q: Matrix[T], r: Matrix[T])
fn (m: Matrix[T]) svd() -> (u: Matrix[T], s: List[T], vt: Matrix[T])
fn (m: Matrix[T]) cholesky() -> Result[Matrix[T], error{NotPositiveDefinite}]
fn (m: Matrix[T]) solve(b: Matrix[T]) -> Result[Matrix[T], error{Singular, ShapeMismatch}] // solves Mx=b
fn (m: Matrix[T]) det() -> T
fn (m: Matrix[T]) inverse() -> Result[Matrix[T], error{Singular}]
fn (m: Matrix[T]) eig() -> Result[(values: List[T], vectors: Matrix[T]), error{NoConverge}]

The own implementation is the floor (portable, always present); the BLAS/LAPACK binding (via C-FFI, §17) is the accelerator, with the same API and a swappable backend. Enabling BLAS is declaring the C dep (cdeps, §17) + a build flag; without that, it runs the own one.

  • Dual backend (own + BLAS opt-in): mirrors the multi-engine of regex: portable is the floor (zero dependency), BLAS is the accelerator when you link it. Same API; comptime chooses.
  • Three layers, one package: fixed (graphics, zero-alloc, by value), Matrix[T] (general dense), decompositions (serious dense). They grow together because they share the types.
  • Tensors (rank > 2) and autograd/NN are the AI lib (tier 2/3): linalg is the foundation; broadcasting and Tensor[rank] enter the layer above, not here. A big piece, deliberately out.
  • at/get, no [] overload: consistent with collections/containers; colorless (allocates via the context).