refactoring
[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.Identifier;
36 import org.forester.phylogeny.data.Taxonomy;
37 import org.forester.phylogeny.iterators.PhylogenyNodeIterator;
38 import org.forester.util.ForesterUtil;
39
40 public abstract class SDI {
41
42     final Phylogeny _gene_tree;
43     final Phylogeny _species_tree;
44     int             _duplications_sum; // Sum of duplications.
45     int             _mapping_cost;    // Mapping cost "L".
46
47     /**
48      * Constructor which sets the gene tree and the species tree to be compared.
49      * species_tree is the species tree to which the gene tree gene_tree will be
50      * compared to.
51      * Infers for each PhylogenyNode of gene_tree whether
52      * it represents a speciation or duplication event by calculating and
53      * interpreting the mapping function M. The most parsimonious sequence of
54      * speciation and duplication events is assumed.
55      * The mapping cost L can be
56      * calculated with method "computeMappingCost()".
57      * <p>
58      * Conditions:
59      * </p>
60      * <ul>
61      * <li>Both Trees must be rooted
62      * <li>Both Trees must have species names in the species name fields of all
63      * their external nodes
64      * </ul>
65      * 
66      * @param gene_tree
67      *            reference to a rooted binary gene Phylogeny to which assign
68      *            duplication vs speciation, must have species names in the
69      *            species name fields for all external nodes
70      * @param species_tree
71      *            reference to a rooted binary species Phylogeny which might get
72      *            stripped in the process, must have species names in the
73      *            species name fields for all external nodes
74      */
75     public SDI( final Phylogeny gene_tree, final Phylogeny species_tree ) {
76         if ( species_tree.isEmpty() || gene_tree.isEmpty() ) {
77             throw new IllegalArgumentException( "attempt to infer duplications using empty tree(s)" );
78         }
79         if ( !gene_tree.isRooted() ) {
80             throw new IllegalArgumentException( "attempt to infer duplications on unrooted gene tree" );
81         }
82         if ( !species_tree.isRooted() ) {
83             throw new IllegalArgumentException( "attempt to infer duplications on unrooted species tree" );
84         }
85         _gene_tree = gene_tree;
86         _species_tree = species_tree;
87         _duplications_sum = 0;
88         _mapping_cost = -1;
89     }
90
91     // Helper method for "computeMappingCost()".
92     private void computeMappingCostHelper( final PhylogenyNode g ) {
93         if ( !g.isExternal() ) {
94             computeMappingCostHelper( g.getChildNode1() );
95             computeMappingCostHelper( g.getChildNode2() );
96             if ( ( g.getLink() != g.getChildNode1().getLink() ) && ( g.getLink() != g.getChildNode2().getLink() ) ) {
97                 _mapping_cost += ( ( g.getChildNode1().getLink().getId() + g.getChildNode2().getLink().getId() )
98                         - ( 2 * g.getLink().getId() ) - 2 );
99             }
100             else if ( ( g.getLink() != g.getChildNode1().getLink() ) && ( g.getLink() == g.getChildNode2().getLink() ) ) {
101                 _mapping_cost += ( ( g.getChildNode1().getLink().getId() - g.getLink().getId() ) + 1 );
102             }
103             else if ( ( g.getLink() == g.getChildNode1().getLink() ) && ( g.getLink() != g.getChildNode2().getLink() ) ) {
104                 _mapping_cost += ( ( g.getChildNode2().getLink().getId() - g.getLink().getId() ) + 1 );
105             }
106             else {
107                 _mapping_cost++;
108             }
109         }
110     }
111
112     /**
113      * Computes the cost of mapping the gene tree gene_tree onto the species
114      * tree species_tree. Before this method can be called, the mapping has to
115      * be calculated with method "infer(boolean)".
116      * <p>
117      * Reference. Zhang, L. (1997) On a Mirkin-Muchnik-Smith Conjecture for
118      * Comparing Molecular Phylogenies. Journal of Computational Biology 4
119      * 177-187.
120      * 
121      * @return the mapping cost "L"
122      */
123     public int computeMappingCostL() {
124         _species_tree.levelOrderReID();
125         _mapping_cost = 0;
126         computeMappingCostHelper( _gene_tree.getRoot() );
127         return _mapping_cost;
128     }
129
130     private TaxonomyComparisonBase determineTaxonomyComparisonBase() {
131         TaxonomyComparisonBase base = null;
132         boolean all_have_id = true;
133         boolean all_have_code = true;
134         boolean all_have_sn = 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             }
149             else {
150                 throw new IllegalArgumentException( "species tree node [" + n + "] has no taxonomic data" );
151             }
152         }
153         for( final PhylogenyNodeIterator iter = _gene_tree.iteratorExternalForward(); iter.hasNext(); ) {
154             final PhylogenyNode n = iter.next();
155             if ( n.getNodeData().isHasTaxonomy() ) {
156                 final Taxonomy tax = n.getNodeData().getTaxonomy();
157                 if ( ( tax.getIdentifier() == null ) || ForesterUtil.isEmpty( tax.getIdentifier().getValue() ) ) {
158                     all_have_id = false;
159                 }
160                 if ( ForesterUtil.isEmpty( tax.getTaxonomyCode() ) ) {
161                     all_have_code = false;
162                 }
163                 if ( ForesterUtil.isEmpty( tax.getScientificName() ) ) {
164                     all_have_sn = false;
165                 }
166             }
167             else {
168                 throw new IllegalArgumentException( "gene tree node [" + n + "] has no taxonomic data" );
169             }
170         }
171         if ( all_have_id ) {
172             base = TaxonomyComparisonBase.ID;
173         }
174         else if ( all_have_code ) {
175             base = TaxonomyComparisonBase.CODE;
176         }
177         else if ( all_have_sn ) {
178             base = TaxonomyComparisonBase.SCIENTIFIC_NAME;
179         }
180         else {
181             throw new IllegalArgumentException( "gene tree and species tree have incomparable taxonomies" );
182         }
183         return base;
184     }
185
186     /**
187      * Returns the number of duplications.
188      * 
189      * @return number of duplications
190      */
191     public int getDuplicationsSum() {
192         return _duplications_sum;
193     }
194
195     /**
196      * Returns the gene tree.
197      * 
198      * @return gene tree
199      */
200     public Phylogeny getGeneTree() {
201         return _gene_tree;
202     }
203
204     /**
205      * Returns the species tree.
206      * 
207      * @return species tree
208      */
209     public Phylogeny getSpeciesTree() {
210         return _species_tree;
211     }
212
213     /**
214      * Calculates the mapping function for the external nodes of the gene tree:
215      * links (sets the field "link" of PhylogenyNode) each external
216      * PhylogenyNode of gene_tree to the external PhylogenyNode of species_tree
217      * which has the same species name.
218      * @throws SDIException 
219      */
220     void linkNodesOfG() throws SDIException {
221         final Map<String, PhylogenyNode> speciestree_ext_nodes = new HashMap<String, PhylogenyNode>();
222         final TaxonomyComparisonBase tax_comp_base = determineTaxonomyComparisonBase();
223         // Put references to all external nodes of the species tree into a map.
224         // Stringyfied taxonomy is the key, node is the value.
225         for( final PhylogenyNodeIterator iter = _species_tree.iteratorExternalForward(); iter.hasNext(); ) {
226             final PhylogenyNode s = iter.next();
227             final String tax_str = taxonomyToString( s, tax_comp_base );
228             if ( speciestree_ext_nodes.containsKey( tax_str ) ) {
229                 throw new IllegalArgumentException( "taxonomy [" + s.getNodeData().getTaxonomy()
230                         + "] is not unique in species phylogeny" );
231             }
232             speciestree_ext_nodes.put( tax_str, s );
233         }
234         // Retrieve the reference to the node with a matching stringyfied taxonomy.
235         for( final PhylogenyNodeIterator iter = _gene_tree.iteratorExternalForward(); iter.hasNext(); ) {
236             final PhylogenyNode g = iter.next();
237             final String tax_str = taxonomyToString( g, tax_comp_base );
238             final PhylogenyNode s = speciestree_ext_nodes.get( tax_str );
239             if ( s == null ) {
240                 throw new IllegalArgumentException( "taxonomy [" + g.getNodeData().getTaxonomy()
241                         + "] not present in species tree" );
242             }
243             g.setLink( s );
244         }
245     }
246
247     /**
248      * Calculates the mapping function for the external nodes of the gene tree:
249      * links (sets the field "link" of PhylogenyNode) each external by taxonomy
250      * identifier
251      * PhylogenyNode of gene_tree to the external PhylogenyNode of species_tree
252      * which has the same species name.
253      * Olivier CHABROL : olivier.chabrol@univ-provence.fr
254      */
255     void linkNodesOfGByTaxonomyIdentifier() {
256         final HashMap<String, PhylogenyNode> speciestree_ext_nodes = new HashMap<String, PhylogenyNode>();
257         if ( _species_tree.getFirstExternalNode().isRoot() ) {
258             speciestree_ext_nodes.put( _species_tree.getFirstExternalNode().getNodeData().getTaxonomy().getIdentifier()
259                     .getValue(), _species_tree.getFirstExternalNode() );
260         }
261         else {
262             for( final PhylogenyNodeIterator iter = _species_tree.iteratorExternalForward(); iter.hasNext(); ) {
263                 final PhylogenyNode s = iter.next();
264                 speciestree_ext_nodes.put( s.getNodeData().getTaxonomy().getIdentifier().getValue(), s );
265             }
266         }
267         for( final PhylogenyNodeIterator iter = _gene_tree.iteratorExternalForward(); iter.hasNext(); ) {
268             final PhylogenyNode g = iter.next();
269             final PhylogenyNode s = speciestree_ext_nodes
270                     .get( g.getNodeData().getTaxonomy().getIdentifier().getValue() );
271             if ( s == null ) {
272                 String message = "species [" + g.getNodeData().getTaxonomy().getIdentifier().getValue();
273                 message += "] not present in species tree";
274                 throw new IllegalArgumentException( message );
275             }
276             g.setLink( s );
277         }
278     }
279
280     @Override
281     public String toString() {
282         final StringBuffer sb = new StringBuffer();
283         sb.append( getClass() );
284         sb.append( ForesterUtil.LINE_SEPARATOR );
285         sb.append( "Duplications sum                   : " + getDuplicationsSum() );
286         sb.append( ForesterUtil.LINE_SEPARATOR );
287         sb.append( "mapping cost L                     : " + computeMappingCostL() );
288         return sb.toString();
289     }
290
291     static String taxonomyToString( final PhylogenyNode n, final TaxonomyComparisonBase base ) {
292         switch ( base ) {
293             case ID:
294                 final Identifier id = n.getNodeData().getTaxonomy().getIdentifier();
295                 if ( id == null ) {
296                     return null;
297                 }
298                 return id.getValuePlusProvider();
299             case CODE:
300                 return n.getNodeData().getTaxonomy().getTaxonomyCode();
301             case SCIENTIFIC_NAME:
302                 return n.getNodeData().getTaxonomy().getScientificName();
303             default:
304                 throw new IllegalArgumentException( "unknown comparison base for taxonomies: " + base );
305         }
306     }
307
308     public enum TaxonomyComparisonBase {
309         ID {
310
311             @Override
312             public String toString() {
313                 return "taxonomy id";
314             }
315         },
316         CODE {
317
318             @Override
319             public String toString() {
320                 return "taxonomy code/mnemonic";
321             }
322         },
323         SCIENTIFIC_NAME {
324
325             @Override
326             public String toString() {
327                 return "scientific name";
328             }
329         }
330     }
331 }