ml-matrix 6.12.2 → 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/matrix.d.ts +22 -2
- package/matrix.js +192 -73
- package/matrix.js.map +1 -1
- package/matrix.umd.js +1 -1
- package/matrix.umd.js.map +1 -1
- package/package.json +2 -1
- package/src/dc/evd.js +35 -24
- package/src/dc/svd.js +66 -51
- package/src/dc/util.js +21 -0
- package/src/matrix.js +72 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ml-matrix",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.13.0",
|
|
4
4
|
"description": "Matrix manipulation and computation library",
|
|
5
5
|
"main": "matrix.js",
|
|
6
6
|
"module": "src/index.js",
|
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
"jest-matcher-deep-close-to": "^3.0.2",
|
|
76
76
|
"mathjs": "^14.5.2",
|
|
77
77
|
"ml-dataset-iris": "^1.2.1",
|
|
78
|
+
"ml-xsadd": "^3.0.1",
|
|
78
79
|
"numeric": "^1.2.6",
|
|
79
80
|
"prettier": "^3.5.3",
|
|
80
81
|
"pretty-hrtime": "^1.0.3",
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
138
|
-
e[k] += V.get(
|
|
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(
|
|
169
|
+
V.set(j, k, V.get(j, k) - (f * e[k] + g * d[k]));
|
|
159
170
|
}
|
|
160
|
-
d[j] = V.get(i - 1
|
|
161
|
-
V.set(
|
|
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,
|
|
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(
|
|
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(
|
|
190
|
+
g += V.get(i + 1, k) * V.get(j, k);
|
|
180
191
|
}
|
|
181
192
|
for (k = 0; k <= i; k++) {
|
|
182
|
-
V.set(
|
|
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(
|
|
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
|
|
194
|
-
V.set(n - 1,
|
|
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(
|
|
268
|
-
V.set(
|
|
269
|
-
V.set(
|
|
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(
|
|
297
|
-
V.set(
|
|
298
|
-
V.set(
|
|
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
|
|
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
|
-
|
|
37
|
-
m =
|
|
38
|
-
n =
|
|
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
|
-
|
|
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
|
-
|
|
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],
|
|
76
|
+
s[k] = hypotenuse(s[k], at.get(k, i));
|
|
69
77
|
}
|
|
70
78
|
if (s[k] !== 0) {
|
|
71
|
-
if (
|
|
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
|
-
|
|
83
|
+
at.set(k, i, at.get(k, i) / s[k]);
|
|
76
84
|
}
|
|
77
|
-
|
|
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 +=
|
|
94
|
+
t += at.get(k, i) * at.get(j, i);
|
|
87
95
|
}
|
|
88
|
-
t = -t /
|
|
96
|
+
t = -t / at.get(k, k);
|
|
89
97
|
for (let i = k; i < m; i++) {
|
|
90
|
-
|
|
98
|
+
at.set(j, i, at.get(j, i) + t * at.get(k, i));
|
|
91
99
|
}
|
|
92
100
|
}
|
|
93
|
-
e[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(
|
|
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] *
|
|
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
|
-
|
|
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(
|
|
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] =
|
|
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] =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
185
|
+
U.set(k, i, 0);
|
|
178
186
|
}
|
|
179
187
|
} else {
|
|
180
188
|
for (let i = 0; i < m; i++) {
|
|
181
|
-
U.set(
|
|
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(
|
|
202
|
+
t += V.get(k, i) * V.get(j, i);
|
|
195
203
|
}
|
|
196
|
-
t = -t / V.get(k + 1
|
|
204
|
+
t = -t / V.get(k, k + 1);
|
|
197
205
|
for (let i = k + 1; i < n; i++) {
|
|
198
|
-
V.set(
|
|
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(
|
|
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(
|
|
269
|
-
V.set(
|
|
270
|
-
V.set(
|
|
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(
|
|
289
|
-
U.set(
|
|
290
|
-
U.set(
|
|
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(
|
|
337
|
-
V.set(
|
|
338
|
-
V.set(
|
|
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(
|
|
353
|
-
U.set(
|
|
354
|
-
U.set(
|
|
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(
|
|
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(
|
|
381
|
-
V.set(
|
|
382
|
-
V.set(
|
|
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(
|
|
388
|
-
U.set(
|
|
389
|
-
U.set(
|
|
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');
|