inprogress (not working)
[jalview.git] / forester / java / src / org / forester / msa_compactor / MsaCompactor.java
1 // $Id:
2 // FORESTER -- software libraries and applications
3 // for evolutionary biology research and applications.
4 //
5 // Copyright (C) 2014 Christian M. Zmasek
6 // Copyright (C) 2014 Sanford-Burnham Medical Research Institute
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 // WWW: https://sites.google.com/site/cmzmasek/home/software/forester
24
25 package org.forester.msa_compactor;
26
27 import java.io.File;
28 import java.io.IOException;
29 import java.io.Writer;
30 import java.math.RoundingMode;
31 import java.text.DecimalFormat;
32 import java.text.NumberFormat;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.List;
36 import java.util.SortedSet;
37 import java.util.TreeSet;
38
39 import org.forester.evoinference.distance.NeighborJoiningF;
40 import org.forester.evoinference.distance.PairwiseDistanceCalculator;
41 import org.forester.evoinference.distance.PairwiseDistanceCalculator.PWD_DISTANCE_METHOD;
42 import org.forester.evoinference.matrix.distance.BasicSymmetricalDistanceMatrix;
43 import org.forester.evoinference.tools.BootstrapResampler;
44 import org.forester.msa.BasicMsa;
45 import org.forester.msa.DeleteableMsa;
46 import org.forester.msa.Mafft;
47 import org.forester.msa.Msa;
48 import org.forester.msa.Msa.MSA_FORMAT;
49 import org.forester.msa.MsaInferrer;
50 import org.forester.msa.MsaMethods;
51 import org.forester.msa.ResampleableMsa;
52 import org.forester.phylogeny.Phylogeny;
53 import org.forester.phylogeny.PhylogenyMethods;
54 import org.forester.sequence.Sequence;
55 import org.forester.tools.ConfidenceAssessor;
56 import org.forester.util.ForesterUtil;
57
58 public class MsaCompactor {
59
60     final private static NumberFormat NF_3         = new DecimalFormat( "#.###" );
61     final private static NumberFormat NF_4         = new DecimalFormat( "#.####" );
62     //   private final String              _maffts_opts = "--retree 1";
63     private final String              _maffts_opts = "--auto";
64     private DeleteableMsa             _msa;
65     private File                      _out_file_base;
66     private String                    _path_to_mafft;
67     private final SortedSet<String>   _removed_seq_ids;
68     static {
69         NF_4.setRoundingMode( RoundingMode.HALF_UP );
70         NF_3.setRoundingMode( RoundingMode.HALF_UP );
71     }
72
73     private MsaCompactor( final DeleteableMsa msa ) {
74         _msa = msa;
75         _removed_seq_ids = new TreeSet<String>();
76     }
77
78     final public Msa getMsa() {
79         return _msa;
80     }
81
82     final public SortedSet<String> getRemovedSeqIds() {
83         return _removed_seq_ids;
84     }
85
86     final public void setOutFileBase( final File out_file_base ) {
87         _out_file_base = out_file_base;
88     }
89
90     final public String writeMsa( final File outfile, final MSA_FORMAT format, final String suffix ) throws IOException {
91         final Double gr = MsaMethods.calcGapRatio( _msa );
92         final String s = outfile + "_" + _msa.getNumberOfSequences() + "_" + _msa.getLength() + "_"
93                 + ForesterUtil.roundToInt( gr * 100 );
94         writeMsa( s + suffix, format );
95         return s;
96     }
97
98     final int calcNonGapResidues( final Sequence seq ) {
99         int ng = 0;
100         for( int i = 0; i < seq.getLength(); ++i ) {
101             if ( !seq.isGapAt( i ) ) {
102                 ++ng;
103             }
104         }
105         return ng;
106     }
107
108     Phylogeny pi( final String matrix ) {
109         final Phylogeny master_phy = inferNJphylogeny( PWD_DISTANCE_METHOD.KIMURA_DISTANCE, _msa, true, matrix );
110         final int seed = 15;
111         final int n = 100;
112         final ResampleableMsa resampleable_msa = new ResampleableMsa( _msa );
113         final int[][] resampled_column_positions = BootstrapResampler.createResampledColumnPositions( _msa.getLength(),
114                                                                                                       n,
115                                                                                                       seed );
116         final Phylogeny[] eval_phys = new Phylogeny[ n ];
117         for( int i = 0; i < n; ++i ) {
118             resampleable_msa.resample( resampled_column_positions[ i ] );
119             eval_phys[ i ] = inferNJphylogeny( PWD_DISTANCE_METHOD.KIMURA_DISTANCE, resampleable_msa, false, null );
120         }
121         ConfidenceAssessor.evaluate( "bootstrap", eval_phys, master_phy, true, 1 );
122         PhylogenyMethods.extractFastaInformation( master_phy );
123         return master_phy;
124     }
125
126     private final GapContribution[] calcGapContribtions( final boolean normalize_for_effective_seq_length ) {
127         final double gappiness[] = calcGappiness();
128         final GapContribution stats[] = new GapContribution[ _msa.getNumberOfSequences() ];
129         for( int row = 0; row < _msa.getNumberOfSequences(); ++row ) {
130             stats[ row ] = new GapContribution( _msa.getIdentifier( row ) );
131             for( int col = 0; col < _msa.getLength(); ++col ) {
132                 if ( !_msa.isGapAt( row, col ) ) {
133                     stats[ row ].addToValue( gappiness[ col ] );
134                 }
135             }
136             if ( normalize_for_effective_seq_length ) {
137                 stats[ row ].divideValue( calcNonGapResidues( _msa.getSequence( row ) ) );
138             }
139             else {
140                 stats[ row ].divideValue( _msa.getLength() );
141             }
142         }
143         return stats;
144     }
145
146     final private GapContribution[] calcGapContribtionsStats( final boolean norm ) {
147         final GapContribution stats[] = calcGapContribtions( norm );
148         Arrays.sort( stats );
149         return stats;
150     }
151
152     private final double[] calcGappiness() {
153         final int l = _msa.getLength();
154         final double gappiness[] = new double[ l ];
155         final int seqs = _msa.getNumberOfSequences();
156         for( int i = 0; i < l; ++i ) {
157             gappiness[ i ] = ( double ) MsaMethods.calcGapSumPerColumn( _msa, i ) / seqs;
158         }
159         return gappiness;
160     }
161
162     final private List<MsaProperties> chart( final int step,
163                                              final boolean realign,
164                                              final boolean norm,
165                                              final boolean verbose ) throws IOException, InterruptedException {
166         final GapContribution stats[] = calcGapContribtionsStats( norm );
167         final List<String> to_remove_ids = new ArrayList<String>();
168         final List<MsaProperties> msa_props = new ArrayList<MsaProperties>();
169         for( final GapContribution gap_gontribution : stats ) {
170             to_remove_ids.add( gap_gontribution.getId() );
171         }
172         if ( verbose ) {
173             printTableHeader();
174         }
175         int i = 0;
176         final int s = _msa.getNumberOfSequences();
177         final int x = ForesterUtil.roundToInt( s / 20.0 );
178         while ( _msa.getNumberOfSequences() > x ) {
179             final String id = to_remove_ids.get( i );
180             //~_msa = MsaMethods.removeSequence( _msa, id );
181             _msa.deleteRow( id );
182             if ( ( s < 500 ) || ( ( step > 0 ) && ( ( ( i + 1 ) % step ) == 0 ) ) ) {
183                 removeGapColumns();
184                 if ( realign && ( ( step > 0 ) && ( ( ( i + 1 ) % step ) == 0 ) ) ) {
185                     realignWithMafft();
186                     msa_props.add( new MsaProperties( _msa ) );
187                     if ( verbose ) {
188                         printMsaStats( id );
189                     }
190                     if ( verbose ) {
191                         System.out.print( "(realigned)" );
192                     }
193                 }
194                 else {
195                     msa_props.add( new MsaProperties( _msa ) );
196                     if ( verbose ) {
197                         printMsaStats( id );
198                     }
199                 }
200                 if ( verbose ) {
201                     System.out.println();
202                 }
203             }
204             ++i;
205         }
206         return msa_props;
207     }
208
209     private Phylogeny inferNJphylogeny( final PWD_DISTANCE_METHOD pwd_distance_method,
210                                         final Msa msa,
211                                         final boolean write_matrix,
212                                         final String matrix_name ) {
213         BasicSymmetricalDistanceMatrix m = null;
214         switch ( pwd_distance_method ) {
215             case KIMURA_DISTANCE:
216                 m = PairwiseDistanceCalculator.calcKimuraDistances( msa );
217                 break;
218             case POISSON_DISTANCE:
219                 m = PairwiseDistanceCalculator.calcPoissonDistances( msa );
220                 break;
221             case FRACTIONAL_DISSIMILARITY:
222                 m = PairwiseDistanceCalculator.calcFractionalDissimilarities( msa );
223                 break;
224             default:
225                 throw new IllegalArgumentException( "invalid pwd method" );
226         }
227         if ( write_matrix ) {
228             try {
229                 m.write( ForesterUtil.createBufferedWriter( matrix_name ) );
230             }
231             catch ( final IOException e ) {
232                 e.printStackTrace();
233             }
234         }
235         final NeighborJoiningF nj = NeighborJoiningF.createInstance( false, 5 );
236         final Phylogeny phy = nj.execute( m );
237         return phy;
238     }
239
240     private StringBuilder msaStatsAsSB() {
241         final StringBuilder sb = new StringBuilder();
242         sb.append( _msa.getNumberOfSequences() );
243         sb.append( "\t" );
244         sb.append( _msa.getLength() );
245         sb.append( "\t" );
246         sb.append( NF_4.format( MsaMethods.calcGapRatio( _msa ) ) );
247         sb.append( "\t" );
248         sb.append( NF_4.format( MsaMethods.calculateIdentityRatio( 0, _msa.getLength() - 1, _msa ).arithmeticMean() ) );
249         return sb;
250     }
251
252     private final void printMsaStats( final String id ) {
253         System.out.print( ForesterUtil.pad( id, 20, ' ', false ) );
254         System.out.print( "\t" );
255         final StringBuilder sb = msaStatsAsSB();
256         System.out.print( sb );
257         System.out.print( "\t" );
258     }
259
260     final private void printMsaStatsWriteOutfileAndRealign( final boolean realign,
261                                                             final boolean verbose,
262                                                             final String id ) throws IOException, InterruptedException {
263         if ( realign ) {
264             realignWithMafft();
265         }
266         if ( verbose ) {
267             printMsaStats( id );
268         }
269         final String s = writeOutfile();
270         if ( verbose ) {
271             System.out.print( "-> " + s + ( realign ? "\t(realigned)" : "" ) );
272         }
273     }
274
275     final private void realignWithMafft() throws IOException, InterruptedException {
276         //  final MsaInferrer mafft = Mafft
277         //       .createInstance( "/home/czmasek/SOFTWARE/MSA/MAFFT/mafft-7.130-without-extensions/scripts/mafft" );
278         final MsaInferrer mafft = Mafft.createInstance( _path_to_mafft );
279         final List<String> opts = new ArrayList<String>();
280         for( final String o : _maffts_opts.split( "\\s" ) ) {
281             opts.add( o );
282         }
283         //opts.add( "--maxiterate" );
284         //opts.add( "1000" );
285         //opts.add( "--localpair" );
286         //opts.add( "--quiet" );
287         _msa = new DeleteableMsa( ( BasicMsa ) mafft.infer( _msa.asSequenceList(), opts ) );
288     }
289
290     final private void removeGapColumns() {
291         //~ _msa = MsaMethods.createInstance().removeGapColumns( 1, 0, _msa );
292         MsaMethods.removeGapColumns( 1, _msa );
293     }
294
295     final private void removeViaGapAverage( final double mean_gapiness,
296                                             final int step,
297                                             final boolean realign,
298                                             final boolean norm,
299                                             final boolean verbose ) throws IOException, InterruptedException {
300         final GapContribution stats[] = calcGapContribtionsStats( norm );
301         final List<String> to_remove_ids = new ArrayList<String>();
302         for( final GapContribution gap_gontribution : stats ) {
303             to_remove_ids.add( gap_gontribution.getId() );
304         }
305         if ( verbose ) {
306             printTableHeader();
307         }
308         int i = 0;
309         while ( MsaMethods.calcGapRatio( _msa ) > mean_gapiness ) {
310             final String id = to_remove_ids.get( i );
311             //`_msa = MsaMethods.removeSequence( _msa, id );
312             _msa.deleteRow( id );
313             removeGapColumns();
314             if ( ( ( step > 0 ) && ( ( ( i + 1 ) % step ) == 0 ) )
315                     || ( MsaMethods.calcGapRatio( _msa ) <= mean_gapiness ) ) {
316                 printMsaStatsWriteOutfileAndRealign( realign, verbose, id );
317             }
318             else if ( verbose ) {
319                 printMsaStats( id );
320             }
321             if ( verbose ) {
322                 System.out.println();
323             }
324             ++i;
325         }
326     }
327
328     final private void removeViaLength( final int length,
329                                         final int step,
330                                         final boolean realign,
331                                         final boolean norm,
332                                         final boolean verbose ) throws IOException, InterruptedException {
333         final GapContribution stats[] = calcGapContribtionsStats( norm );
334         final List<String> to_remove_ids = new ArrayList<String>();
335         for( final GapContribution gap_gontribution : stats ) {
336             to_remove_ids.add( gap_gontribution.getId() );
337         }
338         if ( verbose ) {
339             printTableHeader();
340         }
341         int i = 0;
342         while ( _msa.getLength() > length ) {
343             final String id = to_remove_ids.get( i );
344             //~_msa = MsaMethods.removeSequence( _msa, id );
345             _msa.deleteRow( id );
346             removeGapColumns();
347             if ( ( ( step > 0 ) && ( ( ( i + 1 ) % step ) == 0 ) ) || ( _msa.getLength() <= length ) ) {
348                 printMsaStatsWriteOutfileAndRealign( realign, verbose, id );
349             }
350             else if ( verbose ) {
351                 printMsaStats( id );
352             }
353             if ( verbose ) {
354                 System.out.println();
355             }
356             ++i;
357         }
358     }
359
360     final private void removeWorstOffenders( final int to_remove,
361                                              final int step,
362                                              final boolean realign,
363                                              final boolean norm,
364                                              final boolean verbose ) throws IOException, InterruptedException {
365         final GapContribution stats[] = calcGapContribtionsStats( norm );
366         final List<String> to_remove_ids = new ArrayList<String>();
367         for( int j = 0; j < to_remove; ++j ) {
368             to_remove_ids.add( stats[ j ].getId() );
369             _removed_seq_ids.add( stats[ j ].getId() );
370         }
371         if ( verbose ) {
372             printTableHeader();
373         }
374         for( int i = 0; i < to_remove_ids.size(); ++i ) {
375             final String id = to_remove_ids.get( i );
376             //~ _msa = MsaMethods.removeSequence( _msa, id );
377             _msa.deleteRow( id );
378             removeGapColumns();
379             if ( ( ( step > 0 ) && ( ( ( i + 1 ) % step ) == 0 ) ) || ( i == ( to_remove_ids.size() - 1 ) ) ) {
380                 printMsaStatsWriteOutfileAndRealign( realign, verbose, id );
381             }
382             else if ( verbose ) {
383                 printMsaStats( id );
384             }
385             if ( verbose ) {
386                 System.out.println();
387             }
388         }
389     }
390
391     private void setPathToMafft( final String path_to_mafft ) {
392         _path_to_mafft = path_to_mafft;
393     }
394
395     final private void writeMsa( final String outfile, final MSA_FORMAT format ) throws IOException {
396         final Writer w = ForesterUtil.createBufferedWriter( outfile );
397         _msa.write( w, format );
398         w.close();
399     }
400
401     private String writeOutfile() throws IOException {
402         final String s = writeMsa( _out_file_base, MSA_FORMAT.PHYLIP, ".aln" );
403         //writeMsa( _out_file_base, MSA_FORMAT.FASTA, ".fasta" );
404         return s;
405     }
406
407     public final static MsaCompactor chart( final DeleteableMsa msa,
408                                             final int step,
409                                             final boolean realign,
410                                             final boolean norm,
411                                             final String path_to_mafft ) throws IOException, InterruptedException {
412         final int initial_number_of_seqs = msa.getNumberOfSequences();
413         final MsaCompactor mc = new MsaCompactor( msa );
414         if ( realign ) {
415             mc.setPathToMafft( path_to_mafft );
416         }
417         final List<MsaProperties> msa_props = mc.chart( step, realign, norm, true );
418         Chart.display( msa_props, initial_number_of_seqs );
419         return mc;
420     }
421
422     // Returns null if not path found.
423     final public static String guessPathToMafft() {
424         String path;
425         if ( ForesterUtil.OS_NAME.toLowerCase().indexOf( "win" ) >= 0 ) {
426             path = "C:\\Program Files\\mafft-win\\mafft.bat";
427             if ( MsaInferrer.isInstalled( path ) ) {
428                 return path;
429             }
430         }
431         path = "/home/czmasek/SOFTWARE/MSA/MAFFT/mafft-7.130-without-extensions/scripts/mafft";
432         if ( MsaInferrer.isInstalled( path ) ) {
433             return path;
434         }
435         path = "/usr/local/bin/mafft";
436         if ( MsaInferrer.isInstalled( path ) ) {
437             return path;
438         }
439         path = "/usr/bin/mafft";
440         if ( MsaInferrer.isInstalled( path ) ) {
441             return path;
442         }
443         path = "/bin/mafft";
444         if ( MsaInferrer.isInstalled( path ) ) {
445             return path;
446         }
447         path = "mafft";
448         if ( MsaInferrer.isInstalled( path ) ) {
449             return path;
450         }
451         return null;
452     }
453
454     public final static MsaCompactor reduceGapAverage( final DeleteableMsa msa,
455                                                        final double max_gap_average,
456                                                        final int step,
457                                                        final boolean realign,
458                                                        final boolean norm,
459                                                        final String path_to_mafft,
460                                                        final File out ) throws IOException, InterruptedException {
461         final MsaCompactor mc = new MsaCompactor( msa );
462         if ( realign ) {
463             mc.setPathToMafft( path_to_mafft );
464         }
465         mc.setOutFileBase( out );
466         mc.removeViaGapAverage( max_gap_average, step, realign, norm, true );
467         return mc;
468     }
469
470     public final static MsaCompactor reduceLength( final DeleteableMsa msa,
471                                                    final int length,
472                                                    final int step,
473                                                    final boolean realign,
474                                                    final boolean norm,
475                                                    final String path_to_mafft,
476                                                    final File out ) throws IOException, InterruptedException {
477         final MsaCompactor mc = new MsaCompactor( msa );
478         if ( realign ) {
479             mc.setPathToMafft( path_to_mafft );
480         }
481         mc.setOutFileBase( out );
482         mc.removeViaLength( length, step, realign, norm, true );
483         return mc;
484     }
485
486     public final static MsaCompactor removeWorstOffenders( final DeleteableMsa msa,
487                                                            final int worst_offenders_to_remove,
488                                                            final int step,
489                                                            final boolean realign,
490                                                            final boolean norm,
491                                                            final String path_to_mafft,
492                                                            final File out ) throws IOException, InterruptedException {
493         final MsaCompactor mc = new MsaCompactor( msa );
494         if ( realign ) {
495             mc.setPathToMafft( path_to_mafft );
496         }
497         mc.setOutFileBase( out );
498         mc.removeWorstOffenders( worst_offenders_to_remove, step, realign, norm, true );
499         return mc;
500     }
501
502     private final static void printTableHeader() {
503         System.out.print( ForesterUtil.pad( "Id", 20, ' ', false ) );
504         System.out.print( "\t" );
505         System.out.print( "Seqs" );
506         System.out.print( "\t" );
507         System.out.print( "Length" );
508         System.out.print( "\t" );
509         System.out.print( "Gaps" );
510         System.out.print( "\t" );
511         System.out.print( "MSA qual" );
512         System.out.print( "\t" );
513         System.out.println();
514     }
515 }