1d92b60a00a217921affcd67c9d766b9eac05d3b
[jalview.git] / forester / ruby / evoruby / lib / evo / tool / msa_processor.rb
1 #
2 # = lib/evo/apps/msa_processor.rb - MsaProcessor class
3 #
4 # Copyright::  Copyright (C) 2006-2007 Christian M. Zmasek
5 # License::    GNU Lesser General Public License (LGPL)
6 #
7 # $Id: msa_processor.rb,v 1.33 2010/12/13 19:00:10 cmzmasek Exp $
8 #
9
10 require 'date'
11 require 'set'
12 require 'lib/evo/util/constants'
13 require 'lib/evo/util/util'
14 require 'lib/evo/util/command_line_arguments'
15 require 'lib/evo/msa/msa_factory'
16 require 'lib/evo/io/msa_io'
17 require 'lib/evo/io/writer/phylip_sequential_writer'
18 require 'lib/evo/io/writer/nexus_writer'
19 require 'lib/evo/io/writer/fasta_writer'
20 require 'lib/evo/io/parser/fasta_parser'
21 require 'lib/evo/io/parser/general_msa_parser'
22 require 'lib/evo/io/writer/msa_writer'
23
24 module Evoruby
25   class MsaProcessor
26
27     PRG_NAME       = "msa_pro"
28     PRG_DATE       = "170609"
29     PRG_DESC       = "processing of multiple sequence alignments"
30     PRG_VERSION    = "1.09"
31     WWW            = "https://sites.google.com/site/cmzmasek/home/software/forester"
32
33     NAME_LENGTH_DEFAULT                = 10
34     WIDTH_DEFAULT_FASTA                = 60
35     INPUT_TYPE_OPTION                  = "i"
36     OUTPUT_TYPE_OPTION                 = "o"
37     MAXIMAL_NAME_LENGTH_OPTION         = "n"
38     DIE_IF_NAME_TOO_LONG               = "d"
39     WIDTH_OPTION                       = "w"
40     CLEAN_UP_SEQ_OPTION                = "c"
41     REM_RED_OPTION                     = "rem_red"
42     REMOVE_GAP_COLUMNS_OPTION          = "rgc"
43     REMOVE_GAP_ONLY_COLUMNS            = "rgoc"
44     REMOVE_COLUMNS_GAP_RATIO_OPTION    = "rr"
45     REMOVE_ALL_GAP_CHARACTERS_OPTION   = "rg"
46     REMOVE_ALL_SEQUENCES_LISTED_OPTION = "r"
47     KEEP_ONLY_SEQUENCES_LISTED_OPTION  = "k"
48
49     KEEP_MATCHING_SEQUENCES_OPTION     = "mk"
50     REMOVE_MATCHING_SEQUENCES_OPTION   = "mr"
51
52     TRIM_OPTION                        = "t"
53     SLIDING_EXTRACTION_OPTION          = "se"
54     REMOVE_SEQS_GAP_RATIO_OPTION       = "rsgr"
55     REMOVE_SEQS_NON_GAP_LENGTH_OPTION  = "rsl"
56     SPLIT                              = "split"
57     SPLIT_BY_OS                        = "split_by_os"
58     LOG_SUFFIX                         = "_msa_pro.log"
59     HELP_OPTION_1                      = "help"
60     HELP_OPTION_2                      = "h"
61     def initialize()
62       @input_format_set = false
63       @output_format_set = false
64       @fasta_input      = false
65       @phylip_input     = true
66       @name_length      = NAME_LENGTH_DEFAULT
67       @name_length_set  = false
68       @width            = WIDTH_DEFAULT_FASTA     # fasta only
69       @pi_output        = true
70       @fasta_output     = false
71       @nexus_output     = false
72       @clean            = false  # phylip only
73       @rgc              = false
74       @rgoc             = false
75       @rg               = false  # fasta only
76       @rem_red          = false
77       @die_if_name_too_long  = false
78       @rgr              = -1
79       @rsgr             = -1
80       @rsl              = -1
81       @remove_matching  = nil
82       @keep_matching    = nil
83
84       @seqs_name_file   = nil
85       @remove_seqs      = false
86       @keep_seqs        = false
87       @trim             = false
88       @split            = -1
89       @split_by_os      = false
90       @first            = -1
91       @last             = -1
92       @window = false
93       @step = -1
94       @size =  -1
95     end
96
97     def run()
98
99       Util.print_program_information( PRG_NAME,
100       PRG_VERSION,
101       PRG_DESC,
102       PRG_DATE,
103       WWW,
104       STDOUT )
105
106       if ( ARGV == nil || ARGV.length < 1 )
107         Util.print_message( PRG_NAME, "Illegal number of arguments" )
108         print_help
109         exit( -1 )
110       end
111
112       begin
113         cla = CommandLineArguments.new( ARGV )
114       rescue ArgumentError => e
115         Util.fatal_error( PRG_NAME, "Error: " + e.to_s, STDOUT )
116       end
117
118       if ( cla.is_option_set?( HELP_OPTION_1 ) ||
119       cla.is_option_set?( HELP_OPTION_2 ) )
120         print_help
121         exit( 0 )
122       end
123
124       if ( cla.get_number_of_files != 2 || ARGV.length < 2 )
125         Util.print_message( PRG_NAME, "Illegal number of arguments" )
126         print_help
127         exit( -1 )
128       end
129
130       allowed_opts = Array.new
131       allowed_opts.push( INPUT_TYPE_OPTION )
132       allowed_opts.push( OUTPUT_TYPE_OPTION )
133       allowed_opts.push( MAXIMAL_NAME_LENGTH_OPTION )
134       allowed_opts.push( WIDTH_OPTION )
135       allowed_opts.push( CLEAN_UP_SEQ_OPTION )
136       allowed_opts.push( REMOVE_GAP_COLUMNS_OPTION )
137       allowed_opts.push( REMOVE_GAP_ONLY_COLUMNS )
138       allowed_opts.push( REMOVE_COLUMNS_GAP_RATIO_OPTION )
139       allowed_opts.push( REMOVE_ALL_GAP_CHARACTERS_OPTION )
140       allowed_opts.push( REMOVE_ALL_SEQUENCES_LISTED_OPTION )
141       allowed_opts.push( KEEP_ONLY_SEQUENCES_LISTED_OPTION )
142       allowed_opts.push( TRIM_OPTION )
143       allowed_opts.push( REMOVE_SEQS_GAP_RATIO_OPTION )
144       allowed_opts.push( REMOVE_SEQS_NON_GAP_LENGTH_OPTION )
145       allowed_opts.push( SPLIT )
146       allowed_opts.push( SPLIT_BY_OS )
147       allowed_opts.push( REM_RED_OPTION )
148       allowed_opts.push( KEEP_MATCHING_SEQUENCES_OPTION )
149       allowed_opts.push( REMOVE_MATCHING_SEQUENCES_OPTION )
150       allowed_opts.push( DIE_IF_NAME_TOO_LONG )
151       allowed_opts.push( SLIDING_EXTRACTION_OPTION )
152
153       disallowed = cla.validate_allowed_options_as_str( allowed_opts )
154       if ( disallowed.length > 0 )
155         Util.fatal_error( PRG_NAME,
156         "unknown option(s): " + disallowed )
157       end
158
159       input = cla.get_file_name( 0 )
160       output = cla.get_file_name( 1 )
161
162       analyze_command_line( cla )
163
164       begin
165         Util.check_file_for_readability( input )
166       rescue IOError => e
167         Util.fatal_error( PRG_NAME, "error: " + e.to_s )
168       end
169
170       begin
171         Util.check_file_for_writability( output )
172       rescue IOError => e
173         Util.fatal_error( PRG_NAME, "error: " + e.to_s )
174       end
175
176       if ( @rg )
177         set_pi_output( false )
178         set_fasta_output( true )
179         set_nexus_output( false )
180       end
181
182       if ( !@input_format_set )
183         fasta_like = false
184         begin
185           fasta_like = Util.looks_like_fasta?( input )
186         rescue ArgumentError => e
187           Util.fatal_error( PRG_NAME, "error: " + e.to_s )
188         end
189         @fasta_input = fasta_like
190         @phylip_input = !fasta_like
191         if ( !@output_format_set )
192           @fasta_output = fasta_like
193           @pi_output = !fasta_like
194           @nexus_output = false
195         end
196       end
197
198       ld = Constants::LINE_DELIMITER
199       log = PRG_NAME + " " + PRG_VERSION + " [" + PRG_DATE + "]" + " LOG" + ld
200       now = DateTime.now
201       log << "Date/time: " + now.to_s + ld
202
203       puts()
204       puts( "Input alignment  : " + input )
205       log << "Input alignment  : " + input + ld
206       puts( "Output alignment : " + output )
207       log << "Output alignment : " + output + ld
208       if ( @phylip_input )
209         puts( "Input is         : Phylip, or something like it" )
210         log << "Input is         : Phylip, or something like it" + ld
211       elsif ( @fasta_input )
212         puts( "Input is         : Fasta" )
213         log << "Input is         : Fasta" + ld
214       end
215       if( @rgr >= 0 )
216         puts( "Max col gap ratio: " + @rgr.to_s )
217         log << "Max col gap ratio: " + @rgr.to_s + ld
218       elsif ( @rgc )
219         puts( "Remove gap colums" )
220         log << "Remove gap colums" + ld
221       elsif( @rgoc )
222         puts( "Remove gap only colums" )
223         log << "Remove gap only colums" + ld
224       end
225       if ( @clean )
226         puts( "Clean up         : true" )
227         log << "Clean up         : true" + ld
228       end
229
230       if ( @pi_output )
231         puts( "Output is        : Phylip interleaved" )
232         log << "Output is        : Phylip interleaved" + ld
233       elsif ( @fasta_output )
234         puts( "Output is        : Fasta" )
235         log << "Output is        : Fasta" + ld
236         if ( @width )
237           puts( "Width            : " + @width.to_s )
238           log << "Width            : " + @width.to_s + ld
239         end
240         if ( @rg )
241           puts( "Remove all gap characters (alignment is destroyed)" )
242           log << "Remove all gap characters (alignment is destroyed)" + ld
243         end
244       elsif ( @nexus_output )
245         puts( "Output is        : Nexus" )
246         log << "Output is        : Nexus" + ld
247       end
248       if ( @name_length_set || !@fasta_output )
249         puts( "Max name length  : " + @name_length.to_s )
250         log << "Max name length  : " + @name_length.to_s + ld
251       end
252       if( @rsgr >= 0 )
253         puts( "Remove sequences for which the gap ratio > " + @rsgr.to_s )
254         log << "Remove sequences for which the gap ratio > " + @rsgr.to_s + ld
255       end
256       if( @rsl >= 0 )
257         puts( "Remove sequences with less than "  + @rsl.to_s + " non-gap characters" )
258         log << "Remove sequences with less than "  + @rsl.to_s + " non-gap characters" + ld
259       end
260       if ( @remove_seqs )
261         puts( "Remove sequences listed in: " + @seqs_name_file )
262         log << "Remove sequences listed in: " + @seqs_name_file + ld
263       elsif ( @keep_seqs )
264         puts( "Keep only sequences listed in: " + @seqs_name_file )
265         log << "Keep only sequences listed in: " + @seqs_name_file + ld
266       end
267       if ( @trim )
268         puts( "Keep only columns from: "+ @first.to_s + " to " + @last.to_s )
269         log << "Keep only columns from: "+ @first.to_s + " to " + @last.to_s + ld
270       end
271       if ( @rem_red )
272         puts( "Remove redundant sequences: true" )
273         log << "Remove redundant sequences: true" + ld
274       end
275       if ( @split_by_os )
276         puts( "Split by OS      : true"  )
277         log << "Split            : true" + ld
278       end
279       if ( @split > 0 )
280         puts( "Split            : " + @split.to_s )
281         log << "Split            : " + @split.to_s + ld
282       end
283       if @window
284         puts( "Sliding window extraction: true"  )
285         log << "Sliding window extraction: true" + ld
286         puts( "Sliding window step      : " + @step.to_s )
287         log << "Sliding window step      : " + @step.to_s + ld
288         puts( "Sliding window size      : " +  @size.to_s )
289         log << "Sliding window size      : " +  @size.to_s + ld
290       end
291
292       puts()
293
294       f = MsaFactory.new()
295
296       msa = nil
297
298       begin
299         if ( @phylip_input )
300           msa = f.create_msa_from_file( input, GeneralMsaParser.new() )
301         elsif ( @fasta_input )
302           msa = f.create_msa_from_file( input, FastaParser.new() )
303         end
304       rescue Exception => e
305         Util.fatal_error( PRG_NAME, "error: " + e.to_s, STDOUT )
306       end
307
308       if ( msa.is_aligned() )
309         Util.print_message( PRG_NAME, "Length of original alignment         : " + msa.get_length.to_s )
310         log << "Length of original alignment         : " + msa.get_length.to_s + ld
311         gp = msa.calculate_gap_proportion
312         Util.print_message( PRG_NAME, "Gap-proportion of original alignment : " + gp.to_s )
313         log << "Gap-proportion of original alignment : " +  gp.to_s + ld
314       else
315         Util.print_message( PRG_NAME, "Input is not aligned" )
316         log << "Input is not aligned" + ld
317       end
318
319       all_names = Set.new()
320       for i in 0 ... msa.get_number_of_seqs()
321         current_name = msa.get_sequence( i ).get_name
322         if all_names.include?( current_name )
323           Util.print_warning_message( PRG_NAME, "sequence name [" + current_name + "] is not unique" )
324         else
325           all_names.add( current_name )
326         end
327       end
328
329       begin
330
331         if ( @remove_seqs || @keep_seqs )
332           names = Util.file2array( @seqs_name_file, true )
333           if ( names == nil ||  names.length() < 1 )
334             error_msg = "file \"" + @seqs_name_file.to_s + "\" appears empty"
335             Util.fatal_error( PRG_NAME, error_msg )
336           end
337
338           if ( @remove_seqs )
339             c = 0
340             for i in 0 ... names.length()
341               to_delete = msa.find_by_name( names[ i ], true, false )
342               if ( to_delete.length() < 1 )
343                 error_msg = "sequence name \"" + names[ i ] + "\" not found"
344                 Util.fatal_error( PRG_NAME, error_msg )
345               elsif ( to_delete.length() > 1 )
346                 error_msg = "sequence name \"" + names[ i ] + "\" is not unique"
347                 Util.fatal_error( PRG_NAME, error_msg )
348               else
349                 msa.remove_sequence!( to_delete[ 0 ] )
350                 c += 1
351               end
352             end
353             Util.print_message( PRG_NAME, "Removed " + c.to_s + " sequences" )
354             log <<  "Removed " + c.to_s + " sequences" + ld
355           elsif ( @keep_seqs )
356             msa_new = Msa.new()
357             r = 0
358             k = 0
359             for j in 0 ... msa.get_number_of_seqs()
360               if ( names.include?( msa.get_sequence( j ).get_name() ) )
361                 msa_new.add_sequence( msa.get_sequence( j ) )
362                 k += 1
363               else
364                 r += 1
365               end
366             end
367             msa = msa_new
368             Util.print_message( PRG_NAME, "Kept    " + k.to_s + " sequences" )
369             log << "Kept    " + k.to_s + " sequences" + ld
370             Util.print_message( PRG_NAME, "Removed " + r.to_s + " sequences" )
371             log << "removed " + r.to_s + " sequences" + ld
372           end
373         end
374
375         if ( @trim )
376           msa.trim!( @first, @last, '_S' )
377         end
378
379         if @window
380           msas = msa.sliding_extraction( @step, @size, '_Q' )
381           begin
382             io = MsaIO.new()
383             w = MsaWriter
384             if ( @pi_output )
385               w = PhylipSequentialWriter.new()
386               w.clean( @clean )
387               w.set_max_name_length( @name_length )
388             elsif( @fasta_output )
389               w = FastaWriter.new()
390               w.set_line_width( @width )
391               w.clean( @clean )
392               if ( @name_length_set )
393                 w.set_max_name_length( @name_length )
394               end
395             elsif( @nexus_output )
396               w = NexusWriter.new()
397               w.clean( @clean )
398               w.set_max_name_length( @name_length )
399             end
400             for m in msas
401               name = output + "_" + m.get_name
402               if @fasta_output
403                 name += ".fasta"
404               elsif @nexus_output
405                 name += ".nex"
406               end
407               io.write_to_file( m, name, w )
408             end
409             Util.print_message( PRG_NAME, "wrote " + msas.length.to_s + " files"  )
410             log << "wrote " + msas.length.to_s + " files" + ld
411           rescue Exception => e
412             Util.fatal_error( PRG_NAME, "error: " + e.to_s, STDOUT )
413           end
414         end
415
416         if( @rgr >= 0 )
417           msa.remove_gap_columns_w_gap_ratio!( @rgr )
418         elsif ( @rgc )
419           msa.remove_gap_columns!()
420         elsif( @rgoc )
421           msa.remove_gap_only_columns!()
422         end
423         if( @rsgr >= 0 )
424           n = msa.get_number_of_seqs()
425           removed = msa.remove_sequences_by_gap_ratio!( @rsgr )
426           k = msa.get_number_of_seqs()
427           r = n - k
428           Util.print_message( PRG_NAME, "Kept    " + k.to_s + " sequences" )
429           log << "Kept    " + k.to_s + " sequences" + ld
430           Util.print_message( PRG_NAME, "Removed " + r.to_s + " sequences"  )
431           log << "Removed " + r.to_s + " sequences:" + ld
432           removed.each { | seq_name |
433             log << "         " + seq_name  + ld
434           }
435         end
436         if( @rsl >= 0 )
437           n = msa.get_number_of_seqs()
438           removed = msa.remove_sequences_by_non_gap_length!( @rsl )
439           k = msa.get_number_of_seqs()
440           r = n - k
441           Util.print_message( PRG_NAME, "Kept    " + k.to_s + " sequences" )
442           log << "Kept    " + k.to_s + " sequences" + ld
443           Util.print_message( PRG_NAME, "Removed " + r.to_s + " sequences" )
444           log << "Removed " + r.to_s + " sequences:" + ld
445           removed.each { | seq_name |
446             log << "         " + seq_name  + ld
447           }
448         end
449         if ( @keep_matching )
450           n = msa.get_number_of_seqs
451           to_be_removed = Set.new
452           for ii in 0 ...  n
453             seq = msa.get_sequence( ii )
454             if !seq.get_name.downcase.index( @keep_matching.downcase )
455               to_be_removed.add( ii )
456             end
457           end
458           to_be_removed_ary = to_be_removed.to_a.sort.reverse
459           to_be_removed_ary.each { | index |
460             msa.remove_sequence!( index )
461           }
462           # msa = sort( msa )
463         end
464         if ( @remove_matching )
465           n = msa.get_number_of_seqs
466           to_be_removed = Set.new
467           for iii in 0 ... n
468
469             seq = msa.get_sequence( iii )
470
471             if seq.get_name.downcase.index( @remove_matching.downcase )
472               to_be_removed.add( iii )
473             end
474           end
475           to_be_removed_ary = to_be_removed.to_a.sort.reverse
476           to_be_removed_ary.each { | index |
477             msa.remove_sequence!( index )
478           }
479           msa = sort( msa )
480         end
481
482         if ( @split_by_os )
483           begin
484             msa_hash = msa.split_by_os(true)
485             io = MsaIO.new()
486             w = MsaWriter
487             if ( @pi_output )
488               w = PhylipSequentialWriter.new()
489               w.clean( @clean )
490               w.set_max_name_length( @name_length )
491             elsif( @fasta_output )
492               w = FastaWriter.new()
493               w.set_line_width( @width )
494               if ( @rg )
495                 w.remove_gap_chars( true )
496                 Util.print_warning_message( PRG_NAME, "removing gap character, the output is likely to become unaligned" )
497                 log << "removing gap character, the output is likely to become unaligned" + ld
498               end
499               w.clean( @clean )
500               if ( @name_length_set )
501                 w.set_max_name_length( @name_length )
502               end
503             elsif( @nexus_output )
504               w = NexusWriter.new()
505               w.clean( @clean )
506               w.set_max_name_length( @name_length )
507             end
508             msa_hash.each do |os, m|
509               my_os = os.gsub(' ', '_').gsub('/', '_').gsub('(', '_').gsub(')', '_')
510               io.write_to_file( m, output + '_' + my_os, w )
511             end
512
513             Util.print_message( PRG_NAME, "wrote " + msa_hash.length.to_s + " files"  )
514             log << "wrote " + msa_hash.length.to_s + " files" + ld
515           rescue Exception => e
516             Util.fatal_error( PRG_NAME, "error: " + e.to_s, STDOUT )
517           end
518
519         elsif ( @split > 0 )
520           begin
521             msas = msa.split( @split, true )
522             io = MsaIO.new()
523             w = MsaWriter
524             if ( @pi_output )
525               w = PhylipSequentialWriter.new()
526               w.clean( @clean )
527               w.set_max_name_length( @name_length )
528             elsif( @fasta_output )
529               w = FastaWriter.new()
530               w.set_line_width( @width )
531               if ( @rg )
532                 w.remove_gap_chars( true )
533                 Util.print_warning_message( PRG_NAME, "removing gap character, the output is likely to become unaligned" )
534                 log << "removing gap character, the output is likely to become unaligned" + ld
535               end
536               w.clean( @clean )
537               if ( @name_length_set )
538                 w.set_max_name_length( @name_length )
539               end
540             elsif( @nexus_output )
541               w = NexusWriter.new()
542               w.clean( @clean )
543               w.set_max_name_length( @name_length )
544             end
545             i = 0
546             for m in msas
547               i = i + 1
548               io.write_to_file( m, output + "_" + i.to_s, w )
549             end
550             Util.print_message( PRG_NAME, "wrote " + msas.length.to_s + " files"  )
551             log << "wrote " + msas.length.to_s + " files" + ld
552           rescue Exception => e
553             Util.fatal_error( PRG_NAME, "error: " + e.to_s, STDOUT )
554           end
555         end
556       rescue Exception => e
557         Util.fatal_error( PRG_NAME, "error: " + e.to_s, STDOUT )
558       end
559
560       if (@split <= 0) && (!@split_by_os) && (!@window)
561
562         unless ( @rg )
563           if ( msa.is_aligned() )
564             Util.print_message( PRG_NAME, "Length of processed alignment        : " + msa.get_length.to_s )
565             log <<  "Length of processed alignment        : " + msa.get_length.to_s + ld
566             gp = msa.calculate_gap_proportion
567             Util.print_message( PRG_NAME, "Gap-proportion of processed alignment: " + gp.to_s )
568             log << "Gap-proportion of processed alignment: " +  gp.to_s + ld
569           else
570             min = 0
571             max = 0
572             sum = 0
573             first = true
574             for s in 0 ... msa.get_number_of_seqs
575               seq = msa.get_sequence( s )
576               l = seq.get_length
577               sum += l
578               if l > max
579                 max = l
580               end
581               if first || l < min
582                 min = l
583               end
584               first = false
585             end
586             avg = sum / msa.get_number_of_seqs
587             Util.print_message( PRG_NAME, "Output is not aligned" )
588             log << "Output is not aligned" + ld
589             Util.print_message( PRG_NAME, "Shortest sequence                    : " + min.to_s )
590             log <<  "Shortest sequence                    : " + min.to_s + ld
591             Util.print_message( PRG_NAME, "Longest sequence                     : " + max.to_s )
592             log <<  "Longest sequence                     : " + max.to_s + ld
593             Util.print_message( PRG_NAME, "Average length                       : " + avg.to_s )
594             log <<  "Average length                       : " + avg.to_s + ld
595
596           end
597         end
598
599         if @rem_red
600           removed = msa.remove_redundant_sequences!( true, false )
601           if removed.size > 0
602             identicals = msa.get_identical_seqs_detected
603             log << "the following " + identicals.size.to_s + " sequences are identical:" + ld
604             identicals.each { | identical |
605               log << identical + ld
606             }
607             log << "ignoring the following " + removed.size.to_s + " redundant sequences:" + ld
608             removed.each { | seq_name |
609               log << seq_name + ld
610             }
611             Util.print_message( PRG_NAME, "will store " + msa.get_number_of_seqs.to_s + " non-redundant sequences" )
612             log << "will store " + msa.get_number_of_seqs.to_s + " non-redundant sequences" + ld
613           end
614         end
615
616         io = MsaIO.new()
617
618         w = MsaWriter
619
620         if ( @pi_output )
621           w = PhylipSequentialWriter.new()
622           w.clean( @clean )
623           w.set_max_name_length( @name_length )
624           w.set_exception_if_name_too_long( @die_if_name_too_long )
625         elsif( @fasta_output )
626           w = FastaWriter.new()
627           w.set_line_width( @width )
628           if ( @rg )
629             w.remove_gap_chars( true )
630             Util.print_warning_message( PRG_NAME, "removing gap characters, the output is likely to become unaligned"  )
631             log << "removing gap character, the output is likely to become unaligned" + ld
632           end
633           w.clean( @clean )
634           if ( @name_length_set )
635             w.set_max_name_length( @name_length )
636             w.set_exception_if_name_too_long( @die_if_name_too_long )
637           end
638         elsif( @nexus_output )
639           w = NexusWriter.new()
640           w.clean( @clean )
641           w.set_max_name_length( @name_length )
642           w.set_exception_if_name_too_long( @die_if_name_too_long )
643         end
644
645         begin
646           io.write_to_file( msa, output, w )
647         rescue Exception => e
648           Util.fatal_error( PRG_NAME, "error: " + e.to_s )
649         end
650
651         Util.print_message( PRG_NAME, "Number of sequences in output        : " + msa.get_number_of_seqs.to_s )
652         log << "Number of sequences in output        : " + msa.get_number_of_seqs.to_s + ld
653
654         begin
655           f = File.open( output + LOG_SUFFIX, 'a' )
656           f.print( log )
657           f.close
658         rescue Exception => e
659           Util.fatal_error( PRG_NAME, "error: " + e.to_s )
660         end
661
662       end
663       Util.print_message( PRG_NAME, "OK" )
664       puts
665     end
666
667     private
668
669     def sort( msa )
670       names = Set.new
671       for i in 0 ... msa.get_number_of_seqs
672         name = msa.get_sequence( i ).get_name
673         names.add( name )
674       end
675       sorted_ary = names.to_a.sort
676       new_msa = Msa.new
677       sorted_ary.each { | seq_name |
678         seq = msa.get_sequence( msa.find_by_name( seq_name, true, false )[ 0 ] )
679         new_msa.add_sequence( seq )
680       }
681       new_msa
682     end
683
684     def set_fasta_input( fi = true )
685       @fasta_input = fi
686       @input_format_set = true
687     end
688
689     def set_phylip_input( pi = true )
690       @phylip_input = pi
691       @input_format_set = true
692     end
693
694     def set_name_length( i )
695       @name_length = i
696       @name_length_set = true
697     end
698
699     def set_width( i )
700       @width = i
701     end
702
703     def set_fasta_output( fo = true )
704       @fasta_output = fo
705       @output_format_set = true
706     end
707
708     def set_pi_output( pso = true )
709       @pi_output = pso
710       @output_format_set = true
711     end
712
713     def set_nexus_output( nexus = true )
714       @nexus_output = nexus
715       @output_format_set = true
716     end
717
718     def set_clean( c = true )
719       @clean = c
720     end
721
722     def set_remove_gap_columns( rgc = true )
723       @rgc = rgc
724     end
725
726     def set_remove_gap_only_columns( rgoc = true )
727       @rgoc = rgoc
728     end
729
730     def set_remove_gaps( rg = true )
731       @rg = rg
732     end
733
734     def set_remove_gap_ratio( rgr )
735       @rgr = rgr
736     end
737
738     def set_remove_seqs_gap_ratio( rsgr )
739       @rsgr = rsgr
740     end
741
742     def set_remove_seqs_min_non_gap_length( rsl )
743       @rsl = rsl
744     end
745
746     def set_remove_seqs( file )
747       @seqs_name_file = file
748       @remove_seqs    = true
749       @keep_seqs      = false
750     end
751
752     def set_keep_seqs( file )
753       @seqs_name_file = file
754       @keep_seqs      = true
755       @remove_seqs    = false
756     end
757
758     def set_trim( first, last )
759       @trim            = true
760       @first           = first
761       @last            = last
762     end
763
764     def set_remove_matching( remove )
765       @remove_matching  = remove
766     end
767
768     def set_keep_matching( keep )
769       @keep_matching = keep
770     end
771
772     def set_rem_red( rr )
773       @rem_red = rr
774     end
775
776     def set_split( s )
777       if ( s > 0 )
778         @split            = s
779         @split_by_os      = false
780         @clean            = false  # phylip only
781         @rgc              = false
782         @rgoc             = false
783         @rg               = false  # fasta only
784         @rgr              = -1
785         @rsgr             = -1
786         @rsl              = -1
787         @seqs_name_file   = nil
788         @remove_seqs      = false
789         @keep_seqs        = false
790         @trim             = false
791         @first            = -1
792         @last             = -1
793       end
794     end
795
796     def set_split_by_os()
797       @split            = -1
798       @split_by_os      = true
799       @clean            = false  # phylip only
800       @rgc              = false
801       @rgoc             = false
802       @rg               = false  # fasta only
803       @rgr              = -1
804       @rsgr             = -1
805       @rsl              = -1
806       @seqs_name_file   = nil
807       @remove_seqs      = false
808       @keep_seqs        = false
809       @trim             = false
810       @first            = -1
811       @last             = -1
812       @window           = false
813     end
814
815     def set_window()
816       @split            = -1
817       @split_by_os      = false
818       @rgc              = false
819       @rgoc             = false
820       @rg               = false  # fasta only
821       @rgr              = -1
822       @rsgr             = -1
823       @rsl              = -1
824       @seqs_name_file   = nil
825       @remove_seqs      = false
826       @keep_seqs        = false
827       @trim             = false
828       @first            = -1
829       @last             = -1
830       @window           = true
831     end
832
833     def analyze_command_line( cla )
834       if ( cla.is_option_set?( INPUT_TYPE_OPTION ) )
835         begin
836           type = cla.get_option_value( INPUT_TYPE_OPTION )
837           if ( type == "p" )
838             set_phylip_input( true )
839             set_fasta_input( false )
840           elsif ( type == "f" )
841             set_fasta_input( true )
842             set_phylip_input( false )
843           end
844         rescue ArgumentError => e
845           Util.fatal_error( PRG_NAME, "error: " + e.to_s, STDOUT )
846         end
847       end
848       if ( cla.is_option_set?( OUTPUT_TYPE_OPTION ) )
849         begin
850           type = cla.get_option_value( OUTPUT_TYPE_OPTION )
851           if ( type == "p" )
852             set_pi_output( true )
853             set_fasta_output( false )
854             set_nexus_output( false )
855           elsif ( type == "f" )
856             set_pi_output( false )
857             set_fasta_output( true )
858             set_nexus_output( false )
859           elsif ( type == "n" )
860             set_pi_output( false )
861             set_fasta_output( false )
862             set_nexus_output( true )
863           end
864         rescue ArgumentError => e
865           Util.fatal_error( PRG_NAME, "error: " + e.to_s, STDOUT )
866         end
867       end
868       if ( cla.is_option_set?( MAXIMAL_NAME_LENGTH_OPTION ) )
869         begin
870           l = cla.get_option_value_as_int( MAXIMAL_NAME_LENGTH_OPTION )
871           set_name_length( l )
872         rescue ArgumentError => e
873           Util.fatal_error( PRG_NAME, "error: " + e.to_s, STDOUT )
874         end
875       end
876       if ( cla.is_option_set?( WIDTH_OPTION ) )
877         begin
878           w = cla.get_option_value_as_int( WIDTH_OPTION )
879           set_width( w )
880         rescue ArgumentError => e
881           Util.fatal_error( PRG_NAME, "error: " + e.to_s, STDOUT )
882         end
883       end
884       if ( cla.is_option_set?( CLEAN_UP_SEQ_OPTION ) )
885         set_clean( true )
886       end
887       if ( cla.is_option_set?( REMOVE_GAP_COLUMNS_OPTION ) )
888         set_remove_gap_columns( true )
889       end
890       if ( cla.is_option_set?( REM_RED_OPTION ) )
891         set_rem_red( true )
892       end
893       if ( cla.is_option_set?( REMOVE_GAP_ONLY_COLUMNS ) )
894         set_remove_gap_only_columns( true )
895       end
896       if ( cla.is_option_set?( REMOVE_ALL_GAP_CHARACTERS_OPTION ) )
897         set_remove_gaps( true )
898       end
899       if ( cla.is_option_set?( REMOVE_COLUMNS_GAP_RATIO_OPTION ) )
900         begin
901           f = cla.get_option_value_as_float( REMOVE_COLUMNS_GAP_RATIO_OPTION )
902           set_remove_gap_ratio( f )
903         rescue ArgumentError => e
904           Util.fatal_error( PRG_NAME, "error: " + e.to_s, STDOUT )
905         end
906       end
907       if ( cla.is_option_set?( REMOVE_ALL_SEQUENCES_LISTED_OPTION ) )
908         begin
909           s = cla.get_option_value( REMOVE_ALL_SEQUENCES_LISTED_OPTION )
910           set_remove_seqs( s )
911         rescue ArgumentError => e
912           Util.fatal_error( PRG_NAME, "error: " + e.to_s, STDOUT )
913         end
914       end
915       if ( cla.is_option_set?( KEEP_ONLY_SEQUENCES_LISTED_OPTION ) )
916         begin
917           s = cla.get_option_value( KEEP_ONLY_SEQUENCES_LISTED_OPTION )
918           set_keep_seqs( s )
919         rescue ArgumentError => e
920           Util.fatal_error( PRG_NAME, "error: " + e.to_s, STDOUT )
921         end
922       end
923       if ( cla.is_option_set?( TRIM_OPTION ) )
924         begin
925           s = cla.get_option_value( TRIM_OPTION )
926           if ( s =~ /(\d+)-(\d+)/ )
927             set_trim( $1.to_i(), $2.to_i() )
928           else
929             puts( "illegal argument" )
930             print_help
931             exit( -1 )
932           end
933         rescue ArgumentError => e
934           Util.fatal_error( PRG_NAME, "error: " + e.to_s, STDOUT )
935         end
936       end
937       if ( cla.is_option_set?( SLIDING_EXTRACTION_OPTION ) )
938         begin
939           s = cla.get_option_value( SLIDING_EXTRACTION_OPTION )
940           if ( s =~ /(\d+)\/(\d+)/ )
941             set_window
942             @window = true
943             @step = $1.to_i()
944             @size = $2.to_i()
945           else
946             puts( "illegal argument" )
947             print_help
948             exit( -1 )
949           end
950           if (@step <= 0) || (@size <= 0)
951             puts( "illegal argument" )
952             print_help
953             exit( -1 )
954           end
955         rescue ArgumentError => e
956           Util.fatal_error( PRG_NAME, "error: " + e.to_s, STDOUT )
957         end
958       end
959
960       if ( cla.is_option_set?( REMOVE_SEQS_GAP_RATIO_OPTION ) )
961         begin
962           f = cla.get_option_value_as_float( REMOVE_SEQS_GAP_RATIO_OPTION )
963           set_remove_seqs_gap_ratio( f )
964         rescue ArgumentError => e
965           Util.fatal_error( PRG_NAME, "error: " + e.to_s, STDOUT )
966         end
967       end
968       if ( cla.is_option_set?( REMOVE_SEQS_NON_GAP_LENGTH_OPTION ) )
969         begin
970           f = cla.get_option_value_as_int( REMOVE_SEQS_NON_GAP_LENGTH_OPTION )
971           set_remove_seqs_min_non_gap_length( f )
972         rescue ArgumentError => e
973           Util.fatal_error( PRG_NAME, "error: " + e.to_s, STDOUT )
974         end
975       end
976       if cla.is_option_set?( SPLIT_BY_OS )
977         begin
978           set_split_by_os()
979         rescue ArgumentError => e
980           Util.fatal_error( PRG_NAME, "error: " + e.to_s, STDOUT )
981         end
982       elsif ( cla.is_option_set?( SPLIT ) )
983         begin
984           s = cla.get_option_value_as_int( SPLIT )
985           set_split( s )
986         rescue ArgumentError => e
987           Util.fatal_error( PRG_NAME, "error: " + e.to_s, STDOUT )
988         end
989       end
990       if ( cla.is_option_set?( REMOVE_MATCHING_SEQUENCES_OPTION ) )
991         begin
992           s = cla.get_option_value( REMOVE_MATCHING_SEQUENCES_OPTION )
993           set_remove_matching( s )
994         rescue ArgumentError => e
995           Util.fatal_error( PRG_NAME, "error: " + e.to_s, STDOUT )
996         end
997       end
998       if ( cla.is_option_set?( KEEP_MATCHING_SEQUENCES_OPTION ) )
999         begin
1000           s = cla.get_option_value( KEEP_MATCHING_SEQUENCES_OPTION )
1001           set_keep_matching( s )
1002         rescue ArgumentError => e
1003           Util.fatal_error( PRG_NAME, "error: " + e.to_s, STDOUT )
1004         end
1005       end
1006       if ( cla.is_option_set?( DIE_IF_NAME_TOO_LONG ) )
1007         @die_if_name_too_long = true
1008       end
1009
1010     end
1011
1012     def print_help()
1013       puts()
1014       puts( "Usage:" )
1015       puts()
1016       puts( "  " + PRG_NAME + ".rb [options] <input alignment> <output>" )
1017       puts()
1018       puts( "  options: -" + INPUT_TYPE_OPTION + "=<input type>: f for fasta (default), p for phylip/selex type" )
1019       puts( "           -" + OUTPUT_TYPE_OPTION + "=<output type>: f for fasta (default), n for nexus, p for phylip sequential" )
1020       puts( "           -" + MAXIMAL_NAME_LENGTH_OPTION + "=<n>: n=maximal name length (default for phylip 10, for fasta: unlimited )" )
1021       puts( "           -" + DIE_IF_NAME_TOO_LONG + ": die if sequence name too long" )
1022       puts( "           -" + WIDTH_OPTION + "=<n>: n=width (fasta output only, default is 60)" )
1023       puts( "           -" + CLEAN_UP_SEQ_OPTION + ": clean up sequences" )
1024       puts( "           -" + REMOVE_GAP_COLUMNS_OPTION + ": remove gap columns" )
1025       puts( "           -" + REMOVE_GAP_ONLY_COLUMNS + ": remove gap-only columns" )
1026       puts( "           -" + REMOVE_COLUMNS_GAP_RATIO_OPTION + "=<n>: remove columns for which ( seqs with gap / number of sequences > n )" )
1027       puts( "           -" + REMOVE_ALL_GAP_CHARACTERS_OPTION + ": remove all gap characters (destroys alignment, fasta output only)" )
1028       puts( "           -" + REMOVE_ALL_SEQUENCES_LISTED_OPTION + "=<file>: remove all sequences listed in file" )
1029       puts( "           -" + KEEP_ONLY_SEQUENCES_LISTED_OPTION + "=<file>: keep only sequences listed in file" )
1030       puts( "           -" + TRIM_OPTION + "=<first>-<last>: remove columns before first and after last" )
1031       puts( "           -" + REMOVE_SEQS_GAP_RATIO_OPTION + "=<n>: remove sequences for which the gap ratio > n (after column operations)" )
1032       puts( "           -" + REMOVE_SEQS_NON_GAP_LENGTH_OPTION + "=<n> remove sequences with less than n non-gap characters (after column operations)" )
1033       puts( "           -" + REMOVE_MATCHING_SEQUENCES_OPTION + "=<s> remove all sequences with names containing s" )
1034       puts( "           -" + KEEP_MATCHING_SEQUENCES_OPTION + "=<s> keep only sequences with names containing s" )
1035       puts( "           -" + SPLIT + "=<n> split a fasta file into n files of equal number of sequences (expect for " )
1036       puts( "            last one), cannot be used with other options" )
1037       puts( "           -" + SLIDING_EXTRACTION_OPTION + "=<step>/<window size>: sliding window extraction, cannot be used with other options" )
1038       puts( "           -" + REM_RED_OPTION + ": remove redundant sequences" )
1039       puts()
1040     end
1041
1042   end # class MsaProcessor
1043
1044 end # module Evoruby