merge from 2_4_Release branch
[jalview.git] / src / jalview / analysis / AlignmentSorter.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.4)
3  * Copyright (C) 2008 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
4  * 
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  * 
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * 
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18  */
19 package jalview.analysis;
20
21 import java.util.*;
22
23 import jalview.datamodel.*;
24 import jalview.util.*;
25
26 /**
27  * Routines for manipulating the order of a multiple sequence alignment TODO:
28  * this class retains some global states concerning sort-order which should be
29  * made attributes for the caller's alignment visualization. TODO: refactor to
30  * allow a subset of selected sequences to be sorted within the context of a
31  * whole alignment. Sort method template is: SequenceI[] tobesorted, [ input
32  * data mapping to each tobesorted element to use ], Alignment context of
33  * tobesorted that are to be re-ordered, boolean sortinplace, [special data - ie
34  * seuqence to be sorted w.r.t.]) sortinplace implies that the sorted vector
35  * resulting from applying the operation to tobesorted should be mapped back to
36  * the original positions in alignment. Otherwise, normal behaviour is to re
37  * order alignment so that tobesorted is sorted and grouped together starting
38  * from the first tobesorted position in the alignment. e.g. (a,tb2,b,tb1,c,tb3
39  * becomes a,tb1,tb2,tb3,b,c)
40  */
41 public class AlignmentSorter
42 {
43   static boolean sortIdAscending = true;
44
45   static int lastGroupHash = 0;
46
47   static boolean sortGroupAscending = true;
48
49   static AlignmentOrder lastOrder = null;
50
51   static boolean sortOrderAscending = true;
52
53   static NJTree lastTree = null;
54
55   static boolean sortTreeAscending = true;
56
57   private static String lastSortByScore;
58
59   /**
60    * Sort by Percentage Identity w.r.t. s
61    * 
62    * @param align
63    *                AlignmentI
64    * @param s
65    *                SequenceI
66    * @param tosort
67    *                sequences from align that are to be sorted.
68    */
69   public static void sortByPID(AlignmentI align, SequenceI s,
70           SequenceI[] tosort)
71   {
72     int nSeq = align.getHeight();
73
74     float[] scores = new float[nSeq];
75     SequenceI[] seqs = new SequenceI[nSeq];
76
77     for (int i = 0; i < nSeq; i++)
78     {
79       scores[i] = Comparison.PID(align.getSequenceAt(i)
80               .getSequenceAsString(), s.getSequenceAsString());
81       seqs[i] = align.getSequenceAt(i);
82     }
83
84     QuickSort.sort(scores, 0, scores.length - 1, seqs);
85
86     setReverseOrder(align, seqs);
87   }
88
89   /**
90    * Reverse the order of the sort
91    * 
92    * @param align
93    *                DOCUMENT ME!
94    * @param seqs
95    *                DOCUMENT ME!
96    */
97   private static void setReverseOrder(AlignmentI align, SequenceI[] seqs)
98   {
99     int nSeq = seqs.length;
100
101     int len = 0;
102
103     if ((nSeq % 2) == 0)
104     {
105       len = nSeq / 2;
106     }
107     else
108     {
109       len = (nSeq + 1) / 2;
110     }
111
112     // NOTE: DO NOT USE align.setSequenceAt() here - it will NOT work
113     for (int i = 0; i < len; i++)
114     {
115       // SequenceI tmp = seqs[i];
116       align.getSequences().setElementAt(seqs[nSeq - i - 1], i);
117       align.getSequences().setElementAt(seqs[i], nSeq - i - 1);
118     }
119   }
120
121   /**
122    * Sets the Alignment object with the given sequences
123    * 
124    * @param align
125    *                Alignment object to be updated
126    * @param tmp
127    *                sequences as a vector
128    */
129   private static void setOrder(AlignmentI align, Vector tmp)
130   {
131     setOrder(align, vectorSubsetToArray(tmp, align.getSequences()));
132   }
133
134   /**
135    * Sets the Alignment object with the given sequences
136    * 
137    * @param align
138    *                DOCUMENT ME!
139    * @param seqs
140    *                sequences as an array
141    */
142   public static void setOrder(AlignmentI align, SequenceI[] seqs)
143   {
144     // NOTE: DO NOT USE align.setSequenceAt() here - it will NOT work
145     Vector algn = align.getSequences();
146     Vector tmp = new Vector();
147
148     for (int i = 0; i < seqs.length; i++)
149     {
150       if (algn.contains(seqs[i]))
151       {
152         tmp.addElement(seqs[i]);
153       }
154     }
155
156     algn.removeAllElements();
157     // User may have hidden seqs, then clicked undo or redo
158     for (int i = 0; i < tmp.size(); i++)
159     {
160       algn.addElement(tmp.elementAt(i));
161     }
162
163   }
164
165   /**
166    * Sorts by ID. Numbers are sorted before letters.
167    * 
168    * @param align
169    *                The alignment object to sort
170    */
171   public static void sortByID(AlignmentI align)
172   {
173     int nSeq = align.getHeight();
174
175     String[] ids = new String[nSeq];
176     SequenceI[] seqs = new SequenceI[nSeq];
177
178     for (int i = 0; i < nSeq; i++)
179     {
180       ids[i] = align.getSequenceAt(i).getName();
181       seqs[i] = align.getSequenceAt(i);
182     }
183
184     QuickSort.sort(ids, seqs);
185
186     if (sortIdAscending)
187     {
188       setReverseOrder(align, seqs);
189     }
190     else
191     {
192       setOrder(align, seqs);
193     }
194
195     sortIdAscending = !sortIdAscending;
196   }
197
198   /**
199    * Sorts the alignment by size of group. <br>
200    * Maintains the order of sequences in each group by order in given alignment
201    * object.
202    * 
203    * @param align
204    *                sorts the given alignment object by group
205    */
206   public static void sortByGroup(AlignmentI align)
207   {
208     // MAINTAINS ORIGNAL SEQUENCE ORDER,
209     // ORDERS BY GROUP SIZE
210     Vector groups = new Vector();
211
212     if (groups.hashCode() != lastGroupHash)
213     {
214       sortGroupAscending = true;
215       lastGroupHash = groups.hashCode();
216     }
217     else
218     {
219       sortGroupAscending = !sortGroupAscending;
220     }
221
222     // SORTS GROUPS BY SIZE
223     // ////////////////////
224     for (int i = 0; i < align.getGroups().size(); i++)
225     {
226       SequenceGroup sg = (SequenceGroup) align.getGroups().elementAt(i);
227
228       for (int j = 0; j < groups.size(); j++)
229       {
230         SequenceGroup sg2 = (SequenceGroup) groups.elementAt(j);
231
232         if (sg.getSize() > sg2.getSize())
233         {
234           groups.insertElementAt(sg, j);
235
236           break;
237         }
238       }
239
240       if (!groups.contains(sg))
241       {
242         groups.addElement(sg);
243       }
244     }
245
246     // NOW ADD SEQUENCES MAINTAINING ALIGNMENT ORDER
247     // /////////////////////////////////////////////
248     Vector seqs = new Vector();
249
250     for (int i = 0; i < groups.size(); i++)
251     {
252       SequenceGroup sg = (SequenceGroup) groups.elementAt(i);
253       SequenceI[] orderedseqs = sg.getSequencesInOrder(align);
254
255       for (int j = 0; j < orderedseqs.length; j++)
256       {
257         seqs.addElement(orderedseqs[j]);
258       }
259     }
260
261     if (sortGroupAscending)
262     {
263       setOrder(align, seqs);
264     }
265     else
266     {
267       setReverseOrder(align,
268               vectorSubsetToArray(seqs, align.getSequences()));
269     }
270   }
271
272   /**
273    * Converts Vector to array. java 1.18 does not have Vector.toArray()
274    * 
275    * @param tmp
276    *                Vector of SequenceI objects
277    * 
278    * @return array of Sequence[]
279    */
280   private static SequenceI[] vectorToArray(Vector tmp)
281   {
282     SequenceI[] seqs = new SequenceI[tmp.size()];
283
284     for (int i = 0; i < tmp.size(); i++)
285     {
286       seqs[i] = (SequenceI) tmp.elementAt(i);
287     }
288
289     return seqs;
290   }
291
292   /**
293    * DOCUMENT ME!
294    * 
295    * @param tmp
296    *                DOCUMENT ME!
297    * @param mask
298    *                DOCUMENT ME!
299    * 
300    * @return DOCUMENT ME!
301    */
302   private static SequenceI[] vectorSubsetToArray(Vector tmp, Vector mask)
303   {
304     Vector seqs = new Vector();
305     int i;
306     boolean[] tmask = new boolean[mask.size()];
307
308     for (i = 0; i < mask.size(); i++)
309     {
310       tmask[i] = true;
311     }
312
313     for (i = 0; i < tmp.size(); i++)
314     {
315       Object sq = tmp.elementAt(i);
316
317       if (mask.contains(sq) && tmask[mask.indexOf(sq)])
318       {
319         tmask[mask.indexOf(sq)] = false;
320         seqs.addElement(sq);
321       }
322     }
323
324     for (i = 0; i < tmask.length; i++)
325     {
326       if (tmask[i])
327       {
328         seqs.addElement(mask.elementAt(i));
329       }
330     }
331
332     return vectorToArray(seqs);
333   }
334
335   /**
336    * Sorts by a given AlignmentOrder object
337    * 
338    * @param align
339    *                Alignment to order
340    * @param order
341    *                specified order for alignment
342    */
343   public static void sortBy(AlignmentI align, AlignmentOrder order)
344   {
345     // Get an ordered vector of sequences which may also be present in align
346     Vector tmp = order.getOrder();
347
348     if (lastOrder == order)
349     {
350       sortOrderAscending = !sortOrderAscending;
351     }
352     else
353     {
354       sortOrderAscending = true;
355     }
356
357     if (sortOrderAscending)
358     {
359       setOrder(align, tmp);
360     }
361     else
362     {
363       setReverseOrder(align, vectorSubsetToArray(tmp, align.getSequences()));
364     }
365   }
366
367   /**
368    * DOCUMENT ME!
369    * 
370    * @param align
371    *                alignment to order
372    * @param tree
373    *                tree which has
374    * 
375    * @return DOCUMENT ME!
376    */
377   private static Vector getOrderByTree(AlignmentI align, NJTree tree)
378   {
379     int nSeq = align.getHeight();
380
381     Vector tmp = new Vector();
382
383     tmp = _sortByTree(tree.getTopNode(), tmp, align.getSequences());
384
385     if (tmp.size() != nSeq)
386     {
387       // TODO: JBPNote - decide if this is always an error
388       // (eg. not when a tree is associated to another alignment which has more
389       // sequences)
390       if (tmp.size() < nSeq)
391       {
392         addStrays(align, tmp);
393       }
394
395       if (tmp.size() != nSeq)
396       {
397         System.err.println("ERROR: tmp.size()=" + tmp.size() + " != nseq="
398                 + nSeq + " in getOrderByTree");
399       }
400     }
401
402     return tmp;
403   }
404
405   /**
406    * Sorts the alignment by a given tree
407    * 
408    * @param align
409    *                alignment to order
410    * @param tree
411    *                tree which has
412    */
413   public static void sortByTree(AlignmentI align, NJTree tree)
414   {
415     Vector tmp = getOrderByTree(align, tree);
416
417     // tmp should properly permute align with tree.
418     if (lastTree != tree)
419     {
420       sortTreeAscending = true;
421       lastTree = tree;
422     }
423     else
424     {
425       sortTreeAscending = !sortTreeAscending;
426     }
427
428     if (sortTreeAscending)
429     {
430       setOrder(align, tmp);
431     }
432     else
433     {
434       setReverseOrder(align, vectorSubsetToArray(tmp, align.getSequences()));
435     }
436   }
437
438   /**
439    * DOCUMENT ME!
440    * 
441    * @param align
442    *                DOCUMENT ME!
443    * @param seqs
444    *                DOCUMENT ME!
445    */
446   private static void addStrays(AlignmentI align, Vector seqs)
447   {
448     int nSeq = align.getHeight();
449
450     for (int i = 0; i < nSeq; i++)
451     {
452       if (!seqs.contains(align.getSequenceAt(i)))
453       {
454         seqs.addElement(align.getSequenceAt(i));
455       }
456     }
457
458     if (nSeq != seqs.size())
459     {
460       System.err
461               .println("ERROR: Size still not right even after addStrays");
462     }
463   }
464
465   /**
466    * DOCUMENT ME!
467    * 
468    * @param node
469    *                DOCUMENT ME!
470    * @param tmp
471    *                DOCUMENT ME!
472    * @param seqset
473    *                DOCUMENT ME!
474    * 
475    * @return DOCUMENT ME!
476    */
477   private static Vector _sortByTree(SequenceNode node, Vector tmp,
478           Vector seqset)
479   {
480     if (node == null)
481     {
482       return tmp;
483     }
484
485     SequenceNode left = (SequenceNode) node.left();
486     SequenceNode right = (SequenceNode) node.right();
487
488     if ((left == null) && (right == null))
489     {
490       if (!node.isPlaceholder() && (node.element() != null))
491       {
492         if (node.element() instanceof SequenceI)
493         {
494           if (!tmp.contains(node.element()))
495           {
496             tmp.addElement((SequenceI) node.element());
497           }
498         }
499       }
500
501       return tmp;
502     }
503     else
504     {
505       _sortByTree(left, tmp, seqset);
506       _sortByTree(right, tmp, seqset);
507     }
508
509     return tmp;
510   }
511
512   // Ordering Objects
513   // Alignment.sortBy(OrderObj) - sequence of sequence pointer refs in
514   // appropriate order
515   //
516
517   /**
518    * recover the order of sequences given by the safe numbering scheme introducd
519    * SeqsetUtils.uniquify.
520    */
521   public static void recoverOrder(SequenceI[] alignment)
522   {
523     float[] ids = new float[alignment.length];
524
525     for (int i = 0; i < alignment.length; i++)
526     {
527       ids[i] = (new Float(alignment[i].getName().substring(8)))
528               .floatValue();
529     }
530
531     jalview.util.QuickSort.sort(ids, alignment);
532   }
533
534   /**
535    * Sort sequence in order of increasing score attribute for annotation with a
536    * particular scoreLabel. Or reverse if same label was used previously
537    * 
538    * @param scoreLabel
539    *                exact label for sequence associated AlignmentAnnotation
540    *                scores to use for sorting.
541    * @param alignment
542    *                sequences to be sorted
543    */
544   public static void sortByAnnotationScore(String scoreLabel,
545           AlignmentI alignment)
546   {
547     SequenceI[] seqs = alignment.getSequencesArray();
548     boolean[] hasScore = new boolean[seqs.length]; // per sequence score
549                                                     // presence
550     int hasScores = 0; // number of scores present on set
551     double[] scores = new double[seqs.length];
552     double min = 0, max = 0;
553     for (int i = 0; i < seqs.length; i++)
554     {
555       AlignmentAnnotation[] scoreAnn = seqs[i].getAnnotation(scoreLabel);
556       if (scoreAnn != null)
557       {
558         hasScores++;
559         hasScore[i] = true;
560         scores[i] = scoreAnn[0].getScore(); // take the first instance of this
561                                             // score.
562         if (hasScores == 1)
563         {
564           max = min = scores[i];
565         }
566         else
567         {
568           if (max < scores[i])
569           {
570             max = scores[i];
571           }
572           if (min > scores[i])
573           {
574             min = scores[i];
575           }
576         }
577       }
578       else
579       {
580         hasScore[i] = false;
581       }
582     }
583     if (hasScores == 0)
584     {
585       return; // do nothing - no scores present to sort by.
586     }
587     if (hasScores < seqs.length)
588     {
589       for (int i = 0; i < seqs.length; i++)
590       {
591         if (!hasScore[i])
592         {
593           scores[i] = (max + i);
594         }
595       }
596     }
597
598     jalview.util.QuickSort.sort(scores, seqs);
599     if (lastSortByScore != scoreLabel)
600     {
601       lastSortByScore = scoreLabel;
602       setOrder(alignment, seqs);
603     }
604     else
605     {
606       setReverseOrder(alignment, seqs);
607     }
608   }
609 }