@progress/kendo-charts
Version:
Kendo UI platform-independent Charts library
181 lines (162 loc) • 4.88 kB
JavaScript
// Based on the implementation from kendo-spreadsheet-common/src/calc.js
class Matrix {
constructor() {
this.height = 0;
this.width = 0;
this.data = [];
}
clone() {
const m = new Matrix();
m.height = this.height;
m.width = this.width;
m.data = this.data.map(row => row.slice());
return m;
}
get(row, col) {
const line = this.data[row];
const val = line ? line[col] : null;
return val;
}
set(row, col, data) {
let line = this.data[row];
if (line == null) {
line = this.data[row] = [];
}
line[col] = data;
if (row >= this.height) {
this.height = row + 1;
}
if (col >= this.width) {
this.width = col + 1;
}
}
each(f, includeEmpty) {
for (let row = 0; row < this.height; ++row) {
for (let col = 0; col < this.width; ++col) {
let val = this.get(row, col);
if (includeEmpty || val != null) {
val = f(val, row, col);
if (val !== undefined) {
return val;
}
}
}
}
}
map(f, includeEmpty) {
const m = new Matrix();
this.each(function(el, row, col) {
m.set(row, col, f(el, row, col));
}, includeEmpty);
return m;
}
transpose() {
const m = new Matrix();
this.each(function(el, row, col) {
m.set(col, row, el);
});
return m;
}
unit(n) {
this.width = this.height = n;
const a = this.data = new Array(n);
for (let i = n; --i >= 0;) {
const row = a[i] = new Array(n);
for (let j = n; --j >= 0;) {
row[j] = i === j ? 1 : 0;
}
}
return this;
}
multiply(b) {
const a = this;
const m = new Matrix();
for (let row = 0; row < a.height; ++row) {
for (let col = 0; col < b.width; ++col) {
let s = 0;
for (let i = 0; i < a.width; ++i) {
const va = a.get(row, i);
const vb = b.get(i, col);
if (typeof va === "number" && typeof vb === "number") {
s += va * vb;
}
}
m.set(row, col, s);
}
}
return m;
}
inverse() {
const n = this.width;
const m = this.augment(new Matrix().unit(n));
const a = m.data;
// Gaussian elimination
// https://en.wikipedia.org/wiki/Gaussian_elimination#Finding_the_inverse_of_a_matrix
// 1. Get zeros below main diagonal
for (let k = 0; k < n; ++k) {
const imax = argmax(k, n, function(i) { return a[i][k]; });
if (!a[imax][k]) {
return null; // singular matrix
}
if (k !== imax) {
let tmp = a[k];
a[k] = a[imax];
a[imax] = tmp;
}
for (let i = k + 1; i < n; ++i) {
for (let j = k + 1; j < 2 * n; ++j) {
a[i][j] -= a[k][j] * a[i][k] / a[k][k];
}
a[i][k] = 0;
}
}
// 2. Get 1-s on main diagonal, dividing by pivot
for (let i = 0; i < n; ++i) {
for (let f = a[i][i], j = 0; j < 2 * n; ++j) {
a[i][j] /= f;
}
}
// 3. Get zeros above main diagonal. Actually, we only care to compute the right side
// here (that will be the inverse), so in the inner loop below we go while j >= n,
// instead of j >= k.
for (let k = n; --k >= 0;) {
for (let i = k; --i >= 0;) {
if (a[i][k]) {
for (let j = 2 * n; --j >= n;) {
a[i][j] -= a[k][j] * a[i][k];
}
}
}
}
return m.slice(0, n, n, n);
}
augment(m) {
const ret = this.clone();
const n = ret.width;
m.each(function(val, row, col) {
ret.set(row, col + n, val);
});
return ret;
}
slice(row, col, height, width) {
const m = new Matrix();
for (let i = 0; i < height; ++i) {
for (let j = 0; j < width; ++j) {
m.set(i, j, this.get(row + i, col + j));
}
}
return m;
}
}
function argmax(start, end, f) {
let max = f(start), pos = start;
for (let i = start + 1; i < end; i++) {
const v = f(start);
if (v > max) {
max = v;
pos = start;
}
}
return pos;
}
export default Matrix;