
mikera.matrixx.decompose.impl.eigen.SymmetricQREigenHelper Maven / Gradle / Ivy
/*
* Copyright (c) 2009-2013, Peter Abeles. All Rights Reserved.
*
* This file is part of Efficient Java Matrix Library (EJML).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package mikera.matrixx.decompose.impl.eigen;
import java.util.Random;
import mikera.matrixx.Matrix;
/**
* A helper class for the symmetric matrix implicit QR algorithm for eigenvalue decomposition.
* Performs most of the basic operations needed to extract eigenvalues and eigenvectors.
*
* @author Peter Abeles
*/
public class SymmetricQREigenHelper {
public static final double EPS = Math.pow(2,-52);
// used in exceptional shifts
protected Random rand = new Random(0x34671e);
// how many steps has it taken
protected int steps;
// how many exception shifts has it performed
protected int numExceptional;
// the step number of the last exception shift
protected int lastExceptional;
// used to compute eigenvalues directly
protected EigenvalueSmall eigenSmall = new EigenvalueSmall();
// orthogonal matrix used in similar transform. optional
protected Matrix Q;
// size of the matrix being processed
protected int N;
// diagonal elements in the matrix
protected double diag[];
// the off diagonal elements
protected double off[];
// which submatrix is being processed
protected int x1;
protected int x2;
// where splits are performed
protected int splits[];
protected int numSplits;
// current value of the bulge
private double bulge;
// local helper functions
private double c,s,c2,s2,cs;
public SymmetricQREigenHelper() {
splits = new int[1];
}
public void printMatrix() {
System.out.print("Off Diag[ ");
for( int j = 0; j < N-1; j++ ) {
System.out.printf("%5.2f ",off[j]);
}
System.out.println();
System.out.print(" Diag[ ");
for( int j = 0; j < N; j++ ) {
System.out.printf("%5.2f ",diag[j]);
}
System.out.println();
}
public void setQ(Matrix q) {
Q = q;
}
public void incrementSteps() {
steps++;
}
/**
* Sets up and declares internal data structures.
*
* @param diag Diagonal elements from tridiagonal matrix. Modified.
* @param off Off diagonal elements from tridiagonal matrix. Modified.
* @param numCols number of columns (and rows) in the matrix.
*/
public void init( double diag[] ,
double off[],
int numCols ) {
reset(numCols);
this.diag = diag;
this.off = off;
}
/**
* Exchanges the internal array of the diagonal elements for the provided one.
*/
public double[] swapDiag( double diag[] ) {
double[] ret = this.diag;
this.diag = diag;
return ret;
}
/**
* Exchanges the internal array of the off diagonal elements for the provided one.
*/
public double[] swapOff( double off[] ) {
double[] ret = this.off;
this.off = off;
return ret;
}
/**
* Sets the size of the matrix being decomposed, declares new memory if needed,
* and sets all helper functions to their initial value.
*/
public void reset( int N ) {
this.N = N;
this.diag = null;
this.off = null;
if( splits.length < N ) {
splits = new int[N];
}
numSplits = 0;
x1 = 0;
x2 = N-1;
steps = numExceptional = lastExceptional = 0;
this.Q = null;
}
public double[] copyDiag( double []ret ) {
if( ret == null || ret.length < N ) {
ret = new double[N];
}
System.arraycopy(diag,0,ret,0,N);
return ret;
}
public double[] copyOff( double []ret ) {
if( ret == null || ret.length < N-1 ) {
ret = new double[N-1];
}
System.arraycopy(off,0,ret,0,N-1);
return ret;
}
public double[] copyEigenvalues( double []ret ) {
if( ret == null || ret.length < N ) {
ret = new double[N];
}
System.arraycopy(diag,0,ret,0,N);
return ret;
}
/**
* Sets which submatrix is being processed.
* @param x1 Lower bound, inclusive.
* @param x2 Upper bound, inclusive.
*/
public void setSubmatrix( int x1 , int x2 ) {
this.x1 = x1;
this.x2 = x2;
}
/**
* Checks to see if the specified off diagonal element is zero using a relative metric.
*/
protected boolean isZero( int index ) {
double bottom = Math.abs(diag[index])+Math.abs(diag[index+1]);
return( Math.abs(off[index]) <= bottom*EPS);
}
protected void performImplicitSingleStep( double lambda , boolean byAngle )
{
if( x2-x1 == 1 ) {
createBulge2by2(x1,lambda,byAngle);
} else {
createBulge(x1,lambda,byAngle);
for( int i = x1; i < x2-2 && bulge != 0.0; i++ ) {
removeBulge(i);
}
if( bulge != 0.0 )
removeBulgeEnd(x2-2);
}
}
protected void updateQ( int m , int n , double c , double s )
{
int rowA = m*N;
int rowB = n*N;
// for( int i = 0; i < N; i++ ) {
// double a = Q.data[rowA+i];
// double b = Q.data[rowB+i];
// Q.data[rowA+i] = c*a + s*b;
// Q.data[rowB+i] = -s*a + c*b;
// }
int endA = rowA + N;
while( rowA < endA ) {
double a = Q.data[rowA];
double b = Q.data[rowB];
Q.data[rowA++] = c*a + s*b;
Q.data[rowB++] = -s*a + c*b;
}
}
/**
* Performs a similar transform on A-pI
*/
protected void createBulge( int x1 , double p , boolean byAngle ) {
double a11 = diag[x1];
double a22 = diag[x1+1];
double a12 = off[x1];
double a23 = off[x1+1];
if( byAngle ) {
c = Math.cos(p);
s = Math.sin(p);
c2 = c*c;
s2 = s*s;
cs = c*s;
} else {
computeRotation(a11-p, a12);
}
// multiply the rotator on the top left.
diag[x1] = c2*a11 + 2.0*cs*a12 + s2*a22;
diag[x1+1] = c2*a22 - 2.0*cs*a12 + s2*a11;
off[x1] = a12*(c2-s2) + cs*(a22 - a11);
off[x1+1] = c*a23;
bulge = s*a23;
if( Q != null )
updateQ(x1,x1+1,c,s);
}
protected void createBulge2by2( int x1 , double p , boolean byAngle ) {
double a11 = diag[x1];
double a22 = diag[x1+1];
double a12 = off[x1];
if( byAngle ) {
c = Math.cos(p);
s = Math.sin(p);
c2 = c*c;
s2 = s*s;
cs = c*s;
} else {
computeRotation(a11-p, a12);
}
// multiply the rotator on the top left.
diag[x1] = c2*a11 + 2.0*cs*a12 + s2*a22;
diag[x1+1] = c2*a22 - 2.0*cs*a12 + s2*a11;
off[x1] = a12*(c2-s2) + cs*(a22 - a11);
if( Q != null )
updateQ(x1,x1+1,c,s);
}
/**
* Computes the rotation and stores it in (c,s)
*/
private void computeRotation(double run, double rise) {
// double alpha = Math.sqrt(run*run + rise*rise);
// c = run/alpha;
// s = rise/alpha;
if( Math.abs(rise) > Math.abs(run)) {
double k = run/rise;
double bottom = 1.0 + k*k;
double bottom_sq = Math.sqrt(bottom);
s2 = 1.0/bottom;
c2 = k*k/bottom;
cs = k/bottom;
s = 1.0/bottom_sq;
c = k/bottom_sq;
} else {
double t = rise/run;
double bottom = 1.0 + t*t;
double bottom_sq = Math.sqrt(bottom);
c2 = 1.0/bottom;
s2 = t*t/bottom;
cs = t/bottom;
c = 1.0/bottom_sq;
s = t/bottom_sq;
}
}
protected void removeBulge( int x1 ) {
double a22 = diag[x1+1];
double a33 = diag[x1+2];
double a12 = off[x1];
double a23 = off[x1+1];
double a34 = off[x1+2];
computeRotation(a12, bulge);
// multiply the rotator on the top left.
diag[x1+1] = c2*a22 + 2.0*cs*a23 + s2*a33;
diag[x1+2] = c2*a33 - 2.0*cs*a23 + s2*a22;
off[x1] = c*a12 + s*bulge;
off[x1+1] = a23*(c2-s2) + cs*(a33 - a22);
off[x1+2] = c*a34;
bulge = s*a34;
if( Q != null )
updateQ(x1+1,x1+2,c,s);
}
/**
* Rotator to remove the bulge
*/
protected void removeBulgeEnd( int x1 ) {
double a22 = diag[x1+1];
double a12 = off[x1];
double a23 = off[x1+1];
double a33 = diag[x1+2];
computeRotation(a12, bulge);
// multiply the rotator on the top left.
diag[x1+1] = c2*a22 + 2.0*cs*a23 + s2*a33;
diag[x1+2] = c2*a33 - 2.0*cs*a23 + s2*a22;
off[x1] = c*a12 + s*bulge;
off[x1+1] = a23*(c2-s2) + cs*(a33 - a22);
if( Q != null )
updateQ(x1+1,x1+2,c,s);
}
/**
* Computes the eigenvalue of the 2 by 2 matrix.
*/
protected void eigenvalue2by2( int x1 ) {
double a = diag[x1];
double b = off[x1];
double c = diag[x1+1];
// normalize to reduce overflow
double absA = Math.abs(a);
double absB = Math.abs(b);
double absC = Math.abs(c);
double scale = absA > absB ? absA : absB;
if( absC > scale ) scale = absC;
// see if it is a pathological case. the diagonal must already be zero
// and the eigenvalues are all zero. so just return
if( scale == 0 ) {
off[x1] = 0;
diag[x1] = 0;
diag[x1+1] = 0;
return;
}
a /= scale;
b /= scale;
c /= scale;
eigenSmall.symm2x2_fast(a,b,c);
off[x1] = 0;
diag[x1] = scale*eigenSmall.value0.x;
diag[x1+1] = scale*eigenSmall.value1.x;
}
/**
* Perform a shift in a random direction that is of the same magnitude as the elements in the matrix.
*/
public void exceptionalShift() {
// rotating by a random angle handles at least one case using a random lambda
// does not handle well:
// - two identical eigenvalues are next to each other and a very small diagonal element
numExceptional++;
double mag = 0.05*numExceptional;
if( mag > 1.0 ) mag = 1.0;
double theta = 2.0*(rand.nextDouble()-0.5)*mag;
performImplicitSingleStep(theta,true);
lastExceptional = steps;
}
/**
* Tells it to process the submatrix at the next split. Should be called after the
* current submatrix has been processed.
*/
public boolean nextSplit() {
if( numSplits == 0 )
return false;
x2 = splits[--numSplits];
if( numSplits > 0 )
x1 = splits[numSplits-1]+1;
else
x1 = 0;
return true;
}
public double computeShift() {
if( x2-x1 >= 1 )
return computeWilkinsonShift();
else
return diag[x2];
}
public double computeWilkinsonShift() {
double a = diag[x2-1];
double b = off[x2-1];
double c = diag[x2];
// normalize to reduce overflow
double absA = Math.abs(a);
double absB = Math.abs(b);
double absC = Math.abs(c);
double scale = absA > absB ? absA : absB;
if( absC > scale ) scale = absC;
if( scale == 0 ) {
throw new RuntimeException("this should never happen");
}
a /= scale;
b /= scale;
c /= scale;
// TODO see 385
eigenSmall.symm2x2_fast(a,b,c);
// return the eigenvalue closest to c
double diff0 = Math.abs(eigenSmall.value0.x-c);
double diff1 = Math.abs(eigenSmall.value1.x-c);
if( diff0 < diff1 )
return scale*eigenSmall.value0.x;
else
return scale*eigenSmall.value1.x;
}
public int getMatrixSize() {
return N;
}
public void resetSteps() {
steps = 0;
lastExceptional = 0;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy