ml-matrix 6.10.7 → 6.11.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.
@@ -0,0 +1,248 @@
1
+ /**
2
+ * @typedef {0 | 1 | number | boolean} Mask
3
+ */
4
+ import Matrix, { AbstractMatrix } from './matrix';
5
+
6
+ export class SymmetricMatrix extends AbstractMatrix {
7
+ /** @type {Matrix} */
8
+ #matrix;
9
+
10
+ get size() {
11
+ return this.#matrix.size;
12
+ }
13
+
14
+ get rows() {
15
+ return this.#matrix.rows;
16
+ }
17
+
18
+ get columns() {
19
+ return this.#matrix.columns;
20
+ }
21
+
22
+ get diagonalSize() {
23
+ return this.rows;
24
+ }
25
+
26
+ /**
27
+ * not the same as matrix.isSymmetric()
28
+ * Here is to check if it's instanceof SymmetricMatrix without bundling issues
29
+ *
30
+ * @param value
31
+ * @returns {boolean}
32
+ */
33
+ static isSymmetricMatrix(value) {
34
+ return Matrix.isMatrix(value) && value.klassType === 'SymmetricMatrix';
35
+ }
36
+
37
+ /**
38
+ * @param diagonalSize
39
+ * @return {SymmetricMatrix}
40
+ */
41
+ static zeros(diagonalSize) {
42
+ return new this(diagonalSize);
43
+ }
44
+
45
+ /**
46
+ * @param diagonalSize
47
+ * @return {SymmetricMatrix}
48
+ */
49
+ static ones(diagonalSize) {
50
+ return new this(diagonalSize).fill(1);
51
+ }
52
+
53
+ /**
54
+ * @param {number | AbstractMatrix | ArrayLike<ArrayLike<number>>} diagonalSize
55
+ * @return {this}
56
+ */
57
+ constructor(diagonalSize) {
58
+ super();
59
+
60
+ if (Matrix.isMatrix(diagonalSize)) {
61
+ if (!diagonalSize.isSymmetric()) {
62
+ throw new TypeError('not symmetric data');
63
+ }
64
+
65
+ this.#matrix = Matrix.copy(
66
+ diagonalSize,
67
+ new Matrix(diagonalSize.rows, diagonalSize.rows),
68
+ );
69
+ } else if (Number.isInteger(diagonalSize) && diagonalSize >= 0) {
70
+ this.#matrix = new Matrix(diagonalSize, diagonalSize);
71
+ } else {
72
+ this.#matrix = new Matrix(diagonalSize);
73
+
74
+ if (!this.isSymmetric()) {
75
+ throw new TypeError('not symmetric data');
76
+ }
77
+ }
78
+ }
79
+
80
+ clone() {
81
+ const matrix = new SymmetricMatrix(this.diagonalSize);
82
+
83
+ for (const [row, col, value] of this.upperRightEntries()) {
84
+ matrix.set(row, col, value);
85
+ }
86
+
87
+ return matrix;
88
+ }
89
+
90
+ toMatrix() {
91
+ return new Matrix(this);
92
+ }
93
+
94
+ get(rowIndex, columnIndex) {
95
+ return this.#matrix.get(rowIndex, columnIndex);
96
+ }
97
+ set(rowIndex, columnIndex, value) {
98
+ // symmetric set
99
+ this.#matrix.set(rowIndex, columnIndex, value);
100
+ this.#matrix.set(columnIndex, rowIndex, value);
101
+
102
+ return this;
103
+ }
104
+
105
+ removeCross(index) {
106
+ // symmetric remove side
107
+ this.#matrix.removeRow(index);
108
+ this.#matrix.removeColumn(index);
109
+
110
+ return this;
111
+ }
112
+
113
+ addCross(index, array) {
114
+ if (array === undefined) {
115
+ array = index;
116
+ index = this.diagonalSize;
117
+ }
118
+
119
+ const row = array.slice();
120
+ row.splice(index, 1);
121
+
122
+ this.#matrix.addRow(index, row);
123
+ this.#matrix.addColumn(index, array);
124
+
125
+ return this;
126
+ }
127
+
128
+ /**
129
+ * @param {Mask[]} mask
130
+ */
131
+ applyMask(mask) {
132
+ if (mask.length !== this.diagonalSize) {
133
+ throw new RangeError('Mask size do not match with matrix size');
134
+ }
135
+
136
+ // prepare sides to remove from matrix from mask
137
+ /** @type {number[]} */
138
+ const sidesToRemove = [];
139
+ for (const [index, passthroughs] of mask.entries()) {
140
+ if (passthroughs) continue;
141
+ sidesToRemove.push(index);
142
+ }
143
+ // to remove from highest to lowest for no mutation shifting
144
+ sidesToRemove.reverse();
145
+
146
+ // remove sides
147
+ for (const sideIndex of sidesToRemove) {
148
+ this.removeCross(sideIndex);
149
+ }
150
+
151
+ return this;
152
+ }
153
+
154
+ /**
155
+ * Compact format upper-right corner of matrix
156
+ * iterate from left to right, from top to bottom.
157
+ *
158
+ * ```
159
+ * A B C D
160
+ * A 1 2 3 4
161
+ * B 2 5 6 7
162
+ * C 3 6 8 9
163
+ * D 4 7 9 10
164
+ * ```
165
+ *
166
+ * will return compact 1D array `[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]`
167
+ *
168
+ * length is S(i=0, n=sideSize) => 10 for a 4 sideSized matrix
169
+ *
170
+ * @returns {number[]}
171
+ */
172
+ toCompact() {
173
+ const { diagonalSize } = this;
174
+
175
+ /** @type {number[]} */
176
+ const compact = new Array((diagonalSize * (diagonalSize + 1)) / 2);
177
+ for (let col = 0, row = 0, index = 0; index < compact.length; index++) {
178
+ compact[index] = this.get(row, col);
179
+
180
+ if (++col >= diagonalSize) col = ++row;
181
+ }
182
+
183
+ return compact;
184
+ }
185
+
186
+ /**
187
+ * @param {number[]} compact
188
+ * @return {SymmetricMatrix}
189
+ */
190
+ static fromCompact(compact) {
191
+ const compactSize = compact.length;
192
+ // compactSize = (sideSize * (sideSize + 1)) / 2
193
+ // https://mathsolver.microsoft.com/fr/solve-problem/y%20%3D%20%20x%20%60cdot%20%20%20%60frac%7B%20%20%60left(%20x%2B1%20%20%60right)%20%20%20%20%7D%7B%202%20%20%7D
194
+ // sideSize = (Sqrt(8 × compactSize + 1) - 1) / 2
195
+ const diagonalSize = (Math.sqrt(8 * compactSize + 1) - 1) / 2;
196
+
197
+ if (!Number.isInteger(diagonalSize)) {
198
+ throw new TypeError(
199
+ `This array is not a compact representation of a Symmetric Matrix, ${JSON.stringify(
200
+ compact,
201
+ )}`,
202
+ );
203
+ }
204
+
205
+ const matrix = new SymmetricMatrix(diagonalSize);
206
+ for (let col = 0, row = 0, index = 0; index < compactSize; index++) {
207
+ matrix.set(col, row, compact[index]);
208
+ if (++col >= diagonalSize) col = ++row;
209
+ }
210
+
211
+ return matrix;
212
+ }
213
+
214
+ /**
215
+ * half iterator upper-right-corner from left to right, from top to bottom
216
+ * yield [row, column, value]
217
+ *
218
+ * @returns {Generator<[number, number, number], void, *>}
219
+ */
220
+ *upperRightEntries() {
221
+ for (let row = 0, col = 0; row < this.diagonalSize; void 0) {
222
+ const value = this.get(row, col);
223
+
224
+ yield [row, col, value];
225
+
226
+ // at the end of row, move cursor to next row at diagonal position
227
+ if (++col >= this.diagonalSize) col = ++row;
228
+ }
229
+ }
230
+
231
+ /**
232
+ * half iterator upper-right-corner from left to right, from top to bottom
233
+ * yield value
234
+ *
235
+ * @returns {Generator<[number, number, number], void, *>}
236
+ */
237
+ *upperRightValues() {
238
+ for (let row = 0, col = 0; row < this.diagonalSize; void 0) {
239
+ const value = this.get(row, col);
240
+
241
+ yield value;
242
+
243
+ // at the end of row, move cursor to next row at diagonal position
244
+ if (++col >= this.diagonalSize) col = ++row;
245
+ }
246
+ }
247
+ }
248
+ SymmetricMatrix.prototype.klassType = 'SymmetricMatrix';