inprogress
[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 removeViaLength( final int length, final int step, final boolean realign ) throws IOException,
254             InterruptedException {
255         if ( step < 1 ) {
256             throw new IllegalArgumentException( "step cannot be less than 1" );
257         }
258         if ( length < 11 ) {
259             throw new IllegalArgumentException( "target length cannot be less than 1" );
260         }
261         if ( VERBOSE ) {
262             System.out.println( "orig: " + msaStatsAsSB() );
263         }
264         int counter = step;
265         while ( _msa.getLength() > length ) {
266             removeWorstOffenders( step, 1, false, false, false );
267             if ( realign ) {
268                 realignWithMafft();
269             }
270             if ( VERBOSE ) {
271                 System.out.println( counter + ": " + msaStatsAsSB() );
272             }
273             counter += step;
274         }
275     }
276
277     final private void removeWorstOffenders( final int to_remove,
278                                              final int step,
279                                              final boolean realign,
280                                              final boolean norm,
281                                              final boolean verbose ) throws IOException, InterruptedException {
282         final GapContribution stats[] = calcGapContribtionsStats( norm );
283         final List<String> to_remove_ids = new ArrayList<String>();
284         for( int j = 0; j < to_remove; ++j ) {
285             to_remove_ids.add( stats[ j ].getId() );
286             _removed_seq_ids.add( stats[ j ].getId() );
287         }
288         for( int i = 0; i < to_remove_ids.size(); ++i ) {
289             final String id = to_remove_ids.get( i );
290             _msa = MsaMethods.removeSequence( _msa, id );
291             removeGapColumns();
292             if ( verbose ) {
293                 System.out.print( ForesterUtil.pad( id, 20, ' ', false ) );
294                 System.out.print( "\t" );
295                 final StringBuilder sb = msaStatsAsSB();
296                 System.out.print( sb );
297                 System.out.print( "\t" );
298             }
299             if ( ( ( ( i + 1 ) % step ) == 0 ) || ( i == ( to_remove_ids.size() - 1 ) ) ) {
300                 if ( realign ) {
301                     realignWithMafft();
302                 }
303                 final String s = writeOutfile();
304                 if ( verbose ) {
305                     System.out.print( "-> " + s );
306                 }
307             }
308             if ( verbose ) {
309                 System.out.println();
310             }
311         }
312     }
313
314     private void setPathToMafft( final String path_to_mafft ) {
315         _path_to_mafft = path_to_mafft;
316     }
317
318     final private void writeMsa( final String outfile, final MSA_FORMAT format ) throws IOException {
319         final Writer w = ForesterUtil.createBufferedWriter( outfile );
320         _msa.write( w, format );
321         w.close();
322     }
323
324     private String writeOutfile() throws IOException {
325         final String s = writeMsa( _out_file_base, MSA_FORMAT.PHYLIP, ".aln" );
326         //writeMsa( _out_file_base, MSA_FORMAT.FASTA, ".fasta" );
327         return s;
328     }
329
330     // Returns null if not path found.
331     final public static String guessPathToMafft() {
332         String path;
333         if ( ForesterUtil.OS_NAME.toLowerCase().indexOf( "win" ) >= 0 ) {
334             path = "C:\\Program Files\\mafft-win\\mafft.bat";
335             if ( MsaInferrer.isInstalled( path ) ) {
336                 return path;
337             }
338         }
339         path = "/usr/local/bin/mafft";
340         if ( MsaInferrer.isInstalled( path ) ) {
341             return path;
342         }
343         path = "/usr/bin/mafft";
344         if ( MsaInferrer.isInstalled( path ) ) {
345             return path;
346         }
347         path = "/bin/mafft";
348         if ( MsaInferrer.isInstalled( path ) ) {
349             return path;
350         }
351         path = "mafft";
352         if ( MsaInferrer.isInstalled( path ) ) {
353             return path;
354         }
355         return null;
356     }
357
358     public final static MsaCompactor reduceGapAverage( final Msa msa,
359                                                        final double max_gap_average,
360                                                        final int step,
361                                                        final boolean realign,
362                                                        final int minimal_effective_length,
363                                                        final String path_to_mafft,
364                                                        final File out ) throws IOException, InterruptedException {
365         final MsaCompactor mc = new MsaCompactor( msa );
366         if ( realign ) {
367             mc.setPathToMafft( path_to_mafft );
368         }
369         mc.setOutFileBase( out );
370         mc.removeViaGapAverage( max_gap_average, step, realign, out, minimal_effective_length );
371         return mc;
372     }
373
374     public final static MsaCompactor reduceLength( final Msa msa,
375                                                    final int length,
376                                                    final int step,
377                                                    final boolean realign,
378                                                    final String path_to_mafft,
379                                                    final File out ) throws IOException, InterruptedException {
380         final MsaCompactor mc = new MsaCompactor( msa );
381         if ( realign ) {
382             mc.setPathToMafft( path_to_mafft );
383         }
384         mc.setOutFileBase( out );
385         mc.removeViaLength( length, step, realign );
386         return mc;
387     }
388
389     public final static MsaCompactor removeWorstOffenders( final Msa msa,
390                                                            final int worst_offenders_to_remove,
391                                                            final int step,
392                                                            final boolean realign,
393                                                            final boolean norm,
394                                                            final String path_to_mafft,
395                                                            final File out ) throws IOException, InterruptedException {
396         final MsaCompactor mc = new MsaCompactor( msa );
397         if ( realign ) {
398             mc.setPathToMafft( path_to_mafft );
399         }
400         mc.setOutFileBase( out );
401         mc.removeWorstOffenders( worst_offenders_to_remove, step, realign, norm, true );
402         return mc;
403     }
404
405     private static DescriptiveStatistics calculateIdentityRatio( final int from, final int to, final Msa msa ) {
406         final DescriptiveStatistics stats = new BasicDescriptiveStatistics();
407         for( int c = from; c <= to; ++c ) {
408             stats.addValue( MsaMethods.calculateIdentityRatio( msa, c ) );
409         }
410         return stats;
411     }
412 }