File Coverage

HMM.pd
Criterion Covered Total %
statement 21 21 100.0
branch 8 16 50.0
condition n/a
subroutine 2 2 100.0
pod 2 2 100.0
total 33 41 80.4


line stmt bran cond sub pod time code
1             #-*- Mode: CPerl -*-
2              
3             ##======================================================================
4             ## Header Administrivia
5             ##======================================================================
6              
7             our $VERSION = '0.06010';
8             pp_setversion($VERSION);
9              
10             ##-- accomodate typo-fix in PDL-2.008 API
11             eval "require PDL";
12             use version;
13             our $pdl_version = version->parse($PDL::VERSION);
14             our $propagate_badflag = $pdl_version >= '2.008' ? "propagate_badflag" : "propogate_badflag";
15              
16             ##-- floating-point types
17             ## PDL v2.082 doesn't like LD here (so we use 'E' instead)
18             ## - t/03_baum.t crashes at line 115 with `PP INTERNAL ERROR in logadd: unhandled datatype(11)`
19             my $FLOAT_TYPES = [qw(F D E)];
20              
21             ##------------------------------------------------------
22             ## pm additions
23             pp_addpm({At=>'Top'},<<'EOPM');
24             =pod
25              
26             =head1 NAME
27              
28             PDL::HMM - Hidden Markov Model utilities in PDL
29              
30             =head1 SYNOPSIS
31              
32             use PDL::HMM;
33              
34             ##-----------------------------------------------------
35             ## Dimensions
36              
37             $N = $number_of_states;
38             $M = $number_of_symbols;
39             $T = $length_of_input;
40              
41             $A = $maximum_ambiguity;
42              
43             ##-----------------------------------------------------
44             ## Parameters
45              
46             $af = log(random($N,$N));
47             $bf = log(random($N,$M));
48             $pif = log(random($N));
49             $omegaf = log(random($N));
50              
51             @theta = ($a,$b,$pi,$omega) = hmmmaximize($af,$bf,$pif,$omegaf);
52              
53             $o = long(rint($M*random($T)));
54              
55             maximum_n_ind(dice_axis($a->logsumover+$pi+$b, 1,$o),
56             ($oq=zeroes(long,$A,$T))); ##-- for constrained variants
57              
58             ##-----------------------------------------------------
59             ## Log arithmetic
60              
61             $log0 = logzero;
62             $logz = logadd(log($x),log($y));
63             $logz = logdiff(log($x),log($y));
64             $logz = logsumover(log($x));
65              
66             ##-----------------------------------------------------
67             ## Sequence Probability
68              
69             $alpha = hmmfw ($a,$b,$pi, $o ); ##-- forward (full)
70             $alphaq = hmmfwq($a,$b,$pi, $o, $oq); ##-- forward (constrained)
71              
72             $beta = hmmbw ($a,$b,$omega, $o ); ##-- backward (full)
73             $betaq = hmmbwq($a,$b,$omega, $o,$oq); ##-- backward (constrained)
74              
75             ##-----------------------------------------------------
76             ## Parameter Estimation
77              
78             @expect = ($ea,$eb,$epi,$eomega) = hmmexpect0(@theta); ##-- initialize
79              
80             hmmexpect (@theta, $o, $alpha, $beta, $ea,$eb,$epi); ##-- expect (full)
81             hmmexpectq(@theta, $o,$oq, $alphaq,$betaq, $ea,$eb,$epi); ##-- expect (constrained)
82              
83             ($a,$b,$pi,$omega) = hmmmaximize($ea,$eb,$epi,$eomega); ##-- maximize
84              
85             ##-----------------------------------------------------
86             ## Sequence Analysis
87              
88             ($delta,$psi) = hmmviterbi ($a,$b,$pi, $o); ##-- trellis (full)
89             ($deltaq,$psiq) = hmmviterbiq($a,$b,$pi, $o,$oq); ##-- trellis (constrained)
90              
91             $paths = hmmpath ( $psi, sequence($N)); ##-- backtrace (full)
92             $pathsq = hmmpathq($oq, $psiq, sequence($A)); ##-- backtrace (constrained)
93              
94             =cut
95              
96             EOPM
97             ## /pm additions
98             ##------------------------------------------------------
99              
100             ##------------------------------------------------------
101             ## Exports: None
102             #pp_export_nothing();
103              
104             ##------------------------------------------------------
105             ## Includes / defines
106             pp_addhdr(<<'EOH');
107              
108             #include
109              
110             /*#define DEBUG_ALPHA*/
111             /*#define DEBUG_BETA*/
112             /*#define DEBUG_VITERBI*/
113              
114             EOH
115              
116              
117             ##======================================================================
118             ## C Utilities
119             ##======================================================================
120              
121             ##----------------------------------------------------------------------
122             ## Log addition
123             pp_addhdr(<<'EOH');
124              
125             /* logadd(x,y) = log(exp(x)+exp(y))
126             * + Code from Manning & Schütze (1997), Sec. 9.4, page 337
127             *
128             * LOG_BIG = log(1E31)
129             */
130             #define LOG_BIG 71.3801378828154
131             #define LOG_ZERO -1E+38
132             #define LOG_ONE 0
133             #define LOG_NONE 1
134             static inline double logadd1(double x, double y) {
135             if (y-x > LOG_BIG) return y;
136             else if (x-y > LOG_BIG) return x;
137             /*else return min(x,y) + log(exp(x-min(x,y)) + exp(y-min(x,y))); */
138             else if (x
139             else return y + log(exp(x-y) + 1);
140             }
141             static inline double logadd0(double x, double y) {
142             return log(exp(x)+exp(y));
143             }
144              
145             /* logdiff(x,y) = log(exp(x)-exp(y))
146             * + adapted from above
147             * + always returns positive (i.e. symmetric difference)
148             */
149             static inline double logdiff1(double x, double y) {
150             if (y-x > LOG_BIG) { return y; }
151             else if (x-y > LOG_BIG) { return x; }
152             /*else { return max(x,y) + log(exp(max(x,y)-max(x,y)) - exp(min(x,y)-max(x,y))); } */
153             /* = max(x,y) + log( 1 - exp(min(x,y)-max(x,y))); } */
154             else if (x>y) { return x + log( 1 - exp(y-x)); }
155             else { return y + log( 1 - exp(x-y)); }
156             }
157             static inline double logdiff0(double x, double y) {
158             return log(x>y ? (exp(x)-exp(y)) : (exp(y)-exp(x)));
159             }
160              
161             /*
162             #define logadd(x,y) logadd0(x,y)
163             #define logdiff(x,y) logdiff0(x,y)
164             */
165              
166             #define logadd(x,y) logadd1(x,y)
167             #define logdiff(x,y) logdiff1(x,y)
168              
169             EOH
170              
171              
172             ##======================================================================
173             ## PDL::PP Wrappers
174             ##======================================================================
175              
176             ##======================================================================
177             ## Basic Utilities
178             pp_addpm(<<'EOPM');
179             =pod
180              
181             =head1 Log Arithmetic
182              
183             =cut
184             EOPM
185              
186             ##------------------------------------------------------
187             ## logzero(): near approximation of log(0)
188             pp_def('logzero',
189             Pars => '[o]a()',
190             GenericTypes => $FLOAT_TYPES,
191             Code => 'broadcastloop %{ $a() = LOG_ZERO; %} $PDLSTATESETGOOD(a);',
192             HandleBad=>1,
193             Doc => 'Approximates $a() = log(0), avoids nan.',
194             BadDoc => 'logzero() handles bad values. The state of the output PDL is always good.',
195             );
196              
197              
198             ##------------------------------------------------------
199             ## log addition: logadd(a,b) = log(exp(a)+exp(b))
200             pp_def('logadd',
201             Pars => 'a(); b(); [o]c()',
202             GenericTypes => $FLOAT_TYPES,
203             Inplace=>['a'], ##-- can run inplace on a()
204             Code => '$c() = logadd($a(),$b());',
205             Doc => 'Computes $c() = log(exp($a()) + exp($b())), should be more stable.',
206             );
207              
208             ##------------------------------------------------------
209             ## log subtraction: logdiff(a,b) = log(exp(max(a,b))-exp(min(a,b)))
210             pp_def('logdiff',
211             Pars => 'a(); b(); [o]c()',
212             GenericTypes => $FLOAT_TYPES,
213             Inplace=>['a'], ##-- can run inplace on a()
214             Code => '$c() = logdiff($a(),$b());',
215             Doc => 'Computes log symmetric difference c = log(exp(max(a,b)) - exp(min(a,b))), may be more stable.',
216             );
217              
218              
219             ##------------------------------------------------------
220             ## log sum: logsumover(a) = log(sumover(exp(a)))
221             pp_def('logsumover',
222             Pars => 'a(n); [o]b()',
223             GenericTypes => $FLOAT_TYPES,
224             Code => (join(" ",
225             'double sum=LOG_ZERO;',
226             'loop (n) %{ sum = logadd($a(),sum); %}',
227             '$b() = sum;')),
228             Doc => 'Computes $b() = log(sumover(exp($a()))), should be more stable.',
229             );
230              
231              
232             ##======================================================================
233             ## Sequence Probability
234             pp_addpm(<<'EOPM');
235             =pod
236              
237             =head1 Sequence Probability
238              
239             =cut
240             EOPM
241              
242              
243             ##------------------------------------------------------
244             ## Forward probability: hmmfw(A,B,pi, O, [o]alpha)
245             pp_def
246             ('hmmfw',
247             Pars => 'a(N,N); b(N,M); pi(N); o(T); [o]alpha(N,T)',
248             GenericTypes => $FLOAT_TYPES,
249             Code =>
250             ('
251             /*-- Initialize: t==0 --*/
252             int i,j,t, o_tp1 = $o(T=>0);
253             loop (N) %{
254             $alpha(T=>0) = $pi() + $b(M=>o_tp1);
255              
256             #ifdef DEBUG_ALPHA
257             printf("INIT: j=%u,t=0,o=%d: pi(j=%u)=%.2e b(j=%u,o=%d)=%.2e alpha(j=%u,t=0)=%.2e\n",
258             n,o_tp1,
259             n, exp($pi()),
260             n,o_tp1 exp($b(M=>o_tp1)),
261             n, exp($alpha(T=>0))
262             );
263             #endif
264             %}
265              
266             #ifdef DEBUG_ALPHA
267             printf("\n\n");
268             #endif
269              
270             /*-- Loop: time t>0 --*/
271             for (t=0; t < $SIZE(T)-1; t++) {
272             o_tp1 = $o(T=>t+1);
273              
274             /*-- Loop: state_(t+1)==j --*/
275             for (j=0; j<$SIZE(N); j++) {
276             $GENERIC(alpha) alpha_j_tp1 = ($GENERIC(alpha))LOG_ZERO;
277              
278              
279             /*-- Loop: state_t==i --*/
280             for (i=0; i<$SIZE(N); i++) {
281             alpha_j_tp1 = logadd( $alpha(N=>i,T=>t) + $a(N0=>i,N1=>j), alpha_j_tp1 );
282              
283             #ifdef DEBUG_ALPHA
284             printf("i=%u,j=%u,t=%u,o=%d: alpha(i=%u,t=%u)=%.2e a(i=%u,j=%u)=%.2e b(j=%u,o=%d)=%.2e prod=%.2e sum=%.2e\n",
285             i,j,t,o_tp1,
286             i,t, exp($alpha(N=>i,T=>t)),
287             i,j, exp($a(N0=>i,N1=>j)),
288             j,o_tp1, exp($b(N=>j,M=>o_tp1)),
289             exp( $alpha(N=>i,T=>t) + $a(N0=>i,N1=>j) ), exp(alpha_j_tp1));
290             #endif
291             }
292              
293             /*-- Storage: alpha(time=t+1, state=j) --*/
294             $alpha(N=>j,T=>t+1) = alpha_j_tp1 + $b(N=>j,M=>o_tp1);
295              
296             #ifdef DEBUG_ALPHA
297             printf("----> alpha(j=%u,t=%u)=%.2E\n", j,t+1, exp($alpha(N=>j,T=>t+1)));
298             #endif
299             }
300             #ifdef DEBUG_ALPHA
301             printf("\n\n");
302             #endif
303             }
304             '),
305              
306             Doc=>
307             ('Compute forward probability (alpha) matrix
308             for input $o given model parameters
309             @theta = ($a, $b, $pi, $omega).
310              
311             Output (pseudocode) for all 0<=i
312              
313             $alpha(i,t) = log P( $o(0:t), q(t)==i | @theta )
314              
315             Note that the final-state probability vector $omega() is neither
316             passed to this function nor used in the computation, but
317             can be used to compute the final sequence probability for $o as:
318              
319             log P( $o | @theta ) = logsumover( $omega() + $alpha(:,t-1) )
320              
321             '),
322              
323             );
324              
325             pp_addpm('*hmmalpha = \&hmmfw;');
326             pp_add_exported('','hmmalpha');
327              
328              
329             ##------------------------------------------------------
330             ## Forward probability (constrained): hmmfwq(A,B,pi, O,Q, [o]alphaq)
331             pp_def
332             ('hmmfwq',
333             Pars => 'a(N,N); b(N,M); pi(N); o(T); oq(Q,T); [o]alphaq(Q,T)',
334             GenericTypes => $FLOAT_TYPES,
335             Code =>
336             ('
337             /*-- Initialize: t==0 --*/
338             int i,j,t, o_tp1 = $o(T=>0);
339             int qi,qj;
340              
341             for (qi=0; qi < $SIZE(Q); qi++) {
342             j = $oq(Q=>qi,T=>0);
343             $alphaq(Q=>qi,T=>0) = (j>=0 ? $pi(N=>j) + $b(N=>j,M=>o_tp1) : ($GENERIC(alphaq))LOG_ZERO);
344             }
345              
346             #ifdef DEBUG_ALPHA
347             printf("\n\n");
348             #endif
349              
350             /*-- Loop: time t>0 --*/
351             for (t=0; t < $SIZE(T)-1; t++) {
352             o_tp1 = $o(T=>t+1);
353              
354             /*-- Loop: q_(t+1)=qj : state_(t+1)=oq(qj,t+1)=j --*/
355             for (qj=0; qj < $SIZE(Q); qj++) {
356             $GENERIC(alphaq) alpha_j_tp1 = ($GENERIC(alphaq))LOG_ZERO;
357             j = $oq(Q=>qj,T=>t+1);
358              
359             /*-- Loop: q_(t)=qi : state_(t)=oq(qi,t)=i --*/
360             for (qi=0; j>=0 && qi < $SIZE(Q); qi++) {
361             i = $oq(Q=>qi,T=>t);
362             if (i < 0) break;
363              
364             alpha_j_tp1 = logadd( $alphaq(Q=>qi,T=>t) + $a(N0=>i,N1=>j), alpha_j_tp1 );
365              
366             #ifdef DEBUG_ALPHA
367             printf("qi=%u,i=%d,qj=%u,j=%d,t=%u,o=%d: alphaq(qi=%u,t=%u)=%.2e a(i=%d,j=%d)=%.2e b(j=%d,o=%d)=%.2e prod=%.2e sum=%.2e\n",
368             qi,i, qj,j, t,o_tp1,
369             i,t, exp($alphaq(Q=>qi,T=>t)),
370             i,j, ((i>=0 && j>=0) ? exp($a(N0=>i,N1=>j)) : 0),
371             j,o_tp1, ((i>=0 && j>=0) ? exp($b(N=>j,M=>o_tp1)) : 0),
372             ((i>=0 && j>=0) ? exp( $alphaq(Q=>qi,T=>t) + $a(N0=>i,N1=>j) ) : 0),
373             exp(alpha_j_tp1));
374             #endif
375             }
376             /*-- End Loop: q_(t)=qi : state_(t)=oq(qi,t)=i --*/
377              
378             /*-- Storage: alphaq(time=t+1, stateIndex=qj) --*/
379             if (j>=0) {
380             $alphaq(Q=>qj,T=>t+1) = alpha_j_tp1 + $b(N=>j,M=>o_tp1);
381             } else {
382             $alphaq(Q=>qj,T=>t+1) = ($GENERIC(alphaq))LOG_ZERO;
383             }
384              
385             #ifdef DEBUG_ALPHA
386             printf("----> alphaq(qj=%u [j=%d], t=%u)=%.2E\n", qj,j,t+1, exp($alphaq(Q=>qj,T=>t+1)));
387             #endif
388             }
389             /*-- End Loop: q_(t+1)=qj : state_(t+1)=oq(qj,t+1)=j --*/
390              
391             #ifdef DEBUG_ALPHA
392             printf("\n\n");
393             #endif
394             }
395             /*-- End Loop: time t>0 --*/
396             '),
397              
398             Doc=>
399             ('Compute constrained forward probability (alphaq) matrix
400             for input $o given model parameters
401             @theta = ($a, $b, $pi, $omega),
402             considering only the initial
403             non-negative state indices in $oq(:,t) for each observation $o(t).
404              
405             Output (pseudocode) for all 0<=qi
406              
407             $alphaq(qi,t) = log P( $o(0:t), q(t)==$oq(qi,t) | @theta )
408              
409             Note that the final-state probability vector $omega() is neither
410             passed to this function nor used in the computation, but
411             can be used to compute the final sequence probability for $o as:
412              
413             log P( $o | @theta ) = logsumover( $alphaq(:,t-1) + $omega($oqTi) )
414              
415             where:
416              
417             $oqTi = $oq(:,t-1)->where($oq(:,t-1)>=0)
418              
419             '),
420              
421             );
422              
423             pp_addpm('*hmmalphaq = \&hmmfwq;');
424             pp_add_exported('','hmmalphaq');
425              
426              
427             ##------------------------------------------------------
428             ## Backward probability: hmmbw(A,B,omega, O, [o]beta)
429             pp_def
430             #@l=
431             ('hmmbw',
432             GenericTypes => $FLOAT_TYPES,
433             Pars => 'a(N,N); b(N,M); omega(N); o(T); [o]beta(N,T)',
434             Code =>
435             ('
436             int i,j,t = $SIZE(T)-1;
437              
438             /*-- Initialize: time t==T --*/
439             loop(N) %{ $beta(T=>t) = $omega(); %}
440              
441             /*-- Loop: time t < T --*/
442             for (t--; t >= 0; t--) {
443             int o_tp1 = $o(T=>t+1);
444              
445             /*-- Loop: t
446             for (i=0; i<$SIZE(N); i++) {
447             $GENERIC(beta) beta_i_t = ($GENERIC(beta))LOG_ZERO;
448              
449             /*-- Loop: t
450             for (j=0; j<$SIZE(N); j++) {
451             beta_i_t = logadd( $a(N0=>i,N1=>j) + $b(N=>j,M=>o_tp1) + $beta(N=>j,T=>t+1) , beta_i_t );
452              
453             #ifdef DEBUG_BETA
454             printf("i=%u,j=%u,t=%u,o=%d: a(i=%u,j=%u)=%.2e b(j=%u,o=%u)=%.2e beta(j=%u,t+1=%u)=%.2e prod=%.2e sum=%.2e\n",
455             i,j,t,o_tp1,
456             i,j, exp($a(N0=>i,N1=>j)),
457             j,o_t, exp($b(N=>j,M=>o_t)),
458             j,t+1, exp($beta(N=>j,T=>t+1)),
459             exp($a(N0=>i,N1=>j)+$b(N=>j,M=>o_t)+$beta(N=>j,T=>t+1)), exp(beta_i_t));
460             #endif
461             }
462              
463             /*-- t
464             $beta(N=>i,T=>t) = beta_i_t;
465              
466             #ifdef DEBUG_BETA
467             printf("\n");
468             #endif
469             }
470             #ifdef DEBUG_BETA
471             printf("\n\n");
472             #endif
473             }
474             '
475             ),
476              
477             Doc=>
478             ('Compute backward probability (beta) matrix
479             for input $o given model parameters
480             @theta = ($a, $b, $pi, $omega).
481              
482             Output (pseudocode) for all 0<=i
483              
484             $beta(i,t) = log P( $o(t+1:T-1) | q(t)==i, @theta )
485              
486             Note that the initial-state probability vector $pi() is neither
487             passed to this function nor used in the computation, but
488             can be used to compute the final sequence probability for $o as:
489              
490             log P( $o | @theta ) = logsumover( $pi() + $b(:,$o(0)) + $beta(:,0) )
491              
492             '),
493              
494             );
495              
496              
497             pp_addpm('*hmmbeta = \&hmmbw;');
498             pp_add_exported('','hmmbeta');
499              
500              
501             ##------------------------------------------------------
502             ## Backward probability (constrained): hmmbwq(A,B,omega, O,Q, [o]beta)
503             pp_def
504             #@l=
505             ('hmmbwq',
506             GenericTypes => $FLOAT_TYPES,
507             Pars => 'a(N,N); b(N,M); omega(N); o(T); oq(Q,T); [o]betaq(Q,T)',
508             Code =>
509             ('
510             int i,j,t = $SIZE(T)-1;
511             int qi, qj;
512              
513             /*-- Initialize: time t==T --*/
514             for (qi=0; qi < $SIZE(Q); qi++) {
515             i = $oq(Q=>qi,T=>t);
516             $betaq(Q=>qi,T=>t) = (i>=0 ? $omega(N=>i) : ($GENERIC(betaq))LOG_ZERO);
517             }
518              
519             /*-- Loop: time t < T --*/
520             for (t--; t >= 0; t--) {
521             int o_tp1 = $o(T=>t+1);
522              
523             /*-- Loop: t
524             for (qi=0; qi<$SIZE(Q); qi++) {
525             $GENERIC(betaq) beta_i_t = ($GENERIC(betaq))LOG_ZERO;
526             i = $oq(Q=>qi,T=>t);
527              
528             /*-- Loop: t
529             for (qj=0; i>=0 && qj<$SIZE(Q); qj++) {
530             j = $oq(Q=>qj,T=>t+1);
531             if (j < 0) break;
532              
533             beta_i_t = logadd( $a(N0=>i,N1=>j) + $b(N=>j,M=>o_tp1) + $betaq(Q=>qj,T=>t+1) , beta_i_t );
534             }
535              
536             /*-- t
537             $betaq(Q=>qi,T=>t) = beta_i_t;
538             }
539             /*-- End Loop: t
540             }
541             /*-- End Loop: time t < T --*/
542             '
543             ),
544              
545             Doc=>
546             ('Compute constrained backward probability (betaq) matrix
547             for input $o given model parameters
548             @theta = ($a, $b, $pi, $omega),
549             considering only the initial non-negative state indices in $oq(:,t) for
550             each observation $o(t).
551              
552             Output (pseudocode) for all 0<=qi
553              
554             $betaq(qi,t) = log P( $o(t+1:T-1) | q(t)==$oq(qi,t), @theta )
555              
556             Note that the initial-state probability vector $pi() is neither
557             passed to this function nor used in the computation, but
558             can be used to compute the final sequence probability for $o as:
559              
560             log P( $o | @theta ) = logsumover( $betaq(:,0) + $pi($oq0i) + $b($oq0i,$o(0)) )
561              
562             where:
563              
564             $oq0i = $oq(:,0)->where( $oq(:,0) >= 0 );
565              
566             '),
567              
568             );
569              
570              
571             pp_addpm('*hmmbetaq = \&hmmbwq;');
572             pp_add_exported('','hmmbetaq');
573              
574              
575             ##======================================================================
576             ## Parameter Estimation
577              
578             ##------------------------------------------------------
579             ## Parameter Estimation: Initialize
580             pp_addpm(<<'EOPM');
581             =pod
582              
583             =head1 Parameter Estimation
584              
585             =head2 hmmexpect0
586              
587             =for sig
588              
589             Signature: (a(N,N); b(N,M); pi(N); omega(N); [o]ea(N,N); [o]eb(N,M); [o]epi(N); [o]eomega(N))
590              
591             Initializes expectation matrices $ea(), $eb() and $epi() to logzero().
592             For use with hmmexpect().
593              
594             =cut
595              
596             sub hmmexpect0 {
597 1     1 1 261623 my ($a,$b,$pi,$omega, $ea,$eb,$epi,$eomega) = @_;
598              
599 1 50       12 $ea = zeroes($a->type, $a->dims) if (!defined($ea));
600 1 50       47 $eb = zeroes($b->type, $b->dims) if (!defined($eb));
601 1 50       30 $epi = zeroes($pi->type, $pi->dims) if (!defined($epi));
602 1 50       27 $eomega = zeroes($omega->type, $omega->dims) if (!defined($eomega));
603              
604 1         34 $ea .= PDL::logzero();
605 1         28 $eb .= PDL::logzero();
606 1         23 $epi .= PDL::logzero();
607 1         34 $eomega .= PDL::logzero();
608              
609 1         75 return ($ea,$eb,$epi,$eomega);
610             }
611              
612              
613             EOPM
614              
615             pp_add_exported('', 'hmmexpect0');
616              
617              
618              
619             ##------------------------------------------------------
620             ## Parameter Estimation: Expect
621             pp_def
622             ('hmmexpect',
623             GenericTypes => $FLOAT_TYPES,
624             Pars => join(" ",
625             qw(a(N,N);
626             b(N,M);
627             pi(N);
628             omega(N);
629             o(T);
630             alpha(N,T);
631             beta(N,T);),
632             qw([o]ea(N,N);
633             [o]eb(N,M);
634             [o]epi(N);
635             [o]eomega(N);)),
636             Code =>
637             ('
638             int i,j,t;
639             int o_tp1, o_t;
640             double p_o = LOG_ZERO;
641             double gamma_it;
642             double xi_ijt;
643              
644             /*-- Initialize: t==(T-1): P(o|@theta) --*/
645             t = $SIZE(T)-1;
646             loop (N) %{ p_o = logadd(p_o, $omega() + $alpha(T=>t)); %}
647              
648              
649             /*-- Initialize: t==(T-1): Iterate: state_t==i: get gamma(i,t) --*/
650             o_t = $o(T=>t);
651             for (i=0; i<$SIZE(N); i++) {
652             gamma_it = $alpha(N=>i,T=>t) + $beta(N=>i,T=>t) - p_o;
653             $eb(N=>i,M=>o_t) = logadd($eb(N=>i,M=>o_t), gamma_it);
654             $eomega(N=>i) = logadd($eomega(N=>i) , gamma_it);
655             }
656              
657             /*-- Main: Iterate: T-1 > t >= 0 --*/
658             for (t--; t>=0; t--) {
659             o_tp1 = o_t;
660             o_t = $o(T=>t);
661              
662             /*-- Main: Iterate: state_t == i --*/
663             for (i=0; i<$SIZE(N); i++) {
664             gamma_it = $alpha(N=>i,T=>t) + $beta(N=>i,T=>t) - p_o;
665              
666             /*-- Main: Iterate: state_(t+1) == j --*/
667             for (j=0; j<$SIZE(N); j++) {
668             xi_ijt = $alpha(N=>i,T=>t) + $a(N0=>i,N1=>j) + $b(N=>j,M=>o_tp1) + $beta(N=>j,T=>t+1) - p_o;
669              
670             $ea(N0=>i,N1=>j) = logadd(xi_ijt, $ea(N0=>i,N1=>j));
671             }
672              
673             /*-- Main: Update: pi --*/
674             if (t==0) $epi(N=>i) = logadd(gamma_it, $epi(N=>i));
675              
676             /*-- Main: Update: b --*/
677             $eb(N=>i,M=>o_t) = logadd(gamma_it, $eb(N=>i,M=>o_t));
678             }
679             }
680             '),
681              
682             Doc =>
683             ('Compute partial Baum-Welch re-estimation of the model @theta = ($a, $b, $pi, $omega)
684             for the observation sequence $o() with forward- and backward-probability
685             matrices $alpha(), $beta(). Result is recorded as log pseudo-frequencies
686             in the expectation matrices $ea(), $eb(), $epi(), and $eomega(), which are required parameters,
687             and should have been initialized (e.g. by L()) before calling this function.
688              
689             Can safely be called sequentially for incremental reestimation.
690             '),
691             );
692              
693              
694             ##------------------------------------------------------
695             ## Parameter Estimation: Expect (constrained)
696             pp_def
697             ('hmmexpectq',
698             GenericTypes => $FLOAT_TYPES,
699             Pars => join(" ",
700             qw(a(N,N);
701             b(N,M);
702             pi(N);
703             omega(N);),
704             qw(o(T);
705             oq(Q,T);),
706             qw(alphaq(Q,T);
707             betaq(Q,T);),
708             qw([o]ea(N,N);
709             [o]eb(N,M);
710             [o]epi(N);
711             [o]eomega(N);)),
712             Code =>
713             ('
714             int i,j,t, qi,qj;
715             int o_tp1, o_t;
716             double p_o = LOG_ZERO;
717             double gamma_it;
718             double xi_ijt;
719              
720             /*-- Initialize: t==(T-1): P(o|@theta) --*/
721             t = $SIZE(T)-1;
722             for (qi=0; qi < $SIZE(Q); qi++) {
723             i = $oq(Q=>qi,T=>t);
724             if (i < 0) break;
725              
726             p_o = logadd(p_o, $omega(N=>i) + $alphaq(Q=>qi,T=>t));
727             }
728              
729             /*-- Initialize: t==(T-1): Iterate: q_(t)=qi: state_t=oq(qi,t)=i: get gamma(i,t) --*/
730             o_t = $o(T=>t);
731             for (qi=0; qi < $SIZE(Q); qi++) {
732             i = $oq(Q=>qi,T=>t);
733             if (i < 0) break;
734             gamma_it = $alphaq(Q=>qi,T=>t) + $betaq(Q=>qi,T=>t) - p_o;
735             $eb(N=>i,M=>o_t) = logadd($eb(N=>i,M=>o_t), gamma_it);
736             $eomega(N=>i) = logadd($eomega(N=>i) , gamma_it);
737             }
738              
739             /*-- Loop: T-1 > t >= 0 --*/
740             for (t--; t>=0; t--) {
741             o_tp1 = o_t;
742             o_t = $o(T=>t);
743              
744             /*-- Loop: q_(t)=qi: state_(t)=oq(qi,t)=i --*/
745             for (qi=0; qi<$SIZE(Q); qi++) {
746             i = $oq(Q=>qi,T=>t);
747             if (i < 0) break;
748             gamma_it = $alphaq(Q=>qi,T=>t) + $betaq(Q=>qi,T=>t) - p_o;
749              
750             /*-- Loop: q_(t+1)=qj: state_(t+1)=oq(qj,t+1)=j --*/
751             for (qj=0; qj<$SIZE(Q); qj++) {
752             j = $oq(Q=>qj,T=>t+1);
753             if (j < 0) break;
754              
755             xi_ijt = $alphaq(Q=>qi,T=>t) + $a(N0=>i,N1=>j) + $b(N=>j,M=>o_tp1) + $betaq(Q=>qj,T=>t+1) - p_o;
756              
757             $ea(N0=>i,N1=>j) = logadd(xi_ijt, $ea(N0=>i,N1=>j));
758             }
759              
760             /*-- Update: pi --*/
761             if (t==0) $epi(N=>i) = logadd(gamma_it, $epi(N=>i));
762              
763             /*-- Update: b --*/
764             $eb(N=>i,M=>o_t) = logadd(gamma_it, $eb(N=>i,M=>o_t));
765             }
766             /*-- End Loop: q_(t)=qi: state_(t)=oq(qi,t)=i --*/
767             }
768             /*-- End Loop: T-1 > t >= 0 --*/
769             '),
770              
771             Doc =>
772             ('Compute constrained partial Baum-Welch re-estimation of the model @theta = ($a, $b, $pi, $omega)
773             for the observation sequence $o(),
774             with constrained forward- and backward-probability
775             matrices $alphaq(), $betaq(),
776             considering only the initial non-negative state
777             indices in $oq(:,t) for observation $o(t).
778             Result is recorded as log pseudo-frequencies
779             in the expectation matrices $ea(), $eb(), $epi(), and $eomega(), which are required parameters,
780             and should have been initialized (e.g. by L()) before calling this function.
781              
782             Can safely be called sequentially for incremental reestimation.
783             '),
784             );
785              
786              
787             ##------------------------------------------------------
788             ## Parameter Estimation: Maximization
789             pp_addpm(<<'EOPM');
790             =pod
791              
792             =head2 hmmmaximize
793              
794             =for sig
795              
796             Signature: (Ea(N,N); Eb(N,M); Epi(N); Eomega(N); [o]ahat(N,N); [o]bhat(N,M); [o]pihat(N); [o]omegahat(N));
797              
798             Maximizes expectation values from $Ea(), $Eb(), $Epi(), and $Eomega()
799             to log-probability matrices $ahat(), $bhat(), $pihat(), and $omegahat().
800             Can also be used to compile a maximum-likelihood model
801             from log-frequency matrices.
802              
803             =cut
804              
805             sub hmmmaximize {
806 1     1 1 772 my ($ea,$eb,$epi,$eomega, $ahat,$bhat,$pihat,$omegahat) = @_;
807              
808 1 50       9 $ahat = zeroes($ea->type, $ea->dims) if (!defined($ahat));
809 1 50       41 $bhat = zeroes($eb->type, $eb->dims) if (!defined($bhat));
810 1 50       28 $pihat = zeroes($epi->type, $epi->dims) if (!defined($pihat));
811 1 50       29 $omegahat = zeroes($eomega->type, $eomega->dims) if (!defined($omegahat));
812              
813 1         73 my $easumover = $ea->xchg(0,1)->logsumover->inplace->logadd($eomega);
814              
815 1         33 $ahat .= $ea - $easumover;
816 1         83 $bhat .= $eb - $eb->xchg(0,1)->logsumover;
817 1         45 $pihat .= $epi - $epi->logsumover;
818 1         26 $omegahat .= $eomega - $easumover;
819              
820 1         27 return ($ahat,$bhat,$pihat,$omegahat);
821             }
822              
823             EOPM
824              
825             pp_add_exported('', 'hmmmaximize');
826              
827              
828             ##======================================================================
829             ## Sequence Analysis
830             pp_addpm(<<'EOPM');
831             =pod
832              
833             =head1 Sequence Analysis
834              
835             =cut
836             EOPM
837              
838              
839             ##--------------------------------------------------------------
840             ## Sequence Analysis: Viterbi
841             pp_def
842             ('hmmviterbi',
843             Pars => join(" ",
844             qw(a(N,N);
845             b(N,M);
846             pi(N);),
847             #qw(omega(N);),
848             qw(o(T);
849             [o]delta(N,T);),
850             'int [o]psi(N,T)'),
851             GenericTypes => $FLOAT_TYPES,
852             Code =>
853             ('
854             int i,j, t, o_t;
855             double delta_jt, delta_tmp;
856             int psi_jt;
857              
858             /*-- Initialize: t==0: Loop: state_0==N --*/
859             o_t = $o(T=>0);
860             loop (N) %{
861             $delta(T=>0) = $pi() + $b(M=>o_t);
862             $psi (T=>0) = 0;
863             #ifdef DEBUG_VITERBI
864             printf("t=0,j=%d,o_t=%d: delta(t=0,j=%d)=%.2e psi(t=0,j=%d)=%.0g b(j=%d,o=%d)=%.2e\n",
865             N,o_t,
866             N, exp($delta(T=>0)),
867             N, $psi(T=>0),
868             N,o_t, exp($b(M=>o_t)));
869             #endif
870             %}
871              
872             #ifdef DEBUG_VITERBI
873             printf("\n");
874             #endif
875              
876             /*-- Main: t>0: Loop: time==t --*/
877             for (t=1; t<$SIZE(T); t++) {
878             o_t = $o(T=>t);
879              
880             /*-- Main: t>0: Loop: state_t==j --*/
881             for (j=0; j<$SIZE(N); j++) {
882             psi_jt = 0;
883             delta_jt = $delta(N=>0,T=>t-1) + $a(N0=>0,N1=>j);
884              
885             /*-- Main: t>0: Loop: state_(t-1)==i --*/
886             for (i=1; i<$SIZE(N); i++) {
887             delta_tmp = $delta(N=>i,T=>t-1) + $a(N0=>i,N1=>j);
888              
889             if (delta_tmp > delta_jt) {
890             delta_jt = delta_tmp;
891             psi_jt = i;
892             #ifdef DEBUG_VITERBI
893             printf("+");
894             #endif
895             }
896              
897             #ifdef DEBUG_VITERBI
898             printf("t=%d,i=%d,j=%d,o_t=%d: deltaX(i=%d,t=%d)=%.2e psi(j=%d,t=%d)=%.0g delta(j=%d,t=%d)=%.2e\n",
899             t,i,j,o_t,
900             i,t, exp(delta_tmp),
901             j,t, psi_jt,
902             j,t, exp(delta_jt));
903             #endif
904             }
905              
906             /*-- Main: t>0: Store data for state,time=(j,t) --*/
907             $delta(N=>j,T=>t) = delta_jt + $b(N=>j,M=>o_t);
908             $psi (N=>j,T=>t) = psi_jt;
909              
910             #ifdef DEBUG_VITERBI
911             printf("\n---> t=%d: b(j=%d,o=%d)=%.2e delta(j=%d,t=%d)=%.2e psi(j=%d,t=%d)=%.0g\n\n",
912             t, j,o_t, exp($b(N=>j,M=>o_t)),
913             j,t, exp($delta(N=>j,T=>t)),
914             j,t, $psi(N=>j,T=>t));
915             #endif
916             }
917             #ifdef DEBUG_VITERBI
918             printf("\n");
919             #endif
920             }
921             '),
922             Doc =>
923             ('Computes Viterbi algorithm trellises $delta() and $psi() for the
924             observation sequence $o() given the model parameters @theta = ($a,$b,$pi,$omega).
925              
926             Outputs:
927              
928             Probability matrix $delta(): log probability of best path to state $j at time $t:
929              
930             $delta(j,t) = max_{q(0:t)} log P( $o(0:t), q(0:t-1), $q(t)==j | @theta )
931              
932             Path backtrace matrix $psi(): best predecessor for state $j at time $t:
933              
934             $psi(j,t) = arg_{q(t-1)} max_{q(0:t)} P( $o(0:t), q(0:t-1), $q(t)==j | @theta )
935              
936             Note that if you are using termination probabilities $omega(),
937             then in order to find the most likely final state, you need to
938             compute the contribution of $omega() yourself, which is easy
939             to do:
940              
941             $best_final_q = maximum_ind($delta->slice(",-1") + $omega);
942              
943             '),
944             );
945              
946              
947             ##--------------------------------------------------------------
948             ## Sequence Analysis: Viterbi (constrained)
949             pp_def
950             ('hmmviterbiq',
951             GenericTypes => $FLOAT_TYPES,
952             Pars => join(" ",
953             qw(a(N,N);
954             b(N,M);
955             pi(N);),
956             qw(o(T);
957             oq(Q,T);),
958             '[o]deltaq(Q,T);',
959             'indx [o]psiq(Q,T)',
960             ),
961             Code =>
962             ('
963             int qi,qj, i,j, t, o_t;
964             double deltaq_jt, deltaq_tmp;
965             int psiq_jt;
966              
967             /*-- Initialize: t=0: Loop: q_(0)=qi: state_(0)=oq(qi,0)=i --*/
968             o_t = $o(T=>0);
969             for (qi=0; qi<$SIZE(Q); qi++) {
970             i = $oq(Q=>qi,T=>0);
971             $psiq(Q=>qi,T=>0) = 0;
972             $deltaq(Q=>qi,T=>0) = (i>=0 ? ($pi(N=>i)+$b(N=>i,M=>o_t)) : ($GENERIC(deltaq))LOG_ZERO);
973             }
974              
975             /*-- Loop: t>0: Loop: time==t --*/
976             for (t=1; t<$SIZE(T); t++) {
977             o_t = $o(T=>t);
978              
979             /*-- Loop: t>0: q_(t)=qj : state_(t)=oq(qj,t)=j --*/
980             for (qj=0; qj<$SIZE(Q); qj++) {
981             j = $oq(Q=>qj,T=>t);
982             i = $oq(Q=>0, T=>t-1);
983             psiq_jt = 0;
984              
985             if (j >= 0 && i >=0) {
986             deltaq_jt = $deltaq(Q=>0,T=>t-1) + $a(N0=>i,N1=>j);
987             } else {
988             deltaq_jt = $deltaq(Q=>0,T=>t-1) + LOG_ZERO;
989             }
990              
991             /*-- Loop: t>0: q_(t-1)=qi : state_(t-1)=oq(qi,t)=i --*/
992             for (qi=1; qi<$SIZE(Q); qi++) {
993             i = $oq(Q=>qi,T=>t-1);
994             if (j < 0 || i < 0) break;
995              
996             deltaq_tmp = $deltaq(Q=>qi,T=>t-1) + $a(N0=>i,N1=>j);
997              
998             if (deltaq_tmp > deltaq_jt) {
999             deltaq_jt = deltaq_tmp;
1000             psiq_jt = qi;
1001             }
1002              
1003             }
1004             /*-- End Loop: t>0: q_(t-1)=qi : state_(t-1)=oq(qi,t)=i --*/
1005              
1006             /*-- Main: t>0: Store data for stateIndex,time=(qj,t) --*/
1007             $deltaq(Q=>qj,T=>t) = deltaq_jt + (j>=0 ? $b(N=>j,M=>o_t) : LOG_ZERO);
1008             $psiq (Q=>qj,T=>t) = psiq_jt;
1009              
1010             }
1011             /*-- Loop: t>0: q_(t)=qj : state_(t)=oq(qj,t)=j --*/
1012              
1013             }
1014             /*-- End Loop: t>0: Loop: time==t --*/
1015             '
1016             ),
1017             Doc =>
1018             ('Computes constrained Viterbi algorithm trellises $deltaq() and $psiq() for the
1019             observation sequence $o() given the model parameters @theta = ($a,$b,$pi,$omega),
1020             considering only the initial non-negative state indices $oq(:,t) for each
1021             observarion $o(t).
1022              
1023             Outputs:
1024              
1025             Constrained probability matrix $deltaq(): log probability of best path to state $oq(j,t) at time $t:
1026              
1027             $deltaq(j,t) = max_{j(0:t)} log P( $o(0:t), q(0:t-1)==$oq(:,j(0:t-1)), q(t)==$oq(j,t) | @theta )
1028              
1029             Constrained path backtrace matrix $psiq(): best predecessor index for state $oq(j,t) at time $t:
1030              
1031             $psiq(j,t) = arg_{j(t-1)} max_{j(0:t)} P( $o(0:t), q(0:t-1)=$oq(:,j(0:t-1)), q(t)==$oq(j,t) | @theta )
1032              
1033             Note that if you are using termination probabilities $omega(),
1034             then in order to find the most likely final state, you need to
1035             compute the contribution of $omega() yourself, which is quite easy
1036             to do:
1037              
1038             $best_final_j = maximum_ind($deltaq->slice(",-1") + $omega->index($oq->slice(",(-1)")))
1039              
1040             '),
1041             );
1042              
1043              
1044             ##--------------------------------------------------------------
1045             ## Sequence Analysis: Backtrace
1046             pp_def
1047             ('hmmpath',
1048             Pars => q(psi(N,T); indx qfinal(); indx [o]path(T)),
1049             GenericTypes => $FLOAT_TYPES,
1050             Code =>
1051             ('
1052             /*-- Initialize: t==T-1: state_(t)==final() --*/
1053             int t = $SIZE(T)-1;
1054             $path(T=>t) = $qfinal();
1055              
1056             /*-- Main: T-1 > t >= 0: Loop: time==t --*/
1057             for (t--; t>=0; t--) {
1058             int q_tp1 = $path(T=>t+1);
1059             $path(T=>t) = $psi (T=>t+1,N=>q_tp1);
1060             }
1061             '),
1062             Doc =>
1063             ('Computes best-path backtrace $path() for the final state $qfinal()
1064             from completed Viterbi trellis $psi().
1065              
1066             Outputs:
1067              
1068             Path backtrace $path(): state (in best sequence) at time $t:
1069              
1070             $path(t) = arg_{q(t)} max_{q(0:T-1)} log P( $o(), q(0:T-2), $q(T-1)==$qfinal() | @theta )
1071              
1072             This even threads over multiple final states, if specified,
1073             so you can align paths to their final states just by calling:
1074              
1075             $bestpaths = hmmpath($psi, sequence($N));
1076              
1077             Note that $path(T-1) == $qfinal(): yes, this is redundant,
1078             but also tends to be quite convenient.
1079              
1080             '),
1081             );
1082              
1083             ##--------------------------------------------------------------
1084             ## Sequence Analysis: Backtrace (constrained)
1085             pp_def
1086             ('hmmpathq',
1087             Pars => q(indx oq(Q,T); psiq(Q,T); indx qfinalq(); indx [o]path(T)),
1088             GenericTypes => $FLOAT_TYPES,
1089             Code =>
1090             ('
1091             /*-- Initialize: t==T-1: state_(t)==final() --*/
1092             int t = $SIZE(T)-1;
1093             $path(T=>t) = $qfinalq();
1094              
1095             /*-- Get index backtrace --*/
1096             for (t--; t>=0; t--) {
1097             int qi_tp1 = $path(T=>t+1);
1098             $path(T=>t) = $psiq(T=>t+1,Q=>qi_tp1);
1099             }
1100              
1101             /*-- Convert indices to state ids --*/
1102             loop (T) %{
1103             int qi = $path();
1104             $path() = $oq(Q=>qi);
1105             %}
1106             '),
1107             Doc =>
1108             ('Computes constrained best-path backtrace $path() for the final state index $qfinalq()
1109             from completed constrained Viterbi trellis $psiq().
1110              
1111             Outputs:
1112              
1113             Path backtrace $path(): state (in best sequence) at time $t:
1114              
1115             $path(t) = arg_{q(t)} max_{q(0:T-1)} log P( $o(), q(0:T-2), $q(T-1)==$oq($qfinalq(),T-1) | @theta )
1116              
1117             This is really just a convenience method for dealing with constrained
1118             lookup -- the same thing can be accomplished using hmmpath() and
1119             some PDL index magic.
1120              
1121             '),
1122             );
1123              
1124              
1125              
1126             ##======================================================================
1127             ## Footer Administrivia
1128             ##======================================================================
1129              
1130             ##------------------------------------------------------
1131             ## pm additions
1132             pp_addpm(<<'EOPM');
1133              
1134              
1135             ##---------------------------------------------------------------------
1136             =pod
1137              
1138             =head1 COMMON PARAMETERS
1139              
1140             HMMs are specified by parameters $a(N,N), $b(N,M), $pi(N), and $omega(N);
1141             input sequences are represented as vectors $o(T) of integer values in the range [0..M-1],
1142             where the following notational conventions are used:
1143              
1144             =over 4
1145              
1146              
1147             =item States:
1148              
1149             The model has $N states, denoted $q,
1150             0 <= $q < $N.
1151              
1152              
1153             =item Alphabet:
1154              
1155             The input- (aka "observation-") alphabet of the model has $M elements,
1156             denoted $o(t), 0 <= $o(t) < $M.
1157              
1158              
1159             =item Time indices:
1160              
1161             Time indices are denoted $t,
1162             1 <= $t < $T.
1163              
1164              
1165             =item Input Sequences:
1166              
1167             Input- (aka "observation-") sequences are represented as vectors of
1168             of length $T whose component values are in the range [0..M-1],
1169             i.e. alphabet indices.
1170              
1171              
1172              
1173             =item Initial Probabilities:
1174              
1175             The vector $pi(N) gives the (log) initial state probability distribution:
1176              
1177             $pi(i) = log P( $q(0)==i )
1178              
1179              
1180              
1181             =item Final Probabilities:
1182              
1183             The vector $omega(N) gives the (log) final state probability distribution:
1184              
1185             $omega(i) = log P( $q($T)==i )
1186              
1187             This parameter is a nonstandard extension.
1188             You can simulate the behavior of more traditional definitions
1189             (such as that given in Rabiner (1989)) by setting:
1190              
1191             $omega = zeroes($N);
1192              
1193             wherever it is required.
1194              
1195              
1196              
1197             =item Arc Probabilities:
1198              
1199             The matrix $a(N,N) gives the (log) conditional state-transition probability distribution:
1200              
1201             $a(i,j) = log P( $q(t+1)==j | $q(t)==i )
1202              
1203              
1204              
1205             =item Emission Probabilities:
1206              
1207             The matrix $b(N,M) gives the (log) conditional symbol emission probability:
1208              
1209             $b(j,o) = log P( $o(t)==o | $q(t)==j )
1210              
1211              
1212              
1213             =back
1214              
1215             =cut
1216              
1217             ##---------------------------------------------------------------------
1218             =pod
1219              
1220             =head1 ACKNOWLEDGEMENTS
1221              
1222             Perl by Larry Wall.
1223              
1224             PDL by Karl Glazebrook, Tuomas J. Lukka, Christian Soeller, and others.
1225              
1226             Implementation based largely on the formulae in:
1227             L. E. Rabiner, "A tutorial on Hidden Markov Models and selected
1228             applications in speech recognition," Proceedings of the IEEE 77:2,
1229             Februrary, 1989, pages 257--286.
1230              
1231             =cut
1232              
1233             ##----------------------------------------------------------------------
1234             =pod
1235              
1236             =head1 KNOWN BUGS
1237              
1238             Probably many.
1239              
1240             =cut
1241              
1242              
1243             ##---------------------------------------------------------------------
1244             =pod
1245              
1246             =head1 AUTHOR
1247              
1248             Bryan Jurish Emoocow@cpan.orgE
1249              
1250             =head2 Copyright Policy
1251              
1252             Copyright (C) 2005-2023 by Bryan Jurish. All rights reserved.
1253              
1254             This package is free software, and entirely without warranty.
1255             You may redistribute it and/or modify it under the same terms
1256             as Perl itself.
1257              
1258             =head1 SEE ALSO
1259              
1260             perl(1), PDL(3perl).
1261              
1262             =cut
1263              
1264             EOPM
1265              
1266              
1267             # Always make sure that you finish your PP declarations with
1268             # pp_done
1269             pp_done();
1270             ##----------------------------------------------------------------------