Linear Algebra vs Element-wise Operations

The * and / operators on Vector/Matrix are always element-wise — they are not matrix multiplication or linear system solving. This is the #1 source of confusion.

Several method names are overridden on TMtx/Matrix so that on a two-dimensional container they perform the real linear-algebra operation instead of the element-wise one their base-class signature implies. The key overrides are:

  • TMtx.Mul(A, B) with two TMtx arguments → matrix multiplication (self := A * B), not element-wise.
  • TMtx.Invmatrix inverse (self := self^-1), not per-element 1/x. On TVec/Vector the inherited Inv is per-element.

So whether .Mul/.Inv is element-wise or linear-algebra depends on the receiver type: element-wise on TVec/Vector, linear-algebra on TMtx/Matrix — the polymorphism is deliberate.

The Critical Distinction

OperationElement-wise formLinear-algebra form
Multiply — * operatorc := a * b on Vector/Matrix (always per-element)— (operators never do linear algebra)
Multiply — methodc.Mul(a, b) on TVec/Vector (per-element)C.Mul(A, B) on TMtx/Matrix (matrix product) — overridden, see above
Dividec := a / b on Vector/Matrix (per-element)MtxExpr.Divide(b, A) — solves A^T · x = b
Back-divideMtxExpr.LDivide(A, b) — solves A · x = b
Inversev.Inv on TVec/Vector (per-element 1/x)M.Inv on TMtx/Matrix (matrix inverse) — overridden

Two API Layers

Vector/Matrix — value types with operator overloading, return new objects:

c := a + b;     // allocates result from object cache
c := a * b;     // ELEMENT-WISE multiply (not matrix product!)
c := a * 2.0;   // scalar multiply

TVec/TMtx — pool-managed types, write result into calling object (self-as-destination):

// On TVec (lowercase v, a, b are TVec — vectors):
v.Add(a, b);       // v := a + b       (no allocation)
v.Mul(a, b);       // v := a .* b      (element-wise — the only meaning on 1D)
v.Mul(2.0);        // v := v * 2.0     (scalar multiply)
v.Inv;             // v := 1 ./ v      (element-wise 1/x)

// On TMtx (uppercase M, A, B are TMtx — matrices) the same method
// names are OVERRIDDEN to perform real linear algebra:
M.Add(A, B);       // M := A + B       (element-wise; addition is the same)
M.Mul(A, B);       // M := A * B       (matrix product — not element-wise)
M.MulElem(A, B);   // M := A .* B      (explicit element-wise product on TMtx)
M.Inv;             // M := M^-1        (matrix inverse — not per-element)

The receiver type decides the meaning: on a 1D container (TVec/Vector) every .Mul/.Inv is element-wise because there is nothing else it can be. On a 2D container (TMtx/Matrix) .Mul(A, B) and .Inv mean real linear algebra. Use .MulElem on TMtx when you actually want the Hadamard (per-element) product.

Matrix Multiplication

With Vector/Matrix (via MtxExpr static functions)

MtxExpr functions accept TVec/TMtx inputs and return Vector/Matrix value types. These are the primary way to do linear algebra with expression-friendly syntax.

// C#
using Dew.Math.Units;

Vector y = MtxExpr.Mul(A, x);           // A * x  (matrix × vector)
Vector y = MtxExpr.Mul(x, A);           // x * A  (vector × matrix)
Matrix C = MtxExpr.Mul(A, B);           // A * B  (matrix × matrix)
Matrix C = MtxExpr.Mul(x, y);           // x * y^T (outer product)

// With transpose/conjugate
Matrix C = MtxExpr.Mul(A, B, TMtxOperation.opTranspose);  // A^T * B

// Chain: A * B * C * D
Matrix R = MtxExpr.Mul(A, B, C, D);
// or: MtxExpr.Mul(new TMtx[] { A, B, C, D });

With TVec/TMtx (method calls, zero allocation)

// C# — identical syntax (TMtx/TVec are the same API)
C.Mul(A, B);
y.TensorProd(A, x);
y.TensorProd(x, A);
C.TensorProd(x, y);
C.MulElem(A, B);

Chain multiplication — one call, any number of matrices

TMtx.Mul has overloads for three- and four-factor chains, and TMtx.MulArray accepts an arbitrary TMtxArray so the whole chain is computed in a single call. These are preferred over manual chaining (tmp.Mul(A,B); C.Mul(tmp,D);) because the implementation picks the most efficient evaluation order and avoids redundant temporaries.

// Three and four matrices in one call
R.Mul(A, B, C);
R.Mul(A, B, C, D);

// Arbitrary chain via TMtx[]
TMtx[] chain = new TMtx[] { M0, M1, M2, M3, M4 };
R.MulArray(chain);                           // R := M0*M1*M2*M3*M4

int[] transp = { 0, 1, 0, 0, 1 };            // transpose M1 and M4
R.MulArray(chain, transp);                   // R := M0 * M1^T * M2 * M3 * M4^T

Chain factors must all be matrices. The element type of TMtxArray is TMtx, and the fixed-arity Mul(A, B, C[, D]) overloads take TMtx arguments. There is no heterogeneous [TMtx, TVec, TMtx, ...] overload. For vector weights and vectors-as-operands see the next two subsections.

Folding a vector weight into a chain — `MulDiagLeft` / `MulDiagRight`

When one of the factors in a chain is a diagonal matrix whose diagonal is known as a TVec, you don't need to materialise the full diagonal matrix. TMtx exposes four overloads that multiply in a vector treated as the main diagonal — equivalent to row or column scaling:

From the summaries: "DiagMtx contains the values of the main diagonal of the diagonal matrix. … This operation is the same as calling TMtx.ScaleRows" (left form) or "TMtx.ScaleCols" (right form). The vector length must match self.Cols for MulDiagLeft and self.Rows for MulDiagRight.

Example — A · diag(w) · B in two calls, no diagonal matrix built:

This is strictly faster than materialising diag(w) as a square TMtx and including it in a MulArray — the scaling step is O(n²) instead of the O(n³) a full matrix multiply would cost.

Sparse matrix multiplication — `TSparseMtx.MulLeft` / `MulRight`

TSparseMtx (sparse container) does not share TMtx's Mul/MulArray API. Instead it exposes MulLeft / MulRight, each with a matrix-matrix and a matrix-vector overload. These are the only dense-API methods that take a TVec operand directly as a vector (not as a diagonal):

Summaries from the DB: "Multiply the sparse matrix from left" / "Multiply sparse matrix from left with vector X and place the result in vector Y" — and the symmetrical right-hand forms. Use these when your matrix is stored as a TSparseMtx; for dense matrix-vector products use TVec.TensorProd(Mtx, Vec) as shown above.

Note: TSparseMtx has no chain overload — MulLeft/MulRight are always binary. For a sparse chain you apply them one factor at a time.

Solving Linear Systems (A·x = b)

With MtxExpr (returns Vector/Matrix)

// Solve A * x = b  (left-divide, like MATLAB's A\b)
Vector x = MtxExpr.LDivide(A, b);         // uses LQR internally
Matrix X = MtxExpr.LDivide(A, B);         // multi-column RHS

// Solve x * A = b  (right-divide, like MATLAB's b/A)
Vector x = MtxExpr.Divide(b, A);

With TMtx (method calls, more control)

// LU-based (fastest for square, well-conditioned)
A.LUSolve(b, x);                          // A * x = b, general
A.LUSolve(b, x, TMtxType.mtSymmetric);    // exploit symmetry

// QR-based (robust, handles rank deficiency)
A.LQRSolve(b, x);                         // full rank
int rank = A.LQRSolve(b, x, 1e-9);        // rank-deficient, returns effective rank

// SVD-based (minimum-norm solution)
int rank = A.SVDSolve(b, x, s, 1e-9);     // s receives singular values

Decompositions

With TMtx (in-place, reuse objects)

// Cholesky (symmetric positive definite)
if (A.Cholesky()) { ... }                 // A is replaced by factor

// LU decomposition
A.LU(LU_result, pivots);

// QR / LQ decomposition
A.LQR(L, Q, R);                           // economy-size by default
A.LQR(L, Q, R, P);                        // with column pivoting

// SVD
A.SVD(U, S, V);                           // full SVD
A.SVD(S);                                 // singular values only

// Eigenvalues
A.Eig(D);                                 // eigenvalues in D
A.Eig(D, TMtxType.mtSymmetric);           // symmetric → real eigenvalues
A.Eig(VL, D, VR);                         // with eigenvectors

MtxExpr convenience wrappers

Vector eigenvalues = MtxExpr.Eig(A);              // eigenvalues
Vector singularvals = MtxExpr.SVD(A);              // singular values
bool ok = MtxExpr.Cholesky(A);                     // in-place, returns boolean

Matrix Inverse

// MtxExpr (returns Matrix value type)
Matrix B = MtxExpr.Inverse(A);                           // general inverse
Matrix B = MtxExpr.Inverse(A, TMtxType.mtSymmetric);     // exploit symmetry

// TMtx method (in-place)
A.Inv();                                   // A is replaced by A^(-1)
A.Inv(TMtxType.mtSymmetric);              // symmetric inverse

Important: Prefer LDivide(A, b) over Inverse(A) * b. Solving is faster and more numerically stable than computing the explicit inverse.

Performance: Vector/Matrix vs TVec/TMtx

In C#, Vector, Matrix, VectorInt, and MatrixInt do not benefit from the object cache. C# language design prevents user-defined memory allocation control for value types. Every operator expression allocates new managed objects. Use TVec/TMtx with CreateIt/FreeIt for best performance.

lifetime. Operator expressions are efficient in Delphi.

// SLOW — Vector allocates on every operation
Vector c = a + b;           // allocates new Vector
Vector d = c * e;           // allocates another

// FAST — TVec reuses pooled memory
TVec c = TVec.CreateIt(a.Length);
try {
    c.Add(a, b);            // no allocation
    c.Mul(e);               // in-place, no allocation
} finally {
    TVec.FreeIt(ref c);
}

// Linear algebra — same trade-off:
Vector y = MtxExpr.Mul(A, x);   // convenient but allocates
y.TensorProd(A, x);              // fast, writes into pre-allocated y

Quick Reference

The * and / operators on Vector/Matrix are always element-wise — never matrix multiplication. Use the method calls or MtxExpr free functions for linear algebra.

TaskVector/Matrix operatorMtxExpr / free functionTMtx/TVec method
Element-wise add / subc := a + b, a - bc.Add(a, b), c.Sub(a, b)
Element-wise multiplyc := a * bc.Mul(a, b) on TVec; C.MulElem(A, B) on TMtx
Element-wise dividec := a / bc.DivideVec(a, b) (per-element); see also DivideBy(N) for N / self
Element-wise 1/xv.Inv on TVec/Vector
Matrix × matrix(operators are element-wise!)Mul(A, B) → MatrixC.Mul(A, B) on TMtx
Matrix × vectorMul(A, x) → Vectory.TensorProd(A, x)
Vector × matrixMul(x, A) → Vectory.TensorProd(x, A)
Outer product x · yᵀMul(x, y) → MatrixC.TensorProd(x, y)
Row-scale (diag(w) · A)A.MulDiagLeft(w)ScaleRows
Column-scale (A · diag(w))A.MulDiagRight(w)ScaleCols
Sparse mat × mat / mat × vecS.MulLeft(X, Y) / MulRight(X, Y) on TSparseMtx
Chain matrix productMul(A, B, C[, D]) → MatrixR.Mul(A, B, C[, D]), R.MulArray([...])
Solve A · x = bLDivide(A, b) → VectorA.LUSolve(b, x) or A.LQRSolve(b, x)
Solve x · A = bDivide(b, A) → Vector
Matrix inverseInverse(A) → MatrixA.Inv (overridden on TMtx)
EigenvaluesEig(A) → VectorA.Eig(D)
SVDSVD(A) → VectorA.SVD(U, S, V)