ml-matrix 6.12.1 → 6.13.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ml-matrix",
3
- "version": "6.12.1",
3
+ "version": "6.13.0",
4
4
  "description": "Matrix manipulation and computation library",
5
5
  "main": "matrix.js",
6
6
  "module": "src/index.js",
@@ -18,8 +18,10 @@
18
18
  "files": [
19
19
  "matrix.d.ts",
20
20
  "matrix.js",
21
+ "matrix.js.map",
21
22
  "matrix.mjs",
22
23
  "matrix.umd.js",
24
+ "matrix.umd.js.map",
23
25
  "src"
24
26
  ],
25
27
  "scripts": {
@@ -61,26 +63,27 @@
61
63
  },
62
64
  "homepage": "https://github.com/mljs/matrix",
63
65
  "devDependencies": {
64
- "@babel/plugin-transform-modules-commonjs": "^7.23.0",
65
- "@rollup/plugin-commonjs": "^25.0.7",
66
- "@rollup/plugin-node-resolve": "^15.2.3",
66
+ "@babel/plugin-transform-modules-commonjs": "^7.27.1",
67
+ "@rollup/plugin-commonjs": "^28.0.6",
68
+ "@rollup/plugin-node-resolve": "^16.0.3",
67
69
  "@rollup/plugin-terser": "^0.4.4",
68
- "@vitest/coverage-v8": "^0.34.6",
70
+ "@vitest/coverage-v8": "^3.2.4",
69
71
  "benchmark": "^2.1.4",
70
- "csv-parse": "^5.5.2",
71
- "eslint": "^8.51.0",
72
- "eslint-config-cheminfo": "^9.0.2",
72
+ "csv-parse": "^5.6.0",
73
+ "eslint": "^9.29.0",
74
+ "eslint-config-cheminfo": "^14.1.1",
73
75
  "jest-matcher-deep-close-to": "^3.0.2",
74
- "mathjs": "^11.11.2",
76
+ "mathjs": "^14.5.2",
75
77
  "ml-dataset-iris": "^1.2.1",
78
+ "ml-xsadd": "^3.0.1",
76
79
  "numeric": "^1.2.6",
77
- "prettier": "^3.0.3",
80
+ "prettier": "^3.5.3",
78
81
  "pretty-hrtime": "^1.0.3",
79
- "rollup": "^4.1.4",
80
- "vitest": "^0.34.6"
82
+ "rollup": "^4.44.0",
83
+ "vitest": "^3.2.4"
81
84
  },
82
85
  "dependencies": {
83
- "is-any-array": "^2.0.1",
84
- "ml-array-rescale": "^1.3.7"
86
+ "is-any-array": "^3.0.0",
87
+ "ml-array-rescale": "^2.0.0"
85
88
  }
86
89
  }
package/src/dc/evd.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import Matrix from '../matrix';
2
2
  import WrapperMatrix2D from '../wrap/WrapperMatrix2D';
3
3
 
4
- import { hypotenuse } from './util';
4
+ import { hypotenuse, transposeSquareInPlace } from './util';
5
5
 
6
6
  export default class EigenvalueDecomposition {
7
7
  constructor(matrix, options = {}) {
@@ -31,14 +31,25 @@ export default class EigenvalueDecomposition {
31
31
  }
32
32
 
33
33
  if (isSymmetric) {
34
+ // tred2/tql2 access V almost exclusively down columns (the row index
35
+ // varies in the hot loops). Storing V transposed turns those into
36
+ // sequential row scans of the row-major backing store; we transpose it
37
+ // back to the logical layout before returning. V.get(j, i) holds the
38
+ // logical V(i, j).
34
39
  for (i = 0; i < n; i++) {
35
40
  for (j = 0; j < n; j++) {
36
- V.set(i, j, value.get(i, j));
41
+ V.set(j, i, value.get(i, j));
37
42
  }
38
43
  }
39
44
  tred2(n, e, d, V);
40
45
  tql2(n, e, d, V);
46
+ // V is square; restore the logical layout in place (no allocation).
47
+ transposeSquareInPlace(V);
41
48
  } else {
49
+ // The non-symmetric path (orthes/hqr2) has two O(n^3) phases with opposite
50
+ // memory-layout preferences (the QR sweep favours column-major eigenvectors
51
+ // while the back-transform favours row-major), so a single transposed
52
+ // storage cannot help both. It is left in the original row-major layout.
42
53
  let H = new Matrix(n, n);
43
54
  let ort = new Float64Array(n);
44
55
  for (j = 0; j < n; j++) {
@@ -93,7 +104,7 @@ function tred2(n, e, d, V) {
93
104
  let f, g, h, i, j, k, hh, scale;
94
105
 
95
106
  for (j = 0; j < n; j++) {
96
- d[j] = V.get(n - 1, j);
107
+ d[j] = V.get(j, n - 1);
97
108
  }
98
109
 
99
110
  for (i = n - 1; i > 0; i--) {
@@ -106,9 +117,9 @@ function tred2(n, e, d, V) {
106
117
  if (scale === 0) {
107
118
  e[i] = d[i - 1];
108
119
  for (j = 0; j < i; j++) {
109
- d[j] = V.get(i - 1, j);
110
- V.set(i, j, 0);
120
+ d[j] = V.get(j, i - 1);
111
121
  V.set(j, i, 0);
122
+ V.set(i, j, 0);
112
123
  }
113
124
  } else {
114
125
  for (k = 0; k < i; k++) {
@@ -131,11 +142,11 @@ function tred2(n, e, d, V) {
131
142
 
132
143
  for (j = 0; j < i; j++) {
133
144
  f = d[j];
134
- V.set(j, i, f);
145
+ V.set(i, j, f);
135
146
  g = e[j] + V.get(j, j) * f;
136
147
  for (k = j + 1; k <= i - 1; k++) {
137
- g += V.get(k, j) * d[k];
138
- e[k] += V.get(k, j) * f;
148
+ g += V.get(j, k) * d[k];
149
+ e[k] += V.get(j, k) * f;
139
150
  }
140
151
  e[j] = g;
141
152
  }
@@ -155,43 +166,43 @@ function tred2(n, e, d, V) {
155
166
  f = d[j];
156
167
  g = e[j];
157
168
  for (k = j; k <= i - 1; k++) {
158
- V.set(k, j, V.get(k, j) - (f * e[k] + g * d[k]));
169
+ V.set(j, k, V.get(j, k) - (f * e[k] + g * d[k]));
159
170
  }
160
- d[j] = V.get(i - 1, j);
161
- V.set(i, j, 0);
171
+ d[j] = V.get(j, i - 1);
172
+ V.set(j, i, 0);
162
173
  }
163
174
  }
164
175
  d[i] = h;
165
176
  }
166
177
 
167
178
  for (i = 0; i < n - 1; i++) {
168
- V.set(n - 1, i, V.get(i, i));
179
+ V.set(i, n - 1, V.get(i, i));
169
180
  V.set(i, i, 1);
170
181
  h = d[i + 1];
171
182
  if (h !== 0) {
172
183
  for (k = 0; k <= i; k++) {
173
- d[k] = V.get(k, i + 1) / h;
184
+ d[k] = V.get(i + 1, k) / h;
174
185
  }
175
186
 
176
187
  for (j = 0; j <= i; j++) {
177
188
  g = 0;
178
189
  for (k = 0; k <= i; k++) {
179
- g += V.get(k, i + 1) * V.get(k, j);
190
+ g += V.get(i + 1, k) * V.get(j, k);
180
191
  }
181
192
  for (k = 0; k <= i; k++) {
182
- V.set(k, j, V.get(k, j) - g * d[k]);
193
+ V.set(j, k, V.get(j, k) - g * d[k]);
183
194
  }
184
195
  }
185
196
  }
186
197
 
187
198
  for (k = 0; k <= i; k++) {
188
- V.set(k, i + 1, 0);
199
+ V.set(i + 1, k, 0);
189
200
  }
190
201
  }
191
202
 
192
203
  for (j = 0; j < n; j++) {
193
- d[j] = V.get(n - 1, j);
194
- V.set(n - 1, j, 0);
204
+ d[j] = V.get(j, n - 1);
205
+ V.set(j, n - 1, 0);
195
206
  }
196
207
 
197
208
  V.set(n - 1, n - 1, 1);
@@ -264,9 +275,9 @@ function tql2(n, e, d, V) {
264
275
  d[i + 1] = h + s * (c * g + s * d[i]);
265
276
 
266
277
  for (k = 0; k < n; k++) {
267
- h = V.get(k, i + 1);
268
- V.set(k, i + 1, s * V.get(k, i) + c * h);
269
- V.set(k, i, c * V.get(k, i) - s * h);
278
+ h = V.get(i + 1, k);
279
+ V.set(i + 1, k, s * V.get(i, k) + c * h);
280
+ V.set(i, k, c * V.get(i, k) - s * h);
270
281
  }
271
282
  }
272
283
 
@@ -293,9 +304,9 @@ function tql2(n, e, d, V) {
293
304
  d[k] = d[i];
294
305
  d[i] = p;
295
306
  for (j = 0; j < n; j++) {
296
- p = V.get(j, i);
297
- V.set(j, i, V.get(j, k));
298
- V.set(j, k, p);
307
+ p = V.get(i, j);
308
+ V.set(i, j, V.get(k, j));
309
+ V.set(k, j, p);
299
310
  }
300
311
  }
301
312
  }
package/src/dc/svd.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import Matrix from '../matrix';
2
2
  import WrapperMatrix2D from '../wrap/WrapperMatrix2D';
3
3
 
4
- import { hypotenuse } from './util';
4
+ import { hypotenuse, transposeSquareInPlace } from './util';
5
5
 
6
6
  export default class SingularValueDecomposition {
7
7
  constructor(value, options = {}) {
@@ -23,32 +23,40 @@ export default class SingularValueDecomposition {
23
23
  let wantu = Boolean(computeLeftSingularVectors);
24
24
  let wantv = Boolean(computeRightSingularVectors);
25
25
 
26
+ // Work on the transpose of the input so the hot inner loops (which iterate
27
+ // over rows for a fixed column) scan memory sequentially in the row-major
28
+ // backing store. `at` holds the transpose: at.get(j, i) === a.get(i, j)
29
+ // where `a` is the logical m x n working matrix.
26
30
  let swapped = false;
27
- let a;
31
+ let at;
28
32
  if (m < n) {
29
33
  if (!autoTranspose) {
30
- a = value.clone();
31
34
  // eslint-disable-next-line no-console
32
35
  console.warn(
33
36
  'Computing SVD on a matrix with more columns than rows. Consider enabling autoTranspose',
34
37
  );
38
+ at = value.transpose();
35
39
  } else {
36
- a = value.transpose();
37
- m = a.rows;
38
- n = a.columns;
40
+ at = value.clone();
41
+ m = value.columns;
42
+ n = value.rows;
39
43
  swapped = true;
40
44
  let aux = wantu;
41
45
  wantu = wantv;
42
46
  wantv = aux;
43
47
  }
44
48
  } else {
45
- a = value.clone();
49
+ at = value.transpose();
46
50
  }
47
51
 
48
52
  let nu = Math.min(m, n);
49
53
  let ni = Math.min(m + 1, n);
50
54
  let s = new Float64Array(ni);
51
- let U = new Matrix(m, nu);
55
+ // U and V are stored transposed during the computation so the inner loops
56
+ // (which always vary the row index) scan memory sequentially. They are
57
+ // transposed back to their logical layout before being returned.
58
+ // Ut.get(j, i) === U.get(i, j) and Vt.get(j, i) === V.get(i, j).
59
+ let U = new Matrix(nu, m);
52
60
  let V = new Matrix(n, n);
53
61
 
54
62
  let e = new Float64Array(n);
@@ -65,16 +73,16 @@ export default class SingularValueDecomposition {
65
73
  if (k < nct) {
66
74
  s[k] = 0;
67
75
  for (let i = k; i < m; i++) {
68
- s[k] = hypotenuse(s[k], a.get(i, k));
76
+ s[k] = hypotenuse(s[k], at.get(k, i));
69
77
  }
70
78
  if (s[k] !== 0) {
71
- if (a.get(k, k) < 0) {
79
+ if (at.get(k, k) < 0) {
72
80
  s[k] = -s[k];
73
81
  }
74
82
  for (let i = k; i < m; i++) {
75
- a.set(i, k, a.get(i, k) / s[k]);
83
+ at.set(k, i, at.get(k, i) / s[k]);
76
84
  }
77
- a.set(k, k, a.get(k, k) + 1);
85
+ at.set(k, k, at.get(k, k) + 1);
78
86
  }
79
87
  s[k] = -s[k];
80
88
  }
@@ -83,19 +91,19 @@ export default class SingularValueDecomposition {
83
91
  if (k < nct && s[k] !== 0) {
84
92
  let t = 0;
85
93
  for (let i = k; i < m; i++) {
86
- t += a.get(i, k) * a.get(i, j);
94
+ t += at.get(k, i) * at.get(j, i);
87
95
  }
88
- t = -t / a.get(k, k);
96
+ t = -t / at.get(k, k);
89
97
  for (let i = k; i < m; i++) {
90
- a.set(i, j, a.get(i, j) + t * a.get(i, k));
98
+ at.set(j, i, at.get(j, i) + t * at.get(k, i));
91
99
  }
92
100
  }
93
- e[j] = a.get(k, j);
101
+ e[j] = at.get(j, k);
94
102
  }
95
103
 
96
104
  if (wantu && k < nct) {
97
105
  for (let i = k; i < m; i++) {
98
- U.set(i, k, a.get(i, k));
106
+ U.set(k, i, at.get(k, i));
99
107
  }
100
108
  }
101
109
 
@@ -120,19 +128,19 @@ export default class SingularValueDecomposition {
120
128
  }
121
129
  for (let i = k + 1; i < m; i++) {
122
130
  for (let j = k + 1; j < n; j++) {
123
- work[i] += e[j] * a.get(i, j);
131
+ work[i] += e[j] * at.get(j, i);
124
132
  }
125
133
  }
126
134
  for (let j = k + 1; j < n; j++) {
127
135
  let t = -e[j] / e[k + 1];
128
136
  for (let i = k + 1; i < m; i++) {
129
- a.set(i, j, a.get(i, j) + t * work[i]);
137
+ at.set(j, i, at.get(j, i) + t * work[i]);
130
138
  }
131
139
  }
132
140
  }
133
141
  if (wantv) {
134
142
  for (let i = k + 1; i < n; i++) {
135
- V.set(i, k, e[i]);
143
+ V.set(k, i, e[i]);
136
144
  }
137
145
  }
138
146
  }
@@ -140,20 +148,20 @@ export default class SingularValueDecomposition {
140
148
 
141
149
  let p = Math.min(n, m + 1);
142
150
  if (nct < n) {
143
- s[nct] = a.get(nct, nct);
151
+ s[nct] = at.get(nct, nct);
144
152
  }
145
153
  if (m < p) {
146
154
  s[p - 1] = 0;
147
155
  }
148
156
  if (nrt + 1 < p) {
149
- e[nrt] = a.get(nrt, p - 1);
157
+ e[nrt] = at.get(p - 1, nrt);
150
158
  }
151
159
  e[p - 1] = 0;
152
160
 
153
161
  if (wantu) {
154
162
  for (let j = nct; j < nu; j++) {
155
163
  for (let i = 0; i < m; i++) {
156
- U.set(i, j, 0);
164
+ U.set(j, i, 0);
157
165
  }
158
166
  U.set(j, j, 1);
159
167
  }
@@ -162,23 +170,23 @@ export default class SingularValueDecomposition {
162
170
  for (let j = k + 1; j < nu; j++) {
163
171
  let t = 0;
164
172
  for (let i = k; i < m; i++) {
165
- t += U.get(i, k) * U.get(i, j);
173
+ t += U.get(k, i) * U.get(j, i);
166
174
  }
167
175
  t = -t / U.get(k, k);
168
176
  for (let i = k; i < m; i++) {
169
- U.set(i, j, U.get(i, j) + t * U.get(i, k));
177
+ U.set(j, i, U.get(j, i) + t * U.get(k, i));
170
178
  }
171
179
  }
172
180
  for (let i = k; i < m; i++) {
173
- U.set(i, k, -U.get(i, k));
181
+ U.set(k, i, -U.get(k, i));
174
182
  }
175
183
  U.set(k, k, 1 + U.get(k, k));
176
184
  for (let i = 0; i < k - 1; i++) {
177
- U.set(i, k, 0);
185
+ U.set(k, i, 0);
178
186
  }
179
187
  } else {
180
188
  for (let i = 0; i < m; i++) {
181
- U.set(i, k, 0);
189
+ U.set(k, i, 0);
182
190
  }
183
191
  U.set(k, k, 1);
184
192
  }
@@ -191,16 +199,16 @@ export default class SingularValueDecomposition {
191
199
  for (let j = k + 1; j < n; j++) {
192
200
  let t = 0;
193
201
  for (let i = k + 1; i < n; i++) {
194
- t += V.get(i, k) * V.get(i, j);
202
+ t += V.get(k, i) * V.get(j, i);
195
203
  }
196
- t = -t / V.get(k + 1, k);
204
+ t = -t / V.get(k, k + 1);
197
205
  for (let i = k + 1; i < n; i++) {
198
- V.set(i, j, V.get(i, j) + t * V.get(i, k));
206
+ V.set(j, i, V.get(j, i) + t * V.get(k, i));
199
207
  }
200
208
  }
201
209
  }
202
210
  for (let i = 0; i < n; i++) {
203
- V.set(i, k, 0);
211
+ V.set(k, i, 0);
204
212
  }
205
213
  V.set(k, k, 1);
206
214
  }
@@ -265,9 +273,9 @@ export default class SingularValueDecomposition {
265
273
  }
266
274
  if (wantv) {
267
275
  for (let i = 0; i < n; i++) {
268
- t = cs * V.get(i, j) + sn * V.get(i, p - 1);
269
- V.set(i, p - 1, -sn * V.get(i, j) + cs * V.get(i, p - 1));
270
- V.set(i, j, t);
276
+ t = cs * V.get(j, i) + sn * V.get(p - 1, i);
277
+ V.set(p - 1, i, -sn * V.get(j, i) + cs * V.get(p - 1, i));
278
+ V.set(j, i, t);
271
279
  }
272
280
  }
273
281
  }
@@ -285,9 +293,9 @@ export default class SingularValueDecomposition {
285
293
  e[j] = cs * e[j];
286
294
  if (wantu) {
287
295
  for (let i = 0; i < m; i++) {
288
- t = cs * U.get(i, j) + sn * U.get(i, k - 1);
289
- U.set(i, k - 1, -sn * U.get(i, j) + cs * U.get(i, k - 1));
290
- U.set(i, j, t);
296
+ t = cs * U.get(j, i) + sn * U.get(k - 1, i);
297
+ U.set(k - 1, i, -sn * U.get(j, i) + cs * U.get(k - 1, i));
298
+ U.set(j, i, t);
291
299
  }
292
300
  }
293
301
  }
@@ -333,9 +341,9 @@ export default class SingularValueDecomposition {
333
341
  s[j + 1] = cs * s[j + 1];
334
342
  if (wantv) {
335
343
  for (let i = 0; i < n; i++) {
336
- t = cs * V.get(i, j) + sn * V.get(i, j + 1);
337
- V.set(i, j + 1, -sn * V.get(i, j) + cs * V.get(i, j + 1));
338
- V.set(i, j, t);
344
+ t = cs * V.get(j, i) + sn * V.get(j + 1, i);
345
+ V.set(j + 1, i, -sn * V.get(j, i) + cs * V.get(j + 1, i));
346
+ V.set(j, i, t);
339
347
  }
340
348
  }
341
349
  t = hypotenuse(f, g);
@@ -349,9 +357,9 @@ export default class SingularValueDecomposition {
349
357
  e[j + 1] = cs * e[j + 1];
350
358
  if (wantu && j < m - 1) {
351
359
  for (let i = 0; i < m; i++) {
352
- t = cs * U.get(i, j) + sn * U.get(i, j + 1);
353
- U.set(i, j + 1, -sn * U.get(i, j) + cs * U.get(i, j + 1));
354
- U.set(i, j, t);
360
+ t = cs * U.get(j, i) + sn * U.get(j + 1, i);
361
+ U.set(j + 1, i, -sn * U.get(j, i) + cs * U.get(j + 1, i));
362
+ U.set(j, i, t);
355
363
  }
356
364
  }
357
365
  }
@@ -364,7 +372,7 @@ export default class SingularValueDecomposition {
364
372
  s[k] = s[k] < 0 ? -s[k] : 0;
365
373
  if (wantv) {
366
374
  for (let i = 0; i <= pp; i++) {
367
- V.set(i, k, -V.get(i, k));
375
+ V.set(k, i, -V.get(k, i));
368
376
  }
369
377
  }
370
378
  }
@@ -377,16 +385,16 @@ export default class SingularValueDecomposition {
377
385
  s[k + 1] = t;
378
386
  if (wantv && k < n - 1) {
379
387
  for (let i = 0; i < n; i++) {
380
- t = V.get(i, k + 1);
381
- V.set(i, k + 1, V.get(i, k));
382
- V.set(i, k, t);
388
+ t = V.get(k + 1, i);
389
+ V.set(k + 1, i, V.get(k, i));
390
+ V.set(k, i, t);
383
391
  }
384
392
  }
385
393
  if (wantu && k < m - 1) {
386
394
  for (let i = 0; i < m; i++) {
387
- t = U.get(i, k + 1);
388
- U.set(i, k + 1, U.get(i, k));
389
- U.set(i, k, t);
395
+ t = U.get(k + 1, i);
396
+ U.set(k + 1, i, U.get(k, i));
397
+ U.set(k, i, t);
390
398
  }
391
399
  }
392
400
  k++;
@@ -399,6 +407,13 @@ export default class SingularValueDecomposition {
399
407
  }
400
408
  }
401
409
 
410
+ // Restore the logical (row-major) layout of the singular vectors, which were
411
+ // accumulated in transposed storage for cache-sequential inner loops. V is
412
+ // always square and U is square whenever the input is, so this is done in
413
+ // place (no allocation) in the common case.
414
+ U = U.isSquare() ? transposeSquareInPlace(U) : U.transpose();
415
+ V = transposeSquareInPlace(V);
416
+
402
417
  if (swapped) {
403
418
  let tmp = V;
404
419
  V = U;
package/src/dc/util.js CHANGED
@@ -1,3 +1,24 @@
1
+ /**
2
+ * Transpose a square matrix in place, without allocating a copy.
3
+ * Used to restore the logical layout of decomposition outputs that were
4
+ * accumulated in transposed storage for cache-sequential inner loops.
5
+ * @param {import('../matrix').default} matrix - square matrix, mutated in place
6
+ * @returns {import('../matrix').default} the same matrix
7
+ */
8
+ export function transposeSquareInPlace(matrix) {
9
+ const data = matrix.data;
10
+ const n = matrix.rows;
11
+ for (let i = 0; i < n; i++) {
12
+ const rowI = data[i];
13
+ for (let j = i + 1; j < n; j++) {
14
+ const tmp = rowI[j];
15
+ rowI[j] = data[j][i];
16
+ data[j][i] = tmp;
17
+ }
18
+ }
19
+ return matrix;
20
+ }
21
+
1
22
  export function hypotenuse(a, b) {
2
23
  let r = 0;
3
24
  if (Math.abs(a) > Math.abs(b)) {
package/src/matrix.js CHANGED
@@ -874,6 +874,78 @@ export class AbstractMatrix {
874
874
  return result;
875
875
  }
876
876
 
877
+ gram() {
878
+ const rows = this.rows;
879
+ const n = this.columns;
880
+
881
+ // The Gram matrix `thisᵀ · this` is symmetric, so only its upper triangle is
882
+ // accumulated (then mirrored) and the transpose is never materialized.
883
+ // Row-streaming rank-1 updates read each row of `this` contiguously and skip
884
+ // zero entries, so the cost scales with the number of non-zeros: it is as
885
+ // fast as the dense version on dense matrices (the skip never fires) and far
886
+ // faster on sparse ones.
887
+ const gramData = new Float64Array(n * n);
888
+ for (let r = 0; r < rows; r++) {
889
+ for (let i = 0; i < n; i++) {
890
+ const value = this.get(r, i);
891
+ if (value === 0) continue;
892
+ const offset = i * n;
893
+ for (let j = i; j < n; j++) {
894
+ gramData[offset + j] += value * this.get(r, j);
895
+ }
896
+ }
897
+ }
898
+
899
+ const result = new Matrix(n, n);
900
+ for (let i = 0; i < n; i++) {
901
+ const offset = i * n;
902
+ for (let j = i; j < n; j++) {
903
+ const value = gramData[offset + j];
904
+ result.set(i, j, value);
905
+ result.set(j, i, value);
906
+ }
907
+ }
908
+ return result;
909
+ }
910
+
911
+ mmulByTranspose(scale) {
912
+ let m = this.rows;
913
+ let n = this.columns;
914
+
915
+ if (scale !== undefined && scale.length !== n) {
916
+ throw new RangeError('scale must have one value per column');
917
+ }
918
+
919
+ let result = new Matrix(m, m);
920
+
921
+ // result = this · diag(scale) · thisᵀ is symmetric, so only the upper
922
+ // triangle is computed and mirrored, and the transpose is never
923
+ // materialized. `scale` (one factor per column) is folded into one operand.
924
+ let rowj = new Float64Array(n);
925
+ for (let j = 0; j < m; j++) {
926
+ if (scale === undefined) {
927
+ for (let k = 0; k < n; k++) {
928
+ rowj[k] = this.get(j, k);
929
+ }
930
+ } else {
931
+ for (let k = 0; k < n; k++) {
932
+ rowj[k] = scale[k] * this.get(j, k);
933
+ }
934
+ }
935
+
936
+ for (let i = j; i < m; i++) {
937
+ let s = 0;
938
+ for (let k = 0; k < n; k++) {
939
+ s += this.get(i, k) * rowj[k];
940
+ }
941
+
942
+ result.set(i, j, s);
943
+ result.set(j, i, s);
944
+ }
945
+ }
946
+ return result;
947
+ }
948
+
877
949
  mpow(scalar) {
878
950
  if (!this.isSquare()) {
879
951
  throw new RangeError('Matrix must be square');