JAL-3746 apply copyright to tests
[jalview.git] / test / jalview / analysis / scoremodels / ScoreMatrixTest.java
index 24db894..be8b45d 100644 (file)
@@ -1,8 +1,31 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
 package jalview.analysis.scoremodels;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotEquals;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertNotSame;
+import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
 import static org.testng.internal.junit.ArrayAsserts.assertArrayEquals;
 
@@ -10,6 +33,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;
 
@@ -19,6 +43,8 @@ import java.util.Arrays;
 
 import org.testng.annotations.Test;
 
+import junit.extensions.PA;
+
 public class ScoreMatrixTest
 {
   @Test(groups = "Functional")
@@ -27,24 +53,29 @@ public class ScoreMatrixTest
     // note score matrix does not have to be symmetric (though it should be!)
     float[][] scores = new float[3][];
     scores[0] = new float[] { 1f, 2f, 3f };
-    scores[1] = new float[] { 4f, 5f, 6f };
+    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);
     assertEquals(sm.getPairwiseScore('b', 'c'), 6f);
     assertEquals(sm.getPairwiseScore('c', 'b'), 8f);
-    assertEquals(sm.getPairwiseScore('A', 'D'), 0f);
     assertEquals(sm.getMatrixIndex('c'), 2);
     assertEquals(sm.getMatrixIndex(' '), -1);
 
-    assertEquals(sm.getGapIndex(), -1); // no gap symbol
+    // substitution to or from unknown symbol gets minimum score
+    assertEquals(sm.getPairwiseScore('A', 'D'), -4f);
+    assertEquals(sm.getPairwiseScore('D', 'A'), -4f);
+    // unknown-to-self gets a score of 1
+    assertEquals(sm.getPairwiseScore('D', 'D'), 1f);
   }
 
   @Test(
     groups = "Functional",
-    expectedExceptions = { IllegalArgumentException.class })
+    expectedExceptions =
+    { IllegalArgumentException.class })
   public void testConstructor_matrixTooSmall()
   {
     float[][] scores = new float[2][];
@@ -55,7 +86,8 @@ public class ScoreMatrixTest
 
   @Test(
     groups = "Functional",
-    expectedExceptions = { IllegalArgumentException.class })
+    expectedExceptions =
+    { IllegalArgumentException.class })
   public void testConstructor_matrixTooBig()
   {
     float[][] scores = new float[2][];
@@ -66,7 +98,8 @@ public class ScoreMatrixTest
 
   @Test(
     groups = "Functional",
-    expectedExceptions = { IllegalArgumentException.class })
+    expectedExceptions =
+    { IllegalArgumentException.class })
   public void testConstructor_matrixNotSquare()
   {
     float[][] scores = new float[2][];
@@ -158,8 +191,8 @@ public class ScoreMatrixTest
     assertEquals(sm.getMatrixIndex('D'), 3);
     assertEquals(sm.getMatrixIndex('X'), 22);
     assertEquals(sm.getMatrixIndex('x'), 22);
-    assertEquals(sm.getMatrixIndex('-'), 23);
-    assertEquals(sm.getMatrixIndex('*'), 24);
+    assertEquals(sm.getMatrixIndex('-'), -1);
+    assertEquals(sm.getMatrixIndex('*'), 23);
     assertEquals(sm.getMatrixIndex('.'), -1);
     assertEquals(sm.getMatrixIndex(' '), -1);
     assertEquals(sm.getMatrixIndex('?'), -1);
@@ -167,13 +200,6 @@ public class ScoreMatrixTest
   }
 
   @Test(groups = "Functional")
-  public void testGetGapIndex()
-  {
-    ScoreMatrix sm = ScoreModels.getInstance().getBlosum62();
-    assertEquals(sm.getGapIndex(), 23);
-  }
-
-  @Test(groups = "Functional")
   public void testGetSize()
   {
     ScoreMatrix sm = ScoreModels.getInstance().getBlosum62();
@@ -241,8 +267,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);
@@ -257,10 +283,16 @@ 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());
+
+    sm2 = ScoreModels.getInstance().getPam250();
+    assertFalse(sm.equals(sm2));
+    assertNotEquals(sm.hashCode(), sm2.hashCode());
+
+    assertFalse(sm.equals("hello"));
   }
 
   /**
@@ -275,19 +307,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^-
@@ -298,7 +331,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
@@ -309,7 +342,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
@@ -347,7 +380,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
@@ -358,7 +391,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
@@ -369,7 +402,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
@@ -390,6 +423,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();
@@ -399,9 +438,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]));
       }
     }
   }
@@ -414,67 +454,106 @@ public class ScoreMatrixTest
   {
     ScoreMatrix sm = ScoreModels.getInstance().getBlosum62();
 
+    assertTrue(sm.isProtein());
+    assertFalse(sm.isDNA());
+    assertNull(sm.getDescription());
+
     /*
      * 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 });
   }
 
   /**
@@ -514,7 +593,140 @@ public class ScoreMatrixTest
     assertEquals(sm.getMatrixIndex('-'), 1);
     assertEquals(sm.getMatrixIndex(' '), -1);
     assertEquals(sm.getMatrixIndex('.'), -1);
-  
-    assertEquals(sm.getGapIndex(), 1);
+  }
+
+  @Test(groups = "Functional")
+  public void testGetPairwiseScore()
+  {
+    float[][] scores = new float[2][];
+    scores[0] = new float[] { 1f, 2f };
+    scores[1] = new float[] { -4f, 5f };
+    ScoreMatrix sm = new ScoreMatrix("Test", new char[] { 'A', 'B' },
+            scores);
+    assertEquals(sm.getPairwiseScore('A', 'A'), 1f);
+    assertEquals(sm.getPairwiseScore('A', 'a'), 1f);
+    assertEquals(sm.getPairwiseScore('A', 'B'), 2f);
+    assertEquals(sm.getPairwiseScore('b', 'a'), -4f);
+    assertEquals(sm.getPairwiseScore('B', 'b'), 5f);
+
+    /*
+     * unknown symbols currently score minimum score
+     * or 1 for identity with self
+     */
+    assertEquals(sm.getPairwiseScore('A', '-'), -4f);
+    assertEquals(sm.getPairwiseScore('-', 'A'), -4f);
+    assertEquals(sm.getPairwiseScore('-', '-'), 1f);
+    assertEquals(sm.getPairwiseScore('Q', 'W'), -4f);
+    assertEquals(sm.getPairwiseScore('Q', 'Q'), 1f);
+
+    /*
+     * symbols not in basic ASCII set score zero
+     */
+    char c = (char) 200;
+    assertEquals(sm.getPairwiseScore('Q', c), 0f);
+    assertEquals(sm.getPairwiseScore(c, 'Q'), 0f);
+  }
+
+  @Test(groups = "Functional")
+  public void testGetMinimumScore()
+  {
+    ScoreMatrix sm = ScoreModels.getInstance().getBlosum62();
+    assertEquals(sm.getMinimumScore(), -4f);
+  }
+
+  @Test(groups = "Functional")
+  public void testGetMaximumScore()
+  {
+    ScoreMatrix sm = ScoreModels.getInstance().getBlosum62();
+    assertEquals(sm.getMaximumScore(), 11f);
+  }
+
+  @Test(groups = "Functional")
+  public void testOutputMatrix_html()
+  {
+    float[][] scores = new float[2][];
+    scores[0] = new float[] { 1f, 2f };
+    scores[1] = new float[] { 4f, -5.3E-10f };
+    ScoreMatrix sm = new ScoreMatrix("Test", "AB".toCharArray(), scores);
+    String html = sm.outputMatrix(true);
+    String expected = "<table border=\"1\"><tr><th></th><th>&nbsp;A&nbsp;</th><th>&nbsp;B&nbsp;</th></tr>\n"
+            + "<tr><td>A</td><td>1.0</td><td>2.0</td></tr>\n"
+            + "<tr><td>B</td><td>4.0</td><td>-5.3E-10</td></tr>\n"
+            + "</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));
   }
 }