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