@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.
Files changed (118) hide show
  1. package/README.md +15 -21
  2. package/build/clamp.d.ts +5 -5
  3. package/build/clamp.js +5 -5
  4. package/build/core.d.ts +23 -0
  5. package/build/core.d.ts.map +1 -0
  6. package/build/core.js +25 -0
  7. package/build/core.js.map +1 -0
  8. package/build/index.d.ts +1 -4
  9. package/build/index.d.ts.map +1 -1
  10. package/build/index.js +1 -6
  11. package/build/index.js.map +1 -1
  12. package/build/interpolation.d.ts +158 -171
  13. package/build/interpolation.d.ts.map +1 -1
  14. package/build/interpolation.js +162 -181
  15. package/build/interpolation.js.map +1 -1
  16. package/build/matrices/arithmetic.d.ts +132 -132
  17. package/build/matrices/arithmetic.d.ts.map +1 -1
  18. package/build/matrices/arithmetic.js +194 -226
  19. package/build/matrices/arithmetic.js.map +1 -1
  20. package/build/matrices/asserts.d.ts +30 -408
  21. package/build/matrices/asserts.d.ts.map +1 -1
  22. package/build/matrices/asserts.js +98 -542
  23. package/build/matrices/asserts.js.map +1 -1
  24. package/build/matrices/core.d.ts +117 -46
  25. package/build/matrices/core.d.ts.map +1 -1
  26. package/build/matrices/core.js +127 -103
  27. package/build/matrices/core.js.map +1 -1
  28. package/build/matrices/decompositions.d.ts +95 -96
  29. package/build/matrices/decompositions.d.ts.map +1 -1
  30. package/build/matrices/decompositions.js +119 -160
  31. package/build/matrices/decompositions.js.map +1 -1
  32. package/build/matrices/index.d.ts +0 -1
  33. package/build/matrices/index.d.ts.map +1 -1
  34. package/build/matrices/index.js +0 -3
  35. package/build/matrices/index.js.map +1 -1
  36. package/build/matrices/linear-algebra.d.ts +45 -8
  37. package/build/matrices/linear-algebra.d.ts.map +1 -1
  38. package/build/matrices/linear-algebra.js +76 -32
  39. package/build/matrices/linear-algebra.js.map +1 -1
  40. package/build/matrices/normalization.d.ts +29 -8
  41. package/build/matrices/normalization.d.ts.map +1 -1
  42. package/build/matrices/normalization.js +32 -23
  43. package/build/matrices/normalization.js.map +1 -1
  44. package/build/matrices/transformations.d.ts +116 -148
  45. package/build/matrices/transformations.d.ts.map +1 -1
  46. package/build/matrices/transformations.js +69 -91
  47. package/build/matrices/transformations.js.map +1 -1
  48. package/build/matrices/types.d.ts +32 -60
  49. package/build/matrices/types.d.ts.map +1 -1
  50. package/build/matrices/types.js +63 -1
  51. package/build/matrices/types.js.map +1 -1
  52. package/build/quaternions/asserts.d.ts +29 -38
  53. package/build/quaternions/asserts.d.ts.map +1 -1
  54. package/build/quaternions/asserts.js +61 -77
  55. package/build/quaternions/asserts.js.map +1 -1
  56. package/build/quaternions/conversions.d.ts +26 -26
  57. package/build/quaternions/conversions.d.ts.map +1 -1
  58. package/build/quaternions/conversions.js +24 -24
  59. package/build/quaternions/core.d.ts +70 -69
  60. package/build/quaternions/core.d.ts.map +1 -1
  61. package/build/quaternions/core.js +71 -71
  62. package/build/quaternions/core.js.map +1 -1
  63. package/build/quaternions/index.d.ts +0 -1
  64. package/build/quaternions/index.d.ts.map +1 -1
  65. package/build/quaternions/index.js +0 -2
  66. package/build/quaternions/index.js.map +1 -1
  67. package/build/quaternions/interpolation.d.ts +16 -16
  68. package/build/quaternions/interpolation.d.ts.map +1 -1
  69. package/build/quaternions/interpolation.js +21 -21
  70. package/build/quaternions/interpolation.js.map +1 -1
  71. package/build/quaternions/predefined.d.ts +10 -10
  72. package/build/quaternions/predefined.d.ts.map +1 -1
  73. package/build/quaternions/predefined.js +9 -9
  74. package/build/quaternions/types.d.ts +23 -12
  75. package/build/quaternions/types.d.ts.map +1 -1
  76. package/build/quaternions/types.js +51 -1
  77. package/build/quaternions/types.js.map +1 -1
  78. package/build/random.d.ts +66 -20
  79. package/build/random.d.ts.map +1 -1
  80. package/build/random.js +73 -20
  81. package/build/random.js.map +1 -1
  82. package/build/vectors/asserts.d.ts +50 -220
  83. package/build/vectors/asserts.d.ts.map +1 -1
  84. package/build/vectors/asserts.js +141 -327
  85. package/build/vectors/asserts.js.map +1 -1
  86. package/build/vectors/core.d.ts +182 -229
  87. package/build/vectors/core.d.ts.map +1 -1
  88. package/build/vectors/core.js +234 -288
  89. package/build/vectors/core.js.map +1 -1
  90. package/build/vectors/index.d.ts +0 -1
  91. package/build/vectors/index.d.ts.map +1 -1
  92. package/build/vectors/index.js +0 -2
  93. package/build/vectors/index.js.map +1 -1
  94. package/build/vectors/interpolation.d.ts +40 -40
  95. package/build/vectors/interpolation.d.ts.map +1 -1
  96. package/build/vectors/interpolation.js +75 -86
  97. package/build/vectors/interpolation.js.map +1 -1
  98. package/build/vectors/predefined.d.ts +31 -7
  99. package/build/vectors/predefined.d.ts.map +1 -1
  100. package/build/vectors/predefined.js +30 -6
  101. package/build/vectors/predefined.js.map +1 -1
  102. package/build/vectors/types.d.ts +26 -4
  103. package/build/vectors/types.d.ts.map +1 -1
  104. package/build/vectors/types.js +50 -1
  105. package/build/vectors/types.js.map +1 -1
  106. package/package.json +3 -2
  107. package/build/matrices/_exports.d.ts +0 -13
  108. package/build/matrices/_exports.d.ts.map +0 -1
  109. package/build/matrices/_exports.js +0 -13
  110. package/build/matrices/_exports.js.map +0 -1
  111. package/build/quaternions/_exports.d.ts +0 -11
  112. package/build/quaternions/_exports.d.ts.map +0 -1
  113. package/build/quaternions/_exports.js +0 -11
  114. package/build/quaternions/_exports.js.map +0 -1
  115. package/build/vectors/_exports.d.ts +0 -10
  116. package/build/vectors/_exports.d.ts.map +0 -1
  117. package/build/vectors/_exports.js +0 -10
  118. 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, AssertMatrixRow, AssertMatrixValue, AssertMatrix1, AssertMatrix2, MatrixError } from './asserts.js';
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 {Error} If matrix is not square, not symmetric, or not positive definite
26
+ * @throws {MatrixError} If matrix is not square, not symmetric, or not positive definite
26
27
  *
27
28
  * @example
28
- * ```typescript
29
- * ```ts
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
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
- AssertMatrix(matrix, { square: true });
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 Error(`Matrix is not positive definite at element [${i},${j}]`);
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 Error(`Zero diagonal element at [${j},${j}] - matrix not positive definite`);
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 {Error} If matrix is not square, contains invalid values, or has complex eigenvalues
109
+ * @throws {MatrixError} If matrix is not square, contains invalid values, or has complex eigenvalues
121
110
  *
122
111
  * @example
123
- * ```typescript
124
- * ```ts
125
- * // Simple 2x2 matrix
126
- * const A = [[3, 1], [0, 2]];
127
- * const { eigenvalues, eigenvectors } = MatrixEigen(A);
128
- * // eigenvalues: [3, 2]
129
- * // eigenvectors: matrix where each column corresponds to an eigenvalue
130
- * // Verify eigenvalue equation: A × v = λ × v
131
- * const v = Matrix_GetColumn(eigenvectors, 0); // First eigenvector
132
- * const Av = MatrixMultiplyVector(A, v);
133
- * const lambdaV = Matrix_ScaleVector(v, eigenvalues[0]);
134
- * // Av should approximately equal lambdaV
135
- * ```
136
- * @complexity O(n³) time for an n×n matrix
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
- AssertMatrix(matrix, { square: true });
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 Error('Complex eigenvalues not supported in this implementation');
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 = 50) {
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 instanceof Error && typeof err.message === 'string' && err.message.includes('linearly dependent')) {
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
- * ```typescript
342
- * ```ts
343
- * const A = [[2, 1], [1, 1]];
344
- * const { L, U, P } = MatrixLU(A);
345
- * // L = [[1, 0], [0.5, 1]], U = [[2, 1], [0, 0.5]], P = [0, 1]
346
- * // Verify: L × U should equal P-permuted A
347
- * const product = MatrixMultiply(L, U);
348
- * // product ≈ [[2, 1], [1, 1]]
349
- * ```
350
- * @complexity Time: O(n³/3), Space: O()
351
- * @see {@link MatrixCholesky} For symmetric positive definite matrices (more efficient)
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
- AssertMatrix(matrix, { square: true });
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 {Error} If matrix has more columns than rows or columns are linearly dependent
417
+ * @throws {MatrixError} If matrix has more columns than rows or columns are linearly dependent
439
418
  *
440
419
  * @example
441
- * ```typescript
442
- * ```ts
443
- * const A = [[1, 1], [1, 0], [0, 1]]; // 3×2 matrix
444
- * const { Q, R } = MatrixQR(A);
445
- * // Q: 3×2 orthogonal matrix with Q^T × Q = I₂
446
- * // R: 2×2 upper triangular matrix
447
- * // Verify: Q × R should equal A
448
- * const reconstructed = MatrixMultiply(Q, R);
449
- * // reconstructed A
450
- * // Check orthogonality: Q^T × Q should be identity
451
- * const QT = MatrixTranspose(Q);
452
- * const identity = MatrixMultiply(QT, Q);
453
- * ```
454
- * @complexity Time: O(mn²), Space: O(mn) where m ≥ n
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 Error('QR decomposition requires matrix to have at least as many rows as columns');
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 Error(`Column ${k} is linearly dependent on previous columns`);
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 Error(`Internal error: Q[${i}] is not an array`);
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 Error(`Internal error: Q[${i}] is not an array`);
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
- AssertMatrixRow(rRow);
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
- AssertMatrixValue(qValK, { rowIndex: i, columnIndex: k });
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
- AssertMatrixValue(qValK, { rowIndex: i, columnIndex: k });
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 {Error} If matrix contains invalid values (NaN, Infinity)
565
+ * @throws {MatrixError} If matrix contains invalid values (NaN, Infinity)
602
566
  *
603
567
  * @example
604
- * ```typescript
605
- * ```ts
606
- * const A = [[1, 2], [3, 4], [5, 6]]; // 3×2 matrix
607
- * const { U, S, VT } = MatrixSVD(A);
608
- * // U: 3×2 matrix with orthonormal columns
609
- * // S: [σ₁, σ₂] singular values in descending order
610
- * // VT: 2×2 orthogonal matrix (V transposed)
611
- * // Verify reconstruction: U × diag(S) × VT ≈ A
612
- * const Sigma = Matrix_Diagonal(S);
613
- * const reconstructed = MatrixMultiply(MatrixMultiply(U, Sigma), VT);
614
- * // Matrix rank from singular values (count non-zero values)
615
- * const rank = S.filter(s => s > 1e-10).length;
616
- * // Condition number for stability analysis
617
- * const conditionNumber = S[0] / S[S.length - 1];
618
- * ```
619
- * @complexity Time: O(min(m²n, mn²)), Space: O(m² + n²)
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
- * ```typescript
702
- * // 2x + y = 8
703
- * // 5x + 3y = 20
704
- * MatrixSolve([[2, 1], [5, 3]], [8, 20]); // [4, 0]
705
- * @example
706
- * // Solve a 3×3 system
707
- * const A = [[1, 2, -1], [2, 1, 1], [3, -1, 2]];
708
- * const b = [4, 7, 2];
709
- * MatrixSolve(A, b); // solution vector x
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
- AssertMatrix(a, { square: true });
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 MatrixError(`b[${pi}] is undefined`);
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;