UNPKG

@rayyamhk/matrix

Version:

A professional, comprehensive and high-performance library for you to manipulate matrices.

331 lines (249 loc) 9.07 kB
"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;