ml-matrix 6.10.8 → 6.11.1

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/src/matrix.js CHANGED
@@ -240,6 +240,16 @@ export class AbstractMatrix {
240
240
  return false;
241
241
  }
242
242
 
243
+ isDistance() {
244
+ if (!this.isSymmetric()) return false;
245
+
246
+ for (let i = 0; i < this.rows; i++) {
247
+ if (this.get(i, i) !== 0) return false;
248
+ }
249
+
250
+ return true;
251
+ }
252
+
243
253
  isEchelonForm() {
244
254
  let i = 0;
245
255
  let j = 0;
@@ -1314,13 +1324,21 @@ export class AbstractMatrix {
1314
1324
  }
1315
1325
 
1316
1326
  clone() {
1317
- let newMatrix = new Matrix(this.rows, this.columns);
1318
- for (let row = 0; row < this.rows; row++) {
1319
- for (let column = 0; column < this.columns; column++) {
1320
- newMatrix.set(row, column, this.get(row, column));
1321
- }
1327
+ return this.constructor.copy(this, new Matrix(this.rows, this.columns));
1328
+ }
1329
+
1330
+ /**
1331
+ * @template {AbstractMatrix} M
1332
+ * @param {AbstractMatrix} from
1333
+ * @param {M} to
1334
+ * @return {M}
1335
+ */
1336
+ static copy(from, to) {
1337
+ for (const [row, column, value] of from.entries()) {
1338
+ to.set(row, column, value);
1322
1339
  }
1323
- return newMatrix;
1340
+
1341
+ return to;
1324
1342
  }
1325
1343
 
1326
1344
  sum(by) {
@@ -1504,6 +1522,36 @@ export class AbstractMatrix {
1504
1522
  toString(options) {
1505
1523
  return inspectMatrixWithOptions(this, options);
1506
1524
  }
1525
+
1526
+ [Symbol.iterator]() {
1527
+ return this.entries();
1528
+ }
1529
+
1530
+ /**
1531
+ * iterator from left to right, from top to bottom
1532
+ * yield [row, column, value]
1533
+ * @returns {Generator<[number, number, number], void, *>}
1534
+ */
1535
+ *entries() {
1536
+ for (let row = 0; row < this.rows; row++) {
1537
+ for (let col = 0; col < this.columns; col++) {
1538
+ yield [row, col, this.get(row, col)];
1539
+ }
1540
+ }
1541
+ }
1542
+
1543
+ /**
1544
+ * iterator from left to right, from top to bottom
1545
+ * yield value
1546
+ * @returns {Generator<number, void, *>}
1547
+ */
1548
+ *values() {
1549
+ for (let row = 0; row < this.rows; row++) {
1550
+ for (let col = 0; col < this.columns; col++) {
1551
+ yield this.get(row, col);
1552
+ }
1553
+ }
1554
+ }
1507
1555
  }
1508
1556
 
1509
1557
  AbstractMatrix.prototype.klass = 'Matrix';
@@ -1533,21 +1581,38 @@ AbstractMatrix.prototype.tensorProduct =
1533
1581
  AbstractMatrix.prototype.kroneckerProduct;
1534
1582
 
1535
1583
  export default class Matrix extends AbstractMatrix {
1584
+ /**
1585
+ * @type {Float64Array[]}
1586
+ */
1587
+ data;
1588
+
1589
+ /**
1590
+ * Init an empty matrix
1591
+ * @param {number} nRows
1592
+ * @param {number} nColumns
1593
+ */
1594
+ #initData(nRows, nColumns) {
1595
+ this.data = [];
1596
+
1597
+ if (Number.isInteger(nColumns) && nColumns >= 0) {
1598
+ for (let i = 0; i < nRows; i++) {
1599
+ this.data.push(new Float64Array(nColumns));
1600
+ }
1601
+ } else {
1602
+ throw new TypeError('nColumns must be a positive integer');
1603
+ }
1604
+
1605
+ this.rows = nRows;
1606
+ this.columns = nColumns;
1607
+ }
1608
+
1536
1609
  constructor(nRows, nColumns) {
1537
1610
  super();
1538
1611
  if (Matrix.isMatrix(nRows)) {
1539
- // eslint-disable-next-line no-constructor-return
1540
- return nRows.clone();
1612
+ this.#initData(nRows.rows, nRows.columns);
1613
+ Matrix.copy(nRows, this);
1541
1614
  } else if (Number.isInteger(nRows) && nRows >= 0) {
1542
- // Create an empty matrix
1543
- this.data = [];
1544
- if (Number.isInteger(nColumns) && nColumns >= 0) {
1545
- for (let i = 0; i < nRows; i++) {
1546
- this.data.push(new Float64Array(nColumns));
1547
- }
1548
- } else {
1549
- throw new TypeError('nColumns must be a positive integer');
1550
- }
1615
+ this.#initData(nRows, nColumns);
1551
1616
  } else if (isAnyArray(nRows)) {
1552
1617
  // Copy the values from the 2D array
1553
1618
  const arrayData = nRows;
@@ -1559,6 +1624,7 @@ export default class Matrix extends AbstractMatrix {
1559
1624
  );
1560
1625
  }
1561
1626
  this.data = [];
1627
+
1562
1628
  for (let i = 0; i < nRows; i++) {
1563
1629
  if (arrayData[i].length !== nColumns) {
1564
1630
  throw new RangeError('Inconsistent array dimensions');
@@ -1568,13 +1634,14 @@ export default class Matrix extends AbstractMatrix {
1568
1634
  }
1569
1635
  this.data.push(Float64Array.from(arrayData[i]));
1570
1636
  }
1637
+
1638
+ this.rows = nRows;
1639
+ this.columns = nColumns;
1571
1640
  } else {
1572
1641
  throw new TypeError(
1573
1642
  'First argument must be a positive number or an array',
1574
1643
  );
1575
1644
  }
1576
- this.rows = nRows;
1577
- this.columns = nColumns;
1578
1645
  }
1579
1646
 
1580
1647
  set(rowIndex, columnIndex, value) {
package/src/stat.js CHANGED
@@ -157,7 +157,7 @@ export function getScaleByRow(matrix) {
157
157
  for (let i = 0; i < matrix.rows; i++) {
158
158
  let sum = 0;
159
159
  for (let j = 0; j < matrix.columns; j++) {
160
- sum += Math.pow(matrix.get(i, j), 2) / (matrix.columns - 1);
160
+ sum += matrix.get(i, j) ** 2 / (matrix.columns - 1);
161
161
  }
162
162
  scale.push(Math.sqrt(sum));
163
163
  }
@@ -177,7 +177,7 @@ export function getScaleByColumn(matrix) {
177
177
  for (let j = 0; j < matrix.columns; j++) {
178
178
  let sum = 0;
179
179
  for (let i = 0; i < matrix.rows; i++) {
180
- sum += Math.pow(matrix.get(i, j), 2) / (matrix.rows - 1);
180
+ sum += matrix.get(i, j) ** 2 / (matrix.rows - 1);
181
181
  }
182
182
  scale.push(Math.sqrt(sum));
183
183
  }
@@ -197,7 +197,7 @@ export function getScaleAll(matrix) {
197
197
  let sum = 0;
198
198
  for (let j = 0; j < matrix.columns; j++) {
199
199
  for (let i = 0; i < matrix.rows; i++) {
200
- sum += Math.pow(matrix.get(i, j), 2) / divider;
200
+ sum += matrix.get(i, j) ** 2 / divider;
201
201
  }
202
202
  }
203
203
  return Math.sqrt(sum);
@@ -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';