UNPKG

ts-scikit

Version:

A scientific toolkit written in Typescript

496 lines 17.7 kB
"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