inprogress
[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.io.writers.SequenceWriter;
45 import org.forester.io.writers.SequenceWriter.SEQ_FORMAT;
46 import org.forester.msa.DeleteableMsa;
47 import org.forester.msa.Mafft;
48 import org.forester.msa.Msa;
49 import org.forester.msa.Msa.MSA_FORMAT;
50 import org.forester.msa.MsaInferrer;
51 import org.forester.msa.MsaMethods;
52 import org.forester.msa.ResampleableMsa;
53 import org.forester.phylogeny.Phylogeny;
54 import org.forester.phylogeny.PhylogenyMethods;
55 import org.forester.sequence.Sequence;
56 import org.forester.tools.ConfidenceAssessor;
57 import org.forester.util.ForesterUtil;
58
59 public class MsaCompactor {
60
61     final private static NumberFormat NF_3                      = new DecimalFormat( "#.###" );
62     final private static NumberFormat NF_4                      = new DecimalFormat( "#.####" );
63     private double                    _gap_ratio                = -1;
64     private final short               _longest_id_length;
65     //
66     private String                    _maffts_opts              = "--auto";
67     private int                       _min_length               = -1;
68     //
69     private DeleteableMsa             _msa                      = null;
70     private boolean                   _norm                     = true;
71     private File                      _out_file_base            = null;
72     private MSA_FORMAT                _output_format            = MSA_FORMAT.FASTA;
73     private String                    _path_to_mafft            = null;
74     //
75     private boolean                   _realign                  = false;
76     private final SortedSet<String>   _removed_seq_ids;
77     private final ArrayList<Sequence> _removed_seqs;
78     private File                      _removed_seqs_out_base    = null;
79     private boolean                   _report_aln_mean_identity = false;
80     private int                       _step                     = -1;
81     private int                       _step_for_diagnostics     = -1;
82     static {
83         NF_4.setRoundingMode( RoundingMode.HALF_UP );
84         NF_3.setRoundingMode( RoundingMode.HALF_UP );
85     }
86
87     public MsaCompactor( final DeleteableMsa msa ) {
88         _msa = msa;
89         _removed_seq_ids = new TreeSet<String>();
90         _longest_id_length = _msa.determineMaxIdLength();
91         _removed_seqs = new ArrayList<Sequence>();
92     }
93
94     public final List<MsaProperties> chart( final int step, final boolean realign, final boolean norm )
95             throws IOException, InterruptedException {
96         final GapContribution stats[] = calcGapContribtionsStats( norm );
97         final List<String> to_remove_ids = new ArrayList<String>();
98         final List<MsaProperties> msa_props = new ArrayList<MsaProperties>();
99         for( final GapContribution gap_gontribution : stats ) {
100             to_remove_ids.add( gap_gontribution.getId() );
101         }
102         if ( !_realign ) {
103             _step = -1;
104         }
105         printTableHeader();
106         int x = ForesterUtil.roundToInt( _msa.getNumberOfSequences() / 20.0 );
107         if ( x < 1 ) {
108             x = 1;
109         }
110         MsaProperties msa_prop = new MsaProperties( _msa, _report_aln_mean_identity );
111         msa_props.add( msa_prop );
112         printMsaProperties( "", msa_prop );
113         System.out.println();
114         int i = 0;
115         while ( _msa.getNumberOfSequences() > x ) {
116             final String id = to_remove_ids.get( i );
117             _msa.deleteRow( id, false );
118             if ( realign && isPrintMsaStatsWriteOutfileAndRealign( i ) ) {
119                 removeGapColumns();
120                 realignWithMafft();
121                 msa_prop = new MsaProperties( _msa, _report_aln_mean_identity );
122                 msa_props.add( msa_prop );
123                 printMsaProperties( id, msa_prop );
124                 System.out.print( "(realigned)" );
125                 System.out.println();
126             }
127             else if ( isPrintMsaStats( i ) ) {
128                 removeGapColumns();
129                 msa_prop = new MsaProperties( _msa, _report_aln_mean_identity );
130                 msa_props.add( msa_prop );
131                 printMsaProperties( id, msa_prop );
132                 System.out.println();
133             }
134             ++i;
135         }
136         return msa_props;
137     }
138
139     final public void deleteGapColumns( final double max_allowed_gap_ratio ) {
140         _msa.deleteGapColumns( max_allowed_gap_ratio );
141     }
142
143     final public Msa getMsa() {
144         return _msa;
145     }
146
147     final public SortedSet<String> getRemovedSeqIds() {
148         return _removed_seq_ids;
149     }
150
151     public final void removeSequencesByMinimalLength( final int min_effective_length ) {
152         printMsaProperties( "", new MsaProperties( _msa, _report_aln_mean_identity ) );
153         System.out.println();
154         _msa = DeleteableMsa.createInstance( MsaMethods.removeSequencesByMinimalLength( _msa, min_effective_length ) );
155         removeGapColumns();
156         printMsaProperties( "", new MsaProperties( _msa, _report_aln_mean_identity ) );
157         System.out.println();
158     }
159
160     public final List<MsaProperties> removeViaGapAverage( final double mean_gapiness ) throws IOException,
161             InterruptedException {
162         final GapContribution stats[] = calcGapContribtionsStats( _norm );
163         final List<String> to_remove_ids = new ArrayList<String>();
164         final List<MsaProperties> msa_props = new ArrayList<MsaProperties>();
165         for( final GapContribution gap_gontribution : stats ) {
166             to_remove_ids.add( gap_gontribution.getId() );
167         }
168         printTableHeader();
169         MsaProperties msa_prop = new MsaProperties( _msa, _report_aln_mean_identity );
170         msa_props.add( msa_prop );
171         printMsaProperties( "", msa_prop );
172         System.out.println();
173         int i = 0;
174         while ( MsaMethods.calcGapRatio( _msa ) > mean_gapiness ) {
175             final String id = to_remove_ids.get( i );
176             _removed_seq_ids.add( id );
177             final Sequence deleted = _msa.deleteRow( id, true );
178             _removed_seqs.add( deleted );
179             removeGapColumns();
180             if ( isPrintMsaStatsWriteOutfileAndRealign( i ) || ( MsaMethods.calcGapRatio( _msa ) <= mean_gapiness ) ) {
181                 msa_prop = printMsaStatsWriteOutfileAndRealign( _realign, id );
182                 msa_props.add( msa_prop );
183                 System.out.println();
184             }
185             else if ( isPrintMsaStats( i ) ) {
186                 msa_prop = new MsaProperties( _msa, _report_aln_mean_identity );
187                 msa_props.add( msa_prop );
188                 printMsaProperties( id, msa_prop );
189                 System.out.println();
190             }
191             ++i;
192         }
193         if ( _removed_seqs_out_base != null ) {
194             final String msg = writeAndAlignRemovedSeqs();
195             System.out.println();
196             System.out.println( msg );
197         }
198         return msa_props;
199     }
200
201     public List<MsaProperties> removeViaLength( final int length ) throws IOException, InterruptedException {
202         final GapContribution stats[] = calcGapContribtionsStats( _norm );
203         final List<String> to_remove_ids = new ArrayList<String>();
204         final List<MsaProperties> msa_props = new ArrayList<MsaProperties>();
205         for( final GapContribution gap_gontribution : stats ) {
206             to_remove_ids.add( gap_gontribution.getId() );
207         }
208         printTableHeader();
209         MsaProperties msa_prop = new MsaProperties( _msa, _report_aln_mean_identity );
210         msa_props.add( msa_prop );
211         printMsaProperties( "", msa_prop );
212         System.out.println();
213         int i = 0;
214         while ( _msa.getLength() > length ) {
215             final String id = to_remove_ids.get( i );
216             _removed_seq_ids.add( id );
217             final Sequence deleted = _msa.deleteRow( id, true );
218             _removed_seqs.add( deleted );
219             removeGapColumns();
220             if ( isPrintMsaStatsWriteOutfileAndRealign( i ) || ( _msa.getLength() <= length ) ) {
221                 msa_prop = printMsaStatsWriteOutfileAndRealign( _realign, id );
222                 msa_props.add( msa_prop );
223                 System.out.println();
224             }
225             else if ( isPrintMsaStats( i ) ) {
226                 msa_prop = new MsaProperties( _msa, _report_aln_mean_identity );
227                 printMsaProperties( id, msa_prop );
228                 msa_props.add( msa_prop );
229                 System.out.println();
230             }
231             ++i;
232         }
233         if ( _removed_seqs_out_base != null ) {
234             final String msg = writeAndAlignRemovedSeqs();
235             System.out.println();
236             System.out.println( msg );
237         }
238         return msa_props;
239     }
240
241     public final List<MsaProperties> removeWorstOffenders( final int to_remove ) throws IOException,
242             InterruptedException {
243         final GapContribution stats[] = calcGapContribtionsStats( _norm );
244         final List<String> to_remove_ids = new ArrayList<String>();
245         final List<MsaProperties> msa_props = new ArrayList<MsaProperties>();
246         for( int j = 0; j < to_remove; ++j ) {
247             to_remove_ids.add( stats[ j ].getId() );
248             _removed_seq_ids.add( stats[ j ].getId() );
249         }
250         printTableHeader();
251         MsaProperties msa_prop = new MsaProperties( _msa, _report_aln_mean_identity );
252         msa_props.add( msa_prop );
253         printMsaProperties( "", msa_prop );
254         System.out.println();
255         for( int i = 0; i < to_remove_ids.size(); ++i ) {
256             final String id = to_remove_ids.get( i );
257             _removed_seq_ids.add( id );
258             final Sequence deleted = _msa.deleteRow( id, true );
259             _removed_seqs.add( deleted );
260             removeGapColumns();
261             if ( isPrintMsaStatsWriteOutfileAndRealign( i ) || ( i == ( to_remove_ids.size() - 1 ) ) ) {
262                 msa_prop = printMsaStatsWriteOutfileAndRealign( _realign, id );
263                 msa_props.add( msa_prop );
264                 System.out.println();
265             }
266             else if ( isPrintMsaStats( i ) ) {
267                 msa_prop = new MsaProperties( _msa, _report_aln_mean_identity );
268                 msa_props.add( msa_prop );
269                 printMsaProperties( id, msa_prop );
270                 System.out.println();
271             }
272         }
273         if ( _removed_seqs_out_base != null ) {
274             final String msg = writeAndAlignRemovedSeqs();
275             System.out.println();
276             System.out.println( msg );
277         }
278         return msa_props;
279     }
280
281     public final void setGapRatio( final double gap_ratio ) {
282         _gap_ratio = gap_ratio;
283     }
284
285     public final void setMafftOptions( final String maffts_opts ) {
286         _maffts_opts = maffts_opts;
287     }
288
289     public final void setMinLength( final int min_length ) {
290         _min_length = min_length;
291     }
292
293     public final void setNorm( final boolean norm ) {
294         _norm = norm;
295     }
296
297     final public void setOutFileBase( final File out_file_base ) {
298         _out_file_base = out_file_base;
299     }
300
301     public final void setOutputFormat( final MSA_FORMAT output_format ) {
302         _output_format = output_format;
303     }
304
305     public void setPathToMafft( final String path_to_mafft ) {
306         _path_to_mafft = path_to_mafft;
307     }
308
309     public final void setRealign( final boolean realign ) {
310         _realign = realign;
311     }
312
313     public final void setRemovedSeqsOutBase( final File removed_seqs_out_base ) {
314         _removed_seqs_out_base = removed_seqs_out_base;
315     }
316
317     public final void setReportAlnMeanIdentity( final boolean report_aln_mean_identity ) {
318         _report_aln_mean_identity = report_aln_mean_identity;
319     }
320
321     public final void setStep( final int step ) {
322         _step = step;
323     }
324
325     public final void setStepForDiagnostics( final int step_for_diagnostics ) {
326         _step_for_diagnostics = step_for_diagnostics;
327     }
328
329     final public String writeAndAlignRemovedSeqs() throws IOException, InterruptedException {
330         final StringBuilder msg = new StringBuilder();
331         final String n = _removed_seqs_out_base + "_" + _removed_seqs.size() + ".fasta";
332         SequenceWriter.writeSeqs( _removed_seqs, new File( n ), SEQ_FORMAT.FASTA, 100 );
333         msg.append( "wrote " + _removed_seqs.size() + " removed sequences to " + "\"" + n + "\"" );
334         if ( _realign ) {
335             final MsaInferrer mafft = Mafft.createInstance( _path_to_mafft );
336             final List<String> opts = new ArrayList<String>();
337             for( final String o : _maffts_opts.split( "\\s" ) ) {
338                 opts.add( o );
339             }
340             final Msa removed_msa = mafft.infer( _removed_seqs, opts );
341             final Double gr = MsaMethods.calcGapRatio( removed_msa );
342             String s = _removed_seqs_out_base + "_" + removed_msa.getNumberOfSequences() + "_"
343                     + removed_msa.getLength() + "_" + ForesterUtil.roundToInt( gr * 100 );
344             final String suffix = obtainSuffix();
345             s += suffix;
346             writeMsa( removed_msa, s, _output_format );
347             msg.append( ", and as MSA of length " + removed_msa.getLength() + " to \"" + s + "\"" );
348         }
349         return msg.toString();
350     }
351
352     final public String writeMsa( final File outfile ) throws IOException {
353         final Double gr = MsaMethods.calcGapRatio( _msa );
354         final String s = outfile + "_" + _msa.getNumberOfSequences() + "_" + _msa.getLength() + "_"
355                 + ForesterUtil.roundToInt( gr * 100 );
356         writeMsa( _msa, s + obtainSuffix(), _output_format );
357         return s;
358     }
359
360     final int calcNonGapResidues( final Sequence seq ) {
361         int ng = 0;
362         for( int i = 0; i < seq.getLength(); ++i ) {
363             if ( !seq.isGapAt( i ) ) {
364                 ++ng;
365             }
366         }
367         return ng;
368     }
369
370     private final GapContribution[] calcGapContribtions( final boolean normalize_for_effective_seq_length ) {
371         final double gappiness[] = calcGappiness();
372         final GapContribution stats[] = new GapContribution[ _msa.getNumberOfSequences() ];
373         for( int row = 0; row < _msa.getNumberOfSequences(); ++row ) {
374             stats[ row ] = new GapContribution( _msa.getIdentifier( row ) );
375             for( int col = 0; col < _msa.getLength(); ++col ) {
376                 if ( !_msa.isGapAt( row, col ) ) {
377                     stats[ row ].addToValue( gappiness[ col ] );
378                 }
379             }
380             if ( normalize_for_effective_seq_length ) {
381                 stats[ row ].divideValue( calcNonGapResidues( _msa.getSequence( row ) ) );
382             }
383             else {
384                 stats[ row ].divideValue( _msa.getLength() );
385             }
386         }
387         return stats;
388     }
389
390     final private GapContribution[] calcGapContribtionsStats( final boolean norm ) {
391         final GapContribution stats[] = calcGapContribtions( norm );
392         Arrays.sort( stats );
393         return stats;
394     }
395
396     private final double[] calcGappiness() {
397         final int l = _msa.getLength();
398         final double gappiness[] = new double[ l ];
399         final int seqs = _msa.getNumberOfSequences();
400         for( int i = 0; i < l; ++i ) {
401             gappiness[ i ] = ( double ) MsaMethods.calcGapSumPerColumn( _msa, i ) / seqs;
402         }
403         return gappiness;
404     }
405
406     private final Phylogeny inferNJphylogeny( final PWD_DISTANCE_METHOD pwd_distance_method,
407                                               final Msa msa,
408                                               final boolean write_matrix,
409                                               final String matrix_name ) {
410         BasicSymmetricalDistanceMatrix m = null;
411         switch ( pwd_distance_method ) {
412             case KIMURA_DISTANCE:
413                 m = PairwiseDistanceCalculator.calcKimuraDistances( msa );
414                 break;
415             case POISSON_DISTANCE:
416                 m = PairwiseDistanceCalculator.calcPoissonDistances( msa );
417                 break;
418             case FRACTIONAL_DISSIMILARITY:
419                 m = PairwiseDistanceCalculator.calcFractionalDissimilarities( msa );
420                 break;
421             default:
422                 throw new IllegalArgumentException( "invalid pwd method" );
423         }
424         if ( write_matrix ) {
425             try {
426                 m.write( ForesterUtil.createBufferedWriter( matrix_name ) );
427             }
428             catch ( final IOException e ) {
429                 e.printStackTrace();
430             }
431         }
432         final NeighborJoiningF nj = NeighborJoiningF.createInstance( false, 5 );
433         final Phylogeny phy = nj.execute( m );
434         return phy;
435     }
436
437     private final boolean isPrintMsaStats( final int i ) {
438         return ( ( ( _step == 1 ) && ( _step_for_diagnostics == 1 ) ) || ( ( _step_for_diagnostics > 0 ) && ( ( ( i + 1 ) % _step_for_diagnostics ) == 0 ) ) );
439     }
440
441     private final boolean isPrintMsaStatsWriteOutfileAndRealign( final int i ) {
442         return ( ( ( _step == 1 ) && ( _step_for_diagnostics == 1 ) ) || ( ( _step > 0 ) && ( ( ( i + 1 ) % _step ) == 0 ) ) );
443     }
444
445     private final StringBuilder msaPropertiesAsSB( final MsaProperties msa_properties ) {
446         final StringBuilder sb = new StringBuilder();
447         sb.append( msa_properties.getNumberOfSequences() );
448         sb.append( "\t" );
449         sb.append( msa_properties.getLength() );
450         sb.append( "\t" );
451         sb.append( NF_4.format( msa_properties.getGapRatio() ) );
452         if ( _report_aln_mean_identity ) {
453             sb.append( "\t" );
454             sb.append( NF_4.format( msa_properties.getAverageIdentityRatio() ) );
455         }
456         return sb;
457     }
458
459     private String obtainSuffix() {
460         if ( _output_format == MSA_FORMAT.FASTA ) {
461             return ".fasta";
462         }
463         else if ( _output_format == MSA_FORMAT.PHYLIP ) {
464             return ".aln";
465         }
466         return "";
467     }
468
469     private final Phylogeny pi( final String matrix ) {
470         final Phylogeny master_phy = inferNJphylogeny( PWD_DISTANCE_METHOD.KIMURA_DISTANCE, _msa, true, matrix );
471         final int seed = 15;
472         final int n = 100;
473         final ResampleableMsa resampleable_msa = new ResampleableMsa( _msa );
474         final int[][] resampled_column_positions = BootstrapResampler.createResampledColumnPositions( _msa.getLength(),
475                                                                                                       n,
476                                                                                                       seed );
477         final Phylogeny[] eval_phys = new Phylogeny[ n ];
478         for( int i = 0; i < n; ++i ) {
479             resampleable_msa.resample( resampled_column_positions[ i ] );
480             eval_phys[ i ] = inferNJphylogeny( PWD_DISTANCE_METHOD.KIMURA_DISTANCE, resampleable_msa, false, null );
481         }
482         ConfidenceAssessor.evaluate( "bootstrap", eval_phys, master_phy, true, 1 );
483         PhylogenyMethods.extractFastaInformation( master_phy );
484         return master_phy;
485     }
486
487     private final void printMsaProperties( final String id, final MsaProperties msa_properties ) {
488         if ( ( _step == 1 ) || ( _step_for_diagnostics == 1 ) ) {
489             System.out.print( ForesterUtil.pad( id, _longest_id_length, ' ', false ) );
490             System.out.print( "\t" );
491         }
492         System.out.print( msaPropertiesAsSB( msa_properties ) );
493         System.out.print( "\t" );
494     }
495
496     final private MsaProperties printMsaStatsWriteOutfileAndRealign( final boolean realign, final String id )
497             throws IOException, InterruptedException {
498         if ( realign ) {
499             realignWithMafft();
500         }
501         final MsaProperties msa_prop = new MsaProperties( _msa, _report_aln_mean_identity );
502         printMsaProperties( id, msa_prop );
503         final String s = writeOutfile();
504         System.out.print( "-> " + s + ( realign ? "\t(realigned)" : "" ) );
505         return msa_prop;
506     }
507
508     private final void printTableHeader() {
509         if ( ( _step == 1 ) || ( _step_for_diagnostics == 1 ) ) {
510             System.out.print( ForesterUtil.pad( "Id", _longest_id_length, ' ', false ) );
511             System.out.print( "\t" );
512         }
513         System.out.print( "Seqs" );
514         System.out.print( "\t" );
515         System.out.print( "Length" );
516         System.out.print( "\t" );
517         System.out.print( "Gaps" );
518         System.out.print( "\t" );
519         if ( _report_aln_mean_identity ) {
520             System.out.print( "MSA qual" );
521             System.out.print( "\t" );
522         }
523         System.out.println();
524     }
525
526     final private void realignWithMafft() throws IOException, InterruptedException {
527         final MsaInferrer mafft = Mafft.createInstance( _path_to_mafft );
528         final List<String> opts = new ArrayList<String>();
529         for( final String o : _maffts_opts.split( "\\s" ) ) {
530             opts.add( o );
531         }
532         _msa = DeleteableMsa.createInstance( mafft.infer( _msa.asSequenceList(), opts ) );
533     }
534
535     final private void removeGapColumns() {
536         _msa.deleteGapOnlyColumns();
537     }
538
539     private final String writeOutfile() throws IOException {
540         final String s = writeMsa( _out_file_base );
541         return s;
542     }
543
544     // Returns null if not path found.
545     final public static String guessPathToMafft() {
546         String path;
547         if ( ForesterUtil.OS_NAME.toLowerCase().indexOf( "win" ) >= 0 ) {
548             path = "C:\\Program Files\\mafft-win\\mafft.bat";
549             if ( MsaInferrer.isInstalled( path ) ) {
550                 return path;
551             }
552         }
553         path = "/home/czmasek/SOFTWARE/MSA/MAFFT/mafft-7.130-without-extensions/scripts/mafft";
554         if ( MsaInferrer.isInstalled( path ) ) {
555             return path;
556         }
557         path = "/usr/local/bin/mafft";
558         if ( MsaInferrer.isInstalled( path ) ) {
559             return path;
560         }
561         path = "/usr/bin/mafft";
562         if ( MsaInferrer.isInstalled( path ) ) {
563             return path;
564         }
565         path = "/bin/mafft";
566         if ( MsaInferrer.isInstalled( path ) ) {
567             return path;
568         }
569         path = "mafft";
570         if ( MsaInferrer.isInstalled( path ) ) {
571             return path;
572         }
573         return null;
574     }
575
576     final private static void writeMsa( final Msa msa, final String outfile, final MSA_FORMAT format )
577             throws IOException {
578         final Writer w = ForesterUtil.createBufferedWriter( outfile );
579         msa.write( w, format );
580         w.close();
581     }
582 }