2 # = lib/evo/msa/msa.rb - Msa class
4 # Copyright:: Copyright (C) 2006-2007 Christian M. Zmasek
5 # License:: GNU Lesser General Public License (LGPL)
7 # $Id: msa.rb,v 1.11 2009/01/03 00:42:08 cmzmasek Exp $
11 require 'lib/evo/util/constants'
12 require 'lib/evo/util/util'
13 require 'lib/evo/sequence/sequence'
20 @sequences = Array.new
21 @identical_seqs_detected = Array.new
25 def add_sequence( sequence )
26 @sequences.push( sequence )
29 def add( name, molecular_sequence_str )
30 add_sequence( Sequence.new( name, molecular_sequence_str ) )
33 def get_sequence( index )
34 if ( index < 0 || index > get_number_of_seqs() - 1 )
35 error_msg = "attempt to get sequence " <<
36 index.to_s << " in alignment of " << get_number_of_seqs().to_s <<
38 raise ArgumentError, error_msg
40 return @sequences[ index ]
43 def remove_sequence!( index )
44 if ( index < 0 || index > get_number_of_seqs() - 1 )
45 error_msg = "attempt to remove sequence " <<
46 index.to_s << " in alignment of " << get_number_of_seqs().to_s <<
48 raise ArgumentError, error_msg
50 @sequences.delete_at( index )
53 def get_identical_seqs_detected
54 @identical_seqs_detected
59 if ( get_number_of_seqs < 1 )
62 l = @sequences[ 0 ].get_length()
63 for i in 0 ... get_number_of_seqs()
64 if ( get_sequence( i ).get_length() != l )
72 def find_by_name( name, case_sensitive, partial_match )
74 for i in 0 ... get_number_of_seqs()
75 current_name = get_sequence( i ).get_name()
77 current_name = current_name.downcase
80 if current_name == name ||
81 ( partial_match && current_name.include?( name ) )
88 def find_by_name_pattern( name_re, avoid_similar_to = true )
90 for i in 0 ... get_number_of_seqs()
92 m = name_re.match( get_sequence( i ).get_name() )
93 if m && !m.pre_match.downcase.include?( "similar to " )
97 if name_re.match( get_sequence( i ).get_name() )
105 # throws ArgumentError
106 def get_by_name_pattern( name_re , avoid_similar_to = true )
107 indices = find_by_name_pattern( name_re, avoid_similar_to )
108 if ( indices.length > 1 )
109 error_msg = "pattern " + name_re.to_s + " not unique"
110 raise ArgumentError, error_msg
111 elsif ( indices.length < 1 )
112 error_msg = "pattern " + name_re.to_s + " not found"
113 raise ArgumentError, error_msg
115 get_sequence( indices[ 0 ] )
118 def find_by_name_start( name, case_sensitive )
120 for i in 0 ... get_number_of_seqs()
121 get_sequence( i ).get_name() =~ /^\s*(\S+)/
124 current_name = current_name.downcase
127 if ( current_name == name )
134 def has?( name, case_sensitive = true, partial_match = false )
135 for i in 0 ... get_number_of_seqs()
136 current_name = get_sequence( i ).get_name()
138 current_name = current_name.downcase
141 if current_name == name ||
142 ( partial_match && current_name.include?( name ) )
149 # throws ArgumentError
150 def get_by_name( name, case_sensitive = true, partial_match = false )
151 indices = find_by_name( name, case_sensitive, partial_match )
152 if ( indices.length > 1 )
153 error_msg = "\"" + name + "\" not unique"
154 raise ArgumentError, error_msg
155 elsif ( indices.length < 1 )
156 error_msg = "\"" + name + "\" not found"
157 raise ArgumentError, error_msg
159 get_sequence( indices[ 0 ] )
162 # throws ArgumentError
163 def get_by_name_start( name, case_sensitive = true )
164 indices = find_by_name_start( name, case_sensitive )
165 if ( indices.length > 1 )
166 error_msg = "\"" + name + "\" not unique"
167 raise ArgumentError, error_msg
168 elsif ( indices.length < 1 )
169 error_msg = "\"" + name + "\" not found"
170 raise ArgumentError, error_msg
172 get_sequence( indices[ 0 ] )
176 def get_sub_alignment( seq_numbers )
178 for i in 0 ... seq_numbers.length()
179 msa.add_sequence( get_sequence( seq_numbers[ i ] ).copy() )
184 def get_number_of_seqs()
190 error_msg = "attempt to get length of unaligned msa"
191 raise StandardError, error_msg, caller
193 if get_number_of_seqs() < 1
196 @sequences[ 0 ].get_length()
206 for i in 0...get_number_of_seqs
207 s += @sequences[ i ].to_fasta + Constants::LINE_DELIMITER
213 def print_overlap_diagram( min_overlap = 1, io = STDOUT, max_name_length = 10 )
215 error_msg = "attempt to get overlap diagram of unaligned msa"
216 raise StandardError, error_msg, caller
218 for i in 0 ... get_number_of_seqs()
219 io.print( Util.normalize_seq_name( get_sequence( i ).get_name(), max_name_length ) )
220 for j in 0 ... get_number_of_seqs()
224 if overlap?( i, j, min_overlap )
231 io.print( Evoruby::Constants::LINE_DELIMITER )
235 #returns array of Msa with an overlap of min_overlap
236 def split_into_overlapping_msa( min_overlap = 1 )
238 error_msg = "attempt to split into overlapping msas of unaligned msa"
239 raise StandardError, error_msg, caller
242 bins = get_overlaps( min_overlap )
243 for i in 0 ... bins.length
244 msas.push( get_sub_alignment( bins[ i ] ) )
249 def overlap?( index_1, index_2, min_overlap = 1 )
250 seq_1 = get_sequence( index_1 )
251 seq_2 = get_sequence( index_2 )
253 for i in 0...seq_1.get_length()
254 if !Util.is_aa_gap_character?( seq_1.get_character_code( i ) ) &&
255 !Util.is_aa_gap_character?( seq_2.get_character_code( i ) )
257 if overlap_count >= min_overlap
265 def calculate_overlap( index_1, index_2 )
266 seq_1 = get_sequence( index_1 )
267 seq_2 = get_sequence( index_2 )
269 for i in 0...seq_1.get_length
270 if !Util.is_aa_gap_character?( seq_1.get_character_code( i ) ) &&
271 !Util.is_aa_gap_character?( seq_2.get_character_code( i ) )
278 def calculate_identities( index_1, index_2 )
279 seq_1 = get_sequence( index_1 )
280 seq_2 = get_sequence( index_2 )
282 for i in 0...seq_1.get_length
283 if !Util.is_aa_gap_character?( seq_1.get_character_code( i ) ) &&
284 !Util.is_aa_gap_character?( seq_2.get_character_code( i ) ) &&
285 seq_1.get_character_code( i ) != 63 &&
286 ( seq_1.get_residue( i ).downcase() ==
287 seq_2.get_residue( i ).downcase() )
288 identities_count += 1
294 def remove_gap_only_columns!()
295 remove_columns!( get_gap_only_columns() )
298 def remove_gap_columns!()
299 remove_columns!( get_gap_columns() )
302 # removes columns for which seqs with gap / number of sequences > gap_ratio
303 def remove_gap_columns_w_gap_ratio!( gap_ratio )
304 remove_columns!( get_gap_columns_w_gap_ratio( gap_ratio ) )
308 def remove_sequences_by_gap_ratio!( gap_ratio )
310 error_msg = "attempt to remove sequences by gap ratio on unaligned msa"
311 raise StandardError, error_msg, caller
313 n = get_number_of_seqs
316 if ( get_sequence( ( n - 1 ) - s ).get_gap_ratio() > gap_ratio )
317 if ( Evoruby::Constants::VERBOSE )
318 puts( "removed: " + get_sequence( ( n - 1 ) - s ).get_name )
320 removed << get_sequence( ( n - 1 ) - s ).get_name
321 remove_sequence!( ( n - 1 ) - s )
328 def remove_redundant_sequences!( consider_taxonomy = false, verbose = false )
329 n = get_number_of_seqs
331 to_be_removed = Set.new
332 @identical_seqs_detected = Array.new
333 for i in 0 ... ( n - 1 )
334 for j in ( i + 1 ) ... n
335 if !to_be_removed.include?( i ) && !to_be_removed.include?( j )
336 if !consider_taxonomy ||
337 ( ( get_sequence( i ).get_taxonomy == nil && get_sequence( j ).get_taxonomy == nil ) ||
338 ( get_sequence( i ).get_taxonomy == get_sequence( j ).get_taxonomy ) )
339 if Util.clean_seq_str( get_sequence( i ).get_sequence_as_string ) ==
340 Util.clean_seq_str( get_sequence( j ).get_sequence_as_string )
341 to_be_removed.add( j )
345 if get_sequence( i ).get_taxonomy != nil
346 tax_i = get_sequence( i ).get_taxonomy.get_name
348 if get_sequence( j ).get_taxonomy != nil
349 tax_j = get_sequence( j ).get_taxonomy.get_name
351 identical_pair = get_sequence( i ).get_name + " [#{tax_i}] == " + get_sequence( j ).get_name + " [#{tax_j}]"
352 @identical_seqs_detected.push( identical_pair )
362 to_be_removed_ary = to_be_removed.to_a.sort.reverse
364 to_be_removed_ary.each { | index |
365 removed.push( get_sequence( index ).get_name )
366 remove_sequence!( index )
372 def remove_sequences_by_non_gap_length!( min_non_gap_length )
373 n = get_number_of_seqs
377 seq = get_sequence( x )
378 if ( ( seq.get_length - seq.get_gap_length ) < min_non_gap_length )
379 if ( Evoruby::Constants::VERBOSE )
380 puts( "removed: " + seq.get_name )
382 removed << seq.get_name
383 remove_sequence!( x )
389 def trim!( first, last )
391 for i in 0 ... get_length()
392 if ( i < first || i > last )
396 remove_columns!( cols )
399 def get_gap_only_columns()
401 error_msg = "attempt to get gap only columns of unaligned msa"
402 raise StandardError, error_msg, caller
405 for c in 0 ... get_length
406 nogap_char_found = false
407 for s in 0 ... get_number_of_seqs
408 unless Util.is_aa_gap_character?( get_sequence( s ).get_character_code( c ) )
409 nogap_char_found = true
413 unless nogap_char_found
420 def calculate_gap_proportion()
422 error_msg = "attempt to get gap only columns of unaligned msa"
423 raise StandardError, error_msg, caller
427 for c in 0 ... get_length
428 for s in 0 ... get_number_of_seqs
429 total_sum = total_sum + 1
430 if Util.is_aa_gap_character?( get_sequence( s ).get_character_code( c ) )
431 gap_sum = gap_sum + 1
436 return gap_sum / total_sum
439 def get_gap_columns()
441 error_msg = "attempt to get gap columns of unaligned msa"
442 raise StandardError, error_msg, caller
445 for c in 0 ... get_length
446 gap_char_found = false
447 for s in 0 ... get_number_of_seqs
448 if Util.is_aa_gap_character?( get_sequence( s ).get_character_code( c ) )
449 gap_char_found = true
460 # gap_ratio = seqs with gap / number of sequences
461 # returns column indices for which seqs with gap / number of sequences > gap_ratio
462 def get_gap_columns_w_gap_ratio( gap_ratio )
464 error_msg = "attempt to get gap columns with gap_ratio of unaligned msa"
465 raise StandardError, error_msg, caller
467 if ( gap_ratio < 0 || gap_ratio > 1 )
468 error_msg = "gap ratio must be between 0 and 1 inclusive"
469 raise ArgumentError, error_msg, caller
472 for c in 0 ... get_length
474 for s in 0 ... get_number_of_seqs
475 if Util.is_aa_gap_character?( get_sequence( s ).get_character_code( c ) )
479 if ( ( gap_chars_found.to_f / get_number_of_seqs ) > gap_ratio )
487 # Split an alignment into n alignemnts of equal size, except last one
488 def split( n, verbose = false )
489 if ( n < 2 || n > get_number_of_seqs )
490 error_msg = "attempt to split into less than two or more than the number of sequences"
491 raise StandardError, error_msg, caller
494 r = get_number_of_seqs % n
495 x = get_number_of_seqs / n
500 if ( ( r > 0 ) && ( i == ( n - 1 ) ) )
503 puts( i.to_s + ": " + y.to_s )
506 msa.add_sequence( get_sequence( ( i * x ) + j ) )
510 puts( i.to_s + ": " + x.to_s )
513 msa.add_sequence( get_sequence( ( i * x ) + j ) )
524 def get_overlaps( min_overlap = 1 )
526 error_msg = "attempt to get overlaps of unaligned msa"
527 raise StandardError, error_msg, caller
530 for i in 0 ... get_number_of_seqs()
532 for j in 0 ... bins.length
533 current_bin = bins[ j ]
534 # does seq i overlap with all seqs in current_bin?
536 for z in 0 ... current_bin.length
537 unless overlap?( i, current_bin[ z ], min_overlap )
543 current_bin.push( i )
548 new_bin = Array.new()
556 def remove_columns!( cols )
558 error_msg = "attempt to remove columns of unaligned msa"
559 raise StandardError, error_msg, caller
562 for c in 0 ... cols.length()
564 for s in 0 ... get_number_of_seqs()
565 get_sequence( s ).delete_residue!( col )