ad1d3bf12015941b09bbb5499a9d18154b1c5a0f
[jalview.git] / test / jalview / datamodel / AlignmentTest.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 static org.testng.AssertJUnit.assertEquals;
24 import static org.testng.AssertJUnit.assertFalse;
25 import static org.testng.AssertJUnit.assertNotNull;
26 import static org.testng.AssertJUnit.assertNull;
27 import static org.testng.AssertJUnit.assertSame;
28 import static org.testng.AssertJUnit.assertTrue;
29
30 import jalview.io.AppletFormatAdapter;
31 import jalview.io.FormatAdapter;
32 import jalview.util.MapList;
33
34 import java.io.IOException;
35 import java.util.Collections;
36 import java.util.Iterator;
37
38 import org.testng.annotations.BeforeMethod;
39 import org.testng.annotations.Test;
40
41 /**
42  * Unit tests for Alignment datamodel.
43  * 
44  * @author gmcarstairs
45  *
46  */
47 public class AlignmentTest
48 {
49   // @formatter:off
50   private static final String TEST_DATA = 
51           "# STOCKHOLM 1.0\n" +
52           "#=GS D.melanogaster.1 AC AY119185.1/838-902\n" +
53           "#=GS D.melanogaster.2 AC AC092237.1/57223-57161\n" +
54           "#=GS D.melanogaster.3 AC AY060611.1/560-627\n" +
55           "D.melanogaster.1          G.AGCC.CU...AUGAUCGA\n" +
56           "#=GR D.melanogaster.1 SS  ................((((\n" +
57           "D.melanogaster.2          C.AUUCAACU.UAUGAGGAU\n" +
58           "#=GR D.melanogaster.2 SS  ................((((\n" +
59           "D.melanogaster.3          G.UGGCGCU..UAUGACGCA\n" +
60           "#=GR D.melanogaster.3 SS  (.(((...(....(((((((\n" +
61           "//";
62
63   private static final String AA_SEQS_1 = 
64           ">Seq1Name/5-8\n" +
65           "K-QY--L\n" +
66           ">Seq2Name/12-15\n" +
67           "-R-FP-W-\n";
68
69   private static final String CDNA_SEQS_1 = 
70           ">Seq1Name/100-111\n" +
71           "AC-GG--CUC-CAA-CT\n" +
72           ">Seq2Name/200-211\n" +
73           "-CG-TTA--ACG---AAGT\n";
74
75   private static final String CDNA_SEQS_2 = 
76           ">Seq1Name/50-61\n" +
77           "GCTCGUCGTACT\n" +
78           ">Seq2Name/60-71\n" +
79           "GGGTCAGGCAGT\n";
80   // @formatter:on
81
82   private AlignmentI al;
83
84   /**
85    * Helper method to load an alignment and ensure dataset sequences are set up.
86    * 
87    * @param data
88    * @param format
89    *          TODO
90    * @return
91    * @throws IOException
92    */
93   protected AlignmentI loadAlignment(final String data, String format)
94           throws IOException
95   {
96     AlignmentI a = new FormatAdapter().readFile(data,
97             AppletFormatAdapter.PASTE, format);
98     a.setDataset(null);
99     return a;
100   }
101
102   /*
103    * Read in Stockholm format test data including secondary structure
104    * annotations.
105    */
106   @BeforeMethod(alwaysRun = true)
107   public void setUp() throws IOException
108   {
109     al = loadAlignment(TEST_DATA, "STH");
110     int i = 0;
111     for (AlignmentAnnotation ann : al.getAlignmentAnnotation())
112     {
113       ann.setCalcId("CalcIdFor" + al.getSequenceAt(i).getName());
114       i++;
115     }
116   }
117
118   /**
119    * Test method that returns annotations that match on calcId.
120    */
121   @Test(groups = { "Functional" })
122   public void testFindAnnotation_byCalcId()
123   {
124     Iterable<AlignmentAnnotation> anns = al
125             .findAnnotation("CalcIdForD.melanogaster.2");
126     Iterator<AlignmentAnnotation> iter = anns.iterator();
127     assertTrue(iter.hasNext());
128     AlignmentAnnotation ann = iter.next();
129     assertEquals("D.melanogaster.2", ann.sequenceRef.getName());
130     assertFalse(iter.hasNext());
131   }
132
133   @Test(groups = { "Functional" })
134   public void testDeleteAllAnnotations_includingAutocalculated()
135   {
136     AlignmentAnnotation aa = new AlignmentAnnotation("Consensus",
137             "Consensus", 0.5);
138     aa.autoCalculated = true;
139     al.addAnnotation(aa);
140     AlignmentAnnotation[] anns = al.getAlignmentAnnotation();
141     assertEquals("Wrong number of annotations before deleting", 4,
142             anns.length);
143     al.deleteAllAnnotations(true);
144     assertEquals("Not all deleted", 0, al.getAlignmentAnnotation().length);
145   }
146
147   @Test(groups = { "Functional" })
148   public void testDeleteAllAnnotations_excludingAutocalculated()
149   {
150     AlignmentAnnotation aa = new AlignmentAnnotation("Consensus",
151             "Consensus", 0.5);
152     aa.autoCalculated = true;
153     al.addAnnotation(aa);
154     AlignmentAnnotation[] anns = al.getAlignmentAnnotation();
155     assertEquals("Wrong number of annotations before deleting", 4,
156             anns.length);
157     al.deleteAllAnnotations(false);
158     assertEquals("Not just one annotation left", 1,
159             al.getAlignmentAnnotation().length);
160   }
161
162   /**
163    * Tests for realigning as per a supplied alignment: Dna as Dna.
164    * 
165    * Note: AlignedCodonFrame's state variables are named for protein-to-cDNA
166    * mapping, but can be exploited for a general 'sequence-to-sequence' mapping
167    * as here.
168    * 
169    * @throws IOException
170    */
171   @Test(groups = { "Functional" })
172   public void testAlignAs_dnaAsDna() throws IOException
173   {
174     // aligned cDNA:
175     AlignmentI al1 = loadAlignment(CDNA_SEQS_1, "FASTA");
176     // unaligned cDNA:
177     AlignmentI al2 = loadAlignment(CDNA_SEQS_2, "FASTA");
178
179     /*
180      * Make mappings between sequences. The 'aligned cDNA' is playing the role
181      * of what would normally be protein here.
182      */
183     makeMappings(al2, al1);
184
185     ((Alignment) al2).alignAs(al1, false, true);
186     assertEquals("GC-TC--GUC-GTA-CT", al2.getSequenceAt(0)
187             .getSequenceAsString());
188     assertEquals("-GG-GTC--AGG---CAGT", al2.getSequenceAt(1)
189             .getSequenceAsString());
190   }
191
192   /**
193    * Aligning protein from cDNA.
194    * 
195    * @throws IOException
196    */
197   @Test(groups = { "Functional" })
198   public void testAlignAs_proteinAsCdna() throws IOException
199   {
200     // see also AlignmentUtilsTests
201     AlignmentI al1 = loadAlignment(CDNA_SEQS_1, "FASTA");
202     AlignmentI al2 = loadAlignment(AA_SEQS_1, "FASTA");
203     makeMappings(al1, al2);
204
205     ((Alignment) al2).alignAs(al1, false, true);
206     assertEquals("K-Q-Y-L-", al2.getSequenceAt(0).getSequenceAsString());
207     assertEquals("-R-F-P-W", al2.getSequenceAt(1).getSequenceAsString());
208   }
209
210   /**
211    * Aligning protein from cDNA for a single sequence. This is the 'simple' case
212    * (as there is no need to compute codon 'alignments') but worth testing
213    * before tackling the multiple sequence case.
214    * 
215    * @throws IOException
216    */
217   @Test(groups = { "Functional" })
218   public void testAlignAs_proteinAsCdna_singleSequence() throws IOException
219   {
220     /*
221      * simplest case remove all gaps
222      */
223     verifyAlignAs(">protein\n-Q-K-\n", ">dna\nCAAaaa\n", "QK");
224
225     /*
226      * with sequence offsets
227      */
228     verifyAlignAs(">protein/12-13\n-Q-K-\n", ">dna/20-25\nCAAaaa\n", "QK");
229   }
230
231   /**
232    * Test aligning cdna as per protein alignment.
233    * 
234    * @throws IOException
235    */
236   @Test(groups = { "Functional" })
237   public void testAlignAs_cdnaAsProtein() throws IOException
238   {
239     /*
240      * Load alignments and add mappings for cDNA to protein
241      */
242     AlignmentI al1 = loadAlignment(CDNA_SEQS_1, "FASTA");
243     AlignmentI al2 = loadAlignment(AA_SEQS_1, "FASTA");
244     makeMappings(al1, al2);
245
246     /*
247      * Realign DNA; currently keeping existing gaps in introns only
248      */
249     ((Alignment) al1).alignAs(al2, false, true);
250     assertEquals("ACG---GCUCCA------ACT", al1.getSequenceAt(0)
251             .getSequenceAsString());
252     assertEquals("---CGT---TAACGA---AGT", al1.getSequenceAt(1)
253             .getSequenceAsString());
254   }
255
256   /**
257    * Test aligning cdna as per protein - single sequences
258    * 
259    * @throws IOException
260    */
261   @Test(groups = { "Functional" })
262   public void testAlignAs_cdnaAsProtein_singleSequence() throws IOException
263   {
264     /*
265      * simple case insert one gap
266      */
267     verifyAlignAs(">dna\nCAAaaa\n", ">protein\nQ-K\n", "CAA---aaa");
268
269     /*
270      * simple case but with sequence offsets
271      */
272     verifyAlignAs(">dna/5-10\nCAAaaa\n", ">protein/20-21\nQ-K\n",
273             "CAA---aaa");
274
275     /*
276      * insert gaps as per protein, drop gaps within codons
277      */
278     verifyAlignAs(">dna/10-18\nCA-Aa-aa--AGA\n", ">aa/6-8\n-Q-K--R\n",
279             "---CAA---aaa------AGA");
280   }
281
282   /**
283    * Helper method that makes mappings and then aligns the first alignment as
284    * the second
285    * 
286    * @param fromSeqs
287    * @param toSeqs
288    * @param expected
289    * @throws IOException
290    */
291   public void verifyAlignAs(String fromSeqs, String toSeqs, String expected)
292           throws IOException
293   {
294     /*
295      * Load alignments and add mappings from nucleotide to protein (or from
296      * first to second if both the same type)
297      */
298     AlignmentI al1 = loadAlignment(fromSeqs, "FASTA");
299     AlignmentI al2 = loadAlignment(toSeqs, "FASTA");
300     makeMappings(al1, al2);
301
302     /*
303      * Realign DNA; currently keeping existing gaps in introns only
304      */
305     ((Alignment) al1).alignAs(al2, false, true);
306     assertEquals(expected, al1.getSequenceAt(0).getSequenceAsString());
307   }
308
309   /**
310    * Helper method to make mappings from protein to dna sequences, and add the
311    * mappings to the protein alignment
312    * 
313    * @param alFrom
314    * @param alTo
315    */
316   public void makeMappings(AlignmentI alFrom, AlignmentI alTo)
317   {
318     AlignmentI prot = !alFrom.isNucleotide() ? alFrom : alTo;
319     AlignmentI nuc = alFrom == prot ? alTo : alFrom;
320
321     int ratio = (alFrom.isNucleotide() == alTo.isNucleotide() ? 1 : 3);
322
323     AlignedCodonFrame acf = new AlignedCodonFrame();
324
325     for (int i = 0; i < nuc.getHeight(); i++)
326     {
327       SequenceI seqFrom = nuc.getSequenceAt(i);
328       SequenceI seqTo = prot.getSequenceAt(i);
329       MapList ml = new MapList(new int[] { seqFrom.getStart(),
330           seqFrom.getEnd() },
331               new int[] { seqTo.getStart(), seqTo.getEnd() }, ratio, 1);
332       acf.addMap(seqFrom, seqTo, ml);
333     }
334
335     prot.addCodonFrame(acf);
336   }
337
338   /**
339    * Test aligning dna as per protein alignment, for the case where there are
340    * introns (i.e. some dna sites have no mapping from a peptide).
341    * 
342    * @throws IOException
343    */
344   @Test(groups = { "Functional" })
345   public void testAlignAs_dnaAsProtein_withIntrons() throws IOException
346   {
347     /*
348      * Load alignments and add mappings for cDNA to protein
349      */
350     String dna1 = "A-Aa-gG-GCC-cT-TT";
351     String dna2 = "c--CCGgg-TT--T-AA-A";
352     AlignmentI al1 = loadAlignment(">Seq1/6-17\n" + dna1
353             + "\n>Seq2/20-31\n" + dna2 + "\n", "FASTA");
354     AlignmentI al2 = loadAlignment(
355             ">Seq1/7-9\n-P--YK\n>Seq2/11-13\nG-T--F\n", "FASTA");
356     AlignedCodonFrame acf = new AlignedCodonFrame();
357     // Seq1 has intron at dna positions 3,4,9 so splice is AAG GCC TTT
358     // Seq2 has intron at dna positions 1,5,6 so splice is CCG TTT AAA
359     // TODO sequence offsets
360     MapList ml1 = new MapList(new int[] { 6, 7, 10, 13, 15, 17 }, new int[]
361     { 7, 9 }, 3, 1);
362     acf.addMap(al1.getSequenceAt(0), al2.getSequenceAt(0), ml1);
363     MapList ml2 = new MapList(new int[] { 21, 23, 26, 31 }, new int[] { 11,
364         13 }, 3, 1);
365     acf.addMap(al1.getSequenceAt(1), al2.getSequenceAt(1), ml2);
366     al2.addCodonFrame(acf);
367
368     /*
369      * Align ignoring gaps in dna introns and exons
370      */
371     ((Alignment) al1).alignAs(al2, false, false);
372     assertEquals("---AAagG------GCCcTTT", al1.getSequenceAt(0)
373             .getSequenceAsString());
374     // note 1 gap in protein corresponds to 'gg-' in DNA (3 positions)
375     assertEquals("cCCGgg-TTT------AAA", al1.getSequenceAt(1)
376             .getSequenceAsString());
377
378     /*
379      * Reset and realign, preserving gaps in dna introns and exons
380      */
381     al1.getSequenceAt(0).setSequence(dna1);
382     al1.getSequenceAt(1).setSequence(dna2);
383     ((Alignment) al1).alignAs(al2, true, true);
384     // String dna1 = "A-Aa-gG-GCC-cT-TT";
385     // String dna2 = "c--CCGgg-TT--T-AA-A";
386     // assumption: we include 'the greater of' protein/dna gap lengths, not both
387     assertEquals("---A-Aa-gG------GCC-cT-TT", al1.getSequenceAt(0)
388             .getSequenceAsString());
389     assertEquals("c--CCGgg-TT--T------AA-A", al1.getSequenceAt(1)
390             .getSequenceAsString());
391   }
392
393   @Test(groups = "Functional")
394   public void testCopyConstructor() throws IOException
395   {
396     AlignmentI protein = loadAlignment(AA_SEQS_1, FormatAdapter.PASTE);
397     // create sequence and alignment datasets
398     protein.setDataset(null);
399     AlignedCodonFrame acf = new AlignedCodonFrame();
400     protein.getDataset().setCodonFrames(Collections.singleton(acf));
401     AlignmentI copy = new Alignment(protein);
402
403     /*
404      * copy has different aligned sequences but the same dataset sequences
405      */
406     assertFalse(copy.getSequenceAt(0) == protein.getSequenceAt(0));
407     assertFalse(copy.getSequenceAt(1) == protein.getSequenceAt(1));
408     assertSame(copy.getSequenceAt(0).getDatasetSequence(), protein
409             .getSequenceAt(0).getDatasetSequence());
410     assertSame(copy.getSequenceAt(1).getDatasetSequence(), protein
411             .getSequenceAt(1).getDatasetSequence());
412
413     // TODO should the copy constructor copy the dataset?
414     // or make a new one referring to the same dataset sequences??
415     assertNull(copy.getDataset());
416     // assertArrayEquals(copy.getDataset().getSequencesArray(), protein
417     // .getDataset().getSequencesArray());
418   }
419
420   /**
421    * Test behaviour of createDataset
422    * 
423    * @throws IOException
424    */
425   @Test(groups = "Functional")
426   public void testCreateDatasetAlignment() throws IOException
427   {
428     AlignmentI protein = new FormatAdapter().readFile(AA_SEQS_1,
429             AppletFormatAdapter.PASTE, "FASTA");
430     /*
431      * create a dataset sequence on first sequence
432      * leave the second without one
433      */
434     protein.getSequenceAt(0).createDatasetSequence();
435     assertNotNull(protein.getSequenceAt(0).getDatasetSequence());
436     assertNull(protein.getSequenceAt(1).getDatasetSequence());
437
438     /*
439      * add a mapping to the alignment
440      */
441     AlignedCodonFrame acf = new AlignedCodonFrame();
442     protein.addCodonFrame(acf);
443     assertNull(protein.getDataset());
444     assertTrue(protein.getCodonFrames().contains(acf));
445
446     /*
447      * create the alignment dataset
448      * note this creates sequence datasets where missing
449      * as a side-effect (in this case, on seq2
450      */
451     // TODO promote this method to AlignmentI
452     ((Alignment) protein).createDatasetAlignment();
453
454     // TODO this method should return AlignmentI not Alignment !!
455     Alignment ds = protein.getDataset();
456
457     // side-effect: dataset created on second sequence
458     assertNotNull(protein.getSequenceAt(1).getDatasetSequence());
459     // dataset alignment has references to dataset sequences
460     assertEquals(ds.getSequenceAt(0), protein.getSequenceAt(0)
461             .getDatasetSequence());
462     assertEquals(ds.getSequenceAt(1), protein.getSequenceAt(1)
463             .getDatasetSequence());
464
465     // codon frames should have been moved to the dataset
466     // getCodonFrames() should delegate to the dataset:
467     assertTrue(protein.getCodonFrames().contains(acf));
468     // prove the codon frames are indeed on the dataset:
469     assertTrue(ds.getCodonFrames().contains(acf));
470   }
471 }