JAL-2416 support roundtrip print/parse of ScoreMatrix
[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.assertNotNull;
5 import static org.testng.Assert.assertTrue;
6 import static org.testng.internal.junit.ArrayAsserts.assertArrayEquals;
7
8 import jalview.io.DataSourceType;
9 import jalview.io.FileParse;
10 import jalview.io.ScoreMatrixFile;
11 import jalview.math.MatrixI;
12
13 import java.io.IOException;
14 import java.net.MalformedURLException;
15
16 import org.testng.annotations.Test;
17
18 public class ScoreMatrixTest
19 {
20   @Test(groups = "Functional")
21   public void testConstructor()
22   {
23     // note score matrix does not have to be symmetric (though it should be!)
24     float[][] scores = new float[3][];
25     scores[0] = new float[] { 1f, 2f, 3f };
26     scores[1] = new float[] { 4f, 5f, 6f };
27     scores[2] = new float[] { 7f, 8f, 9f };
28     ScoreMatrix sm = new ScoreMatrix("Test", "ABC".toCharArray(), scores);
29     assertEquals(sm.getSize(), 3);
30     assertArrayEquals(scores, sm.getMatrix());
31     assertEquals(sm.getPairwiseScore('A', 'a'), 1f);
32     assertEquals(sm.getPairwiseScore('b', 'c'), 6f);
33     assertEquals(sm.getPairwiseScore('c', 'b'), 8f);
34     assertEquals(sm.getPairwiseScore('A', 'D'), 0f);
35     assertEquals(sm.getMatrixIndex('c'), 2);
36     assertEquals(sm.getMatrixIndex(' '), -1);
37   }
38
39   @Test(
40     groups = "Functional",
41     expectedExceptions = { IllegalArgumentException.class })
42   public void testConstructor_matrixTooSmall()
43   {
44     float[][] scores = new float[2][];
45     scores[0] = new float[] { 1f, 2f };
46     scores[1] = new float[] { 3f, 4f };
47     new ScoreMatrix("Test", "ABC".toCharArray(), scores);
48   }
49
50   @Test(
51     groups = "Functional",
52     expectedExceptions = { IllegalArgumentException.class })
53   public void testConstructor_matrixTooBig()
54   {
55     float[][] scores = new float[2][];
56     scores[0] = new float[] { 1f, 2f };
57     scores[1] = new float[] { 3f, 4f };
58     new ScoreMatrix("Test", "A".toCharArray(), scores);
59   }
60
61   @Test(
62     groups = "Functional",
63     expectedExceptions = { IllegalArgumentException.class })
64   public void testConstructor_matrixNotSquare()
65   {
66     float[][] scores = new float[2][];
67     scores[0] = new float[] { 1f, 2f };
68     scores[1] = new float[] { 3f };
69     new ScoreMatrix("Test", "AB".toCharArray(), scores);
70   }
71
72   @Test(groups = "Functional")
73   public void testBuildSymbolIndex()
74   {
75     short[] index = ScoreMatrix.buildSymbolIndex("AX-. yxYp".toCharArray());
76
77     assertEquals(index.length, 128); // ASCII character set size
78
79     assertEquals(index['A'], 0);
80     assertEquals(index['a'], 0); // lower-case mapping added
81     assertEquals(index['X'], 1);
82     assertEquals(index['-'], 2);
83     assertEquals(index['.'], 3);
84     assertEquals(index[' '], 4);
85     assertEquals(index['y'], 5); // lower-case override
86     assertEquals(index['x'], 6); // lower-case override
87     assertEquals(index['Y'], 7);
88     assertEquals(index['p'], 8);
89     assertEquals(index['P'], -1); // lower-case doesn't map upper-case
90
91     /*
92      * check all unmapped symbols have index for unmapped
93      */
94     for (int c = 0; c < index.length; c++)
95     {
96       if (!"AaXx-. Yyp".contains(String.valueOf((char) c)))
97       {
98         assertEquals(index[c], -1);
99       }
100     }
101   }
102
103   /**
104    * check that characters not in the basic ASCII set are simply ignored
105    */
106   @Test(groups = "Functional")
107   public void testBuildSymbolIndex_nonAscii()
108   {
109     char[] weird = new char[] { 128, 245, 'P' };
110     short[] index = ScoreMatrix.buildSymbolIndex(weird);
111     assertEquals(index.length, 128);
112     assertEquals(index['P'], 2);
113     assertEquals(index['p'], 2);
114     for (int c = 0; c < index.length; c++)
115     {
116       if (c != 'P' && c != 'p')
117       {
118         assertEquals(index[c], -1);
119       }
120     }
121   }
122
123   @Test(groups = "Functional")
124   public void testGetMatrixIndex()
125   {
126     ScoreMatrix sm = ScoreModels.getInstance().getBlosum62();
127     assertEquals(sm.getMatrixIndex('A'), 0);
128     assertEquals(sm.getMatrixIndex('R'), 1);
129     assertEquals(sm.getMatrixIndex('r'), 1);
130     assertEquals(sm.getMatrixIndex('N'), 2);
131     assertEquals(sm.getMatrixIndex('D'), 3);
132     assertEquals(sm.getMatrixIndex('X'), 22);
133     assertEquals(sm.getMatrixIndex('x'), 22);
134     assertEquals(sm.getMatrixIndex(' '), 23);
135     assertEquals(sm.getMatrixIndex('*'), 24);
136     assertEquals(sm.getMatrixIndex('.'), -1);
137     assertEquals(sm.getMatrixIndex('-'), -1);
138     assertEquals(sm.getMatrixIndex('?'), -1);
139     assertEquals(sm.getMatrixIndex((char) 128), -1);
140   }
141
142   @Test(groups = "Functional")
143   public void testGetSize()
144   {
145     ScoreMatrix sm = ScoreModels.getInstance().getBlosum62();
146     assertEquals(sm.getMatrix().length, sm.getSize());
147   }
148
149   @Test(groups = "Functional")
150   public void testComputePairwiseScores()
151   {
152     /*
153      * NB score matrix assumes space for gap - Jalview converts
154      * space to gap before computing PCA or Tree
155      */
156     String[] seqs = new String[] { "FKL", "R D", "QIA", "GWC" };
157     ScoreMatrix sm = ScoreModels.getInstance().getBlosum62();
158
159     MatrixI pairwise = sm.computePairwiseScores(seqs);
160
161     /*
162      * should be NxN where N = number of sequences
163      */
164     assertEquals(pairwise.height(), 4);
165     assertEquals(pairwise.width(), 4);
166
167     /*
168      * should be symmetrical (because BLOSUM62 is)
169      */
170     for (int i = 0; i < pairwise.height(); i++)
171     {
172       for (int j = i + 1; j < pairwise.width(); j++)
173       {
174         assertEquals(pairwise.getValue(i, j), pairwise.getValue(j, i),
175                 String.format("Not symmetric at [%d, %d]", i, j));
176       }
177     }
178     /*
179      * verify expected BLOSUM dot product scores
180      */
181     // F.F + K.K + L.L = 6 + 5 + 4 = 15
182     assertEquals(pairwise.getValue(0, 0), 15d);
183     // R.R + -.- + D.D = 5 + 1 + 6 = 12
184     assertEquals(pairwise.getValue(1, 1), 12d);
185     // Q.Q + I.I + A.A = 5 + 4 + 4 = 13
186     assertEquals(pairwise.getValue(2, 2), 13d);
187     // G.G + W.W + C.C = 6 + 11 + 9 = 26
188     assertEquals(pairwise.getValue(3, 3), 26d);
189     // F.R + K.- + L.D = -3 + -4 + -4 = -11
190     assertEquals(pairwise.getValue(0, 1), -11d);
191     // F.Q + K.I + L.A = -3 + -3 + -1 = -7
192     assertEquals(pairwise.getValue(0, 2), -7d);
193     // F.G + K.W + L.C = -3 + -3 + -1 = -7
194     assertEquals(pairwise.getValue(0, 3), -7d);
195     // R.Q + -.I + D.A = 1 + -4 + -2 = -5
196     assertEquals(pairwise.getValue(1, 2), -5d);
197     // R.G + -.W + D.C = -2 + -4 + -3 = -9
198     assertEquals(pairwise.getValue(1, 3), -9d);
199     // Q.G + I.W + A.C = -2 + -3 + 0 = -5
200     assertEquals(pairwise.getValue(2, 3), -5d);
201   }
202
203   /**
204    * Test that the result of outputMatrix can be reparsed to give an identical
205    * ScoreMatrix
206    * 
207    * @throws IOException
208    * @throws MalformedURLException
209    */
210   @Test(groups = "Functional")
211   public void testOutputMatrix_roundTrip() throws MalformedURLException,
212           IOException
213   {
214     ScoreMatrix sm = ScoreModels.getInstance().getBlosum62();
215     String output = sm.outputMatrix(false);
216     FileParse fp = new FileParse(output, DataSourceType.PASTE);
217     ScoreMatrixFile parser = new ScoreMatrixFile(fp);
218     ScoreMatrix sm2 = parser.parseMatrix();
219     assertNotNull(sm2);
220     assertTrue(sm2.equals(sm));
221   }
222 }