in progress...
[jalview.git] / forester / java / src / org / forester / sdi / SDI.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 // Copyright (C) 2000-2001 Washington University School of Medicine
8 // and Howard Hughes Medical Institute
9 // All rights reserved
10 //
11 // This library is free software; you can redistribute it and/or
12 // modify it under the terms of the GNU Lesser General Public
13 // License as published by the Free Software Foundation; either
14 // version 2.1 of the License, or (at your option) any later version.
15 //
16 // This library is distributed in the hope that it will be useful,
17 // but WITHOUT ANY WARRANTY; without even the implied warranty of
18 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 // Lesser General Public License for more details.
20 //
21 // You should have received a copy of the GNU Lesser General Public
22 // License along with this library; if not, write to the Free Software
23 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 //
25 // Contact: phylosoft @ gmail . com
26 // WWW: www.phylosoft.org
27
28 package org.forester.sdi;
29
30 import java.util.HashMap;
31 import java.util.Map;
32
33 import org.forester.phylogeny.Phylogeny;
34 import org.forester.phylogeny.PhylogenyNode;
35 import org.forester.phylogeny.data.Taxonomy;
36 import org.forester.phylogeny.iterators.PhylogenyNodeIterator;
37 import org.forester.util.ForesterUtil;
38
39 public abstract class SDI {
40
41     final Phylogeny _gene_tree;
42     final Phylogeny _species_tree;
43     int             _duplications_sum; // Sum of duplications.
44     int             _mapping_cost;    // Mapping cost "L".
45
46     /**
47      * Constructor which sets the gene tree and the species tree to be compared.
48      * species_tree is the species tree to which the gene tree gene_tree will be
49      * compared to.
50      * Infers for each PhylogenyNode of gene_tree whether
51      * it represents a speciation or duplication event by calculating and
52      * interpreting the mapping function M. The most parsimonious sequence of
53      * speciation and duplication events is assumed.
54      * The mapping cost L can be
55      * calculated with method "computeMappingCost()".
56      * <p>
57      * Conditions:
58      * </p>
59      * <ul>
60      * <li>Both Trees must be rooted
61      * <li>Both Trees must have species names in the species name fields of all
62      * their external nodes
63      * </ul>
64      * 
65      * @param gene_tree
66      *            reference to a rooted binary gene Phylogeny to which assign
67      *            duplication vs speciation, must have species names in the
68      *            species name fields for all external nodes
69      * @param species_tree
70      *            reference to a rooted binary species Phylogeny which might get
71      *            stripped in the process, must have species names in the
72      *            species name fields for all external nodes
73      */
74     public SDI( final Phylogeny gene_tree, final Phylogeny species_tree ) {
75         if ( species_tree.isEmpty() || gene_tree.isEmpty() ) {
76             throw new IllegalArgumentException( "attempt to infer duplications using empty tree(s)" );
77         }
78         if ( !gene_tree.isRooted() ) {
79             throw new IllegalArgumentException( "attempt to infer duplications on unrooted gene tree" );
80         }
81         if ( !species_tree.isRooted() ) {
82             throw new IllegalArgumentException( "attempt to infer duplications on unrooted species tree" );
83         }
84         _gene_tree = gene_tree;
85         _species_tree = species_tree;
86         _duplications_sum = 0;
87         _mapping_cost = -1;
88     }
89
90     // Helper method for "computeMappingCost()".
91     private void computeMappingCostHelper( final PhylogenyNode g ) {
92         if ( !g.isExternal() ) {
93             computeMappingCostHelper( g.getChildNode1() );
94             computeMappingCostHelper( g.getChildNode2() );
95             if ( ( g.getLink() != g.getChildNode1().getLink() ) && ( g.getLink() != g.getChildNode2().getLink() ) ) {
96                 _mapping_cost += ( g.getChildNode1().getLink().getId() + g.getChildNode2().getLink().getId()
97                         - ( 2 * g.getLink().getId() ) - 2 );
98             }
99             else if ( ( g.getLink() != g.getChildNode1().getLink() ) && ( g.getLink() == g.getChildNode2().getLink() ) ) {
100                 _mapping_cost += ( g.getChildNode1().getLink().getId() - g.getLink().getId() + 1 );
101             }
102             else if ( ( g.getLink() == g.getChildNode1().getLink() ) && ( g.getLink() != g.getChildNode2().getLink() ) ) {
103                 _mapping_cost += ( g.getChildNode2().getLink().getId() - g.getLink().getId() + 1 );
104             }
105             else {
106                 _mapping_cost++;
107             }
108         }
109     }
110
111     /**
112      * Computes the cost of mapping the gene tree gene_tree onto the species
113      * tree species_tree. Before this method can be called, the mapping has to
114      * be calculated with method "infer(boolean)".
115      * <p>
116      * Reference. Zhang, L. (1997) On a Mirkin-Muchnik-Smith Conjecture for
117      * Comparing Molecular Phylogenies. Journal of Computational Biology 4
118      * 177-187.
119      * 
120      * @return the mapping cost "L"
121      */
122     public int computeMappingCostL() {
123         _species_tree.levelOrderReID();
124         _mapping_cost = 0;
125         computeMappingCostHelper( _gene_tree.getRoot() );
126         return _mapping_cost;
127     }
128
129     private TaxonomyComparisonBase determineTaxonomyComparisonBase() {
130         TaxonomyComparisonBase base = null;
131         boolean all_have_id = true;
132         boolean all_have_code = true;
133         boolean all_have_sn = true;
134         boolean all_have_cn = true;
135         for( final PhylogenyNodeIterator iter = _species_tree.iteratorExternalForward(); iter.hasNext(); ) {
136             final PhylogenyNode n = iter.next();
137             if ( n.getNodeData().isHasTaxonomy() ) {
138                 final Taxonomy tax = n.getNodeData().getTaxonomy();
139                 if ( ( tax.getIdentifier() == null ) || ForesterUtil.isEmpty( tax.getIdentifier().getValue() ) ) {
140                     all_have_id = false;
141                 }
142                 if ( ForesterUtil.isEmpty( tax.getTaxonomyCode() ) ) {
143                     all_have_code = false;
144                 }
145                 if ( ForesterUtil.isEmpty( tax.getScientificName() ) ) {
146                     all_have_sn = false;
147                 }
148                 if ( ForesterUtil.isEmpty( tax.getCommonName() ) ) {
149                     all_have_cn = false;
150                 }
151             }
152             else {
153                 throw new IllegalArgumentException( "species tree node [" + n + "] has no taxonomic data" );
154             }
155         }
156         for( final PhylogenyNodeIterator iter = _gene_tree.iteratorExternalForward(); iter.hasNext(); ) {
157             final PhylogenyNode n = iter.next();
158             if ( n.getNodeData().isHasTaxonomy() ) {
159                 final Taxonomy tax = n.getNodeData().getTaxonomy();
160                 if ( ( tax.getIdentifier() == null ) || ForesterUtil.isEmpty( tax.getIdentifier().getValue() ) ) {
161                     all_have_id = false;
162                 }
163                 if ( ForesterUtil.isEmpty( tax.getTaxonomyCode() ) ) {
164                     all_have_code = false;
165                 }
166                 if ( ForesterUtil.isEmpty( tax.getScientificName() ) ) {
167                     all_have_sn = false;
168                 }
169                 if ( ForesterUtil.isEmpty( tax.getCommonName() ) ) {
170                     all_have_cn = false;
171                 }
172             }
173             else {
174                 throw new IllegalArgumentException( "gene tree node [" + n + "] has no taxonomic data" );
175             }
176         }
177         if ( all_have_id ) {
178             base = TaxonomyComparisonBase.ID;
179         }
180         else if ( all_have_code ) {
181             base = TaxonomyComparisonBase.CODE;
182         }
183         else if ( all_have_sn ) {
184             base = TaxonomyComparisonBase.SCIENTIFIC_NAME;
185         }
186         else if ( all_have_cn ) {
187             base = TaxonomyComparisonBase.COMMON_NAME;
188         }
189         else {
190             throw new IllegalArgumentException( "gene tree and species tree have incomparable taxonomies" );
191         }
192         return base;
193     }
194
195     /**
196      * Returns the number of duplications.
197      * 
198      * @return number of duplications
199      */
200     public int getDuplicationsSum() {
201         return _duplications_sum;
202     }
203
204     /**
205      * Returns the gene tree.
206      * 
207      * @return gene tree
208      */
209     public Phylogeny getGeneTree() {
210         return _gene_tree;
211     }
212
213     /**
214      * Returns the species tree.
215      * 
216      * @return species tree
217      */
218     public Phylogeny getSpeciesTree() {
219         return _species_tree;
220     }
221
222     /**
223      * Calculates the mapping function for the external nodes of the gene tree:
224      * links (sets the field "link" of PhylogenyNode) each external
225      * PhylogenyNode of gene_tree to the external PhylogenyNode of species_tree
226      * which has the same species name.
227      */
228     void linkNodesOfG() {
229         final Map<String, PhylogenyNode> speciestree_ext_nodes = new HashMap<String, PhylogenyNode>();
230         final TaxonomyComparisonBase tax_comp_base = determineTaxonomyComparisonBase();
231         // Put references to all external nodes of the species tree into a map.
232         // Stringyfied taxonomy is the key, node is the value.
233         for( final PhylogenyNodeIterator iter = _species_tree.iteratorExternalForward(); iter.hasNext(); ) {
234             final PhylogenyNode s = iter.next();
235             final String tax_str = taxonomyToString( s, tax_comp_base );
236             if ( speciestree_ext_nodes.containsKey( tax_str ) ) {
237                 throw new IllegalArgumentException( "taxonomy [" + s.getNodeData().getTaxonomy()
238                         + "] is not unique in species phylogeny" );
239             }
240             speciestree_ext_nodes.put( tax_str, s );
241         }
242         // Retrieve the reference to the node with a matching stringyfied taxonomy.
243         for( final PhylogenyNodeIterator iter = _gene_tree.iteratorExternalForward(); iter.hasNext(); ) {
244             final PhylogenyNode g = iter.next();
245             final String tax_str = taxonomyToString( g, tax_comp_base );
246             final PhylogenyNode s = speciestree_ext_nodes.get( tax_str );
247             if ( s == null ) {
248                 throw new IllegalArgumentException( "taxonomy [" + g.getNodeData().getTaxonomy()
249                         + "] not present in species tree" );
250             }
251             g.setLink( s );
252         }
253     }
254
255     /**
256      * Calculates the mapping function for the external nodes of the gene tree:
257      * links (sets the field "link" of PhylogenyNode) each external by taxonomy
258      * identifier
259      * PhylogenyNode of gene_tree to the external PhylogenyNode of species_tree
260      * which has the same species name.
261      * Olivier CHABROL : olivier.chabrol@univ-provence.fr
262      */
263     void linkNodesOfGByTaxonomyIdentifier() {
264         final HashMap<String, PhylogenyNode> speciestree_ext_nodes = new HashMap<String, PhylogenyNode>();
265         if ( _species_tree.getFirstExternalNode().isRoot() ) {
266             speciestree_ext_nodes.put( _species_tree.getFirstExternalNode().getNodeData().getTaxonomy().getIdentifier()
267                     .getValue(), _species_tree.getFirstExternalNode() );
268         }
269         else {
270             for( final PhylogenyNodeIterator iter = _species_tree.iteratorExternalForward(); iter.hasNext(); ) {
271                 final PhylogenyNode s = iter.next();
272                 speciestree_ext_nodes.put( s.getNodeData().getTaxonomy().getIdentifier().getValue(), s );
273             }
274         }
275         for( final PhylogenyNodeIterator iter = _gene_tree.iteratorExternalForward(); iter.hasNext(); ) {
276             final PhylogenyNode g = iter.next();
277             final PhylogenyNode s = speciestree_ext_nodes
278                     .get( g.getNodeData().getTaxonomy().getIdentifier().getValue() );
279             if ( s == null ) {
280                 String message = "species [" + g.getNodeData().getTaxonomy().getIdentifier().getValue();
281                 message += "] not present in species tree";
282                 throw new IllegalArgumentException( message );
283             }
284             g.setLink( s );
285         }
286     }
287
288     @Override
289     public String toString() {
290         final StringBuffer sb = new StringBuffer();
291         sb.append( getClass() );
292         sb.append( ForesterUtil.LINE_SEPARATOR );
293         sb.append( "Duplications sum                   : " + getDuplicationsSum() );
294         sb.append( ForesterUtil.LINE_SEPARATOR );
295         sb.append( "mapping cost L                     : " + computeMappingCostL() );
296         return sb.toString();
297     }
298
299     private static String taxonomyToString( final PhylogenyNode n, final TaxonomyComparisonBase base ) {
300         final Taxonomy tax = n.getNodeData().getTaxonomy();
301         switch ( base ) {
302             case ID:
303                 return tax.getIdentifier().getValue();
304             case CODE:
305                 return tax.getTaxonomyCode();
306             case SCIENTIFIC_NAME:
307                 return tax.getScientificName();
308             case COMMON_NAME:
309                 return tax.getCommonName();
310             default:
311                 throw new IllegalArgumentException( "unknown comparison base for taxonomies: " + base );
312         }
313     }
314
315     enum TaxonomyComparisonBase {
316         ID, CODE, SCIENTIFIC_NAME, COMMON_NAME;
317     }
318 }