ml-matrix
Version:
Matrix manipulation and computation library
249 lines (206 loc) • 6 kB
JavaScript
/**
* @typedef {0 | 1 | number | boolean} Mask
*/
import Matrix, { AbstractMatrix } from './matrix';
export class SymmetricMatrix extends AbstractMatrix {
/** @type {Matrix} */
#matrix;
get size() {
return this.#matrix.size;
}
get rows() {
return this.#matrix.rows;
}
get columns() {
return this.#matrix.columns;
}
get diagonalSize() {
return this.rows;
}
/**
* not the same as matrix.isSymmetric()
* Here is to check if it's instanceof SymmetricMatrix without bundling issues
*
* @param value
* @returns {boolean}
*/
static isSymmetricMatrix(value) {
return Matrix.isMatrix(value) && value.klassType === 'SymmetricMatrix';
}
/**
* @param diagonalSize
* @return {SymmetricMatrix}
*/
static zeros(diagonalSize) {
return new this(diagonalSize);
}
/**
* @param diagonalSize
* @return {SymmetricMatrix}
*/
static ones(diagonalSize) {
return new this(diagonalSize).fill(1);
}
/**
* @param {number | AbstractMatrix | ArrayLike<ArrayLike<number>>} diagonalSize
* @return {this}
*/
constructor(diagonalSize) {
super();
if (Matrix.isMatrix(diagonalSize)) {
if (!diagonalSize.isSymmetric()) {
throw new TypeError('not symmetric data');
}
this.#matrix = Matrix.copy(
diagonalSize,
new Matrix(diagonalSize.rows, diagonalSize.rows),
);
} else if (Number.isInteger(diagonalSize) && diagonalSize >= 0) {
this.#matrix = new Matrix(diagonalSize, diagonalSize);
} else {
this.#matrix = new Matrix(diagonalSize);
if (!this.isSymmetric()) {
throw new TypeError('not symmetric data');
}
}
}
clone() {
const matrix = new SymmetricMatrix(this.diagonalSize);
for (const [row, col, value] of this.upperRightEntries()) {
matrix.set(row, col, value);
}
return matrix;
}
toMatrix() {
return new Matrix(this);
}
get(rowIndex, columnIndex) {
return this.#matrix.get(rowIndex, columnIndex);
}
set(rowIndex, columnIndex, value) {
// symmetric set
this.#matrix.set(rowIndex, columnIndex, value);
this.#matrix.set(columnIndex, rowIndex, value);
return this;
}
removeCross(index) {
// symmetric remove side
this.#matrix.removeRow(index);
this.#matrix.removeColumn(index);
return this;
}
addCross(index, array) {
if (array === undefined) {
array = index;
index = this.diagonalSize;
}
const row = array.slice();
row.splice(index, 1);
this.#matrix.addRow(index, row);
this.#matrix.addColumn(index, array);
return this;
}
/**
* @param {Mask[]} mask
*/
applyMask(mask) {
if (mask.length !== this.diagonalSize) {
throw new RangeError('Mask size do not match with matrix size');
}
// prepare sides to remove from matrix from mask
/** @type {number[]} */
const sidesToRemove = [];
for (const [index, passthroughs] of mask.entries()) {
if (passthroughs) continue;
sidesToRemove.push(index);
}
// to remove from highest to lowest for no mutation shifting
sidesToRemove.reverse();
// remove sides
for (const sideIndex of sidesToRemove) {
this.removeCross(sideIndex);
}
return this;
}
/**
* Compact format upper-right corner of matrix
* iterate from left to right, from top to bottom.
*
* ```
* A B C D
* A 1 2 3 4
* B 2 5 6 7
* C 3 6 8 9
* D 4 7 9 10
* ```
*
* will return compact 1D array `[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]`
*
* length is S(i=0, n=sideSize) => 10 for a 4 sideSized matrix
*
* @returns {number[]}
*/
toCompact() {
const { diagonalSize } = this;
/** @type {number[]} */
const compact = new Array((diagonalSize * (diagonalSize + 1)) / 2);
for (let col = 0, row = 0, index = 0; index < compact.length; index++) {
compact[index] = this.get(row, col);
if (++col >= diagonalSize) col = ++row;
}
return compact;
}
/**
* @param {number[]} compact
* @return {SymmetricMatrix}
*/
static fromCompact(compact) {
const compactSize = compact.length;
// compactSize = (sideSize * (sideSize + 1)) / 2
// 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
// sideSize = (Sqrt(8 × compactSize + 1) - 1) / 2
const diagonalSize = (Math.sqrt(8 * compactSize + 1) - 1) / 2;
if (!Number.isInteger(diagonalSize)) {
throw new TypeError(
`This array is not a compact representation of a Symmetric Matrix, ${JSON.stringify(
compact,
)}`,
);
}
const matrix = new SymmetricMatrix(diagonalSize);
for (let col = 0, row = 0, index = 0; index < compactSize; index++) {
matrix.set(col, row, compact[index]);
if (++col >= diagonalSize) col = ++row;
}
return matrix;
}
/**
* half iterator upper-right-corner from left to right, from top to bottom
* yield [row, column, value]
*
* @returns {Generator<[number, number, number], void, void>}
*/
*upperRightEntries() {
for (let row = 0, col = 0; row < this.diagonalSize; void 0) {
const value = this.get(row, col);
yield [row, col, value];
// at the end of row, move cursor to next row at diagonal position
if (++col >= this.diagonalSize) col = ++row;
}
}
/**
* half iterator upper-right-corner from left to right, from top to bottom
* yield value
*
* @returns {Generator<[number, number, number], void, void>}
*/
*upperRightValues() {
for (let row = 0, col = 0; row < this.diagonalSize; void 0) {
const value = this.get(row, col);
yield value;
// at the end of row, move cursor to next row at diagonal position
if (++col >= this.diagonalSize) col = ++row;
}
}
}
SymmetricMatrix.prototype.klassType = 'SymmetricMatrix';