@mathigon/fermat
Version:
Powerful mathematics and statistics library for JavaScript.
190 lines (153 loc) • 4.94 kB
text/typescript
// =============================================================================
// Fermat.js | Matrix
// (c) Mathigon
// =============================================================================
import {repeat2D, tabulate2D} from '@mathigon/core';
import {nearlyEquals} from './arithmetic';
type Matrix = number[][];
// ---------------------------------------------------------------------------
// Constructors
/** Fills a matrix of size x, y with a given value. */
export function fill(value: number, x: number, y: number) {
return repeat2D(value, x, y);
}
/** Returns the identity matrix of size n. */
export function identity(n = 2) {
const x = fill(0, n, n);
for (let i = 0; i < n; ++i) x[i][i] = 1;
return x;
}
export function rotation(angle: number) {
const sin = Math.sin(angle);
const cos = Math.cos(angle);
return [[cos, -sin], [sin, cos]];
}
export function shear(lambda: number) {
return [[1, lambda], [0, 1]];
}
export function reflection(angle: number) {
const sin = Math.sin(2 * angle);
const cos = Math.cos(2 * angle);
return [[cos, sin], [sin, -cos]];
}
// ---------------------------------------------------------------------------
// Matrix Operations
/** Calculates the sum of two or more matrices. */
export function sum(...matrices: Matrix[]): Matrix {
const [M1, ...rest] = matrices;
const M2 = rest.length > 1 ? sum(...rest) : rest[0];
if (M1.length !== M2.length || M1[0].length !== M2[0].length) {
throw new Error('Matrix sizes don’t match');
}
const S = [];
for (let i = 0; i < M1.length; ++i) {
const row = [];
for (let j = 0; j < M1[i].length; ++j) {
row.push(M1[i][j] + M2[i][j]);
}
S.push(row);
}
return S;
}
/** Multiplies a matrix M by a scalar v. */
export function scalarProduct(M: Matrix, v: number) {
return M.map(row => row.map(x => x * v));
}
/** Calculates the matrix product of multiple matrices. */
export function product(...matrices: Matrix[]): Matrix {
const [M1, ...rest] = matrices;
const M2 = rest.length > 1 ? product(...rest) : rest[0];
if (M1[0].length !== M2.length) {
throw new Error('Matrix sizes don’t match.');
}
const P = [];
for (let i = 0; i < M1.length; ++i) {
const row = [];
for (let j = 0; j < M2[0].length; ++j) {
let value = 0;
for (let k = 0; k < M2.length; ++k) {
value += M1[i][k] * M2[k][j];
}
row.push(value);
}
P.push(row);
}
return P;
}
// ---------------------------------------------------------------------------
// Matrix Properties
/** Calculates the transpose of a matrix M. */
export function transpose(M: Matrix) {
const T = [];
for (let j = 0; j < M[0].length; ++j) {
const row = [];
for (let i = 0; i < M.length; ++i) {
row.push(M[i][j]);
}
T.push(row);
}
return T;
}
/** Calculates the determinant of a matrix M. */
export function determinant(M: Matrix) {
if (M.length !== M[0].length) throw new Error('Not a square matrix.');
const n = M.length;
// Shortcuts for small n
if (n === 1) return M[0][0];
if (n === 2) return M[0][0] * M[1][1] - M[0][1] * M[1][0];
let det = 0;
for (let j = 0; j < n; ++j) {
let diagLeft = M[0][j];
let diagRight = M[0][j];
for (let i = 1; i < n; ++i) {
diagRight *= M[i][(j + i) % n];
diagLeft *= M[i][(j - i + n) % n];
}
det += diagRight - diagLeft;
}
return det;
}
/** Calculates the inverse of a matrix M. */
export function inverse(M: Matrix) {
// Perform Gaussian elimination:
// (1) Apply the same operations to both I and C.
// (2) Turn C into the identity, thereby turning I into the inverse of C.
const n = M.length;
if (n !== M[0].length) throw new Error('Not a square matrix.');
const I = identity(n);
const C = tabulate2D((x, y) => M[x][y], n, n); // Copy of original matrix
for (let i = 0; i < n; ++i) {
// Loop over the elements e in along the diagonal of C.
let e = C[i][i];
// If e is 0, we need to swap this row with a lower row.
if (nearlyEquals(e, 0)) {
for (let ii = i + 1; ii < n; ++ii) {
if (C[ii][i] !== 0) {
for (let j = 0; j < n; ++j) {
[C[ii][j], C[i][j]] = [C[i][j], C[ii][j]];
[I[ii][j], I[i][j]] = [I[i][j], I[ii][j]];
}
break;
}
}
e = C[i][i];
if (nearlyEquals(e, 0)) throw new Error('Matrix not invertible.');
}
// Scale row by e, so that we have a 1 on the diagonal.
for (let j = 0; j < n; ++j) {
C[i][j] = C[i][j] / e;
I[i][j] = I[i][j] / e;
}
// Subtract a multiple of this row from all other rows,
// so that they end up having 0s in this column.
for (let ii = 0; ii < n; ++ii) {
if (ii === i) continue;
const f = C[ii][i];
for (let j = 0; j < n; ++j) {
C[ii][j] -= f * C[i][j];
I[ii][j] -= f * I[i][j];
}
}
}
return I;
}