File Coverage

EditDistance.pd
Criterion Covered Total %
statement 56 66 84.8
branch 20 52 38.4
condition 0 6 0.0
subroutine 15 16 93.7
pod 11 11 100.0
total 102 151 67.5


line stmt bran cond sub pod time code
1             ##-*- Mode: CPerl -*-
2              
3             ##======================================================================
4             ## Header Administrivia
5             ##======================================================================
6              
7             our $VERSION = '0.10';
8             pp_setversion($VERSION);
9              
10             ##------------------------------------------------------
11             ## pm additions
12             pp_addpm({At=>'Top'},<<'EOPM');
13 2     2   15 use strict;
  2         3  
  2         70  
14 2     2   354 use version;
  2         1475  
  2         12  
15              
16             ## $PDL_ATLEAST_2_014 : avoid in-place reshape() in _edit_pdl() for PDL >= 2.014
17             ## + prior to PDL-2.014, PDL::reshape() returned a new PDL, but modifies
18             ## the calling object in-place for v2.014
19             our $PDL_ATLEAST_2_014 = version->parse($PDL::VERSION) >= version->parse("2.014");
20              
21             =pod
22              
23             =head1 NAME
24              
25             PDL::EditDistance - Wagner-Fischer edit distance and alignment for PDLs.
26              
27             =head1 SYNOPSIS
28              
29             use PDL;
30             use PDL::EditDistance;
31              
32             ##-- input PDLs
33             $a = pdl([map { ord($_) } qw(G U M B O)]);
34             $b = pdl([map { ord($_) } qw(G A M B O L)]);
35              
36             $a1 = pdl([0, map { ord($_) } qw(G U M B O)]);
37             $b1 = pdl([0, map { ord($_) } qw(G A M B O L)]);
38              
39             ##-------------------------------------------------------------
40             ## Levenshtein distance
41             $dist = edit_distance_static($a,$b, 0,1,1,1);
42             ($dist,$align) = edit_align_static($a,$b, 0,1,1,1);
43              
44             ##-------------------------------------------------------------
45             ## Wagner-Fischer distance
46             @costs = ($costMatch=0,$costInsert=1,$costDelete=1,$costSubstitute=2);
47             $dist = edit_distance_static($a,$b, @costs);
48             ($dist,$align) = edit_align_static($a,$b, @costs);
49              
50             ##-------------------------------------------------------------
51             ## General edit distance
52             $costsMatch = random($a->nelem+1, $b->nelem+1);
53             $costsIns = random($a->nelem+1, $b->nelem+1);
54             $costsDel = random($a->nelem+1, $b->nelem+1);
55             $costsSubst = random($a->nelem+1, $b->nelem+1);
56             @costs = ($costsMatch,$costsIns,$costDel,$costsSubst);
57             $dist = edit_distance_full($a,$b,@costs);
58             ($dist,$align) = edit_align_full($a,$b,@costs);
59              
60             ##-------------------------------------------------------------
61             ## Alignment
62             $op_match = align_op_match(); ##-- constant
63             $op_del = align_op_insert1(); ##-- constant
64             $op_ins = align_op_insert2(); ##-- constant
65             $op_subst = align_op_substitute(); ##-- constant
66              
67             ($apath,$bpath,$pathlen) = edit_bestpath($align);
68             ($ai,$bi,$ops,$pathlen) = edit_pathtrace($align);
69              
70             ##-------------------------------------------------------------
71             ## Longest Common Subsequence
72             $lcs = edit_lcs($a,$b);
73             ($ai,$bi,$lcslen) = lcs_backtrace($a,$b,$lcs);
74              
75             =cut
76              
77             EOPM
78             ## /pm additions
79             ##------------------------------------------------------
80              
81             ##------------------------------------------------------
82             ## Exports: None
83             pp_export_nothing();
84              
85             ##------------------------------------------------------
86             ## Includes / defines
87             #pp_addhdr(<<'EOH');
88             #EOH
89              
90              
91             ##======================================================================
92             ## C Utilities
93             ##======================================================================
94             pp_addhdr(<<'EOH');
95              
96             #define ALIGN_OP_MATCH 0
97             #define ALIGN_OP_INSERT1 1
98             #define ALIGN_OP_INSERT2 2
99             #define ALIGN_OP_SUBSTITUTE 3
100              
101             EOH
102              
103              
104             ##======================================================================
105             ## PDL::PP Wrappers
106             ##======================================================================
107              
108             ##======================================================================
109             ## Basic Utilities
110             #pp_addpm(<<'EOPM');
111             #=pod
112             #
113             #=head1 Basic Utilities
114             #
115             #=cut
116             #EOPM
117              
118             ##======================================================================
119             ## Convenience Methods
120              
121             ##------------------------------------------------------
122             ## _edit_pdl($a()) : ensures pdl-ness
123             #pp_add_exported('','_edit_pdl');
124             pp_addpm(<<'EOPM');
125              
126              
127             =pod
128              
129             =head2 _edit_pdl
130              
131             =for sig
132              
133             Signature: (a(N); [o]apdl(N+1))
134              
135             Convenience method.
136             Returns a pdl $apdl() suitable for representing $a(),
137             which can be specified as a UTF-8 or byte-string, as an arrays of numbers, or as a PDL.
138             $apdl(0) is always set to zero.
139              
140             =cut
141 20 100   20   192034  
    100          
142 17 50       44 sub _edit_pdl {
143             if (UNIVERSAL::isa($_[0],'PDL')) {
144             return ($PDL_ATLEAST_2_014 ? $_[0]->pdl : $_[0])->flat->reshape($_[0]->nelem+1)->rotate(1);
145             }
146 2 100       12 #return pdl(byte,[0, map { ord($_) } split(//,$_[0])]) if (!ref($_[0]) && !utf8::is_utf8($_[0])); ##-- byte-string (old)
147 1         6 elsif (!ref($_[0])) {
148             return pdl(long,[0, unpack('C0C*',$_[0])]) if (utf8::is_utf8($_[0])); ##-- utf8-string
149 1         2 return pdl(byte,[0, unpack('U0C*',$_[0])]); ##-- byte-string
  1         6  
150             }
151             return pdl([0,@{$_[0]}]);
152             }
153              
154             EOPM
155              
156             ##------------------------------------------------------
157             ## edit_cost_matrices() : generate cost-matrices (convenience)
158             pp_add_exported('','edit_costs');
159             pp_addpm(<<'EOPM');
160              
161              
162             =pod
163              
164             =head2 edit_costs
165              
166             =for sig
167              
168             Signature: (PDL::Type type; int N; int M;
169             [o]costsMatch(N+1,M+1); [o]costsIns(N+1,M+1); [o]costsDel(N+1,M+1); [o]costsSubst(N+1,M+1))
170              
171             Convenience method.
172             Ensures existence and proper dimensionality of cost matrices for inputs
173             of length N and M.
174              
175             =cut
176 3     3 1 12  
177             sub edit_costs {
178             return _edit_costs($_[0],$_[1]+1,$_[2]+1,@_[3..$#_]);
179             }
180              
181             EOPM
182              
183              
184             ##------------------------------------------------------
185             ## _edit_costs() : generate cost-matrices (low-level, convenience)
186             pp_add_exported('','_edit_costs');
187             pp_addpm(<<'EOPM');
188              
189              
190             =pod
191              
192             =head2 _edit_costs
193              
194             =for sig
195              
196             Signature: (PDL::Type type; int N1; int M1;
197             [o]costsMatch(N1,M1); [o]costsIns(N1,M1); [o]costsDel(N1,M1); [o]costsSubst(N1,M1))
198              
199             Low-level method.
200             Ensures existence and proper dimensionality of cost matrices for inputs
201             of length N1-1 and M1-1.
202              
203             =cut
204              
205 3     3   9 sub _edit_costs {
206             #my ($type,$n1,$m1,$costsMatch,$costsIns,$costsDel,$costsSubst) = @_;
207             return (_edit_matrix(@_[0..2],$_[3]),
208             _edit_matrix(@_[0..2],$_[4]),
209             _edit_matrix(@_[0..2],$_[5]),
210             _edit_matrix(@_[0..2],$_[6]));
211             }
212              
213 12 50   12   82 ##-- $matrix = _edit_matrix($type,$dim0,$dim1,$mat)
214 0 0 0     0 sub _edit_matrix {
      0        
215 0 0       0 return zeroes(@_[0..2]) if (!defined($_[3]));
216             $_[3]->reshape(@_[1,2]) if ($_[3]->ndims != 2 || $_[3]->dim(0) != $_[1] || $_[3]->dim(1) != $_[2]);
217             return $_[3]->type == $_[0] ? $_[3] : $_[3]->convert($_[0]);
218             }
219              
220             EOPM
221              
222              
223             ##------------------------------------------------------
224             ## edit_cost_static() : generate static cost-matrices (convenience)
225             pp_add_exported('','edit_costs_static');
226             pp_addpm(<<'EOPM');
227              
228             =pod
229              
230             =head2 edit_costs_static
231              
232             =for sig
233              
234             Signature: (PDL::Type type; int N; int M;
235             staticCostMatch(); staticCostIns(); staticCostSubst();
236             [o]costsMatch(N+1,M+1); [o]costsIns(N+1,M+1); [o]costsDel(N+1,M+1); [o]costsSubst(N+1,M+1))
237              
238             Convenience method.
239              
240             =cut
241              
242             sub edit_costs_static {
243 3     3 1 4571 #my ($type,$n,$m, $cMatch,$cIns,$cDel,$cSubst, $costsMatch,$costsIns,$costsDel,$costsSubst) = @_;
244 3         29 my @costs = edit_costs(@_[0..2],@_[7..$#_]);
245 3         183 $costs[$_] .= $_[$_+3] foreach (0..3);
246             return @costs;
247             }
248              
249             EOPM
250              
251             ##======================================================================
252             ## Distance: Full
253              
254             ##------------------------------------------------------
255             ## edit_distance_matrix_full() : full distance matrix
256             pp_add_exported('','edit_distance_full');
257              
258             pp_addpm(<<'EOPM');
259              
260             =pod
261              
262             =head2 edit_distance_full
263              
264             =for sig
265              
266             Signature: (a(N); b(M);
267             costsMatch(N+1,M+1); costsIns(N+1,M+1); costsDel(N+1,M+1); costsSubst(N+1,M+1);
268             [o]dist(N+1,M+1); [o]align(N+1,M+1))
269              
270             Convenience method.
271             Compute the edit distance matrix for inputs $a() and $b(), and
272             cost matrices $costsMatch(), $costsIns(), $costsDel(), and $costsSubst().
273             $a() and $b() may be specified as PDLs, arrays of numbers, or as strings.
274              
275             =cut
276              
277 1     1 1 70 sub edit_distance_full {
278             return _edit_distance_full(_edit_pdl($_[0]), _edit_pdl($_[1]), @_[2..$#_]);
279             }
280              
281             EOPM
282              
283              
284             ##------------------------------------------------------
285             ## _edit_distance_full : get distance matrix from full cost matrices
286             pp_add_exported('','_edit_distance_full');
287             pp_def('_edit_distance_full',
288             Pars => ('a1(N1); b1(M1);'
289             .' costsMatch(N1,M1); costsIns(N1,M1); costsDel(N1,M1); costsSubst(N1,M1);'
290             .' [o]dist(N1,M1);'),
291             Code =>
292             ('
293             int i,j;
294             //
295             //-- initialize distance matrix: insertion costs
296             $dist (N1=>0,M1=>0) = $costsMatch(N1=>0,M1=>0); //-- BOS always matches
297             for (i=1; i < $SIZE(N1); i++) {
298             $dist(N1=>i,M1=>0) = $dist(N1=>i-1,M1=>0) + $costsDel(N1=>i,M1=>0); //-- delete (insert1)
299             }
300             for (j=1; j < $SIZE(M1); j++) {
301             $dist(N1=>0,M1=>j) = $dist(N1=>0,M1=>j-1) + $costsIns(N1=>0,M1=>j); //-- insert (insert2)
302             }
303             //
304             //-- compute distance
305             for (i=1; i < $SIZE(N1); i++) {
306             for (j=1; j < $SIZE(M1); j++) {
307             $GENERIC(dist) cost_insert_1 = $dist(N1=>i-1,M1=>j ) + $costsDel(N1=>i,M1=>j);
308             $GENERIC(dist) cost_insert_2 = $dist(N1=>i, M1=>j-1) + $costsIns(N1=>i,M1=>j);
309             $GENERIC(dist) cost_subst = $dist(N1=>i-1,M1=>j-1);
310             if ($a1(N1=>i)==$b1(M1=>j)) {
311             cost_subst += $costsMatch(N1=>i,M1=>j);
312             } else {
313             cost_subst += $costsSubst(N1=>i,M1=>j);
314             }
315             //
316             if (cost_insert_1 < cost_insert_2) {
317             if (cost_insert_1 < cost_subst) {
318             $dist( N1=>i,M1=>j) = cost_insert_1;
319             } else {
320             $dist(N1=>i,M1=>j) = cost_subst;
321             }
322             } else if (cost_insert_2 < cost_subst) {
323             $dist( N1=>i,M1=>j) = cost_insert_2;
324             } else {
325             $dist( N1=>i,M1=>j) = cost_subst;
326             }
327             }
328             }
329             '),
330             Doc =>
331             q(
332             Low-level method.
333             Compute the edit distance matrix for input PDLs $a1() and $b1() and
334             cost matrices $costsMatch(), $costsIns(), $costsDel(), and $costsSubst().
335              
336             The first elements of $a1() and $b1() are ignored.
337             ),
338              
339             );
340              
341              
342             ##======================================================================
343             ## Distance + Alignment: Full
344              
345             ##------------------------------------------------------
346             ## edit_align_full() : full distance & alignment matrix
347             pp_add_exported('','edit_align_full');
348              
349             pp_addpm(<<'EOPM');
350              
351             =pod
352              
353             =head2 edit_align_full
354              
355             =for sig
356              
357             Signature: (a(N); b(M);
358             costsMatch(N+1,M+1); costsIns(N+1,M+1); costsDel(N+1,N+1); costsSubst(N+1,M+1);
359             [o]dist(N+1,M+1); [o]align(N+1,M+1))
360              
361             Convenience method.
362             Compute the edit distance and alignment matrices for inputs $a() and $b(), and
363             cost matrices $costsMatch(), $costsIns(), $costsDel(), and $costsSubst().
364             $a() and $b() may be specified as PDLs, arrays of numbers, or as strings.
365              
366             =cut
367              
368 1     1 1 62 sub edit_align_full {
369             return _edit_align_full(_edit_pdl($_[0]), _edit_pdl($_[1]), @_[2..$#_]);
370             }
371              
372             EOPM
373              
374              
375             ##------------------------------------------------------
376             ## _edit_align_full : get distance & alignment matrices given full cost matrices
377             pp_add_exported('','_edit_align_full');
378             pp_def('_edit_align_full',
379             Pars => ('a1(N1); b1(M1);'
380             .' costsMatch(N1,M1); costsIns(N1,M1); costsDel(N1,M1); costsSubst(N1,M1);'
381             .' [o]dist(N1,M1); byte [o]align(N1,M1);'),
382             Code =>
383             ('
384             int i,j;
385             //
386             //-- initialize distance matrix: insertion costs
387             $dist (N1=>0,M1=>0) = $costsMatch(N1=>0,M1=>0); //-- BOS always matches
388             $align(N1=>0,M1=>0) = ALIGN_OP_MATCH; //-- ... marked as "substitute"
389             for (i=1; i < $SIZE(N1); i++) {
390             $dist (N1=>i,M1=>0) = $dist(N1=>i-1,M1=>0) + $costsDel(N1=>i,M1=>0);
391             $align(N1=>i,M1=>0) = ALIGN_OP_INSERT1;
392             }
393             for (j=1; j < $SIZE(M1); j++) {
394             $dist (N1=>0,M1=>j) = $dist(N1=>0,M1=>j-1) + $costsIns(N1=>0,M1=>j);
395             $align(N1=>0,M1=>j) = ALIGN_OP_INSERT2;
396             }
397             //
398             //-- compute distance
399             for (i=1; i < $SIZE(N1); i++) {
400             for (j=1; j < $SIZE(M1); j++) {
401             $GENERIC(dist) cost_insert_1 = $dist(N1=>i-1,M1=>j ) + $costsDel(N1=>i,M1=>j);
402             $GENERIC(dist) cost_insert_2 = $dist(N1=>i, M1=>j-1) + $costsIns(N1=>i,M1=>j);
403             $GENERIC(dist) cost_subst = $dist(N1=>i-1,M1=>j-1);
404             char subst_op;
405             //
406             if ($a1(N1=>i)==$b1(M1=>j)) {
407             cost_subst += $costsMatch(N1=>i,M1=>j);
408             subst_op = ALIGN_OP_MATCH;
409             } else {
410             cost_subst += $costsSubst(N1=>i,M1=>j);
411             subst_op = ALIGN_OP_SUBSTITUTE;
412             }
413             //
414             if (cost_insert_1 < cost_insert_2) {
415             if (cost_insert_1 < cost_subst) {
416             $dist( N1=>i,M1=>j) = cost_insert_1;
417             $align(N1=>i,M1=>j) = ALIGN_OP_INSERT1;
418             } else {
419             $dist(N1=>i,M1=>j) = cost_subst;
420             $align(N1=>i,M1=>j) = subst_op;
421             }
422             } else if (cost_insert_2 < cost_subst) {
423             $dist( N1=>i,M1=>j) = cost_insert_2;
424             $align(N1=>i,M1=>j) = ALIGN_OP_INSERT2;
425             } else {
426             $dist( N1=>i,M1=>j) = cost_subst;
427             $align(N1=>i,M1=>j) = subst_op;
428             }
429             }
430             }
431             '),
432             Doc =>
433             q(
434             Low-level method.
435             Compute the edit distance and alignment matrix for input PDLs $a1() and $b1() and
436             cost matrices $costsMatch(), $costsIns(), $costsDel(), and $costsSubst().
437              
438             The first elements of $a1() and $b1() are ignored.
439             ),
440              
441             );
442              
443              
444             ##======================================================================
445             ## Distance: Static
446              
447             ##------------------------------------------------------
448             ## edit_distance_static() : distance matrix using static cost schema
449             pp_add_exported('','edit_distance_static');
450              
451             pp_addpm(<<'EOPM');
452              
453             =pod
454              
455             =head2 edit_distance_static
456              
457             =for sig
458              
459             Signature: (a(N); b(M);
460             staticCostMatch(); staticCostIns(); staticCostDel(); staticCostSubst();
461             [o]dist(N+1,M+1))
462              
463             Convenience method.
464             Compute the edit distance matrix for inputs $a() and $b() given
465             a static cost schema @costs = ($staticCostMatch(), $staticCostIns(), $staticCostDel(), and $staticCostSubst()).
466             $a() and $b() may be specified as PDLs, arrays of numbers, or as strings.
467             Functionally equivalent to edit_distance_full($matches,@costs,$dist),
468             but slightly faster.
469              
470             =cut
471              
472 1     1 1 1595 sub edit_distance_static {
473             return _edit_distance_static(_edit_pdl($_[0]), _edit_pdl($_[1]), @_[2..$#_]);
474             }
475              
476             EOPM
477              
478              
479             ##------------------------------------------------------
480             ## _edit_distance_static : get distance matrix from static cost schema
481             pp_add_exported('','_edit_distance_static');
482             pp_def('_edit_distance_static',
483             Pars => ('a1(N1); b1(M1); costMatch(); costIns(); costDel(); costSubst();'
484             .' [o]dist(N1,M1);'),
485             Code =>
486             ('
487             int i,j;
488             //
489             //-- initialize distance matrix: insertion costs
490             $dist(N1=>0,M1=>0) = $costMatch(); //-- BOS always matches
491             for (i=1; i < $SIZE(N1); i++) {
492             $dist(N1=>i,M1=>0) = $dist(N1=>i-1,M1=>0) + $costDel(); //-- delete (insert1)
493             }
494             for (j=1; j < $SIZE(M1); j++) {
495             $dist(N1=>0,M1=>j) = $dist(N1=>0,M1=>j-1) + $costIns(); //-- insert (insert2)
496             }
497             //
498             //-- compute distance
499             for (i=1; i < $SIZE(N1); i++) {
500             for (j=1; j < $SIZE(M1); j++) {
501             $GENERIC(dist) cost_insert_1 = $dist(N1=>i-1,M1=>j ) + $costDel();
502             $GENERIC(dist) cost_insert_2 = $dist(N1=>i, M1=>j-1) + $costIns();
503             $GENERIC(dist) cost_subst = $dist(N1=>i-1,M1=>j-1) + ($a1(N1=>i)==$b1(M1=>j)
504             ? $costMatch()
505             : $costSubst());
506             if (cost_insert_1 < cost_insert_2) {
507             if (cost_insert_1 < cost_subst) {
508             $dist(N1=>i,M1=>j) = cost_insert_1;
509             } else {
510             $dist(N1=>i,M1=>j) = cost_subst;
511             }
512             } else if (cost_insert_2 < cost_subst) {
513             $dist(N1=>i,M1=>j) = cost_insert_2;
514             } else {
515             $dist(N1=>i,M1=>j) = cost_subst;
516             }
517             }
518             }
519             '),
520             Doc =>
521             q(
522             Low-level method.
523             Compute the edit distance matrix for input PDLs $a1() and $b1() given a
524             static cost schema @costs = ($costMatch(), $costIns(), $costDel(), $costSubst()).
525             Functionally identitical to _edit_distance_matrix_full($matches,@costs,$dist),
526             but slightly faster.
527              
528             The first elements of $a1() and $b1() are ignored.
529             ),
530             );
531              
532              
533             ##======================================================================
534             ## Distance + Alignment: Static
535              
536             ##------------------------------------------------------
537             ## edit_align_static() : distance + alignment matrices using static cost schema
538             pp_add_exported('','edit_align_static');
539              
540             pp_addpm(<<'EOPM');
541              
542             =pod
543              
544             =head2 edit_align_static
545              
546             =for sig
547              
548             Signature: (a(N); b(M);
549             staticCostMatch(); staticCostIns(); staticCostDel(); staticCostSubst();
550             [o]dist(N+1,M+1); [o]align(N+1,M+1))
551              
552             Convenience method.
553             Compute the edit distance and alignment matrices for inputs $a() and $b() given
554             a static cost schema @costs = ($staticCostMatch(), $staticCostIns(), $staticCostDel(), and $staticCostSubst()).
555             $a() and $b() may be specified as PDLs, arrays of numbers, or as strings.
556             Functionally equivalent to edit_align_full($matches,@costs,$dist),
557             but slightly faster.
558              
559             =cut
560              
561 3     3 1 7261 sub edit_align_static {
562             return _edit_align_static(_edit_pdl($_[0]), _edit_pdl($_[1]), @_[2..$#_]);
563             }
564              
565             EOPM
566              
567              
568             ##------------------------------------------------------
569             ## _edit_align_static : get distance & alignment matrices from static cost schema
570             pp_add_exported('','_edit_align_static');
571             pp_def('_edit_align_static',
572             Pars => ('a1(N1); b1(M1); costMatch(); costIns(); costDel(); costSubst();'
573             .' [o]dist(N1,M1); byte [o]align(N1,M1)'),
574             Code =>
575             ('
576             int i,j;
577             //
578             //-- initialize distance matrix: insertion costs
579             $dist( N1=>0,M1=>0) = $costMatch(); //-- BOS always matches
580             $align(N1=>0,M1=>0) = ALIGN_OP_MATCH; //-- ... and is marked as "match"
581             for (i=1; i < $SIZE(N1); i++) {
582             $dist (N1=>i,M1=>0) = $dist(N1=>i-1,M1=>0) + $costDel(); //-- delete (insert1)
583             $align(N1=>i,M1=>0) = ALIGN_OP_INSERT1;
584             }
585             for (j=1; j < $SIZE(M1); j++) {
586             $dist (N1=>0,M1=>j) = $dist(N1=>0,M1=>j-1) + $costIns(); //-- insert (insert2)
587             $align(N1=>0,M1=>j) = ALIGN_OP_INSERT2;
588             }
589             //
590             //-- compute distance
591             for (i=1; i < $SIZE(N1); i++) {
592             for (j=1; j < $SIZE(M1); j++) {
593             $GENERIC(dist) cost_insert_1 = $dist(N1=>i-1,M1=>j ) + $costDel();
594             $GENERIC(dist) cost_insert_2 = $dist(N1=>i, M1=>j-1) + $costIns();
595             $GENERIC(dist) cost_subst = $dist(N1=>i-1,M1=>j-1);
596             char subst_op;
597             //
598             if ($a1(N1=>i)==$b1(M1=>j)) {
599             cost_subst += $costMatch();
600             subst_op = ALIGN_OP_MATCH;
601             } else {
602             cost_subst += $costSubst();
603             subst_op = ALIGN_OP_SUBSTITUTE;
604             }
605             //
606             if (cost_insert_1 < cost_insert_2) {
607             if (cost_insert_1 < cost_subst) {
608             $dist(N1=>i,M1=>j) = cost_insert_1;
609             $align(N1=>i,M1=>j) = ALIGN_OP_INSERT1;
610             } else {
611             $dist(N1=>i,M1=>j) = cost_subst;
612             $align(N1=>i,M1=>j) = subst_op;
613             }
614             } else if (cost_insert_2 < cost_subst) {
615             $dist(N1=>i,M1=>j) = cost_insert_2;
616             $align(N1=>i,M1=>j) = ALIGN_OP_INSERT2;
617             } else {
618             $dist(N1=>i,M1=>j) = cost_subst;
619             $align(N1=>i,M1=>j) = subst_op;
620             }
621             }
622             }
623             '),
624             Doc =>
625             q(
626             Low-level method.
627             Compute the edit distance and alignment matrices for input PDLs $a1() and $b1() given a
628             static cost schema @costs = ($costMatch(), $costIns(), $costDel(), $costSubst()).
629             Functionally identitical to _edit_distance_matrix_full($matches,@costs,$dist),
630             but slightly faster.
631              
632             The first elements of $a1() and $b1() are ignored.
633             ),
634             );
635              
636             ##==============================================================
637             ## Alignment
638              
639             ##------------------------------------------------------
640             ## Alignment: Constants
641             pp_add_exported('','align_op_insert1');
642             pp_def('align_op_insert1',
643             Pars=>'[o]a()',
644             Code=>'$a() = ALIGN_OP_INSERT1;',
645             Doc => 'Alignment matrix value constant for insertion operations on $a() string.',
646             );
647              
648             pp_add_exported('','align_op_insert2');
649             pp_def('align_op_insert2',
650             Pars=>'[o]a()',
651             Code=>'$a() = ALIGN_OP_INSERT2;',
652             Doc => 'Alignment matrix value constant for insertion operations on $a() string.',
653             );
654              
655             pp_add_exported('','align_op_match');
656             pp_def('align_op_match',
657             Pars=>'[o]a()',
658             Code=>'$a() = ALIGN_OP_MATCH;',
659             Doc => 'Alignment matrix value constant for matches.',
660             );
661              
662             pp_add_exported('','align_op_substitute');
663             pp_def('align_op_substitute',
664             Pars=>'[o]a()',
665             Code=>'$a() = ALIGN_OP_SUBSTITUTE;',
666             Doc => 'Alignment matrix value constant for substitution operations.',
667             );
668              
669              
670             pp_add_exported('','align_op_insert');
671             pp_add_exported('','align_op_delete');
672             pp_addpm(<<'EOPM');
673              
674             =pod
675              
676             =head2 align_op_delete
677              
678             Alias for align_op_insert1()
679              
680             =head2 align_op_insert
681              
682             Alias for align_op_insert2()
683              
684             =cut
685              
686             *align_op_delete = \&align_op_insert1;
687             *align_op_insert = \&align_op_insert2;
688              
689             EOPM
690              
691              
692             pp_add_exported('','align_ops');
693             pp_addpm(<<'EOPM');
694             =pod
695              
696             =head2 align_ops
697              
698             =for sig
699              
700             Signature: ([o]ops(4))
701              
702             Alignment matrix value constants 4-element pdl (match,insert1,insert2,substitute).a
703              
704             =cut
705              
706 0     0 1 0 sub align_ops { return PDL->sequence(PDL::byte(),4); }
707              
708             EOPM
709              
710             ##------------------------------------------------------
711             ## Alignment: best path
712             ##------------------------------------------------------
713             ## edit_bestpath() : distance + alignment matrices using static cost schema
714             pp_add_exported('','edit_bestpath');
715              
716             pp_addpm(<<'EOPM');
717              
718             =pod
719              
720             =head2 edit_bestpath
721              
722             =for sig
723              
724             Signature: (align(N+1,M+1); [o]apath(N+M+2); [o]bpath(N+M+2); [o]pathlen())
725              
726             Convenience method.
727             Compute best path through alignment matrix $align().
728             Stores paths for original input strings $a() and $b() in $apath() and $bpath()
729             respectively.
730             Negative values in $apath() and $bpath() indicate insertion/deletion operations.
731             On completion, $pathlen() holds the actual length of the paths.
732              
733             =cut
734              
735 1     1 1 117 sub edit_bestpath {
736 1 50       6 my ($align,$apath,$bpath,$len) = @_;
737 1 50       50 $len=pdl(long,$align->dim(0)+$align->dim(1)) if (!defined($len));
  1         2  
738 0 0       0 if (!defined($apath)) { $apath=zeroes(long,$len); }
739 1 50       10 else { $apath->reshape($len) if ($apath->nelem < $len); }
  1         3  
740 0 0       0 if (!defined($bpath)) { $bpath = zeroes(long,$len); }
741 1         55 else { $bpath->reshape($len) if ($bpath->nelem < $len); }
742 1         3 _edit_bestpath($align, $apath, $bpath, $len, $align->dim(0)-1, $align->dim(1)-1);
743             return ($apath,$bpath,$len);
744             }
745              
746             EOPM
747              
748              
749             ##------------------------------------------------------
750             ## _edit_bestpath : get best path
751             pp_add_exported('','_edit_bestpath');
752             pp_def('_edit_bestpath',
753             Pars => ('align(N1,M1); int [o]apath(L); int [o]bpath(L); int [o]len();'),
754             OtherPars => ('int ifinal; int jfinal'),
755             Code =>
756             ('
757             int i,j,p,endp,tmp;
758             char op;
759             //-- get reversed path & real path length
760             $len()=0;
761             for (p=0,i=$COMP(ifinal),j=$COMP(jfinal); p < $SIZE(L) && (i>0 || j>0); p++) {
762             $apath(L=>p) = i-1;
763             $bpath(L=>p) = j-1;
764             //
765             op = $align(N1=>i,M1=>j);
766             if (op==ALIGN_OP_INSERT1) {
767             --i;
768             } else if (op==ALIGN_OP_INSERT2) {
769             --j;
770             } else { /* if (op==ALIGN_OP_MATCH || op==ALIGN_OP_SUBSTITUTE) */
771             --i;
772             --j;
773             }
774             }
775             $len() = p;
776             //-- now reverse the paths
777             for (p=0; p < ($len()+1)/2; p++) {
778             endp = $len()-p-1;
779             //
780             tmp = $apath(L=>p);
781             $apath(L=>p) = $apath(L=>endp);
782             $apath(L=>endp) = tmp;
783             //
784             tmp = $bpath(L=>p);
785             $bpath(L=>p) = $bpath(L=>endp);
786             $bpath(L=>endp) = tmp;
787             }
788             //-- now, sanitize the paths
789             for (p=$len(); p > 0; p--) {
790             if ($apath(L=>p) == $apath(L=>p-1)) $apath(L=>p) = -1;
791             if ($bpath(L=>p) == $bpath(L=>p-1)) $bpath(L=>p) = -1;
792             }
793             '),
794             Doc =>
795             q(
796             Low-level method.
797             Compute best path through alignment matrix $align() from final index ($ifinal,$jfinal).
798             Stores paths for (original) input strings $a() and $b() in $apath() and $bpath()
799             respectively.
800             Negative values in $apath() and $bpath() indicate insertion/deletion operations.
801             On completion, $pathlen() holds the actual length of the paths.
802             ),
803             );
804              
805             ##------------------------------------------------------
806             ## Alignment: operation backtrace
807              
808             ##------------------------------------------------------
809             ## edit_backtrace() : alignment operation backtrace
810             pp_add_exported('','edit_pathtrace');
811             pp_addpm(<<'EOPM');
812              
813             =pod
814              
815             =head2 edit_pathtrace
816              
817             =for sig
818              
819             Signature: ( align(N+1,M+1); [o]ai(L); [o]bi(L); [o]ops(L); [o]$pathlen() )
820              
821             Convenience method.
822             Compute alignment path backtrace through alignment matrix $align() from final index ($ifinal,$jfinal).
823             Stores raw paths for (original) input strings $a() and $b() in $ai() and $bi()
824             respectively.
825             Unlike edit_bestpath(), null-moves for $ai() and $bi() are not stored here as negative values.
826             Returned pdls ($ai,$bi,$ops) are trimmed to the appropriate path length.
827              
828             =cut
829              
830 1     1 1 187 sub edit_pathtrace {
831 1 50       8 my ($align,$ai,$bi,$ops,$len) = @_;
832 1 50       54 $len=pdl(long,$align->dim(0)+$align->dim(1)) if (!defined($len));
  1         2  
833 0 0       0 if (!defined($ai)) { $ai=zeroes(long,$len); }
834 1 50       19 else { $ai->reshape($len) if ($ai->nelem < $len); }
  1         3  
835 0 0       0 if (!defined($bi)) { $bi = zeroes(long,$len); }
836 1 50       11 else { $bi->reshape($len) if ($bi->nelem < $len); }
  1         2  
837 0 0       0 if (!defined($ops)) { $ops = zeroes(long,$len); }
838 1         54 else { $ops->reshape($len) if ($ops->nelem < $len); }
839 1         3 _edit_pathtrace($align, $ai,$bi,$ops,$len, $align->dim(0)-1,$align->dim(1)-1);
840 1         2 my $lens = ($len->sclr-1);
  3         31  
841             return ((map { $_->slice("0:$lens") } ($ai,$bi,$ops)), $len);
842             }
843              
844             EOPM
845              
846             ##------------------------------------------------------
847             ## _edit_pathlog : trace path
848             pp_add_exported('','_edit_pathtrace');
849             pp_def('_edit_pathtrace',
850             Pars => ('align(N1,M1); int [o]ai(L); int [o]bi(L); int [o]ops(L); int [o]len();'),
851             OtherPars => ('int ifinal; int jfinal'),
852             Code =>
853             ('
854             int i,j,p, tmp;
855             //-- get reversed path & real path length
856             $len() = 0;
857             for (p=0, i=$COMP(ifinal),j=$COMP(jfinal); p < $SIZE(L) && (i>0 || j>0); p++) {
858             int op = $align(N1=>i,M1=>j);
859             $ai(L=>p) = i;
860             $bi(L=>p) = j;
861             $ops(L=>p) = op;
862             switch (op) {
863             case ALIGN_OP_INSERT1: --i; break;
864             case ALIGN_OP_INSERT2: --j; break;
865             case ALIGN_OP_MATCH:
866             case ALIGN_OP_SUBSTITUTE:
867             default: --i; --j; break;
868             }
869             }
870             $len() = p;
871             //-- now reverse the paths
872             for (p=0; p < ($len()+1)/2; p++) {
873             int endp = $len()-p-1;
874             //
875             tmp = $ai(L=>p);
876             $ai(L=>p) = $ai(L=>endp);
877             $ai(L=>endp) = tmp;
878             //
879             tmp = $bi(L=>p);
880             $bi(L=>p) = $bi(L=>endp);
881             $bi(L=>endp) = tmp;
882             //
883             tmp = $ops(L=>p);
884             $ops(L=>p) = $ops(L=>endp);
885             $ops(L=>endp) = tmp;
886             }
887             '),
888             Doc =>
889             q(
890             Low-level method.
891             Compute alignment path backtrace through alignment matrix $align() from final index ($ifinal,$jfinal).
892             Stores raw paths for (original) input strings $a() and $b() in $ai() and $bi()
893             respectively.
894             Unlike edit_bestpath(), null-moves for $ai() and $bi() are not stored here as negative values.
895             Returned pdls ($ai,$bi,$ops) are trimmed to the appropriate path length.
896             ),
897             );
898              
899             ##======================================================================
900             ## Longest Common Subsequence (LCS)
901              
902             ##------------------------------------------------------
903             ## edit_lcs() : lcs matrix (convenience)
904             pp_add_exported('','edit_lcs');
905             pp_addpm(<<'EOPM');
906              
907             =pod
908              
909             =head2 edit_lcs
910              
911             =for sig
912              
913             Signature: (a(N); b(M); int [o]lcs(N+1,M+1);)
914              
915             Convenience method.
916             Compute the longest common subsequence (LCS) matrix for input PDLs $a1() and $b1().
917             The output matrix $lcs() contains at cell ($i+1,$j+1) the length of the LCS
918             between $a1(0..$i) and $b1(0..$j); thus $lcs($N,$M) contains the
919             length of the LCS between $a() and $b().
920              
921             =cut
922              
923 1     1 1 2686 sub edit_lcs {
924             return _edit_lcs(_edit_pdl($_[0]), _edit_pdl($_[1]), @_[2..$#_]);
925             }
926              
927             EOPM
928              
929             ##------------------------------------------------------
930             ## _edit_lcs : get longest common subsequence matrix
931             pp_add_exported('','_edit_lcs');
932             pp_def('_edit_lcs',
933             Pars => ('a1(N1); b1(M1); int [o]lcs(N1,M1);'),
934             Code =>
935             ('
936             int i,j, iprev,jprev;
937             //
938             //-- initialize lcs matrix: lcs=0 at borders
939             for (i=0; i < $SIZE(N1); i++) { $lcs(N1=>i,M1=>0) = 0; }
940             for (j=1; j < $SIZE(M1); j++) { $lcs(N1=>0,M1=>j) = 0; }
941             //
942             //-- compute lcs
943             for (i=1,iprev=0; i < $SIZE(N1); i++,iprev++) {
944             for (j=1,jprev=0; j < $SIZE(M1); j++,jprev++) {
945             //
946             if ($a1(N1=>i)==$b1(M1=>j)) {
947             $lcs(N1=>i,M1=>j) = $lcs(N1=>iprev,M1=>jprev)+1;
948             } else {
949             $GENERIC(lcs) lcs_i_jp = $lcs(N1=>i,M1=>jprev);
950             $GENERIC(lcs) lcs_ip_j = $lcs(N1=>iprev,M1=>j);
951             $lcs(N1=>i,M1=>j) = lcs_i_jp > lcs_ip_j ? lcs_i_jp : lcs_ip_j;
952             }
953             }
954             }
955             '),
956             Doc =>
957             q(
958             Low-level method.
959             Compute the longest common subsequence (LCS) matrix for input PDLs $a1() and $b1().
960             The initial (zeroth) elements of $a1() and $b1() are ignored.
961             The output matrix $lcs() contains at cell ($i,$j) the length of the LCS
962             between $a1(1..$i) and $b1(1..$j); thus $lcs($N1-1,$M1-1) contains the
963             length of the LCS between $a1() and $b1().
964             ),
965             );
966              
967             ##------------------------------------------------------
968             ## lcs_backtrace : get LCS backtrace (convenience)
969             pp_add_exported('','lcs_backtrace');
970             pp_addpm(<<'EOPM');
971              
972             =pod
973              
974             =head2 lcs_backtrace
975              
976             =for sig
977              
978             Signature: (a(N); b(M); int lcs(N+1,M+1); int ifinal(); int jfinal(); int [o]ai(L); int [o]bi(L); int [o]len())
979              
980             Convenience method.
981             Compute longest-common-subsequence backtrace through LCS matrix $lcs()
982             for original input strings ($a(),$b()) from final index ($ifinal,$jfinal).
983             Stores raw paths for (original) input strings $a() and $b() in $ai() and $bi()
984             respectively.
985              
986             =cut
987              
988 1     1 1 167 sub lcs_backtrace {
989 1 50       7 my ($a,$b,$lcs,$ifinal,$jfinal,$ai,$bi,$len) = @_;
990 1 50       183 $len=pdl(long, pdl(long,$lcs->dims)->min) if (!defined($len));
  1         4  
991 0 0       0 if (!defined($ai)) { $ai=zeroes(long,$len); }
992 1 50       13 else { $ai->reshape($len) if ($ai->nelem < $len); }
  1         3  
993 0 0       0 if (!defined($bi)) { $bi = zeroes(long,$len); }
994 1 50       7 else { $bi->reshape($len) if ($bi->nelem < $len); }
  1         3  
995 1 50       3 if (!defined($ifinal)) { $ifinal = $lcs->dim(0)-1; }
  1         2  
996 1         6 if (!defined($jfinal)) { $jfinal = $lcs->dim(1)-1; }
997 1         132 _lcs_backtrace(_edit_pdl($a),_edit_pdl($b), $lcs,$ifinal,$jfinal, $ai,$bi,$len);
998 1         11 my $lens = ($len->sclr-1);
999             return ($ai->slice("0:$lens"),$bi->slice("0:$lens"), $len);
1000             }
1001              
1002             EOPM
1003              
1004             ##------------------------------------------------------
1005             ## _lcs_backtrace : get LCS backtrace
1006             pp_add_exported('','_lcs_backtrace');
1007             pp_def('_lcs_backtrace',
1008             Pars => ('a1(N1); b1(M1); int lcs(N1,M1); int ifinal(); int jfinal(); [o]ai(L); [o]bi(L); int [o]len()'),
1009             Code =>
1010             ('
1011             int i,j,p, iprev,jprev, tmp;
1012             //-- get reversed path & real path length
1013             $len() = 0;
1014             for (p=0, i=$ifinal(), j=$jfinal(); p < $SIZE(L) && i>0 && j>0; ) {
1015             $GENERIC(a1) a1i = $a1(N1=>i);
1016             $GENERIC(b1) b1j = $b1(M1=>j);
1017             iprev=i-1;
1018             jprev=j-1;
1019             if (a1i==b1j) {
1020             $ai(L=>p) = iprev;
1021             $bi(L=>p) = jprev;
1022             --i;
1023             --j;
1024             ++p;
1025             } else if ($lcs(N1=>i,M1=>jprev) > $lcs(N1=>iprev,M1=>j)) {
1026             --j;
1027             } else {
1028             --i;
1029             }
1030             }
1031             $len() = p;
1032             //-- now reverse the paths
1033             for (p=0; p < ($len()+1)/2; p++) {
1034             int endp = $len()-p-1;
1035             //
1036             tmp = $ai(L=>p);
1037             $ai(L=>p) = $ai(L=>endp);
1038             $ai(L=>endp) = tmp;
1039             //
1040             tmp = $bi(L=>p);
1041             $bi(L=>p) = $bi(L=>endp);
1042             $bi(L=>endp) = tmp;
1043             }
1044             '),
1045             Doc =>
1046             q(
1047             Low-level method.
1048             Compute longest-common-subsequence backtrace through LCS matrix $lcs()
1049             for initial-padded strings ($a1(),$b1()) from final index ($ifinal,$jfinal).
1050             Stores raw paths for (original) input strings $a() and $b() in $ai() and $bi()
1051             respectively.
1052             ),
1053             );
1054              
1055              
1056              
1057             ##======================================================================
1058             ## Footer Administrivia
1059             ##======================================================================
1060              
1061             ##------------------------------------------------------
1062             ## pm additions
1063             pp_addpm(<<'EOPM');
1064              
1065             ##---------------------------------------------------------------------
1066             =pod
1067              
1068             =head1 ACKNOWLEDGEMENTS
1069              
1070             Perl by Larry Wall.
1071              
1072             PDL by Karl Glazebrook, Tuomas J. Lukka, Christian Soeller, and others.
1073              
1074             =cut
1075              
1076             ##----------------------------------------------------------------------
1077             =pod
1078              
1079             =head1 KNOWN BUGS
1080              
1081             Probably many.
1082              
1083             =cut
1084              
1085              
1086             ##---------------------------------------------------------------------
1087             =pod
1088              
1089             =head1 AUTHOR
1090              
1091             Bryan Jurish Emoocow@cpan.orgE
1092              
1093             =head2 Copyright Policy
1094              
1095             Copyright (C) 2006-2015, Bryan Jurish. All rights reserved.
1096              
1097             This package is free software, and entirely without warranty.
1098             You may redistribute it and/or modify it under the same terms
1099             as Perl itself, either Perl 5.20.2, or at your option any later
1100             version of Perl 5.
1101              
1102             =head1 SEE ALSO
1103              
1104             perl(1), PDL(3perl).
1105              
1106             =cut
1107              
1108             EOPM
1109              
1110              
1111             # Always make sure that you finish your PP declarations with
1112             # pp_done
1113             pp_done();
1114             ##----------------------------------------------------------------------