ts-scikit
Version:
A scientific toolkit written in Typescript
496 lines • 17.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EigenSolver = void 0;
const utils_1 = require("../utils");
/**
* Special-purpose eigensolvers for digital signal processing.
* <p>
* Methods of this class solve small eigen-problems efficiently.
*/
class EigenSolver {
/**
* Iterative Jacobi solver. Slower, but more accurate.
* @private
*/
static _SolveSymmetric3x3Jacobi(a, v, d) {
// Copy matrix to local variables.
let a00 = a[0][0], a01 = a[0][1], a11 = a[1][1], a02 = a[0][2], a12 = a[1][2], a22 = a[2][2];
// Initial eigenvectors.
let v00 = 1.0, v01 = 0.0, v02 = 0.0, v10 = 0.0, v11 = 1.0, v12 = 0.0, v20 = 0.0, v21 = 0.0, v22 = 1.0;
// Tiny constant to avoid overflow of r * r (in computation of t) below.
const tiny = 0.1 * Math.sqrt(Number.EPSILON);
// Absolute values of off-diagonal elements.
let aa01 = Math.abs(a01), aa02 = Math.abs(a02), aa12 = Math.abs(a12);
// Apply Jacobi rotations until all off-diagonal elements are zero.
// Count rotations, just in case this does not converge.
for (let nrot = 0; aa01 + aa02 + aa12 > 0.0; ++nrot) {
utils_1.Check.state(nrot < 100, 'number of Jacobi rotations is less than 100');
let c, r, s, t, u, vpr, vqr, apr, aqr;
// If a01 is the largest off-diagonal element, ...
if (aa01 >= aa02 && aa01 >= aa12) {
u = a11 - a00;
if (Math.abs(a01) < tiny * Math.abs(u)) {
t = a01 / u;
}
else {
r = 0.5 * u / a01;
t = (r >= 0.0) ? 1.0 / (r + Math.sqrt(1.0 + r * r)) : 1.0 / (r - Math.sqrt(1.0 + r * r));
}
c = 1.0 / Math.sqrt(1.0 + t * t);
s = t * c;
u = s / (1.0 + c);
r = t * a01;
a00 -= r;
a11 += r;
a01 = 0.0;
apr = a02;
aqr = a12;
a02 = apr - s * (aqr + apr * u);
a12 = aqr + s * (apr - aqr * u);
vpr = v00;
vqr = v10;
v00 = vpr - s * (vqr + vpr * u);
v10 = vqr + s * (vpr - vqr * u);
vpr = v01;
vqr = v11;
v01 = vpr - s * (vqr + vpr * u);
v11 = vqr + s * (vpr - vqr * u);
vpr = v02;
vqr = v12;
v02 = vpr - s * (vqr + vpr * u);
v12 = vqr + s * (vpr - vqr * u);
}
// Else if a02 is the largest off-diagonal element, ...
else if (aa02 >= aa01 && a02 >= aa12) {
u = a22 - a00;
if (Math.abs(a02) < tiny * Math.abs(u)) {
t = a02 / u;
}
else {
r = 0.5 * u / a02;
t = (r >= 0.0)
? 1.0 / (r + Math.sqrt(1.0 + r * r))
: 1.0 / (r - Math.sqrt(1.0 + r * r));
}
c = 1.0 / Math.sqrt(1.0 + t * t);
s = t * c;
u = s / (1.0 + c);
r = t * a02;
a00 -= r;
a22 += r;
a02 = 0.0;
apr = a01;
aqr = a12;
a01 = apr - s * (aqr + apr * u);
a12 = aqr + s * (apr - aqr * u);
vpr = v00;
vqr = v20;
v00 = vpr - s * (vqr + vpr * u);
v20 = vqr + s * (vpr - vqr * u);
vpr = v01;
vqr = v21;
v01 = vpr - s * (vqr + vpr * u);
v21 = vqr + s * (vpr - vqr * u);
vpr = v02;
vqr = v22;
v02 = vpr - s * (vqr + vpr * u);
v22 = vqr + s * (vpr - vqr * u);
}
// Else if a12 is the largest off-diagonal element, ...
else {
u = a22 - a11;
if (Math.abs(a12) < tiny * Math.abs(u)) {
t = a12 / u;
}
else {
r = 0.5 * u / a12;
t = (r >= 0.0) ? 1.0 / (r + Math.sqrt(1.0 + r * r)) : 1.0 / (r - Math.sqrt(1.0 + r * r));
}
c = 1.0 / Math.sqrt(1.0 + t * t);
s = t * c;
u = s / (1.0 + c);
r = t * a12;
a11 -= r;
a22 += r;
a12 = 0.0;
apr = a01;
aqr = a02;
a01 = apr - s * (aqr + apr * u);
a02 = aqr + s * (apr - aqr * u);
vpr = v10;
vqr = v20;
v10 = vpr - s * (vqr + vpr * u);
v20 = vqr + s * (vpr - vqr * u);
vpr = v11;
vqr = v21;
v11 = vpr - s * (vqr + vpr * u);
v21 = vqr + s * (vpr - vqr * u);
vpr = v12;
vqr = v22;
v12 = vpr - s * (vqr + vpr * u);
v22 = vqr + s * (vpr - vqr * u);
}
aa01 = Math.abs(a01);
aa02 = Math.abs(a02);
aa12 = Math.abs(a12);
}
// Copy eigenvalues and eigenvectors to output arrays.
d[0] = a00;
d[1] = a11;
d[2] = a22;
v[0][0] = v00;
v[0][1] = v01;
v[0][2] = v02;
v[1][0] = v10;
v[1][1] = v11;
v[1][2] = v12;
v[2][0] = v20;
v[2][1] = v21;
v[2][2] = v22;
// Sort eigenvalues (and eigenvectors) in descending order.
EigenSolver._SortDescending3x3(v, d);
}
/**
* Implementation of Kopp's hybrid method for real symmetric 3x3 matrices.
* <p>
* Computes eigenvalues and eigenvectors using Cardano's analytical method
* for the eigenvalues and, typically, an analytical vector cross-product
* algorithm for the eigenvectors. When necessary for accuracy, this method
* uses a slower but more accurate QL algorithm.
* @private
*/
static _SolveSymmetric3x3Hybrid(a, v, d) {
EigenSolver._GetEigenvaluesSymmetric3x3(a, d);
const a00 = a[0][0];
const a01 = a[0][1], a11 = a[1][1];
const a02 = a[0][2], a12 = a[1][2];
const d0 = d[0], d1 = d[1], d2 = d[2];
const n0 = a00 * a00 + a01 * a01 + a02 * a02;
const n1 = a01 * a01 + a11 * a11 + a12 * a12;
let t = Math.abs(d0);
let u = Math.abs(d1);
if (u > t) {
t = u;
}
u = Math.abs(d2);
if (u > t) {
t = u;
}
if (t < 1.0) {
u = t;
}
else {
u = Math.sqrt(t);
}
const error = 256.0 * Number.EPSILON * (n0 + u) * (n1 + u);
let v10 = a01 * a12 - a02 * a11;
let v11 = a02 * a01 - a12 * a00;
let v12 = a01 * a01;
// Compute 1st eigenvector via v0 = (A - d0) * d1 x (A - d0) * e2
let v00 = v10 + a02 * d0;
let v01 = v11 + a12 * d0;
let v02 = (a00 - d0) * (a11 - d0) - v12;
let v0s = v00 * v00 + v01 * v01 + v02 * v02;
// If vectors are nearly linearly dependent, or if large cancellation
// may have occurred in the calcuation of A-d0, fall back to the QL
// algorithm. This case should be rare.
if (v0s <= error) {
EigenSolver._SolveSymmetric3x3QL(a, v, d);
}
else {
v0s = Math.sqrt(1.0 / v0s);
v00 *= v0s;
v01 *= v0s;
v02 *= v0s;
}
// Compute 2nd eigenvector via v1 = (A - d1) * e1 x (A - d1) * e2.
v10 = v10 + a02 * d1;
v11 = v11 + a12 * d1;
v12 = (a00 - d1) * (a11 - d1) - v12;
let v1s = v10 * v10 + v11 * v11 + v12 * v12;
// Same check above but now for 2nd eigenvector.
if (v1s <= error) {
EigenSolver._SolveSymmetric3x3QL(a, v, d);
return;
}
else {
v1s = Math.sqrt(1.0 / v1s);
v10 *= v1s;
v11 *= v1s;
v12 *= v1s;
}
// Compute 3rd eigenvector via v2 = v0 x v1;.
const v20 = v01 * v12 - v02 * v11;
const v21 = v02 * v10 - v00 * v12;
const v22 = v01 * v11 - v01 * v10;
// Return eigenvectors.
v[0][0] = v00;
v[0][1] = v01;
v[0][2] = v02;
v[1][0] = v10;
v[1][1] = v11;
v[1][2] = v12;
v[2][0] = v20;
v[2][1] = v21;
v[2][2] = v22;
}
/**
* Sorts eigenvalues d and eigenvectors v in descending order.
* @private
*/
static _SortDescending3x3(v, d) {
for (let i = 0; i < 3; ++i) {
for (let j = i; j > 0 && d[j - 1] < d[j]; --j) {
const dj = d[j];
d[j] = d[j - 1];
d[j - 1] = dj;
const vj = v[j];
v[j] = v[j - 1];
v[j - 1] = vj;
}
}
}
/**
* Computes eigenvalues of a symmetric 3x3 matrix using Cardano's analytical method.
* @private
*/
static _GetEigenvaluesSymmetric3x3(a, d) {
const a00 = a[0][0], a01 = a[0][1], a11 = a[1][1], a02 = a[0][2], a12 = a[1][2], a22 = a[2][2];
const de = a01 * a12;
const dd = a01 * a01;
const ee = a12 * a12;
const ff = a02 * a02;
const c2 = a00 + a11 + a22;
const c1 = (a00 * a11 + a00 * a22 + a11 * a22) - (dd + ee + ff);
const c0 = a22 * dd + a00 * ee + a11 * ff - a00 * a11 * a22 - 2.0 * a02 * de;
const p = c2 * c2 - 3.0 * c1;
const q = c2 * (p - 1.5 * c1) - 13.5 * c0; // 13.5 = 27 / 2
const t = 27.0 * (0.25 * c1 * c1 * (p - c1) + c0 * (q + 6.75 * c0)); // 6.75 = 27 / 4
const phi = (1.0 / 3.0) * Math.atan2(Math.sqrt(Math.abs(t)), q);
const sqrtp = Math.sqrt(Math.abs(p));
const c = sqrtp * Math.cos(phi);
const s = (1.0 / Math.sqrt(3)) * sqrtp * Math.sin(phi);
const dt = (1.0 / 3.0) * (c2 - c);
d[0] = dt + c;
d[1] = dt + s;
d[2] = dt - s;
}
/**
* Kopps's solver for eigenvalues and eigenvectors vial QL decomposition.
* @private
*/
static _SolveSymmetric3x3QL(a, v, d) {
// Reduce A to a tri-diagonal form.
const e = new Array(3);
EigenSolver._ReduceSymmetric3x3(a, v, d, e);
// Loop over off-diagonal elements e[0] and e[1].
for (let l = 0; l < 2; ++l) {
// While not converged and number of iterations not too large...
for (let niter = 0; niter <= 100; ++niter) {
let m;
for (m = l; m < 2; ++m) {
const g = Math.abs(d[m]) + Math.abs(d[m + 1]);
if (Math.abs(e[m]) + g === g) {
break;
}
}
if (m === l) {
break;
}
// Compute Householder transformation.
let g = (d[l + 1] - d[l]) / (e[l] + e[l]);
let r = Math.sqrt(g * g + 1.0);
if (g > 0.0) {
g = d[m] - d[l] + e[l] / (g + r);
}
else {
g = d[m] - d[l] + e[l] / (g - r);
}
let s = 1.0;
let c = 1.0;
let p = 0.0;
for (let i = m - 1; i >= l; --i) {
const f = s * e[i];
const b = c * e[i];
if (Math.abs(f) > Math.abs(g)) {
c = g / f;
r = Math.sqrt(c * c + 1.0);
e[i + 1] = f * r;
s = 1.0 / r;
c *= s;
}
else {
s = f / g;
r = Math.sqrt(s * s + 1.0);
e[i + 1] = g * r;
c = 1.0 / r;
s *= c;
}
g = d[i + 1] - p;
r = (d[i] - g) * s + 2.0 * c * b;
p = s * r;
d[i + 1] = g + p;
g = c * r - b;
for (let k = 0; k < 3; ++k) {
const t = v[i + 1][k];
v[i + 1][k] = s * v[i][k] + c * t;
v[i][k] = c * v[i][k] - s * t;
}
}
d[l] -= p;
e[l] = g;
e[m] = 0.0;
}
}
EigenSolver._SortDescending3x3(v, d);
}
/**
* Kopp's tri-diagonal reduction for real symmetric 3x3 matrices.
* Diagonal is { d[0], d[1], d[2] } and super-diagonal is { e[0], e[1] }.
* Householder transformations are stored in the matrix v.
* @private
*/
static _ReduceSymmetric3x3(a, v, d, e) {
const a00 = a[0][0], a01 = a[0][1], a11 = a[1][1], a02 = a[0][2], a12 = a[1][2], a22 = a[2][2];
let v11 = 1.0;
let v12 = 0.0;
let v21 = 0.0;
let v22 = 1.0;
const h = a01 * a01 + a02 * a02;
const g = (a01 > 0.0) ? -Math.sqrt(h) : Math.sqrt(h);
const e0 = g;
let f = g * a01;
const u1 = a01 - g;
const u2 = a02;
let omega = h - f;
let d0, d1, d2, e1, s, q1, q2;
if (omega > 0.0) {
omega = 1.0 / omega;
s = 0.0;
f = a11 * u1 + a12 * u2;
q1 = omega * f;
s += u1 * f;
f = a12 * u1 + a22 * u2;
q2 = omega * f;
s += u2 * f;
s *= 0.5 * omega * omega;
q1 -= s * u1;
q2 -= s * u2;
d0 = a00;
d1 = a11 - 2.0 * q1 * u1;
d2 = a22 - 2.0 * q2 * u2;
f = omega * u1;
v11 -= f * u1;
v12 -= f * u2;
f = omega * u2;
v21 -= f * u1;
v22 -= f * u2;
e1 = a12 - q1 * u2 - u1 * q2;
}
else {
d0 = a00;
d1 = a11;
d2 = a22;
e1 = a12;
}
d[0] = d0;
d[1] = d1;
d[2] = d2;
e[0] = e0;
e[1] = e1;
v[0][0] = 1.0;
v[0][1] = 0.0;
v[0][2] = 0.0;
v[1][0] = 0.0;
v[1][1] = v11;
v[1][2] = v12;
v[2][0] = 0.0;
v[2][1] = v21;
v[2][2] = v22;
}
/**
* Computes eigenvalues and eigenvectors for a symmetric 2x2 matrix A.
* If the eigenvectors are placed in columns in a matrix V, and the
* eigenvalues are placed in corresponding columns of a diagonal
* matrix D, then AV = VD.
* @param a the symmetric matrix A.
* @param v the array of eigenvectors v[0] and v[1].
* @param d the array of eigenvalues d[0] and d[1].
*/
static SolveSymmetric2x2(a, v, d) {
// Copy matrix to local variables.
let a00 = a[0][0];
const a01 = a[0][1];
let a11 = a[1][1];
// Initial eigenvectors.
let v00 = 1.0;
let v01 = 0.0;
let v10 = 0.0;
let v11 = 1.0;
// If off-diagonal element is non-zero, zero it with a Jacobi rotation.
if (a01 !== 0.0) {
const tiny = 0.1 * Math.sqrt(Number.EPSILON);
let c, r, s, t, u, vpr, vqr;
u = a11 - a00;
if (Math.abs(a01) < tiny * Math.abs(u)) {
t = a01 / u;
}
else {
r = 0.5 * u / a01;
t = (r >= 0.0)
? 1.0 / (r + Math.sqrt(1.0 + r * r))
: 1.0 / (r - Math.sqrt(1.0 + r * r));
}
c = 1.0 / Math.sqrt(1.0 + t * t);
s = t * c;
u = s / (1.0 + c);
r = t * a01;
a00 -= r;
a11 += r;
vpr = v00;
vqr = v10;
v00 = vpr - s * (vqr + vpr * u);
v10 = vqr + s * (vpr - vqr * u);
vpr = v01;
vqr = v11;
v01 = vpr - s * (vqr + vpr * u);
v11 = vqr + s * (vpr - vqr * u);
}
// Copy eigenvalues an eigenvectors to output arrays.
d[0] = a00;
d[1] = a11;
v[0][0] = v00;
v[0][1] = v01;
v[1][0] = v10;
v[1][1] = v11;
// Sort eigenvalues (and eigenvectors) in descending order.
if (d[0] < d[1]) {
const dt = d[1];
d[1] = d[0];
d[0] = dt;
const vt = v[1];
v[1] = v[0];
v[0] = vt;
}
}
/**
* Computes eigenvalues and eigenvectors for a symmetric 3x3 matrix A.
* If the eigenvectors are placed in columns in a matrix V, and the
* eigenvalues are placed in corresponding columns of a diagonal
* matrix D, then AV = VD.
* @param a the symmetric matrix A.
* @param v the array of eigenvectors v[0], v[1], and v[2].
* @param d the array of eigenvalues d[0], d[1] and d[2].
* @param useJacobi true, if using the slower (but more accurate) iterative Jacobi solver. Default is false.
*/
static SolveSymmetric3x3(a, v, d, useJacobi = false) {
if (useJacobi) {
EigenSolver._SolveSymmetric3x3Jacobi(a, v, d);
}
else {
EigenSolver._SolveSymmetric3x3Hybrid(a, v, d);
}
}
}
exports.EigenSolver = EigenSolver;
//# sourceMappingURL=eigen-solver.js.map