/*
* Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved.
*/
package matrix4cj
import std.math.*
/** Eigenvalues and eigenvectors of a real matrix.
<P>
If A is symmetric, then A = V*D*V' where the eigenvalue matrix D is
diagonal and the eigenvector matrix V is orthogonal.
I.e. A = V.times(D.times(V.transpose())) and
V.times(V.transpose()) equals the identity matrix.
<P>
If A is not symmetric, then the eigenvalue matrix D is block diagonal
with the real eigenvalues in 1-by-1 blocks and any complex eigenvalues,
lambda + i*mu, in 2-by-2 blocks, [lambda, mu; -mu, lambda]. The
columns of V represent the eigenvectors in the sense that A*V = V*D,
i.e. A.times(V) equals V.times(D). The matrix V may be badly
conditioned, or even singular, so the validity of the equation
A = V*D*inverse(V) depends upon V.cond().
**/
public class EigenvalueDecomposition {
/**Row and column dimension (square matrix)*/
private var n: Int64 = 0
/**Symmetry flag*/
private var issymmetric: Bool = false
/**Arrays for internal storage of eigenvalues*/
private var d: Array<Float64>
/**Array for internal storage of eigenvectors*/
private var e: Array<Float64>
/**Array for internal storage of nonsymmetric Hessenberg form*/
private var V: Matrix
/**Working storage for nonsymmetric algorithm*/
private var H: Array<Array<Float64>> = Array<Array<Float64>>()
private var ort: Array<Float64> = Array<Float64>(0, repeat: 0.0)
private func tred2(): Unit {
// This is derived from the Algol procedures tred2 by
// Bowdler, Martin, Reinsch, and Wilkinson, Handbook for
// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding
// Fortran subroutine in EISPACK.
for (j in 0..n) {
d[j] = V[n - 1, j]
}
for (i in (n - 1)..0 : -1) {
// Scale to avoid under/overflow.
var scale: Float64 = 0.0
var h: Float64 = 0.0
for (k in 0..i) {
scale = scale + abs(d[k])
}
if (scale == 0.0) {
e[i] = d[i - 1]
for (j in 0..i) {
d[j] = V[i - 1, j]
V[i, j] = 0.0
V[j, i] = 0.0
}
} else {
// Generate Householder vector.
for (k in 0..i) {
d[k] /= scale
h += d[k] * d[k]
}
var f: Float64 = d[i - 1]
var g: Float64 = sqrt(h)
if (f > 0.0) {
g = -g
}
e[i] = scale * g
h = h - f * g
d[i - 1] = f - g
for (j in 0..i) {
e[j] = 0.0
}
// Apply similarity transformation to remaining columns.
for (j in 0..i) {
f = d[j]
V[j, i] = f
g = e[j] + V[j, j] * f
do {
var k = j + 1
while (k <= i - 1) {
g += V[k, j] * d[k]
e[k] += V[k, j] * f
k++
}
} while (false)
e[j] = g
}
f = 0.0
for (j in 0..i) {
e[j] /= h
f += e[j] * d[j]
}
let hh: Float64 = f / (h + h)
for (j in 0..i) {
e[j] -= hh * d[j]
}
for (j in 0..i) {
f = d[j]
g = e[j]
for (k in j..(i)) {
V[k, j] -= (f * e[k] + g * d[k])
}
d[j] = V[i - 1, j]
V[i, j] = 0.0
}
}
d[i] = h
}
// Accumulate transformations.
for (i in 0..(n - 1)) {
V[n - 1, i] = V[i, i]
V[i, i] = 1.0
let h = d[i + 1]
if (h != 0.0) {
for (k in 0..(i + 1)) {
d[k] = V[k, i + 1] / h
}
for (j in 0..i + 1) {
var g = 0.0
for (k in 0..(i + 1)) {
g += V[k, i + 1] * V[k, j]
}
for (k in 0..(i + 1)) {
V[k, j] -= g * d[k]
}
}
}
for (k in 0..(i + 1)) {
V[k, i + 1] = 0.0
}
}
for (j in 0..n) {
d[j] = V[n - 1, j]
V[n - 1, j] = 0.0
}
V[n - 1, n - 1] = 1.0
e[0] = 0.0
}
// Symmetric tridiagonal QL algorithm.
private func tql2(): Unit {
// This is derived from the Algol procedures tql2, by
// Bowdler, Martin, Reinsch, and Wilkinson, Handbook for
// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding
// Fortran subroutine in EISPACK.
for (i in 1..n) {
e[i - 1] = e[i]
}
e[n - 1] = 0.0
var f: Float64 = 0.0
var tst1: Float64 = 0.0
let eps: Float64 = pow(2.0, -52.0)
for (l in 0..n) {
tst1 = max(tst1, abs(d[Int64(l)]) + abs(e[Int64(l)]))
var m = l
while (m < n) {
if (abs(e[m]) <= eps * tst1) {
break
}
m++
}
if (m > l) {
var iter: Int64 = 0
do {
iter = iter + 1
var g: Float64 = d[l]
var p: Float64 = (d[l + 1] - g) / (2.0 * e[l])
var r: Float64 = Maths.hypot(p, 1.0)
if (p < 0.0) {
r = -r
}
d[l] = e[l] / (p + r)
d[l + 1] = e[l] * (p + r)
let dl1: Float64 = d[l + 1]
var h: Float64 = g - d[l]
for (i in (l + 2)..n) {
d[i] -= h
}
f = f + h
p = d[m]
var c: Float64 = 1.0
var c2: Float64 = c
var c3: Float64 = c
let el1: Float64 = e[l + 1]
var s: Float64 = 0.0
var s2: Float64 = 0.0
for (i in (m - 1)..l - 1 : -1) {
c3 = c2
c2 = c
s2 = s
g = c * e[i]
h = c * p
r = Maths.hypot(p, e[i])
e[i + 1] = s * r
s = e[i] / r
c = p / r
p = c * d[i] - s * g
d[i + 1] = h + s * (c * g + s * d[i])
for (k in 0..n) {
h = V[k, i + 1]
V[k, i + 1] = s * V[k, i] + c * h
V[k, i] = c * V[k, i] - s * h
}
}
p = -s * s2 * c3 * el1 * e[l] / dl1
e[l] = s * p
d[l] = c * p
} while (abs(e[l]) > eps * tst1)
}
d[l] = d[l] + f
e[l] = 0.0
}
// Sort eigenvalues and corresponding vectors.
for (i in 0..(n - 1)) {
var k = i
var p = d[i]
for (j in (i + 1)..n) {
if (d[j] < p) {
k = j
p = d[j]
}
}
if (k != i) {
d[k] = d[i]
d[i] = p
for (j in 0..n) {
p = V[j, i]
V[j, i] = V[j, k]
V[j, k] = p
}
}
}
}
// Nonsymmetric reduction to Hessenberg form.
private func orthes(): Unit {
// This is derived from the Algol procedures orthes and ortran,
// by Martin and Wilkinson, Handbook for Auto. Comp.,
// Vol.ii-Linear Algebra, and the corresponding
// Fortran subroutines in EISPACK.
let low: Int64 = 0
let high: Int64 = n - 1
for (m in low + 1..high) {
var scale = 0.0
for (i in m..high + 1) {
scale = scale + abs(H[i][m - 1])
}
if (scale != 0.0) {
var h: Float64 = 0.0
for (i in high..=m : -1) {
ort[i] = H[i][m - 1] / scale
h += ort[i] * ort[i]
}
var g: Float64 = sqrt(h)
if (ort[m] > 0.0) {
g = -g
}
h = h - ort[m] * g
ort[m] = ort[m] - g
for (j in m..n) {
var f: Float64 = 0.0
for (i in high..=m : -1) {
f += ort[i] * H[i][j]
}
f = f / h
for (i in m..(high + 1)) {
H[i][j] -= f * ort[i]
}
}
for (i in 0..(high + 1)) {
var f: Float64 = 0.0
for (j in high..=m : -1) {
f += ort[j] * H[i][j]
}
f = f / h
for (j in m..(high + 1)) {
H[i][j] -= f * ort[j]
}
}
ort[m] = scale * ort[m]
H[m][m - 1] = scale * g
}
}
for (i in 0..n) {
for (j in 0..n) {
V[i, j] = (if (i == j) {
1.0
} else {
0.0
})
}
}
for (m in (high - 1)..=(low + 1) : -1) {
if (H[m][m - 1] != 0.0) {
for (i in (m + 1)..(high + 1)) {
ort[i] = H[i][m - 1]
}
for (j in m..(high + 1)) {
var g: Float64 = 0.0
for (i in m..(high + 1)) {
g += ort[i] * V[i, j]
}
g = (g / ort[m]) / H[m][m - 1]
for (i in m..(high + 1)) {
V[i, j] += g * ort[i]
}
}
}
}
}
private var cdivr: Float64 = 0.0
private var cdivi: Float64 = 0.0
// Complex scalar division.
private func cdiv(xr: Float64, xi: Float64, yr: Float64, yi: Float64): Unit {
let r: Float64
let d: Float64
if (abs(yr) > abs(yi)) {
r = yi / yr
d = yr + r * yi
cdivr = (xr + r * xi) / d
cdivi = (xi - r * xr) / d
} else {
r = yr / yi
d = yi + r * yr
cdivr = (r * xr + xi) / d
cdivi = (r * xi - xr) / d
}
}
// Nonsymmetric reduction from Hessenberg to real Schur form.
private func hqr2(): Unit {
// This is derived from the Algol procedure hqr2,
// by Martin and Wilkinson, Handbook for Auto. Comp.,
// Vol.ii-Linear Algebra, and the corresponding
// Fortran subroutine in EISPACK.
let nn: Int64 = this.n
var n: Int64 = nn - 1
let low: Int64 = 0
let high: Int64 = nn - 1
let eps: Float64 = pow(2.0, -52.0)
var exshift: Float64 = 0.0
var p: Float64 = 0.0
var q: Float64 = 0.0
var r: Float64 = 0.0
var s: Float64 = 0.0
var z: Float64 = 0.0
var t: Float64 = 0.0
var w: Float64 = 0.0
var x: Float64 = 0.0
var y: Float64 = 0.0
// Store roots isolated by balanc and compute matrix norm
var norm: Float64 = 0.0
for (i in 0..nn) {
if (i < low || i > high) {
d[i] = H[i][i]
e[i] = 0.0
}
for (j in max(i - 1, 0)..nn) {
norm = norm + abs(H[i][j])
}
}
// Outer loop over eigenvalue index
var iter = 0
while (n >= low) {
// Look for single small sub-diagonal element
var l = n
while (l > low) {
s = abs(H[l - 1][l - 1]) + abs(H[l][l])
if (s == 0.0) {
s = norm
}
if (abs(H[l][l - 1]) < eps * s) {
break
}
l--
}
// Check for convergence
// One root found
if (l == n) {
H[n][n] = H[n][n] + exshift
d[n] = H[n][n]
e[n] = 0.0
n--
iter = 0
// Two roots found
} else if (l == n - 1) {
w = H[n][n - 1] * H[n - 1][n]
p = (H[n - 1][n - 1] - H[n][n]) / 2.0
q = p * p + w
z = sqrt(abs(q))
H[n][n] = H[n][n] + exshift
H[n - 1][n - 1] = H[n - 1][n - 1] + exshift
x = H[n][n]
// Real pair
if (q >= 0.0) {
if (p >= 0.0) {
z = p + z
} else {
z = p - z
}
d[n - 1] = x + z
d[n] = d[n - 1]
if (z != 0.0) {
d[n] = x - w / z
}
e[n - 1] = 0.0
e[n] = 0.0
x = H[n][n - 1]
s = abs(x) + abs(z)
p = x / s
q = z / s
r = sqrt(p * p + q * q)
p = p / r
q = q / r
// Row modification
for (j in (n - 1)..nn) {
z = H[n - 1][j]
H[n - 1][j] = q * z + p * H[n][j]
H[n][j] = q * H[n][j] - p * z
}
// Column modification
for (i in 0..(n + 1)) {
z = H[i][n - 1]
H[i][n - 1] = q * z + p * H[i][n]
H[i][n] = q * H[i][n] - p * z
}
// Accumulate transformations
for (i in low..(high + 1)) {
z = V[i, n - 1]
V[i, n - 1] = q * z + p * V[i, n]
V[i, n] = q * V[i, n] - p * z
}
// Complex pair
} else {
d[n - 1] = x + p
d[n] = x + p
e[n - 1] = z
e[n] = -z
}
n = n - 2
iter = 0
// No convergence yet
} else {
x = H[n][n]
y = 0.0
w = 0.0
if (l < n) {
y = H[n - 1][n - 1]
w = H[n][n - 1] * H[n - 1][n]
}
// Wilkinson's original ad hoc shift
if (iter == 10) {
exshift += x
for (i in low..(n + 1)) {
H[i][i] -= x
}
s = abs(H[n][n - 1]) + abs(H[n - 1][n - 2])
y = 0.75 * s
x = y
w = -0.4375 * s * s
}
// MATLAB's new ad hoc shift
if (iter == 30) {
s = (y - x) / 2.0
s = s * s + w
if (s > 0.0) {
s = sqrt(s)
if (y < x) {
s = -s
}
s = x - w / ((y - x) / 2.0 + s)
for (i in low..(n + 1)) {
H[i][i] -= s
}
exshift += s
w = 0.964
y = w
x = y
}
}
iter = iter + 1 // (Could check iteration count here.)
var m = n - 2
// Look for two consecutive small sub-diagonal elements
while (m >= l) {
z = H[m][m]
r = x - z
s = y - z
p = (r * s - w) / H[m + 1][m] + H[m][m + 1]
q = H[m + 1][m + 1] - z - r - s
r = H[m + 2][m + 1]
s = abs(p) + abs(q) + abs(r)
p = p / s
q = q / s
r = r / s
if (m == l) {
break
}
if (abs(H[m][m - 1]) * (abs(q) + abs(r)) < eps * (abs(p) * (abs(H[m - 1][m - 1]) + abs(z) + abs(
H[m + 1][m + 1])))) {
break
}
m--
}
for (i in (m + 2)..(n + 1)) {
H[i][i - 2] = 0.0
if (i > m + 2) {
H[i][i - 3] = 0.0
}
}
// Double QR step involving rows l:n and columns m:n
for (k in m..n) {
let notlast = (k != n - 1)
if (k != m) {
p = H[k][k - 1]
q = H[k + 1][k - 1]
r = (if (notlast) {
H[k + 2][k - 1]
} else {
0.0
})
x = abs(p) + abs(q) + abs(r)
if (x == 0.0) {
continue
}
p = p / x
q = q / x
r = r / x
}
s = sqrt(p * p + q * q + r * r)
if (p < 0.0) {
s = -s
}
if (s != 0.0) {
if (k != m) {
H[k][k - 1] = -s * x
} else if (l != m) {
H[k][k - 1] = -H[k][k - 1]
}
p = p + s
x = p / s
y = q / s
z = r / s
q = q / p
r = r / p
// Row modification
for (j in k..nn) {
p = H[k][j] + q * H[k + 1][j]
if (notlast) {
p = p + r * H[k + 2][j]
H[k + 2][j] = H[k + 2][j] - p * z
}
H[k][j] = H[k][j] - p * x
H[k + 1][j] = H[k + 1][j] - p * y
}
// Column modification
for (i in 0..=min(n, k + 3)) {
p = x * H[i][k] + y * H[i][k + 1]
if (notlast) {
p = p + z * H[i][k + 2]
H[i][k + 2] = H[i][k + 2] - p * r
}
H[i][k] = H[i][k] - p
H[i][k + 1] = H[i][k + 1] - p * q
}
// Accumulate transformations
for (i in low..(high + 1)) {
p = x * V[i, k] + y * V[i, k + 1]
if (notlast) {
p = p + z * V[i, k + 2]
V[i, k + 2] = V[i, k + 2] - p * r
}
V[i, k] = V[i, k] - p
V[i, k + 1] = V[i, k + 1] - p * q
}
}
}
}
}
// Backsubstitute to find vectors of upper triangular form
if (norm == 0.0) {
return
}
for (n_t in nn - 1..=0 : -1) {
n = n_t
p = d[n]
q = e[n]
if (q == 0.0) {
var l: Int64 = n
H[n][n] = 1.0
for (i in (n - 1)..-1 : -1) {
w = H[i][i] - p
r = 0.0
for (j in l..(n + 1)) {
r = r + H[i][j] * H[j][n]
}
if (e[i] < 0.0) {
z = w
s = r
} else {
l = i
if (e[Int64(i)] == 0.0) {
if (w != 0.0) {
H[i][n] = -r / w
} else {
H[i][n] = -r / (eps * norm)
}
// Solve real equations
} else {
x = H[i][i + 1]
y = H[i + 1][i]
q = (d[i] - p) * (d[i] - p) + e[i] * e[i]
t = (x * s - z * r) / q
H[i][n] = t
if (abs(x) > abs(z)) {
H[i + 1][n] = (-r - w * t) / x
} else {
H[i + 1][n] = (-s - y * t) / z
}
}
// Overflow control
t = abs(H[i][n])
if ((eps * t) * t > 1.0) {
for (j in i..(n + 1)) {
H[j][n] = H[j][n] / t
}
}
}
}
// Complex vector
} else if (q < 0.0) {
var l: Int64 = n - 1
// Last vector component imaginary so matrix is triangular
if (abs(H[n][n - 1]) > abs(H[n - 1][n])) {
H[n - 1][n - 1] = q / H[n][n - 1]
H[n - 1][n] = -(H[n][n] - p) / H[n][n - 1]
} else {
cdiv(0.0, -H[n - 1][n], H[n - 1][n - 1] - p, q)
H[n - 1][n - 1] = cdivr
H[n - 1][n] = cdivi
}
H[n][n - 1] = 0.0
H[n][n] = 1.0
for (i in (n - 2)..=0 : -1) {
var ra: Float64 = 0.0
var sa: Float64 = 0.0
var vr: Float64 = 0.0
let vi: Float64
ra = 0.0
sa = 0.0
do {
var j = l
while (j <= n) {
ra = ra + H[i][j] * H[j][n - 1]
sa = sa + H[i][j] * H[j][n]
j++
}
} while (false)
w = H[i][i] - p
if (e[i] < 0.0) {
z = w
r = ra
s = sa
} else {
l = i
if (e[i] == 0.0) {
cdiv(-ra, -sa, w, q)
H[i][n - 1] = cdivr
H[i][n] = cdivi
} else {
// Solve complex equations
x = H[i][i + 1]
y = H[i + 1][i]
vr = (d[i] - p) * (d[i] - p) + e[i] * e[i] - q * q
vi = (d[i] - p) * 2.0 * q
if (vr == 0.0 && vi == 0.0) {
vr = eps * norm * (abs(w) + abs(q) + abs(x) + abs(y) + abs(z))
}
cdiv(x * r - z * ra + q * sa, x * s - z * sa - q * ra, vr, vi)
H[i][n - 1] = cdivr
H[i][n] = cdivi
if (abs(x) > (abs(z) + abs(q))) {
H[i + 1][n - 1] = (-ra - w * H[i][n - 1] + q * H[i][n]) / x
H[i + 1][n] = (-sa - w * H[i][n] - q * H[i][n - 1]) / x
} else {
cdiv(-r - y * H[i][n - 1], -s - y * H[i][n], z, q)
H[i + 1][n - 1] = cdivr
H[i + 1][n] = cdivi
}
}
// Overflow control
t = max(abs(H[i][n - 1]), abs(H[i][n]))
if ((eps * t) * t > 1.0) {
for (j in i..(n + 1)) {
H[j][n - 1] = H[j][n - 1] / t
H[j][n] = H[j][n] / t
}
}
}
}
}
}
// Vectors of isolated roots
for (i in 0..nn) {
if (i < low || i > high) {
for (j in i..nn) {
V[i, j] = H[i][j]
}
}
}
// Back transformation to get eigenvectors of original matrix
for (j in (nn - 1)..(low - 1) : -1) {
for (i in low..(high + 1)) {
z = 0.0
for (k in low..=min(j, high)) {
z = z + V[i, k] * H[k][j]
}
V[i, j] = z
}
}
}
/**
* Check for symmetry, then construct the eigenvalue decomposition
* Structure to access D and V.
* @param Arg Square matrix
*/
public init(Arg: Matrix) {
n = Arg.colNum
V = Matrix(n, n)
d = Array<Float64>(n) {_ => 0.0}
e = Array<Float64>(n) {_ => 0.0}
issymmetric = true
for (j in 0..n where issymmetric) {
for (i in 0..n where issymmetric) {
issymmetric = (Arg[i, j] == Arg[j, i])
}
}
if (issymmetric) {
for (i in 0..n) {
for (j in 0..n) {
V[i, j] = Arg[i, j]
}
}
// Tridiagonalize.
tred2()
// Diagonalize.
tql2()
} else {
let array_dim_generated_n = n
H = Array<Array<Float64>>(array_dim_generated_n) {_ => Array<Float64>(array_dim_generated_n) {_ => 0.0}}
ort = Array<Float64>(n) {_ => 0.0}
for (j in 0..n) {
for (i in 0..n) {
H[i][j] = Arg[i, j]
}
}
// Reduce to Hessenberg form.
orthes()
// Reduce Hessenberg to real Schur form.
hqr2()
}
}
/**
* Return the eigenvector matrix
* @return V
*/
public func getV(): Matrix {
return V
}
/**
* Return the real parts of the eigenvalues
* @return real(diag(D))
*/
public func getRealEigenvalues(): Array<Float64> {
return d
}
/**
* Return the imaginary parts of the eigenvalues
* @return imag(diag(D))
*/
public func getImagEigenvalues(): Array<Float64> {
return e
}
/**
* Return the block diagonal eigenvalue matrix D
* @return D
*/
public func getD(): Matrix {
let X = Matrix(n, n, value: 0.0)
for (i in 0..n){
X[i, i] = d[i]
if (e[i] > 0.0) {
X[i, i + 1] = e[i]
} else if (e[i] < 0.0) {
X[i, i - 1] = e[i]
}
}
return X
}
}