mathjs
Version:
Math.js is an extensive math library for JavaScript and Node.js. It features a flexible expression parser with support for symbolic computation, comes with a large set of built-in functions and constants, and offers an integrated solution to work with dif
374 lines (352 loc) • 11 kB
JavaScript
import { clone } from '../../utils/object'
import { factory } from '../../utils/factory'
import { format } from '../../utils/string'
const name = 'eigs'
const dependencies = ['config', 'typed', 'matrix', 'addScalar', 'equal', 'subtract', 'abs', 'atan', 'cos', 'sin', 'multiplyScalar', 'inv', 'bignumber', 'multiply', 'add']
export const createEigs = /* #__PURE__ */ factory(name, dependencies, ({ config, typed, matrix, addScalar, subtract, equal, abs, atan, cos, sin, multiplyScalar, inv, bignumber, multiply, add }) => {
/**
* Compute eigenvalue and eigenvector of a real symmetric matrix.
* Only applicable to two dimensional symmetric matrices. Uses Jacobi
* Algorithm. Matrix containing mixed type ('number', 'bignumber', 'fraction')
* of elements are not supported. Input matrix or 2D array should contain all elements
* of either 'number', 'bignumber' or 'fraction' type. For 'number' and 'fraction', the
* eigenvalues are of 'number' type. For 'bignumber' the eigenvalues are of ''bignumber' type.
* Eigenvectors are always of 'number' type.
*
* Syntax:
*
* math.eigs(x)
*
* Examples:
*
* const H = [[5, 2.3], [2.3, 1]]
* const ans = math.eigs(H) // returns {values: [E1,E2...sorted], vectors: [v1,v2.... corresponding vectors as columns]}
* const E = ans.values
* const U = ans.vectors
* math.multiply(H, math.column(U, 0)) // returns math.multiply(E[0], math.column(U, 0))
* const UTxHxU = math.multiply(math.transpose(U), H, U) // rotates H to the eigen-representation
* E[0] == UTxHxU[0][0] // returns true
* See also:
*
* inv
*
* @param {Array | Matrix} x Matrix to be diagonalized
* @return {{values: Array, vectors: Array} | {values: Matrix, vectors: Matrix}} Object containing eigenvalues (Array or Matrix) and eigenvectors (2D Array/Matrix with eigenvectors as columns).
*/
return typed('eigs', {
Array: function (x) {
// check array size
const mat = matrix(x)
const size = mat.size()
if (size.length !== 2 || size[0] !== size[1]) {
throw new RangeError('Matrix must be square ' +
'(size: ' + format(size) + ')')
}
// use dense 2D matrix implementation
const ans = checkAndSubmit(mat, size[0])
return { values: ans[0], vectors: ans[1] }
},
Matrix: function (x) {
// use dense 2D array implementation
// dense matrix
const size = x.size()
if (size.length !== 2 || size[0] !== size[1]) {
throw new RangeError('Matrix must be square ' +
'(size: ' + format(size) + ')')
}
const ans = checkAndSubmit(x, size[0])
return { values: matrix(ans[0]), vectors: matrix(ans[1]) }
}
})
// Is the matrix
// symmetric ?
function isSymmetric (x, n) {
for (let i = 0; i < n; i++) {
for (let j = i; j < n; j++) {
// not symmtric
if (!equal(x[i][j], x[j][i])) {
throw new TypeError('Input matrix is not symmetric')
}
}
}
}
// check input for possible problems
// and perform diagonalization efficiently for
// specific type of number
function checkAndSubmit (x, n) {
let type = x.datatype()
// type check
if (type === undefined) {
type = x.getDataType()
}
if (type !== 'number' && type !== 'BigNumber' && type !== 'Fraction') {
if (type === 'mixed') {
throw new TypeError('Mixed matrix element type is not supported')
} else {
throw new TypeError('Matrix element type not supported (' + type + ')')
}
} else {
isSymmetric(x.toArray(), n)
}
// perform efficient calculation for 'numbers'
if (type === 'number') {
return diag(x.toArray())
} else if (type === 'Fraction') {
const xArr = x.toArray()
// convert fraction to numbers
for (let i = 0; i < n; i++) {
for (let j = i; j < n; j++) {
xArr[i][j] = xArr[i][j].valueOf()
xArr[j][i] = xArr[i][j]
}
}
return diag(x.toArray())
} else if (type === 'BigNumber') {
return diagBig(x.toArray())
}
}
// diagonalization implementation for number (efficient)
function diag (x) {
const N = x.length
const e0 = Math.abs(config.epsilon / N)
let psi
let Sij = new Array(N)
// Sij is Identity Matrix
for (let i = 0; i < N; i++) {
Sij[i] = createArray(N, 0)
Sij[i][i] = 1.0
}
// initial error
let Vab = getAij(x)
while (Math.abs(Vab[1]) >= Math.abs(e0)) {
const i = Vab[0][0]
const j = Vab[0][1]
psi = getTheta(x[i][i], x[j][j], x[i][j])
x = x1(x, psi, i, j)
Sij = Sij1(Sij, psi, i, j)
Vab = getAij(x)
}
const Ei = createArray(N, 0) // eigenvalues
for (let i = 0; i < N; i++) {
Ei[i] = x[i][i]
}
return sorting(clone(Ei), clone(Sij))
}
// diagonalization implementation for bigNumber
function diagBig (x) {
const N = x.length
const e0 = abs(config.epsilon / N)
let psi
let Sij = new Array(N)
// Sij is Identity Matrix
for (let i = 0; i < N; i++) {
Sij[i] = createArray(N, 0)
Sij[i][i] = 1.0
}
// initial error
let Vab = getAijBig(x)
while (abs(Vab[1]) >= abs(e0)) {
const i = Vab[0][0]
const j = Vab[0][1]
psi = getThetaBig(x[i][i], x[j][j], x[i][j])
x = x1Big(x, psi, i, j)
Sij = Sij1Big(Sij, psi, i, j)
Vab = getAijBig(x)
}
const Ei = createArray(N, 0) // eigenvalues
for (let i = 0; i < N; i++) {
Ei[i] = x[i][i]
}
// return [clone(Ei), clone(Sij)]
return sorting(clone(Ei), clone(Sij))
}
// get angle
function getTheta (aii, ajj, aij) {
const denom = (ajj - aii)
if (Math.abs(denom) <= config.epsilon) {
return Math.PI / 4
} else {
return 0.5 * Math.atan(2 * aij / (ajj - aii))
}
}
// get angle
function getThetaBig (aii, ajj, aij) {
const denom = subtract(ajj, aii)
if (abs(denom) <= config.epsilon) {
return bignumber(-1).acos().div(4)
} else {
return multiplyScalar(0.5, atan(multiply(2, aij, inv(denom))))
}
}
// update eigvec
function Sij1 (Sij, theta, i, j) {
const N = Sij.length
const c = Math.cos(theta)
const s = Math.sin(theta)
const Ski = createArray(N, 0)
const Skj = createArray(N, 0)
for (let k = 0; k < N; k++) {
Ski[k] = c * Sij[k][i] - s * Sij[k][j]
Skj[k] = s * Sij[k][i] + c * Sij[k][j]
}
for (let k = 0; k < N; k++) {
Sij[k][i] = Ski[k]
Sij[k][j] = Skj[k]
}
return Sij
}
// update eigvec for overlap
function Sij1Big (Sij, theta, i, j) {
const N = Sij.length
const c = cos(theta)
const s = sin(theta)
const Ski = createArray(N, bignumber(0))
const Skj = createArray(N, bignumber(0))
for (let k = 0; k < N; k++) {
Ski[k] = subtract(multiplyScalar(c, Sij[k][i]), multiplyScalar(s, Sij[k][j]))
Skj[k] = addScalar(multiplyScalar(s, Sij[k][i]), multiplyScalar(c, Sij[k][j]))
}
for (let k = 0; k < N; k++) {
Sij[k][i] = Ski[k]
Sij[k][j] = Skj[k]
}
return Sij
}
// update matrix
function x1Big (Hij, theta, i, j) {
const N = Hij.length
const c = bignumber(cos(theta))
const s = bignumber(sin(theta))
const c2 = multiplyScalar(c, c)
const s2 = multiplyScalar(s, s)
const Aki = createArray(N, bignumber(0))
const Akj = createArray(N, bignumber(0))
// 2cs Hij
const csHij = multiply(bignumber(2), c, s, Hij[i][j])
// Aii
const Aii = addScalar(subtract(multiplyScalar(c2, Hij[i][i]), csHij), multiplyScalar(s2, Hij[j][j]))
const Ajj = add(multiplyScalar(s2, Hij[i][i]), csHij, multiplyScalar(c2, Hij[j][j]))
// 0 to i
for (let k = 0; k < N; k++) {
Aki[k] = subtract(multiplyScalar(c, Hij[i][k]), multiplyScalar(s, Hij[j][k]))
Akj[k] = addScalar(multiplyScalar(s, Hij[i][k]), multiplyScalar(c, Hij[j][k]))
}
// Modify Hij
Hij[i][i] = Aii
Hij[j][j] = Ajj
Hij[i][j] = bignumber(0)
Hij[j][i] = bignumber(0)
// 0 to i
for (let k = 0; k < N; k++) {
if (k !== i && k !== j) {
Hij[i][k] = Aki[k]
Hij[k][i] = Aki[k]
Hij[j][k] = Akj[k]
Hij[k][j] = Akj[k]
}
}
return Hij
}
// update matrix
function x1 (Hij, theta, i, j) {
const N = Hij.length
const c = Math.cos(theta)
const s = Math.sin(theta)
const c2 = c * c
const s2 = s * s
const Aki = createArray(N, 0)
const Akj = createArray(N, 0)
// Aii
const Aii = c2 * Hij[i][i] - 2 * c * s * Hij[i][j] + s2 * Hij[j][j]
const Ajj = s2 * Hij[i][i] + 2 * c * s * Hij[i][j] + c2 * Hij[j][j]
// 0 to i
for (let k = 0; k < N; k++) {
Aki[k] = c * Hij[i][k] - s * Hij[j][k]
Akj[k] = s * Hij[i][k] + c * Hij[j][k]
}
// Modify Hij
Hij[i][i] = Aii
Hij[j][j] = Ajj
Hij[i][j] = 0
Hij[j][i] = 0
// 0 to i
for (let k = 0; k < N; k++) {
if (k !== i && k !== j) {
Hij[i][k] = Aki[k]
Hij[k][i] = Aki[k]
Hij[j][k] = Akj[k]
Hij[k][j] = Akj[k]
}
}
return Hij
}
// get max off-diagonal value from Upper Diagonal
function getAij (Mij) {
const N = Mij.length
let maxMij = 0
let maxIJ = [0, 1]
for (let i = 0; i < N; i++) {
for (let j = i + 1; j < N; j++) {
if (Math.abs(maxMij) < Math.abs(Mij[i][j])) {
maxMij = Math.abs(Mij[i][j])
maxIJ = [i, j]
}
}
}
return [maxIJ, maxMij]
}
// get max off-diagonal value from Upper Diagonal
function getAijBig (Mij) {
const N = Mij.length
let maxMij = 0
let maxIJ = [0, 1]
for (let i = 0; i < N; i++) {
for (let j = i + 1; j < N; j++) {
if (abs(maxMij) < abs(Mij[i][j])) {
maxMij = abs(Mij[i][j])
maxIJ = [i, j]
}
}
}
return [maxIJ, maxMij]
}
// sort results
function sorting (E, S) {
const N = E.length
const Ef = Array(N)
const Sf = Array(N)
for (let k = 0; k < N; k++) {
Sf[k] = Array(N)
}
for (let i = 0; i < N; i++) {
let minID = 0
let minE = E[0]
for (let j = 0; j < E.length; j++) {
if (E[j] < minE) {
minID = j
minE = E[minID]
}
}
Ef[i] = E.splice(minID, 1)[0]
for (let k = 0; k < N; k++) {
Sf[k][i] = S[k][minID]
S[k].splice(minID, 1)
}
}
return [clone(Ef), clone(Sf)]
}
/**
* Create an array of a certain size and fill all items with an initial value
* @param {number} size
* @param {number} value
* @return {number[]}
*/
function createArray (size, value) {
// TODO: as soon as all browsers support Array.fill, use that instead (IE doesn't support it)
const array = new Array(size)
for (let i = 0; i < size; i++) {
array[i] = value
}
return array
}
})