Files
gonum/mat64/eigen.go
kortschak b10f3a00f3 Change NewDense signature and behaviour
This is an API breaking change.

NewDense now panics if len(mat) != r*c, unless mat == nil. When mat is
nil a new, correctly sized slice is allocated.
2014-01-08 09:56:39 +10:30

825 lines
17 KiB
Go

// Copyright ©2013 The gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Based on the EigenvalueDecomposition class from Jama 1.0.3.
package mat64
import (
"math"
)
func symmetric(m *Dense) bool {
n, _ := m.Dims()
for i := 0; i < n; i++ {
for j := 0; j < i; j++ {
if m.At(i, j) != m.At(j, i) {
return false
}
}
}
return true
}
type EigenFactors struct {
V *Dense
d, e []float64
}
// Eigen returns the Eigenvalues and eigenvectors of a square real matrix.
// The matrix a is overwritten during the decomposition. If a is symmetric,
// then a = v*D*v' where the eigenvalue matrix D is diagonal and the
// eigenvector matrix v is orthogonal.
//
// 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.v equals v.D. The matrix v may be badly conditioned, or even
// singular, so the validity of the equation a = v*D*inverse(v) depends
// upon the 2-norm condition number of v.
func Eigen(a *Dense, epsilon float64) EigenFactors {
m, n := a.Dims()
if m != n {
panic(ErrSquare)
}
var v *Dense
d := make([]float64, n)
e := make([]float64, n)
if symmetric(a) {
// Tridiagonalize.
v = tred2(a, d, e)
// Diagonalize.
tql2(d, e, v, epsilon)
} else {
// Reduce to Hessenberg form.
var hess *Dense
hess, v = orthes(a)
// Reduce Hessenberg to real Schur form.
hqr2(d, e, hess, v, epsilon)
}
return EigenFactors{v, d, e}
}
// Symmetric Householder reduction to tridiagonal form.
//
// 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.
func tred2(a *Dense, d, e []float64) (v *Dense) {
n := len(d)
v = a
for j := 0; j < n; j++ {
d[j] = v.At(n-1, j)
}
// Householder reduction to tridiagonal form.
for i := n - 1; i > 0; i-- {
// Scale to avoid under/overflow.
var (
scale float64
h float64
)
for k := 0; k < i; k++ {
scale += math.Abs(d[k])
}
if scale == 0 {
e[i] = d[i-1]
for j := 0; j < i; j++ {
d[j] = v.At(i-1, j)
v.Set(i, j, 0)
v.Set(j, i, 0)
}
} else {
// Generate Householder vector.
for k := 0; k < i; k++ {
d[k] /= scale
h += d[k] * d[k]
}
f := d[i-1]
g := math.Sqrt(h)
if f > 0 {
g = -g
}
e[i] = scale * g
h -= f * g
d[i-1] = f - g
for j := 0; j < i; j++ {
e[j] = 0
}
// Apply similarity transformation to remaining columns.
for j := 0; j < i; j++ {
f = d[j]
v.Set(j, i, f)
g = e[j] + v.At(j, j)*f
for k := j + 1; k <= i-1; k++ {
g += v.At(k, j) * d[k]
e[k] += v.At(k, j) * f
}
e[j] = g
}
f = 0
for j := 0; j < i; j++ {
e[j] /= h
f += e[j] * d[j]
}
hh := f / (h + h)
for j := 0; j < i; j++ {
e[j] -= hh * d[j]
}
for j := 0; j < i; j++ {
f = d[j]
g = e[j]
for k := j; k <= i-1; k++ {
v.Set(k, j, v.At(k, j)-(f*e[k]+g*d[k]))
}
d[j] = v.At(i-1, j)
v.Set(i, j, 0)
}
}
d[i] = h
}
// Accumulate transformations.
for i := 0; i < n-1; i++ {
v.Set(n-1, i, v.At(i, i))
v.Set(i, i, 1)
h := d[i+1]
if h != 0 {
for k := 0; k <= i; k++ {
d[k] = v.At(k, i+1) / h
}
for j := 0; j <= i; j++ {
var g float64
for k := 0; k <= i; k++ {
g += v.At(k, i+1) * v.At(k, j)
}
for k := 0; k <= i; k++ {
v.Set(k, j, v.At(k, j)-g*d[k])
}
}
}
for k := 0; k <= i; k++ {
v.Set(k, i+1, 0)
}
}
for j := 0; j < n; j++ {
d[j] = v.At(n-1, j)
v.Set(n-1, j, 0)
}
v.Set(n-1, n-1, 1)
e[0] = 0
return v
}
// Symmetric tridiagonal QL algorithm.
//
// 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.
func tql2(d, e []float64, v *Dense, epsilon float64) {
n := len(d)
for i := 1; i < n; i++ {
e[i-1] = e[i]
}
e[n-1] = 0
var (
f float64
tst1 float64
)
for l := 0; l < n; l++ {
// Find small subdiagonal element
tst1 = math.Max(tst1, math.Abs(d[l])+math.Abs(e[l]))
m := l
for m < n {
if math.Abs(e[m]) <= epsilon*tst1 {
break
}
m++
}
// If m == l, d[l] is an eigenvalue, otherwise, iterate.
if m > l {
for iter := 0; ; iter++ { // Could check iteration count here.
// Compute implicit shift
g := d[l]
p := (d[l+1] - g) / (2 * e[l])
r := math.Hypot(p, 1)
if p < 0 {
r = -r
}
d[l] = e[l] / (p + r)
d[l+1] = e[l] * (p + r)
dl1 := d[l+1]
h := g - d[l]
for i := l + 2; i < n; i++ {
d[i] -= h
}
f += h
// Implicit QL transformation.
p = d[m]
c := 1.
c2 := c
c3 := c
el1 := e[l+1]
var (
s float64
s2 float64
)
for i := m - 1; i >= l; i-- {
c3 = c2
c2 = c
s2 = s
g = c * e[i]
h = c * p
r = math.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])
// Accumulate transformation.
for k := 0; k < n; k++ {
h = v.At(k, i+1)
v.Set(k, i+1, s*v.At(k, i)+c*h)
v.Set(k, i, c*v.At(k, i)-s*h)
}
}
p = -s * s2 * c3 * el1 * e[l] / dl1
e[l] = s * p
d[l] = c * p
// Check for convergence.
if math.Abs(e[l]) <= epsilon*tst1 {
break
}
}
}
d[l] += f
e[l] = 0
}
// Sort eigenvalues and corresponding vectors.
for i := 0; i < n-1; i++ {
k := i
p := d[i]
for j := i + 1; j < n; j++ {
if d[j] < p {
k = j
p = d[j]
}
}
if k != i {
d[k] = d[i]
d[i] = p
for j := 0; j < n; j++ {
p = v.At(j, i)
v.Set(j, i, v.At(j, k))
v.Set(j, k, p)
}
}
}
}
// Nonsymmetric reduction to Hessenberg form.
//
// 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.
func orthes(a *Dense) (hess, v *Dense) {
n, _ := a.Dims()
hess = a
ort := make([]float64, n)
low := 0
high := n - 1
for m := low + 1; m <= high-1; m++ {
// Scale column.
var scale float64
for i := m; i <= high; i++ {
scale += math.Abs(hess.At(i, m-1))
}
if scale != 0 {
// Compute Householder transformation.
var h float64
for i := high; i >= m; i-- {
ort[i] = hess.At(i, m-1) / scale
h += ort[i] * ort[i]
}
g := math.Sqrt(h)
if ort[m] > 0 {
g = -g
}
h -= ort[m] * g
ort[m] -= g
// Apply Householder similarity transformation
// hess = (I-u*u'/h)*hess*(I-u*u')/h)
for j := m; j < n; j++ {
var f float64
for i := high; i >= m; i-- {
f += ort[i] * hess.At(i, j)
}
f /= h
for i := m; i <= high; i++ {
hess.Set(i, j, hess.At(i, j)-f*ort[i])
}
}
for i := 0; i <= high; i++ {
var f float64
for j := high; j >= m; j-- {
f += ort[j] * hess.At(i, j)
}
f /= h
for j := m; j <= high; j++ {
hess.Set(i, j, hess.At(i, j)-f*ort[j])
}
}
ort[m] *= scale
hess.Set(m, m-1, scale*g)
}
}
// Accumulate transformations (Algol's ortran).
v = NewDense(n, n, nil)
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
if i == j {
v.Set(i, j, 1)
} else {
v.Set(i, j, 0)
}
}
}
for m := high - 1; m >= low+1; m-- {
if hess.At(m, m-1) != 0 {
for i := m + 1; i <= high; i++ {
ort[i] = hess.At(i, m-1)
}
for j := m; j <= high; j++ {
var g float64
for i := m; i <= high; i++ {
g += ort[i] * v.At(i, j)
}
// Double division avoids possible underflow
g = (g / ort[m]) / hess.At(m, m-1)
for i := m; i <= high; i++ {
v.Set(i, j, v.At(i, j)+g*ort[i])
}
}
}
}
return hess, v
}
func cdiv(xr, xi, yr, yi float64) (float64, float64) {
r := complex(xr, xi) / complex(yr, yi)
return real(r), imag(r)
}
// Nonsymmetric reduction from Hessenberg to real Schur form.
//
// 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.
func hqr2(d, e []float64, hess, v *Dense, epsilon float64) {
// Initialize
nn := len(d)
n := nn - 1
low := 0
high := n
var exshift, p, q, r, s, z, t, w, x, y float64
// Store roots isolated by balanc and compute matrix norm
var norm float64
for i := 0; i < nn; i++ {
if i < low || i > high {
d[i] = hess.At(i, i)
e[i] = 0
}
for j := max(i-1, 0); j < nn; j++ {
norm += math.Abs(hess.At(i, j))
}
}
// Outer loop over eigenvalue index
for iter := 0; n >= low; {
// Look for single small sub-diagonal element
l := n
for l > low {
s = math.Abs(hess.At(l-1, l-1)) + math.Abs(hess.At(l, l))
if s == 0 {
s = norm
}
if math.Abs(hess.At(l, l-1)) < epsilon*s {
break
}
l--
}
// Check for convergence
if l == n {
// One root found
hess.Set(n, n, hess.At(n, n)+exshift)
d[n] = hess.At(n, n)
e[n] = 0
n--
iter = 0
} else if l == n-1 {
// Two roots found
w = hess.At(n, n-1) * hess.At(n-1, n)
p = (hess.At(n-1, n-1) - hess.At(n, n)) / 2.0
q = p*p + w
z = math.Sqrt(math.Abs(q))
hess.Set(n, n, hess.At(n, n)+exshift)
hess.Set(n-1, n-1, hess.At(n-1, n-1)+exshift)
x = hess.At(n, n)
// Real pair
if q >= 0 {
if p >= 0 {
z = p + z
} else {
z = p - z
}
d[n-1] = x + z
d[n] = d[n-1]
if z != 0 {
d[n] = x - w/z
}
e[n-1] = 0
e[n] = 0
x = hess.At(n, n-1)
s = math.Abs(x) + math.Abs(z)
p = x / s
q = z / s
r = math.Hypot(p, q)
p /= r
q /= r
// Row modification
for j := n - 1; j < nn; j++ {
z = hess.At(n-1, j)
hess.Set(n-1, j, q*z+p*hess.At(n, j))
hess.Set(n, j, q*hess.At(n, j)-p*z)
}
// Column modification
for i := 0; i <= n; i++ {
z = hess.At(i, n-1)
hess.Set(i, n-1, q*z+p*hess.At(i, n))
hess.Set(i, n, q*hess.At(i, n)-p*z)
}
// Accumulate transformations
for i := low; i <= high; i++ {
z = v.At(i, n-1)
v.Set(i, n-1, q*z+p*v.At(i, n))
v.Set(i, n, q*v.At(i, n)-p*z)
}
} else {
// Complex pair
d[n-1] = x + p
d[n] = x + p
e[n-1] = z
e[n] = -z
}
n -= 2
iter = 0
} else {
// No convergence yet
// Form shift
x = hess.At(n, n)
y = 0
w = 0
if l < n {
y = hess.At(n-1, n-1)
w = hess.At(n, n-1) * hess.At(n-1, n)
}
// Wilkinson's original ad hoc shift
if iter == 10 {
exshift += x
for i := low; i <= n; i++ {
hess.Set(i, i, hess.At(i, i)-x)
}
s = math.Abs(hess.At(n, n-1)) + math.Abs(hess.At(n-1, n-2))
x = 0.75 * s
y = x
w = -0.4375 * s * s
}
// MATLAB's new ad hoc shift
if iter == 30 {
s = (y - x) / 2
s = s*s + w
if s > 0 {
s = math.Sqrt(s)
if y < x {
s = -s
}
s = x - w/((y-x)/2+s)
for i := low; i <= n; i++ {
hess.Set(i, i, hess.At(i, i)-s)
}
exshift += s
x = 0.964
y = x
w = x
}
}
iter++ // Could check iteration count here.
// Look for two consecutive small sub-diagonal elements
m := n - 2
for m >= l {
z = hess.At(m, m)
r = x - z
s = y - z
p = (r*s-w)/hess.At(m+1, m) + hess.At(m, m+1)
q = hess.At(m+1, m+1) - z - r - s
r = hess.At(m+2, m+1)
s = math.Abs(p) + math.Abs(q) + math.Abs(r)
p /= s
q /= s
r /= s
if m == l {
break
}
if math.Abs(hess.At(m, m-1))*(math.Abs(q)+math.Abs(r)) <
epsilon*(math.Abs(p)*(math.Abs(hess.At(m-1, m-1))+math.Abs(z)+math.Abs(hess.At(m+1, m+1)))) {
break
}
m--
}
for i := m + 2; i <= n; i++ {
hess.Set(i, i-2, 0)
if i > m+2 {
hess.Set(i, i-3, 0)
}
}
// Double QR step involving rows l:n and columns m:n
for k := m; k <= n-1; k++ {
notlast := k != n-1
if k != m {
p = hess.At(k, k-1)
q = hess.At(k+1, k-1)
if notlast {
r = hess.At(k+2, k-1)
} else {
r = 0
}
x = math.Abs(p) + math.Abs(q) + math.Abs(r)
if x == 0 {
continue
}
p /= x
q /= x
r /= x
}
s = math.Sqrt(p*p + q*q + r*r)
if p < 0 {
s = -s
}
if s != 0 {
if k != m {
hess.Set(k, k-1, -s*x)
} else if l != m {
hess.Set(k, k-1, -hess.At(k, k-1))
}
p += s
x = p / s
y = q / s
z = r / s
q /= p
r /= p
// Row modification
for j := k; j < nn; j++ {
p = hess.At(k, j) + q*hess.At(k+1, j)
if notlast {
p += r * hess.At(k+2, j)
hess.Set(k+2, j, hess.At(k+2, j)-p*z)
}
hess.Set(k, j, hess.At(k, j)-p*x)
hess.Set(k+1, j, hess.At(k+1, j)-p*y)
}
// Column modification
for i := 0; i <= min(n, k+3); i++ {
p = x*hess.At(i, k) + y*hess.At(i, k+1)
if notlast {
p += z * hess.At(i, k+2)
hess.Set(i, k+2, hess.At(i, k+2)-p*r)
}
hess.Set(i, k, hess.At(i, k)-p)
hess.Set(i, k+1, hess.At(i, k+1)-p*q)
}
// Accumulate transformations
for i := low; i <= high; i++ {
p = x*v.At(i, k) + y*v.At(i, k+1)
if notlast {
p += z * v.At(i, k+2)
v.Set(i, k+2, v.At(i, k+2)-p*r)
}
v.Set(i, k, v.At(i, k)-p)
v.Set(i, k+1, v.At(i, k+1)-p*q)
}
}
}
}
}
// Backsubstitute to find vectors of upper triangular form
if norm == 0 {
return
}
for n = nn - 1; n >= 0; n-- {
p = d[n]
q = e[n]
if q == 0 {
// Real vector
l := n
hess.Set(n, n, 1)
for i := n - 1; i >= 0; i-- {
w = hess.At(i, i) - p
r = 0
for j := l; j <= n; j++ {
r += hess.At(i, j) * hess.At(j, n)
}
if e[i] < 0 {
z = w
s = r
} else {
l = i
if e[i] == 0 {
if w != 0 {
hess.Set(i, n, -r/w)
} else {
hess.Set(i, n, -r/(epsilon*norm))
}
} else {
// Solve real equations
x = hess.At(i, i+1)
y = hess.At(i+1, i)
q = (d[i]-p)*(d[i]-p) + e[i]*e[i]
t = (x*s - z*r) / q
hess.Set(i, n, t)
if math.Abs(x) > math.Abs(z) {
hess.Set(i+1, n, (-r-w*t)/x)
} else {
hess.Set(i+1, n, (-s-y*t)/z)
}
}
// Overflow control
t = math.Abs(hess.At(i, n))
if epsilon*t*t > 1 {
for j := i; j <= n; j++ {
hess.Set(j, n, hess.At(j, n)/t)
}
}
}
}
} else if q < 0 {
// Complex vector
l := n - 1
// Last vector component imaginary so matrix is triangular
if math.Abs(hess.At(n, n-1)) > math.Abs(hess.At(n-1, n)) {
hess.Set(n-1, n-1, q/hess.At(n, n-1))
hess.Set(n-1, n, -(hess.At(n, n)-p)/hess.At(n, n-1))
} else {
re, im := cdiv(0, -hess.At(n-1, n), hess.At(n-1, n-1)-p, q)
hess.Set(n-1, n-1, re)
hess.Set(n-1, n, im)
}
hess.Set(n, n-1, 0)
hess.Set(n, n, 1)
for i := n - 2; i >= 0; i-- {
var ra, sa, vr, vi float64
for j := l; j <= n; j++ {
ra += hess.At(i, j) * hess.At(j, n-1)
sa += hess.At(i, j) * hess.At(j, n)
}
w = hess.At(i, i) - p
if e[i] < 0 {
z = w
r = ra
s = sa
} else {
l = i
if e[i] == 0 {
re, im := cdiv(-ra, -sa, w, q)
hess.Set(i, n-1, re)
hess.Set(i, n, im)
} else {
// Solve complex equations
x = hess.At(i, i+1)
y = hess.At(i+1, i)
vr = (d[i]-p)*(d[i]-p) + e[i]*e[i] - q*q
vi = (d[i] - p) * 2 * q
if vr == 0 && vi == 0 {
vr = epsilon * norm * (math.Abs(w) + math.Abs(q) + math.Abs(x) + math.Abs(y) + math.Abs(z))
}
re, im := cdiv(x*r-z*ra+q*sa, x*s-z*sa-q*ra, vr, vi)
hess.Set(i, n-1, re)
hess.Set(i, n, im)
if math.Abs(x) > (math.Abs(z) + math.Abs(q)) {
hess.Set(i+1, n-1, (-ra-w*hess.At(i, n-1)+q*hess.At(i, n))/x)
hess.Set(i+1, n, (-sa-w*hess.At(i, n)-q*hess.At(i, n-1))/x)
} else {
re, im := cdiv(-r-y*hess.At(i, n-1), -s-y*hess.At(i, n), z, q)
hess.Set(i+1, n-1, re)
hess.Set(i+1, n, im)
}
}
// Overflow control
t = math.Max(math.Abs(hess.At(i, n-1)), math.Abs(hess.At(i, n)))
if (epsilon*t)*t > 1 {
for j := i; j <= n; j++ {
hess.Set(j, n-1, hess.At(j, n-1)/t)
hess.Set(j, n, hess.At(j, n)/t)
}
}
}
}
}
}
// Vectors of isolated roots
for i := 0; i < nn; i++ {
if i < low || i > high {
for j := i; j < nn; j++ {
v.Set(i, j, hess.At(i, j))
}
}
}
// Back transformation to get eigenvectors of original matrix
for j := nn - 1; j >= low; j-- {
for i := low; i <= high; i++ {
z = 0
for k := low; k <= min(j, high); k++ {
z += v.At(i, k) * hess.At(k, j)
}
v.Set(i, j, z)
}
}
}
// D returns the block diagonal eigenvalue matrix from the real and imaginary
// components d and e.
func (f EigenFactors) D() *Dense {
d, e := f.d, f.e
var n int
if n = len(d); n != len(e) {
panic(ErrSquare)
}
dm := NewDense(n, n, nil)
for i := 0; i < n; i++ {
dm.Set(i, i, d[i])
if e[i] > 0 {
dm.Set(i, i+1, e[i])
} else if e[i] < 0 {
dm.Set(i, i-1, e[i])
}
}
return dm
}