Merge commit 'alpha/update_2_12_for_2_11_2_series_merge^2' into HEAD
[jalview.git] / test / jalview / datamodel / ResidueCountTest.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.datamodel;
22
23 import java.util.Locale;
24
25 import static org.testng.Assert.assertEquals;
26 import static org.testng.Assert.assertFalse;
27 import static org.testng.Assert.assertTrue;
28
29 import jalview.datamodel.ResidueCount.SymbolCounts;
30 import jalview.gui.JvOptionPane;
31
32 import java.util.Arrays;
33
34 import org.junit.Assert;
35 import org.testng.annotations.BeforeClass;
36 import org.testng.annotations.Test;
37
38 public class ResidueCountTest
39 {
40
41   @BeforeClass(alwaysRun = true)
42   public void setUpJvOptionPane()
43   {
44     JvOptionPane.setInteractiveMode(false);
45     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
46   }
47
48   /**
49    * Test a mix of add and put for nucleotide counting
50    */
51   @Test(groups = "Functional")
52   public void test_countNucleotide()
53   {
54     ResidueCount rc = new ResidueCount(true);
55     assertEquals(rc.getCount('A'), 0);
56     assertEquals(rc.getGapCount(), 0);
57     // add then add
58     assertEquals(rc.add('A'), 1);
59     assertEquals(rc.add('a'), 2);
60     // put then add
61     rc.put('g', 3);
62     assertEquals(rc.add('G'), 4);
63     // add then put
64     assertEquals(rc.add('c'), 1);
65     rc.put('C', 4);
66     assertEquals(rc.add('N'), 1);
67
68     assertEquals(rc.getCount('a'), 2);
69     assertEquals(rc.getCount('A'), 2);
70     assertEquals(rc.getCount('G'), 4);
71     assertEquals(rc.getCount('c'), 4);
72     assertEquals(rc.getCount('T'), 0); // never seen
73     assertEquals(rc.getCount('N'), 1);
74     assertEquals(rc.getCount('?'), 0);
75     assertEquals(rc.getCount('-'), 0);
76     assertEquals(rc.getTotalResidueCount(), 11);
77
78     assertFalse(rc.isCountingInts());
79     assertFalse(rc.isUsingOtherData());
80   }
81
82   /**
83    * Test adding to gap count (either using addGap or add)
84    */
85   @Test(groups = "Functional")
86   public void testAddGap()
87   {
88     ResidueCount rc = new ResidueCount(true);
89     rc.addGap();
90     rc.add('-');
91     rc.add('.');
92     rc.add(' ');
93     
94     assertEquals(rc.getGapCount(), 4);
95     assertEquals(rc.getCount(' '), 4);
96     assertEquals(rc.getCount('-'), 4);
97     assertEquals(rc.getCount('.'), 4);
98     assertEquals(rc.getTotalResidueCount(), 0);
99     assertFalse(rc.isUsingOtherData());
100     assertFalse(rc.isCountingInts());
101     
102     rc.set(ResidueCount.GAP_COUNT, Short.MAX_VALUE-2);
103     assertEquals(rc.getGapCount(), Short.MAX_VALUE-2);
104     assertFalse(rc.isCountingInts());
105     rc.addGap();
106     assertEquals(rc.getGapCount(), Short.MAX_VALUE-1);
107     assertFalse(rc.isCountingInts());
108     rc.addGap();
109     assertEquals(rc.getGapCount(), Short.MAX_VALUE);
110     rc.addGap();
111     assertTrue(rc.isCountingInts());
112     assertEquals(rc.getGapCount(), Short.MAX_VALUE+1);
113   }
114
115   @Test(groups = "Functional")
116   public void testOverflow()
117   {
118     /*
119      * overflow from add
120      */
121     ResidueCount rc = new ResidueCount(true);
122     rc.addGap();
123     rc.put('A', Short.MAX_VALUE - 1);
124     assertFalse(rc.isCountingInts());
125     rc.add('A');
126     assertFalse(rc.isCountingInts());
127     rc.add('A');
128     assertTrue(rc.isCountingInts());
129     assertEquals(rc.getCount('a'), Short.MAX_VALUE + 1);
130     rc.add('A');
131     assertTrue(rc.isCountingInts());
132     assertEquals(rc.getCount('a'), Short.MAX_VALUE + 2);
133     assertEquals(rc.getGapCount(), 1);
134     rc.addGap();
135     assertEquals(rc.getGapCount(), 2);
136
137     /*
138      * overflow from put
139      */
140     rc = new ResidueCount(true);
141     rc.put('G', Short.MAX_VALUE + 1);
142     assertTrue(rc.isCountingInts());
143     assertEquals(rc.getCount('g'), Short.MAX_VALUE + 1);
144     rc.put('G', 1);
145     assertTrue(rc.isCountingInts());
146     assertEquals(rc.getCount('g'), 1);
147
148     /*
149      * underflow from put
150      */
151     rc = new ResidueCount(true);
152     rc.put('G', Short.MIN_VALUE - 1);
153     assertTrue(rc.isCountingInts());
154     assertEquals(rc.getCount('g'), Short.MIN_VALUE - 1);
155   }
156
157   /**
158    * Test a mix of add and put for peptide counting
159    */
160   @Test(groups = "Functional")
161   public void test_countPeptide()
162   {
163     ResidueCount rc = new ResidueCount(false);
164     rc.put('q', 4);
165     rc.add('Q');
166     rc.add('X');
167     rc.add('x');
168     rc.add('W');
169     rc.put('w', 7);
170     rc.put('m', 12);
171     rc.put('M', 13);
172
173     assertEquals(rc.getCount('q'), 5);
174     assertEquals(rc.getCount('X'), 2);
175     assertEquals(rc.getCount('W'), 7);
176     assertEquals(rc.getCount('m'), 13);
177     assertEquals(rc.getCount('G'), 0);
178     assertEquals(rc.getCount('-'), 0);
179     assertEquals(rc.getTotalResidueCount(), 27);
180
181     assertFalse(rc.isCountingInts());
182     assertFalse(rc.isUsingOtherData());
183   }
184
185   @Test(groups = "Functional")
186   public void test_unexpectedPeptide()
187   {
188     ResidueCount rc = new ResidueCount(false);
189     // expected characters (upper or lower case):
190     String aas = "ACDEFGHIKLMNPQRSTVWXY";
191     String lower = aas.toLowerCase(Locale.ROOT);
192     for (int i = 0; i < aas.length(); i++)
193     {
194       rc.put(aas.charAt(i), i);
195       rc.add(lower.charAt(i));
196     }
197     for (int i = 0; i < aas.length(); i++)
198     {
199       assertEquals(rc.getCount(aas.charAt(i)), i + 1);
200     }
201     assertFalse(rc.isUsingOtherData());
202
203     rc.put('J', 4);
204     assertTrue(rc.isUsingOtherData());
205     assertEquals(rc.getCount('J'), 4);
206     rc.add('j');
207     assertEquals(rc.getCount('J'), 5);
208   }
209
210   @Test(groups = "Functional")
211   public void test_unexpectedNucleotide()
212   {
213     ResidueCount rc = new ResidueCount(true);
214     // expected characters (upper or lower case):
215     String nucs = "ACGTUN";
216     String lower = nucs.toLowerCase(Locale.ROOT);
217     for (int i = 0; i < nucs.length(); i++)
218     {
219       rc.put(nucs.charAt(i), i);
220       rc.add(lower.charAt(i));
221     }
222     for (int i = 0; i < nucs.length(); i++)
223     {
224       assertEquals(rc.getCount(nucs.charAt(i)), i + 1);
225     }
226     assertFalse(rc.isUsingOtherData());
227
228     rc.add('J');
229     assertTrue(rc.isUsingOtherData());
230   }
231
232   @Test(groups = "Functional")
233   public void testGetModalCount()
234   {
235     ResidueCount rc = new ResidueCount(true);
236     rc.add('c');
237     rc.add('g');
238     rc.add('c');
239     assertEquals(rc.getModalCount(), 2);
240
241     // modal count is in the 'short overflow' counts
242     rc = new ResidueCount();
243     rc.add('c');
244     rc.put('g', Short.MAX_VALUE);
245     rc.add('G');
246     assertEquals(rc.getModalCount(), Short.MAX_VALUE + 1);
247
248     // modal count is in the 'other data' counts
249     rc = new ResidueCount(false);
250     rc.add('Q');
251     rc.add('{');
252     rc.add('{');
253     assertEquals(rc.getModalCount(), 2);
254
255     // verify modal count excludes gap
256     rc = new ResidueCount();
257     rc.add('Q');
258     rc.add('P');
259     rc.add('Q');
260     rc.addGap();
261     rc.addGap();
262     rc.addGap();
263     assertEquals(rc.getModalCount(), 2);
264   }
265
266   @Test(groups = "Functional")
267   public void testGetResiduesForCount()
268   {
269     ResidueCount rc = new ResidueCount(true);
270     rc.add('c');
271     rc.add('g');
272     rc.add('c');
273     assertEquals(rc.getResiduesForCount(2), "C");
274     assertEquals(rc.getResiduesForCount(1), "G");
275     assertEquals(rc.getResiduesForCount(3), "");
276     assertEquals(rc.getResiduesForCount(0), "");
277     assertEquals(rc.getResiduesForCount(-1), "");
278
279     // modal count is in the 'short overflow' counts
280     rc = new ResidueCount(true);
281     rc.add('c');
282     rc.put('g', Short.MAX_VALUE);
283     rc.add('G');
284     assertEquals(rc.getResiduesForCount(Short.MAX_VALUE + 1), "G");
285     assertEquals(rc.getResiduesForCount(1), "C");
286
287     // peptide modal count is in the 'short overflow' counts
288     rc = new ResidueCount(false);
289     rc.add('c');
290     rc.put('p', Short.MAX_VALUE);
291     rc.add('P');
292     assertEquals(rc.getResiduesForCount(Short.MAX_VALUE + 1), "P");
293     assertEquals(rc.getResiduesForCount(1), "C");
294   
295     // modal count is in the 'other data' counts
296     rc = new ResidueCount();
297     rc.add('Q');
298     rc.add('{');
299     rc.add('{');
300     assertEquals(rc.getResiduesForCount(1), "Q");
301     assertEquals(rc.getResiduesForCount(2), "{");
302
303     // residues share modal count
304     rc = new ResidueCount();
305     rc.add('G');
306     rc.add('G');
307     rc.add('c');
308     rc.add('C');
309     rc.add('U');
310     assertEquals(rc.getResiduesForCount(1), "U");
311     assertEquals(rc.getResiduesForCount(2), "CG");
312
313     // expected and unexpected symbols share modal count
314     rc = new ResidueCount();
315     rc.add('G');
316     rc.add('t');
317     rc.add('[');
318     rc.add('[');
319     rc.add('t');
320     rc.add('G');
321     rc.add('c');
322     rc.add('C');
323     rc.add('U');
324     assertEquals(rc.getResiduesForCount(1), "U");
325     assertEquals(rc.getResiduesForCount(2), "CGT[");
326   }
327
328   @Test(groups = "Functional")
329   public void testGetSymbolCounts_nucleotide()
330   {
331     ResidueCount rc = new ResidueCount(true);
332     rc.add('g');
333     rc.add('c');
334     rc.add('G');
335     rc.add('J'); // 'otherData'
336     rc.add('g');
337     rc.add('N');
338     rc.put('[', 0); // 'otherdata'
339
340     SymbolCounts sc = rc.getSymbolCounts();
341     Assert.assertArrayEquals(new char[] { 'C', 'G', 'N', 'J', '[' },
342             sc.symbols);
343     Assert.assertArrayEquals(new int[] { 1, 3, 1, 1, 0 }, sc.values);
344
345     // now with overflow to int counts
346     rc.put('U', Short.MAX_VALUE);
347     rc.add('u');
348     sc = rc.getSymbolCounts();
349     Assert.assertArrayEquals(new char[] { 'C', 'G', 'N', 'U', 'J', '[' },
350             sc.symbols);
351     Assert.assertArrayEquals(new int[] { 1, 3, 1, 32768, 1, 0 }, sc.values);
352   }
353
354   @Test(groups = "Functional")
355   public void testGetSymbolCounts_peptide()
356   {
357     ResidueCount rc = new ResidueCount(false);
358     rc.add('W');
359     rc.add('q');
360     rc.add('W');
361     rc.add('Z'); // 'otherData'
362     rc.add('w');
363     rc.add('L');
364
365     SymbolCounts sc = rc.getSymbolCounts();
366     Assert.assertArrayEquals(new char[] { 'L', 'Q', 'W', 'Z' }, sc.symbols);
367     Assert.assertArrayEquals(new int[] { 1, 1, 3, 1 }, sc.values);
368
369     // now with overflow to int counts
370     rc.put('W', Short.MAX_VALUE);
371     rc.add('W');
372     sc = rc.getSymbolCounts();
373     Assert.assertArrayEquals(new char[] { 'L', 'Q', 'W', 'Z' }, sc.symbols);
374     Assert.assertArrayEquals(new int[] { 1, 1, 32768, 1 }, sc.values);
375   }
376
377   @Test(groups = "Functional")
378   public void testToString()
379   {
380     ResidueCount rc = new ResidueCount();
381     rc.add('q');
382     rc.add('c');
383     rc.add('Q');
384     assertEquals(rc.toString(), "[ C:1 Q:2 ]");
385
386     // add 'other data'
387     rc.add('{');
388     assertEquals(rc.toString(), "[ C:1 Q:2 {:1 ]");
389
390     // switch from short to int counting:
391     rc.put('G', Short.MAX_VALUE);
392     rc.add('g');
393     assertEquals(rc.toString(), "[ C:1 G:32768 Q:2 {:1 ]");
394   }
395
396   @Test(groups = "Functional")
397   public void testGetTooltip()
398   {
399     ResidueCount rc = new ResidueCount();
400
401     // no counts!
402     assertEquals(rc.getTooltip(20, 1), "");
403
404     /*
405      * count 7 C, 6 K, 7 Q, 10 P, 9 W, 1 F (total 40)
406      */
407     for (int i = 0; i < 7; i++)
408     {
409       rc.add('c');
410       rc.add('q');
411     }
412     for (int i = 0; i < 10; i++)
413     {
414       rc.add('p');
415     }
416     for (int i = 0; i < 9; i++)
417     {
418       rc.add('W');
419     }
420     for (int i = 0; i < 6; i++)
421     {
422       rc.add('K');
423     }
424     rc.add('F');
425     
426     /*
427      * percentages are rounded (0.5 rounded up)
428      * 10/40 9/40 7/40 6/40 1/40
429      */
430     assertEquals(rc.getTooltip(40, 0),
431             "P 25%; W 23%; C 18%; Q 18%; K 15%; F 3%");
432
433     rc.add('Q');
434     /*
435      * 10/30 9/30 8/30 7/30 6/30 1/30
436      */
437     assertEquals(rc.getTooltip(30, 1),
438             "P 33.3%; W 30.0%; Q 26.7%; C 23.3%; K 20.0%; F 3.3%");
439   }
440
441   @Test(groups = "Functional")
442   public void testPut()
443   {
444     ResidueCount rc = new ResidueCount();
445     rc.put('q', 3);
446     assertEquals(rc.getCount('Q'), 3);
447     rc.put(' ', 4);
448     assertEquals(rc.getGapCount(), 4);
449     rc.put('.', 5);
450     assertEquals(rc.getGapCount(), 5);
451     rc.put('-', 6);
452     assertEquals(rc.getGapCount(), 6);
453
454     rc.put('?', 5);
455     assertEquals(rc.getCount('?'), 5);
456     rc.put('?', 6);
457     rc.put('!', 7);
458     assertEquals(rc.getCount('?'), 6);
459     assertEquals(rc.getCount('!'), 7);
460   }
461
462   @Test(groups = "Functional")
463   public void testConstructor_forSequences()
464   {
465     SequenceI seq1 = new Sequence("seq1", "abcde--. FCD");
466     SequenceI seq2 = new Sequence("seq2", "ab.kKqBd-.");
467     ResidueCount rc = new ResidueCount(Arrays.asList(seq1, seq2));
468
469     assertEquals(rc.getGapCount(), 7);
470     assertEquals(rc.getTotalResidueCount(), 15); // excludes gaps
471     assertEquals(rc.getCount('a'), 2);
472     assertEquals(rc.getCount('A'), 2);
473     assertEquals(rc.getCount('B'), 3);
474     assertEquals(rc.getCount('c'), 2);
475     assertEquals(rc.getCount('D'), 3);
476     assertEquals(rc.getCount('f'), 1);
477     assertEquals(rc.getCount('K'), 2);
478     assertEquals(rc.getCount('Q'), 1);
479   }
480 }