E:\home\projects\trilogy\raptor\Clustering\src\cern\colt\matrix\linalg\LanczosDecomposition.java
|
package cern.colt.matrix.linalg;
import cern.colt.matrix.DoubleFactory1D;
import cern.colt.matrix.DoubleFactory2D;
import cern.colt.matrix.DoubleMatrix1D;
import cern.colt.matrix.DoubleMatrix2D;
import cern.jet.math.Functions;
/**
* @author <a href="razvan.surdulescu@post.harvard.edu">Razvan Surdulescu</a>, Copyright (c) 2003
*/
public class LanczosDecomposition {
private static final double EPSILON = 1E-6;
private boolean m_log;
private DoubleMatrix1D m_eigenvalues;
private DoubleMatrix2D m_eigenvectors;
public LanczosDecomposition(DoubleMatrix2D matrix) {
this(matrix, matrix.columns(), false);
}
public LanczosDecomposition(DoubleMatrix2D matrix, int sought) {
this(matrix, sought, false);
}
public LanczosDecomposition(DoubleMatrix2D matrix, boolean log) {
this(matrix, matrix.columns(), log);
}
public LanczosDecomposition(DoubleMatrix2D matrix, int sought, boolean log) {
Property.DEFAULT.checkSquare(matrix);
if (!Property.DEFAULT.isSymmetric(matrix)) {
throw new IllegalArgumentException("Matrix must be symmetric: " + cern.colt.matrix.doublealgo.Formatter.shape(matrix));
}
m_log = log;
lanczos(matrix, sought);
}
/**
* Returns the eigenvalues.
*/
public DoubleMatrix1D getRealEigenvalues() {
return m_eigenvalues;
}
/**
* Returns the eigenvector matrix.
*/
public DoubleMatrix2D getV() {
return m_eigenvectors;
}
/**
* Iterative Lanczos algorithm for finding the approximate eigenvalues and
* eigenvectors of a matrix.
*
* This implementation was pieced together using information from:
* <ul>
* <li>Lecture notes from <a href="http://www.cs.berkeley.edu/~demmel/cs267/lecture20/lecture20.html">
* CS267</a> at Berkeley.</li>
* <li>Axel Ruhe's <a href="http://www.cs.utk.edu/~dongarra/etemplates/node103.html">description</a>
* of the Lanczos Algorithm.</li>
* </ul>
*
* @param input The matrix whose eigenvalues we wish to compute.
* @param sought The number of eigenvalues we wish to compute for the input matrix.
* This number cannot exceed the number of columns for the matrix.
*/
private void lanczos(DoubleMatrix2D input, int sought) {
int n = input.columns();
if (sought > n) {
throw new IllegalArgumentException("Number of eigensolutions sought cannot exceed size of the matrix: " +
sought + " > " + n);
}
// random vector
DoubleMatrix1D r = DoubleFactory1D.dense.make(n);
for (int i = 0; i < n; i++) {
r.set(i, Math.random());
}
if (m_log) {
System.out.println("Random vector: " + r);
}
// initial size for algorithm data structures
// this may grow after a number of iterations
int size = sought;
// diagonal elements of T
DoubleMatrix1D a = DoubleFactory1D.dense.make(size);
// off-diagonal elements of T
DoubleMatrix1D b = DoubleFactory1D.dense.make(size);
// basis vectors for the Krylov subspace
DoubleMatrix2D v = DoubleFactory2D.dense.make(n, size);
// arrays used in the QL decomposition
double[] d = new double[size + 1];
double[] e = new double[size + 1];
double[][] z = new double[size + 1][size + 1];
// which ritzvalues are converged?
boolean[] c = new boolean[size];
// how many converged eigenvalues have been found?
int found = 0;
// algorithm iterations
int i = 0;
while (true) {
if (m_log) {
System.out.println("Iteration=" + i);
}
// v(i) = r / b(i-1)
if (i == 0) {
v.viewColumn(i).assign(r).assign(Functions.div(Math.sqrt(Algebra.DEFAULT.norm2(r))));
} else if (b.get(i - 1) != 0) {
v.viewColumn(i).assign(r).assign(Functions.div(b.get(i - 1)));
}
if (m_log) {
System.out.println("v(" + i + ")=" + v.viewColumn(i));
}
// r = A * v(i)
r.assign(Algebra.DEFAULT.mult(input, v.viewColumn(i)));
if (m_log) {
System.out.println("r = A * v(" + i + ") = " + r);
}
// r = r - b(i-1) * v(i-1)
if (i == 0) {
// v(i-1) = 0, so r is unchanged in this case
} else {
DoubleMatrix1D t1 = v.viewColumn(i - 1).copy().assign(Functions.mult(b.get(i - 1)));
r.assign(t1, Functions.minus);
}
if (m_log) {
System.out.println("r = r - b(" + (i - 1) + ") * v(" + (i - 1) + ") = " + r);
}
// a(i) = v(i)' * r
a.set(i, Algebra.DEFAULT.mult(v.viewColumn(i), r));
if (m_log) {
System.out.println("a(" + i + ") = v(" + i + ")' * r = " + a);
}
// r = r - a(i)*v(i)
DoubleMatrix1D t2 = v.viewColumn(i).copy().assign(Functions.mult(a.get(i)));
r.assign(t2, Functions.minus);
if (m_log) {
System.out.println("r = r - a(" + i + ") * v(" + i + ") = " + r);
}
// TODO: re-orthogonalize if necessary
// b(i) = norm(r)
b.set(i, Math.sqrt(Algebra.DEFAULT.norm2(r)));
if (m_log) {
System.out.println("b(" + i + ") = norm(r) = " + b);
}
// prepare to compute the eigenvalues and eigenvectors
// of the tridiagonal matrix defined by a and b
System.arraycopy(a.toArray(), 0, d, 1, i + 1);
System.arraycopy(b.toArray(), 0, e, 2, i);
for (int p = 1; p <= i + 1; p++) {
for (int q = 1; q <= i + 1; q++) {
if (p == q) {
z[p][q] = 1;
} else {
z[p][q] = 0;
}
}
}
// compute the eigenvalues and eigenvectors of the
// tridiagonal matrix
tqli(d, e, i + 1, z);
// count the number of converged eigenvalues
found = 0;
for (int j = 0; j <= i; j++) {
double ritzvalue = d[j + 1];
double residual = Math.abs(b.get(i) * z[i + 1][j + 1]);
if (residual <= EPSILON) {
c[j] = true;
found++;
} else {
c[j] = false;
}
if (m_log) {
System.out.println("Ritz value[" + j + "]=" + ritzvalue + ", residual[" + j + "]=" + residual);
}
}
if (found >= sought) {
break;
}
i++;
if (i >= n) {
break;
} else if (i >= size) {
if (m_log) {
System.out.println("Growing arrays from " + size + " to " + (2 * size));
}
size = 2 * size;
a = grow(a, size);
b = grow(b, size);
v = grow(v, size);
d = grow(d, size + 1);
e = grow(e, size + 1);
z = grow(z, size + 1);
c = grow(c, size + 1);
}
}
m_eigenvalues = DoubleFactory1D.dense.make(found);
m_eigenvectors = DoubleFactory2D.dense.make(n, found);
DoubleMatrix2D ritzvectors = DoubleFactory2D.dense.make(z).viewPart(1, 1, size, size);
int index = 0;
for (int col = 0; col < i; col++) {
if (c[col]) {
m_eigenvalues.set(index, d[col + 1]);
m_eigenvectors.viewColumn(index).assign(Algebra.DEFAULT.mult(v, ritzvectors.viewColumn(col)));
index++;
}
}
if (m_log) {
System.out.println("Eigenvalues: " + m_eigenvalues);
System.out.println("Eigenvectors: " + m_eigenvectors);
}
}
private DoubleMatrix1D grow(DoubleMatrix1D matrix, int length) {
DoubleMatrix1D result = DoubleFactory1D.dense.make(length);
for (int index = 0; index < matrix.size(); index++) {
result.setQuick(index, matrix.getQuick(index));
}
return result;
}
private DoubleMatrix2D grow(DoubleMatrix2D matrix, int columns) {
DoubleMatrix2D result = DoubleFactory2D.dense.make(matrix.rows(), columns);
for (int col = 0; col < matrix.columns(); col++) {
result.viewColumn(col).assign(matrix.viewColumn(col));
}
return result;
}
private double[] grow(double[] array, int length) {
double[] result = new double[length];
System.arraycopy(array, 0, result, 0, array.length);
return result;
}
private double[][] grow(double[][] array, int length) {
double[][] result = new double[length][length];
for (int row = 0; row < array.length; row++) {
System.arraycopy(array[row], 0, result[row], 0, array.length);
}
return result;
}
private boolean[] grow(boolean[] array, int length) {
boolean[] result = new boolean[length];
System.arraycopy(array, 0, result, 0, array.length);
return result;
}
/**
* Return the absolute value of a with the same sign as b.
*/
private double sign(double a, double b) {
return (b >= 0.0 ? Math.abs(a) : -Math.abs(a));
}
/**
* Returns sqrt(a^2 + b^2) without under/overflow.
*/
private double pythag(double a, double b) {
double r;
if (Math.abs(a) > Math.abs(b)) {
r = b / a;
r = Math.abs(a) * Math.sqrt(1 + r * r);
} else if (b != 0) {
r = a / b;
r = Math.abs(b) * Math.sqrt(1 + r * r);
} else {
r = 0.0;
}
return r;
}
/**
* "Tridiagonal QL Implicit" routine for computing eigenvalues and eigenvectors of a symmetric,
* real, tridiagonal matrix.
*
* The routine works extremely well in practice. The number of iterations for the first few
* eigenvalues might be 4 or 5, say, but meanwhile the off-diagonal elements in the lower right-hand
* corner have been reduced too. The later eigenvalues are liberated with very little work. The
* average number of iterations per eigenvalue is typically 1.3 - 1.6. The operation count per
* iteration is O(n), with a fairly large effective coefficient, say, ~20n. The total operation count
* for the diagonalization is then ~20n * (1.3 - 1.6)n = ~30n^2. If the eigenvectors are required,
* there is an additional, much larger, workload of about 3n^3 operations.
*
* This implementation is taken directly from "Numerical Recipes in C"
* <a href="http://www.ulib.org/webRoot/Books/Numerical_Recipes/bookcpdf/c11-3.pdf">Chapter 11.3</a>
* and translated to Java.
*
* @param d [1..n] array. On input, it contains the diagonal elements of the tridiagonal matrix.
* On output, it contains the eigenvalues.
* @param e [1..n] array. On input, it contains the subdiagonal elements of the tridiagonal
* matrix, with e[1] arbitrary. On output, its contents are destroyed.
* @param n The size of all parameter arrays.
* @param z [1..n][1..n] array. On input, it contains the identity matrix. On output, the kth column
* of z returns the normalized eigenvector corresponding to d[k].
*/
private void tqli(double d[], double e[], int n, double[][] z) {
int i;
// Convenient to renumber the elements of e.
for (i = 2; i <= n; i++) {
e[i - 1] = e[i];
}
e[n] = 0.0;
for (int l = 1; l <= n; l++) {
int iter = 0;
int m;
do {
// Look for a single small subdiagonal element to split the matrix.
for (m = l; m <= n - 1; m++) {
double dd = Math.abs(d[m]) + Math.abs(d[m + 1]);
if (Math.abs(e[m]) + dd == dd) {
break;
}
}
if (m != l) {
iter = iter + 1;
if (iter == 30) {
throw new RuntimeException("Too many iterations");
}
// Form shift.
double g = (d[l + 1] - d[l]) / (2.0 * e[l]);
double r = pythag(g, 1.0);
// This is dm / ks.
g = d[m] - d[l] + e[l] / (g + sign(r, g));
double s, c;
s = c = 1.0;
double p = 0.0;
// A plane rotation as in the original QL, followed by Givens rotations to restore tridiagonal form.
for (i = m - 1; i >= l; i--) {
double f = s * e[i];
double b = c * e[i];
e[i + 1] = (r = pythag(f, g));
// Recover from underflow.
if (r == 0.0) {
d[i + 1] -= p;
e[m] = 0.0;
break;
}
s = f / r;
c = g / r;
g = d[i + 1] - p;
r = (d[i] - g) * s + 2.0 * c * b;
d[i + 1] = g + (p = s * r);
g = c * r - b;
// Form eigenvectors (optional).
for (int k = 1; k <= n; k++) {
f = z[k][i + 1];
z[k][i + 1] = s * z[k][i] + c * f;
z[k][i] = c * z[k][i] - s * f;
}
}
if (r == 0.0 && i >= l) {
continue;
}
d[l] -= p;
e[l] = g;
e[m] = 0.0;
}
} while (m != l);
}
}
}