You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by lu...@apache.org on 2009/12/31 18:52:16 UTC

svn commit: r894908 - in /commons/proper/math/trunk/src: main/java/org/apache/commons/math/linear/SingularValueDecompositionImpl.java site/xdoc/changes.xml test/java/org/apache/commons/math/linear/SingularValueSolverTest.java

Author: luc
Date: Thu Dec 31 17:52:16 2009
New Revision: 894908

URL: http://svn.apache.org/viewvc?rev=894908&view=rev
Log:
Singular Value Decomposition now computes either the compact SVD (using only positive singular values) or truncated SVD (using a user-specified maximal number of singular values).
Fixed Singular Value Decomposition solving of singular systems.
JIRA: MATH-320, MATH-321

Modified:
    commons/proper/math/trunk/src/main/java/org/apache/commons/math/linear/SingularValueDecompositionImpl.java
    commons/proper/math/trunk/src/site/xdoc/changes.xml
    commons/proper/math/trunk/src/test/java/org/apache/commons/math/linear/SingularValueSolverTest.java

Modified: commons/proper/math/trunk/src/main/java/org/apache/commons/math/linear/SingularValueDecompositionImpl.java
URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/main/java/org/apache/commons/math/linear/SingularValueDecompositionImpl.java?rev=894908&r1=894907&r2=894908&view=diff
==============================================================================
--- commons/proper/math/trunk/src/main/java/org/apache/commons/math/linear/SingularValueDecompositionImpl.java (original)
+++ commons/proper/math/trunk/src/main/java/org/apache/commons/math/linear/SingularValueDecompositionImpl.java Thu Dec 31 17:52:16 2009
@@ -159,27 +159,28 @@
             if (m >= n) {
                 // the tridiagonal matrix is Bt.B, where B is upper bidiagonal
                 final RealMatrix e =
-                    eigenDecomposition.getV().getSubMatrix(0, p - 1, 0, p - 1);
+                    eigenDecomposition.getV().getSubMatrix(0, n - 1, 0, p - 1);
                 final double[][] eData = e.getData();
                 final double[][] wData = new double[m][p];
                 double[] ei1 = eData[0];
-                for (int i = 0; i < p - 1; ++i) {
+                for (int i = 0; i < p; ++i) {
                     // compute W = B.E.S^(-1) where E is the eigenvectors matrix
                     final double mi = mainBidiagonal[i];
-                    final double si = secondaryBidiagonal[i];
                     final double[] ei0 = ei1;
                     final double[] wi  = wData[i];
-                    ei1 = eData[i + 1];
-                    for (int j = 0; j < p; ++j) {
-                        wi[j] = (mi * ei0[j] + si * ei1[j]) / singularValues[j];
+                    if (i < n - 1) {
+                        ei1 = eData[i + 1];
+                        final double si = secondaryBidiagonal[i];
+                        for (int j = 0; j < p; ++j) {
+                            wi[j] = (mi * ei0[j] + si * ei1[j]) / singularValues[j];
+                        }
+                    } else {
+                        for (int j = 0; j < p; ++j) {
+                            wi[j] = mi * ei0[j] / singularValues[j];
+                        }
                     }
                 }
-                // last row
-                final double lastMain = mainBidiagonal[p - 1];
-                final double[] wr1  = wData[p - 1];
-                for (int j = 0; j < p; ++j) {
-                    wr1[j] = ei1[j] * lastMain / singularValues[j];
-                }
+
                 for (int i = p; i < m; ++i) {
                     wData[i] = new double[p];
                 }
@@ -247,26 +248,26 @@
                 // the tridiagonal matrix is B.Bt, where B is lower bidiagonal
                 // compute W = Bt.E.S^(-1) where E is the eigenvectors matrix
                 final RealMatrix e =
-                    eigenDecomposition.getV().getSubMatrix(0, p - 1, 0, p - 1);
+                    eigenDecomposition.getV().getSubMatrix(0, m - 1, 0, p - 1);
                 final double[][] eData = e.getData();
                 final double[][] wData = new double[n][p];
                 double[] ei1 = eData[0];
-                for (int i = 0; i < p - 1; ++i) {
+                for (int i = 0; i < p; ++i) {
                     final double mi = mainBidiagonal[i];
-                    final double si = secondaryBidiagonal[i];
                     final double[] ei0 = ei1;
                     final double[] wi  = wData[i];
-                    ei1 = eData[i + 1];
-                    for (int j = 0; j < p; ++j) {
-                        wi[j] = (mi * ei0[j] + si * ei1[j]) / singularValues[j];
+                    if (i < m - 1) {
+                        ei1 = eData[i + 1];
+                        final double si = secondaryBidiagonal[i];
+                        for (int j = 0; j < p; ++j) {
+                            wi[j] = (mi * ei0[j] + si * ei1[j]) / singularValues[j];
+                        }
+                    } else {
+                        for (int j = 0; j < p; ++j) {
+                            wi[j] = mi * ei0[j] / singularValues[j];
+                        }
                     }
                 }
-                // last row
-                final double lastMain = mainBidiagonal[p - 1];
-                final double[] wr1  = wData[p - 1];
-                for (int j = 0; j < p; ++j) {
-                    wr1[j] = ei1[j] * lastMain / singularValues[j];
-                }
                 for (int i = p; i < n; ++i) {
                     wData[i] = new double[p];
                 }

Modified: commons/proper/math/trunk/src/site/xdoc/changes.xml
URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/site/xdoc/changes.xml?rev=894908&r1=894907&r2=894908&view=diff
==============================================================================
--- commons/proper/math/trunk/src/site/xdoc/changes.xml (original)
+++ commons/proper/math/trunk/src/site/xdoc/changes.xml Thu Dec 31 17:52:16 2009
@@ -39,10 +39,18 @@
   </properties>
   <body>
     <release version="2.1" date="TBD" description="TBD">
+      <action dev="luc" type="add" issue="MATH-321" >
+        Singular Value Decomposition now computes either the compact SVD (using only
+        positive singular values) or truncated SVD (using a user-specified maximal
+        number of singular values).
+      </action>
+      <action dev="luc" type="fix" issue="MATH-320" >
+        Fixed Singular Value Decomposition solving of singular systems.
+      </action>
       <action dev="psteitz" type="update" issue="MATH-239" due-to="Christian Semrau">
         Added MathUtils methods to compute gcd and lcm for long arguments.
-      </actions>
-      <action dev="psteitz" tyoe="update" issue="MATH-287" due-to="Matthew Rowles">
+      </action>
+      <action dev="psteitz" type="update" issue="MATH-287" due-to="Matthew Rowles">
         Added support for weighted univariate statistics.
       </action>
       <action dev="luc" type="fix" issue="MATH-326" due-to="Jake Mannix">

Modified: commons/proper/math/trunk/src/test/java/org/apache/commons/math/linear/SingularValueSolverTest.java
URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/test/java/org/apache/commons/math/linear/SingularValueSolverTest.java?rev=894908&r1=894907&r2=894908&view=diff
==============================================================================
--- commons/proper/math/trunk/src/test/java/org/apache/commons/math/linear/SingularValueSolverTest.java (original)
+++ commons/proper/math/trunk/src/test/java/org/apache/commons/math/linear/SingularValueSolverTest.java Thu Dec 31 17:52:16 2009
@@ -139,6 +139,32 @@
     }
 
     @Test
+    public void testTruncated() {
+
+        RealMatrix rm = new Array2DRowRealMatrix(new double[][] {
+            { 1.0, 2.0, 3.0 }, { 2.0, 3.0, 4.0 }, { 3.0, 5.0, 7.0 }
+        });
+        double s439  = Math.sqrt(439.0);
+        double[] reference = new double[] {
+            Math.sqrt(3.0 * (21.0 + s439))
+        };
+        SingularValueDecomposition svd =
+            new SingularValueDecompositionImpl(rm, 1);
+
+        // check we get the expected theoretical singular values
+        double[] singularValues = svd.getSingularValues();
+        Assert.assertEquals(reference.length, singularValues.length);
+        for (int i = 0; i < reference.length; ++i) {
+            Assert.assertEquals(reference[i], singularValues[i], 4.0e-13);
+        }
+
+        // check the truncated decomposition DON'T allows to recover the original matrix
+        RealMatrix recomposed = svd.getU().multiply(svd.getS()).multiply(svd.getVT());
+        Assert.assertTrue(recomposed.subtract(rm).getNorm() > 1.4);
+
+    }
+
+    @Test
     public void testMath320A() {
         RealMatrix rm = new Array2DRowRealMatrix(new double[][] {
             { 1.0, 2.0, 3.0 }, { 2.0, 3.0, 4.0 }, { 3.0, 5.0, 7.0 }
@@ -149,44 +175,38 @@
         };
         SingularValueDecomposition svd =
             new SingularValueDecompositionImpl(rm);
+
+        // check we get the expected theoretical singular values
         double[] singularValues = svd.getSingularValues();
+        Assert.assertEquals(reference.length, singularValues.length);
         for (int i = 0; i < reference.length; ++i) {
             Assert.assertEquals(reference[i], singularValues[i], 4.0e-13);
         }
-        regularElements(svd.getU());
-        regularElements(svd.getVT());
-//        double[] b = new double[] { 5.0, 6.0, 7.0 };
-//        double[] resSVD = svd.getSolver().solve(b);
-//        Assert.assertEquals(rm.getColumnDimension(), resSVD.length);
-//        System.out.println("resSVD = " + resSVD[0] + " " + resSVD[1] + " " + resSVD[2]);
-//        double minResidual = Double.POSITIVE_INFINITY;
-//        double d0Min = Double.NaN;
-//        double d1Min = Double.NaN;
-//        double d2Min = Double.NaN;
-//        double h = 0.01;
-//        int    k = 100;
-//        for (double d0 = -k * h; d0 <= k * h; d0 += h) {
-//            for (double d1 = -k * h ; d1 <= k * h; d1 += h) {
-//                for (double d2 = -k * h; d2 <= k * h; d2 += h) {
-//                    double[] f = rm.operate(new double[] { resSVD[0] + d0, resSVD[1] + d1, resSVD[2] + d2 });
-//                    double residual = Math.sqrt((f[0] - b[0]) * (f[0] - b[0]) +
-//                                                (f[1] - b[1]) * (f[1] - b[1]) +
-//                                                (f[2] - b[2]) * (f[2] - b[2]));
-//                    if (residual < minResidual) {
-//                        d0Min = d0;
-//                        d1Min = d1;
-//                        d2Min = d2;
-//                        minResidual = residual;
-//                    }
-//                }
-//            }
-//        }
-//        System.out.println(d0Min + " " + d1Min + " " + d2Min + " -> " + minResidual);
-//        Assert.assertEquals(0, d0Min, 1.0e-15);
-//        Assert.assertEquals(0, d1Min, 1.0e-15);
-//        Assert.assertEquals(0, d2Min, 1.0e-15);
-    }
 
+        // check the decomposition allows to recover the original matrix
+        RealMatrix recomposed = svd.getU().multiply(svd.getS()).multiply(svd.getVT());
+        Assert.assertEquals(0.0, recomposed.subtract(rm).getNorm(), 5.0e-13);
+
+        // check we can solve a singular system
+        double[] b = new double[] { 5.0, 6.0, 7.0 };
+        double[] resSVD = svd.getSolver().solve(b);
+        Assert.assertEquals(rm.getColumnDimension(), resSVD.length);
+
+        // check the solution really minimizes the residuals
+        double svdMinResidual = residual(rm, resSVD, b);
+        double epsilon = 2 * Math.ulp(svdMinResidual);
+        double h = 0.1;
+        int    k = 3;
+        for (double d0 = -k * h; d0 <= k * h; d0 += h) {
+            for (double d1 = -k * h ; d1 <= k * h; d1 += h) {
+                for (double d2 = -k * h; d2 <= k * h; d2 += h) {
+                    double[] x = new double[] { resSVD[0] + d0, resSVD[1] + d1, resSVD[2] + d2 };
+                    Assert.assertTrue((residual(rm, x, b) - svdMinResidual) > -epsilon);
+                }
+            }
+        }
+
+    }
 
     @Test
     public void testMath320B() {
@@ -195,18 +215,17 @@
         });
         SingularValueDecomposition svd =
             new SingularValueDecompositionImpl(rm);
-        regularElements(svd.getU());
-        regularElements(svd.getVT());
+        RealMatrix recomposed = svd.getU().multiply(svd.getS()).multiply(svd.getVT());
+        Assert.assertEquals(0.0, recomposed.subtract(rm).getNorm(), 2.0e-15);
     }
 
-    private void regularElements(RealMatrix m) {
-        for (int i = 0; i < m.getRowDimension(); ++i) {
-            for (int j = 0; j < m.getColumnDimension(); ++j) {
-                double mij = m.getEntry(i, j);
-                Assert.assertFalse(Double.isInfinite(mij));
-                Assert.assertFalse(Double.isNaN(mij));
-            }
+    private double residual(RealMatrix a, double[] x, double[] b) {
+        double[] ax = a.operate(x);
+        double sum = 0;
+        for (int i = 0; i < ax.length; ++i) {
+            sum += (ax[i] - b[i]) * (ax[i] - b[i]);
         }
+        return Math.sqrt(sum);
     }
 
 }