You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mahout.apache.org by td...@apache.org on 2012/05/04 00:36:50 UTC

svn commit: r1333667 - in /mahout/trunk/math/src: main/java/org/apache/mahout/math/decomposer/lanczos/ main/java/org/apache/mahout/math/matrix/linalg/ main/java/org/apache/mahout/math/solver/ test/java/org/apache/mahout/math/decomposer/lanczos/ test/ja...

Author: tdunning
Date: Thu May  3 22:36:49 2012
New Revision: 1333667

URL: http://svn.apache.org/viewvc?rev=1333667&view=rev
Log:
MAHOUT-1005 - Added new implementation of EigenDecomposition based on JAMA code.  Deleted old EigenvalueDecomposition from Colt.

Added:
    mahout/trunk/math/src/main/java/org/apache/mahout/math/solver/EigenDecomposition.java
    mahout/trunk/math/src/test/java/org/apache/mahout/math/solver/EigenDecompositionTest.java
Removed:
    mahout/trunk/math/src/main/java/org/apache/mahout/math/matrix/linalg/EigenvalueDecomposition.java
Modified:
    mahout/trunk/math/src/main/java/org/apache/mahout/math/decomposer/lanczos/LanczosSolver.java
    mahout/trunk/math/src/test/java/org/apache/mahout/math/decomposer/lanczos/TestLanczosSolver.java

Modified: mahout/trunk/math/src/main/java/org/apache/mahout/math/decomposer/lanczos/LanczosSolver.java
URL: http://svn.apache.org/viewvc/mahout/trunk/math/src/main/java/org/apache/mahout/math/decomposer/lanczos/LanczosSolver.java?rev=1333667&r1=1333666&r2=1333667&view=diff
==============================================================================
--- mahout/trunk/math/src/main/java/org/apache/mahout/math/decomposer/lanczos/LanczosSolver.java (original)
+++ mahout/trunk/math/src/main/java/org/apache/mahout/math/decomposer/lanczos/LanczosSolver.java Thu May  3 22:36:49 2012
@@ -23,9 +23,7 @@ import org.apache.mahout.math.Vector;
 import org.apache.mahout.math.VectorIterable;
 import org.apache.mahout.math.function.DoubleFunction;
 import org.apache.mahout.math.function.PlusMult;
-import org.apache.mahout.math.matrix.DoubleMatrix1D;
-import org.apache.mahout.math.matrix.DoubleMatrix2D;
-import org.apache.mahout.math.matrix.linalg.EigenvalueDecomposition;
+import org.apache.mahout.math.solver.EigenDecomposition;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -50,7 +48,7 @@ import java.util.Map;
  * problems into floating point <em>underflow</em> (ie, very small singular values will become invisible, as they
  * will appear to be zero and the algorithm will terminate).
  * </p>
- * <p>This implementation uses {@link org.apache.mahout.math.matrix.linalg.EigenvalueDecomposition} to do the
+ * <p>This implementation uses {@link EigenDecomposition} to do the
  * eigenvalue extraction from the small (desiredRank x desiredRank) tridiagonal matrix.  Numerical stability is
  * achieved via brute-force: re-orthogonalization against all previous eigenvectors is computed after every pass.
  * This can be made smarter if (when!) this proves to be a major bottleneck.  Of course, this step can be parallelized
@@ -140,16 +138,16 @@ public class LanczosSolver {
 
     log.info("Lanczos iteration complete - now to diagonalize the tri-diagonal auxiliary matrix.");
     // at this point, have tridiag all filled out, and basis is all filled out, and orthonormalized
-    EigenvalueDecomposition decomp = new EigenvalueDecomposition(triDiag);
+    EigenDecomposition decomp = new EigenDecomposition(triDiag);
 
-    DoubleMatrix2D eigenVects = decomp.getV();
-    DoubleMatrix1D eigenVals = decomp.getRealEigenvalues();
+    Matrix eigenVects = decomp.getV();
+    Vector eigenVals = decomp.getRealEigenvalues();
     endTime(TimingSection.TRIDIAG_DECOMP);
     startTime(TimingSection.FINAL_EIGEN_CREATE);
     for (int row = 0; row < i; row++) {
       Vector realEigen = null;
       // the eigenvectors live as columns of V, in reverse order.  Weird but true.
-      DoubleMatrix1D ejCol = eigenVects.viewColumn(i - row - 1);
+      Vector ejCol = eigenVects.viewColumn(i - row - 1);
       int size = Math.min(ejCol.size(), state.getBasisSize());
       for (int j = 0; j < size; j++) {
         double d = ejCol.get(j);

Added: mahout/trunk/math/src/main/java/org/apache/mahout/math/solver/EigenDecomposition.java
URL: http://svn.apache.org/viewvc/mahout/trunk/math/src/main/java/org/apache/mahout/math/solver/EigenDecomposition.java?rev=1333667&view=auto
==============================================================================
--- mahout/trunk/math/src/main/java/org/apache/mahout/math/solver/EigenDecomposition.java (added)
+++ mahout/trunk/math/src/main/java/org/apache/mahout/math/solver/EigenDecomposition.java Thu May  3 22:36:49 2012
@@ -0,0 +1,895 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+/**
+ * Adapted from the public domain Jama code.
+ */
+
+package org.apache.mahout.math.solver;
+
+
+import org.apache.mahout.math.DenseMatrix;
+import org.apache.mahout.math.DenseVector;
+import org.apache.mahout.math.Matrix;
+import org.apache.mahout.math.Vector;
+import org.apache.mahout.math.function.Functions;
+
+/**
+ * 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 EigenDecomposition {
+  /**
+   * Row and column dimension (square matrix).
+   */
+  private int n;
+
+  /**
+   * Arrays for internal storage of eigenvalues.
+   */
+  private Vector d, e;
+
+  /**
+   * Array for internal storage of eigenvectors.
+   */
+  private Matrix v;
+
+  public EigenDecomposition(Matrix x) {
+    this(x, isSymmetric(x));
+  }
+
+  public EigenDecomposition(Matrix x, boolean isSymmetric) {
+    n = x.columnSize();
+    d = new DenseVector(n);
+    e = new DenseVector(n);
+    v = new DenseMatrix(n, n);
+
+    if (isSymmetric) {
+      v.assign(x);
+
+      // Tridiagonalize.
+      tred2();
+
+      // Diagonalize.
+      tql2();
+
+    } else {
+      // Reduce to Hessenberg form.
+      // Reduce Hessenberg to real Schur form.
+      hqr2(orthes(x));
+    }
+  }
+
+  /**
+   * Return the eigenvector matrix
+   *
+   * @return V
+   */
+  public Matrix getV() {
+    return v.like().assign(v);
+  }
+
+  /**
+   * Return the real parts of the eigenvalues
+   */
+  public Vector getRealEigenvalues() {
+    return d;
+  }
+
+  /**
+   * Return the imaginary parts of the eigenvalues
+   */
+  public Vector getImagEigenvalues() {
+    return e;
+  }
+
+  /**
+   * Return the block diagonal eigenvalue matrix
+   *
+   * @return D
+   */
+  public Matrix getD() {
+    Matrix X = new DenseMatrix(n, n);
+    X.assign(0);
+    X.viewDiagonal().assign(d);
+    for (int i = 0; i < n; i++) {
+      final double v = e.getQuick(i);
+      if (v > 0) {
+        X.setQuick(i, i + 1, v);
+      } else if (v < 0) {
+        X.setQuick(i, i - 1, v);
+      }
+    }
+    return X;
+  }
+
+  // Symmetric Householder reduction to tridiagonal form.
+  private void tred2() {
+    //  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.
+
+    d.assign(v.viewColumn(n - 1));
+
+    // Householder reduction to tridiagonal form.
+
+    for (int i = n - 1; i > 0; i--) {
+
+      // Scale to avoid under/overflow.
+
+      double scale = d.norm(1);
+      double h = 0.0;
+
+
+      if (scale == 0.0) {
+        e.setQuick(i, d.getQuick(i - 1));
+        for (int j = 0; j < i; j++) {
+          d.setQuick(j, v.getQuick(i - 1, j));
+          v.setQuick(i, j, 0.0);
+          v.setQuick(j, i, 0.0);
+        }
+      } else {
+
+        // Generate Householder vector.
+
+        for (int k = 0; k < i; k++) {
+          d.setQuick(k, d.getQuick(k) / scale);
+          h += d.getQuick(k) * d.getQuick(k);
+        }
+        double f = d.getQuick(i - 1);
+        double g = Math.sqrt(h);
+        if (f > 0) {
+          g = -g;
+        }
+        e.setQuick(i, scale * g);
+        h = h - f * g;
+        d.setQuick(i - 1, f - g);
+        for (int j = 0; j < i; j++) {
+          e.setQuick(j, 0.0);
+        }
+
+        // Apply similarity transformation to remaining columns.
+
+        for (int j = 0; j < i; j++) {
+          f = d.getQuick(j);
+          v.setQuick(j, i, f);
+          g = e.getQuick(j) + v.getQuick(j, j) * f;
+          for (int k = j + 1; k <= i - 1; k++) {
+            g += v.getQuick(k, j) * d.getQuick(k);
+            e.setQuick(k, e.getQuick(k) + v.getQuick(k, j) * f);
+          }
+          e.setQuick(j, g);
+        }
+        f = 0.0;
+        for (int j = 0; j < i; j++) {
+          e.setQuick(j, e.getQuick(j) / h);
+          f += e.getQuick(j) * d.getQuick(j);
+        }
+        double hh = f / (h + h);
+        for (int j = 0; j < i; j++) {
+          e.setQuick(j, e.getQuick(j) - hh * d.getQuick(j));
+        }
+        for (int j = 0; j < i; j++) {
+          f = d.getQuick(j);
+          g = e.getQuick(j);
+          for (int k = j; k <= i - 1; k++) {
+            v.setQuick(k, j, v.getQuick(k, j) - (f * e.getQuick(k) + g * d.getQuick(k)));
+          }
+          d.setQuick(j, v.getQuick(i - 1, j));
+          v.setQuick(i, j, 0.0);
+        }
+      }
+      d.setQuick(i, h);
+    }
+
+    // Accumulate transformations.
+
+    for (int i = 0; i < n - 1; i++) {
+      v.setQuick(n - 1, i, v.getQuick(i, i));
+      v.setQuick(i, i, 1.0);
+      double h = d.getQuick(i + 1);
+      if (h != 0.0) {
+        for (int k = 0; k <= i; k++) {
+          d.setQuick(k, v.getQuick(k, i + 1) / h);
+        }
+        for (int j = 0; j <= i; j++) {
+          double g = 0.0;
+          for (int k = 0; k <= i; k++) {
+            g += v.getQuick(k, i + 1) * v.getQuick(k, j);
+          }
+          for (int k = 0; k <= i; k++) {
+            v.setQuick(k, j, v.getQuick(k, j) - g * d.getQuick(k));
+          }
+        }
+      }
+      for (int k = 0; k <= i; k++) {
+        v.setQuick(k, i + 1, 0.0);
+      }
+    }
+    d.assign(v.viewRow(n - 1));
+    v.viewRow(n - 1).assign(0);
+    v.setQuick(n - 1, n - 1, 1.0);
+    e.setQuick(0, 0.0);
+  }
+
+  // Symmetric tridiagonal QL algorithm.
+  private void tql2() {
+
+    //  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.
+
+    e.viewPart(0, n - 1).assign(e.viewPart(1, n - 1));
+    e.setQuick(n - 1, 0.0);
+
+    double f = 0.0;
+    double tst1 = 0.0;
+    double eps = Math.pow(2.0, -52.0);
+    for (int l = 0; l < n; l++) {
+
+      // Find small subdiagonal element
+
+      tst1 = Math.max(tst1, Math.abs(d.getQuick(l)) + Math.abs(e.getQuick(l)));
+      int m = l;
+      while (m < n) {
+        if (Math.abs(e.getQuick(m)) <= eps * tst1) {
+          break;
+        }
+        m++;
+      }
+
+      // If m == l, d.getQuick(l) is an eigenvalue,
+      // otherwise, iterate.
+
+      if (m > l) {
+        int iter = 0;
+        do {
+          iter = iter + 1;  // (Could check iteration count here.)
+
+          // Compute implicit shift
+
+          double g = d.getQuick(l);
+          double p = (d.getQuick(l + 1) - g) / (2.0 * e.getQuick(l));
+          double r = Math.hypot(p, 1.0);
+          if (p < 0) {
+            r = -r;
+          }
+          d.setQuick(l, e.getQuick(l) / (p + r));
+          d.setQuick(l + 1, e.getQuick(l) * (p + r));
+          double dl1 = d.getQuick(l + 1);
+          double h = g - d.getQuick(l);
+          for (int i = l + 2; i < n; i++) {
+            d.setQuick(i, d.getQuick(i) - h);
+          }
+          f = f + h;
+
+          // Implicit QL transformation.
+
+          p = d.getQuick(m);
+          double c = 1.0;
+          double c2 = c;
+          double c3 = c;
+          double el1 = e.getQuick(l + 1);
+          double s = 0.0;
+          double s2 = 0.0;
+          for (int i = m - 1; i >= l; i--) {
+            c3 = c2;
+            c2 = c;
+            s2 = s;
+            g = c * e.getQuick(i);
+            h = c * p;
+            r = Math.hypot(p, e.getQuick(i));
+            e.setQuick(i + 1, s * r);
+            s = e.getQuick(i) / r;
+            c = p / r;
+            p = c * d.getQuick(i) - s * g;
+            d.setQuick(i + 1, h + s * (c * g + s * d.getQuick(i)));
+
+            // Accumulate transformation.
+
+            for (int k = 0; k < n; k++) {
+              h = v.getQuick(k, i + 1);
+              v.setQuick(k, i + 1, s * v.getQuick(k, i) + c * h);
+              v.setQuick(k, i, c * v.getQuick(k, i) - s * h);
+            }
+          }
+          p = -s * s2 * c3 * el1 * e.getQuick(l) / dl1;
+          e.setQuick(l, s * p);
+          d.setQuick(l, c * p);
+
+          // Check for convergence.
+
+        } while (Math.abs(e.getQuick(l)) > eps * tst1);
+      }
+      d.setQuick(l, d.getQuick(l) + f);
+      e.setQuick(l, 0.0);
+    }
+
+    // Sort eigenvalues and corresponding vectors.
+
+    for (int i = 0; i < n - 1; i++) {
+      int k = i;
+      double p = d.getQuick(i);
+      for (int j = i + 1; j < n; j++) {
+        if (d.getQuick(j) < p) {
+          k = j;
+          p = d.getQuick(j);
+        }
+      }
+      if (k != i) {
+        d.setQuick(k, d.getQuick(i));
+        d.setQuick(i, p);
+        for (int j = 0; j < n; j++) {
+          p = v.getQuick(j, i);
+          v.setQuick(j, i, v.getQuick(j, k));
+          v.setQuick(j, k, p);
+        }
+      }
+    }
+  }
+
+  // Nonsymmetric reduction to Hessenberg form.
+  private Matrix orthes(Matrix x) {
+    // Working storage for nonsymmetric algorithm.
+    Vector ort = new DenseVector(n);
+    Matrix H = new DenseMatrix(n, n).assign(x);
+
+    //  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.
+
+    int low = 0;
+    int high = n - 1;
+
+    for (int m = low + 1; m <= high - 1; m++) {
+
+      // Scale column.
+
+      double scale = H.viewColumn(m - 1).viewPart(m, high - m + 1).norm(1);
+
+      if (scale != 0.0) {
+        // Compute Householder transformation.
+
+        ort.viewPart(m, high - m + 1).assign(H.viewColumn(m - 1).viewPart(m, high - m + 1), Functions.plusMult(1 / scale));
+        double h = ort.viewPart(m, high - m + 1).getLengthSquared();
+
+        double g = Math.sqrt(h);
+        if (ort.getQuick(m) > 0) {
+          g = -g;
+        }
+        h = h - ort.getQuick(m) * g;
+        ort.setQuick(m, ort.getQuick(m) - g);
+
+        // Apply Householder similarity transformation
+        // H = (I-u*u'/h)*H*(I-u*u')/h)
+
+        Vector ortPiece = ort.viewPart(m, high - m + 1);
+        for (int j = m; j < n; j++) {
+          double f = ortPiece.dot(H.viewColumn(j).viewPart(m, high - m + 1)) / h;
+          H.viewColumn(j).viewPart(m, high - m + 1).assign(ortPiece, Functions.plusMult(-f));
+        }
+
+        for (int i = 0; i <= high; i++) {
+          double f = ortPiece.dot(H.viewRow(i).viewPart(m, high - m + 1)) / h;
+          H.viewRow(i).viewPart(m, high - m + 1).assign(ortPiece, Functions.plusMult(-f));
+        }
+        ort.setQuick(m, scale * ort.getQuick(m));
+        H.setQuick(m, m - 1, scale * g);
+      }
+    }
+
+    // Accumulate transformations (Algol's ortran).
+
+    v.assign(0);
+    v.viewDiagonal().assign(1);
+
+    for (int m = high - 1; m >= low + 1; m--) {
+      if (H.getQuick(m, m - 1) != 0.0) {
+        ort.viewPart(m + 1, high - m).assign(H.viewColumn(m - 1).viewPart(m + 1, high - m));
+        for (int j = m; j <= high; j++) {
+          double g = ort.viewPart(m, high - m + 1).dot(v.viewColumn(j).viewPart(m, high - m + 1));
+          // Double division avoids possible underflow
+          g = (g / ort.getQuick(m)) / H.getQuick(m, m - 1);
+          v.viewColumn(j).viewPart(m, high - m + 1).assign(ort.viewPart(m, high - m + 1), Functions.plusMult(g));
+        }
+      }
+    }
+    return H;
+  }
+
+
+  // Complex scalar division.
+  private transient double cdivr, cdivi;
+
+  private void cdiv(double xr, double xi, double yr, double yi) {
+    double r, d;
+    if (Math.abs(yr) > Math.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 void hqr2(Matrix h) {
+
+    //  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
+
+    final int nn = this.n;
+    int n = nn - 1;
+    final int low = 0;
+    final int high = nn - 1;
+    double eps = Math.pow(2.0, -52.0);
+    double exshift = 0.0;
+    double p = 0, q = 0, r = 0, s = 0, z = 0, t, w, x, y;
+
+    // Store roots isolated by balanc and compute matrix norm
+
+    double norm = h.aggregate(Functions.PLUS, Functions.ABS);
+
+    // Outer loop over eigenvalue index
+
+    int iter = 0;
+    while (n >= low) {
+
+      // Look for single small sub-diagonal element
+
+      int l = n;
+      while (l > low) {
+        s = Math.abs(h.getQuick(l - 1, l - 1)) + Math.abs(h.getQuick(l, l));
+        if (s == 0.0) {
+          s = norm;
+        }
+        if (Math.abs(h.getQuick(l, l - 1)) < eps * s) {
+          break;
+        }
+        l--;
+      }
+
+      // Check for convergence
+
+      if (l == n) {
+        // One root found
+        h.setQuick(n, n, h.getQuick(n, n) + exshift);
+        d.setQuick(n, h.getQuick(n, n));
+        e.setQuick(n, 0.0);
+        n--;
+        iter = 0;
+
+
+      } else if (l == n - 1) {
+        // Two roots found
+        w = h.getQuick(n, n - 1) * h.getQuick(n - 1, n);
+        p = (h.getQuick(n - 1, n - 1) - h.getQuick(n, n)) / 2.0;
+        q = p * p + w;
+        z = Math.sqrt(Math.abs(q));
+        h.setQuick(n, n, h.getQuick(n, n) + exshift);
+        h.setQuick(n - 1, n - 1, h.getQuick(n - 1, n - 1) + exshift);
+        x = h.getQuick(n, n);
+
+        // Real pair
+        if (q >= 0) {
+          if (p >= 0) {
+            z = p + z;
+          } else {
+            z = p - z;
+          }
+          d.setQuick(n - 1, x + z);
+          d.setQuick(n, d.getQuick(n - 1));
+          if (z != 0.0) {
+            d.setQuick(n, x - w / z);
+          }
+          e.setQuick(n - 1, 0.0);
+          e.setQuick(n, 0.0);
+          x = h.getQuick(n, n - 1);
+          s = Math.abs(x) + Math.abs(z);
+          p = x / s;
+          q = z / s;
+          r = Math.sqrt(p * p + q * q);
+          p = p / r;
+          q = q / r;
+
+          // Row modification
+
+          for (int j = n - 1; j < nn; j++) {
+            z = h.getQuick(n - 1, j);
+            h.setQuick(n - 1, j, q * z + p * h.getQuick(n, j));
+            h.setQuick(n, j, q * h.getQuick(n, j) - p * z);
+          }
+
+          // Column modification
+
+          for (int i = 0; i <= n; i++) {
+            z = h.getQuick(i, n - 1);
+            h.setQuick(i, n - 1, q * z + p * h.getQuick(i, n));
+            h.setQuick(i, n, q * h.getQuick(i, n) - p * z);
+          }
+
+          // Accumulate transformations
+
+          for (int i = low; i <= high; i++) {
+            z = v.getQuick(i, n - 1);
+            v.setQuick(i, n - 1, q * z + p * v.getQuick(i, n));
+            v.setQuick(i, n, q * v.getQuick(i, n) - p * z);
+          }
+
+          // Complex pair
+
+        } else {
+          d.setQuick(n - 1, x + p);
+          d.setQuick(n, x + p);
+          e.setQuick(n - 1, z);
+          e.setQuick(n, -z);
+        }
+        n = n - 2;
+        iter = 0;
+
+        // No convergence yet
+
+      } else {
+
+        // Form shift
+
+        x = h.getQuick(n, n);
+        y = 0.0;
+        w = 0.0;
+        if (l < n) {
+          y = h.getQuick(n - 1, n - 1);
+          w = h.getQuick(n, n - 1) * h.getQuick(n - 1, n);
+        }
+
+        // Wilkinson's original ad hoc shift
+
+        if (iter == 10) {
+          exshift += x;
+          for (int i = low; i <= n; i++) {
+            h.setQuick(i, i, x);
+          }
+          s = Math.abs(h.getQuick(n, n - 1)) + Math.abs(h.getQuick(n - 1, n - 2));
+          x = y = 0.75 * s;
+          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) {
+            s = Math.sqrt(s);
+            if (y < x) {
+              s = -s;
+            }
+            s = x - w / ((y - x) / 2.0 + s);
+            for (int i = low; i <= n; i++) {
+              h.setQuick(i, i, h.getQuick(i, i) - s);
+            }
+            exshift += s;
+            x = y = w = 0.964;
+          }
+        }
+
+        iter = iter + 1;   // (Could check iteration count here.)
+
+        // Look for two consecutive small sub-diagonal elements
+
+        int m = n - 2;
+        while (m >= l) {
+          z = h.getQuick(m, m);
+          r = x - z;
+          s = y - z;
+          p = (r * s - w) / h.getQuick(m + 1, m) + h.getQuick(m, m + 1);
+          q = h.getQuick(m + 1, m + 1) - z - r - s;
+          r = h.getQuick(m + 2, m + 1);
+          s = Math.abs(p) + Math.abs(q) + Math.abs(r);
+          p = p / s;
+          q = q / s;
+          r = r / s;
+          if (m == l) {
+            break;
+          }
+          if (Math.abs(h.getQuick(m, m - 1)) * (Math.abs(q) + Math.abs(r)) <
+            eps * (Math.abs(p) * (Math.abs(h.getQuick(m - 1, m - 1)) + Math.abs(z) +
+              Math.abs(h.getQuick(m + 1, m + 1))))) {
+            break;
+          }
+          m--;
+        }
+
+        for (int i = m + 2; i <= n; i++) {
+          h.setQuick(i, i - 2, 0.0);
+          if (i > m + 2) {
+            h.setQuick(i, i - 3, 0.0);
+          }
+        }
+
+        // Double QR step involving rows l:n and columns m:n
+
+        for (int k = m; k <= n - 1; k++) {
+          boolean notlast = (k != n - 1);
+          if (k != m) {
+            p = h.getQuick(k, k - 1);
+            q = h.getQuick(k + 1, k - 1);
+            r = (notlast ? h.getQuick(k + 2, k - 1) : 0.0);
+            x = Math.abs(p) + Math.abs(q) + Math.abs(r);
+            if (x != 0.0) {
+              p = p / x;
+              q = q / x;
+              r = r / x;
+            }
+          }
+          if (x == 0.0) {
+            break;
+          }
+          s = Math.sqrt(p * p + q * q + r * r);
+          if (p < 0) {
+            s = -s;
+          }
+          if (s != 0) {
+            if (k != m) {
+              h.setQuick(k, k - 1, -s * x);
+            } else if (l != m) {
+              h.setQuick(k, k - 1, -h.getQuick(k, k - 1));
+            }
+            p = p + s;
+            x = p / s;
+            y = q / s;
+            z = r / s;
+            q = q / p;
+            r = r / p;
+
+            // Row modification
+
+            for (int j = k; j < nn; j++) {
+              p = h.getQuick(k, j) + q * h.getQuick(k + 1, j);
+              if (notlast) {
+                p = p + r * h.getQuick(k + 2, j);
+                h.setQuick(k + 2, j, h.getQuick(k + 2, j) - p * z);
+              }
+              h.setQuick(k, j, h.getQuick(k, j) - p * x);
+              h.setQuick(k + 1, j, h.getQuick(k + 1, j) - p * y);
+            }
+
+            // Column modification
+
+            for (int i = 0; i <= Math.min(n, k + 3); i++) {
+              p = x * h.getQuick(i, k) + y * h.getQuick(i, k + 1);
+              if (notlast) {
+                p = p + z * h.getQuick(i, k + 2);
+                h.setQuick(i, k + 2, h.getQuick(i, k + 2) - p * r);
+              }
+              h.setQuick(i, k, h.getQuick(i, k) - p);
+              h.setQuick(i, k + 1, h.getQuick(i, k + 1) - p * q);
+            }
+
+            // Accumulate transformations
+
+            for (int i = low; i <= high; i++) {
+              p = x * v.getQuick(i, k) + y * v.getQuick(i, k + 1);
+              if (notlast) {
+                p = p + z * v.getQuick(i, k + 2);
+                v.setQuick(i, k + 2, v.getQuick(i, k + 2) - p * r);
+              }
+              v.setQuick(i, k, v.getQuick(i, k) - p);
+              v.setQuick(i, k + 1, v.getQuick(i, k + 1) - p * q);
+            }
+          }  // (s != 0)
+        }  // k loop
+      }  // check convergence
+    }  // while (n >= low)
+
+    // Backsubstitute to find vectors of upper triangular form
+
+    if (norm == 0.0) {
+      return;
+    }
+
+    for (n = nn - 1; n >= 0; n--) {
+      p = d.getQuick(n);
+      q = e.getQuick(n);
+
+      // Real vector
+
+      if (q == 0) {
+        int l = n;
+        h.setQuick(n, n, 1.0);
+        for (int i = n - 1; i >= 0; i--) {
+          w = h.getQuick(i, i) - p;
+          r = 0.0;
+          for (int j = l; j <= n; j++) {
+            r = r + h.getQuick(i, j) * h.getQuick(j, n);
+          }
+          if (e.getQuick(i) < 0.0) {
+            z = w;
+            s = r;
+          } else {
+            l = i;
+            if (e.getQuick(i) == 0.0) {
+              if (w != 0.0) {
+                h.setQuick(i, n, -r / w);
+              } else {
+                h.setQuick(i, n, -r / (eps * norm));
+              }
+
+              // Solve real equations
+
+            } else {
+              x = h.getQuick(i, i + 1);
+              y = h.getQuick(i + 1, i);
+              q = (d.getQuick(i) - p) * (d.getQuick(i) - p) + e.getQuick(i) * e.getQuick(i);
+              t = (x * s - z * r) / q;
+              h.setQuick(i, n, t);
+              if (Math.abs(x) > Math.abs(z)) {
+                h.setQuick(i + 1, n, (-r - w * t) / x);
+              } else {
+                h.setQuick(i + 1, n, (-s - y * t) / z);
+              }
+            }
+
+            // Overflow control
+
+            t = Math.abs(h.getQuick(i, n));
+            if ((eps * t) * t > 1) {
+              for (int j = i; j <= n; j++) {
+                h.setQuick(j, n, h.getQuick(j, n) / t);
+              }
+            }
+          }
+        }
+
+        // Complex vector
+
+      } else if (q < 0) {
+        int l = n - 1;
+
+        // Last vector component imaginary so matrix is triangular
+
+        if (Math.abs(h.getQuick(n, n - 1)) > Math.abs(h.getQuick(n - 1, n))) {
+          h.setQuick(n - 1, n - 1, q / h.getQuick(n, n - 1));
+          h.setQuick(n - 1, n, -(h.getQuick(n, n) - p) / h.getQuick(n, n - 1));
+        } else {
+          cdiv(0.0, -h.getQuick(n - 1, n), h.getQuick(n - 1, n - 1) - p, q);
+          h.setQuick(n - 1, n - 1, cdivr);
+          h.setQuick(n - 1, n, cdivi);
+        }
+        h.setQuick(n, n - 1, 0.0);
+        h.setQuick(n, n, 1.0);
+        for (int i = n - 2; i >= 0; i--) {
+          double ra, sa, vr, vi;
+          ra = 0.0;
+          sa = 0.0;
+          for (int j = l; j <= n; j++) {
+            ra = ra + h.getQuick(i, j) * h.getQuick(j, n - 1);
+            sa = sa + h.getQuick(i, j) * h.getQuick(j, n);
+          }
+          w = h.getQuick(i, i) - p;
+
+          if (e.getQuick(i) < 0.0) {
+            z = w;
+            r = ra;
+            s = sa;
+          } else {
+            l = i;
+            if (e.getQuick(i) == 0) {
+              cdiv(-ra, -sa, w, q);
+              h.setQuick(i, n - 1, cdivr);
+              h.setQuick(i, n, cdivi);
+            } else {
+
+              // Solve complex equations
+
+              x = h.getQuick(i, i + 1);
+              y = h.getQuick(i + 1, i);
+              vr = (d.getQuick(i) - p) * (d.getQuick(i) - p) + e.getQuick(i) * e.getQuick(i) - q * q;
+              vi = (d.getQuick(i) - p) * 2.0 * q;
+              if (vr == 0.0 & vi == 0.0) {
+                vr = eps * norm * (Math.abs(w) + Math.abs(q) +
+                  Math.abs(x) + Math.abs(y) + Math.abs(z));
+              }
+              cdiv(x * r - z * ra + q * sa, x * s - z * sa - q * ra, vr, vi);
+              h.setQuick(i, n - 1, cdivr);
+              h.setQuick(i, n, cdivi);
+              if (Math.abs(x) > (Math.abs(z) + Math.abs(q))) {
+                h.setQuick(i + 1, n - 1, (-ra - w * h.getQuick(i, n - 1) + q * h.getQuick(i, n)) / x);
+                h.setQuick(i + 1, n, (-sa - w * h.getQuick(i, n) - q * h.getQuick(i, n - 1)) / x);
+              } else {
+                cdiv(-r - y * h.getQuick(i, n - 1), -s - y * h.getQuick(i, n), z, q);
+                h.setQuick(i + 1, n - 1, cdivr);
+                h.setQuick(i + 1, n, cdivi);
+              }
+            }
+
+            // Overflow control
+
+            t = Math.max(Math.abs(h.getQuick(i, n - 1)), Math.abs(h.getQuick(i, n)));
+            if ((eps * t) * t > 1) {
+              for (int j = i; j <= n; j++) {
+                h.setQuick(j, n - 1, h.getQuick(j, n - 1) / t);
+                h.setQuick(j, n, h.getQuick(j, n) / t);
+              }
+            }
+          }
+        }
+      }
+    }
+
+    // Vectors of isolated roots
+
+    for (int i = 0; i < nn; i++) {
+      if (i < low | i > high) {
+        for (int j = i; j < nn; j++) {
+          v.setQuick(i, j, h.getQuick(i, j));
+        }
+      }
+    }
+
+    // Back transformation to get eigenvectors of original matrix
+
+    for (int j = nn - 1; j >= low; j--) {
+      for (int i = low; i <= high; i++) {
+        z = 0.0;
+        for (int k = low; k <= Math.min(j, high); k++) {
+          z = z + v.getQuick(i, k) * h.getQuick(k, j);
+        }
+        v.setQuick(i, j, z);
+      }
+    }
+  }
+
+
+
+  private static boolean isSymmetric(Matrix a) {
+    /*
+    Symmetry flag.
+    */
+    int n = a.columnSize();
+
+    boolean isSymmetric = true;
+    for (int j = 0; (j < n) & isSymmetric; j++) {
+      for (int i = 0; (i < n) & isSymmetric; i++) {
+        isSymmetric = (a.getQuick(i, j) == a.getQuick(j, i));
+      }
+    }
+    return isSymmetric;
+  }
+}

Modified: mahout/trunk/math/src/test/java/org/apache/mahout/math/decomposer/lanczos/TestLanczosSolver.java
URL: http://svn.apache.org/viewvc/mahout/trunk/math/src/test/java/org/apache/mahout/math/decomposer/lanczos/TestLanczosSolver.java?rev=1333667&r1=1333666&r2=1333667&view=diff
==============================================================================
--- mahout/trunk/math/src/test/java/org/apache/mahout/math/decomposer/lanczos/TestLanczosSolver.java (original)
+++ mahout/trunk/math/src/test/java/org/apache/mahout/math/decomposer/lanczos/TestLanczosSolver.java Thu May  3 22:36:49 2012
@@ -21,8 +21,7 @@ import org.apache.mahout.math.DenseVecto
 import org.apache.mahout.math.Matrix;
 import org.apache.mahout.math.Vector;
 import org.apache.mahout.math.decomposer.SolverTest;
-import org.apache.mahout.math.matrix.DoubleMatrix1D;
-import org.apache.mahout.math.matrix.linalg.EigenvalueDecomposition;
+import org.apache.mahout.math.solver.EigenDecomposition;
 import org.junit.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -45,8 +44,8 @@ public final class TestLanczosSolver ext
     // set initial vector?
     solver.solve(state, desiredRank, true);
 
-    EigenvalueDecomposition decomposition = new EigenvalueDecomposition(m);
-    DoubleMatrix1D eigenvalues = decomposition.getRealEigenvalues();
+    EigenDecomposition decomposition = new EigenDecomposition(m);
+    Vector eigenvalues = decomposition.getRealEigenvalues();
 
     float fractionOfEigensExpectedGood = 0.6f;
     for(int i = 0; i < fractionOfEigensExpectedGood * desiredRank; i++) {
@@ -55,7 +54,7 @@ public final class TestLanczosSolver ext
       log.info("{} : L = {}, E = {}", new Object[] {i, s, e});
       assertTrue("Singular value differs from eigenvalue", Math.abs((s-e)/e) < ERROR_TOLERANCE);
       Vector v = state.getRightSingularVector(i);
-      Vector v2 = decomposition.getV().viewColumn(eigenvalues.size() - i - 1).toVector();
+      Vector v2 = decomposition.getV().viewColumn(eigenvalues.size() - i - 1);
       double error = 1 - Math.abs(v.dot(v2)/(v.norm(2) * v2.norm(2)));
       log.info("error: {}", error);
       assertTrue(i + ": 1 - cosAngle = " + error, error < ERROR_TOLERANCE);

Added: mahout/trunk/math/src/test/java/org/apache/mahout/math/solver/EigenDecompositionTest.java
URL: http://svn.apache.org/viewvc/mahout/trunk/math/src/test/java/org/apache/mahout/math/solver/EigenDecompositionTest.java?rev=1333667&view=auto
==============================================================================
--- mahout/trunk/math/src/test/java/org/apache/mahout/math/solver/EigenDecompositionTest.java (added)
+++ mahout/trunk/math/src/test/java/org/apache/mahout/math/solver/EigenDecompositionTest.java Thu May  3 22:36:49 2012
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.mahout.math.solver;
+
+import org.apache.mahout.common.RandomUtils;
+import org.apache.mahout.math.DenseMatrix;
+import org.apache.mahout.math.Matrix;
+import org.apache.mahout.math.MatrixSlice;
+import org.apache.mahout.math.Vector;
+import org.apache.mahout.math.function.DoubleFunction;
+import org.apache.mahout.math.function.Functions;
+import org.junit.Test;
+
+import java.util.Random;
+
+import static org.junit.Assert.assertEquals;
+
+public class EigenDecompositionTest {
+  @Test
+  public void testDeficientRank() {
+    Matrix a = new DenseMatrix(10, 3).assign(new DoubleFunction() {
+      Random gen = RandomUtils.getRandom();
+
+      @Override
+      public double apply(double arg1) {
+        return gen.nextGaussian();
+      }
+    });
+
+    a = a.transpose().times(a);
+
+    EigenDecomposition eig = new EigenDecomposition(a);
+    Matrix d = eig.getD();
+    Matrix v = eig.getV();
+    check("EigenvalueDecomposition (rank deficient)...", a.times(v), v.times(d));
+
+    assertEquals(0, eig.getImagEigenvalues().norm(1), 1e-10);
+    assertEquals(3, eig.getRealEigenvalues().norm(0), 1e-10);
+  }
+
+  @Test
+  public void testEigen() {
+    double[] evals =
+      {0., 1., 0., 0.,
+        1., 0., 2.e-7, 0.,
+        0., -2.e-7, 0., 1.,
+        0., 0., 1., 0.};
+    int i = 0;
+    Matrix a = new DenseMatrix(4, 4);
+    for (MatrixSlice row : a) {
+      for (Vector.Element element : row.vector()) {
+        element.set(evals[i++]);
+      }
+    }
+    EigenDecomposition eig = new EigenDecomposition(a);
+    Matrix d = eig.getD();
+    Matrix v = eig.getV();
+    check("EigenvalueDecomposition (nonsymmetric)...", a.times(v), v.times(d));
+  }
+
+  @Test
+  public void testSequential() {
+    int validld = 3;
+    Matrix A = new DenseMatrix(validld, validld);
+    double[] columnwise = {1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12.};
+    int i = 0;
+    for (MatrixSlice row : A) {
+      for (Vector.Element element : row.vector()) {
+        element.set(columnwise[i++]);
+      }
+    }
+
+    EigenDecomposition Eig = new EigenDecomposition(A);
+    Matrix D = Eig.getD();
+    Matrix V = Eig.getV();
+    check("EigenvalueDecomposition (nonsymmetric)...", A.times(V), V.times(D));
+
+    A = A.transpose().times(A);
+    Eig = new EigenDecomposition(A);
+    D = Eig.getD();
+    V = Eig.getV();
+    check("EigenvalueDecomposition (symmetric)...", A.times(V), V.times(D));
+
+  }
+
+  private void check(String msg, Matrix a, Matrix b) {
+    assertEquals(msg, 0, a.minus(b).aggregate(Functions.PLUS, Functions.ABS), 1e-10);
+  }
+
+}