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