]> git.donarmstrong.com Git - biopieces.git/blob - code_perl/Maasha/AlignTwoSeq.pm
working on AlignTwoSeq
[biopieces.git] / code_perl / Maasha / AlignTwoSeq.pm
1 package Maasha::AlignTwoSeq;
2
3 # Copyright (C) 2007 Martin A. Hansen.
4
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
19 # http://www.gnu.org/copyleft/gpl.html
20
21
22 # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> DESCRIPTION <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
23
24
25 # Routines to create a pair-wise alignment.
26
27
28 # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
29
30
31 use warnings;
32 use strict;
33 use Data::Dumper;
34 use Maasha::Calc;
35 use Maasha::Seq;
36 use vars qw ( @ISA @EXPORT );
37
38 @ISA = qw( Exporter );
39
40
41 # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
42
43
44 sub align_two_seq
45 {
46     # Martin A. Hansen, August 2006.
47
48     # Generates an alignment by chaining matches, which are subsequences
49     # shared between two sequences. The routine functions by considering 
50     # only matches within a given search space. If no matches are given
51     # these will be generated, if long matches are found these will be
52     # included in the alignment, otherwise matches will be included depending
53     # on a calculated score. New search spaces spanning the spaces between
54     # matches and the search space boundaries will be cast and recursed into.
55     
56     # Usage: align_two_seq( { Q_SEQ => \$q_seq, S_SEQ => \$s_seq }, [] );
57
58     my ( $space,     # search space
59          $matches,   # list of matches
60        ) = @_;
61
62     # Returns a list.
63
64     my ( @chain, $best_match, $left_space, $right_space );
65
66     new_space( $space );
67
68     matches_select( $matches, $space );
69
70     if ( scalar @{ $matches } == 0 )   # no matches - find some!
71     {
72         $matches = matches_find( $space );
73
74         if ( scalar @{ $matches } > 0 ) {
75             push @chain, align_two_seq( $space, $matches );
76         }
77     }
78     else   # matches are included according to score
79     {
80         $matches = matches_filter( $space, $matches );
81
82         $best_match = shift @{ $matches };
83
84         if ( $best_match )
85         {
86             push @chain, $best_match;
87
88             $left_space  = new_space_left( $best_match, $space );
89             $right_space = new_space_right( $best_match, $space );
90
91             push @chain, align_two_seq( $left_space, $matches )  if defined $left_space;
92             push @chain, align_two_seq( $right_space, $matches ) if defined $right_space;
93         }
94     }
95
96     return wantarray ? @chain : \@chain;
97 }
98
99
100 sub new_space
101 {
102     # Martin A. Hansen, August 2009.
103
104     # Define a new search space if not previously
105     # defined. This occurs if align_two_seq() is
106     # called initially with only the sequences.
107
108     my ( $space,   # search space
109        ) = @_;
110
111     # Returns nothing.
112
113     if ( not defined $space->{ 'Q_MAX' } )
114     {
115         $space->{ 'Q_MIN' } = 0;
116         $space->{ 'S_MIN' } = 0;
117         $space->{ 'Q_MAX' } = length( ${ $space->{ 'Q_SEQ' } } ) - 1,
118         $space->{ 'S_MAX' } = length( ${ $space->{ 'S_SEQ' } } ) - 1,
119     }
120 }
121
122
123 sub new_space_left
124 {
125     # Martin A. Hansen, August 2009.
126
127     # Create a new search space between the beginning of
128     # the current search space and the best match.
129
130     my ( $best_match,   # best match
131          $space,        # search space
132        ) = @_;
133
134     # Returns hashref.
135
136     my ( $new_space );
137
138     if ( $best_match->{ 'Q_BEG' } - $space->{ 'Q_MIN' } >= 2 and $best_match->{ 'S_BEG' } - $space->{ 'S_MIN' } >= 2 )
139     {
140         $new_space = {
141             Q_SEQ => $space->{ 'Q_SEQ' },
142             S_SEQ => $space->{ 'S_SEQ' },
143             Q_MIN => $space->{ 'Q_MIN' },
144             Q_MAX => $best_match->{ 'Q_BEG' } - 1,
145             S_MIN => $space->{ 'S_MIN' },
146             S_MAX => $best_match->{ 'S_BEG' } - 1,
147         };
148     }
149
150     return wantarray ? %{ $new_space } : $new_space;
151 }
152
153
154 sub new_space_right
155 {
156     # Martin A. Hansen, August 2009.
157
158     # Create a new search space between the best match
159     # and the end of the current search space.
160
161     my ( $best_match,   # best match
162          $space,        # search space
163        ) = @_;
164
165     # Returns hashref.
166
167     my ( $new_space );
168
169     if ( $space->{ 'Q_MAX' } - $best_match->{ 'Q_END' } >= 2 and $space->{ 'S_MAX' } - $best_match->{ 'S_END' } >= 2 )
170     {
171         $new_space = {
172             Q_SEQ => $space->{ 'Q_SEQ' },
173             S_SEQ => $space->{ 'S_SEQ' },
174             Q_MIN => $best_match->{ 'Q_END' } + 1,
175             Q_MAX => $space->{ 'Q_MAX' },
176             S_MIN => $best_match->{ 'S_END' } + 1,
177             S_MAX => $space->{ 'S_MAX' },
178         };
179     }
180
181     return wantarray ? %{ $new_space } : $new_space;
182 }
183
184
185 sub matches_select
186 {
187     # Martin A. Hansen, August 2006.
188
189     # Given a list of matches and a search space,
190     # include only those matches contained within
191     # this search space.
192
193     my ( $matches,   # list of matches
194          $space,     # search space
195        ) = @_;
196
197     # Returns nothing.
198
199     @{ $matches } = grep { $_->{ 'Q_BEG' } >= $space->{ 'Q_MIN' } and
200                            $_->{ 'S_BEG' } >= $space->{ 'S_MIN' } and
201                            $_->{ 'Q_END' } <= $space->{ 'Q_MAX' } and
202                            $_->{ 'S_END' } <= $space->{ 'S_MAX' } } @{ $matches };
203 }
204
205
206 sub word_size_calc
207 {
208     # Martin A. Hansen, August 2008.
209   
210     # Given a search space calculates the wordsize for a word so a match
211     # occurs only a few times. More matches may be needed at low similarity in
212     # order to avoid starting with a wrong match.
213
214     my ( $space,   # search space
215        ) = @_;
216
217     # Returns integer.
218
219     my ( $factor, $word_size );
220
221     $factor = 0.05;
222
223     if ( $space->{ 'Q_MAX' } - $space->{ 'Q_MIN' } < $space->{ 'S_MAX' } - $space->{ 'S_MIN' } ) {
224         $word_size = int( $factor * ( $space->{ 'Q_MAX' } - $space->{ 'Q_MIN' } ) ) + 1;
225     } else {
226         $word_size = int( $factor * ( $space->{ 'S_MAX' } - $space->{ 'S_MIN' } ) ) + 1;
227     }
228
229     return $word_size;
230 }
231
232
233 sub seq_index
234 {
235     # Martin A. Hansen, August 2009.
236
237     # Indexes a query sequence in a specified search space
238     # Overlapping words are saved as keys in an index hash,
239     # and a list of word postions are saved as value.
240
241     my ( $space,       # search space
242          $word_size,   # word size
243        ) = @_;
244
245     # Returns a hashref.
246     
247     my ( $i, $word, %index );
248
249     for ( $i = $space->{ 'Q_MIN' }; $i <= $space->{ 'Q_MAX' } - $word_size + 1; $i++ )
250     {
251         $word = uc substr ${ $space->{ 'Q_SEQ' } }, $i, $word_size;
252
253         next if $word =~ tr/N//;   # Skip sequences with Ns.
254
255         push @{ $index{ $word } }, $i;
256     }
257
258     return wantarray ? %index : \%index;
259 }
260
261
262 sub seq_scan
263 {
264     # Martin A. Hansen, August 2009.
265
266     my ( $index,       # sequence index
267          $space,       # search space
268          $word_size,   # word size
269        ) = @_;
270
271     # Returns a list.
272
273     my ( $i, $word, $pos, $match, @matches, $redundant );
274
275     $redundant = {};
276
277     for ( $i = $space->{ 'S_MIN' }; $i <= $space->{ 'S_MAX' } - $word_size + 1; $i++ )
278     {
279         $word = uc substr ${ $space->{ 'S_SEQ' } }, $i, $word_size;
280
281         if ( exists $index->{ $word } )
282         {
283             foreach $pos ( @{ $index->{ $word } } )
284             {
285                 $match = {
286                     'Q_BEG' => $pos,
287                     'Q_END' => $pos + $word_size - 1,
288                     'S_BEG' => $i,
289                     'S_END' => $i + $word_size - 1,
290                     'LEN'   => $word_size,
291                     'SCORE' => 0,
292                 };
293
294                 if ( not match_redundant( $match, $redundant ) )
295                 {
296                     match_expand( $match, $space );
297
298                     push @matches, $match;
299
300                     match_redundant_add( $match, $redundant );
301                 }
302             }
303         }
304     }
305
306     return wantarray ? @matches : \@matches;
307 }
308
309
310 sub matches_find
311 {
312     # Martin A. Hansen, November 2006
313
314     # Given two sequences, find all maximum expanded matches between these.
315
316     my ( $space,   # search space
317        ) = @_;
318
319     # returns list of matches
320
321     my ( $word_size, $index, $matches );
322
323     $word_size = word_size_calc( $space );
324
325     $matches = [];
326
327     while ( scalar @{ $matches } == 0 and $word_size > 0 )
328     {
329         $index   = seq_index( $space, $word_size );
330         $matches = seq_scan( $index, $space, $word_size );
331
332         $word_size--;
333     }
334
335     return wantarray ? @{ $matches } : $matches;
336 }
337
338
339 sub match_expand
340 {
341     # Martin A. Hansen, August 2009.
342
343     # Given a match and a search space hash,
344     # expand the match maximally both forward and
345     # backward direction.
346
347     my ( $match,   # match to expand
348          $space,   # search space
349        ) = @_;
350
351     # Returns nothing.
352
353     match_expand_forward( $match, $space );
354     match_expand_backward( $match, $space );
355 }
356
357
358 sub match_expand_forward
359 {
360     # Martin A. Hansen, August 2009.
361
362     # Given a match and a search space hash,
363     # expand the match maximally in the forward
364     # direction.
365
366     my ( $match,   # match to expand
367          $space,   # search space
368        ) = @_;
369
370     # Returns nothing.
371
372     while ( $match->{ 'Q_END' } <= $space->{ 'Q_MAX' } and
373             $match->{ 'S_END' } <= $space->{ 'S_MAX' } and 
374             lc substr( ${ $space->{ 'Q_SEQ' } }, $match->{ 'Q_END' }, 1 ) eq lc substr( ${ $space->{ 'S_SEQ' } }, $match->{ 'S_END' }, 1 ) )
375     {
376         $match->{ 'Q_END' }++;
377         $match->{ 'S_END' }++;
378         $match->{ 'LEN' }++;
379     }
380
381     $match->{ 'Q_END' }--;
382     $match->{ 'S_END' }--;
383     $match->{ 'LEN' }--;
384 }
385
386
387 sub match_expand_backward
388 {
389     # Martin A. Hansen, August 2009.
390
391     # Given a match and a search space hash,
392     # expand the match maximally in the backward
393     # direction.
394
395     my ( $match,   # match to expand
396          $space,   # search space
397        ) = @_;
398
399     # Returns nothing.
400
401     while ( $match->{ 'Q_BEG' } >= $space->{ 'Q_MIN' } and
402             $match->{ 'S_BEG' } >= $space->{ 'S_MIN' } and 
403             lc substr( ${ $space->{ 'Q_SEQ' } }, $match->{ 'Q_BEG' }, 1 ) eq lc substr( ${ $space->{ 'S_SEQ' } }, $match->{ 'S_BEG' }, 1 ) )
404     {
405         $match->{ 'Q_BEG'}--;
406         $match->{ 'S_BEG'}--;
407         $match->{ 'LEN' }++;
408     }
409
410     $match->{ 'Q_BEG'}++;
411     $match->{ 'S_BEG'}++;
412     $match->{ 'LEN' }--;
413 }
414
415
416 sub match_redundant
417 {
418     # Martin A. Hansen, August 2009
419     
420     my ( $new_match,       # match
421          $redundant,   # hashref
422        ) = @_;
423
424     # Returns boolean.
425
426     my ( $match );
427
428     foreach $match ( @{ $redundant->{ $new_match->{ 'Q_BEG' } } } )
429     {
430         if ( $new_match->{ 'S_BEG' } >= $match->{ 'S_BEG' } and $new_match->{ 'S_END' } <= $match->{ 'S_END' } ) {
431             return 1;
432         }
433     }
434
435     return 0;
436 }
437
438
439 sub match_redundant_add
440 {
441     # Martin A. Hansen, August 2009
442
443     my ( $match,       # match
444          $redundant,   # hash ref
445        ) = @_;
446
447     # Returns nothing.
448
449     map { push @{ $redundant->{ $_ } }, $match } ( $match->{ 'Q_BEG' } .. $match->{ 'Q_END' } );
450 }
451
452
453 sub matches_filter
454 {
455     # Martin A. Hansen, August 2009.
456
457     my ( $space,     # search space
458          $matches,   # list of matches
459        ) = @_;
460
461     # Returns a list.
462
463     my ( $best_match, $match, @new_matches );
464
465     $best_match = shift @{ $matches };
466
467     match_score( $best_match, $space );
468
469     foreach $match ( @{ $matches } )
470     {
471         match_score( $match, $space );
472
473         if ( $match->{ 'SCORE' } > 0 )
474         {
475             if ( $match->{ 'SCORE' } > $best_match->{ 'SCORE' } ) {
476                 $best_match = $match;
477             } else {
478                 push @new_matches, $match;
479             }
480         }
481     }
482
483     unshift @new_matches, $best_match if $best_match->{ 'SCORE' } > 0;
484
485     return wantarray ? @new_matches : \@new_matches;
486 }
487
488
489 sub match_score
490 {
491     # Martin A. Hansen, August 2009
492     
493     # Given a match and a search space scores the match according to three criteria:
494     
495     # 1) length of match - the longer the better.
496     # 2) distance to closest corner - the shorter the better.
497     # 3) distance to closest narrow end of the search space - the shorter the better.
498     
499     # Each of these scores are divided by search space dimensions, and the total score
500     # is calculated: total = score_len - score_corner - score_narrow.
501     
502     # The higher the score, the better the match.
503
504     my ( $match,   # match
505          $space,   # search space
506        ) = @_;
507
508     # Returns nothing.
509
510     match_score_len( $match );
511     match_score_diag( $match, $space );
512     match_score_narrow( $match, $space );
513 }
514
515
516 sub match_score_len
517 {
518     # Martin A. Hansen, August 2009
519
520     # Score according to match length.
521
522     my ( $match,   # match
523        ) = @_;
524
525     # Returns nothing.
526
527     $match->{ 'SCORE' } = $match->{ 'LEN' } ** 3;
528 }
529
530
531 sub match_score_diag
532 {
533     # Martin A. Hansen, August 2009
534
535     # Score according to distance from diagonal.
536
537     my ( $match,   # match
538          $space,   # search space
539        ) = @_;
540
541     # Returns nothing.
542
543     my ( $q_dim, $s_dim, $beg_diag_dist, $end_diag_dist, $min_diag_dist, $score_diag );
544
545     $q_dim = $space->{ 'Q_MAX' } - $space->{ 'Q_MIN' } + 1;
546     $s_dim = $space->{ 'S_MAX' } - $space->{ 'S_MIN' } + 1;
547
548     if ( $q_dim >= $s_dim ) # s_dim is the narrow end
549     {
550         $beg_diag_dist = Maasha::Calc::dist_point2line( $match->{ 'Q_BEG' },
551                                                         $match->{ 'S_BEG' },
552                                                         $space->{ 'Q_MIN' },
553                                                         $space->{ 'S_MIN' },
554                                                         $space->{ 'Q_MIN' } + $s_dim,
555                                                         $space->{ 'S_MIN' } + $s_dim );
556
557         $end_diag_dist = Maasha::Calc::dist_point2line( $match->{ 'Q_BEG' },
558                                                         $match->{ 'S_BEG' },
559                                                         $space->{ 'Q_MAX' } - $s_dim,
560                                                         $space->{ 'S_MAX' } - $s_dim,
561                                                         $space->{ 'Q_MAX' },
562                                                         $space->{ 'S_MAX' } );
563     }
564     else
565     {
566         $beg_diag_dist = Maasha::Calc::dist_point2line( $match->{ 'Q_BEG' },
567                                                         $match->{ 'S_BEG' },
568                                                         $space->{ 'Q_MIN' },
569                                                         $space->{ 'S_MIN' },
570                                                         $space->{ 'Q_MIN' } + $q_dim,
571                                                         $space->{ 'S_MIN' } + $q_dim );
572
573         $end_diag_dist = Maasha::Calc::dist_point2line( $match->{ 'Q_BEG' },
574                                                         $match->{ 'S_BEG' },
575                                                         $space->{ 'Q_MAX' } - $q_dim,
576                                                         $space->{ 's_max' } - $q_dim,
577                                                         $space->{ 'Q_MAX' },
578                                                         $space->{ 'S_MAX' } );
579     }
580
581     $min_diag_dist = Maasha::Calc::min( $beg_diag_dist, $end_diag_dist );
582
583     $score_diag = 2 * $min_diag_dist ** 2;
584
585     $match->{ 'SCORE' } -= $score_diag;
586 }
587
588
589 sub match_score_narrow
590 {
591     # Martin A. Hansen, August 2009
592
593     # Score according to distance to the narrow end of the search space.
594
595     my ( $match,   # match
596          $space,   # search space
597        ) = @_;
598
599     # Returns nothing.
600
601     my ( $max_narrow_dist, $q_dim, $s_dim, $beg_narrow_dist, $end_narrow_dist, $score_narrow );
602
603     $max_narrow_dist = 0;
604
605     $q_dim = $space->{ 'Q_MAX' } - $space->{ 'Q_MIN' } + 1;
606     $s_dim = $space->{ 'S_MAX' } - $space->{ 'S_MIN' } + 1;
607
608     if ( $q_dim > $s_dim ) # s_dim is the narrow end
609     {
610         $beg_narrow_dist = $match->{ 'Q_BEG' } - $space->{ 'Q_MIN' };
611         $end_narrow_dist = $space->{ 'Q_MAX' } - $match->{ 'Q_BEG' };
612
613         $max_narrow_dist = Maasha::Calc::max( $beg_narrow_dist, $end_narrow_dist );
614     }
615     elsif ( $q_dim < $s_dim ) # q_dim is the narrow end
616     {
617         $beg_narrow_dist = $match->{ 'S_BEG' } - $space->{ 'S_MIN' };
618         $end_narrow_dist = $space->{ 'S_MAX' } - $match->{ 'S_BEG' };
619
620         $max_narrow_dist = Maasha::Calc::max( $beg_narrow_dist, $end_narrow_dist );
621     }
622
623     $score_narrow = $max_narrow_dist;
624
625     $match->{ 'SCORE' } -= $score_narrow;
626 }
627
628
629 __END__
630
631
632 sub insert_indels
633 {
634     # Martin A. Hansen, June 2009.
635
636     # Given a list of matches and references to q_seq and s_seq,
637     # insert indels to form aligned sequences.
638
639     my ( $matches,   # list of matches
640          $q_seq,     # ref to query sequence
641          $s_seq,     # ref to subject sequence
642        ) = @_;
643
644     # Returns nothing.
645
646     my ( $q_indels, $s_indels, $match, $diff, $first );
647
648     @{ $matches } = sort { $a->{ Q_BEG ] <=> $b->[ Q_BEG ] } @{ $matches };
649
650     $first    = 1;
651     $q_indels = 0;
652     $s_indels = 0;
653
654     # ---- initial indels ----
655
656     $match = shift @{ $matches };
657
658     substr ${ $q_seq }, $match->[ Q_BEG ] + $q_indels, $match->[ LEN ], uc substr ${ $q_seq }, $match->[ Q_BEG ] + $q_indels, $match->[ LEN ];
659     substr ${ $s_seq }, $match->[ S_BEG ] + $s_indels, $match->[ LEN ], uc substr ${ $s_seq }, $match->[ S_BEG ] + $s_indels, $match->[ LEN ];
660
661     $diff = ( $match->[ Q_BEG ] + $q_indels ) - ( $match->[ S_BEG ] + $s_indels );
662
663     if ( $diff < 0 )
664     {
665         substr ${ $q_seq }, 0, 0, '-' x abs $diff;
666
667         $q_indels += abs $diff;
668     }
669     elsif ( $diff > 0 )
670     {
671         substr ${ $s_seq }, 0, 0, '-' x abs $diff;
672
673         $s_indels += abs $diff;
674     }
675
676     # ---- internal indels ----
677
678     foreach $match ( @{ $matches } )
679     {
680         substr ${ $q_seq }, $match->[ Q_BEG ] + $q_indels, $match->[ LEN ], uc substr ${ $q_seq }, $match->[ Q_BEG ] + $q_indels, $match->[ LEN ];
681         substr ${ $s_seq }, $match->[ S_BEG ] + $s_indels, $match->[ LEN ], uc substr ${ $s_seq }, $match->[ S_BEG ] + $s_indels, $match->[ LEN ];
682
683         $diff = ( $match->[ Q_BEG ] + $q_indels ) - ( $match->[ S_BEG ] + $s_indels );
684
685         if ( $diff < 0 )
686         {
687             substr ${ $q_seq }, $match->[ Q_BEG ] + $q_indels, 0, '-' x abs $diff;
688
689             $q_indels += abs $diff;
690         }
691         elsif ( $diff > 0 )
692         {
693             substr ${ $s_seq }, $match->[ S_BEG ] + $s_indels, 0, '-' x abs $diff;
694
695             $s_indels += abs $diff;
696         }
697     }
698
699     # ---- last indels ----
700
701     $diff = length( ${ $s_seq } ) - length( ${ $q_seq } );
702
703     if ( $diff > 0 ) {
704          ${ $q_seq } .= '-' x abs $diff;
705     } else {
706          ${ $s_seq } .= '-' x abs $diff;
707     }
708 }
709
710
711 sub align_sim_local
712 {
713     # Martin A. Hansen, May 2007.
714
715     # Calculate the local similarity of an alignment based on
716     # an alignment chain. The similarity is calculated as
717     # the number of matching residues divided by the overall
718     # length of the alignment chain. This means that a short
719     # but "good" alignment will yield a high similarity, while
720     # a long "poor" alignment will yeild a low similarity.
721
722     my ( $chain,   # list of matches in alignment
723        ) = @_;
724
725     # returns a float
726
727     my ( $match, $match_tot, $q_beg, $q_end, $s_beg, $s_end, $q_diff, $s_diff, $max, $sim );
728
729     return 0 if not @{ $chain };
730
731     $match_tot = 0;
732     $q_end     = 0;
733     $s_end     = 0;
734     $q_beg     = 999999999;
735     $s_beg     = 999999999;
736
737     foreach $match ( @{ $chain } )
738     {
739         $match_tot += $match->[ LEN ];
740     
741         $q_beg = $match->[ Q_BEG ] if $match->[ Q_BEG ] < $q_beg;
742         $s_beg = $match->[ S_BEG ] if $match->[ S_BEG ] < $s_beg;
743     
744         $q_end = $match->[ Q_END ] if $match->[ Q_END ] > $q_end;
745         $s_end = $match->[ S_END ] if $match->[ S_END ] > $s_end;
746     }
747
748     $q_diff = $q_end - $q_beg + 1;
749     $s_diff = $s_end - $s_beg + 1;
750
751     $max = Maasha::Calc::max( $q_diff, $s_diff );
752
753     $sim = sprintf( "%.2f", ( $match_tot / $max ) * 100 );
754
755     return $sim;
756 }
757
758
759 sub align_sim_global
760 {
761     # Martin A. Hansen, June 2007.
762
763     # Calculate the global similarity of an alignment based on
764     # an alignment chain. The similarity is calculated as
765     # the number of matching residues divided by the
766     # length of the shortest sequence.
767
768     my ( $chain,   # list of matches in alignment
769          $q_seq,   # ref to query sequence
770          $s_seq,   # ref to subject sequence
771        ) = @_;
772
773     # returns a float
774
775     my ( $match_tot, $min, $sim );
776
777     return 0 if not @{ $chain };
778
779     $match_tot = 0;
780
781     map { $match_tot += $_->[ LEN ] } @{ $chain }; 
782
783     $min = Maasha::Calc::min( length( ${ $q_seq } ), length( ${ $s_seq } ) );
784
785     $sim = sprintf( "%.2f", ( $match_tot / $min ) * 100 );
786
787     return $sim;
788 }
789
790
791 sub align_consensus
792 {
793     # Martin A. Hansen, June 2006.
794
795     # Given an alignment as a list of FASTA entries,
796     # generates a consensus sequences based on the
797     # entropies for each column similar to the way
798     # a sequence logo i calculated. Returns the
799     # consensus sequence as a FASTA entry.
800
801     my ( $entries,   # list of aligned FASTA entries
802          $type,      # residue type - OPTIONAL
803          $min_sim,   # minimum similarity - OPTIONAL
804        ) = @_;
805
806     # Returns tuple
807
808     my ( $bit_max, $data, $pos, $char, $score, $entry );
809
810     $type    ||= Maasha::Seq::seq_guess_type( $entries->[ 0 ] );
811     $min_sim ||= 50;
812
813     if ( $type =~ /protein/ ) {
814         $bit_max = 4;   
815     } else {
816         $bit_max = 2;
817     }
818
819     $data = Maasha::Seq::seqlogo_calc( $bit_max, $entries );
820
821     foreach $pos ( @{ $data } )
822     {
823         ( $char, $score ) = @{ $pos->[ -1 ] };
824         
825         if ( ( $score / $bit_max ) * 100 >= $min_sim ) {
826             $entry->[ SEQ ] .= $char;
827         } else {
828             $entry->[ SEQ ] .= "-";
829         }
830     }
831
832     $entry->[ HEAD ] = "Consensus: $min_sim%";
833
834     return wantarray ? @{ $entry } : $entry;
835 }
836
837
838 # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<