File Coverage

blib/lib/Algorithm/NeedlemanWunsch.pm
Criterion Covered Total %
statement 346 360 96.1
branch 113 138 81.8
condition 11 15 73.3
subroutine 26 26 100.0
pod 5 5 100.0
total 501 544 92.1


line stmt bran cond sub pod time code
1             package Algorithm::NeedlemanWunsch;
2              
3 6     6   32962 use warnings;
  6         11  
  6         172  
4 6     6   30 use strict;
  6         7  
  6         179  
5              
6 6     6   31 use List::Util qw(max);
  6         19  
  6         561  
7 6     6   32 use Carp;
  6         7  
  6         21191  
8              
9             our $VERSION = '0.04';
10              
11             my $from_diag = 1;
12             my $from_up = 2;
13             my $from_left = 4;
14             my $from_diag_idx = 0;
15             my $from_up_idx = 1;
16             my $from_left_idx = 2;
17              
18             sub _curry_callback {
19 6     6   12 my ($univ_cb, $spec_name) = @_;
20              
21 6         6 my $cb;
22 6 100       40 if ($spec_name eq 'align') {
23             $cb = sub {
24 9     9   25 my $arg = { align => [ @_ ] };
25 9         24 my $rv = &$univ_cb($arg);
26 9 50       102 croak "select_align callback returned invalid selection $rv."
27             unless $rv eq 'align';
28 2         10 };
29             } else {
30             $cb = sub {
31 4     4   8 my $arg = { $spec_name => $_[0] };
32 4         9 my $rv = &$univ_cb($arg);
33 4 50       43 croak "select_align callback returned invalid selection $rv."
34             unless $rv eq $spec_name;
35 4         16 };
36             }
37              
38 6         24 return $cb;
39             }
40              
41             sub _canonicalize_callbacks {
42 33     33   40 my $cb;
43 33 100       78 if (@_) {
44 26         40 $cb = $_[0];
45             } else {
46 7         13 $cb = { };
47             }
48              
49 33 100       105 if (exists($cb->{select_align})) {
50 4         11 my @cn = qw(align shift_a shift_b);
51 4         11 foreach (@cn) {
52 12 100       40 if (!exists($cb->{$_})) {
53 6         17 $cb->{$_} = _curry_callback($cb->{select_align}, $_);
54             }
55             }
56             }
57              
58 33         99 return $cb;
59             }
60              
61             sub new {
62 12     12 1 11833 my $class = shift;
63 12         22 my $score_sub = shift;
64              
65 12         46 my $self = { score_sub => $score_sub, local => 0 };
66 12 100       44 if (@_) {
67 6         17 $self->{gap_penalty} = $_[0];
68             }
69              
70 12         45 return bless $self, $class;
71             }
72              
73             sub local {
74 12     12 1 18854 my $self = shift;
75              
76 12 50       36 if (@_) {
77 12         28 $self->{local} = $_[0];
78             }
79              
80 12         27 return $self->{local};
81             }
82              
83             sub gap_open_penalty {
84 3     3 1 19 my $self = shift;
85              
86 3 50       13 if (@_) {
87 3         13 $self->{gap_open_penalty} = $_[0];
88             }
89              
90 3         10 return $self->{gap_open_penalty};
91             }
92              
93             sub gap_extend_penalty {
94 3     3 1 13 my $self = shift;
95              
96 3 50       18 if (@_) {
97 3         8 $self->{gap_extend_penalty} = $_[0];
98             }
99              
100 3         8 return $self->{gap_extend_penalty};
101             }
102              
103             sub align {
104 33     33 1 10961 my $self = shift;
105              
106 33         50 my $a = shift;
107 33         43 my $b = shift;
108              
109 33         83 $self->{callbacks} = _canonicalize_callbacks(@_);
110              
111 33 100       114 if (!exists($self->{gap_open_penalty})) {
112 27 50       70 if (exists($self->{gap_extend_penalty})) {
113 0         0 croak "gap_open_penalty must be defined together with gap_extend_penalty";
114             }
115              
116 27 100       76 if (!exists($self->{gap_penalty})) {
117 3         3 $self->{gap_penalty} = &{$self->{score_sub}}();
  3         11  
118             }
119              
120 27         73 return $self->_align_basic($a, $b);
121             } else {
122 6 50       27 if (!exists($self->{gap_extend_penalty})) {
123 0         0 croak "gap_extend_penalty must be defined together with gap_open_penalty";
124             }
125              
126 6 50       26 if ($self->{gap_open_penalty} >= $self->{gap_extend_penalty}) {
127 0         0 croak "gap_open_penalty must be smaller than gap_extend_penalty";
128             }
129              
130 6         29 return $self->_align_affine($a, $b);
131             }
132             }
133              
134             sub _align_basic {
135 27     27   40 my $self = shift;
136 27         32 my $a = shift;
137 27         31 my $b = shift;
138              
139 27         63 my $A = [ [ 0 ] ];
140 27         58 my $D = [ [ 0 ] ];
141 27         40 my $m = scalar(@$b);
142 27         35 my $n = scalar(@$a);
143              
144             my $score_diag = sub {
145 2065     2065   2226 my ($i, $j) = @_;
146              
147 2065         5810 $D->[$i - 1]->[$j - 1] +
148 2065         3544 &{$self->{score_sub}}($a->[$j - 1], $b->[$i - 1]);
149 27         139 };
150              
151             my $score_up = sub {
152 2065     2065   2386 my ($i, $j) = @_;
153              
154 2065         4617 $D->[$i - 1]->[$j] + $self->{gap_penalty};
155 27         101 };
156              
157 27         35 my $score_left;
158 27 100       68 if (!$self->{local}) {
159             $score_left = sub {
160 940     940   1002 my ($i, $j) = @_;
161              
162 940         2543 $D->[$i]->[$j - 1] + $self->{gap_penalty};
163 20         73 };
164             } else {
165             $score_left = sub {
166 1125     1125   1226 my ($i, $j) = @_;
167              
168 1125 100       4046 ($i < $m) ?
169             $D->[$i]->[$j - 1] + $self->{gap_penalty} :
170             $D->[$i]->[$j - 1];
171 7         34 };
172             }
173              
174             # order must correspond to $from_* constants
175 27         55 my @subproblems = ( $score_diag, $score_up, $score_left );
176              
177 27         31 my $j = 1;
178 27         64 while ($j <= $n) {
179 193         280 $A->[0]->[$j] = $from_left;
180 193         362 ++$j;
181             }
182              
183 27 100       65 if (!$self->{local}) {
184 20         40 $j = 1;
185 20         70 while ($j <= $n) {
186 110         176 $D->[0]->[$j] = $j * $self->{gap_penalty};
187 110         191 ++$j;
188             }
189             } else {
190 7         9 $j = 1;
191 7         18 while ($j <= $n) {
192 83         104 $D->[0]->[$j] = 0;
193 83         132 ++$j;
194             }
195             }
196              
197 27         30 my $i = 1;
198 27         70 while ($i <= $m) {
199 186         288 $A->[$i]->[0] = $from_up;
200 186         323 ++$i;
201             }
202              
203 27         33 $i = 1;
204 27         57 while ($i <= $m) {
205 186         354 $D->[$i]->[0] = $i * $self->{gap_penalty};
206 186         365 ++$i;
207             }
208              
209 27         32 $i = 1;
210 27         50 while ($i <= $m) {
211 186         208 $j = 1;
212 186         1315 while ($j <= $n) {
213 2065         2540 my @scores = map { &$_($i, $j); } @subproblems;
  6195         18526  
214 2065         3774 my $d = max(@scores);
215              
216 2065         1960 my $a = 0;
217 2065         1906 my $from = 1;
218 2065         1953 my $k = 0;
219 2065         3978 while ($k < scalar(@scores)) {
220 6195 100       10695 if ($scores[$k] == $d) {
221 2671         2886 $a |= $from;
222             }
223              
224 6195         5758 $from *= 2;
225 6195         11195 ++$k;
226             }
227              
228 2065         3078 $A->[$i]->[$j] = $a;
229 2065         2891 $D->[$i]->[$j] = $d;
230              
231             # my $x = join ', ', @scores;
232             # warn "$i, $j: $x -> ", $D->[$i]->[$j], "\n";
233 2065         4908 ++$j;
234             }
235              
236 186         357 ++$i;
237             }
238              
239 27         35 $i = $m;
240 27         30 $j = $n;
241 27   100     82 while (($i > 0) || ($j > 0)) {
242 230         298 my $a = $A->[$i]->[$j];
243 230         233 my @alt;
244 230 100       400 if ($a & $from_diag) {
245 151 50 33     563 die "internal error" unless ($i > 0) && ($j > 0);
246 151         318 push @alt, [ $i - 1, $j - 1 ];
247             }
248              
249 230 100       428 if ($a & $from_up) {
250 60 50       106 die "internal error" unless ($i > 0);
251 60         109 push @alt, [ $i - 1, $j ];
252             }
253              
254 230 100       379 if ($a & $from_left) {
255 59 50       101 die "internal error" unless ($j > 0);
256 59         125 push @alt, [ $i, $j - 1];
257             }
258              
259 230 50       425 if (!@alt) {
260 0         0 die "internal error";
261             }
262              
263 230         361 my $cur = [ $i, $j ];
264 230         237 my $move;
265 230 100       353 if (@alt == 1) {
266 197         460 $move = $self->_simple_trace_back($cur, $alt[0],
267             $self->{callbacks});
268             } else {
269 33         85 $move = $self->_trace_back($cur, \@alt);
270             }
271              
272 230 100       523 if ($move eq 'align') {
    100          
    50          
273 149         147 --$i;
274 149         555 --$j;
275             } elsif ($move eq 'shift_a') {
276 44         180 --$j;
277             } elsif ($move eq 'shift_b') {
278 37         142 --$i;
279             } else {
280 0         0 die "internal error";
281             }
282             }
283              
284 27         409 return $D->[$m]->[$n];
285             }
286              
287             sub _align_affine {
288 6     6   10 my $self = shift;
289 6         14 my $a = shift;
290 6         10 my $b = shift;
291              
292 6         37 my @D = ([ [ 0 ] ], [ [ 0 ] ], [ [ 0 ] ]); # indexed by $from_*_idx
293 6         13 my $m = scalar(@$b);
294 6         13 my $n = scalar(@$a);
295              
296             my $score_diag = sub {
297 1713     1713   2040 my ($i, $j) = @_;
298              
299 1713         2154 my @base = map { $_->[$i - 1]->[$j - 1]; } @D;
  5139         10041  
300 1713         3593 my $base = max(@base);
301 1713         2504 $base + &{$self->{score_sub}}($a->[$j - 1], $b->[$i - 1]);
  1713         4087  
302 6         38 };
303              
304             my $score_up = sub {
305 1713     1713   2005 my ($i, $j) = @_;
306              
307 1713         2169 my @base = map { $_->[$i - 1]->[$j]; } @D;
  5139         9228  
308 1713         2746 $base[$from_diag_idx] += $self->{gap_open_penalty};
309 1713         2186 $base[$from_up_idx] += $self->{gap_extend_penalty};
310 1713         2335 $base[$from_left_idx] += $self->{gap_open_penalty};
311 1713         5036 max(@base);
312 6         72 };
313              
314 6         9 my $score_left;
315 6 100       25 if (!$self->{local}) {
316             $score_left = sub {
317 876     876   954 my ($i, $j) = @_;
318              
319 876         1035 my @base = map { $_->[$i]->[$j - 1]; } @D;
  2628         4402  
320 876         1301 $base[$from_diag_idx] += $self->{gap_open_penalty};
321 876         1051 $base[$from_up_idx] += $self->{gap_open_penalty};
322 876         1032 $base[$from_left_idx] += $self->{gap_extend_penalty};
323              
324 876         2461 max(@base);
325 4         23 };
326             } else {
327             $score_left = sub {
328 837     837   1001 my ($i, $j) = @_;
329              
330 837         1064 my @base = map { $_->[$i]->[$j - 1]; } @D;
  2511         4829  
331 837 100       1686 if ($i < $m) {
332 792         1118 $base[$from_diag_idx] += $self->{gap_open_penalty};
333 792         987 $base[$from_up_idx] += $self->{gap_open_penalty};
334 792         1141 $base[$from_left_idx] += $self->{gap_extend_penalty};
335             }
336              
337 837         2365 max(@base);
338 2         12 };
339             }
340              
341 6         11 my $j;
342 6 100       36 if (!$self->{local}) {
343 4         7 $j = 1;
344 4         14 while ($j <= $n) {
345 59         88 foreach (@D) {
346 177         507 $_->[0]->[$j] = $self->{gap_open_penalty} +
347             ($j - 1) * $self->{gap_extend_penalty};
348             }
349              
350 59         135 ++$j;
351             }
352             } else {
353 2         4 $j = 1;
354 2         10 while ($j <= $n) {
355 45         57 foreach (@D) {
356 135         241 $_->[0]->[$j] = 0;
357             }
358              
359 45         95 ++$j;
360             }
361             }
362              
363 6         12 my $i = 1;
364 6         20 while ($i <= $m) {
365 92         131 foreach (@D) {
366 276         792 $_->[$i]->[0] = $self->{gap_open_penalty} +
367             ($i - 1) * $self->{gap_extend_penalty};
368             }
369              
370 92         278 ++$i;
371             }
372              
373             # order must correspond to $from_* constants
374 6         16 my @subproblems = ( $score_diag, $score_up, $score_left );
375              
376 6         9 $i = 1;
377 6         25 while ($i <= $m) {
378 92         108 $j = 1;
379 92         164 while ($j <= $n) {
380 1713         1848 my $k = 0;
381 1713         3154 while ($k < 3) { # scalar(@D), scalar(@subproblems)
382 5139         5883 $D[$k]->[$i]->[$j] = &{$subproblems[$k]}($i, $j);
  5139         9131  
383 5139         19442 ++$k;
384             }
385              
386             # my $x = join ', ', map { $_->[$i]->[$j]; } @D;
387             # warn "$i, $j: $x\n";
388              
389 1713         3742 ++$j;
390             }
391              
392 92         184 ++$i;
393             }
394              
395             # like $score_up
396 6         22 my @delta_up = ( $self->{gap_open_penalty}, $self->{gap_extend_penalty},
397             $self->{gap_open_penalty} );
398              
399             # like $score_left
400 6         20 my @delta_left = ( $self->{gap_open_penalty}, $self->{gap_open_penalty},
401             $self->{gap_extend_penalty} );
402              
403 6         14 my @no_delta = (0, 0, 0);
404              
405 6         13 my @score = map { $_->[$m]->[$n]; } @D;
  18         44  
406 6         17 my $res = max(@score);
407              
408 6         11 my $arrow = 0;
409 6         14 my $flag = 1;
410 6         10 my $idx = 0;
411 6         23 while ($idx < 3) { # scalar(@score)
412 18 100       41 if ($score[$idx] == $res) {
413 8         32 $arrow |= $flag;
414             }
415              
416 18         23 $flag *= 2;
417 18         60 ++$idx;
418             }
419              
420 6         13 $i = $m;
421 6         10 $j = $n;
422 6   100     34 while (($i > 0) || ($j > 0)) {
423 132         151 my @alt;
424 132 100       260 if ($arrow & $from_diag) {
425 64 50 33     266 die "internal error" unless ($i > 0) && ($j > 0);
426 64         153 push @alt, [ $i - 1, $j - 1 ];
427             }
428              
429 132 100       284 if ($arrow & $from_up) {
430 49 50       97 die "internal error" unless ($i > 0);
431 49         102 push @alt, [ $i - 1, $j ];
432             }
433              
434 132 100       362 if ($arrow & $from_left) {
435 45 50       90 die "internal error" unless ($j > 0);
436 45         98 push @alt, [ $i, $j - 1];
437             }
438              
439 132 50       257 if (!@alt) {
440 0         0 die "internal error";
441             }
442              
443             # my $x = join ', ', map { "[ " . $_->[0] . ", " . $_->[1] . " ]"; } @alt;
444             # warn "$i, $j: $x\n";
445              
446 132         216 my $cur = [ $i, $j ];
447 132         175 my $move;
448 132 100       244 if (@alt == 1) {
449 107         295 $move = $self->_simple_trace_back($cur, $alt[0],
450             $self->{callbacks});
451             } else {
452 25         75 $move = $self->_trace_back($cur, \@alt);
453             }
454              
455 132 100       361 if ($move eq 'align') {
    100          
    50          
456 64         69 --$i;
457 64         62 --$j;
458              
459 64         90 @score = map { $_->[$i]->[$j]; } @D;
  192         393  
460 64 100       175 if ($i == 0) {
    100          
461 2         13 $arrow = $from_left;
462             } elsif ($j == 0) {
463 2         9 $arrow = $from_up;
464             } else {
465 60         124 my $d = max(@score);
466 60         65 $arrow = 0;
467 60         64 $flag = 1;
468 60         64 $idx = 0;
469 60         132 while ($idx < 3) { # scalar(@score)
470 180 100       355 if ($score[$idx] == $d) {
471 77         92 $arrow |= $flag;
472             }
473              
474 180         175 $flag *= 2;
475 180         558 ++$idx;
476             }
477             }
478             } elsif ($move eq 'shift_a') {
479 40         49 --$j;
480              
481 40         53 my @base = map { $_->[$i]->[$j] } @D;
  120         254  
482 40         49 my $delta;
483 40 100 100     207 if ($self->{local} && ($i == $m)) {
484 5         9 $delta = \@no_delta;
485             } else {
486 35         51 $delta = \@delta_left;
487             }
488              
489 40         104 $arrow = $self->_retread($score[$from_left_idx], $i, $j,
490             \@base, $delta);
491 40         232 @score = @base;
492             } elsif ($move eq 'shift_b') {
493 28         29 --$i;
494              
495 28         47 my @base = map { $_->[$i]->[$j] } @D;
  84         161  
496 28         79 $arrow = $self->_retread($score[$from_up_idx], $i, $j,
497             \@base, \@delta_up);
498 28         143 @score = @base;
499             } else {
500 0         0 die "internal error";
501             }
502             }
503              
504 6         234 return $res;
505             }
506              
507             sub _retread {
508 68     68   118 my ($self, $to_score, $i, $j, $base, $delta) = @_;
509              
510 68 100       177 if ($i == 0) {
    50          
511 22         39 return $from_left;
512             } elsif ($j == 0) {
513 0         0 return $from_up;
514             }
515              
516 46         59 my $a = 0;
517 46         49 my $flag = 1;
518 46         55 my $idx = 0;
519 46         94 while ($idx < 3) {
520 138 100       298 if ($base->[$idx] + $delta->[$idx] == $to_score) {
521 53         71 $a |= $flag;
522             }
523              
524 138         134 $flag *= 2;
525 138         260 ++$idx;
526             }
527              
528 46         91 return $a;
529             }
530              
531             sub _trace_back {
532 58     58   79 my ($self, $cur, $sources) = @_;
533              
534 58         88 my $arg = { };
535 58         104 foreach my $next (@$sources) {
536 124         277 my $m = $self->_simple_trace_back($cur, $next, { });
537 124 100       375 if ($m eq 'align') {
    100          
    50          
538 48         174 $arg->{align} = [ $cur->[1] - 1, $cur->[0] - 1 ];
539             } elsif ($m eq 'shift_a') {
540 32         130 $arg->{shift_a} = $cur->[1] - 1;
541             } elsif ($m eq 'shift_b') {
542 44         140 $arg->{shift_b} = $cur->[0] - 1;
543             } else {
544 0         0 die "internal error";
545             }
546             }
547              
548 58         78 my $move;
549 58         94 my $cb = $self->{callbacks};
550 58 100       119 if (exists($cb->{select_align})) {
551 4         5 $move = &{$cb->{select_align}}($arg);
  4         16  
552 4 50       47 if (!exists($arg->{$move})) {
553 0         0 die "select_align callback returned invalid selection $move.";
554             }
555             } else {
556 54         116 my @cn = qw(align shift_a shift_b);
557 54         88 foreach my $m (@cn) {
558 64 100       139 if (exists($arg->{$m})) {
559 54         60 $move = $m;
560 54         77 last;
561             }
562             }
563              
564 54 50       99 if (!$move) {
565 0         0 die "internal error";
566             }
567              
568 54 100       122 if (exists($cb->{$move})) {
569 47 100       84 if ($move eq 'align') {
570 37         41 &{$cb->{align}}(@{$arg->{align}});
  37         97  
  37         64  
571             } else {
572 10         15 &{$cb->{$move}}($arg->{$move});
  10         48  
573             }
574             }
575             }
576              
577 58         434 return $move;
578             }
579              
580             sub _simple_trace_back {
581 428     428   555 my ($self, $cur, $next, $cb) = @_;
582              
583 428 100       848 if ($next->[0] == $cur->[0] - 1) {
584 324 100       578 if ($next->[1] == $cur->[1] - 1) {
585 215 100       475 if (exists($cb->{align})) {
586 155         200 &{$cb->{align}}($next->[1], $next->[0]);
  155         354  
587             }
588              
589 215         1091 return 'align';
590             } else {
591 109 50       226 if ($next->[1] != $cur->[1]) {
592 0         0 die "internal error";
593             }
594              
595 109 100       212 if (exists($cb->{shift_b})) {
596 59         80 &{$cb->{shift_b}}($cur->[0] - 1);
  59         144  
597             }
598              
599 109         492 return 'shift_b';
600             }
601             } else {
602 104 50       232 if ($next->[0] != $cur->[0]) {
603 0         0 die "internal error";
604             }
605              
606 104 50       206 if ($next->[1] != $cur->[1] - 1) {
607 0         0 die "internal error";
608             }
609              
610 104 100       212 if (exists($cb->{shift_a})) {
611 70         86 &{$cb->{shift_a}}($cur->[1] - 1);
  70         182  
612             }
613              
614 104         551 return 'shift_a';
615             }
616             }
617              
618             1;
619              
620             __END__