JAL-2403 JAL-1483 push 'reverseRange' inside Matrix
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Fri, 24 Feb 2017 10:08:03 +0000 (10:08 +0000)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Fri, 24 Feb 2017 10:08:03 +0000 (10:08 +0000)
src/jalview/analysis/NJTree.java
src/jalview/analysis/PCA.java
src/jalview/math/Matrix.java
src/jalview/math/MatrixI.java
test/jalview/math/MatrixTest.java

index 5f268ae..db2efba 100644 (file)
@@ -285,8 +285,7 @@ public class NJTree
        */
       MatrixI result = ((SimilarityScoreModelI) sm)
               .findSimilarities(seqData);
-      double maxScore = result.getMaxValue();
-      result.subtractAllFrom(maxScore);
+      result.reverseRange(true);
       distance = result;
     }
 
@@ -793,8 +792,7 @@ public class NJTree
        */
       result = ((SimilarityScoreModelI) scoreModel)
               .findSimilarities(seqData);
-      double maxScore = result.getMaxValue();
-      result.subtractAllFrom(maxScore);
+      result.reverseRange(true);
     }
     else
     {
index 738da7d..62e6895 100755 (executable)
@@ -235,8 +235,7 @@ public class PCA implements Runnable
     else if (scoreModel instanceof DistanceScoreModelI)
     {
       result = ((DistanceScoreModelI) scoreModel).findDistances(av);
-      double maxDistance = result.getMaxValue();
-      result.subtractAllFrom(maxDistance);
+      result.reverseRange(true);
     }
     else
     {
index 3f0bae4..aaeb8da 100755 (executable)
@@ -891,37 +891,58 @@ public class Matrix implements MatrixI
     return row;
   }
 
-  @Override
-  public double getMaxValue()
+  /**
+   * Returns a length 2 array of {minValue, maxValue} of all values in the
+   * matrix. Returns null if the matrix is null or empty.
+   * 
+   * @return
+   */
+  double[] findMinMax()
   {
     if (value == null)
     {
-      return 0;
+      return null;
     }
+    double min = Double.MAX_VALUE;
     double max = -Double.MAX_VALUE;
+    boolean empty = true;
     for (double[] row : value)
     {
       if (row != null)
       {
         for (double x : row)
         {
+          empty = false;
           if (x > max)
           {
             max = x;
           }
+          if (x < min)
+          {
+            min = x;
+          }
         }
       }
     }
-    return max;
+    return empty ? null : new double[] { min, max };
   }
 
+  /**
+   * {@inheritDoc}
+   */
   @Override
-  public void subtractAllFrom(double val)
+  public void reverseRange(boolean maxToZero)
   {
     if (value == null)
     {
       return;
     }
+    double[] minMax = findMinMax();
+    if (minMax == null)
+    {
+      return; // empty matrix
+    }
+    double subtractFrom = maxToZero ? minMax[1] : minMax[0] + minMax[1];
 
     for (double[] row : value)
     {
@@ -930,7 +951,7 @@ public class Matrix implements MatrixI
         int j = 0;
         for (double x : row)
         {
-          row[j] = val - x;
+          row[j] = subtractFrom - x;
           j++;
         }
       }
index 05fe22f..d7a1b70 100644 (file)
@@ -65,12 +65,26 @@ public interface MatrixI
 
   void tred();
 
-  double getMaxValue();
-
   /**
-   * Update each value in the matrix by subtracting it from the given value
+   * Reverses the range of the matrix values, so that the smallest values become
+   * the largest, and the largest become the smallest. This operation supports
+   * using a distance measure as a similarity measure, or vice versa.
+   * <p>
+   * If parameter <code>maxToZero</code> is true, then the maximum value becomes
+   * zero, i.e. all values are subtracted from the maximum. This is consistent
+   * with converting a similarity score to a distance score - the most similar
+   * (identity) corresponds to zero distance. However note that the operation is
+   * not reversible (unless the original minimum value is zero). For example a
+   * range of 10-40 would become 30-0, which would reverse a second time to
+   * 0-30. Also note that a similarity measure (such as BLOSUM) may give
+   * different identity scores for different sequences, so they cannot all
+   * convert to zero distance.
+   * <p>
+   * If parameter <code>maxToZero</code> is false, then the values are reflected
+   * about the average of {min, max} (effectively swapping min and max). This
+   * operation <em>is</em> reversible.
    * 
-   * @param val
+   * @param maxToZero
    */
-  void subtractAllFrom(double val);
+  void reverseRange(boolean maxToZero);
 }
index 0d066dd..61b98f3 100644 (file)
@@ -1,6 +1,7 @@
 package jalview.math;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
 import static org.testng.Assert.fail;
 
@@ -12,7 +13,7 @@ import org.testng.internal.junit.ArrayAsserts;
 
 public class MatrixTest
 {
-  final static double DELTA = 0.0001d;
+  final static double DELTA = 0.000001d;
 
   @Test(groups = "Timing")
   public void testPreMultiply_timing()
@@ -380,54 +381,125 @@ public class MatrixTest
   }
 
   @Test(groups = "Functional")
-  public void testGetMaxValue() {
+  public void testFindMinMax()
+  {
+    /*
+     * empty matrix case
+     */
+    Matrix m = new Matrix(new double[][] { {} });
+    assertNull(m.findMinMax());
+
+    /*
+     * normal case
+     */
     double[][] vals = new double[2][];
     vals[0] = new double[] {7d, 1d, -2.3d};
     vals[1] = new double[] {-12d, 94.3d, -102.34d};
-    MatrixI m = new Matrix(vals);
-    assertEquals(m.getMaxValue(), 94.3d);
+    m = new Matrix(vals);
+    double[] minMax = m.findMinMax();
+    assertEquals(minMax[0], -102.34d);
+    assertEquals(minMax[1], 94.3d);
   }
 
   @Test(groups = { "Functional", "Timing" })
-  public void testGetMaxValue_timing()
+  public void testFindMinMax_timing()
   {
     Random r = new Random();
     int size = 1000; // increase to stress test timing
     double[][] vals = new double[size][size];
     double max = -Double.MAX_VALUE;
+    double min = Double.MAX_VALUE;
     for (int i = 0; i < size; i++)
     {
       vals[i] = new double[size];
       for (int j = 0; j < size; j++)
       {
-        double d = r.nextDouble();
+        // use nextLong rather than nextDouble to include negative values
+        double d = r.nextLong();
         if (d > max)
         {
           max = d;
         }
+        if (d < min)
+        {
+          min = d;
+        }
         vals[i][j] = d;
       }
-      i++;
     }
-    MatrixI m = new Matrix(vals);
+    Matrix m = new Matrix(vals);
     long now = System.currentTimeMillis();
-    double theMax = m.getMaxValue();
-    System.out.println(String.format("getMaxValue for %d x %d took %dms",
+    double[] minMax = m.findMinMax();
+    System.out.println(String.format("findMinMax for %d x %d took %dms",
             size, size, (System.currentTimeMillis() - now)));
-    assertEquals(theMax, max);
+    assertEquals(minMax[0], min);
+    assertEquals(minMax[1], max);
+  }
+
+  /**
+   * Test range reversal with maximum value becoming zero
+   */
+  @Test(groups = "Functional")
+  public void testReverseRange_maxToZero()
+  {
+    Matrix m1 = new Matrix(
+            new double[][] { { 2, 3.5, 4 }, { -3.4, 4, 15 } });
+
+    /*
+     * subtract all from max: range -3.4 to 15 becomes 18.4 to 0
+     */
+    m1.reverseRange(true);
+    assertEquals(m1.getValue(0, 0), 13d, DELTA);
+    assertEquals(m1.getValue(0, 1), 11.5d, DELTA);
+    assertEquals(m1.getValue(0, 2), 11d, DELTA);
+    assertEquals(m1.getValue(1, 0), 18.4d, DELTA);
+    assertEquals(m1.getValue(1, 1), 11d, DELTA);
+    assertEquals(m1.getValue(1, 2), 0d, DELTA);
+
+    /*
+     * repeat operation - range is now 0 to 18.4
+     */
+    m1.reverseRange(true);
+    assertEquals(m1.getValue(0, 0), 5.4d, DELTA);
+    assertEquals(m1.getValue(0, 1), 6.9d, DELTA);
+    assertEquals(m1.getValue(0, 2), 7.4d, DELTA);
+    assertEquals(m1.getValue(1, 0), 0d, DELTA);
+    assertEquals(m1.getValue(1, 1), 7.4d, DELTA);
+    assertEquals(m1.getValue(1, 2), 18.4d, DELTA);
   }
 
+  /**
+   * Test range reversal with minimum and maximum values swapped
+   */
   @Test(groups = "Functional")
-  public void testSubtractAllFrom()
+  public void testReverseRange_swapMinMax()
   {
-    Matrix m1 = new Matrix(new double[][] { { 2, 3, 4 }, { -3, 4, 15 } });
-    m1.subtractAllFrom(12.5);
-    assertEquals(m1.getValue(0, 0), 10.5d);
-    assertEquals(m1.getValue(0, 1), 9.5d);
-    assertEquals(m1.getValue(0, 2), 8.5d);
-    assertEquals(m1.getValue(1, 0), 15.5d);
-    assertEquals(m1.getValue(1, 1), 8.5d);
-    assertEquals(m1.getValue(1, 2), -2.5d);
+    Matrix m1 = new Matrix(
+            new double[][] { { 2, 3.5, 4 }, { -3.4, 4, 15 } });
+  
+    /*
+     * swap all values in min-max range
+     * = subtract from (min + max = 11.6) 
+     * range -3.4 to 15 becomes 18.4 to -3.4
+     */
+    m1.reverseRange(false);
+    assertEquals(m1.getValue(0, 0), 9.6d, DELTA);
+    assertEquals(m1.getValue(0, 1), 8.1d, DELTA);
+    assertEquals(m1.getValue(0, 2), 7.6d, DELTA);
+    assertEquals(m1.getValue(1, 0), 15d, DELTA);
+    assertEquals(m1.getValue(1, 1), 7.6d, DELTA);
+    assertEquals(m1.getValue(1, 2), -3.4d, DELTA);
+  
+    /*
+     * repeat operation - original values restored
+     */
+    m1.reverseRange(false);
+    assertEquals(m1.getValue(0, 0), 2d, DELTA);
+    assertEquals(m1.getValue(0, 1), 3.5d, DELTA);
+    assertEquals(m1.getValue(0, 2), 4d, DELTA);
+    assertEquals(m1.getValue(1, 0), -3.4d, DELTA);
+    assertEquals(m1.getValue(1, 1), 4d, DELTA);
+    assertEquals(m1.getValue(1, 2), 15d, DELTA);
   }
 
 }