a9e9f8a298bcb4227c37aec197fe7da0164feed1
[jalview.git] / forester / java / src / org / forester / msa_compactor / MsaCompactor.java
1
2 package org.forester.msa_compactor;
3
4 import java.io.File;
5 import java.io.IOException;
6 import java.io.Writer;
7 import java.math.RoundingMode;
8 import java.text.DecimalFormat;
9 import java.text.NumberFormat;
10 import java.util.ArrayList;
11 import java.util.Arrays;
12 import java.util.List;
13 import java.util.SortedSet;
14 import java.util.TreeSet;
15
16 import org.forester.evoinference.distance.NeighborJoiningF;
17 import org.forester.evoinference.distance.PairwiseDistanceCalculator;
18 import org.forester.evoinference.distance.PairwiseDistanceCalculator.PWD_DISTANCE_METHOD;
19 import org.forester.evoinference.matrix.distance.BasicSymmetricalDistanceMatrix;
20 import org.forester.evoinference.tools.BootstrapResampler;
21 import org.forester.msa.BasicMsa;
22 import org.forester.msa.Mafft;
23 import org.forester.msa.Msa;
24 import org.forester.msa.Msa.MSA_FORMAT;
25 import org.forester.msa.MsaInferrer;
26 import org.forester.msa.MsaMethods;
27 import org.forester.msa.ResampleableMsa;
28 import org.forester.phylogeny.Phylogeny;
29 import org.forester.phylogeny.PhylogenyMethods;
30 import org.forester.sequence.Sequence;
31 import org.forester.tools.ConfidenceAssessor;
32 import org.forester.util.BasicDescriptiveStatistics;
33 import org.forester.util.DescriptiveStatistics;
34 import org.forester.util.ForesterUtil;
35
36 public class MsaCompactor {
37
38     final private static NumberFormat NF_3    = new DecimalFormat( "#.###" );
39     final private static NumberFormat NF_4    = new DecimalFormat( "#.####" );
40     private static final boolean      VERBOSE = false;
41     private Msa                       _msa;
42     private File                      _out_file_base;
43     private String                    _path_to_mafft;
44     private final SortedSet<String>   _removed_seq_ids;
45     static {
46         NF_4.setRoundingMode( RoundingMode.HALF_UP );
47         NF_3.setRoundingMode( RoundingMode.HALF_UP );
48     }
49
50     private MsaCompactor( final Msa msa ) {
51         _msa = msa;
52         _removed_seq_ids = new TreeSet<String>();
53     }
54
55     final public Msa getMsa() {
56         return _msa;
57     }
58
59     final public SortedSet<String> getRemovedSeqIds() {
60         return _removed_seq_ids;
61     }
62
63     final public void setOutFileBase( final File out_file_base ) {
64         _out_file_base = out_file_base;
65     }
66
67     final public String writeMsa( final File outfile, final MSA_FORMAT format, final String suffix ) throws IOException {
68         final Double gr = MsaMethods.calcGapRatio( _msa );
69         final String s = outfile + "_" + _msa.getNumberOfSequences() + "_" + _msa.getLength() + "_"
70                 + ForesterUtil.roundToInt( gr * 100 );
71         writeMsa( s + suffix, format );
72         return s;
73     }
74
75     final int calcNonGapResidues( final Sequence seq ) {
76         int ng = 0;
77         for( int i = 0; i < seq.getLength(); ++i ) {
78             if ( !seq.isGapAt( i ) ) {
79                 ++ng;
80             }
81         }
82         return ng;
83     }
84
85     Phylogeny pi( final String matrix ) {
86         final Phylogeny master_phy = inferNJphylogeny( PWD_DISTANCE_METHOD.KIMURA_DISTANCE, _msa, true, matrix );
87         final int seed = 15;
88         final int n = 100;
89         final ResampleableMsa resampleable_msa = new ResampleableMsa( ( BasicMsa ) _msa );
90         final int[][] resampled_column_positions = BootstrapResampler.createResampledColumnPositions( _msa.getLength(),
91                                                                                                       n,
92                                                                                                       seed );
93         final Phylogeny[] eval_phys = new Phylogeny[ n ];
94         for( int i = 0; i < n; ++i ) {
95             resampleable_msa.resample( resampled_column_positions[ i ] );
96             eval_phys[ i ] = inferNJphylogeny( PWD_DISTANCE_METHOD.KIMURA_DISTANCE, resampleable_msa, false, null );
97         }
98         ConfidenceAssessor.evaluate( "bootstrap", eval_phys, master_phy, true, 1 );
99         PhylogenyMethods.extractFastaInformation( master_phy );
100         return master_phy;
101     }
102
103     private final GapContribution[] calcGapContribtions( final boolean normalize_for_effective_seq_length ) {
104         final double gappiness[] = calcGappiness();
105         final GapContribution stats[] = new GapContribution[ _msa.getNumberOfSequences() ];
106         for( int row = 0; row < _msa.getNumberOfSequences(); ++row ) {
107             stats[ row ] = new GapContribution( _msa.getIdentifier( row ) );
108             for( int col = 0; col < _msa.getLength(); ++col ) {
109                 if ( !_msa.isGapAt( row, col ) ) {
110                     stats[ row ].addToValue( gappiness[ col ] );
111                 }
112             }
113             if ( normalize_for_effective_seq_length ) {
114                 stats[ row ].divideValue( calcNonGapResidues( _msa.getSequence( row ) ) );
115             }
116             else {
117                 stats[ row ].divideValue( _msa.getLength() );
118             }
119         }
120         return stats;
121     }
122
123     final private GapContribution[] calcGapContribtionsStats( final boolean norm ) {
124         final GapContribution stats[] = calcGapContribtions( norm );
125         Arrays.sort( stats );
126         // for( final GapContribution stat : stats ) {
127         //  final StringBuilder sb = new StringBuilder();
128         //  sb.append( stat.getId() );
129         //  sb.append( "\t" );
130         //  sb.append( NF_4.format( stat.getValue() ) );
131         //  sb.append( "\t" );
132         //            sb.append( NF_4.format( stat.median() ) );
133         //            sb.append( "\t" );
134         //            sb.append( NF_4.format( stat.getMin() ) );
135         //            sb.append( "\t" );
136         //            sb.append( NF_4.format( stat.getMax() ) );
137         //sb.append( "\t" );
138         //System.out.println( sb );
139         // }
140         return stats;
141     }
142
143     private final double[] calcGappiness() {
144         final int l = _msa.getLength();
145         final double gappiness[] = new double[ l ];
146         final int seqs = _msa.getNumberOfSequences();
147         for( int i = 0; i < l; ++i ) {
148             gappiness[ i ] = ( double ) MsaMethods.calcGapSumPerColumn( _msa, i ) / seqs;
149         }
150         return gappiness;
151     }
152
153     private Phylogeny inferNJphylogeny( final PWD_DISTANCE_METHOD pwd_distance_method,
154                                         final Msa msa,
155                                         final boolean write_matrix,
156                                         final String matrix_name ) {
157         BasicSymmetricalDistanceMatrix m = null;
158         switch ( pwd_distance_method ) {
159             case KIMURA_DISTANCE:
160                 m = PairwiseDistanceCalculator.calcKimuraDistances( msa );
161                 break;
162             case POISSON_DISTANCE:
163                 m = PairwiseDistanceCalculator.calcPoissonDistances( msa );
164                 break;
165             case FRACTIONAL_DISSIMILARITY:
166                 m = PairwiseDistanceCalculator.calcFractionalDissimilarities( msa );
167                 break;
168             default:
169                 throw new IllegalArgumentException( "invalid pwd method" );
170         }
171         if ( write_matrix ) {
172             try {
173                 m.write( ForesterUtil.createBufferedWriter( matrix_name ) );
174             }
175             catch ( final IOException e ) {
176                 // TODO Auto-generated catch block
177                 e.printStackTrace();
178             }
179         }
180         final NeighborJoiningF nj = NeighborJoiningF.createInstance( false, 5 );
181         final Phylogeny phy = nj.execute( m );
182         return phy;
183     }
184
185     private StringBuilder msaStatsAsSB() {
186         final StringBuilder sb = new StringBuilder();
187         sb.append( _msa.getNumberOfSequences() );
188         sb.append( "\t" );
189         sb.append( _msa.getLength() );
190         sb.append( "\t" );
191         sb.append( NF_3.format( MsaMethods.calcGapRatio( _msa ) ) );
192         sb.append( "\t" );
193         sb.append( NF_3.format( calculateIdentityRatio( 0, _msa.getLength() - 1, _msa ).arithmeticMean() ) );
194         return sb;
195     }
196
197     final private void realignWithMafft() throws IOException, InterruptedException {
198         //  final MsaInferrer mafft = Mafft
199         //       .createInstance( "/home/czmasek/SOFTWARE/MSA/MAFFT/mafft-7.130-without-extensions/scripts/mafft" );
200         final MsaInferrer mafft = Mafft.createInstance( _path_to_mafft );
201         final List<String> opts = new ArrayList<String>();
202         opts.add( "--maxiterate" );
203         opts.add( "1000" );
204         opts.add( "--localpair" );
205         opts.add( "--quiet" );
206         _msa = mafft.infer( _msa.asSequenceList(), opts );
207     }
208
209     final private void removeGapColumns() {
210         _msa = MsaMethods.createInstance().removeGapColumns( 1, 0, _msa );
211     }
212
213     final private void removeViaGapAverage( final double mean_gapiness,
214                                             final int step,
215                                             final boolean realign,
216                                             final File outfile,
217                                             final int minimal_effective_length ) throws IOException,
218             InterruptedException {
219         if ( step < 1 ) {
220             throw new IllegalArgumentException( "step cannot be less than 1" );
221         }
222         if ( mean_gapiness < 0 ) {
223             throw new IllegalArgumentException( "target average gap ratio cannot be less than 0" );
224         }
225         if ( VERBOSE ) {
226             System.out.println( "orig: " + msaStatsAsSB() );
227         }
228         if ( minimal_effective_length > 1 ) {
229             _msa = MsaMethods.removeSequencesByMinimalLength( _msa, minimal_effective_length );
230             if ( VERBOSE ) {
231                 System.out.println( "short seq removal: " + msaStatsAsSB() );
232             }
233         }
234         int counter = step;
235         double gr;
236         do {
237             removeWorstOffenders( step, 1, false, false, false );
238             if ( realign ) {
239                 realignWithMafft();
240             }
241             gr = MsaMethods.calcGapRatio( _msa );
242             if ( VERBOSE ) {
243                 System.out.println( counter + ": " + msaStatsAsSB() );
244             }
245             //   write( outfile, gr );
246             counter += step;
247         } while ( gr > mean_gapiness );
248         if ( VERBOSE ) {
249             System.out.println( "final: " + msaStatsAsSB() );
250         }
251     }
252
253     final private void removeWorstOffenders( final int to_remove,
254                                              final int step,
255                                              final boolean realign,
256                                              final boolean norm,
257                                              final boolean verbose ) throws IOException, InterruptedException {
258         final GapContribution stats[] = calcGapContribtionsStats( norm );
259         final List<String> to_remove_ids = new ArrayList<String>();
260         for( int j = 0; j < to_remove; ++j ) {
261             to_remove_ids.add( stats[ j ].getId() );
262             _removed_seq_ids.add( stats[ j ].getId() );
263         }
264         for( int i = 0; i < to_remove_ids.size(); ++i ) {
265             final String id = to_remove_ids.get( i );
266             _msa = MsaMethods.removeSequence( _msa, id );
267             removeGapColumns();
268             if ( verbose ) {
269                 System.out.print( ForesterUtil.pad( id, 20, ' ', false ) );
270                 System.out.print( "\t" );
271                 final StringBuilder sb = msaStatsAsSB();
272                 System.out.print( sb );
273                 System.out.print( "\t" );
274             }
275             if ( ( ( ( i + 1 ) % step ) == 0 ) || ( i == ( to_remove_ids.size() - 1 ) ) ) {
276                 if ( realign ) {
277                     realignWithMafft();
278                 }
279                 final String s = writeOutfile();
280                 if ( verbose ) {
281                     System.out.print( "-> " + s );
282                 }
283             }
284             if ( verbose ) {
285                 System.out.println();
286             }
287         }
288     }
289
290     final private void removeViaLength( final int length,
291                                         final int step,
292                                         final boolean realign,
293                                         final boolean norm,
294                                         final boolean verbose ) throws IOException, InterruptedException {
295         final GapContribution stats[] = calcGapContribtionsStats( norm );
296         final List<String> to_remove_ids = new ArrayList<String>();
297         for( final GapContribution gap_gontribution : stats ) {
298             to_remove_ids.add( gap_gontribution.getId() );
299         }
300         int i = 0;
301         while ( _msa.getLength() > length ) {
302             final String id = to_remove_ids.get( i );
303             _msa = MsaMethods.removeSequence( _msa, id );
304             removeGapColumns();
305             if ( verbose ) {
306                 System.out.print( ForesterUtil.pad( id, 20, ' ', false ) );
307                 System.out.print( "\t" );
308                 final StringBuilder sb = msaStatsAsSB();
309                 System.out.print( sb );
310                 System.out.print( "\t" );
311             }
312             if ( ( ( ( i + 1 ) % step ) == 0 ) || ( _msa.getLength() <= length ) ) {
313                 if ( realign ) {
314                     realignWithMafft();
315                 }
316                 final String s = writeOutfile();
317                 if ( verbose ) {
318                     System.out.print( "-> " + s );
319                 }
320             }
321             if ( verbose ) {
322                 System.out.println();
323             }
324             ++i;
325         }
326     }
327
328     private void setPathToMafft( final String path_to_mafft ) {
329         _path_to_mafft = path_to_mafft;
330     }
331
332     final private void writeMsa( final String outfile, final MSA_FORMAT format ) throws IOException {
333         final Writer w = ForesterUtil.createBufferedWriter( outfile );
334         _msa.write( w, format );
335         w.close();
336     }
337
338     private String writeOutfile() throws IOException {
339         final String s = writeMsa( _out_file_base, MSA_FORMAT.PHYLIP, ".aln" );
340         //writeMsa( _out_file_base, MSA_FORMAT.FASTA, ".fasta" );
341         return s;
342     }
343
344     // Returns null if not path found.
345     final public static String guessPathToMafft() {
346         String path;
347         if ( ForesterUtil.OS_NAME.toLowerCase().indexOf( "win" ) >= 0 ) {
348             path = "C:\\Program Files\\mafft-win\\mafft.bat";
349             if ( MsaInferrer.isInstalled( path ) ) {
350                 return path;
351             }
352         }
353         path = "/usr/local/bin/mafft";
354         if ( MsaInferrer.isInstalled( path ) ) {
355             return path;
356         }
357         path = "/usr/bin/mafft";
358         if ( MsaInferrer.isInstalled( path ) ) {
359             return path;
360         }
361         path = "/bin/mafft";
362         if ( MsaInferrer.isInstalled( path ) ) {
363             return path;
364         }
365         path = "mafft";
366         if ( MsaInferrer.isInstalled( path ) ) {
367             return path;
368         }
369         return null;
370     }
371
372     public final static MsaCompactor reduceGapAverage( final Msa msa,
373                                                        final double max_gap_average,
374                                                        final int step,
375                                                        final boolean realign,
376                                                        final int minimal_effective_length,
377                                                        final String path_to_mafft,
378                                                        final File out ) throws IOException, InterruptedException {
379         final MsaCompactor mc = new MsaCompactor( msa );
380         if ( realign ) {
381             mc.setPathToMafft( path_to_mafft );
382         }
383         mc.setOutFileBase( out );
384         mc.removeViaGapAverage( max_gap_average, step, realign, out, minimal_effective_length );
385         return mc;
386     }
387
388     public final static MsaCompactor reduceLength( final Msa msa,
389                                                    final int length,
390                                                    final int step,
391                                                    final boolean realign,
392                                                    final boolean norm,
393                                                    final String path_to_mafft,
394                                                    final File out ) throws IOException, InterruptedException {
395         final MsaCompactor mc = new MsaCompactor( msa );
396         if ( realign ) {
397             mc.setPathToMafft( path_to_mafft );
398         }
399         mc.setOutFileBase( out );
400         mc.removeViaLength( length, step, realign, norm, true );
401         return mc;
402     }
403
404     public final static MsaCompactor removeWorstOffenders( final Msa msa,
405                                                            final int worst_offenders_to_remove,
406                                                            final int step,
407                                                            final boolean realign,
408                                                            final boolean norm,
409                                                            final String path_to_mafft,
410                                                            final File out ) throws IOException, InterruptedException {
411         final MsaCompactor mc = new MsaCompactor( msa );
412         if ( realign ) {
413             mc.setPathToMafft( path_to_mafft );
414         }
415         mc.setOutFileBase( out );
416         mc.removeWorstOffenders( worst_offenders_to_remove, step, realign, norm, true );
417         return mc;
418     }
419
420     private static DescriptiveStatistics calculateIdentityRatio( final int from, final int to, final Msa msa ) {
421         final DescriptiveStatistics stats = new BasicDescriptiveStatistics();
422         for( int c = from; c <= to; ++c ) {
423             stats.addValue( MsaMethods.calculateIdentityRatio( msa, c ) );
424         }
425         return stats;
426     }
427 }