e0cd6f38d629e465b11d4eebbc12db0d0d3122b2
[jalview.git] / forester / java / src / org / forester / sdi / GSDI.java
1 // $Id:
2 // FORESTER -- software libraries and applications
3 // for evolutionary biology research and applications.
4 //
5 // Copyright (C) 2008-2009 Christian M. Zmasek
6 // Copyright (C) 2008-2009 Burnham Institute for Medical Research
7 // All rights reserved
8 //
9 // This library is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU Lesser General Public
11 // License as published by the Free Software Foundation; either
12 // version 2.1 of the License, or (at your option) any later version.
13 //
14 // This library is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 // Lesser General Public License for more details.
18 //
19 // You should have received a copy of the GNU Lesser General Public
20 // License along with this library; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 //
23 // Contact: phylosoft @ gmail . com
24 // WWW: www.phylosoft.org/forester
25
26 package org.forester.sdi;
27
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.Set;
31
32 import org.forester.phylogeny.Phylogeny;
33 import org.forester.phylogeny.PhylogenyNode;
34 import org.forester.phylogeny.data.Event;
35 import org.forester.phylogeny.data.Taxonomy;
36 import org.forester.phylogeny.iterators.PhylogenyNodeIterator;
37 import org.forester.util.ForesterUtil;
38
39 /*
40  * Implements our algorithm for speciation - duplication inference (SDI). <p>
41  * The initialization is accomplished by: </p> <ul> <li>method
42  * "linkExtNodesOfG()" of class SDI: setting the links for the external nodes of
43  * the gene tree <li>"preorderReID(int)" from class Phylogeny: numbering of
44  * nodes of the species tree in preorder <li>the optional stripping of the
45  * species tree is accomplished by method "stripTree(Phylogeny,Phylogeny)" of
46  * class Phylogeny </ul> <p> The recursion part is accomplished by this class'
47  * method "geneTreePostOrderTraversal(PhylogenyNode)". <p> Requires JDK 1.5 or
48  * greater.
49  * 
50  * @see SDI#linkNodesOfG()
51  * 
52  * @see Phylogeny#preorderReID(int)
53  * 
54  * @see
55  * PhylogenyMethods#taxonomyBasedDeletionOfExternalNodes(Phylogeny,Phylogeny)
56  * 
57  * @see #geneTreePostOrderTraversal(PhylogenyNode)
58  * 
59  * @author Christian M. Zmasek
60  */
61 public final class GSDI extends SDI {
62
63     private final HashMap<PhylogenyNode, Integer> _transversal_counts;
64     private final boolean                         _most_parsimonious_duplication_model;
65     private final boolean                         _strip_gene_tree;
66     private int                                   _speciation_or_duplication_events_sum;
67     private int                                   _speciations_sum;
68     private final Set<PhylogenyNode>              _stripped_gene_tree_nodes; 
69
70     /**
71      * Constructor which sets the gene tree and the species tree to be compared.
72      * species_tree is the species tree to which the gene tree gene_tree will be
73      * compared to - with method "infer(boolean)". Both Trees must be completely
74      * binary and rooted. The actual inference is accomplished with method
75      * "infer(boolean)". The mapping cost L can then be calculated with method
76      * "computeMappingCost()".
77      * <p>
78      * 
79      * @see #infer(boolean)
80      * @see SDI#computeMappingCostL()
81      * @param gene_tree
82      *            reference to a rooted gene tree to which assign duplication vs
83      *            speciation, must have species names in the species name fields
84      *            for all external nodes
85      * @param species_tree
86      *            reference to a rooted binary species tree which might get
87      *            stripped in the process, must have species names in the
88      *            species name fields for all external nodes
89      * 
90      * @param most_parsimonious_duplication_model
91      *            set to true to assign nodes as speciations which would
92      *            otherwise be assiged as unknown because of polytomies in the
93      *            species tree.
94      * 
95      */
96     public GSDI( final Phylogeny gene_tree,
97                  final Phylogeny species_tree,
98                  final boolean most_parsimonious_duplication_model,
99                  final boolean strip_gene_tree ) {
100         super( gene_tree, species_tree );
101         _speciation_or_duplication_events_sum = 0;
102         _speciations_sum = 0;
103         _most_parsimonious_duplication_model = most_parsimonious_duplication_model;
104         _transversal_counts = new HashMap<PhylogenyNode, Integer>();
105         _duplications_sum = 0; 
106         _strip_gene_tree = strip_gene_tree;
107         _stripped_gene_tree_nodes = new HashSet<PhylogenyNode>(); 
108         getSpeciesTree().preOrderReId();
109         linkNodesOfG();
110         geneTreePostOrderTraversal( getGeneTree().getRoot() );
111     }
112
113     public GSDI( final Phylogeny gene_tree,
114                  final Phylogeny species_tree,
115                  final boolean most_parsimonious_duplication_model ) {
116         this( gene_tree, species_tree, most_parsimonious_duplication_model, false );
117     }
118
119     private final Event createDuplicationEvent() {
120         final Event event = Event.createSingleDuplicationEvent();
121         ++_duplications_sum;
122         return event;
123     }
124
125     private final Event createSingleSpeciationOrDuplicationEvent() {
126         final Event event = Event.createSingleSpeciationOrDuplicationEvent();
127         ++_speciation_or_duplication_events_sum;
128         return event;
129     }
130
131     private final Event createSpeciationEvent() {
132         final Event event = Event.createSingleSpeciationEvent();
133         ++_speciations_sum;
134         return event;
135     }
136
137     // s is the node on the species tree g maps to.
138     private final void determineEvent( final PhylogenyNode s, final PhylogenyNode g ) {
139         Event event = null;
140         // Determine how many children map to same node as parent.
141         int sum_g_childs_mapping_to_s = 0;
142         for( final PhylogenyNodeIterator iter = g.iterateChildNodesForward(); iter.hasNext(); ) {
143             if ( iter.next().getLink() == s ) {
144                 ++sum_g_childs_mapping_to_s;
145             }
146         }
147         // Determine the sum of traversals.
148         int traversals_sum = 0;
149         int max_traversals = 0;
150         PhylogenyNode max_traversals_node = null;
151         if ( !s.isExternal() ) {
152             for( final PhylogenyNodeIterator iter = s.iterateChildNodesForward(); iter.hasNext(); ) {
153                 final PhylogenyNode current_node = iter.next();
154                 final int traversals = getTraversalCount( current_node );
155                 traversals_sum += traversals;
156                 if ( traversals > max_traversals ) {
157                     max_traversals = traversals;
158                     max_traversals_node = current_node;
159                 }
160             }
161         }
162         // System.out.println( " sum=" + traversals_sum );
163         // System.out.println( " max=" + max_traversals );
164         // System.out.println( " m=" + sum_g_childs_mapping_to_s );
165         if ( sum_g_childs_mapping_to_s > 0 ) {
166             if ( traversals_sum == 2 ) {
167                 event = createDuplicationEvent();
168             }
169             else if ( traversals_sum > 2 ) {
170                 if ( max_traversals <= 1 ) {
171                     if ( _most_parsimonious_duplication_model ) {
172                         event = createSpeciationEvent();
173                     }
174                     else {
175                         event = createSingleSpeciationOrDuplicationEvent();
176                     }
177                 }
178                 else {
179                     event = createDuplicationEvent();
180                     _transversal_counts.put( max_traversals_node, 1 );
181                 }
182             }
183             else {
184                 event = createDuplicationEvent();
185             }
186         }
187         else {
188             event = createSpeciationEvent();
189         }
190         g.getNodeData().setEvent( event );
191     }
192
193     /**
194      * Traverses the subtree of PhylogenyNode g in postorder, calculating the
195      * mapping function M, and determines which nodes represent speciation
196      * events and which ones duplication events.
197      * <p>
198      * Preconditions: Mapping M for external nodes must have been calculated and
199      * the species tree must be labeled in preorder.
200      * <p>
201      * (Last modified: )
202      * 
203      * @param g
204      *            starting node of a gene tree - normally the root
205      */
206     final void geneTreePostOrderTraversal( final PhylogenyNode g ) {
207         if ( !g.isExternal() ) {
208             for( final PhylogenyNodeIterator iter = g.iterateChildNodesForward(); iter.hasNext(); ) {
209                 geneTreePostOrderTraversal( iter.next() );
210             }
211             final PhylogenyNode[] linked_nodes = new PhylogenyNode[ g.getNumberOfDescendants() ];
212             for( int i = 0; i < linked_nodes.length; ++i ) {
213                 linked_nodes[ i ] = g.getChildNode( i ).getLink();
214             }
215             final int[] min_max = obtainMinMaxIdIndices( linked_nodes );
216             int min_i = min_max[ 0 ];
217             int max_i = min_max[ 1 ];
218             // initTransversalCounts();
219             while ( linked_nodes[ min_i ] != linked_nodes[ max_i ] ) {
220                 increaseTraversalCount( linked_nodes[ max_i ] );
221                 linked_nodes[ max_i ] = linked_nodes[ max_i ].getParent();
222                 final int[] min_max_ = obtainMinMaxIdIndices( linked_nodes );
223                 min_i = min_max_[ 0 ];
224                 max_i = min_max_[ 1 ];
225             }
226             final PhylogenyNode s = linked_nodes[ max_i ];
227             g.setLink( s );
228             // Determines whether dup. or spec.
229             determineEvent( s, g );
230             // _transversal_counts.clear();
231         }
232     }
233
234     public final int getSpeciationOrDuplicationEventsSum() {
235         return _speciation_or_duplication_events_sum;
236     }
237
238     public final int getSpeciationsSum() {
239         return _speciations_sum;
240     }
241
242     private final int getTraversalCount( final PhylogenyNode node ) {
243         if ( _transversal_counts.containsKey( node ) ) {
244             return _transversal_counts.get( node );
245         }
246         return 0;
247     }
248
249     private final void increaseTraversalCount( final PhylogenyNode node ) {
250         if ( _transversal_counts.containsKey( node ) ) {
251             _transversal_counts.put( node, _transversal_counts.get( node ) + 1 );
252         }
253         else {
254             _transversal_counts.put( node, 1 );
255         }
256         // System.out.println( "count for node " + node.getID() + " is now "
257         // + getTraversalCount( node ) );
258     }
259
260     /**
261      * This allows for linking of internal nodes of the species tree (as opposed
262      * to just external nodes, as in the method it overrides.
263      * 
264      */
265     @Override
266     final void linkNodesOfG() {
267         final HashMap<Taxonomy, PhylogenyNode> speciestree_ext_nodes = createTaxonomyToNodeMap();
268         if ( _strip_gene_tree ) {
269             stripGeneTree( speciestree_ext_nodes );
270             if ( ( _gene_tree == null ) || ( _gene_tree.getNumberOfExternalNodes() < 2 ) ) {
271                 throw new IllegalArgumentException( "species tree does not contain any"
272                         + " nodes matching species in the gene tree" );
273             }
274         }
275         // Retrieve the reference to the PhylogenyNode with a matching species.
276         for( final PhylogenyNodeIterator iter = _gene_tree.iteratorExternalForward(); iter.hasNext(); ) {
277             final PhylogenyNode g = iter.next();
278             if ( !g.getNodeData().isHasTaxonomy() ) {
279                 throw new IllegalArgumentException( "gene tree node " + g + " has no taxonomic data" );
280             }
281             final PhylogenyNode s = speciestree_ext_nodes.get( g.getNodeData().getTaxonomy() );
282             if ( s == null ) {
283                 throw new IllegalArgumentException( "species " + g.getNodeData().getTaxonomy()
284                         + " not present in species tree" );
285             }
286             g.setLink( s );
287         }
288     }
289
290     final private HashMap<Taxonomy, PhylogenyNode> createTaxonomyToNodeMap() {
291         final HashMap<Taxonomy, PhylogenyNode> speciestree_ext_nodes = new HashMap<Taxonomy, PhylogenyNode>();
292         for( final PhylogenyNodeIterator iter = _species_tree.iteratorLevelOrder(); iter.hasNext(); ) {
293             final PhylogenyNode n = iter.next();
294             if ( n.getNodeData().isHasTaxonomy() ) {
295                 if ( speciestree_ext_nodes.containsKey( n.getNodeData().getTaxonomy() ) ) {
296                     throw new IllegalArgumentException( "taxonomy [" + n.getNodeData().getTaxonomy()
297                             + "] is not unique in species phylogeny" );
298                 }
299                 speciestree_ext_nodes.put( n.getNodeData().getTaxonomy(), n );
300             }
301         }
302         return speciestree_ext_nodes;
303     }
304
305     private final void stripGeneTree( final HashMap<Taxonomy, PhylogenyNode> speciestree_ext_nodes ) {
306       //  final Set<PhylogenyNode> to_delete = new HashSet<PhylogenyNode>();
307         
308         for( final PhylogenyNodeIterator iter = _gene_tree.iteratorExternalForward(); iter.hasNext(); ) {
309             final PhylogenyNode g = iter.next();
310             if ( !g.getNodeData().isHasTaxonomy() ) {
311                 throw new IllegalArgumentException( "gene tree node " + g + " has no taxonomic data" );
312             }
313             final PhylogenyNode s = speciestree_ext_nodes.get( g.getNodeData().getTaxonomy() );
314             if ( s == null ) {
315                 _stripped_gene_tree_nodes.add( g );
316             }
317         }
318         for( final PhylogenyNode n :  _stripped_gene_tree_nodes ) {
319             _gene_tree.deleteSubtree( n, true );
320             
321         }
322         
323     }
324     
325     public Set<PhylogenyNode> getStrippedExternalGeneTreeNodes() {
326         return _stripped_gene_tree_nodes;
327     }
328
329     @Override
330     public final String toString() {
331         final StringBuffer sb = new StringBuffer();
332         sb.append( "Most parsimonious duplication model: " + _most_parsimonious_duplication_model );
333         sb.append( ForesterUtil.getLineSeparator() );
334         sb.append( "Speciations sum                    : " + getSpeciationsSum() );
335         sb.append( ForesterUtil.getLineSeparator() );
336         sb.append( "Duplications sum                   : " + getDuplicationsSum() );
337         sb.append( ForesterUtil.getLineSeparator() );
338         if ( !_most_parsimonious_duplication_model ) {
339             sb.append( "Speciation or duplications sum     : " + getSpeciationOrDuplicationEventsSum() );
340             sb.append( ForesterUtil.getLineSeparator() );
341         }
342         sb.append( "mapping cost L                     : " + computeMappingCostL() );
343         return sb.toString();
344     }
345
346     static final int[] obtainMinMaxIdIndices( final PhylogenyNode[] linked_nodes ) {
347         int max_i = 0;
348         int min_i = 0;
349         int max_i_id = -Integer.MAX_VALUE;
350         int min_i_id = Integer.MAX_VALUE;
351         for( int i = 0; i < linked_nodes.length; ++i ) {
352             final int id_i = linked_nodes[ i ].getId();
353             if ( id_i > max_i_id ) {
354                 max_i = i;
355                 max_i_id = linked_nodes[ max_i ].getId();
356             }
357             if ( id_i < min_i_id ) {
358                 min_i = i;
359                 min_i_id = linked_nodes[ min_i ].getId();
360             }
361         }
362         return new int[] { min_i, max_i };
363     }
364     /**
365      * Updates the mapping function M after the root of the gene tree has been
366      * moved by one branch. It calculates M for the root of the gene tree and
367      * one of its two children.
368      * <p>
369      * To be used ONLY by method "SDIunrooted.fastInfer(Phylogeny,Phylogeny)".
370      * <p>
371      * (Last modfied: )
372      * 
373      * @param prev_root_was_dup
374      *            true if the previous root was a duplication, false otherwise
375      * @param prev_root_c1
376      *            child 1 of the previous root
377      * @param prev_root_c2
378      *            child 2 of the previous root
379      * @return number of duplications which have been assigned in gene tree
380      */
381     // int updateM( final boolean prev_root_was_dup,
382     // final PhylogenyNode prev_root_c1, final PhylogenyNode prev_root_c2 ) {
383     // final PhylogenyNode root = getGeneTree().getRoot();
384     // if ( ( root.getChildNode1() == prev_root_c1 )
385     // || ( root.getChildNode2() == prev_root_c1 ) ) {
386     // calculateMforNode( prev_root_c1 );
387     // }
388     // else {
389     // calculateMforNode( prev_root_c2 );
390     // }
391     // Event event = null;
392     // if ( prev_root_was_dup ) {
393     // event = Event.createSingleDuplicationEvent();
394     // }
395     // else {
396     // event = Event.createSingleSpeciationEvent();
397     // }
398     // root.getPhylogenyNodeData().setEvent( event );
399     // calculateMforNode( root );
400     // return getDuplications();
401     // } // updateM( boolean, PhylogenyNode, PhylogenyNode )
402     // Helper method for updateM( boolean, PhylogenyNode, PhylogenyNode )
403     // Calculates M for PhylogenyNode n, given that M for the two children
404     // of n has been calculated.
405     // (Last modified: 10/02/01)
406     // private void calculateMforNode( final PhylogenyNode n ) {
407     // if ( !n.isExternal() ) {
408     // boolean was_duplication = n.isDuplication();
409     // PhylogenyNode a = n.getChildNode1().getLink(), b = n
410     // .getChildNode2().getLink();
411     // while ( a != b ) {
412     // if ( a.getID() > b.getID() ) {
413     // a = a.getParent();
414     // }
415     // else {
416     // b = b.getParent();
417     // }
418     // }
419     // n.setLink( a );
420     // Event event = null;
421     // if ( ( a == n.getChildNode1().getLink() )
422     // || ( a == n.getChildNode2().getLink() ) ) {
423     // event = Event.createSingleDuplicationEvent();
424     // if ( !was_duplication ) {
425     // increaseDuplications();
426     // }
427     // }
428     // else {
429     // event = Event.createSingleSpeciationEvent();
430     // if ( was_duplication ) {
431     // decreaseDuplications();
432     // }
433     // }
434     // n.getPhylogenyNodeData().setEvent( event );
435     // }
436     // } // calculateMforNode( PhylogenyNode )
437 }