File Coverage

lib/PDL/Ufunc.pd
Criterion Covered Total %
statement 26 26 100.0
branch 34 264 12.8
condition n/a
subroutine n/a
pod n/a
total 60 290 20.6


line stmt bran cond sub pod time code
1             use strict;
2             use warnings;
3             use PDL::Types qw(types ppdefs_complex ppdefs_all);
4              
5             my $T = [map $_->ppsym, grep $_->integer, types];
6             my $A = [ppdefs_all];
7             my $AF = [map $_->ppsym, grep !$_->integer, types];
8              
9             pp_addpm({At=>'Top'},<<'EOD');
10             use strict;
11             use warnings;
12              
13             =encoding utf8
14              
15             =head1 NAME
16              
17             PDL::Ufunc - primitive ufunc operations for pdl
18              
19             =head1 DESCRIPTION
20              
21             This module provides some primitive and useful functions defined
22             using PDL::PP based on functionality of what are sometimes called
23             I (for example NumPY and Mathematica talk about these).
24             It collects all the functions generally used to C or
25             C along a dimension. These all do their job across the
26             first dimension but by using the slicing functions you can do it
27             on any dimension.
28              
29             The L module provides an alternative interface
30             to many of the functions in this module.
31              
32             =head1 SYNOPSIS
33              
34             use PDL::Ufunc;
35              
36             =cut
37              
38             use PDL::Slices;
39             use Carp;
40             EOD
41              
42             # helper functions
43             sub projectdocs {
44             my $name = shift;
45             my $op = shift;
46             my $extras = shift;
47             <
48              
49             =for ref
50              
51             Project via $name to N-1 dimensions
52              
53             This function reduces the dimensionality of an ndarray
54             by one by taking the $name along the 1st dimension.
55              
56             By using L etc. it is possible to use
57             I dimension.
58              
59             $extras
60             EOD
61              
62             } # sub: projectdocs()
63              
64             sub cumuprojectdocs {
65             my $name = shift;
66             my $op = shift;
67             my $extras = shift;
68             <
69              
70             =for ref
71              
72             Cumulative $name
73              
74             This function calculates the cumulative $name
75             along the 1st dimension.
76              
77             By using L etc. it is possible to use
78             I dimension.
79              
80             The sum is started so that the first element in the cumulative $name
81             is the first element of the parameter.
82              
83             $extras
84             EOD
85              
86             } # sub: cumuprojectdocs()
87              
88             my %over =
89             (
90             sumover => { name => 'sum', op => '+=', init => 0, },
91             prodover => { name => 'product', op => '*=', init => 1, leftzero => 'tmp == 0' },
92             );
93              
94             foreach my $func ( sort keys %over ) {
95             my ($name, $op, $init, $leftzero) = @{$over{$func}}{qw(name op init leftzero)};
96             pp_def(
97             $_->[0].$func,
98             HandleBad => 1,
99             Pars => "a(n); $_->[1] [o]b();",
100             $_->[2] ? (GenericTypes=>$_->[2]) : (),
101             Code => <
102             \$GENERIC(b) tmp = $init;
103             PDL_IF_BAD(int flag = 0;,)
104             loop(n) %{
105             PDL_IF_BAD(if ( \$ISBAD(a()) ) continue; flag = 1;,)
106             tmp $op \$a();
107             @{[$leftzero ? "if ($leftzero) break;" : '']}
108             %}
109             PDL_IF_BAD(if ( !flag ) \$SETBAD(b()); else,)
110             \$b() = tmp;
111             EOF
112             Doc => projectdocs( $name, $_->[0].$func, $_->[3] ),
113             ) for (
114             ['', 'int+', $A, ''],
115             ['d', 'double', undef,
116             "Unlike L, the calculations are performed in double precision."
117             ],
118             );
119              
120             my $cfunc = "cumu${func}";
121             pp_def(
122             $_->[0].$cfunc,
123             HandleBad => 1,
124             Pars => "a(n); $_->[1] [o]b(n);",
125             $_->[2] ? (GenericTypes=>$_->[2]) : (),
126             Code => <
127             \$GENERIC(b) tmp = $init;
128             loop(n) %{
129             PDL_IF_BAD(if ( \$ISBAD(a()) ) { \$SETBAD(b()); continue; },)
130             tmp $op \$a();
131             \$b() = tmp;
132             %}
133             EOF
134             Doc => cumuprojectdocs( $name, $_->[0].$cfunc, $_->[3] ),
135             ) for (
136             ['', 'int+', $A, ''],
137             ['d', 'double', undef,
138             "Unlike L, the calculations are performed in double precision."
139             ],
140             );
141             } # foreach: my $func
142              
143             %over = (
144             firstnonzeroover => { txt => 'first non-zero value', alltypes => 1,
145             op => 'tmp = $a();', leftzero => 'tmp' },
146             zcover => { def=>'char tmp', txt => '== 0', init => 1, alltypes => 1,
147             op => 'tmp &= ($a() == 0);', leftzero => '!tmp' },
148             andover => { def=>'char tmp', txt => 'logical and', init => 1, alltypes => 1,
149             op => 'tmp &= ($a() != 0);', leftzero => '!tmp' },
150             orover => { def=>'char tmp', txt => 'logical or', alltypes => 1,
151             op => 'tmp |= ($a() != 0);', leftzero => 'tmp' },
152             xorover => { def=>'char tmp', txt => 'logical xor', alltypes => 1,
153             op => 'tmp ^= ($a() != 0);', leftzero => 0 },
154             bandover => { txt => 'bitwise and', init => '~0',
155             op => 'tmp &= $a();', leftzero => '!tmp' },
156             borover => { txt => 'bitwise or',
157             op => 'tmp |= $a() ;', leftzero => '!~tmp' },
158             bxorover => { txt => 'bitwise xor',
159             op => 'tmp ^= $a() ;', leftzero => 0 },
160             );
161             foreach my $func ( sort keys %over ) {
162             my ($def,$txt,$init,$op,$leftzero) = @{$over{$func}}{qw(def txt init op leftzero)};
163             $def ||= '$GENERIC() tmp';
164             $init //= '0';
165             pp_def(
166             $func,
167             HandleBad => 1,
168             GenericTypes => $over{$func}{alltypes} ? [ppdefs_all] : $T,
169             Pars => 'a(n); [o] b()',
170             Code => pp_line_numbers(__LINE__, <
171             $def = $init;
172             PDL_IF_BAD(int flag = 0;,)
173             loop(n) %{
174             PDL_IF_BAD(if ( \$ISBAD(a()) ) continue; flag = 1;,)
175             $op
176             if ( $leftzero ) break;
177             %}
178             PDL_IF_BAD(if (!flag) { \$SETBAD(b()); \$PDLSTATESETBAD(b); } else,)
179             \$b() = tmp;
180             EOF
181             Doc => projectdocs( $txt, $func,''),
182             BadDoc =>
183             'If C contains only bad data (and its bad flag is set),
184             C is set bad. Otherwise C will have its bad flag cleared,
185             as it will not contain any bad values.',
186             );
187             } # foreach: $func
188              
189             pp_def('numdiff',
190             Pars => 'x(t); [o]dx(t)',
191             Inplace => 1,
192             GenericTypes => [ppdefs_all],
193             Code => <<'EOF',
194             /* do it in reverse so inplace works */
195             loop (t=::-1) %{
196             $dx() = t ? $x() - $x(t=>t-1) : $x();
197             %}
198             EOF
199             Doc => <<'EOF',
200             =for ref
201              
202             Numerical differencing. DX(t) = X(t) - X(t-1), DX(0) = X(0).
203             Combined with C, can be used for backward differencing.
204              
205             Unlike L, output vector is same length.
206             Originally by Maggie J. Xiong.
207              
208             Compare to L, which acts as the converse of this.
209             See also L, L, L, L.
210             EOF
211             );
212              
213             pp_def('diffcentred',
214             HandleBad => 1,
215             Pars => 'a(n); [o]o(n)',
216             GenericTypes => [ppdefs_all],
217             Code => <<'EOF',
218             loop(n) %{
219             PDL_Indx left = n-1, right = n+1;
220             if (left < 0) left = $SIZE(n)-1;
221             if (right >= $SIZE(n)) right = 0;
222             PDL_IF_BAD(if ($ISBAD($a(n=>left)) || $ISBAD($a(n=>right))) {
223             $SETBAD(o()); continue;
224             },)
225             $o() = ($a(n=>right) - $a(n=>left))/2;
226             %}
227             EOF
228             Doc => <<'EOF',
229             =for ref
230              
231             Calculates centred differences along a vector's 0th dimension.
232             Always periodic on boundaries; currently to change this, you must
233             pad your data, and/or trim afterwards. This is so that when using
234             with L, the size of data stays the same and therefore
235             compatible if differentiated along different dimensions, e.g.
236             calculating "curl".
237              
238             By using L etc. it is possible to use I dimension.
239              
240             See also L, L, L, L.
241             EOF
242             BadDoc => <<'EOF',
243             A bad value at C means the affected output values at C,C
244             (if in boounds) are set bad.
245             EOF
246             );
247              
248             pp_add_exported('partial');
249             pp_addpm(<<'EOF');
250             =head2 partial
251              
252             =for ref
253              
254             Take a numerical partial derivative along a given dimension, either
255             forward, backward, or centred.
256              
257             See also L, L,
258             L, L,
259             and L, which are currently used to implement this.
260              
261             Can be used to implement divergence and curl calculations (adapted
262             from Luis Mochán's work at
263             https://sourceforge.net/p/pdl/mailman/message/58843767/):
264              
265             use v5.36;
266             use PDL;
267             sub curl ($f) {
268             my ($f0, $f1, $f2) = $f->using(0..2);
269             my $o = {dir=>'c'};
270             pdl(
271             $f2->partial(1,$o) - $f1->partial(2,$o),
272             $f0->partial(2,$o) - $f2->partial(0,$o),
273             $f1->partial(0,$o) - $f0->partial(1,$o),
274             )->mv(-1,0);
275             }
276             sub div ($f) {
277             my ($f0, $f1, $f2) = $f->using(0..2);
278             my $o = {dir=>'c'};
279             $f0->partial(0,$o) + $f1->partial(1,$o) + $f2->partial(2,$o);
280             }
281             sub trim3d ($f) { $f->slice(':,1:-2,1:-2,1:-2') } # adjust if change "dir"
282             my $z=zeroes(5,5,5);
283             my $v=pdl(-$z->yvals, $z->xvals, $z->zvals)->mv(-1,0);
284             say trim3d(curl($v));
285             say div($v);
286              
287             =for usage
288              
289             $pdl->partial(2); # along dim 2, centred
290             $pdl->partial(2, {d=>'c'}); # along dim 2, centred
291             $pdl->partial(2, {d=>'f'}); # along dim 2, forward
292             $pdl->partial(2, {d=>'b'}); # along dim 2, backward
293             $pdl->partial(2, {d=>'p'}); # along dim 2, piecewise cubic Hermite
294             $pdl->partial(2, {d=>'s'}); # along dim 2, cubic spline
295              
296             =cut
297              
298             my %dirtype2func = (
299             f => \&numdiff,
300             b => sub { $_[0]->slice('-1:0')->numdiff },
301             c => \&diffcentred,
302             p => sub {(PDL::Primitive::pchip_chim($_[0]->xvals, $_[0]))[0]},
303             s => sub {(PDL::Primitive::pchip_chsp([0,0], [0,0], $_[0]->xvals, $_[0]))[0]},
304             );
305             *partial = \&PDL::partial;
306             sub PDL::partial {
307             my ($f, $dim, $opts) = @_;
308             $opts ||= {};
309             my $difftype = $opts->{dir} || $opts->{d} || 'c';
310             my $func = $dirtype2func{$difftype} || barf "partial: unknown 'dir' option '$difftype', only know (@{[sort keys %dirtype2func]})";
311             $f = $f->mv($dim, 0) if $dim;
312             my $ret = $f->$func;
313             $dim ? $ret->mv(0, $dim) : $ret;
314             }
315             EOF
316              
317             pp_def('diff2',
318             HandleBad => 1,
319             Pars => 'a(n); [o]o(nminus1=CALC($SIZE(n) - 1))',
320             GenericTypes => [ppdefs_all],
321             Code => <<'EOF',
322             $GENERIC() lastval = PDL_IF_BAD($ISBAD($a(n=>0)) ? 0 :,) $a(n=>0);
323             loop(n=1) %{
324             PDL_IF_BAD(if ($ISBAD($a()))
325             $SETBAD(o(nminus1=>n-1));
326             else,)
327             $o(nminus1=>n-1) = $a() - lastval;
328             PDL_IF_BAD(if ($ISGOOD($a())),) lastval = $a();
329             %}
330             EOF
331             Doc => <<'EOF',
332             =for ref
333              
334             Numerically forward differentiates a vector along 0th dimension.
335              
336             By using L etc. it is possible to use I dimension.
337             Unlike L, output vector is one shorter.
338             Combined with C, can be used for backward differencing.
339              
340             See also L, L, L, L.
341              
342             =for example
343              
344             print pdl(q[3 4 2 3 2 3 1])->diff2;
345             # [1 -2 1 -1 1 -2]
346             EOF
347             BadDoc => <<'EOF',
348             On bad value, output value is set bad. On next good value, output value
349             is difference between that and last good value.
350             EOF
351             );
352              
353             # this would need a lot of work to support bad values
354             # plus it gives me a chance to check out HandleBad => 0 ;)
355             #
356             pp_def('intover',
357             HandleBad => 0,
358             Pars => 'a(n); float+ [o]b();',
359             Code =>
360             '/* Integration formulae from Press et al 2nd Ed S 4.1 */
361             switch ($SIZE(n)) {
362             case 1:
363             broadcastloop %{
364             $b() = 0.; /* not a(n=>0); as interval has zero width */
365             %}
366             break;
367             case 2:
368             broadcastloop %{
369             $b() = 0.5*($a(n=>0)+$a(n=>1));
370             %}
371             break;
372             case 3:
373             broadcastloop %{
374             $b() = ($a(n=>0)+4*$a(n=>1)+$a(n=>2))/3.;
375             %}
376             break;
377             case 4:
378             broadcastloop %{
379             $b() = ($a(n=>0)+$a(n=>3)+3.*($a(n=>1)+$a(n=>2)))*0.375;
380             %}
381             break;
382             case 5:
383             broadcastloop %{
384             $b() = (14.*($a(n=>0)+$a(n=>4))
385             +64.*($a(n=>1)+$a(n=>3))
386             +24.*$a(n=>2))/45.;
387             %}
388             break;
389             default:
390             broadcastloop %{
391             $GENERIC(b) tmp = 0;
392             loop (n=3:-3) %{ tmp += $a(); %}
393             loop (n=-3:-2) %{ tmp += (23./24.)*($a(n=>2)+$a()); %}
394             loop (n=-2:-1) %{ tmp += (7./6.) *($a(n=>1)+$a()); %}
395             loop (n=-1:) %{ tmp += (3./8.) *($a(n=>0)+$a()); %}
396             $b() = tmp;
397             %}
398             }
399             ',
400             Doc => projectdocs('integral','intover', <<'EOF'),
401             Notes:
402              
403             C uses a point spacing of one (i.e., delta-h==1). You will
404             need to scale the result to correct for the true point delta.
405              
406             For C 3>, these are all C (like Simpson's rule), but are
407             integrals between the end points assuming the pdl gives values just at
408             these centres: for such `functions', sumover is correct to C, but
409             is the natural (and correct) choice for binned data, of course.
410             EOF
411             ); # intover
412              
413             sub synonym {
414             my ($name, $synonym) = @_;
415             pp_add_exported('', $synonym);
416             pp_addpm(
417             "=head2 $synonym\n\n=for ref\n\nSynonym for L.\n\n=cut\n
418             *PDL::$synonym = *$synonym = \\&PDL::$name;"
419             );
420             }
421              
422             sub make_average {
423             my ($prefix, $outpar_type, $extra) = @_;
424             pp_def("${prefix}average",
425             HandleBad => 1,
426             Pars => "a(n); $outpar_type [o]b();",
427             Code => <<'EOF',
428             $GENERIC(b) tmp = 0;
429             PDL_Indx cnt = 0;
430             loop(n) %{
431             PDL_IF_BAD(if ( $ISBAD(a()) ) continue;,)
432             cnt++;
433             tmp += $a();
434             %}
435             if ( !cnt ) {
436             PDL_IF_BAD($SETBAD(b()), $b() = PDL_IF_GENTYPE_INTEGER(0,NAN));
437             } else
438             $b() = tmp / cnt;
439             EOF
440             Doc => projectdocs( 'average', "${prefix}average", $extra||'' ),
441             );
442             synonym(map "$prefix$_", qw(average avgover));
443             }
444              
445             make_average('', 'int+');
446             make_average('c', 'cdouble',
447             "Unlike L, the calculation is performed in complex double
448             precision."
449             );
450             make_average('d', 'double',
451             "Unlike L, the calculation is performed in double
452             precision."
453             );
454              
455             for my $which (
456             [qw(minimum < minover)],
457             [qw(maximum > maxover)],
458             ) {
459             my ($name, $op, $synonym) = @$which;
460              
461             pp_def($name,
462             HandleBad => 1,
463             Pars => 'a(n); [o]c();',
464             Code =>
465             '$GENERIC() cur = 0;
466             int flag = 0;
467             loop(n) %{
468             PDL_IF_BAD(if ($ISBAD(a())) continue;,)
469             if ( flag && !($a() '.$op.' cur) && !PDL_ISNAN_$PPSYM()(cur) ) continue;
470             cur = $a(); flag = 1;
471             %}
472             if ( flag ) { $c() = cur; }
473             else { $SETBAD(c()); $PDLSTATESETBAD(c); }',
474             Doc => projectdocs($name,$name,''),
475             BadDoc =>
476             'Output is set bad if no elements of the input are non-bad,
477             otherwise the bad flag is cleared for the output ndarray.
478              
479             Note that C are considered to be valid values and will "win" over non-C;
480             see L and L
481             for ways of masking NaNs.
482             ',
483             );
484             synonym($name, $synonym);
485              
486             pp_def("${name}_ind",
487             HandleBad => 1,
488             Pars => 'a(n); indx [o] c();',
489             Code =>
490             '$GENERIC() cur = 0;
491             PDL_Indx curind = -1;
492             loop(n) %{
493             PDL_IF_BAD(if ($ISBAD(a())) continue;,)
494             if (curind != -1 && !($a() '.$op.' cur) && !PDL_ISNAN_$PPSYM()(cur)) continue;
495             cur = $a(); curind = n;
496             %}
497             if ( curind != -1 ) { $c() = curind; }
498             else { $SETBAD(c()); $PDLSTATESETBAD(c); }',
499             Doc => "Like $name but returns the first matching index rather than the value",
500             BadDoc =>
501             'Output is set bad if no elements of the input are non-bad,
502             otherwise the bad flag is cleared for the output ndarray.
503              
504             Note that C are considered to be valid values and will "win" over non-C;
505             see L and L
506             for ways of masking NaNs.
507             ',
508             );
509             synonym("${name}_ind", "${synonym}_ind");
510              
511             pp_def("${name}_n_ind",
512             HandleBad => 1,
513             Pars => 'a(n); indx [o]c(m);',
514             OtherPars => 'PDL_Indx m_size => m;',
515             PMCode => PDL::PP::pp_line_numbers(__LINE__, <
516             sub PDL::${name}_n_ind {
517             my (\$a, \$c, \$m_size) = \@_;
518             \$m_size //= ref(\$c) ? \$c->dim(0) : \$c; # back-compat with pre-2.077
519             my \$set_out = 1;
520             \$set_out = 0, \$c = null if !ref \$c;
521             \$c = \$c->indx if !\$c->isnull;
522             PDL::_${name}_n_ind_int(\$a, \$c, \$m_size);
523             \$set_out ? \$_[1] = \$c : \$c;
524             }
525             EOF
526             RedoDimsCode => 'if($SIZE(m) > $SIZE(n)) $CROAK("m_size > n_size");',
527             Code =>
528             '$GENERIC() cur = 0; PDL_Indx curind; register PDL_Indx ns = $SIZE(n);
529             $PDLSTATESETGOOD(c);
530             loop(m) %{
531             curind = ns;
532             loop(n) %{
533             PDL_Indx outer_m = m; int flag=0;
534             loop (m=:outer_m) %{
535             if ($c() == n) {flag=1; break;}
536             %}
537             if (!flag &&
538             PDL_IF_BAD($ISGOOD(a()) &&,)
539             ((curind == ns) || $a() '.$op.' cur || PDL_ISNAN_$PPSYM()(cur)))
540             {cur = $a(); curind = n;}
541             %}
542             if (curind != ns) { $c() = curind; }
543             else { $SETBAD(c()); $PDLSTATESETBAD(c); }
544             %}',
545             Doc => <
546             =for ref
547              
548             Returns the index of first C $name elements. As of 2.077, you can
549             specify how many by either passing in an ndarray of the given size
550             (DEPRECATED - will be converted to indx if needed and the input arg will
551             be set to that), or just the size, or a null and the size.
552              
553             =for usage
554              
555             ${name}_n_ind(\$pdl, \$out = zeroes(5)); # DEPRECATED
556             \$out = ${name}_n_ind(\$pdl, 5);
557             ${name}_n_ind(\$pdl, \$out = null, 5);
558              
559             EOF
560             BadDoc =>
561             'Output bad flag is cleared for the output ndarray if sufficient non-bad elements found,
562             else remaining slots in C<$c()> are set bad.
563              
564             Note that C are considered to be valid values and will "win" over non-C;
565             see L and L
566             for ways of masking NaNs.
567             ',
568             );
569             synonym("${name}_n_ind", "${synonym}_n_ind");
570             } # foreach: $which
571              
572             pp_def('minmaximum',
573             HandleBad => 1,
574             Pars => 'a(n); [o]cmin(); [o] cmax(); indx [o]cmin_ind(); indx [o]cmax_ind();',
575             Code => <<'EOF',
576             $GENERIC() curmin = 0, curmax = 0; /* Handle null ndarray --CED */
577             PDL_Indx curmin_ind = 0, curmax_ind = 0; int flag = 0;
578             loop(n) %{
579             PDL_IF_BAD(if ($ISBAD(a())) continue;,)
580             if (PDL_ISNAN_$PPSYM()($a())) continue;
581             if (!flag) {
582             curmin = curmax = $a();
583             curmin_ind = curmax_ind = n;
584             flag = 1;
585             } else {
586             if ($a() < curmin) { curmin = $a(); curmin_ind = n; }
587             if ($a() > curmax) { curmax = $a(); curmax_ind = n; }
588             }
589             %}
590             if ( !flag ) { /* Handle null ndarray */
591             $SETBAD(cmin()); $SETBAD(cmin_ind());
592             $SETBAD(cmax()); $SETBAD(cmax_ind());
593             $PDLSTATESETBAD(cmin); $PDLSTATESETBAD(cmin_ind);
594             $PDLSTATESETBAD(cmax); $PDLSTATESETBAD(cmax_ind);
595             } else {
596             $cmin() = curmin; $cmin_ind() = curmin_ind;
597             $cmax() = curmax; $cmax_ind() = curmax_ind;
598             }
599             EOF
600             Doc => '
601             =for ref
602              
603             Find minimum and maximum and their indices for a given ndarray;
604              
605             =for example
606              
607             pdl> $x=pdl [[-2,3,4],[1,0,3]]
608             pdl> ($min, $max, $min_ind, $max_ind)=minmaximum($x)
609             pdl> p $min, $max, $min_ind, $max_ind
610             [-2 0] [4 3] [0 1] [2 2]
611              
612             See also L, which clumps the ndarray together.
613              
614             =cut
615             ',
616             BadDoc =>
617             'If C contains only bad data, then the output ndarrays will
618             be set bad, along with their bad flag.
619             Otherwise they will have their bad flags cleared,
620             since they will not contain any bad values.',
621             ); # pp_def minmaximum
622             synonym(qw(minmaximum minmaxover));
623              
624             # Generate small ops functions to do entire array
625             # How to handle a return value of BAD - ie what
626             # datatype to use?
627             for my $op ( ['avg','average','average'],
628             ['sum','sumover','sum'],
629             ['prod','prodover','product'],
630              
631             ['davg','daverage','average (in double precision)'],
632             ['dsum','dsumover','sum (in double precision)'],
633             ['dprod','dprodover','product (in double precision)'],
634              
635             ['zcheck','zcover','check for zero'],
636             ['and','andover','logical and'],
637             ['band','bandover','bitwise and'],
638             ['or','orover','logical or'],
639             ['bor','borover','bitwise or'],
640             ['xorall','xorover','logical xor'],
641             ['bxor','bxorover','bitwise xor'],
642             ['min','minimum','minimum'],
643             ['max','maximum','maximum'],
644             ['median', 'medover', 'median'],
645             ['mode', 'modeover', 'mode'],
646             ['oddmedian','oddmedover','oddmedian']) {
647             my ($name, $func, $text) = @$op;
648             pp_add_exported('', $name);
649             pp_addpm(<<"EOD");
650             =head2 $name
651              
652             =for ref
653              
654             Return the $text of all elements in an ndarray.
655              
656             See the documentation for L for more information.
657              
658             =for usage
659              
660             \$x = $name(\$data);
661              
662             =for bad
663              
664             This routine handles bad values.
665              
666             =cut
667              
668             *$name = \\&PDL::$name;
669             sub PDL::$name { \$_[0]->flat->${func} }
670             EOD
671              
672             } # for $op
673              
674             pp_add_exported('','any all');
675             pp_addpm(<<'EOPM');
676              
677             =head2 any
678              
679             =for ref
680              
681             Return true if any element in ndarray set
682              
683             Useful in conditional expressions:
684              
685             =for example
686              
687             if (any $x>15) { print "some values are greater than 15\n" }
688              
689             =for bad
690              
691             See L for comments on what happens when all elements
692             in the check are bad.
693              
694             =cut
695              
696             *any = \∨
697             *PDL::any = \&PDL::or;
698              
699             =head2 all
700              
701             =for ref
702              
703             Return true if all elements in ndarray set
704              
705             Useful in conditional expressions:
706              
707             =for example
708              
709             if (all $x>15) { print "all values are greater than 15\n" }
710              
711             =for bad
712              
713             See L for comments on what happens when all elements
714             in the check are bad.
715              
716             =cut
717              
718             *all = \∧
719             *PDL::all = \&PDL::and;
720              
721             =head2 minmax
722              
723             =for ref
724              
725             Returns a list with minimum and maximum values of an ndarray.
726              
727             =for usage
728              
729             ($mn, $mx) = minmax($pdl);
730              
731             This routine does I broadcast over the dimensions of C<$pdl>;
732             it returns the minimum and maximum values of the whole ndarray.
733             See L if this is not what is required.
734             The two values are returned as Perl scalars,
735             and therefore ignore whether the values are bad.
736              
737             =for example
738              
739             pdl> $x = pdl [1,-2,3,5,0]
740             pdl> ($min, $max) = minmax($x);
741             pdl> p "$min $max\n";
742             -2 5
743              
744             =cut
745              
746             *minmax = \&PDL::minmax;
747             sub PDL::minmax { map $_->sclr, ($_[0]->flat->minmaximum)[0,1] }
748              
749             EOPM
750              
751             pp_add_exported('', 'minmax');
752              
753             my $chdr_qsort = 'PDL_TYPELIST_REAL(PDL_QSORT)';
754             my $chdr_qsorti = PDL::PP::pp_line_numbers(__LINE__, <<'EOF');
755             #define X(symbol, ctype, ppsym, ...) \
756             static inline void qsort_ind_ ## ppsym(ctype* xx, PDL_Indx* ix, PDL_Indx a, PDL_Indx b) { \
757             PDL_Indx i,j; \
758             PDL_Indx t; \
759             ctype median; \
760             i = a; j = b; \
761             median = xx[ix[(i+j) / 2]]; \
762             do { \
763             while (xx[ix[i]] < median) \
764             i++; \
765             while (median < xx[ix[j]]) \
766             j--; \
767             if (i <= j) { \
768             t = ix[i]; ix[i] = ix[j]; ix[j] = t; \
769             i++; j--; \
770             } \
771             } while (i <= j); \
772             if (a < j) \
773             qsort_ind_ ## ppsym(xx,ix,a,j); \
774             if (i < b) \
775             qsort_ind_ ## ppsym(xx,ix,i,b); \
776             }
777             PDL_TYPELIST_REAL(X)
778             #undef X
779             EOF
780             my $chdr_qsortvec_def = PDL::PP::pp_line_numbers(__LINE__, <<'EOF');
781             #define X(symbol, ctype, ppsym, ...) \
782             /******* \
783             * qsortvec helper routines \
784             * --CED 21-Aug-2003 \
785             */ \
786             /* Compare a vector in lexicographic order, return equivalent of "<=>". \
787             */ \
788             static inline signed int pdl_cmpvec_ ## ppsym(ctype *a, ctype *b, PDL_Indx n) { \
789             PDL_Indx i; \
790             for(i=0; i
791             if( *a < *b ) return -1; \
792             if( *a > *b ) return 1; \
793             } \
794             return 0; \
795             }
796 204 100       634 PDL_TYPELIST_REAL(X)
  70 100       116  
  204 50       2758  
  70 100       359  
  70 100       106  
  70 100       5589  
  70 100       384  
  70 100       104  
  70 100       582  
  70 100       491  
  70 0       118  
  70 0       181288  
  4 0       30  
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
797             #undef X
798             #define PDL_QSORTVEC(ppsym, RECURSE, INDEXTERM, swapcode) \
799             PDL_Indx i,j, median_ind; \
800             i = a; \
801             j = b; \
802             median_ind = (i+j)/2; \
803             do { \
804             while( pdl_cmpvec_ ## ppsym( &(xx[n*INDEXTERM(i)]), &(xx[n*INDEXTERM(median_ind)]), n ) < 0 ) \
805             i++; \
806             while( pdl_cmpvec_ ## ppsym( &(xx[n*INDEXTERM(j)]), &(xx[n*INDEXTERM(median_ind)]), n ) > 0 ) \
807             j--; \
808             if(i<=j) { \
809             PDL_Indx k; \
810             swapcode \
811             if (median_ind==i) \
812             median_ind=j; \
813             else if (median_ind==j) \
814             median_ind=i; \
815             i++; \
816             j--; \
817             } \
818             } while (i <= j); \
819             if (a < j) \
820             RECURSE( ppsym, a, j ); \
821             if (i < b) \
822             RECURSE( ppsym, i, b );
823             EOF
824             my $chdr_qsortvec = PDL::PP::pp_line_numbers(__LINE__, <<'EOF');
825             #define PDL_QSORTVEC_INDEXTERM(indexterm) indexterm
826             #define PDL_QSORTVEC_RECURSE(ppsym, ...) pdl_qsortvec_ ## ppsym(xx, n, __VA_ARGS__)
827             #define X(symbol, ctype, ppsym, ...) \
828             static inline void pdl_qsortvec_ ## ppsym(ctype *xx, PDL_Indx n, PDL_Indx a, PDL_Indx b) { \
829             PDL_QSORTVEC(ppsym, PDL_QSORTVEC_RECURSE, PDL_QSORTVEC_INDEXTERM, \
830             ctype *aa = &xx[n*i]; \
831             ctype *bb = &xx[n*j]; \
832             for( k=0; k
833             ctype z = *aa; \
834             *aa = *bb; \
835             *bb = z; \
836             } \
837             ) \
838             }
839             PDL_TYPELIST_REAL(X)
840             #undef X
841             #undef PDL_QSORTVEC_INDEXTERM
842             #undef PDL_QSORTVEC_RECURSE
843             EOF
844             my $chdr_qsortvec_ind = PDL::PP::pp_line_numbers(__LINE__, <<'EOF');
845             #define PDL_QSORTVEC_INDEXTERM(indexterm) ix[indexterm]
846             #define PDL_QSORTVEC_RECURSE(ppsym, ...) pdl_qsortvec_ind_ ## ppsym(xx, ix, n, __VA_ARGS__)
847             #define X(symbol, ctype, ppsym, ...) \
848             static inline void pdl_qsortvec_ind_ ## ppsym(ctype *xx, PDL_Indx *ix, PDL_Indx n, PDL_Indx a, PDL_Indx b) { \
849             PDL_QSORTVEC(ppsym, PDL_QSORTVEC_RECURSE, PDL_QSORTVEC_INDEXTERM, \
850             k = ix[i]; \
851             ix[i] = ix[j]; \
852             ix[j] = k; \
853             ) \
854             }
855 45 0       21 PDL_TYPELIST_REAL(X)
  4 0       26  
  45 0       19  
  4 0       34  
  4 0       229  
  4 0       56  
  1 0       5  
  1 0       10  
  1 100       2  
  1 100       7  
  1 50       6  
  1 100       66  
  1 100       10  
    100          
    100          
    100          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
856             #undef X
857             #undef PDL_QSORTVEC_INDEXTERM
858             #undef PDL_QSORTVEC_RECURSE
859             EOF
860              
861             # when copying the data over to the temporary array,
862             # ignore the bad values and then only send the number
863             # of good elements to the sort routines
864             # should use broadcastloop ?
865             my $copy_to_temp = pp_line_numbers(__LINE__, '
866             if ($PDL(a)->nvals == 0)
867             $CROAK("cannot process empty ndarray");
868             PDL_Indx nn = PDL_IF_BAD(0,$SIZE(n)-1);
869             loop(n) %{
870             PDL_IF_BAD(if ( $ISGOOD(a()) ) { $tmp(n=>nn) = $a(); nn++; },
871             $tmp() = $a();)
872             %}
873             PDL_IF_BAD(if ( nn == 0 ) {
874             $SETBAD(b());
875             } else {
876             nn -= 1;,{)
877             qsort_$PPSYM() ($P(tmp), 0, nn);');
878              
879             my $find_median_average = '
880             PDL_Indx nn1 = nn/2, nn2 = nn1+1;
881             if (nn%2==0) {
882             $b() = $tmp(n => nn1);
883             }
884             else {
885             $b() = 0.5*( $tmp(n => nn1) + $tmp(n => nn2) );
886             }';
887             pp_def('medover',
888             HandleBad => 1,
889             Pars => 'a(n); [o]b(); [t]tmp(n);',
890             Doc => projectdocs('median','medover',''),
891             CHeader => 'PDL_TYPELIST_REAL(PDL_QSORT)',
892             Code => $copy_to_temp . $find_median_average . '}',
893             ); # pp_def: medover
894              
895             my $find_median_lower = '
896             PDL_Indx nn1 = nn/2;
897             $b() = $tmp(n => nn1);';
898             pp_def('oddmedover',
899             HandleBad => 1,
900             Pars => 'a(n); [o]b(); [t]tmp(n);',
901             Doc => projectdocs('oddmedian','oddmedover','
902              
903             The median is sometimes not a good choice as if the array has
904             an even number of elements it lies half-way between the two
905             middle values - thus it does not always correspond to a data
906             value. The lower-odd median is just the lower of these two values
907             and so it ALWAYS sits on an actual data value which is useful in
908             some circumstances.
909             '),
910             CHeader => 'PDL_TYPELIST_REAL(PDL_QSORT)',
911             Code => $copy_to_temp . $find_median_lower . '}',
912             ); # pp_def: oddmedover
913              
914             pp_def('modeover',
915             HandleBad=>undef,
916             Pars => 'data(n); [o]out(); [t]sorted(n);',
917             GenericTypes=>$T,
918             Doc=>projectdocs('mode','modeover','
919             The mode is the single element most frequently found in a
920             discrete data set.
921              
922             It I makes sense for integer data types, since
923             floating-point types are demoted to integer before the
924             mode is calculated.
925              
926             C treats BAD the same as any other value: if
927             BAD is the most common element, the returned value is also BAD.
928             '),
929             CHeader => 'PDL_TYPELIST_INTEGER(PDL_QSORT)',
930             Code => <<'EOCODE',
931             PDL_Indx i = 0;
932             PDL_Indx most = 0;
933             $GENERIC() curmode = 0;
934             $GENERIC() curval = 0;
935              
936             /* Copy input to buffer for sorting, and sort it */
937             loop(n) %{ $sorted() = $data(); %}
938             qsort_$PPSYM()($P(sorted),0,$SIZE(n)-1);
939              
940             /* Walk through the sorted data and find the most common elemen */
941             loop(n) %{
942             if( n==0 || curval != $sorted() ) {
943             curval = $sorted();
944             i=0;
945             } else {
946             i++;
947             if(i>most){
948             most=i;
949             curmode = curval;
950             }
951             }
952             %}
953             $out() = curmode;
954             EOCODE
955             );
956              
957              
958             my $find_pct_interpolate = '
959             double np, pp1, pp2;
960             np = nn * $p();
961             PDL_Indx nn1 = PDLMIN(nn,PDLMAX(0,np));
962             PDL_Indx nn2 = PDLMIN(nn,PDLMAX(0,np+1));
963             if (nn == 0) {
964             pp1 = 0;
965             pp2 = 0;
966             } else {
967             pp1 = (double)nn1/(double)(nn);
968             pp2 = (double)nn2/(double)(nn);
969             }
970             if ( np <= 0.0 ) {
971             $b() = $tmp(n => 0);
972             } else if ( np >= nn ) {
973             $b() = $tmp(n => nn);
974             } else if ($tmp(n => nn2) == $tmp(n => nn1)) {
975             $b() = $tmp(n => nn1);
976             } else if ($p() == pp1) {
977             $b() = $tmp(n => nn1);
978             } else if ($p() == pp2) {
979             $b() = $tmp(n => nn2);
980             } else {
981             $b() = (np - nn1)*($tmp(n => nn2) - $tmp(n => nn1)) + $tmp(n => nn1);
982             }
983             ';
984             pp_def('pctover',
985             HandleBad => 1,
986             Pars => 'a(n); p(); [o]b(); [t]tmp(n);',
987             Doc => projectdocs('specified percentile', 'pctover',
988             'The specified
989             percentile must be between 0.0 and 1.0. When the specified percentile
990             falls between data points, the result is interpolated. Values outside
991             the allowed range are clipped to 0.0 or 1.0 respectively. The algorithm
992             implemented here is based on the interpolation variant described at
993             L as used by Microsoft Excel
994             and recommended by NIST.
995             '),
996             CHeader => 'PDL_TYPELIST_REAL(PDL_QSORT)',
997             Code => $copy_to_temp . $find_pct_interpolate . '}',
998             );
999              
1000             pp_def('oddpctover',
1001             HandleBad => 1,
1002             Pars => 'a(n); p(); [o]b(); [t]tmp(n);',
1003             Doc => projectdocs('specified percentile', 'oddpctover',
1004             'The specified
1005             percentile must be between 0.0 and 1.0. When the specified percentile
1006             falls between two values, the nearest data value is the result.
1007             The algorithm implemented is from the textbook version described
1008             first at L.
1009             '),
1010             CHeader => 'PDL_TYPELIST_REAL(PDL_QSORT)',
1011             Code =>
1012             $copy_to_temp . '
1013             PDL_Indx np = PDLMAX(0,PDLMIN(nn,(nn+1)*$p()));
1014             $b() = $tmp(n => np);
1015             }',
1016             );
1017              
1018             for (
1019             ['','result is interpolated'],
1020             ['odd','nearest data value is the result'],
1021             ) {
1022             pp_add_exported('', $_->[0].'pct');
1023             pp_addpm(<
1024             =head2 $_->[0]pct
1025              
1026             =for ref
1027              
1028             Return the specified percentile of all elements in an ndarray. The
1029             specified percentile (p) must be between 0.0 and 1.0. When the
1030             specified percentile falls between data points, the $_->[1].
1031              
1032             =for usage
1033              
1034             \$x = $_->[0]pct(\$data, \$pct);
1035              
1036             =cut
1037              
1038             *$_->[0]pct = \\&PDL::$_->[0]pct;
1039             sub PDL::$_->[0]pct {
1040             my(\$x, \$p) = \@_;
1041             \$x->flat->$_->[0]pctover(\$p, my \$tmp=PDL->nullcreate(\$x));
1042             \$tmp;
1043             }
1044             EOD
1045             }
1046              
1047             sub qsort_returnempty { 'if ($PDL(a)->nvals == 0) return PDL_err;' }
1048              
1049             pp_def('qsort',
1050             HandleBad => 1,
1051             Inplace => 1,
1052             Pars => '!complex a(n); !complex [o]b(n);',
1053             CHeader => 'PDL_TYPELIST_REAL(PDL_QSORT)',
1054             Code => PDL::PP::pp_line_numbers(__LINE__, '
1055             register PDL_Indx nn = PDL_IF_BAD(0,$SIZE(n));
1056             char is_inplace = ($P(a) == $P(b));
1057             PDL_IF_BAD(register PDL_Indx nb = $SIZE(n) - 1;,)
1058             ').qsort_returnempty().'
1059             PDL_IF_BAD(
1060             loop(n) %{
1061             if ($ISGOOD(a())) {
1062             if (!is_inplace) $b(n=>nn) = $a();
1063             nn++;
1064             continue;
1065             }
1066             while (is_inplace && $ISBAD(b(n=>nb)) && nb > n) nb--;
1067             if (nb > n && is_inplace) { $a() = $b(n=>nb); nn++; }
1068             if (nb > n || !is_inplace) { $SETBAD(b(n=>nb)); nb--; }
1069             if (nb < n) break;
1070             %}
1071             ,
1072             if (!is_inplace) loop(n) %{ $b() = $a(); %}
1073             )
1074             if ( nn == 0 ) continue; nn -= 1;
1075             qsort_$PPSYM() ($P(b), 0, nn);',
1076             Doc => 'Quicksort a vector into ascending order.',
1077             BadDoc =>
1078             '
1079             Bad values are moved to the end of the array:
1080              
1081             pdl> p $y
1082             [42 47 98 BAD 22 96 74 41 79 76 96 BAD 32 76 25 59 BAD 96 32 BAD]
1083             pdl> p qsort($y)
1084             [22 25 32 32 41 42 47 59 74 76 76 79 96 96 96 98 BAD BAD BAD BAD]
1085             ',
1086             );
1087              
1088             pp_def('qsorti',
1089             HandleBad => 1,
1090             Pars => '!complex a(n); indx [o]indx(n);',
1091             CHeader => $chdr_qsorti,
1092             Code => PDL::PP::pp_line_numbers(__LINE__, '
1093             register PDL_Indx nn = PDL_IF_BAD(0,$SIZE(n)-1), nb = $SIZE(n) - 1; (void)nb;
1094             ').qsort_returnempty().'
1095             loop(n) %{
1096             PDL_IF_BAD(if ($ISBAD(a())) { $indx(n=>nb) = n; nb--; }
1097             else { $indx(n=>nn) = n; nn++; } /* play safe since nn used more than once */
1098             ,$indx() = n;)
1099             %}
1100             PDL_IF_BAD(if ( nn == 0 ) continue; nn -= 1;,)
1101             qsort_ind_$PPSYM() ($P(a), $P(indx), 0, nn);',
1102             BadDoc =>
1103             'Bad elements are moved to the end of the array:
1104              
1105             pdl> p $y
1106             [42 47 98 BAD 22 96 74 41 79 76 96 BAD 32 76 25 59 BAD 96 32 BAD]
1107             pdl> p $y->index( qsorti($y) )
1108             [22 25 32 32 41 42 47 59 74 76 76 79 96 96 96 98 BAD BAD BAD BAD]
1109             ',
1110             Doc => '
1111             =for ref
1112              
1113             Quicksort a vector and return index of elements in ascending order.
1114              
1115             =for example
1116              
1117             $ix = qsorti $x;
1118             print $x->index($ix); # Sorted list
1119             '
1120             );
1121              
1122             # move all bad values to the end of the array
1123             #
1124             pp_def('qsortvec',
1125             HandleBad => 1,
1126             Inplace => 1,
1127             Pars => '!complex a(n,m); !complex [o]b(n,m);',
1128             CHeader => $chdr_qsortvec_def . $chdr_qsortvec,
1129             Code => '
1130             register PDL_Indx nn = PDL_IF_BAD(0,$SIZE(m)-1);
1131             char is_inplace = ($P(a) == $P(b));
1132             '.qsort_returnempty().'
1133             PDL_IF_BAD(register PDL_Indx nb = $SIZE(m) - 1; loop(m) %{
1134             char allgood_a = 1;
1135             loop(n) %{ if ( $ISBAD(a()) ) { allgood_a = 0; break; } %}
1136             PDL_Indx copy_dest = allgood_a ? nn++ : nb--;
1137             if (is_inplace) {
1138             if (allgood_a) continue; /* nothing to do */
1139             char anybad_b = 0;
1140             do {
1141             anybad_b = 0;
1142             loop(n) %{ if ($ISBAD(b(m=>copy_dest))) { anybad_b = 1; break; } %}
1143             if (anybad_b) copy_dest = nb--;
1144             } while (anybad_b);
1145             if (m != copy_dest)
1146             loop(n) %{
1147             /* as in-place we know same badval source and dest */
1148             $GENERIC() tmp = $b(m=>copy_dest);
1149             $b(m=>copy_dest) = $a();
1150             $a() = tmp;
1151             %}
1152             if (m >= nb-1) { nn = nb+1; break; } /* run out of "good" vectors */
1153             } else {
1154             loop(n) %{
1155             if ($ISBAD(a())) $SETBAD(b(m=>copy_dest));
1156             else $b(m=>copy_dest) = $a();
1157             %}
1158             }
1159             %}
1160             if ( nn == 0 ) continue; nn -= 1;,
1161             if (!is_inplace) { loop(n,m) %{ $b() = $a(); %} }
1162             )
1163             pdl_qsortvec_$PPSYM() ($P(b), $SIZE(n), 0, nn);',
1164             Doc => '
1165             =for ref
1166              
1167             Sort a list of vectors lexicographically.
1168              
1169             The 0th dimension of the source ndarray is dimension in the vector;
1170             the 1st dimension is list order. Higher dimensions are broadcasted over.
1171              
1172             =for example
1173              
1174             print qsortvec pdl([[1,2],[0,500],[2,3],[4,2],[3,4],[3,5]]);
1175             [
1176             [ 0 500]
1177             [ 1 2]
1178             [ 2 3]
1179             [ 3 4]
1180             [ 3 5]
1181             [ 4 2]
1182             ]
1183              
1184             =cut
1185             ',
1186             BadDoc => '
1187             Vectors with bad components are moved to the end of the array:
1188              
1189             pdl> p $p = pdl("[0 0] [-100 0] [BAD 0] [100 0]")->qsortvec
1190              
1191             [
1192             [-100 0]
1193             [ 0 0]
1194             [ 100 0]
1195             [ BAD 0]
1196             ]
1197             ',
1198             );
1199              
1200             pp_def('qsortveci',
1201             HandleBad => 1,
1202             Pars => '!complex a(n,m); indx [o]indx(m);',
1203             CHeader => $chdr_qsortvec_def . $chdr_qsortvec_ind,
1204             Code =>
1205             'register PDL_Indx nn = PDL_IF_BAD(0,$SIZE(m)-1);
1206             PDL_IF_BAD(register PDL_Indx nb = $SIZE(m) - 1;,)
1207             '.qsort_returnempty().'
1208             loop(m) %{
1209             PDL_IF_BAD(
1210             char allgood_a = 1;
1211             loop(n) %{ if ( $ISBAD(a()) ) { allgood_a = 0; break; } %}
1212             PDL_Indx copy_dest = allgood_a ? nn++ : nb--;
1213             $indx(m=>copy_dest) = m;
1214             ,
1215             $indx()=m;
1216             )
1217             %}
1218             PDL_IF_BAD(if ( nn == 0 ) continue; nn -= 1;,)
1219             pdl_qsortvec_ind_$PPSYM() ($P(a), $P(indx), $SIZE(n), 0, nn);',
1220             Doc => '
1221             =for ref
1222              
1223             Sort a list of vectors lexicographically, returning the indices of the
1224             sorted vectors rather than the sorted list itself.
1225              
1226             As with C, the input PDL should be an NxM array containing M
1227             separate N-dimensional vectors. The return value is an integer M-PDL
1228             containing the M-indices of original array rows, in sorted order.
1229              
1230             As with C, the zeroth element of the vectors runs slowest in the
1231             sorted list.
1232              
1233             Additional dimensions are broadcasted over: each plane is sorted separately,
1234             so qsortveci may be thought of as a collapse operator of sorts (groan).
1235              
1236             =cut
1237             ',
1238             BadDoc => '
1239             Vectors with bad components are moved to the end of the array as
1240             for L.
1241             ',
1242             );
1243              
1244             pp_def('magnover',
1245             HandleBad => 1,
1246             Pars => "a(n); real [o]b();",
1247             GenericTypes => [(grep $_ ne 'F', @$AF), 'F'], # F last for back-compat
1248             Code => <<'EOF',
1249             long double sum=0;
1250             PDL_IF_BAD(int flag = 0;,)
1251             loop(n) %{
1252             PDL_IF_BAD(if ($ISBAD(a())) continue; flag = 1;,)
1253             sum += PDL_IF_GENTYPE_REAL(
1254             $a()*$a(),
1255             creall($a())*creall($a()) + cimagl($a())*cimagl($a())
1256             );
1257             %}
1258             PDL_IF_BAD(if ( !flag ) { $SETBAD(b()); continue; },)
1259             $b() = sum == 0 ? 0 : sqrtl(sum);
1260             EOF
1261             Doc => projectdocs( 'Euclidean (aka Pythagorean) distance', 'magnover', <<'EOF' ),
1262             Minimum C precision output.
1263             See also L, L.
1264             EOF
1265             );
1266              
1267             pp_addpm({At=>'Bot'},<<'EOD');
1268              
1269             =head1 AUTHOR
1270              
1271             Copyright (C) Tuomas J. Lukka 1997 (lukka@husc.harvard.edu).
1272             Contributions by Christian Soeller (c.soeller@auckland.ac.nz)
1273             and Karl Glazebrook (kgb@aaoepp.aao.gov.au). All rights
1274             reserved. There is no warranty. You are allowed to redistribute this
1275             software / documentation under certain conditions. For details, see
1276             the file COPYING in the PDL distribution. If this file is separated
1277             from the PDL distribution, the copyright notice should be included in
1278             the file.
1279              
1280             =cut
1281             EOD
1282              
1283             pp_done();