summaryrefslogtreecommitdiff
path: root/lib/matrix/eigenvalue_decomposition.rb
diff options
context:
space:
mode:
authorHiroshi SHIBATA <hsbt@ruby-lang.org>2021-05-26 15:36:16 +0900
committerHiroshi SHIBATA <hsbt@ruby-lang.org>2021-05-27 14:42:11 +0900
commit454a36794f83395d0827a9e2e85ac8f0d9e53e16 (patch)
tree8ab881ece59bba38ad83eaba6ddf077d0ad0d545 /lib/matrix/eigenvalue_decomposition.rb
parentc9178c11271ccd3410c53687dd9cb2508e180a98 (diff)
Promote matrix to the bundled gems
Notes
Notes: Merged: https://github.com/ruby/ruby/pull/4530
Diffstat (limited to 'lib/matrix/eigenvalue_decomposition.rb')
-rw-r--r--lib/matrix/eigenvalue_decomposition.rb882
1 files changed, 0 insertions, 882 deletions
diff --git a/lib/matrix/eigenvalue_decomposition.rb b/lib/matrix/eigenvalue_decomposition.rb
deleted file mode 100644
index bf6637635a2..00000000000
--- a/lib/matrix/eigenvalue_decomposition.rb
+++ /dev/null
@@ -1,882 +0,0 @@
-# frozen_string_literal: false
-class Matrix
- # Adapted from JAMA: http://math.nist.gov/javanumerics/jama/
-
- # Eigenvalues and eigenvectors of a real matrix.
- #
- # Computes the eigenvalues and eigenvectors of a matrix A.
- #
- # If A is diagonalizable, this provides matrices V and D
- # such that A = V*D*V.inv, where D is the diagonal matrix with entries
- # equal to the eigenvalues and V is formed by the eigenvectors.
- #
- # If A is symmetric, then V is orthogonal and thus A = V*D*V.t
-
- class EigenvalueDecomposition
-
- # Constructs the eigenvalue decomposition for a square matrix +A+
- #
- def initialize(a)
- # @d, @e: Arrays for internal storage of eigenvalues.
- # @v: Array for internal storage of eigenvectors.
- # @h: Array for internal storage of nonsymmetric Hessenberg form.
- raise TypeError, "Expected Matrix but got #{a.class}" unless a.is_a?(Matrix)
- @size = a.row_count
- @d = Array.new(@size, 0)
- @e = Array.new(@size, 0)
-
- if (@symmetric = a.symmetric?)
- @v = a.to_a
- tridiagonalize
- diagonalize
- else
- @v = Array.new(@size) { Array.new(@size, 0) }
- @h = a.to_a
- @ort = Array.new(@size, 0)
- reduce_to_hessenberg
- hessenberg_to_real_schur
- end
- end
-
- # Returns the eigenvector matrix +V+
- #
- def eigenvector_matrix
- Matrix.send(:new, build_eigenvectors.transpose)
- end
- alias_method :v, :eigenvector_matrix
-
- # Returns the inverse of the eigenvector matrix +V+
- #
- def eigenvector_matrix_inv
- r = Matrix.send(:new, build_eigenvectors)
- r = r.transpose.inverse unless @symmetric
- r
- end
- alias_method :v_inv, :eigenvector_matrix_inv
-
- # Returns the eigenvalues in an array
- #
- def eigenvalues
- values = @d.dup
- @e.each_with_index{|imag, i| values[i] = Complex(values[i], imag) unless imag == 0}
- values
- end
-
- # Returns an array of the eigenvectors
- #
- def eigenvectors
- build_eigenvectors.map{|ev| Vector.send(:new, ev)}
- end
-
- # Returns the block diagonal eigenvalue matrix +D+
- #
- def eigenvalue_matrix
- Matrix.diagonal(*eigenvalues)
- end
- alias_method :d, :eigenvalue_matrix
-
- # Returns [eigenvector_matrix, eigenvalue_matrix, eigenvector_matrix_inv]
- #
- def to_ary
- [v, d, v_inv]
- end
- alias_method :to_a, :to_ary
-
-
- private def build_eigenvectors
- # JAMA stores complex eigenvectors in a strange way
- # See http://web.archive.org/web/20111016032731/http://cio.nist.gov/esd/emaildir/lists/jama/msg01021.html
- @e.each_with_index.map do |imag, i|
- if imag == 0
- Array.new(@size){|j| @v[j][i]}
- elsif imag > 0
- Array.new(@size){|j| Complex(@v[j][i], @v[j][i+1])}
- else
- Array.new(@size){|j| Complex(@v[j][i-1], -@v[j][i])}
- end
- end
- end
-
- # Complex scalar division.
-
- private def cdiv(xr, xi, yr, yi)
- if (yr.abs > yi.abs)
- r = yi/yr
- d = yr + r*yi
- [(xr + r*xi)/d, (xi - r*xr)/d]
- else
- r = yr/yi
- d = yi + r*yr
- [(r*xr + xi)/d, (r*xi - xr)/d]
- end
- end
-
-
- # Symmetric Householder reduction to tridiagonal form.
-
- private def tridiagonalize
-
- # 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.
-
- @size.times do |j|
- @d[j] = @v[@size-1][j]
- end
-
- # Householder reduction to tridiagonal form.
-
- (@size-1).downto(0+1) do |i|
-
- # Scale to avoid under/overflow.
-
- scale = 0.0
- h = 0.0
- i.times do |k|
- scale = scale + @d[k].abs
- end
- if (scale == 0.0)
- @e[i] = @d[i-1]
- i.times do |j|
- @d[j] = @v[i-1][j]
- @v[i][j] = 0.0
- @v[j][i] = 0.0
- end
- else
-
- # Generate Householder vector.
-
- i.times do |k|
- @d[k] /= scale
- h += @d[k] * @d[k]
- end
- f = @d[i-1]
- g = Math.sqrt(h)
- if (f > 0)
- g = -g
- end
- @e[i] = scale * g
- h -= f * g
- @d[i-1] = f - g
- i.times do |j|
- @e[j] = 0.0
- end
-
- # Apply similarity transformation to remaining columns.
-
- i.times do |j|
- f = @d[j]
- @v[j][i] = f
- g = @e[j] + @v[j][j] * f
- (j+1).upto(i-1) do |k|
- g += @v[k][j] * @d[k]
- @e[k] += @v[k][j] * f
- end
- @e[j] = g
- end
- f = 0.0
- i.times do |j|
- @e[j] /= h
- f += @e[j] * @d[j]
- end
- hh = f / (h + h)
- i.times do |j|
- @e[j] -= hh * @d[j]
- end
- i.times do |j|
- f = @d[j]
- g = @e[j]
- j.upto(i-1) do |k|
- @v[k][j] -= (f * @e[k] + g * @d[k])
- end
- @d[j] = @v[i-1][j]
- @v[i][j] = 0.0
- end
- end
- @d[i] = h
- end
-
- # Accumulate transformations.
-
- 0.upto(@size-1-1) do |i|
- @v[@size-1][i] = @v[i][i]
- @v[i][i] = 1.0
- h = @d[i+1]
- if (h != 0.0)
- 0.upto(i) do |k|
- @d[k] = @v[k][i+1] / h
- end
- 0.upto(i) do |j|
- g = 0.0
- 0.upto(i) do |k|
- g += @v[k][i+1] * @v[k][j]
- end
- 0.upto(i) do |k|
- @v[k][j] -= g * @d[k]
- end
- end
- end
- 0.upto(i) do |k|
- @v[k][i+1] = 0.0
- end
- end
- @size.times do |j|
- @d[j] = @v[@size-1][j]
- @v[@size-1][j] = 0.0
- end
- @v[@size-1][@size-1] = 1.0
- @e[0] = 0.0
- end
-
-
- # Symmetric tridiagonal QL algorithm.
-
- private def diagonalize
- # 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.
-
- 1.upto(@size-1) do |i|
- @e[i-1] = @e[i]
- end
- @e[@size-1] = 0.0
-
- f = 0.0
- tst1 = 0.0
- eps = Float::EPSILON
- @size.times do |l|
-
- # Find small subdiagonal element
-
- tst1 = [tst1, @d[l].abs + @e[l].abs].max
- m = l
- while (m < @size) do
- if (@e[m].abs <= eps*tst1)
- break
- end
- m+=1
- end
-
- # If m == l, @d[l] is an eigenvalue,
- # otherwise, iterate.
-
- if (m > l)
- iter = 0
- begin
- iter = iter + 1 # (Could check iteration count here.)
-
- # Compute implicit shift
-
- g = @d[l]
- p = (@d[l+1] - g) / (2.0 * @e[l])
- r = Math.hypot(p, 1.0)
- if (p < 0)
- r = -r
- end
- @d[l] = @e[l] / (p + r)
- @d[l+1] = @e[l] * (p + r)
- dl1 = @d[l+1]
- h = g - @d[l]
- (l+2).upto(@size-1) do |i|
- @d[i] -= h
- end
- f += h
-
- # Implicit QL transformation.
-
- p = @d[m]
- c = 1.0
- c2 = c
- c3 = c
- el1 = @e[l+1]
- s = 0.0
- s2 = 0.0
- (m-1).downto(l) do |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.
-
- @size.times do |k|
- h = @v[k][i+1]
- @v[k][i+1] = s * @v[k][i] + c * h
- @v[k][i] = c * @v[k][i] - s * h
- end
- end
- p = -s * s2 * c3 * el1 * @e[l] / dl1
- @e[l] = s * p
- @d[l] = c * p
-
- # Check for convergence.
-
- end while (@e[l].abs > eps*tst1)
- end
- @d[l] = @d[l] + f
- @e[l] = 0.0
- end
-
- # Sort eigenvalues and corresponding vectors.
-
- 0.upto(@size-2) do |i|
- k = i
- p = @d[i]
- (i+1).upto(@size-1) do |j|
- if (@d[j] < p)
- k = j
- p = @d[j]
- end
- end
- if (k != i)
- @d[k] = @d[i]
- @d[i] = p
- @size.times do |j|
- p = @v[j][i]
- @v[j][i] = @v[j][k]
- @v[j][k] = p
- end
- end
- end
- end
-
- # Nonsymmetric reduction to Hessenberg form.
-
- private def reduce_to_hessenberg
- # 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.
-
- low = 0
- high = @size-1
-
- (low+1).upto(high-1) do |m|
-
- # Scale column.
-
- scale = 0.0
- m.upto(high) do |i|
- scale = scale + @h[i][m-1].abs
- end
- if (scale != 0.0)
-
- # Compute Householder transformation.
-
- h = 0.0
- high.downto(m) do |i|
- @ort[i] = @h[i][m-1]/scale
- h += @ort[i] * @ort[i]
- end
- g = Math.sqrt(h)
- if (@ort[m] > 0)
- g = -g
- end
- h -= @ort[m] * g
- @ort[m] = @ort[m] - g
-
- # Apply Householder similarity transformation
- # @h = (I-u*u'/h)*@h*(I-u*u')/h)
-
- m.upto(@size-1) do |j|
- f = 0.0
- high.downto(m) do |i|
- f += @ort[i]*@h[i][j]
- end
- f = f/h
- m.upto(high) do |i|
- @h[i][j] -= f*@ort[i]
- end
- end
-
- 0.upto(high) do |i|
- f = 0.0
- high.downto(m) do |j|
- f += @ort[j]*@h[i][j]
- end
- f = f/h
- m.upto(high) do |j|
- @h[i][j] -= f*@ort[j]
- end
- end
- @ort[m] = scale*@ort[m]
- @h[m][m-1] = scale*g
- end
- end
-
- # Accumulate transformations (Algol's ortran).
-
- @size.times do |i|
- @size.times do |j|
- @v[i][j] = (i == j ? 1.0 : 0.0)
- end
- end
-
- (high-1).downto(low+1) do |m|
- if (@h[m][m-1] != 0.0)
- (m+1).upto(high) do |i|
- @ort[i] = @h[i][m-1]
- end
- m.upto(high) do |j|
- g = 0.0
- m.upto(high) do |i|
- g += @ort[i] * @v[i][j]
- end
- # Double division avoids possible underflow
- g = (g / @ort[m]) / @h[m][m-1]
- m.upto(high) do |i|
- @v[i][j] += g * @ort[i]
- end
- end
- end
- end
- end
-
- # Nonsymmetric reduction from Hessenberg to real Schur form.
-
- private def hessenberg_to_real_schur
-
- # 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.
-
- # Initialize
-
- nn = @size
- n = nn-1
- low = 0
- high = nn-1
- eps = Float::EPSILON
- exshift = 0.0
- p = q = r = s = z = 0
-
- # Store roots isolated by balanc and compute matrix norm
-
- norm = 0.0
- nn.times do |i|
- if (i < low || i > high)
- @d[i] = @h[i][i]
- @e[i] = 0.0
- end
- ([i-1, 0].max).upto(nn-1) do |j|
- norm = norm + @h[i][j].abs
- end
- end
-
- # Outer loop over eigenvalue index
-
- iter = 0
- while (n >= low) do
-
- # Look for single small sub-diagonal element
-
- l = n
- while (l > low) do
- s = @h[l-1][l-1].abs + @h[l][l].abs
- if (s == 0.0)
- s = norm
- end
- if (@h[l][l-1].abs < eps * s)
- break
- end
- l-=1
- end
-
- # 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-=1
- iter = 0
-
- # Two roots found
-
- elsif (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 = Math.sqrt(q.abs)
- @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)
- if (p >= 0)
- z = p + z
- else
- z = p - z
- end
- @d[n-1] = x + z
- @d[n] = @d[n-1]
- if (z != 0.0)
- @d[n] = x - w / z
- end
- @e[n-1] = 0.0
- @e[n] = 0.0
- x = @h[n][n-1]
- s = x.abs + z.abs
- p = x / s
- q = z / s
- r = Math.sqrt(p * p+q * q)
- p /= r
- q /= r
-
- # Row modification
-
- (n-1).upto(nn-1) do |j|
- z = @h[n-1][j]
- @h[n-1][j] = q * z + p * @h[n][j]
- @h[n][j] = q * @h[n][j] - p * z
- end
-
- # Column modification
-
- 0.upto(n) do |i|
- z = @h[i][n-1]
- @h[i][n-1] = q * z + p * @h[i][n]
- @h[i][n] = q * @h[i][n] - p * z
- end
-
- # Accumulate transformations
-
- low.upto(high) do |i|
- z = @v[i][n-1]
- @v[i][n-1] = q * z + p * @v[i][n]
- @v[i][n] = q * @v[i][n] - p * z
- end
-
- # Complex pair
-
- else
- @d[n-1] = x + p
- @d[n] = x + p
- @e[n-1] = z
- @e[n] = -z
- end
- n -= 2
- iter = 0
-
- # No convergence yet
-
- else
-
- # Form shift
-
- 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]
- end
-
- # Wilkinson's original ad hoc shift
-
- if (iter == 10)
- exshift += x
- low.upto(n) do |i|
- @h[i][i] -= x
- end
- s = @h[n][n-1].abs + @h[n-1][n-2].abs
- x = y = 0.75 * s
- w = -0.4375 * s * s
- end
-
- # MATLAB's new ad hoc shift
-
- if (iter == 30)
- s = (y - x) / 2.0
- s *= s + w
- if (s > 0)
- s = Math.sqrt(s)
- if (y < x)
- s = -s
- end
- s = x - w / ((y - x) / 2.0 + s)
- low.upto(n) do |i|
- @h[i][i] -= s
- end
- exshift += s
- x = y = w = 0.964
- end
- end
-
- iter = iter + 1 # (Could check iteration count here.)
-
- # Look for two consecutive small sub-diagonal elements
-
- m = n-2
- while (m >= l) do
- 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 = p.abs + q.abs + r.abs
- p /= s
- q /= s
- r /= s
- if (m == l)
- break
- end
- if (@h[m][m-1].abs * (q.abs + r.abs) <
- eps * (p.abs * (@h[m-1][m-1].abs + z.abs +
- @h[m+1][m+1].abs)))
- break
- end
- m-=1
- end
-
- (m+2).upto(n) do |i|
- @h[i][i-2] = 0.0
- if (i > m+2)
- @h[i][i-3] = 0.0
- end
- end
-
- # Double QR step involving rows l:n and columns m:n
-
- m.upto(n-1) do |k|
- notlast = (k != n-1)
- if (k != m)
- p = @h[k][k-1]
- q = @h[k+1][k-1]
- r = (notlast ? @h[k+2][k-1] : 0.0)
- x = p.abs + q.abs + r.abs
- next if x == 0
- p /= x
- q /= x
- r /= x
- end
- s = Math.sqrt(p * p + q * q + r * r)
- if (p < 0)
- s = -s
- end
- if (s != 0)
- if (k != m)
- @h[k][k-1] = -s * x
- elsif (l != m)
- @h[k][k-1] = -@h[k][k-1]
- end
- p += s
- x = p / s
- y = q / s
- z = r / s
- q /= p
- r /= p
-
- # Row modification
-
- k.upto(nn-1) do |j|
- p = @h[k][j] + q * @h[k+1][j]
- if (notlast)
- p += r * @h[k+2][j]
- @h[k+2][j] = @h[k+2][j] - p * z
- end
- @h[k][j] = @h[k][j] - p * x
- @h[k+1][j] = @h[k+1][j] - p * y
- end
-
- # Column modification
-
- 0.upto([n, k+3].min) do |i|
- p = x * @h[i][k] + y * @h[i][k+1]
- if (notlast)
- p += z * @h[i][k+2]
- @h[i][k+2] = @h[i][k+2] - p * r
- end
- @h[i][k] = @h[i][k] - p
- @h[i][k+1] = @h[i][k+1] - p * q
- end
-
- # Accumulate transformations
-
- low.upto(high) do |i|
- p = x * @v[i][k] + y * @v[i][k+1]
- if (notlast)
- p += z * @v[i][k+2]
- @v[i][k+2] = @v[i][k+2] - p * r
- end
- @v[i][k] = @v[i][k] - p
- @v[i][k+1] = @v[i][k+1] - p * q
- end
- end # (s != 0)
- end # k loop
- end # check convergence
- end # while (n >= low)
-
- # Backsubstitute to find vectors of upper triangular form
-
- if (norm == 0.0)
- return
- end
-
- (nn-1).downto(0) do |k|
- p = @d[k]
- q = @e[k]
-
- # Real vector
-
- if (q == 0)
- l = k
- @h[k][k] = 1.0
- (k-1).downto(0) do |i|
- w = @h[i][i] - p
- r = 0.0
- l.upto(k) do |j|
- r += @h[i][j] * @h[j][k]
- end
- if (@e[i] < 0.0)
- z = w
- s = r
- else
- l = i
- if (@e[i] == 0.0)
- if (w != 0.0)
- @h[i][k] = -r / w
- else
- @h[i][k] = -r / (eps * norm)
- end
-
- # 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][k] = t
- if (x.abs > z.abs)
- @h[i+1][k] = (-r - w * t) / x
- else
- @h[i+1][k] = (-s - y * t) / z
- end
- end
-
- # Overflow control
-
- t = @h[i][k].abs
- if ((eps * t) * t > 1)
- i.upto(k) do |j|
- @h[j][k] = @h[j][k] / t
- end
- end
- end
- end
-
- # Complex vector
-
- elsif (q < 0)
- l = n-1
-
- # Last vector component imaginary so matrix is triangular
-
- if (@h[n][n-1].abs > @h[n-1][n].abs)
- @h[n-1][n-1] = q / @h[n][n-1]
- @h[n-1][n] = -(@h[n][n] - p) / @h[n][n-1]
- else
- cdivr, cdivi = 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
- end
- @h[n][n-1] = 0.0
- @h[n][n] = 1.0
- (n-2).downto(0) do |i|
- ra = 0.0
- sa = 0.0
- l.upto(n) do |j|
- ra = ra + @h[i][j] * @h[j][n-1]
- sa = sa + @h[i][j] * @h[j][n]
- end
- w = @h[i][i] - p
-
- if (@e[i] < 0.0)
- z = w
- r = ra
- s = sa
- else
- l = i
- if (@e[i] == 0)
- cdivr, cdivi = 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 * (w.abs + q.abs +
- x.abs + y.abs + z.abs)
- end
- cdivr, cdivi = 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 (x.abs > (z.abs + q.abs))
- @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
- cdivr, cdivi = 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
- end
- end
-
- # Overflow control
-
- t = [@h[i][n-1].abs, @h[i][n].abs].max
- if ((eps * t) * t > 1)
- i.upto(n) do |j|
- @h[j][n-1] = @h[j][n-1] / t
- @h[j][n] = @h[j][n] / t
- end
- end
- end
- end
- end
- end
-
- # Vectors of isolated roots
-
- nn.times do |i|
- if (i < low || i > high)
- i.upto(nn-1) do |j|
- @v[i][j] = @h[i][j]
- end
- end
- end
-
- # Back transformation to get eigenvectors of original matrix
-
- (nn-1).downto(low) do |j|
- low.upto(high) do |i|
- z = 0.0
- low.upto([j, high].min) do |k|
- z += @v[i][k] * @h[k][j]
- end
- @v[i][j] = z
- end
- end
- end
-
- end
-end