de57b1b315d66b052f22b3a32d43d984ccf4b689
[jalview.git] / test / jalview / analysis / AnnotationSorterTest.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;
22
23 import static org.testng.AssertJUnit.assertEquals;
24
25 import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
26 import jalview.api.AlignViewportI;
27 import jalview.bin.Cache;
28 import jalview.datamodel.Alignment;
29 import jalview.datamodel.AlignmentAnnotation;
30 import jalview.datamodel.AlignmentI;
31 import jalview.datamodel.Sequence;
32 import jalview.datamodel.SequenceI;
33 import jalview.gui.AlignViewport;
34 import jalview.gui.JvOptionPane;
35
36 import java.util.Random;
37
38 import org.testng.annotations.BeforeClass;
39 import org.testng.annotations.BeforeMethod;
40 import org.testng.annotations.Test;
41
42 public class AnnotationSorterTest
43 {
44
45   @BeforeClass(alwaysRun = true)
46   public void setUpJvOptionPane()
47   {
48     JvOptionPane.setInteractiveMode(false);
49     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
50   }
51
52   private static final int NUM_SEQS = 6;
53
54   private static final int NUM_ANNS = 7;
55
56   private static final String SS = "secondary structure";
57
58   AlignViewportI av = null;
59
60   /**
61    * Configure so that the viewport does not create autocalculated annotation -
62    * test methods flag selected annotation as autocalculated instead
63    */
64   @BeforeClass(alwaysRun = true)
65   public void setUpBeforeClass()
66   {
67     Cache.loadProperties("test/jalview/io/testProps.jvprops");
68     Cache.setProperty("SHOW_QUALITY", "false");
69     Cache.setProperty("SHOW_CONSERVATION", "false");
70     Cache.setProperty("SHOW_IDENTITY", "false");
71     Cache.setProperty("SHOW_OCCUPANCY", "false");
72   }
73
74   /*
75    * Set up 6 sequences and 7 annotations.
76    */
77   @BeforeMethod(alwaysRun = true)
78   public void setUp()
79   {
80     av = buildAlignment(NUM_SEQS, NUM_ANNS);
81   }
82
83   /**
84    * Make an alignment viewport with numSeqs sequences and numAnns annotations
85    * in it
86    * 
87    * @param numSeqs
88    * @param numAnns
89    * 
90    * @return
91    */
92   private AlignViewportI buildAlignment(int numSeqs, int numAnns)
93   {
94     SequenceI[] seqs = new Sequence[numSeqs];
95     for (int i = 0; i < numSeqs; i++)
96     {
97       seqs[i] = new Sequence("Sequence" + i, "axrdkfp");
98     }
99     Alignment al = new Alignment(seqs);
100
101     for (int i = 0; i < numAnns; i++)
102     {
103       AlignmentAnnotation ann = new AlignmentAnnotation(SS + i, "", 0);
104       al.addAnnotation(ann);
105     }
106
107     return new AlignViewport(al);
108   }
109
110   /**
111    * Test sorting by annotation type (label) within sequence order, including
112    * <ul>
113    * <li>annotations with no sequence reference - sort to end keeping mutual
114    * ordering</li>
115    * <li>annotations with sequence ref = sort in sequence order</li>
116    * <li>multiple annotations for same sequence ref - sort by label
117    * non-case-specific</li>
118    * <li>annotations with reference to sequence not in alignment - treat like no
119    * sequence ref</li>
120    * </ul>
121    */
122   @Test(groups = { "Functional" })
123   public void testSortBySequenceAndLabel_autocalcLast()
124   {
125     AlignmentI al = av.getAlignment();
126     AlignmentAnnotation[] anns = al.getAlignmentAnnotation();
127
128     // @formatter:off
129     anns[0].sequenceRef = al.getSequenceAt(1); anns[0].label = "label0";
130     anns[1].sequenceRef = al.getSequenceAt(3); anns[1].label = "structure";
131     anns[2].sequenceRef = al.getSequenceAt(3); anns[2].label = "iron";
132     anns[3].autoCalculated = true;             anns[3].label = "Quality";
133     anns[4].autoCalculated = true;             anns[4].label = "Consensus";
134     anns[5].sequenceRef = al.getSequenceAt(0); anns[5].label = "label5";
135     anns[6].sequenceRef = al.getSequenceAt(3); anns[6].label = "IRP";
136     // @formatter:on
137
138     av.setShowAutocalculatedAbove(false);
139     AnnotationSorter testee = new AnnotationSorter(av);
140     testee.sort(SequenceAnnotationOrder.SEQUENCE_AND_LABEL, false);
141     assertEquals("label5", anns[0].label); // for sequence 0
142     assertEquals("label0", anns[1].label); // for sequence 1
143     assertEquals("iron", anns[2].label); // sequence 3 /iron
144     assertEquals("IRP", anns[3].label); // sequence 3/IRP
145     assertEquals("structure", anns[4].label); // sequence 3/structure
146     assertEquals("Quality", anns[5].label); // autocalc annotations
147     assertEquals("Consensus", anns[6].label); // retain ordering
148   }
149
150   /**
151    * Variant with autocalculated annotations sorting to front
152    */
153   @Test(groups = { "Functional" })
154   public void testSortBySequenceAndLabel_autocalcFirst()
155   {
156     AlignmentI al = av.getAlignment();
157     AlignmentAnnotation[] anns = al.getAlignmentAnnotation();
158
159     // @formatter:off
160     anns[0].sequenceRef = al.getSequenceAt(1); anns[0].label = "label0";
161     anns[1].sequenceRef = al.getSequenceAt(3); anns[1].label = "structure";
162     anns[2].sequenceRef = al.getSequenceAt(3); anns[2].label = "iron";
163     anns[3].autoCalculated = true;             anns[3].label = "Quality";
164     anns[4].autoCalculated = true;             anns[4].label = "Consensus";
165     anns[5].sequenceRef = al.getSequenceAt(0); anns[5].label = "label5";
166     anns[6].sequenceRef = al.getSequenceAt(3); anns[6].label = "IRP";
167     // @formatter:on
168
169     av.setShowAutocalculatedAbove(true);
170     AnnotationSorter testee = new AnnotationSorter(av);
171     testee.sort(SequenceAnnotationOrder.SEQUENCE_AND_LABEL, false);
172     assertEquals("Quality", anns[0].label); // autocalc annotations
173     assertEquals("Consensus", anns[1].label); // retain ordering
174     assertEquals("label5", anns[2].label); // for sequence 0
175     assertEquals("label0", anns[3].label); // for sequence 1
176     assertEquals("iron", anns[4].label); // sequence 3 /iron
177     assertEquals("IRP", anns[5].label); // sequence 3/IRP
178     assertEquals("structure", anns[6].label); // sequence 3/structure
179   }
180
181   /**
182    * Test sorting by annotation type (label) within sequence order, including
183    * <ul>
184    * <li>annotations with no sequence reference - sort to end keeping mutual
185    * ordering</li>
186    * <li>annotations with sequence ref = sort in sequence order</li>
187    * <li>multiple annotations for same sequence ref - sort by label
188    * non-case-specific</li>
189    * <li>annotations with reference to sequence not in alignment - treat like no
190    * sequence ref</li>
191    * </ul>
192    */
193   @Test(groups = { "Functional" })
194   public void testSortByLabelAndSequence_autocalcLast()
195   {
196     AlignmentI al = av.getAlignment();
197     AlignmentAnnotation[] anns = al.getAlignmentAnnotation();
198
199     // @formatter:off
200     anns[0].sequenceRef = al.getSequenceAt(1); anns[0].label = "label0";
201     anns[1].sequenceRef = al.getSequenceAt(3); anns[1].label = "structure";
202     anns[2].sequenceRef = al.getSequenceAt(3); anns[2].label = "iron";
203     anns[3].autoCalculated = true;             anns[3].label = "Quality";
204     anns[4].autoCalculated = true;             anns[4].label = "Consensus";
205     anns[5].sequenceRef = al.getSequenceAt(0); anns[5].label = "IRON";
206     anns[6].sequenceRef = al.getSequenceAt(2); anns[6].label = "Structure";
207     // @formatter:on
208
209     av.setShowAutocalculatedAbove(false);
210     AnnotationSorter testee = new AnnotationSorter(av);
211     testee.sort(SequenceAnnotationOrder.LABEL_AND_SEQUENCE, false);
212     assertEquals("IRON", anns[0].label); // IRON / sequence 0
213     assertEquals("iron", anns[1].label); // iron / sequence 3
214     assertEquals("label0", anns[2].label); // label0 / sequence 1
215     assertEquals("Structure", anns[3].label); // Structure / sequence 2
216     assertEquals("structure", anns[4].label); // structure / sequence 3
217     assertEquals("Quality", anns[5].label); // autocalc annotations
218     assertEquals("Consensus", anns[6].label); // retain ordering
219   }
220
221   /**
222    * Variant of test with autocalculated annotations sorted to front
223    */
224   @Test(groups = { "Functional" })
225   public void testSortByLabelAndSequence_autocalcFirst()
226   {
227     AlignmentI al = av.getAlignment();
228     AlignmentAnnotation[] anns = al.getAlignmentAnnotation();
229
230     // @formatter:off
231     anns[0].sequenceRef = al.getSequenceAt(1); anns[0].label = "label0";
232     anns[1].sequenceRef = al.getSequenceAt(3); anns[1].label = "structure";
233     anns[2].sequenceRef = al.getSequenceAt(3); anns[2].label = "iron";
234     anns[3].autoCalculated = true;             anns[3].label = "Quality";
235     anns[4].autoCalculated = true;             anns[4].label = "Consensus";
236     anns[5].sequenceRef = al.getSequenceAt(0); anns[5].label = "IRON";
237     anns[6].sequenceRef = al.getSequenceAt(2); anns[6].label = "Structure";
238     // @formatter:on
239
240     av.setShowAutocalculatedAbove(true);
241     AnnotationSorter testee = new AnnotationSorter(av);
242     testee.sort(SequenceAnnotationOrder.LABEL_AND_SEQUENCE, false);
243     assertEquals("Quality", anns[0].label); // autocalc annotations
244     assertEquals("Consensus", anns[1].label); // retain ordering
245     assertEquals("IRON", anns[2].label); // IRON / sequence 0
246     assertEquals("iron", anns[3].label); // iron / sequence 3
247     assertEquals("label0", anns[4].label); // label0 / sequence 1
248     assertEquals("Structure", anns[5].label); // Structure / sequence 2
249     assertEquals("structure", anns[6].label); // structure / sequence 3
250   }
251
252   /**
253    * Variant of test with autocalculated annotations sorted to front but
254    * otherwise no change.
255    */
256   @Test(groups = { "Functional" })
257   public void testNoSort_autocalcFirst()
258   {
259     AlignmentI al = av.getAlignment();
260     AlignmentAnnotation[] anns = al.getAlignmentAnnotation();
261
262     // @formatter:off
263     anns[0].sequenceRef = al.getSequenceAt(1); anns[0].label = "label0";
264     anns[1].sequenceRef = al.getSequenceAt(3); anns[1].label = "structure";
265     anns[2].sequenceRef = al.getSequenceAt(3); anns[2].label = "iron";
266     anns[3].autoCalculated = true;             anns[3].label = "Quality";
267     anns[4].autoCalculated = true;             anns[4].label = "Consensus";
268     anns[5].sequenceRef = al.getSequenceAt(0); anns[5].label = "IRON";
269     anns[6].sequenceRef = al.getSequenceAt(2); anns[6].label = "Structure";
270     // @formatter:on
271
272     av.setShowAutocalculatedAbove(true);
273     AnnotationSorter testee = new AnnotationSorter(av);
274     testee.sort(SequenceAnnotationOrder.NONE, false);
275     assertEquals("Quality", anns[0].label); // autocalc annotations
276     assertEquals("Consensus", anns[1].label); // retain ordering
277     assertEquals("label0", anns[2].label);
278     assertEquals("structure", anns[3].label);
279     assertEquals("iron", anns[4].label);
280     assertEquals("IRON", anns[5].label);
281     assertEquals("Structure", anns[6].label);
282   }
283
284   @Test(groups = { "Functional" })
285   public void testSort_timingPresorted()
286   {
287     testTiming_presorted(50, 100);
288     testTiming_presorted(500, 1000);
289     testTiming_presorted(5000, 10000);
290   }
291
292   /**
293    * Test timing to sort annotations already in the sort order.
294    * 
295    * @param numSeqs
296    * @param numAnns
297    */
298   private void testTiming_presorted(final int numSeqs, final int numAnns)
299   {
300     AlignViewportI viewport = buildAlignment(numSeqs, numAnns);
301     AlignmentI alignment = viewport.getAlignment();
302     AlignmentAnnotation[] annotations = alignment.getAlignmentAnnotation();
303
304     /*
305      * Set the annotations presorted by label
306      */
307     Random r = new Random();
308     final SequenceI[] sequences = alignment.getSequencesArray();
309     for (int i = 0; i < annotations.length; i++)
310     {
311       SequenceI randomSequenceRef = sequences[r.nextInt(sequences.length)];
312       annotations[i].sequenceRef = randomSequenceRef;
313       annotations[i].label = "label" + i;
314     }
315     long startTime = System.currentTimeMillis();
316     viewport.setShowAutocalculatedAbove(false);
317     AnnotationSorter testee = new AnnotationSorter(viewport);
318     testee.sort(SequenceAnnotationOrder.LABEL_AND_SEQUENCE, false);
319     long endTime = System.currentTimeMillis();
320     final long elapsed = endTime - startTime;
321     System.out.println("Timing test for presorted " + numSeqs
322             + " sequences and " + numAnns + " annotations took " + elapsed
323             + "ms");
324   }
325
326   /**
327    * Timing tests for sorting randomly sorted annotations for various sizes.
328    */
329   @Test(groups = { "Functional" })
330   public void testSort_timingUnsorted()
331   {
332     testTiming_unsorted(50, 100);
333     testTiming_unsorted(500, 1000);
334     testTiming_unsorted(5000, 10000);
335   }
336
337   /**
338    * Generate annotations randomly sorted with respect to sequences, and time
339    * sorting.
340    * 
341    * @param numSeqs
342    * @param numAnns
343    */
344   private void testTiming_unsorted(final int numSeqs, final int numAnns)
345   {
346     AlignViewportI viewport = buildAlignment(numSeqs, numAnns);
347     AlignmentI alignment = viewport.getAlignment();
348     AlignmentAnnotation[] annotations = alignment.getAlignmentAnnotation();
349
350     /*
351      * Set the annotations in random order with respect to the sequences
352      */
353     Random r = new Random();
354     final SequenceI[] sequences = alignment.getSequencesArray();
355     for (int i = 0; i < annotations.length; i++)
356     {
357       SequenceI randomSequenceRef = sequences[r.nextInt(sequences.length)];
358       annotations[i].sequenceRef = randomSequenceRef;
359       annotations[i].label = "label" + i;
360     }
361     long startTime = System.currentTimeMillis();
362     av.setShowAutocalculatedAbove(false);
363     AnnotationSorter testee = new AnnotationSorter(av);
364     testee.sort(SequenceAnnotationOrder.SEQUENCE_AND_LABEL, false);
365     long endTime = System.currentTimeMillis();
366     final long elapsed = endTime - startTime;
367     System.out.println("Timing test for unsorted " + numSeqs
368             + " sequences and " + numAnns + " annotations took " + elapsed
369             + "ms");
370   }
371
372   /**
373    * Timing test for sorting annotations with a limited range of types (labels).
374    */
375   @Test(groups = { "Functional" })
376   public void testSort_timingSemisorted()
377   {
378     testTiming_semiSorted(50, 100);
379     testTiming_semiSorted(500, 1000);
380     testTiming_semiSorted(5000, 10000);
381   }
382
383   /**
384    * Mimic 'semi-sorted' annotations:
385    * <ul>
386    * <li>set up in sequence order, with randomly assigned labels from a limited
387    * range</li>
388    * <li>sort by label and sequence order, report timing</li>
389    * <li>resort by sequence and label, report timing</li>
390    * <li>resort by label and sequence, report timing</li>
391    * </ul>
392    * 
393    * @param numSeqs
394    * @param numAnns
395    */
396   private void testTiming_semiSorted(final int numSeqs, final int numAnns)
397   {
398     AlignViewportI viewport = buildAlignment(numSeqs, numAnns);
399     AlignmentI alignment = viewport.getAlignment();
400     AlignmentAnnotation[] annotations = alignment.getAlignmentAnnotation();
401
402     String[] labels = new String[] { "label1", "label2", "label3",
403         "label4", "label5", "label6" };
404
405     /*
406      * Set the annotations in sequence order with randomly assigned labels.
407      */
408     Random r = new Random();
409     final SequenceI[] sequences = alignment.getSequencesArray();
410     for (int i = 0; i < annotations.length; i++)
411     {
412       SequenceI sequenceRef = sequences[i % sequences.length];
413       annotations[i].sequenceRef = sequenceRef;
414       annotations[i].label = labels[r.nextInt(labels.length)];
415     }
416     long startTime = System.currentTimeMillis();
417     av.setShowAutocalculatedAbove(false);
418     AnnotationSorter testee = new AnnotationSorter(av);
419     testee.sort(SequenceAnnotationOrder.LABEL_AND_SEQUENCE, false);
420     long endTime = System.currentTimeMillis();
421     long elapsed = endTime - startTime;
422     System.out.println("Sort by label for semisorted " + numSeqs
423             + " sequences and " + numAnns + " annotations took " + elapsed
424             + "ms");
425
426     // now resort by sequence
427     startTime = System.currentTimeMillis();
428     testee.sort(SequenceAnnotationOrder.SEQUENCE_AND_LABEL, false);
429     endTime = System.currentTimeMillis();
430     elapsed = endTime - startTime;
431     System.out.println("Resort by sequence for semisorted " + numSeqs
432             + " sequences and " + numAnns + " annotations took " + elapsed
433             + "ms");
434
435     // now resort by label
436     startTime = System.currentTimeMillis();
437     testee.sort(SequenceAnnotationOrder.LABEL_AND_SEQUENCE, false);
438     endTime = System.currentTimeMillis();
439     elapsed = endTime - startTime;
440     System.out.println("Resort by label for semisorted " + numSeqs
441             + " sequences and " + numAnns + " annotations took " + elapsed
442             + "ms");
443   }
444
445   /**
446    * Test that sort does nothing if sort order is CUSTOM (manually ordered
447    * annotations)
448    */
449   @Test(groups = { "Functional" })
450   public void testSort_custom()
451   {
452     AlignmentI al = av.getAlignment();
453     AlignmentAnnotation[] anns = al.getAlignmentAnnotation();
454
455     // @formatter:off
456     anns[0].sequenceRef = al.getSequenceAt(1); anns[0].label = "label0";
457     anns[1].sequenceRef = al.getSequenceAt(3); anns[1].label = "structure";
458     anns[2].sequenceRef = al.getSequenceAt(3); anns[2].label = "iron";
459     anns[3].autoCalculated = true;             anns[3].label = "Quality";
460     anns[4].autoCalculated = true;             anns[4].label = "Consensus";
461     anns[5].sequenceRef = al.getSequenceAt(0); anns[5].label = "label5";
462     anns[6].sequenceRef = al.getSequenceAt(3); anns[6].label = "IRP";
463     // @formatter:on
464
465     /*
466      * showAutocalcAbove=true ignored if CUSTOM ordering
467      */
468     av.setShowAutocalculatedAbove(true);
469     AnnotationSorter testee = new AnnotationSorter(av);
470     testee.sort(SequenceAnnotationOrder.CUSTOM, false);
471     assertEquals("label0", anns[0].label); // all unchanged
472     assertEquals("structure", anns[1].label);
473     assertEquals("iron", anns[2].label);
474     assertEquals("Quality", anns[3].label);
475     assertEquals("Consensus", anns[4].label);
476     assertEquals("label5", anns[5].label);
477     assertEquals("IRP", anns[6].label);
478   }
479
480   /**
481    * Test of sorting only autocalculated annotations
482    */
483   @Test(groups = { "Functional" })
484   public void testSort_autocalcOnly()
485   {
486     AlignmentI al = av.getAlignment();
487     AlignmentAnnotation[] anns = al.getAlignmentAnnotation();
488
489     // @formatter:off
490     anns[0].sequenceRef = al.getSequenceAt(1); anns[0].label = "label0";
491     anns[1].sequenceRef = al.getSequenceAt(3); anns[1].label = "structure";
492     anns[2].sequenceRef = al.getSequenceAt(3); anns[2].label = "iron";
493     anns[3].autoCalculated = true;             anns[3].label = "Quality";
494     anns[4].autoCalculated = true;             anns[4].label = "Consensus";
495     anns[5].sequenceRef = al.getSequenceAt(0); anns[5].label = "label5";
496     anns[6].sequenceRef = al.getSequenceAt(3); anns[6].label = "IRP";
497     // @formatter:on
498
499     /*
500      * showAutocalcAbove=true, autocalcOnly=true
501      */
502     av.setShowAutocalculatedAbove(true);
503     AnnotationSorter testee = new AnnotationSorter(av);
504     testee.sort(SequenceAnnotationOrder.LABEL_AND_SEQUENCE, true);
505     assertEquals("Quality", anns[0].label); // moved to top
506     assertEquals("Consensus", anns[1].label); // moved to top
507     assertEquals("label0", anns[2].label); // the rest unchanged
508     assertEquals("structure", anns[3].label);
509     assertEquals("iron", anns[4].label);
510     assertEquals("label5", anns[5].label);
511     assertEquals("IRP", anns[6].label);
512
513     /*
514      * showAutocalcAbove=false, autocalcOnly=true
515      */
516     av.setShowAutocalculatedAbove(false);
517     testee = new AnnotationSorter(av);
518     testee.sort(SequenceAnnotationOrder.LABEL_AND_SEQUENCE, true);
519     assertEquals("label0", anns[0].label); // unchanged
520     assertEquals("structure", anns[1].label);
521     assertEquals("iron", anns[2].label);
522     assertEquals("label5", anns[3].label);
523     assertEquals("IRP", anns[4].label);
524     assertEquals("Quality", anns[5].label); // moved to bottom
525     assertEquals("Consensus", anns[6].label); // moved to bottom
526   }
527 }