Merge branch 'feature/JAL-3205symmetricCalc' into develop
[jalview.git] / test / jalview / analysis / scoremodels / ScoreMatrixTest.java
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));
+  }
 }