JAL-3205 worked examples in unit test for symmetric/asymmetric matrix
[jalview.git] / test / jalview / analysis / scoremodels / ScoreMatrixTest.java
1 package jalview.analysis.scoremodels;
2
3 import static org.testng.Assert.assertEquals;
4 import static org.testng.Assert.assertFalse;
5 import static org.testng.Assert.assertNotEquals;
6 import static org.testng.Assert.assertNotNull;
7 import static org.testng.Assert.assertNotSame;
8 import static org.testng.Assert.assertNull;
9 import static org.testng.Assert.assertTrue;
10 import static org.testng.internal.junit.ArrayAsserts.assertArrayEquals;
11
12 import jalview.api.analysis.SimilarityParamsI;
13 import jalview.io.DataSourceType;
14 import jalview.io.FileParse;
15 import jalview.io.ScoreMatrixFile;
16 import jalview.math.Matrix;
17 import jalview.math.MatrixI;
18 import jalview.schemes.ResidueProperties;
19
20 import java.io.IOException;
21 import java.net.MalformedURLException;
22 import java.util.Arrays;
23
24 import org.testng.annotations.Test;
25
26 import junit.extensions.PA;
27
28 public class ScoreMatrixTest
29 {
30   @Test(groups = "Functional")
31   public void testConstructor()
32   {
33     // note score matrix does not have to be symmetric (though it should be!)
34     float[][] scores = new float[3][];
35     scores[0] = new float[] { 1f, 2f, 3f };
36     scores[1] = new float[] { -4f, 5f, 6f };
37     scores[2] = new float[] { 7f, 8f, 9f };
38     ScoreMatrix sm = new ScoreMatrix("Test", "ABC".toCharArray(), scores);
39     assertFalse(sm.isSymmetric());
40     assertEquals(sm.getSize(), 3);
41     assertArrayEquals(scores, sm.getMatrix());
42     assertEquals(sm.getPairwiseScore('A', 'a'), 1f);
43     assertEquals(sm.getPairwiseScore('b', 'c'), 6f);
44     assertEquals(sm.getPairwiseScore('c', 'b'), 8f);
45     assertEquals(sm.getMatrixIndex('c'), 2);
46     assertEquals(sm.getMatrixIndex(' '), -1);
47
48     // substitution to or from unknown symbol gets minimum score
49     assertEquals(sm.getPairwiseScore('A', 'D'), -4f);
50     assertEquals(sm.getPairwiseScore('D', 'A'), -4f);
51     // unknown-to-self gets a score of 1
52     assertEquals(sm.getPairwiseScore('D', 'D'), 1f);
53   }
54
55   @Test(
56     groups = "Functional",
57     expectedExceptions = { IllegalArgumentException.class })
58   public void testConstructor_matrixTooSmall()
59   {
60     float[][] scores = new float[2][];
61     scores[0] = new float[] { 1f, 2f };
62     scores[1] = new float[] { 3f, 4f };
63     new ScoreMatrix("Test", "ABC".toCharArray(), scores);
64   }
65
66   @Test(
67     groups = "Functional",
68     expectedExceptions = { IllegalArgumentException.class })
69   public void testConstructor_matrixTooBig()
70   {
71     float[][] scores = new float[2][];
72     scores[0] = new float[] { 1f, 2f };
73     scores[1] = new float[] { 3f, 4f };
74     new ScoreMatrix("Test", "A".toCharArray(), scores);
75   }
76
77   @Test(
78     groups = "Functional",
79     expectedExceptions = { IllegalArgumentException.class })
80   public void testConstructor_matrixNotSquare()
81   {
82     float[][] scores = new float[2][];
83     scores[0] = new float[] { 1f, 2f };
84     scores[1] = new float[] { 3f };
85     new ScoreMatrix("Test", "AB".toCharArray(), scores);
86   }
87
88   @Test(groups = "Functional")
89   public void testBuildSymbolIndex()
90   {
91     float[][] scores = new float[2][];
92     scores[0] = new float[] { 1f, 2f };
93     scores[1] = new float[] { 3f, 4f };
94     ScoreMatrix sm = new ScoreMatrix("Test", new char[] { 'A', '.' },
95             scores);
96     short[] index = sm.buildSymbolIndex("AX-yxYp".toCharArray());
97
98     assertEquals(index.length, 128); // ASCII character set size
99
100     assertEquals(index['A'], 0);
101     assertEquals(index['a'], 0); // lower-case mapping added
102     assertEquals(index['X'], 1);
103     assertEquals(index['-'], 2);
104     assertEquals(index['y'], 3); // lower-case override
105     assertEquals(index['x'], 4); // lower-case override
106     assertEquals(index['Y'], 5);
107     assertEquals(index['p'], 6);
108     assertEquals(index['P'], -1); // lower-case doesn't map upper-case
109
110     /*
111      * check all unmapped symbols have index for unmapped
112      */
113     for (int c = 0; c < index.length; c++)
114     {
115       if (!"AaXx-. Yyp".contains(String.valueOf((char) c)))
116       {
117         assertEquals(index[c], -1);
118       }
119     }
120   }
121
122   /**
123    * check that characters not in the basic ASCII set are simply ignored
124    */
125   @Test(groups = "Functional")
126   public void testBuildSymbolIndex_nonAscii()
127   {
128     float[][] scores = new float[2][];
129     scores[0] = new float[] { 1f, 2f };
130     scores[1] = new float[] { 3f, 4f };
131     ScoreMatrix sm = new ScoreMatrix("Test", new char[] { 'A', '.' },
132             scores);
133     char[] weird = new char[] { 128, 245, 'P' };
134     short[] index = sm.buildSymbolIndex(weird);
135     assertEquals(index.length, 128);
136     assertEquals(index['P'], 2);
137     assertEquals(index['p'], 2);
138     for (int c = 0; c < index.length; c++)
139     {
140       if (c != 'P' && c != 'p')
141       {
142         assertEquals(index[c], -1);
143       }
144     }
145   }
146
147   @Test(groups = "Functional")
148   public void testGetMatrix()
149   {
150     ScoreMatrix sm = ScoreModels.getInstance().getBlosum62();
151     float[][] m = sm.getMatrix();
152     assertEquals(m.length, sm.getSize());
153     assertEquals(m[2][4], -3f);
154     // verify a defensive copy is returned
155     float[][] m2 = sm.getMatrix();
156     assertNotSame(m, m2);
157     assertTrue(Arrays.deepEquals(m, m2));
158   }
159
160   @Test(groups = "Functional")
161   public void testGetMatrixIndex()
162   {
163     ScoreMatrix sm = ScoreModels.getInstance().getBlosum62();
164     assertEquals(sm.getMatrixIndex('A'), 0);
165     assertEquals(sm.getMatrixIndex('R'), 1);
166     assertEquals(sm.getMatrixIndex('r'), 1);
167     assertEquals(sm.getMatrixIndex('N'), 2);
168     assertEquals(sm.getMatrixIndex('D'), 3);
169     assertEquals(sm.getMatrixIndex('X'), 22);
170     assertEquals(sm.getMatrixIndex('x'), 22);
171     assertEquals(sm.getMatrixIndex('-'), -1);
172     assertEquals(sm.getMatrixIndex('*'), 23);
173     assertEquals(sm.getMatrixIndex('.'), -1);
174     assertEquals(sm.getMatrixIndex(' '), -1);
175     assertEquals(sm.getMatrixIndex('?'), -1);
176     assertEquals(sm.getMatrixIndex((char) 128), -1);
177   }
178
179   @Test(groups = "Functional")
180   public void testGetSize()
181   {
182     ScoreMatrix sm = ScoreModels.getInstance().getBlosum62();
183     assertEquals(sm.getMatrix().length, sm.getSize());
184   }
185
186   @Test(groups = "Functional")
187   public void testComputePairwiseScores()
188   {
189     /*
190      * NB score matrix expects '-' for gap
191      */
192     String[] seqs = new String[] { "FKL", "R-D", "QIA", "GWC" };
193     ScoreMatrix sm = ScoreModels.getInstance().getBlosum62();
194
195     MatrixI pairwise = sm.findSimilarities(seqs, SimilarityParams.Jalview);
196
197     /*
198      * should be NxN where N = number of sequences
199      */
200     assertEquals(pairwise.height(), 4);
201     assertEquals(pairwise.width(), 4);
202
203     /*
204      * should be symmetrical (because BLOSUM62 is)
205      */
206     for (int i = 0; i < pairwise.height(); i++)
207     {
208       for (int j = i + 1; j < pairwise.width(); j++)
209       {
210         assertEquals(pairwise.getValue(i, j), pairwise.getValue(j, i),
211                 String.format("Not symmetric at [%d, %d]", i, j));
212       }
213     }
214     /*
215      * verify expected BLOSUM dot product scores
216      */
217     // F.F + K.K + L.L = 6 + 5 + 4 = 15
218     assertEquals(pairwise.getValue(0, 0), 15d);
219     // R.R + -.- + D.D = 5 + 1 + 6 = 12
220     assertEquals(pairwise.getValue(1, 1), 12d);
221     // Q.Q + I.I + A.A = 5 + 4 + 4 = 13
222     assertEquals(pairwise.getValue(2, 2), 13d);
223     // G.G + W.W + C.C = 6 + 11 + 9 = 26
224     assertEquals(pairwise.getValue(3, 3), 26d);
225     // F.R + K.- + L.D = -3 + -4 + -4 = -11
226     assertEquals(pairwise.getValue(0, 1), -11d);
227     // F.Q + K.I + L.A = -3 + -3 + -1 = -7
228     assertEquals(pairwise.getValue(0, 2), -7d);
229     // F.G + K.W + L.C = -3 + -3 + -1 = -7
230     assertEquals(pairwise.getValue(0, 3), -7d);
231     // R.Q + -.I + D.A = 1 + -4 + -2 = -5
232     assertEquals(pairwise.getValue(1, 2), -5d);
233     // R.G + -.W + D.C = -2 + -4 + -3 = -9
234     assertEquals(pairwise.getValue(1, 3), -9d);
235     // Q.G + I.W + A.C = -2 + -3 + 0 = -5
236     assertEquals(pairwise.getValue(2, 3), -5d);
237   }
238
239   /**
240    * Test that the result of outputMatrix can be reparsed to give an identical
241    * ScoreMatrix
242    * 
243    * @throws IOException
244    * @throws MalformedURLException
245    */
246   @Test(groups = "Functional")
247   public void testOutputMatrix_roundTrip() throws MalformedURLException,
248           IOException
249   {
250     ScoreMatrix sm = ScoreModels.getInstance().getBlosum62();
251     String output = sm.outputMatrix(false);
252     FileParse fp = new FileParse(output, DataSourceType.PASTE);
253     ScoreMatrixFile parser = new ScoreMatrixFile(fp);
254     ScoreMatrix sm2 = parser.parseMatrix();
255     assertNotNull(sm2);
256     assertTrue(sm2.equals(sm));
257   }
258
259   @Test(groups = "Functional")
260   public void testEqualsAndHashCode()
261   {
262     ScoreMatrix sm = ScoreModels.getInstance().getBlosum62();
263     ScoreMatrix sm2 = new ScoreMatrix(sm.getName(), sm.getSymbols()
264             .toCharArray(), sm.getMatrix());
265     assertTrue(sm.equals(sm2));
266     assertEquals(sm.hashCode(), sm2.hashCode());
267
268     sm2 = ScoreModels.getInstance().getPam250();
269     assertFalse(sm.equals(sm2));
270     assertNotEquals(sm.hashCode(), sm2.hashCode());
271
272     assertFalse(sm.equals("hello"));
273   }
274
275   /**
276    * Tests for scoring options where the longer length of two sequences is used
277    */
278   @Test(groups = "Functional")
279   public void testcomputeSimilarity_matchLongestSequence()
280   {
281     /*
282      * ScoreMatrix expects '-' for gaps
283      */
284     String s1 = "FR-K-S";
285     String s2 = "FS--L";
286     ScoreMatrix blosum = ScoreModels.getInstance().getBlosum62();
287   
288     /*
289      * score gap-gap and gap-char
290      * shorter sequence treated as if with trailing gaps
291      * score = F^F + R^S + -^- + K^- + -^L + S^-
292      * = 6 + -1 + 1 + -4 + -4 + -4 = -6
293      */
294     SimilarityParamsI params = new SimilarityParams(true, true, true, false);
295     assertEquals(blosum.computeSimilarity(s1, s2, params), -6d);
296     // matchGap (arg2) is ignored:
297     params = new SimilarityParams(true, false, true, false);
298     assertEquals(blosum.computeSimilarity(s1, s2, params), -6d);
299   
300     /*
301      * score gap-char but not gap-gap
302      * score = F^F + R^S + 0 + K^- + -^L + S^-
303      * = 6 + -1 + 0 + -4 + -4 + -4 = -7
304      */
305     params = new SimilarityParams(false, true, true, false);
306     assertEquals(blosum.computeSimilarity(s1, s2, params), -7d);
307     // matchGap (arg2) is ignored:
308     params = new SimilarityParams(false, false, true, false);
309     assertEquals(blosum.computeSimilarity(s1, s2, params), -7d);
310   
311     /*
312      * score gap-gap but not gap-char
313      * score = F^F + R^S + -^- + 0 + 0 + 0
314      * = 6 + -1 + 1 = 6
315      */
316     params = new SimilarityParams(true, false, false, false);
317     assertEquals(blosum.computeSimilarity(s1, s2, params), 6d);
318     // matchGap (arg2) is ignored:
319     params = new SimilarityParams(true, true, false, false);
320     assertEquals(blosum.computeSimilarity(s1, s2, params), 6d);
321   
322     /*
323      * score neither gap-gap nor gap-char
324      * score = F^F + R^S + 0 + 0 + 0 + 0
325      * = 6 + -1  = 5
326      */
327     params = new SimilarityParams(false, false, false, false);
328     assertEquals(blosum.computeSimilarity(s1, s2, params), 5d);
329     // matchGap (arg2) is ignored:
330     params = new SimilarityParams(false, true, false, false);
331     assertEquals(blosum.computeSimilarity(s1, s2, params), 5d);
332   }
333
334   /**
335    * Tests for scoring options where only the shorter length of two sequences is
336    * used
337    */
338   @Test(groups = "Functional")
339   public void testcomputeSimilarity_matchShortestSequence()
340   {
341     /*
342      * ScoreMatrix expects '-' for gaps
343      */
344     String s1 = "FR-K-S";
345     String s2 = "FS--L";
346     ScoreMatrix blosum = ScoreModels.getInstance().getBlosum62();
347
348     /*
349      * score gap-gap and gap-char
350      * match shorter sequence only
351      * score = F^F + R^S + -^- + K^- + -^L
352      * = 6 + -1 + 1 + -4 + -4 = -2
353      */
354     SimilarityParamsI params = new SimilarityParams(true, true, true, true);
355     assertEquals(blosum.computeSimilarity(s1, s2, params), -2d);
356     // matchGap (arg2) is ignored:
357     params = new SimilarityParams(true, false, true, true);
358     assertEquals(blosum.computeSimilarity(s1, s2, params), -2d);
359   
360     /*
361      * score gap-char but not gap-gap
362      * score = F^F + R^S + 0 + K^- + -^L
363      * = 6 + -1 + 0 + -4 + -4 = -3
364      */
365     params = new SimilarityParams(false, true, true, true);
366     assertEquals(blosum.computeSimilarity(s1, s2, params), -3d);
367     // matchGap (arg2) is ignored:
368     params = new SimilarityParams(false, false, true, true);
369     assertEquals(blosum.computeSimilarity(s1, s2, params), -3d);
370   
371     /*
372      * score gap-gap but not gap-char
373      * score = F^F + R^S + -^- + 0 + 0
374      * = 6 + -1 + 1 = 6
375      */
376     params = new SimilarityParams(true, false, false, true);
377     assertEquals(blosum.computeSimilarity(s1, s2, params), 6d);
378     // matchGap (arg2) is ignored:
379     params = new SimilarityParams(true, true, false, true);
380     assertEquals(blosum.computeSimilarity(s1, s2, params), 6d);
381   
382     /*
383      * score neither gap-gap nor gap-char
384      * score = F^F + R^S + 0 + 0 + 0
385      * = 6 + -1  = 5
386      */
387     params = new SimilarityParams(false, false, false, true);
388     assertEquals(blosum.computeSimilarity(s1, s2, params), 5d);
389     // matchGap (arg2) is ignored:
390     params = new SimilarityParams(false, true, false, true);
391     assertEquals(blosum.computeSimilarity(s1, s2, params), 5d);
392   }
393
394   @Test(groups = "Functional")
395   public void testSymmetric()
396   {
397     verifySymmetric(ScoreModels.getInstance().getBlosum62());
398     verifySymmetric(ScoreModels.getInstance().getPam250());
399     verifySymmetric(ScoreModels.getInstance().getDefaultModel(false)); // dna
400   }
401
402   /**
403    * A helper method that inspects a loaded matrix and reports any asymmetry as
404    * a test failure
405    * 
406    * @param sm
407    */
408   private void verifySymmetric(ScoreMatrix sm)
409   {
410     float[][] m = sm.getMatrix();
411     int rows = m.length;
412     for (int row = 0; row < rows; row++)
413     {
414       assertEquals(m[row].length, rows);
415       for (int col = 0; col < rows; col++)
416       {
417         assertEquals(m[row][col], m[col][row], String.format("%s [%s, %s]",
418                 sm.getName(), ResidueProperties.aa[row],
419                 ResidueProperties.aa[col]));
420       }
421     }
422   }
423
424   /**
425    * A test that just asserts the expected values in the Blosum62 score matrix
426    */
427   @Test(groups = "Functional")
428   public void testBlosum62_values()
429   {
430     ScoreMatrix sm = ScoreModels.getInstance().getBlosum62();
431
432     assertTrue(sm.isProtein());
433     assertFalse(sm.isDNA());
434     assertNull(sm.getDescription());
435
436     /*
437      * verify expected scores against ARNDCQEGHILKMFPSTWYVBZX
438      * scraped from https://www.ncbi.nlm.nih.gov/Class/FieldGuide/BLOSUM62.txt
439      */
440     verifyValues(sm, 'A', new float[] { 4, -1, -2, -2, 0, -1, -1, 0, -2,
441         -1,
442         -1, -1, -1, -2, -1, 1, 0, -3, -2, 0, -2, -1, 0 });
443     verifyValues(sm, 'R', new float[] { -1, 5, 0, -2, -3, 1, 0, -2, 0, -3,
444         -2, 2, -1, -3, -2, -1, -1, -3, -2, -3, -1, 0, -1 });
445     verifyValues(sm, 'N', new float[] { -2, 0, 6, 1, -3, 0, 0, 0, 1, -3,
446         -3,
447         0, -2, -3, -2, 1, 0, -4, -2, -3, 3, 0, -1 });
448     verifyValues(sm, 'D', new float[] { -2, -2, 1, 6, -3, 0, 2, -1, -1, -3,
449         -4, -1, -3, -3, -1, 0, -1, -4, -3, -3, 4, 1, -1 });
450     verifyValues(sm, 'C', new float[] { 0, -3, -3, -3, 9, -3, -4, -3, -3,
451         -1,
452         -1, -3, -1, -2, -3, -1, -1, -2, -2, -1, -3, -3, -2 });
453     verifyValues(sm, 'Q', new float[] { -1, 1, 0, 0, -3, 5, 2, -2, 0, -3,
454         -2,
455         1, 0, -3, -1, 0, -1, -2, -1, -2, 0, 3, -1 });
456     verifyValues(sm, 'E', new float[] { -1, 0, 0, 2, -4, 2, 5, -2, 0, -3,
457         -3,
458         1, -2, -3, -1, 0, -1, -3, -2, -2, 1, 4, -1 });
459     verifyValues(sm, 'G', new float[] { 0, -2, 0, -1, -3, -2, -2, 6, -2,
460         -4,
461         -4, -2, -3, -3, -2, 0, -2, -2, -3, -3, -1, -2, -1 });
462     verifyValues(sm, 'H', new float[] { -2, 0, 1, -1, -3, 0, 0, -2, 8, -3,
463         -3, -1, -2, -1, -2, -1, -2, -2, 2, -3, 0, 0, -1 });
464     verifyValues(sm, 'I', new float[] { -1, -3, -3, -3, -1, -3, -3, -4, -3,
465         4, 2, -3, 1, 0, -3, -2, -1, -3, -1, 3, -3, -3, -1 });
466     verifyValues(sm, 'L', new float[] { -1, -2, -3, -4, -1, -2, -3, -4, -3,
467         2, 4, -2, 2, 0, -3, -2, -1, -2, -1, 1, -4, -3, -1 });
468     verifyValues(sm, 'K', new float[] { -1, 2, 0, -1, -3, 1, 1, -2, -1, -3,
469         -2, 5, -1, -3, -1, 0, -1, -3, -2, -2, 0, 1, -1 });
470     verifyValues(sm, 'M', new float[] { -1, -1, -2, -3, -1, 0, -2, -3, -2,
471         1,
472         2, -1, 5, 0, -2, -1, -1, -1, -1, 1, -3, -1, -1 });
473     verifyValues(sm, 'F', new float[] { -2, -3, -3, -3, -2, -3, -3, -3, -1,
474         0, 0, -3, 0, 6, -4, -2, -2, 1, 3, -1, -3, -3, -1 });
475     verifyValues(sm, 'P', new float[] { -1, -2, -2, -1, -3, -1, -1, -2, -2,
476         -3, -3, -1, -2, -4, 7, -1, -1, -4, -3, -2, -2, -1, -2 });
477     verifyValues(sm, 'S', new float[] { 1, -1, 1, 0, -1, 0, 0, 0, -1, -2,
478         -2,
479         0, -1, -2, -1, 4, 1, -3, -2, -2, 0, 0, 0 });
480     verifyValues(sm, 'T', new float[] { 0, -1, 0, -1, -1, -1, -1, -2, -2,
481         -1,
482         -1, -1, -1, -2, -1, 1, 5, -2, -2, 0, -1, -1, 0 });
483     verifyValues(sm, 'W', new float[] { -3, -3, -4, -4, -2, -2, -3, -2, -2,
484         -3, -2, -3, -1, 1, -4, -3, -2, 11, 2, -3, -4, -3, -2 });
485     verifyValues(sm, 'Y', new float[] { -2, -2, -2, -3, -2, -1, -2, -3, 2,
486         -1, -1, -2, -1, 3, -3, -2, -2, 2, 7, -1, -3, -2, -1 });
487     verifyValues(sm, 'V', new float[] { 0, -3, -3, -3, -1, -2, -2, -3, -3,
488         3,
489         1, -2, 1, -1, -2, -2, 0, -3, -1, 4, -3, -2, -1 });
490     verifyValues(sm, 'B', new float[] { -2, -1, 3, 4, -3, 0, 1, -1, 0, -3,
491         -4, 0, -3, -3, -2, 0, -1, -4, -3, -3, 4, 1, -1 });
492     verifyValues(sm, 'Z', new float[] { -1, 0, 0, 1, -3, 3, 4, -2, 0, -3,
493         -3,
494         1, -1, -3, -1, 0, -1, -3, -2, -2, 1, 4, -1 });
495     verifyValues(sm, 'X', new float[] { 0, -1, -1, -1, -2, -1, -1, -1, -1,
496         -1, -1, -1, -1, -1, -2, 0, 0, -2, -1, -1, -1, -1, -1 });
497   }
498
499   /**
500    * Helper method to check pairwise scores for one residue
501    * 
502    * @param sm
503    * @param res
504    * @param expected
505    *          score values against 'res', in ResidueProperties.aaIndex order
506    */
507   private void verifyValues(ScoreMatrix sm, char res, float[] expected)
508   {
509     for (int j = 0; j < expected.length; j++)
510     {
511       char c2 = ResidueProperties.aa[j].charAt(0);
512       assertEquals(sm.getPairwiseScore(res, c2), expected[j],
513               String.format("%s->%s", res, c2));
514     }
515   }
516
517   @Test(groups = "Functional")
518   public void testConstructor_gapDash()
519   {
520     float[][] scores = new float[2][];
521     scores[0] = new float[] { 1f, 2f };
522     scores[1] = new float[] { 4f, 5f };
523     ScoreMatrix sm = new ScoreMatrix("Test", new char[] { 'A', '-' },
524             scores);
525     assertEquals(sm.getSize(), 2);
526     assertArrayEquals(scores, sm.getMatrix());
527     assertEquals(sm.getPairwiseScore('A', 'a'), 1f);
528     assertEquals(sm.getPairwiseScore('A', 'A'), 1f);
529     assertEquals(sm.getPairwiseScore('a', '-'), 2f);
530     assertEquals(sm.getPairwiseScore('-', 'A'), 4f);
531     assertEquals(sm.getMatrixIndex('a'), 0);
532     assertEquals(sm.getMatrixIndex('A'), 0);
533     assertEquals(sm.getMatrixIndex('-'), 1);
534     assertEquals(sm.getMatrixIndex(' '), -1);
535     assertEquals(sm.getMatrixIndex('.'), -1);
536   }
537
538   @Test(groups = "Functional")
539   public void testGetPairwiseScore()
540   {
541     float[][] scores = new float[2][];
542     scores[0] = new float[] { 1f, 2f };
543     scores[1] = new float[] { -4f, 5f };
544     ScoreMatrix sm = new ScoreMatrix("Test", new char[] { 'A', 'B' },
545             scores);
546     assertEquals(sm.getPairwiseScore('A', 'A'), 1f);
547     assertEquals(sm.getPairwiseScore('A', 'a'), 1f);
548     assertEquals(sm.getPairwiseScore('A', 'B'), 2f);
549     assertEquals(sm.getPairwiseScore('b', 'a'), -4f);
550     assertEquals(sm.getPairwiseScore('B', 'b'), 5f);
551
552     /*
553      * unknown symbols currently score minimum score
554      * or 1 for identity with self
555      */
556     assertEquals(sm.getPairwiseScore('A', '-'), -4f);
557     assertEquals(sm.getPairwiseScore('-', 'A'), -4f);
558     assertEquals(sm.getPairwiseScore('-', '-'), 1f);
559     assertEquals(sm.getPairwiseScore('Q', 'W'), -4f);
560     assertEquals(sm.getPairwiseScore('Q', 'Q'), 1f);
561
562     /*
563      * symbols not in basic ASCII set score zero
564      */
565     char c = (char) 200;
566     assertEquals(sm.getPairwiseScore('Q', c), 0f);
567     assertEquals(sm.getPairwiseScore(c, 'Q'), 0f);
568   }
569
570   @Test(groups = "Functional")
571   public void testGetMinimumScore()
572   {
573     ScoreMatrix sm = ScoreModels.getInstance().getBlosum62();
574     assertEquals(sm.getMinimumScore(), -4f);
575   }
576
577   @Test(groups = "Functional")
578   public void testGetMaximumScore()
579   {
580     ScoreMatrix sm = ScoreModels.getInstance().getBlosum62();
581     assertEquals(sm.getMaximumScore(), 11f);
582   }
583
584   @Test(groups = "Functional")
585   public void testOutputMatrix_html()
586   {
587     float[][] scores = new float[2][];
588     scores[0] = new float[] { 1f, 2f };
589     scores[1] = new float[] { 4f, -5.3E-10f };
590     ScoreMatrix sm = new ScoreMatrix("Test", "AB".toCharArray(), scores);
591     String html = sm.outputMatrix(true);
592     String expected = "<table border=\"1\"><tr><th></th><th>&nbsp;A&nbsp;</th><th>&nbsp;B&nbsp;</th></tr>\n"
593             + "<tr><td>A</td><td>1.0</td><td>2.0</td></tr>\n"
594             + "<tr><td>B</td><td>4.0</td><td>-5.3E-10</td></tr>\n"
595             + "</table>";
596     assertEquals(html, expected);
597   }
598
599   @Test(groups = "Functional")
600   public void testIsSymmetric()
601   {
602     float[][] scores = new float[][] { { 1f, -2f }, { -2f, 3f } };
603     ScoreMatrix sm = new ScoreMatrix("Test", "AB".toCharArray(), scores);
604     assertTrue(sm.isSymmetric());
605
606     /*
607      * verify that with a symmetric score matrix,
608      * pairwise similarity matrix is also symmetric
609      * seq1.seq1 = 5*A.A + 3*B.B = 5+9 = 14
610      * seq1.seq2 = 3*A.A + 2*A.B + B.A + 2*B.B = 3 + -4 + -2 + 6 = 3
611      * seq2.seq1 = 3*A.A + A.B + 2*B.A + 2*B.B = 3 + -2 + -4 + 6 = 3   
612      * seq2.seq2 = 4*A.A + 4*B.B = 4 + 12 = 16   
613      */
614     SimilarityParamsI params = new SimilarityParams(true, true, true,
615             false);
616     String seq1 = "AAABBBAA";
617     String seq2 = "AABBABBA";
618     String[] seqs1 = new String[] { seq1, seq2 };
619     MatrixI res1 = sm.findSimilarities(seqs1, params);
620     assertEquals(res1,
621             new Matrix(new double[][]
622             { { 14d, 3d }, { 3d, 16d } }));
623
624     /*
625      * order of sequences affects diagonal, but not off-diagonal values
626      * [0, 0] is now seq2.seq2, [1, 1] is seq1.seq1
627      * [0, 1] is now seq2.seq1 = seq1.seq2 by symmetry
628      */
629     String[] seqs2 = new String[] { seq2, seq1 };
630     MatrixI res2 = sm.findSimilarities(seqs2, params);
631     assertFalse(res1.equals(res2));
632     assertEquals(res2,
633             new Matrix(new double[][]
634             { { 16d, 3d }, { 3d, 14d } }));
635
636     /*
637      * now make the score matrix asymmetric
638      * seq1.seq1 = 5*A.A + 3*B.B = 5+9 = 14
639      * seq1.seq2 = 3*A.A + 2*A.B + B.A + 2*B.B = 3 + -4 + 2 + 6 = 7
640      * seq2.seq1 = 3*A.A + A.B + 2*B.A + 2*B.B = 3 + -2 + 4 + 6 = 11  
641      * seq2.seq2 = 4*A.A + 4*B.B = 4 + 12 = 16   
642      */
643     scores = new float[][] { { 1f, -2f }, { 2f, 3f } };
644     sm = new ScoreMatrix("Test", "AB".toCharArray(), scores);
645     assertFalse(sm.isSymmetric()); // [0, 1] != [1, 0]
646     res1 = sm.findSimilarities(seqs1, params);
647     assertEquals(res1,
648             new Matrix(new double[][]
649             { { 14d, 7d }, { 11d, 16d } }));
650
651     /*
652      * reverse order of sequences
653      * - reverses order of main diagonal
654      * - reflects off-diagonal values
655      */
656     res2 = sm.findSimilarities(seqs2, params);
657     assertFalse(res1.equals(res2));
658     assertEquals(res2,
659             new Matrix(new double[][]
660             { { 16d, 11d }, { 7d, 14d } }));
661
662     /*
663      * verify that forcing an asymmetric matrix to use
664      * symmetric calculation gives a different (wrong) result
665      */
666     PA.setValue(sm, "symmetric", true);
667     assertTrue(sm.isSymmetric()); // it's not true!
668     res2 = sm.findSimilarities(seqs1, params);
669     assertFalse(res1.equals(res2));
670   }
671 }