@rayyamhk/matrix
Version:
A professional, comprehensive and high-performance library for you to manipulate matrices.
331 lines (249 loc) • 9.07 kB
JavaScript
"use strict";
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
/* eslint-disable no-param-reassign */
// reference: https://people.inf.ethz.ch/arbenz/ewp/Lnotes/chapter4.pdf
var Complex = require('@rayyamhk/complex');
var Matrix = require('../..');
var _require = require('../../Error'),
INVALID_SQUARE_MATRIX = _require.INVALID_SQUARE_MATRIX;
/**
* Calculates the eigenvalues of any square Matrix using QR Algorithm.<br><br>
*
* The eigenvalues can be either real number or complex number.
* Note that all eigenvalues are instance of Complex,
* for more details please visit [Complex.js]{@link https://rayyamhk.github.io/Complex.js}.<br><br>
*
* The eigenvalues are cached.
* @memberof Matrix
* @instance
* @returns {Complex[]} Array of eigenvalues
*/
function eigenvalues() {
if (!this.isSquare()) {
throw new Error(INVALID_SQUARE_MATRIX);
}
if (this._eigenvalues !== undefined) {
return this._eigenvalues;
}
var size = this.size()[0];
var values = [];
var digit = this._digit;
var EPSILON = 1 / (Math.pow(10, digit) * 2);
var clone = Matrix.clone(this)._matrix;
var isConvergent = true; // flag
var skip = false; // Transform matrix to Hessenberg matrix
HouseholderTransform(clone, digit);
for (var i = size - 1; i > 0; i--) {
var divergenceCount = 0;
var prev = void 0; // used to determine convergence
// if obtains complex eigenvalues pair in previous iteration, skip current round
if (skip) {
skip = false;
continue;
}
var shift = clone[size - 1][size - 1]; // eslint-disable-next-line no-constant-condition
while (true) {
if (!isConvergent) {
// if the current eigenvalue is not real
prev = size2Eigenvalues(clone[i - 1][i - 1], clone[i - 1][i], clone[i][i - 1], clone[i][i]).metric;
} else {
// if the current eigenvalue is real
prev = Math.abs(clone[i][i - 1]);
} // apply single shift
for (var j = 0; j < size; j++) {
clone[j][j] -= shift;
} // Apply QR Algorithm
HessenbergQR(clone, digit);
for (var _j = 0; _j < size; _j++) {
clone[_j][_j] += shift;
}
if (isConvergent && prev < Math.abs(clone[i][i - 1])) {
divergenceCount++;
} // if the current eigenvalue is real and the entry is almost ZERO => break;
if (isConvergent && Math.abs(clone[i][i - 1]) < EPSILON) {
values[i] = new Complex(clone[i][i]);
break;
} // if the current eigenvalues pair is complex, if the difference of the previous eiganvalues and the
// eigenvalues of submatrix is almost ZERO => break
var _size2Eigenvalues = size2Eigenvalues(clone[i - 1][i - 1], clone[i - 1][i], clone[i][i - 1], clone[i][i]),
metric = _size2Eigenvalues.metric,
eigen1 = _size2Eigenvalues.eigen1,
eigen2 = _size2Eigenvalues.eigen2;
if (!isConvergent && Math.abs(prev - metric) < EPSILON) {
isConvergent = true; // re-initialize
skip = true;
var re1 = eigen1.re,
im1 = eigen1.im;
var re2 = eigen2.re,
im2 = eigen2.im;
values[i] = new Complex(re1, im1);
values[i - 1] = new Complex(re2, im2);
break;
} // if the entry doesn't converge => complex eigenvalues pair
if (divergenceCount > 3) {
isConvergent = false;
}
}
}
if (!skip) {
values[0] = new Complex(clone[0][0]);
}
this._eigenvalues = values;
return values;
}
;
function HouseholderTransform(A, digit) {
var size = A.length;
var EPSILON = 1 / (Math.pow(10, digit) * 2);
for (var j = 0; j < size - 2; j++) {
var xNorm = 0;
var u = new Array(size - j - 1);
for (var i = j + 1; i < size; i++) {
var entry = A[i][j];
xNorm += Math.pow(entry, 2);
u[i - j - 1] = entry;
}
xNorm = Math.sqrt(xNorm);
if (Math.abs(xNorm) < EPSILON) {
continue;
}
if (u[0] >= 0) {
u[0] += xNorm;
} else {
u[0] -= xNorm;
} // Make 'u' unit vector
var uNorm = 0;
for (var _i = 0; _i < u.length; _i++) {
uNorm += Math.pow(u[_i], 2);
}
uNorm = Math.sqrt(uNorm);
for (var _i2 = 0; _i2 < u.length; _i2++) {
u[_i2] /= uNorm;
} // update the matrix, multiply P from left
for (var n = j; n < size; n++) {
// column
var v = new Array(size - j - 1);
for (var m = j + 1; m < size; m++) {
v[m - j - 1] = A[m][n];
}
var scaler = 0;
for (var _m = 0; _m < v.length; _m++) {
scaler += v[_m] * u[_m];
}
scaler *= 2;
for (var _m2 = j + 1; _m2 < size; _m2++) {
// row
if (n === j && _m2 !== j + 1) {
A[_m2][n] = 0;
} else {
A[_m2][n] = v[_m2 - j - 1] - scaler * u[_m2 - j - 1];
}
}
} // update the matrix, multiply P from right
for (var _m3 = 0; _m3 < size; _m3++) {
// row
var _v = new Array(size - j - 1);
for (var _n = j + 1; _n < size; _n++) {
_v[_n - j - 1] = A[_m3][_n];
}
var _scaler = 0;
for (var _n2 = 0; _n2 < _v.length; _n2++) {
_scaler += _v[_n2] * u[_n2];
}
_scaler *= 2;
for (var _n3 = j + 1; _n3 < size; _n3++) {
// column
A[_m3][_n3] = _v[_n3 - j - 1] - _scaler * u[_n3 - j - 1];
}
}
}
}
function HessenbergQR(H, digit) {
var size = H.length;
var EPSILON = 1 / (Math.pow(10, digit) * 2);
var sincos = new Array(size - 1);
for (var i = 0; i < size - 1; i++) {
var a = H[i][i];
var c = H[i + 1][i];
var norm = Math.sqrt(Math.pow(a, 2) + Math.pow(c, 2));
if (norm < EPSILON) {
continue;
}
var cos = a / norm;
var sin = c * -1 / norm;
sincos[i] = [sin, cos];
var row1 = new Array(size - i);
var row2 = new Array(size - i);
for (var j = i; j < size; j++) {
row1[j - i] = H[i][j];
row2[j - i] = H[i + 1][j];
}
for (var _j2 = i; _j2 < size; _j2++) {
H[i][_j2] = cos * row1[_j2 - i] + sin * -1 * row2[_j2 - i];
if (i === _j2) {
H[i + 1][_j2] = 0;
} else {
H[i + 1][_j2] = sin * row1[_j2 - i] + cos * row2[_j2 - i];
}
}
}
for (var _j3 = 0; _j3 < size - 1; _j3++) {
if (!sincos[_j3]) {
continue;
}
var _sincos$_j = _slicedToArray(sincos[_j3], 2),
_sin = _sincos$_j[0],
_cos = _sincos$_j[1];
var col1 = new Array(_j3 + 2);
var col2 = new Array(_j3 + 2);
for (var _i3 = 0; _i3 <= _j3 + 1; _i3++) {
col1[_i3] = H[_i3][_j3];
col2[_i3] = H[_i3][_j3 + 1];
}
for (var _i4 = 0; _i4 <= _j3 + 1; _i4++) {
H[_i4][_j3] = col1[_i4] * _cos - col2[_i4] * _sin;
H[_i4][_j3 + 1] = col1[_i4] * _sin + col2[_i4] * _cos;
}
}
} // find the eigenvalues of 2x2 matrix
function size2Eigenvalues(e11, e12, e21, e22) {
var b = (e11 + e22) * -1;
var c = e11 * e22 - e21 * e12;
var delta = Math.pow(b, 2) - 4 * c;
var re1;
var im1;
var re2;
var im2;
if (delta >= 0) {
im1 = 0;
im2 = 0;
if (b >= 0) {
re1 = (b * -1 - Math.sqrt(delta)) / 2;
} else {
re1 = (b * -1 + Math.sqrt(delta)) / 2;
}
re2 = c / re1;
} else {
re1 = -b / 2;
re2 = re1;
im1 = Math.sqrt(delta * -1) / 2;
im2 = im1 * -1;
}
return {
metric: Math.sqrt(Math.pow(re1, 2) + Math.pow(im1, 2)),
eigen1: {
re: re1,
im: im1
},
eigen2: {
re: re2,
im: im2
}
};
}
module.exports = eigenvalues;