A linear system is a question about data, and some data answers no: a singular matrix has no inverse, an indefinite one no Cholesky factor. Those are not program errors — no assertion was violated, no index was wrong — so the linalg unit is born on the failure channel:

imp
    Solve, Invert, Cholesky, Qr : linalg;
    LinalgError, QrDecomposition : linalg;
    Singular, NotPositiveDefinite : linalg;

Every routine fails LinalgError, and the members say what the data was: Singular from an exactly zero pivot — the reference implementations’ own rule, no epsilon to tune — NotPositiveDefinite from Cholesky’s first non-positive pivot, and ShapeMismatch where a routine meets sizes that fit no question at all.

The example is examples/Linalg in the tutorial repository.

Solve

x := Solve(a, b) on fail e do
begin
    WriteLn("unexpected failure");
    leave;
end;

Solve answers the x with a·x = b through one partial-pivot LU factorization and one substitution pass. The pivoting is part of the answer’s quality, not an option — the example’s system deliberately starts with a zero on the diagonal, so the row exchange must happen for the answer to exist.

Invert, and when not to

Invert answers the whole inverse: the factorization once, then one substitution per identity column. The example verifies it the strongest way available — with the shipped operators themselves:

inv := Invert(a) on fail e do
begin
    WriteLn("unexpected failure");
    leave;
end;
idm := a * inv;                          { multiplies back to the identity }

Where a program only ever multiplies the inverse onto vectors, Solve per vector is the cheaper and better-conditioned road; Invert exists for the answers that are themselves the matrix.

Cholesky as a question

Cholesky answers the lower factor with a = l·lᵀ, reading only the lower triangle — the symmetric contract’s own convention. Because the first non-positive pivot refuses as NotPositiveDefinite, the call doubles as the cheapest definiteness test there is:

l := Cholesky(a) on fail e do
begin
    if e = NotPositiveDefinite then
        WriteLn("not positive definite — and that is the answer");
end;

Qr

Qr answers the reduced orthogonal-triangular pair by Householder reflections — the numerically sound road — as one record:

parts := Qr(a) on fail e do
begin
    WriteLn("unexpected failure");
    leave;
end;
back := parts.q * parts.r;               { restores the argument }

(Inside a function that itself declares fails LinalgError, the shorter on fail leave forwards the failure instead.)

For a 3×2 argument, parts.q is 3×2 with orthonormal columns and parts.r is 2×2 upper-triangular, with exact zeros below the diagonal — the triangle is a stated fact of the answer, not a rounding accident.

The value model is the working storage

Every classic factorization overwrites its matrix in place. In Mica that costs no caution at all: a parameter arrives as the callee’s own deep copy, so the elimination works in place on that copy while the caller’s matrix never moves. No caller ever sees its matrix overwritten.

Every fold runs ascending in a fixed order, so results are bit-identical across optimization tiers and architectures — the same promise the operators keep. No BLAS is linked; the compiler’s own code generation carries the unit.