Merge branch 'feature/JAL-3205symmetricCalc' into develop
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Thu, 7 Mar 2019 09:14:33 +0000 (09:14 +0000)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Thu, 7 Mar 2019 09:14:33 +0000 (09:14 +0000)
src/jalview/analysis/scoremodels/PIDModel.java
src/jalview/analysis/scoremodels/ScoreMatrix.java
src/jalview/math/Matrix.java
src/jalview/math/MatrixI.java
test/jalview/analysis/scoremodels/PIDModelTest.java
test/jalview/analysis/scoremodels/ScoreMatrixTest.java
test/jalview/math/MatrixTest.java

index c1e8b42..ddfe5e4 100644 (file)
@@ -152,15 +152,17 @@ public class PIDModel extends SimilarityScoreModel
   protected MatrixI findSimilarities(String[] seqs,
           SimilarityParamsI options)
   {
-    // TODO reuse code in ScoreMatrix instead somehow
-    double[][] values = new double[seqs.length][];
+    /*
+     * calculation is symmetric so just compute lower diagonal
+     */
+    double[][] values = new double[seqs.length][seqs.length];
     for (int row = 0; row < seqs.length; row++)
     {
-      values[row] = new double[seqs.length];
-      for (int col = 0; col < seqs.length; col++)
+      for (int col = row; col < seqs.length; col++)
       {
         double total = computePID(seqs[row], seqs[col], options);
         values[row][col] = total;
+        values[col][row] = total;
       }
     }
     return new Matrix(values);
index 6cdfacb..b206339 100644 (file)
@@ -99,6 +99,8 @@ public class ScoreMatrix extends SimilarityScoreModel
 
   private float maxValue;
 
+  private boolean symmetric;
+
   /**
    * Constructor given a name, symbol alphabet, and matrix of scores for pairs
    * of symbols. The matrix should be square and of the same size as the
@@ -156,6 +158,8 @@ public class ScoreMatrix extends SimilarityScoreModel
 
     findMinMax();
 
+    symmetric = checkSymmetry();
+
     /*
      * crude heuristic for now...
      */
@@ -163,6 +167,27 @@ public class ScoreMatrix extends SimilarityScoreModel
   }
 
   /**
+   * Answers true if the matrix is symmetric, else false. Usually, substitution
+   * matrices are symmetric, which allows calculations to be short cut.
+   * 
+   * @return
+   */
+  private boolean checkSymmetry()
+  {
+    for (int i = 0; i < matrix.length; i++)
+    {
+      for (int j = i; j < matrix.length; j++)
+      {
+        if (matrix[i][j] != matrix[j][i])
+        {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+  /**
    * Record the minimum and maximum score values
    */
   protected void findMinMax()
@@ -457,14 +482,17 @@ public class ScoreMatrix extends SimilarityScoreModel
   protected MatrixI findSimilarities(String[] seqs,
           SimilarityParamsI params)
   {
-    double[][] values = new double[seqs.length][];
+    double[][] values = new double[seqs.length][seqs.length];
     for (int row = 0; row < seqs.length; row++)
     {
-      values[row] = new double[seqs.length];
-      for (int col = 0; col < seqs.length; col++)
+      for (int col = symmetric ? row : 0; col < seqs.length; col++)
       {
         double total = computeSimilarity(seqs[row], seqs[col], params);
         values[row][col] = total;
+        if (symmetric)
+        {
+          values[col][row] = total;
+        }
       }
     }
     return new Matrix(values);
@@ -592,4 +620,9 @@ public class ScoreMatrix extends SimilarityScoreModel
   {
     return this;
   }
+
+  public boolean isSymmetric()
+  {
+    return symmetric;
+  }
 }
index 77862c8..b22bf4e 100755 (executable)
@@ -984,4 +984,42 @@ public class Matrix implements MatrixI
   {
     e = v;
   }
+
+  public double getTotal()
+  {
+    double d = 0d;
+    for (int i = 0; i < this.height(); i++)
+    {
+      for (int j = 0; j < this.width(); j++)
+      {
+        d += value[i][j];
+      }
+    }
+    return d;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean equals(MatrixI m2, double delta)
+  {
+    if (m2 == null || this.height() != m2.height()
+            || this.width() != m2.width())
+    {
+      return false;
+    }
+    for (int i = 0; i < this.height(); i++)
+    {
+      for (int j = 0; j < this.width(); j++)
+      {
+        double diff = this.getValue(i, j) - m2.getValue(i, j);
+        if (Math.abs(diff) > delta)
+        {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
 }
index d72890a..2c78653 100644 (file)
@@ -159,4 +159,14 @@ public interface MatrixI
    * @param d
    */
   void multiply(double d);
+
+  /**
+   * Answers true if the two matrices have the same dimensions, and corresponding values all differ by no
+   * more than delta (which should be a positive value), else false
+   * 
+   * @param m2
+   * @param delta
+   * @return
+   */
+  boolean equals(MatrixI m2, double delta);
 }
index 212f825..e8ffd2f 100644 (file)
@@ -44,6 +44,8 @@ public class PIDModelTest
     double newScore = PIDModel.computePID(s1, s2, params);
     double oldScore = Comparison.PID(s1, s2);
     assertEquals(newScore, oldScore, DELTA);
+    // and verify PIDModel calculation is symmetric
+    assertEquals(newScore, PIDModel.computePID(s2, s1, params));
 
     /*
      * same length, with gaps
@@ -54,6 +56,7 @@ public class PIDModelTest
     newScore = PIDModel.computePID(s1, s2, params);
     oldScore = Comparison.PID(s1, s2);
     assertEquals(newScore, oldScore, DELTA);
+    assertEquals(newScore, PIDModel.computePID(s2, s1, params));
 
     /*
      * s2 longer than s1, with gaps
@@ -64,6 +67,7 @@ public class PIDModelTest
     newScore = PIDModel.computePID(s1, s2, params);
     oldScore = Comparison.PID(s1, s2);
     assertEquals(newScore, oldScore, DELTA);
+    assertEquals(newScore, PIDModel.computePID(s2, s1, params));
 
     /*
      * s1 longer than s2, with gaps
@@ -74,6 +78,7 @@ public class PIDModelTest
     newScore = PIDModel.computePID(s1, s2, params);
     oldScore = Comparison.PID(s1, s2);
     assertEquals(newScore, oldScore, DELTA);
+    assertEquals(newScore, PIDModel.computePID(s2, s1, params));
 
     /*
      * same but now also with gapped columns
@@ -84,6 +89,7 @@ public class PIDModelTest
     newScore = PIDModel.computePID(s1, s2, params);
     oldScore = Comparison.PID(s1, s2);
     assertEquals(newScore, oldScore, DELTA);
+    assertEquals(newScore, PIDModel.computePID(s2, s1, params));
   }
 
   /**
@@ -102,6 +108,7 @@ public class PIDModelTest
      */
     SimilarityParamsI params = new SimilarityParams(true, true, true, true);
     assertEquals(PIDModel.computePID(s1, s2, params), 80d);
+    assertEquals(PIDModel.computePID(s2, s1, params), 80d);
 
     /*
      * match gap-char but not gap-gap
@@ -109,6 +116,7 @@ public class PIDModelTest
      */
     params = new SimilarityParams(false, true, true, true);
     assertEquals(PIDModel.computePID(s1, s2, params), 75d);
+    assertEquals(PIDModel.computePID(s2, s1, params), 75d);
 
     /*
      * include gaps but don't match them
@@ -117,6 +125,7 @@ public class PIDModelTest
      */
     params = new SimilarityParams(true, false, true, true);
     assertEquals(PIDModel.computePID(s1, s2, params), 40d);
+    assertEquals(PIDModel.computePID(s2, s1, params), 40d);
 
     /*
      * include gaps but don't match them
@@ -125,6 +134,7 @@ public class PIDModelTest
      */
     params = new SimilarityParams(false, false, true, true);
     assertEquals(PIDModel.computePID(s1, s2, params), 25d);
+    assertEquals(PIDModel.computePID(s2, s1, params), 25d);
   }
 
   /**
@@ -144,6 +154,7 @@ public class PIDModelTest
      */
     SimilarityParamsI params = new SimilarityParams(true, true, true, false);
     assertEquals(PIDModel.computePID(s1, s2, params), 500d / 6);
+    assertEquals(PIDModel.computePID(s2, s1, params), 500d / 6);
   
     /*
      * match gap-char but not gap-gap
@@ -151,6 +162,7 @@ public class PIDModelTest
      */
     params = new SimilarityParams(false, true, true, false);
     assertEquals(PIDModel.computePID(s1, s2, params), 80d);
+    assertEquals(PIDModel.computePID(s2, s1, params), 80d);
   
     /*
      * include gaps but don't match them
@@ -159,6 +171,7 @@ public class PIDModelTest
      */
     params = new SimilarityParams(true, false, true, false);
     assertEquals(PIDModel.computePID(s1, s2, params), 100d / 3);
+    assertEquals(PIDModel.computePID(s2, s1, params), 100d / 3);
   
     /*
      * include gaps but don't match them
@@ -167,6 +180,7 @@ public class PIDModelTest
      */
     params = new SimilarityParams(false, false, true, false);
     assertEquals(PIDModel.computePID(s1, s2, params), 20d);
+    assertEquals(PIDModel.computePID(s2, s1, params), 20d);
 
     /*
      * no tests for matchGaps=true, includeGaps=false
index 1a5d43c..15bdce1 100644 (file)
@@ -13,6 +13,7 @@ import jalview.api.analysis.SimilarityParamsI;
 import jalview.io.DataSourceType;
 import jalview.io.FileParse;
 import jalview.io.ScoreMatrixFile;
+import jalview.math.Matrix;
 import jalview.math.MatrixI;
 import jalview.schemes.ResidueProperties;
 
@@ -22,6 +23,8 @@ import java.util.Arrays;
 
 import org.testng.annotations.Test;
 
+import junit.extensions.PA;
+
 public class ScoreMatrixTest
 {
   @Test(groups = "Functional")
@@ -33,6 +36,7 @@ public class ScoreMatrixTest
     scores[1] = new float[] { -4f, 5f, 6f };
     scores[2] = new float[] { 7f, 8f, 9f };
     ScoreMatrix sm = new ScoreMatrix("Test", "ABC".toCharArray(), scores);
+    assertFalse(sm.isSymmetric());
     assertEquals(sm.getSize(), 3);
     assertArrayEquals(scores, sm.getMatrix());
     assertEquals(sm.getPairwiseScore('A', 'a'), 1f);
@@ -50,7 +54,8 @@ public class ScoreMatrixTest
 
   @Test(
     groups = "Functional",
-    expectedExceptions = { IllegalArgumentException.class })
+    expectedExceptions =
+    { IllegalArgumentException.class })
   public void testConstructor_matrixTooSmall()
   {
     float[][] scores = new float[2][];
@@ -61,7 +66,8 @@ public class ScoreMatrixTest
 
   @Test(
     groups = "Functional",
-    expectedExceptions = { IllegalArgumentException.class })
+    expectedExceptions =
+    { IllegalArgumentException.class })
   public void testConstructor_matrixTooBig()
   {
     float[][] scores = new float[2][];
@@ -72,7 +78,8 @@ public class ScoreMatrixTest
 
   @Test(
     groups = "Functional",
-    expectedExceptions = { IllegalArgumentException.class })
+    expectedExceptions =
+    { IllegalArgumentException.class })
   public void testConstructor_matrixNotSquare()
   {
     float[][] scores = new float[2][];
@@ -240,8 +247,8 @@ public class ScoreMatrixTest
    * @throws MalformedURLException
    */
   @Test(groups = "Functional")
-  public void testOutputMatrix_roundTrip() throws MalformedURLException,
-          IOException
+  public void testOutputMatrix_roundTrip()
+          throws MalformedURLException, IOException
   {
     ScoreMatrix sm = ScoreModels.getInstance().getBlosum62();
     String output = sm.outputMatrix(false);
@@ -256,8 +263,8 @@ public class ScoreMatrixTest
   public void testEqualsAndHashCode()
   {
     ScoreMatrix sm = ScoreModels.getInstance().getBlosum62();
-    ScoreMatrix sm2 = new ScoreMatrix(sm.getName(), sm.getSymbols()
-            .toCharArray(), sm.getMatrix());
+    ScoreMatrix sm2 = new ScoreMatrix(sm.getName(),
+            sm.getSymbols().toCharArray(), sm.getMatrix());
     assertTrue(sm.equals(sm2));
     assertEquals(sm.hashCode(), sm2.hashCode());
 
@@ -280,19 +287,20 @@ public class ScoreMatrixTest
     String s1 = "FR-K-S";
     String s2 = "FS--L";
     ScoreMatrix blosum = ScoreModels.getInstance().getBlosum62();
-  
+
     /*
      * score gap-gap and gap-char
      * shorter sequence treated as if with trailing gaps
      * score = F^F + R^S + -^- + K^- + -^L + S^-
      * = 6 + -1 + 1 + -4 + -4 + -4 = -6
      */
-    SimilarityParamsI params = new SimilarityParams(true, true, true, false);
+    SimilarityParamsI params = new SimilarityParams(true, true, true,
+            false);
     assertEquals(blosum.computeSimilarity(s1, s2, params), -6d);
     // matchGap (arg2) is ignored:
     params = new SimilarityParams(true, false, true, false);
     assertEquals(blosum.computeSimilarity(s1, s2, params), -6d);
-  
+
     /*
      * score gap-char but not gap-gap
      * score = F^F + R^S + 0 + K^- + -^L + S^-
@@ -303,7 +311,7 @@ public class ScoreMatrixTest
     // matchGap (arg2) is ignored:
     params = new SimilarityParams(false, false, true, false);
     assertEquals(blosum.computeSimilarity(s1, s2, params), -7d);
-  
+
     /*
      * score gap-gap but not gap-char
      * score = F^F + R^S + -^- + 0 + 0 + 0
@@ -314,7 +322,7 @@ public class ScoreMatrixTest
     // matchGap (arg2) is ignored:
     params = new SimilarityParams(true, true, false, false);
     assertEquals(blosum.computeSimilarity(s1, s2, params), 6d);
-  
+
     /*
      * score neither gap-gap nor gap-char
      * score = F^F + R^S + 0 + 0 + 0 + 0
@@ -352,7 +360,7 @@ public class ScoreMatrixTest
     // matchGap (arg2) is ignored:
     params = new SimilarityParams(true, false, true, true);
     assertEquals(blosum.computeSimilarity(s1, s2, params), -2d);
-  
+
     /*
      * score gap-char but not gap-gap
      * score = F^F + R^S + 0 + K^- + -^L
@@ -363,7 +371,7 @@ public class ScoreMatrixTest
     // matchGap (arg2) is ignored:
     params = new SimilarityParams(false, false, true, true);
     assertEquals(blosum.computeSimilarity(s1, s2, params), -3d);
-  
+
     /*
      * score gap-gap but not gap-char
      * score = F^F + R^S + -^- + 0 + 0
@@ -374,7 +382,7 @@ public class ScoreMatrixTest
     // matchGap (arg2) is ignored:
     params = new SimilarityParams(true, true, false, true);
     assertEquals(blosum.computeSimilarity(s1, s2, params), 6d);
-  
+
     /*
      * score neither gap-gap nor gap-char
      * score = F^F + R^S + 0 + 0 + 0
@@ -395,6 +403,12 @@ public class ScoreMatrixTest
     verifySymmetric(ScoreModels.getInstance().getDefaultModel(false)); // dna
   }
 
+  /**
+   * A helper method that inspects a loaded matrix and reports any asymmetry as
+   * a test failure
+   * 
+   * @param sm
+   */
   private void verifySymmetric(ScoreMatrix sm)
   {
     float[][] m = sm.getMatrix();
@@ -404,9 +418,10 @@ public class ScoreMatrixTest
       assertEquals(m[row].length, rows);
       for (int col = 0; col < rows; col++)
       {
-        assertEquals(m[row][col], m[col][row], String.format("%s [%s, %s]",
-                sm.getName(), ResidueProperties.aa[row],
-                ResidueProperties.aa[col]));
+        assertEquals(m[row][col], m[col][row],
+                String.format("%s [%s, %s]", sm.getName(),
+                        ResidueProperties.aa[row],
+                        ResidueProperties.aa[col]));
       }
     }
   }
@@ -427,63 +442,98 @@ public class ScoreMatrixTest
      * verify expected scores against ARNDCQEGHILKMFPSTWYVBZX
      * scraped from https://www.ncbi.nlm.nih.gov/Class/FieldGuide/BLOSUM62.txt
      */
-    verifyValues(sm, 'A', new float[] { 4, -1, -2, -2, 0, -1, -1, 0, -2,
-        -1,
-        -1, -1, -1, -2, -1, 1, 0, -3, -2, 0, -2, -1, 0 });
-    verifyValues(sm, 'R', new float[] { -1, 5, 0, -2, -3, 1, 0, -2, 0, -3,
-        -2, 2, -1, -3, -2, -1, -1, -3, -2, -3, -1, 0, -1 });
-    verifyValues(sm, 'N', new float[] { -2, 0, 6, 1, -3, 0, 0, 0, 1, -3,
-        -3,
-        0, -2, -3, -2, 1, 0, -4, -2, -3, 3, 0, -1 });
-    verifyValues(sm, 'D', new float[] { -2, -2, 1, 6, -3, 0, 2, -1, -1, -3,
-        -4, -1, -3, -3, -1, 0, -1, -4, -3, -3, 4, 1, -1 });
-    verifyValues(sm, 'C', new float[] { 0, -3, -3, -3, 9, -3, -4, -3, -3,
-        -1,
-        -1, -3, -1, -2, -3, -1, -1, -2, -2, -1, -3, -3, -2 });
-    verifyValues(sm, 'Q', new float[] { -1, 1, 0, 0, -3, 5, 2, -2, 0, -3,
-        -2,
-        1, 0, -3, -1, 0, -1, -2, -1, -2, 0, 3, -1 });
-    verifyValues(sm, 'E', new float[] { -1, 0, 0, 2, -4, 2, 5, -2, 0, -3,
-        -3,
-        1, -2, -3, -1, 0, -1, -3, -2, -2, 1, 4, -1 });
-    verifyValues(sm, 'G', new float[] { 0, -2, 0, -1, -3, -2, -2, 6, -2,
-        -4,
-        -4, -2, -3, -3, -2, 0, -2, -2, -3, -3, -1, -2, -1 });
-    verifyValues(sm, 'H', new float[] { -2, 0, 1, -1, -3, 0, 0, -2, 8, -3,
-        -3, -1, -2, -1, -2, -1, -2, -2, 2, -3, 0, 0, -1 });
-    verifyValues(sm, 'I', new float[] { -1, -3, -3, -3, -1, -3, -3, -4, -3,
-        4, 2, -3, 1, 0, -3, -2, -1, -3, -1, 3, -3, -3, -1 });
-    verifyValues(sm, 'L', new float[] { -1, -2, -3, -4, -1, -2, -3, -4, -3,
-        2, 4, -2, 2, 0, -3, -2, -1, -2, -1, 1, -4, -3, -1 });
-    verifyValues(sm, 'K', new float[] { -1, 2, 0, -1, -3, 1, 1, -2, -1, -3,
-        -2, 5, -1, -3, -1, 0, -1, -3, -2, -2, 0, 1, -1 });
-    verifyValues(sm, 'M', new float[] { -1, -1, -2, -3, -1, 0, -2, -3, -2,
-        1,
-        2, -1, 5, 0, -2, -1, -1, -1, -1, 1, -3, -1, -1 });
-    verifyValues(sm, 'F', new float[] { -2, -3, -3, -3, -2, -3, -3, -3, -1,
-        0, 0, -3, 0, 6, -4, -2, -2, 1, 3, -1, -3, -3, -1 });
-    verifyValues(sm, 'P', new float[] { -1, -2, -2, -1, -3, -1, -1, -2, -2,
-        -3, -3, -1, -2, -4, 7, -1, -1, -4, -3, -2, -2, -1, -2 });
-    verifyValues(sm, 'S', new float[] { 1, -1, 1, 0, -1, 0, 0, 0, -1, -2,
-        -2,
-        0, -1, -2, -1, 4, 1, -3, -2, -2, 0, 0, 0 });
-    verifyValues(sm, 'T', new float[] { 0, -1, 0, -1, -1, -1, -1, -2, -2,
-        -1,
-        -1, -1, -1, -2, -1, 1, 5, -2, -2, 0, -1, -1, 0 });
-    verifyValues(sm, 'W', new float[] { -3, -3, -4, -4, -2, -2, -3, -2, -2,
-        -3, -2, -3, -1, 1, -4, -3, -2, 11, 2, -3, -4, -3, -2 });
-    verifyValues(sm, 'Y', new float[] { -2, -2, -2, -3, -2, -1, -2, -3, 2,
-        -1, -1, -2, -1, 3, -3, -2, -2, 2, 7, -1, -3, -2, -1 });
-    verifyValues(sm, 'V', new float[] { 0, -3, -3, -3, -1, -2, -2, -3, -3,
-        3,
-        1, -2, 1, -1, -2, -2, 0, -3, -1, 4, -3, -2, -1 });
-    verifyValues(sm, 'B', new float[] { -2, -1, 3, 4, -3, 0, 1, -1, 0, -3,
-        -4, 0, -3, -3, -2, 0, -1, -4, -3, -3, 4, 1, -1 });
-    verifyValues(sm, 'Z', new float[] { -1, 0, 0, 1, -3, 3, 4, -2, 0, -3,
-        -3,
-        1, -1, -3, -1, 0, -1, -3, -2, -2, 1, 4, -1 });
-    verifyValues(sm, 'X', new float[] { 0, -1, -1, -1, -2, -1, -1, -1, -1,
-        -1, -1, -1, -1, -1, -2, 0, 0, -2, -1, -1, -1, -1, -1 });
+    verifyValues(sm, 'A',
+            new float[]
+            { 4, -1, -2, -2, 0, -1, -1, 0, -2, -1, -1, -1, -1, -2, -1, 1, 0,
+                -3, -2, 0, -2, -1, 0 });
+    verifyValues(sm, 'R',
+            new float[]
+            { -1, 5, 0, -2, -3, 1, 0, -2, 0, -3, -2, 2, -1, -3, -2, -1, -1,
+                -3, -2, -3, -1, 0, -1 });
+    verifyValues(sm, 'N',
+            new float[]
+            { -2, 0, 6, 1, -3, 0, 0, 0, 1, -3, -3, 0, -2, -3, -2, 1, 0, -4,
+                -2, -3, 3, 0, -1 });
+    verifyValues(sm, 'D',
+            new float[]
+            { -2, -2, 1, 6, -3, 0, 2, -1, -1, -3, -4, -1, -3, -3, -1, 0, -1,
+                -4, -3, -3, 4, 1, -1 });
+    verifyValues(sm, 'C',
+            new float[]
+            { 0, -3, -3, -3, 9, -3, -4, -3, -3, -1, -1, -3, -1, -2, -3, -1,
+                -1, -2, -2, -1, -3, -3, -2 });
+    verifyValues(sm, 'Q',
+            new float[]
+            { -1, 1, 0, 0, -3, 5, 2, -2, 0, -3, -2, 1, 0, -3, -1, 0, -1, -2,
+                -1, -2, 0, 3, -1 });
+    verifyValues(sm, 'E',
+            new float[]
+            { -1, 0, 0, 2, -4, 2, 5, -2, 0, -3, -3, 1, -2, -3, -1, 0, -1,
+                -3, -2, -2, 1, 4, -1 });
+    verifyValues(sm, 'G',
+            new float[]
+            { 0, -2, 0, -1, -3, -2, -2, 6, -2, -4, -4, -2, -3, -3, -2, 0,
+                -2, -2, -3, -3, -1, -2, -1 });
+    verifyValues(sm, 'H',
+            new float[]
+            { -2, 0, 1, -1, -3, 0, 0, -2, 8, -3, -3, -1, -2, -1, -2, -1, -2,
+                -2, 2, -3, 0, 0, -1 });
+    verifyValues(sm, 'I',
+            new float[]
+            { -1, -3, -3, -3, -1, -3, -3, -4, -3, 4, 2, -3, 1, 0, -3, -2,
+                -1, -3, -1, 3, -3, -3, -1 });
+    verifyValues(sm, 'L',
+            new float[]
+            { -1, -2, -3, -4, -1, -2, -3, -4, -3, 2, 4, -2, 2, 0, -3, -2,
+                -1, -2, -1, 1, -4, -3, -1 });
+    verifyValues(sm, 'K',
+            new float[]
+            { -1, 2, 0, -1, -3, 1, 1, -2, -1, -3, -2, 5, -1, -3, -1, 0, -1,
+                -3, -2, -2, 0, 1, -1 });
+    verifyValues(sm, 'M',
+            new float[]
+            { -1, -1, -2, -3, -1, 0, -2, -3, -2, 1, 2, -1, 5, 0, -2, -1, -1,
+                -1, -1, 1, -3, -1, -1 });
+    verifyValues(sm, 'F',
+            new float[]
+            { -2, -3, -3, -3, -2, -3, -3, -3, -1, 0, 0, -3, 0, 6, -4, -2,
+                -2, 1, 3, -1, -3, -3, -1 });
+    verifyValues(sm, 'P',
+            new float[]
+            { -1, -2, -2, -1, -3, -1, -1, -2, -2, -3, -3, -1, -2, -4, 7, -1,
+                -1, -4, -3, -2, -2, -1, -2 });
+    verifyValues(sm, 'S',
+            new float[]
+            { 1, -1, 1, 0, -1, 0, 0, 0, -1, -2, -2, 0, -1, -2, -1, 4, 1, -3,
+                -2, -2, 0, 0, 0 });
+    verifyValues(sm, 'T',
+            new float[]
+            { 0, -1, 0, -1, -1, -1, -1, -2, -2, -1, -1, -1, -1, -2, -1, 1,
+                5, -2, -2, 0, -1, -1, 0 });
+    verifyValues(sm, 'W',
+            new float[]
+            { -3, -3, -4, -4, -2, -2, -3, -2, -2, -3, -2, -3, -1, 1, -4, -3,
+                -2, 11, 2, -3, -4, -3, -2 });
+    verifyValues(sm, 'Y',
+            new float[]
+            { -2, -2, -2, -3, -2, -1, -2, -3, 2, -1, -1, -2, -1, 3, -3, -2,
+                -2, 2, 7, -1, -3, -2, -1 });
+    verifyValues(sm, 'V',
+            new float[]
+            { 0, -3, -3, -3, -1, -2, -2, -3, -3, 3, 1, -2, 1, -1, -2, -2, 0,
+                -3, -1, 4, -3, -2, -1 });
+    verifyValues(sm, 'B',
+            new float[]
+            { -2, -1, 3, 4, -3, 0, 1, -1, 0, -3, -4, 0, -3, -3, -2, 0, -1,
+                -4, -3, -3, 4, 1, -1 });
+    verifyValues(sm, 'Z',
+            new float[]
+            { -1, 0, 0, 1, -3, 3, 4, -2, 0, -3, -3, 1, -1, -3, -1, 0, -1,
+                -3, -2, -2, 1, 4, -1 });
+    verifyValues(sm, 'X',
+            new float[]
+            { 0, -1, -1, -1, -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, 0,
+                0, -2, -1, -1, -1, -1, -1 });
   }
 
   /**
@@ -585,4 +635,76 @@ public class ScoreMatrixTest
             + "</table>";
     assertEquals(html, expected);
   }
+
+  @Test(groups = "Functional")
+  public void testIsSymmetric()
+  {
+    double delta = 0.0001d;
+    float[][] scores = new float[][] { { 1f, -2f }, { -2f, 3f } };
+    ScoreMatrix sm = new ScoreMatrix("Test", "AB".toCharArray(), scores);
+    assertTrue(sm.isSymmetric());
+
+    /*
+     * verify that with a symmetric score matrix,
+     * pairwise similarity matrix is also symmetric
+     * seq1.seq1 = 5*A.A + 3*B.B = 5+9 = 14
+     * seq1.seq2 = 3*A.A + 2*A.B + B.A + 2*B.B = 3 + -4 + -2 + 6 = 3
+     * seq2.seq1 = 3*A.A + A.B + 2*B.A + 2*B.B = 3 + -2 + -4 + 6 = 3   
+     * seq2.seq2 = 4*A.A + 4*B.B = 4 + 12 = 16   
+     */
+    SimilarityParamsI params = new SimilarityParams(true, true, true,
+            false);
+    String seq1 = "AAABBBAA";
+    String seq2 = "AABBABBA";
+    String[] seqs1 = new String[] { seq1, seq2 };
+    MatrixI res1 = sm.findSimilarities(seqs1, params);
+    assertTrue(
+            res1.equals(new Matrix(new double[][]
+            { { 14d, 3d }, { 3d, 16d } }), delta));
+
+    /*
+     * order of sequences affects diagonal, but not off-diagonal values
+     * [0, 0] is now seq2.seq2, [1, 1] is seq1.seq1
+     * [0, 1] is now seq2.seq1 = seq1.seq2 by symmetry
+     */
+    String[] seqs2 = new String[] { seq2, seq1 };
+    MatrixI res2 = sm.findSimilarities(seqs2, params);
+    assertFalse(res1.equals(res2));
+    assertTrue(res2.equals(new Matrix(new double[][]
+            { { 16d, 3d }, { 3d, 14d } }), delta));
+
+    /*
+     * now make the score matrix asymmetric
+     * seq1.seq1 = 5*A.A + 3*B.B = 5+9 = 14
+     * seq1.seq2 = 3*A.A + 2*A.B + B.A + 2*B.B = 3 + -4 + 2 + 6 = 7
+     * seq2.seq1 = 3*A.A + A.B + 2*B.A + 2*B.B = 3 + -2 + 4 + 6 = 11  
+     * seq2.seq2 = 4*A.A + 4*B.B = 4 + 12 = 16   
+     */
+    scores = new float[][] { { 1f, -2f }, { 2f, 3f } };
+    sm = new ScoreMatrix("Test", "AB".toCharArray(), scores);
+    assertFalse(sm.isSymmetric()); // [0, 1] != [1, 0]
+    res1 = sm.findSimilarities(seqs1, params);
+    assertTrue(res1.equals(new Matrix(new double[][]
+            { { 14d, 7d }, { 11d, 16d } }), delta));
+
+    /*
+     * reverse order of sequences
+     * - reverses order of main diagonal
+     * - reflects off-diagonal values
+     */
+    res2 = sm.findSimilarities(seqs2, params);
+    assertFalse(res1.equals(res2));
+    assertTrue(
+            res2.equals(new Matrix(new double[][]
+            { { 16d, 11d }, { 7d, 14d } }), delta));
+
+    /*
+     * verify that forcing an asymmetric matrix to use
+     * symmetric calculation gives a different (wrong) result
+     */
+    PA.setValue(sm, "symmetric", true);
+    assertTrue(sm.isSymmetric()); // it's not true!
+    res2 = sm.findSimilarities(seqs1, params);
+    assertFalse(res1.equals(res2, delta));
+  }
 }
index 2cde593..7dc3b9e 100644 (file)
@@ -1,6 +1,7 @@
 package jalview.math;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertNotSame;
 import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
@@ -547,4 +548,30 @@ public class MatrixTest
     values[0][0] = -1d;
     assertEquals(m.getValue(0, 0), 1d, DELTA); // unchanged
   }
+
+  @Test(groups = "Functional")
+  public void testEquals()
+  {
+    double[][] values = new double[][] { { 1, 2, 3 }, { 4, 5, 6 } };
+    Matrix m1 = new Matrix(values);
+    double[][] values2 = new double[][] { { 1, 2, 3 }, { 4, 5, 6 } };
+    Matrix m2 = new Matrix(values2);
+
+    double delta = 0.0001d;
+    assertTrue(m1.equals(m1, delta));
+    assertTrue(m1.equals(m2, delta));
+    assertTrue(m2.equals(m1, delta));
+
+    double[][] values3 = new double[][] { { 1, 2, 3 }, { 4, 5, 7 } };
+    m2 = new Matrix(values3);
+    assertFalse(m1.equals(m2, delta));
+    assertFalse(m2.equals(m1, delta));
+
+    // must be same shape
+    values2 = new double[][] { { 1, 2, 3 } };
+    m2 = new Matrix(values2);
+    assertFalse(m2.equals(m1, delta));
+
+    assertFalse(m1.equals(null, delta));
+  }
 }