@pawells/math-extended 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -21
- package/build/clamp.d.ts +5 -5
- package/build/clamp.js +5 -5
- package/build/core.d.ts +23 -0
- package/build/core.d.ts.map +1 -0
- package/build/core.js +25 -0
- package/build/core.js.map +1 -0
- package/build/index.d.ts +1 -4
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -6
- package/build/index.js.map +1 -1
- package/build/interpolation.d.ts +158 -171
- package/build/interpolation.d.ts.map +1 -1
- package/build/interpolation.js +162 -181
- package/build/interpolation.js.map +1 -1
- package/build/matrices/arithmetic.d.ts +132 -132
- package/build/matrices/arithmetic.d.ts.map +1 -1
- package/build/matrices/arithmetic.js +194 -226
- package/build/matrices/arithmetic.js.map +1 -1
- package/build/matrices/asserts.d.ts +30 -408
- package/build/matrices/asserts.d.ts.map +1 -1
- package/build/matrices/asserts.js +98 -542
- package/build/matrices/asserts.js.map +1 -1
- package/build/matrices/core.d.ts +117 -46
- package/build/matrices/core.d.ts.map +1 -1
- package/build/matrices/core.js +127 -103
- package/build/matrices/core.js.map +1 -1
- package/build/matrices/decompositions.d.ts +95 -96
- package/build/matrices/decompositions.d.ts.map +1 -1
- package/build/matrices/decompositions.js +119 -160
- package/build/matrices/decompositions.js.map +1 -1
- package/build/matrices/index.d.ts +0 -1
- package/build/matrices/index.d.ts.map +1 -1
- package/build/matrices/index.js +0 -3
- package/build/matrices/index.js.map +1 -1
- package/build/matrices/linear-algebra.d.ts +45 -8
- package/build/matrices/linear-algebra.d.ts.map +1 -1
- package/build/matrices/linear-algebra.js +76 -32
- package/build/matrices/linear-algebra.js.map +1 -1
- package/build/matrices/normalization.d.ts +29 -8
- package/build/matrices/normalization.d.ts.map +1 -1
- package/build/matrices/normalization.js +32 -23
- package/build/matrices/normalization.js.map +1 -1
- package/build/matrices/transformations.d.ts +116 -148
- package/build/matrices/transformations.d.ts.map +1 -1
- package/build/matrices/transformations.js +69 -91
- package/build/matrices/transformations.js.map +1 -1
- package/build/matrices/types.d.ts +32 -60
- package/build/matrices/types.d.ts.map +1 -1
- package/build/matrices/types.js +63 -1
- package/build/matrices/types.js.map +1 -1
- package/build/quaternions/asserts.d.ts +29 -38
- package/build/quaternions/asserts.d.ts.map +1 -1
- package/build/quaternions/asserts.js +61 -77
- package/build/quaternions/asserts.js.map +1 -1
- package/build/quaternions/conversions.d.ts +26 -26
- package/build/quaternions/conversions.d.ts.map +1 -1
- package/build/quaternions/conversions.js +24 -24
- package/build/quaternions/core.d.ts +70 -69
- package/build/quaternions/core.d.ts.map +1 -1
- package/build/quaternions/core.js +71 -71
- package/build/quaternions/core.js.map +1 -1
- package/build/quaternions/index.d.ts +0 -1
- package/build/quaternions/index.d.ts.map +1 -1
- package/build/quaternions/index.js +0 -2
- package/build/quaternions/index.js.map +1 -1
- package/build/quaternions/interpolation.d.ts +16 -16
- package/build/quaternions/interpolation.d.ts.map +1 -1
- package/build/quaternions/interpolation.js +21 -21
- package/build/quaternions/interpolation.js.map +1 -1
- package/build/quaternions/predefined.d.ts +10 -10
- package/build/quaternions/predefined.d.ts.map +1 -1
- package/build/quaternions/predefined.js +9 -9
- package/build/quaternions/types.d.ts +23 -12
- package/build/quaternions/types.d.ts.map +1 -1
- package/build/quaternions/types.js +51 -1
- package/build/quaternions/types.js.map +1 -1
- package/build/random.d.ts +66 -20
- package/build/random.d.ts.map +1 -1
- package/build/random.js +73 -20
- package/build/random.js.map +1 -1
- package/build/vectors/asserts.d.ts +50 -220
- package/build/vectors/asserts.d.ts.map +1 -1
- package/build/vectors/asserts.js +141 -327
- package/build/vectors/asserts.js.map +1 -1
- package/build/vectors/core.d.ts +182 -229
- package/build/vectors/core.d.ts.map +1 -1
- package/build/vectors/core.js +234 -288
- package/build/vectors/core.js.map +1 -1
- package/build/vectors/index.d.ts +0 -1
- package/build/vectors/index.d.ts.map +1 -1
- package/build/vectors/index.js +0 -2
- package/build/vectors/index.js.map +1 -1
- package/build/vectors/interpolation.d.ts +40 -40
- package/build/vectors/interpolation.d.ts.map +1 -1
- package/build/vectors/interpolation.js +75 -86
- package/build/vectors/interpolation.js.map +1 -1
- package/build/vectors/predefined.d.ts +31 -7
- package/build/vectors/predefined.d.ts.map +1 -1
- package/build/vectors/predefined.js +30 -6
- package/build/vectors/predefined.js.map +1 -1
- package/build/vectors/types.d.ts +26 -4
- package/build/vectors/types.d.ts.map +1 -1
- package/build/vectors/types.js +50 -1
- package/build/vectors/types.js.map +1 -1
- package/package.json +3 -2
- package/build/matrices/_exports.d.ts +0 -13
- package/build/matrices/_exports.d.ts.map +0 -1
- package/build/matrices/_exports.js +0 -13
- package/build/matrices/_exports.js.map +0 -1
- package/build/quaternions/_exports.d.ts +0 -11
- package/build/quaternions/_exports.d.ts.map +0 -1
- package/build/quaternions/_exports.js +0 -11
- package/build/quaternions/_exports.js.map +0 -1
- package/build/vectors/_exports.d.ts +0 -10
- package/build/vectors/_exports.d.ts.map +0 -1
- package/build/vectors/_exports.js +0 -10
- package/build/vectors/_exports.js.map +0 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { AssertNumber, AssertInstanceOf, ArraySortBy } from '@pawells/typescript-common';
|
|
2
2
|
import { MatrixMultiply } from './arithmetic.js';
|
|
3
|
-
import { AssertMatrix,
|
|
3
|
+
import { AssertMatrix, AssertMatrix1, AssertMatrix2, AssertMatrixSquare, MatrixError } from './asserts.js';
|
|
4
4
|
import { MatrixSize, MatrixCreate, MatrixClone, MatrixIdentity, MatrixTranspose } from './core.js';
|
|
5
5
|
import { MatrixGramSchmidt } from './linear-algebra.js';
|
|
6
6
|
const MATRIX_NUMERICAL_TOLERANCE = 1e-12;
|
|
7
7
|
const EIGEN_CONVERGENCE_TOLERANCE = 1e-10;
|
|
8
|
+
const EIGEN_MAX_ITERATIONS = 50;
|
|
8
9
|
/**
|
|
9
10
|
* Performs Cholesky decomposition for symmetric positive definite matrices A = L × L^T.
|
|
10
11
|
*
|
|
@@ -22,53 +23,45 @@ const EIGEN_CONVERGENCE_TOLERANCE = 1e-10;
|
|
|
22
23
|
*
|
|
23
24
|
* @param matrix - The symmetric positive definite square matrix to decompose
|
|
24
25
|
* @returns Lower triangular matrix L such that A = L × L^T
|
|
25
|
-
* @throws {
|
|
26
|
+
* @throws {MatrixError} If matrix is not square, not symmetric, or not positive definite
|
|
26
27
|
*
|
|
27
28
|
* @example
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
* @see {@link MatrixLU} For general square matrices that may not be positive definite
|
|
41
|
-
* ```
|
|
29
|
+
* ```typescript
|
|
30
|
+
* // Symmetric positive definite matrix
|
|
31
|
+
* const A = [[4, 2], [2, 3]];
|
|
32
|
+
* const L = MatrixCholesky(A);
|
|
33
|
+
* // L = [[2, 0], [1, √2]] ≈ [[2, 0], [1, 1.414]]
|
|
34
|
+
* // Verify: L × L^T should equal A
|
|
35
|
+
* const LT = MatrixTranspose(L);
|
|
36
|
+
* const reconstructed = MatrixMultiply(L, LT);
|
|
37
|
+
* // reconstructed ≈ [[4, 2], [2, 3]]
|
|
38
|
+
* ```
|
|
39
|
+
* @complexity Time: O(n³/3), Space: O(n²) - About 2x faster than general LU decomposition
|
|
40
|
+
* @see {@link MatrixLU} For general square matrices that may not be positive definite
|
|
42
41
|
*/
|
|
43
42
|
export function MatrixCholesky(matrix) {
|
|
44
|
-
|
|
43
|
+
AssertMatrixSquare(matrix);
|
|
45
44
|
const [n] = MatrixSize(matrix);
|
|
46
45
|
const L = MatrixCreate(n, n);
|
|
47
46
|
// Perform Cholesky decomposition using the Cholesky-Banachiewicz algorithm
|
|
48
47
|
for (let i = 0; i < n; i++) {
|
|
49
48
|
const lRowI = L[i];
|
|
50
49
|
const matrixRowI = matrix[i];
|
|
51
|
-
AssertMatrixRow(lRowI);
|
|
52
|
-
AssertMatrixRow(matrixRowI);
|
|
53
50
|
for (let j = 0; j <= i; j++) {
|
|
54
51
|
const lRowJ = L[j];
|
|
55
|
-
AssertMatrixRow(lRowJ);
|
|
56
52
|
if (i === j) {
|
|
57
53
|
// Compute diagonal elements: L[i,i] = √(A[i,i] - Σ(k=0 to i-1) L[i,k]²)
|
|
58
54
|
let sum = 0;
|
|
59
55
|
// Sum of squares of elements in row i before column j
|
|
60
56
|
for (let k = 0; k < j; k++) {
|
|
61
57
|
const lVal = lRowI[k];
|
|
62
|
-
AssertMatrixValue(lVal, { rowIndex: i, columnIndex: k });
|
|
63
58
|
sum += lVal * lVal;
|
|
64
59
|
}
|
|
65
60
|
const matrixVal = matrixRowI[j];
|
|
66
|
-
AssertMatrixValue(matrixVal, { rowIndex: i, columnIndex: j });
|
|
67
61
|
const diagonal = matrixVal - sum;
|
|
68
62
|
// Check positive definiteness - diagonal must be positive
|
|
69
|
-
if (diagonal <= 0)
|
|
70
|
-
throw new
|
|
71
|
-
}
|
|
63
|
+
if (diagonal <= 0)
|
|
64
|
+
throw new MatrixError(`Matrix is not positive definite at element [${i},${j}]`);
|
|
72
65
|
lRowI[j] = Math.sqrt(diagonal);
|
|
73
66
|
}
|
|
74
67
|
else {
|
|
@@ -78,17 +71,13 @@ export function MatrixCholesky(matrix) {
|
|
|
78
71
|
for (let k = 0; k < j; k++) {
|
|
79
72
|
const lIVal = lRowI[k];
|
|
80
73
|
const lJVal = lRowJ[k];
|
|
81
|
-
AssertMatrixValue(lIVal, { rowIndex: i, columnIndex: k });
|
|
82
|
-
AssertMatrixValue(lJVal, { rowIndex: j, columnIndex: k });
|
|
83
74
|
sum += lIVal * lJVal;
|
|
84
75
|
}
|
|
85
76
|
const matrixVal = matrixRowI[j];
|
|
86
77
|
const lJDiag = lRowJ[j];
|
|
87
|
-
AssertMatrixValue(matrixVal, { rowIndex: i, columnIndex: j });
|
|
88
|
-
AssertMatrixValue(lJDiag, { rowIndex: j, columnIndex: j });
|
|
89
78
|
// Check for numerical stability - diagonal element should not be too small
|
|
90
79
|
if (Math.abs(lJDiag) < MATRIX_NUMERICAL_TOLERANCE) {
|
|
91
|
-
throw new
|
|
80
|
+
throw new MatrixError(`Zero diagonal element at [${j},${j}] - matrix not positive definite`);
|
|
92
81
|
}
|
|
93
82
|
lRowI[j] = (matrixVal - sum) / lJDiag;
|
|
94
83
|
}
|
|
@@ -117,28 +106,26 @@ export function MatrixCholesky(matrix) {
|
|
|
117
106
|
*
|
|
118
107
|
* @param matrix - The square matrix to decompose
|
|
119
108
|
* @returns Object containing eigenvalues and eigenvectors
|
|
120
|
-
* @throws {
|
|
109
|
+
* @throws {MatrixError} If matrix is not square, contains invalid values, or has complex eigenvalues
|
|
121
110
|
*
|
|
122
111
|
* @example
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
* @see {@link MatrixEigenQRIteration} For the iterative algorithm used for larger matrices
|
|
138
|
-
* ```
|
|
112
|
+
* ```typescript
|
|
113
|
+
* // Simple 2x2 matrix
|
|
114
|
+
* const A = [[3, 1], [0, 2]];
|
|
115
|
+
* const { eigenvalues, eigenvectors } = MatrixEigen(A);
|
|
116
|
+
* // eigenvalues: [3, 2]
|
|
117
|
+
* // eigenvectors: matrix where each column corresponds to an eigenvalue
|
|
118
|
+
* // Verify eigenvalue equation: A × v = λ × v
|
|
119
|
+
* const v = Matrix_GetColumn(eigenvectors, 0); // First eigenvector
|
|
120
|
+
* const Av = MatrixMultiplyVector(A, v);
|
|
121
|
+
* const lambdaV = Matrix_ScaleVector(v, eigenvalues[0]);
|
|
122
|
+
* // Av should approximately equal lambdaV
|
|
123
|
+
* ```
|
|
124
|
+
* @complexity O(n³) time for an n×n matrix
|
|
125
|
+
* @see {@link MatrixEigenQRIteration} For the iterative algorithm used for larger matrices
|
|
139
126
|
*/
|
|
140
127
|
export function MatrixEigen(matrix) {
|
|
141
|
-
|
|
128
|
+
AssertMatrixSquare(matrix);
|
|
142
129
|
const [n] = MatrixSize(matrix);
|
|
143
130
|
// For small matrices, use direct analytical computation for efficiency and accuracy
|
|
144
131
|
if (n === 1) {
|
|
@@ -162,7 +149,7 @@ export function MatrixEigen(matrix) {
|
|
|
162
149
|
const discriminant = (trace * trace) - (4 * det);
|
|
163
150
|
// Check for complex eigenvalues
|
|
164
151
|
if (discriminant < 0) {
|
|
165
|
-
throw new
|
|
152
|
+
throw new MatrixError('Complex eigenvalues not supported in this implementation');
|
|
166
153
|
}
|
|
167
154
|
const sqrtDisc = Math.sqrt(discriminant);
|
|
168
155
|
const lambda1 = (trace + sqrtDisc) / 2;
|
|
@@ -175,8 +162,6 @@ export function MatrixEigen(matrix) {
|
|
|
175
162
|
// First eigenvector: [1, 0] (for lambda1 = a)
|
|
176
163
|
// Second eigenvector: [1, (lambda2 - a)/b]
|
|
177
164
|
const [eigenvectorsRow0, eigenvectorsRow1] = eigenvectors;
|
|
178
|
-
AssertMatrixRow(eigenvectorsRow0);
|
|
179
|
-
AssertMatrixRow(eigenvectorsRow1);
|
|
180
165
|
eigenvectorsRow0[0] = 1;
|
|
181
166
|
eigenvectorsRow1[0] = 0;
|
|
182
167
|
eigenvectorsRow0[1] = 1;
|
|
@@ -185,8 +170,6 @@ export function MatrixEigen(matrix) {
|
|
|
185
170
|
else if (Math.abs(c) > MATRIX_NUMERICAL_TOLERANCE) {
|
|
186
171
|
// Use the first row to find eigenvectors when c ≠ 0
|
|
187
172
|
const [eigenvectorsRow0, eigenvectorsRow1] = eigenvectors;
|
|
188
|
-
AssertMatrixRow(eigenvectorsRow0);
|
|
189
|
-
AssertMatrixRow(eigenvectorsRow1);
|
|
190
173
|
eigenvectorsRow0[0] = lambda1 - d;
|
|
191
174
|
eigenvectorsRow1[0] = c;
|
|
192
175
|
eigenvectorsRow0[1] = lambda2 - d;
|
|
@@ -195,8 +178,6 @@ export function MatrixEigen(matrix) {
|
|
|
195
178
|
else {
|
|
196
179
|
// Diagonal matrix case - eigenvectors are standard basis vectors
|
|
197
180
|
const [eigenvectorsRow0, eigenvectorsRow1] = eigenvectors;
|
|
198
|
-
AssertMatrixRow(eigenvectorsRow0);
|
|
199
|
-
AssertMatrixRow(eigenvectorsRow1);
|
|
200
181
|
eigenvectorsRow0[0] = 1;
|
|
201
182
|
eigenvectorsRow1[0] = 0;
|
|
202
183
|
eigenvectorsRow0[1] = 0;
|
|
@@ -208,7 +189,6 @@ export function MatrixEigen(matrix) {
|
|
|
208
189
|
// Calculate the norm (length) of the eigenvector
|
|
209
190
|
for (let i = 0; i < 2; i++) {
|
|
210
191
|
const eigenvectorsRowI = eigenvectors[i];
|
|
211
|
-
AssertMatrixRow(eigenvectorsRowI);
|
|
212
192
|
const val = eigenvectorsRowI[j];
|
|
213
193
|
AssertNumber(val, {}, { message: `Eigenvector[${i},${j}] Not a Number` });
|
|
214
194
|
norm += val * val;
|
|
@@ -218,9 +198,7 @@ export function MatrixEigen(matrix) {
|
|
|
218
198
|
if (norm > MATRIX_NUMERICAL_TOLERANCE) {
|
|
219
199
|
for (let i = 0; i < 2; i++) {
|
|
220
200
|
const eigenvectorsRowI = eigenvectors[i];
|
|
221
|
-
AssertMatrixRow(eigenvectorsRowI);
|
|
222
201
|
const val = eigenvectorsRowI[j];
|
|
223
|
-
AssertMatrixValue(val, { rowIndex: i, columnIndex: j });
|
|
224
202
|
eigenvectorsRowI[j] = val / norm;
|
|
225
203
|
}
|
|
226
204
|
}
|
|
@@ -248,15 +226,22 @@ export function MatrixEigen(matrix) {
|
|
|
248
226
|
* 4. Repeat until convergence (off-diagonal elements become small)
|
|
249
227
|
* 5. Eigenvalues are the diagonal elements of the final matrix
|
|
250
228
|
*
|
|
229
|
+
* **Convergence Notes:**
|
|
230
|
+
* The convergence check (examining off-diagonal elements) is optimized for symmetric
|
|
231
|
+
* inputs, which SVD provides via A^T A. For practical n×n matrices with well-conditioned
|
|
232
|
+
* symmetric inputs, convergence typically occurs within 2–5n iterations. The default
|
|
233
|
+
* maximum of 50 iterations is sufficient for matrices up to size ~10×10; larger matrices
|
|
234
|
+
* may require heuristic adjustments.
|
|
235
|
+
*
|
|
251
236
|
* @private
|
|
252
237
|
* @param matrix - Square matrix to compute eigenvalues for
|
|
253
|
-
* @param iterations - Maximum number of QR iterations (default: 50)
|
|
238
|
+
* @param iterations - Maximum number of QR iterations (default: EIGEN_MAX_ITERATIONS = 50)
|
|
254
239
|
* @returns Object containing eigenvalues and approximated eigenvectors
|
|
255
240
|
*
|
|
256
241
|
* @complexity O(n³) per iteration, typically converges in O(n) iterations
|
|
257
242
|
* @see {@link MatrixQR} For the QR decomposition used in each iteration
|
|
258
243
|
*/
|
|
259
|
-
export function MatrixEigenQRIteration(matrix, iterations =
|
|
244
|
+
export function MatrixEigenQRIteration(matrix, iterations = EIGEN_MAX_ITERATIONS) {
|
|
260
245
|
const [n] = MatrixSize(matrix);
|
|
261
246
|
// Copy matrix for iteration to avoid modifying the original
|
|
262
247
|
let A = MatrixClone(matrix);
|
|
@@ -271,7 +256,7 @@ export function MatrixEigenQRIteration(matrix, iterations = 50) {
|
|
|
271
256
|
catch (err) {
|
|
272
257
|
// If QR fails due to linear dependence, fill Q with orthonormal basis and R with zeros
|
|
273
258
|
AssertInstanceOf(err, Error, { message: 'Unexpected error in QR iteration' });
|
|
274
|
-
if (err
|
|
259
|
+
if (err.message.includes('linearly dependent')) {
|
|
275
260
|
Q = MatrixGramSchmidt(A);
|
|
276
261
|
R = MatrixCreate(n, n); // All zeros
|
|
277
262
|
}
|
|
@@ -285,10 +270,8 @@ export function MatrixEigenQRIteration(matrix, iterations = 50) {
|
|
|
285
270
|
let converged = true;
|
|
286
271
|
for (let i = 0; i < n - 1; i++) {
|
|
287
272
|
const aRowI = A[i];
|
|
288
|
-
AssertMatrixRow(aRowI);
|
|
289
273
|
for (let j = i + 1; j < n; j++) {
|
|
290
274
|
const aVal = aRowI[j];
|
|
291
|
-
AssertMatrixValue(aVal, { rowIndex: i, columnIndex: j });
|
|
292
275
|
if (Math.abs(aVal) > EIGEN_CONVERGENCE_TOLERANCE) {
|
|
293
276
|
converged = false;
|
|
294
277
|
break;
|
|
@@ -304,9 +287,7 @@ export function MatrixEigenQRIteration(matrix, iterations = 50) {
|
|
|
304
287
|
const eigenvalues = [];
|
|
305
288
|
for (let i = 0; i < n; i++) {
|
|
306
289
|
const aRowI = A[i];
|
|
307
|
-
AssertMatrixRow(aRowI);
|
|
308
290
|
const eigenvalue = aRowI[i];
|
|
309
|
-
AssertMatrixValue(eigenvalue, { rowIndex: i, columnIndex: i });
|
|
310
291
|
eigenvalues.push(eigenvalue);
|
|
311
292
|
}
|
|
312
293
|
return {
|
|
@@ -338,22 +319,20 @@ export function MatrixEigenQRIteration(matrix, iterations = 50) {
|
|
|
338
319
|
* @throws {MatrixError} If matrix is not square, singular (zero pivot), or contains invalid values
|
|
339
320
|
*
|
|
340
321
|
* @example
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
* @see {@link MatrixSolve} For solving Ax = b directly
|
|
353
|
-
* ```
|
|
322
|
+
* ```typescript
|
|
323
|
+
* const A = [[2, 1], [1, 1]];
|
|
324
|
+
* const { L, U, P } = MatrixLU(A);
|
|
325
|
+
* // L = [[1, 0], [0.5, 1]], U = [[2, 1], [0, 0.5]], P = [0, 1]
|
|
326
|
+
* // Verify: L × U should equal P-permuted A
|
|
327
|
+
* const product = MatrixMultiply(L, U);
|
|
328
|
+
* // product ≈ [[2, 1], [1, 1]]
|
|
329
|
+
* ```
|
|
330
|
+
* @complexity Time: O(n³/3), Space: O(n²)
|
|
331
|
+
* @see {@link MatrixCholesky} For symmetric positive definite matrices (more efficient)
|
|
332
|
+
* @see {@link MatrixSolve} For solving Ax = b directly
|
|
354
333
|
*/
|
|
355
334
|
export function MatrixLU(matrix) {
|
|
356
|
-
|
|
335
|
+
AssertMatrixSquare(matrix);
|
|
357
336
|
const [n] = MatrixSize(matrix);
|
|
358
337
|
// Work on a mutable copy so partial pivoting can reorder rows
|
|
359
338
|
const A = matrix.map((row) => [...row]);
|
|
@@ -435,32 +414,30 @@ export function MatrixLU(matrix) {
|
|
|
435
414
|
*
|
|
436
415
|
* @param matrix - Matrix to decompose (m×n where m ≥ n, must have full column rank)
|
|
437
416
|
* @returns Object containing orthogonal Q and upper triangular R matrices
|
|
438
|
-
* @throws {
|
|
417
|
+
* @throws {MatrixError} If matrix has more columns than rows or columns are linearly dependent
|
|
439
418
|
*
|
|
440
419
|
* @example
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
* @see {@link MatrixGramSchmidt} {@link MatrixLU} {@link MatrixEigenQRIteration}
|
|
456
|
-
* ```
|
|
420
|
+
* ```typescript
|
|
421
|
+
* const A = [[1, 1], [1, 0], [0, 1]]; // 3×2 matrix
|
|
422
|
+
* const { Q, R } = MatrixQR(A);
|
|
423
|
+
* // Q: 3×2 orthogonal matrix with Q^T × Q = I₂
|
|
424
|
+
* // R: 2×2 upper triangular matrix
|
|
425
|
+
* // Verify: Q × R should equal A
|
|
426
|
+
* const reconstructed = MatrixMultiply(Q, R);
|
|
427
|
+
* // reconstructed ≈ A
|
|
428
|
+
* // Check orthogonality: Q^T × Q should be identity
|
|
429
|
+
* const QT = MatrixTranspose(Q);
|
|
430
|
+
* const identity = MatrixMultiply(QT, Q);
|
|
431
|
+
* ```
|
|
432
|
+
* @complexity Time: O(mn²), Space: O(mn) where m ≥ n
|
|
433
|
+
* @see {@link MatrixGramSchmidt} {@link MatrixLU} {@link MatrixEigenQRIteration}
|
|
457
434
|
*/
|
|
458
435
|
export function MatrixQR(matrix, allowDependentColumns = false) {
|
|
459
436
|
AssertMatrix(matrix);
|
|
460
437
|
const [m, n] = MatrixSize(matrix);
|
|
461
438
|
// Verify that the matrix has at least as many rows as columns
|
|
462
439
|
if (m < n) {
|
|
463
|
-
throw new
|
|
440
|
+
throw new MatrixError('QR decomposition requires matrix to have at least as many rows as columns');
|
|
464
441
|
}
|
|
465
442
|
const Q = MatrixClone(matrix);
|
|
466
443
|
const R = MatrixCreate(n, n);
|
|
@@ -471,16 +448,13 @@ export function MatrixQR(matrix, allowDependentColumns = false) {
|
|
|
471
448
|
const qRow = Q[i];
|
|
472
449
|
if (!qRow)
|
|
473
450
|
continue;
|
|
474
|
-
AssertMatrixRow(qRow);
|
|
475
451
|
const qVal = qRow[k];
|
|
476
|
-
AssertMatrixValue(qVal, { rowIndex: i, columnIndex: k });
|
|
477
452
|
norm += qVal * qVal;
|
|
478
453
|
}
|
|
479
454
|
norm = Math.sqrt(norm);
|
|
480
455
|
if (norm < MATRIX_NUMERICAL_TOLERANCE) {
|
|
481
|
-
if (!allowDependentColumns)
|
|
482
|
-
throw new
|
|
483
|
-
}
|
|
456
|
+
if (!allowDependentColumns)
|
|
457
|
+
throw new MatrixError(`Column ${k} is linearly dependent on previous columns`);
|
|
484
458
|
// Fill Q[:,k] with an orthonormal vector not in the span of previous columns
|
|
485
459
|
const candidate = Array(m).fill(0);
|
|
486
460
|
candidate[k % m] = 1;
|
|
@@ -488,16 +462,12 @@ export function MatrixQR(matrix, allowDependentColumns = false) {
|
|
|
488
462
|
let dot = 0;
|
|
489
463
|
for (let i = 0; i < m; i++) {
|
|
490
464
|
const qRow = Q[i];
|
|
491
|
-
AssertMatrixRow(qRow);
|
|
492
465
|
const qVal = qRow[j];
|
|
493
|
-
AssertMatrixValue(qVal, { rowIndex: i, columnIndex: j });
|
|
494
466
|
dot += qVal * candidate[i];
|
|
495
467
|
}
|
|
496
468
|
for (let i = 0; i < m; i++) {
|
|
497
469
|
const qRow = Q[i];
|
|
498
|
-
AssertMatrixRow(qRow);
|
|
499
470
|
const qVal = qRow[j];
|
|
500
|
-
AssertMatrixValue(qVal, { rowIndex: i, columnIndex: j });
|
|
501
471
|
const candidateVal = candidate[i];
|
|
502
472
|
AssertNumber(candidateVal, {}, { message: `candidate[${i}] Not a Number` });
|
|
503
473
|
candidate[i] = candidateVal - (dot * qVal);
|
|
@@ -508,7 +478,7 @@ export function MatrixQR(matrix, allowDependentColumns = false) {
|
|
|
508
478
|
for (let i = 0; i < m; i++) {
|
|
509
479
|
const qRow = Q[i];
|
|
510
480
|
if (!Array.isArray(qRow))
|
|
511
|
-
throw new
|
|
481
|
+
throw new MatrixError(`Internal error: Q[${i}] is not an array`);
|
|
512
482
|
qRow[k] = candidate[i] / candNorm;
|
|
513
483
|
}
|
|
514
484
|
}
|
|
@@ -516,53 +486,47 @@ export function MatrixQR(matrix, allowDependentColumns = false) {
|
|
|
516
486
|
for (let i = 0; i < m; i++) {
|
|
517
487
|
const qRow = Q[i];
|
|
518
488
|
if (!Array.isArray(qRow))
|
|
519
|
-
throw new
|
|
489
|
+
throw new MatrixError(`Internal error: Q[${i}] is not an array`);
|
|
520
490
|
qRow[k] = 0;
|
|
521
491
|
}
|
|
522
492
|
}
|
|
523
493
|
const rRow = R[k];
|
|
524
|
-
|
|
525
|
-
for (let j = 0; j < n; j++) {
|
|
494
|
+
for (let j = 0; j < n; j++)
|
|
526
495
|
rRow[j] = 0;
|
|
527
|
-
}
|
|
528
496
|
continue;
|
|
529
497
|
}
|
|
530
498
|
const rRow = R[k];
|
|
531
|
-
AssertMatrixRow(rRow);
|
|
532
499
|
rRow[k] = norm;
|
|
533
500
|
for (let i = 0; i < m; i++) {
|
|
534
501
|
const qRow = Q[i];
|
|
535
502
|
if (!qRow)
|
|
536
503
|
continue;
|
|
537
|
-
AssertMatrixRow(qRow);
|
|
538
504
|
const qVal = qRow[k];
|
|
539
|
-
AssertMatrixValue(qVal, { rowIndex: i, columnIndex: k });
|
|
540
505
|
qRow[k] = qVal / norm;
|
|
541
506
|
}
|
|
507
|
+
// Extract k-th column of Q for cache-efficient access
|
|
508
|
+
const qColK = [];
|
|
509
|
+
for (let i = 0; i < m; i++) {
|
|
510
|
+
const qRow = Q[i];
|
|
511
|
+
if (qRow)
|
|
512
|
+
qColK[i] = qRow[k];
|
|
513
|
+
}
|
|
542
514
|
for (let j = k + 1; j < n; j++) {
|
|
543
515
|
let dot = 0;
|
|
544
516
|
for (let i = 0; i < m; i++) {
|
|
545
517
|
const qRow = Q[i];
|
|
546
518
|
if (!qRow)
|
|
547
519
|
continue;
|
|
548
|
-
AssertMatrixRow(qRow);
|
|
549
|
-
const qValK = qRow[k];
|
|
550
520
|
const qValJ = qRow[j];
|
|
551
|
-
|
|
552
|
-
AssertMatrixValue(qValJ, { rowIndex: i, columnIndex: j });
|
|
553
|
-
dot += qValK * qValJ;
|
|
521
|
+
dot += qColK[i] * qValJ;
|
|
554
522
|
}
|
|
555
523
|
rRow[j] = dot;
|
|
556
524
|
for (let i = 0; i < m; i++) {
|
|
557
525
|
const qRow = Q[i];
|
|
558
526
|
if (!qRow)
|
|
559
527
|
continue;
|
|
560
|
-
AssertMatrixRow(qRow);
|
|
561
|
-
const qValK = qRow[k];
|
|
562
528
|
const qValJ = qRow[j];
|
|
563
|
-
|
|
564
|
-
AssertMatrixValue(qValJ, { rowIndex: i, columnIndex: j });
|
|
565
|
-
qRow[j] = qValJ - (dot * qValK);
|
|
529
|
+
qRow[j] = qValJ - (dot * qColK[i]);
|
|
566
530
|
}
|
|
567
531
|
}
|
|
568
532
|
}
|
|
@@ -598,27 +562,25 @@ export function MatrixQR(matrix, allowDependentColumns = false) {
|
|
|
598
562
|
*
|
|
599
563
|
* @param matrix - Matrix to decompose (any m×n matrix)
|
|
600
564
|
* @returns Object containing U, S (singular values), and VT matrices
|
|
601
|
-
* @throws {
|
|
565
|
+
* @throws {MatrixError} If matrix contains invalid values (NaN, Infinity)
|
|
602
566
|
*
|
|
603
567
|
* @example
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
* @see {@link MatrixQR} {@link MatrixEigenQRIteration} {@link Matrix_PseudoInverse}
|
|
621
|
-
* ```
|
|
568
|
+
* ```typescript
|
|
569
|
+
* const A = [[1, 2], [3, 4], [5, 6]]; // 3×2 matrix
|
|
570
|
+
* const { U, S, VT } = MatrixSVD(A);
|
|
571
|
+
* // U: 3×2 matrix with orthonormal columns
|
|
572
|
+
* // S: [σ₁, σ₂] singular values in descending order
|
|
573
|
+
* // VT: 2×2 orthogonal matrix (V transposed)
|
|
574
|
+
* // Verify reconstruction: U × diag(S) × VT ≈ A
|
|
575
|
+
* // Note: Create diagonal matrix Sigma with S on diagonal, then: reconstructed = U × Sigma × VT
|
|
576
|
+
* const reconstructed = MatrixMultiply(MatrixMultiply(U, [[S[0], 0], [0, S[1]]]), VT);
|
|
577
|
+
* // Matrix rank from singular values (count non-zero values)
|
|
578
|
+
* const rank = S.filter(s => s > 1e-10).length;
|
|
579
|
+
* // Condition number for stability analysis
|
|
580
|
+
* const conditionNumber = S[0] / S[S.length - 1];
|
|
581
|
+
* ```
|
|
582
|
+
* @complexity Time: O(min(m²n, mn²)), Space: O(m² + n²)
|
|
583
|
+
* @see {@link MatrixQR} {@link MatrixEigenQRIteration} {@link Matrix_PseudoInverse}
|
|
622
584
|
*/
|
|
623
585
|
export function MatrixSVD(matrix) {
|
|
624
586
|
AssertMatrix(matrix);
|
|
@@ -698,19 +660,21 @@ export function MatrixSVD(matrix) {
|
|
|
698
660
|
* @throws {MatrixError} If A is not square, singular, or b has the wrong length
|
|
699
661
|
*
|
|
700
662
|
* @example
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
663
|
+
* ```typescript
|
|
664
|
+
* // 2x + y = 8
|
|
665
|
+
* // 5x + 3y = 20
|
|
666
|
+
* MatrixSolve([[2, 1], [5, 3]], [8, 20]); // [4, 0]
|
|
667
|
+
* ```
|
|
668
|
+
* @example
|
|
669
|
+
* ```typescript
|
|
670
|
+
* // Solve a 3×3 system
|
|
671
|
+
* const A = [[1, 2, -1], [2, 1, 1], [3, -1, 2]];
|
|
672
|
+
* const b = [4, 7, 2];
|
|
673
|
+
* MatrixSolve(A, b); // solution vector x
|
|
674
|
+
* ```
|
|
711
675
|
*/
|
|
712
676
|
export function MatrixSolve(a, b) {
|
|
713
|
-
|
|
677
|
+
AssertMatrixSquare(a);
|
|
714
678
|
const [n] = MatrixSize(a);
|
|
715
679
|
if (b.length !== n) {
|
|
716
680
|
throw new MatrixError(`Right-hand side vector length (${b.length}) must match matrix dimension (${n})`);
|
|
@@ -720,18 +684,16 @@ export function MatrixSolve(a, b) {
|
|
|
720
684
|
const bPerm = P.map((pi) => {
|
|
721
685
|
const val = b[pi];
|
|
722
686
|
if (val === undefined)
|
|
723
|
-
throw new
|
|
687
|
+
throw new Error(`b[${pi}] is undefined`);
|
|
724
688
|
return val;
|
|
725
689
|
});
|
|
726
690
|
// Forward substitution: solve Ly = b_perm (L has 1s on its diagonal)
|
|
727
691
|
const y = new Array(n).fill(0);
|
|
728
692
|
for (let i = 0; i < n; i++) {
|
|
729
693
|
const lRow = L[i];
|
|
730
|
-
AssertMatrixRow(lRow);
|
|
731
694
|
let sum = 0;
|
|
732
695
|
for (let j = 0; j < i; j++) {
|
|
733
696
|
const lVal = lRow[j];
|
|
734
|
-
AssertMatrixValue(lVal);
|
|
735
697
|
sum += lVal * y[j];
|
|
736
698
|
}
|
|
737
699
|
y[i] = bPerm[i] - sum;
|
|
@@ -740,15 +702,12 @@ export function MatrixSolve(a, b) {
|
|
|
740
702
|
const x = new Array(n).fill(0);
|
|
741
703
|
for (let i = n - 1; i >= 0; i--) {
|
|
742
704
|
const uRow = U[i];
|
|
743
|
-
AssertMatrixRow(uRow);
|
|
744
705
|
let sum = 0;
|
|
745
706
|
for (let j = i + 1; j < n; j++) {
|
|
746
707
|
const uVal = uRow[j];
|
|
747
|
-
AssertMatrixValue(uVal);
|
|
748
708
|
sum += uVal * x[j];
|
|
749
709
|
}
|
|
750
710
|
const uDiag = uRow[i];
|
|
751
|
-
AssertMatrixValue(uDiag);
|
|
752
711
|
if (uDiag === 0)
|
|
753
712
|
throw new MatrixError('Matrix is singular — cannot solve the linear system');
|
|
754
713
|
x[i] = (y[i] - sum) / uDiag;
|