File Coverage

lib/PDL/Primitive.pd
Criterion Covered Total %
statement 83 87 95.4
branch 130 186 69.8
condition n/a
subroutine n/a
pod n/a
total 213 273 78.0


line stmt bran cond sub pod time code
1             use strict;
2             use warnings;
3             use PDL::Types qw(ppdefs_all types);
4             my $F = [map $_->ppsym, grep $_->real && !$_->integer, types()];
5             my $AF = [map $_->ppsym, grep !$_->integer, types];
6             $AF = [(grep $_ ne 'D', @$AF), 'D']; # so defaults to D if non-float given
7              
8             { no warnings 'once'; # pass info back to Makefile.PL
9             $PDL::Core::Dev::EXTRAS{$::PDLMOD}{OBJECT} .= " $::PDLBASE-xoshiro256plus\$(OBJ_EXT)";
10             }
11              
12             pp_addpm({At=>'Top'},<<'EOD');
13             use strict;
14             use warnings;
15             use PDL::Slices;
16             use Carp;
17              
18             =head1 NAME
19              
20             PDL::Primitive - primitive operations for pdl
21              
22             =head1 DESCRIPTION
23              
24             This module provides some primitive and useful functions defined
25             using PDL::PP and able to use the new indexing tricks.
26              
27             See L for how to use indices creatively.
28             For explanation of the signature format, see L.
29              
30             =head1 SYNOPSIS
31              
32             # Pulls in PDL::Primitive, among other modules.
33             use PDL;
34              
35             # Only pull in PDL::Primitive:
36             use PDL::Primitive;
37              
38             =cut
39              
40             EOD
41              
42             ################################################################
43             # a whole bunch of quite basic functions for inner, outer
44             # and matrix products (operations that are not normally
45             # available via operator overloading)
46             ################################################################
47              
48             pp_def('inner',
49             HandleBad => 1,
50             Pars => 'a(n); b(n); [o]c();',
51             GenericTypes => [ppdefs_all],
52             Code => <<'EOF',
53             complex long double tmp = 0;
54             PDL_IF_BAD(int badflag = 0;,)
55             loop(n) %{
56             PDL_IF_BAD(if ($ISBAD(a()) || $ISBAD(b())) { badflag = 1; break; }
57             else,) { tmp += $a() * $b(); }
58             %}
59             PDL_IF_BAD(if (badflag) { $SETBAD(c()); $PDLSTATESETBAD(c); }
60             else,) { $c() = tmp; }
61             EOF
62             Doc => '
63             =for ref
64              
65             Inner product over one dimension
66              
67             c = sum_i a_i * b_i
68              
69             See also L, L.
70             ',
71             BadDoc => '
72             If C contains only bad data,
73             C is set bad. Otherwise C will have its bad flag cleared,
74             as it will not contain any bad values.
75             ',
76             ); # pp_def( inner )
77              
78             pp_def('outer',
79             HandleBad => 1,
80             Pars => 'a(n); b(m); [o]c(n,m);',
81             GenericTypes => [ppdefs_all],
82             Code => <<'EOF',
83             loop(n,m) %{
84             PDL_IF_BAD(if ( $ISBAD(a()) || $ISBAD(b()) ) { $SETBAD(c()); continue; },)
85             $c() = $a() * $b();
86             %}
87             EOF
88             Doc => '
89             =for ref
90              
91             outer product over one dimension
92              
93             Naturally, it is possible to achieve the effects of outer
94             product simply by broadcasting over the "C<*>"
95             operator but this function is provided for convenience.
96             '); # pp_def( outer )
97              
98             pp_addpm(<<'EOD');
99             =head2 x
100              
101             =for sig
102              
103             Signature: (a(i,z), b(x,i),[o]c(x,z))
104              
105             =for ref
106              
107             Matrix multiplication
108              
109             PDL overloads the C operator (normally the repeat operator) for
110             matrix multiplication. The number of columns (size of the 0
111             dimension) in the left-hand argument must normally equal the number of
112             rows (size of the 1 dimension) in the right-hand argument.
113              
114             Row vectors are represented as (N x 1) two-dimensional PDLs, or you
115             may be sloppy and use a one-dimensional PDL. Column vectors are
116             represented as (1 x N) two-dimensional PDLs.
117              
118             Broadcasting occurs in the usual way, but as both the 0 and 1 dimension
119             (if present) are included in the operation, you must be sure that
120             you don't try to broadcast over either of those dims.
121              
122             Of note, due to how Perl v5.14.0 and above implement operator overloading of
123             the C operator, the use of parentheses for the left operand creates a list
124             context, that is
125              
126             pdl> ( $x * $y ) x $z
127             ERROR: Argument "..." isn't numeric in repeat (x) ...
128              
129             treats C<$z> as a numeric count for the list repeat operation and does not call
130             the scalar form of the overloaded operator. To use the operator in this case,
131             use a scalar context:
132              
133             pdl> scalar( $x * $y ) x $z
134              
135             or by calling L directly:
136              
137             pdl> ( $x * $y )->matmult( $z )
138              
139             EXAMPLES
140              
141             Here are some simple ways to define vectors and matrices:
142              
143             pdl> $r = pdl(1,2); # A row vector
144             pdl> $c = pdl([[3],[4]]); # A column vector
145             pdl> $c = pdl(3,4)->(*1); # A column vector, using NiceSlice
146             pdl> $m = pdl([[1,2],[3,4]]); # A 2x2 matrix
147              
148             Now that we have a few objects prepared, here is how to
149             matrix-multiply them:
150              
151             pdl> print $r x $m # row x matrix = row
152             [
153             [ 7 10]
154             ]
155              
156             pdl> print $m x $r # matrix x row = ERROR
157             PDL: Dim mismatch in matmult of [2x2] x [2x1]: 2 != 1
158              
159             pdl> print $m x $c # matrix x column = column
160             [
161             [ 5]
162             [11]
163             ]
164              
165             pdl> print $m x 2 # Trivial case: scalar mult.
166             [
167             [2 4]
168             [6 8]
169             ]
170              
171             pdl> print $r x $c # row x column = scalar
172             [
173             [11]
174             ]
175              
176             pdl> print $c x $r # column x row = matrix
177             [
178             [3 6]
179             [4 8]
180             ]
181              
182             INTERNALS
183              
184             The mechanics of the multiplication are carried out by the
185             L method.
186              
187             =cut
188              
189             EOD
190              
191             pp_def('matmult',
192             HandleBad=>1,
193             Pars => 'a(t,h); b(w,t); [o]c(w,h);',
194             GenericTypes => [ppdefs_all],
195             Overload => 'x',
196             PMCode => pp_line_numbers(__LINE__, <<'EOPM'),
197             sub PDL::matmult {
198             my ($x,$y,$c) = @_;
199             $y = PDL->topdl($y);
200             $c = PDL->null if !UNIVERSAL::isa($c, 'PDL');
201             while($x->getndims < 2) {$x = $x->dummy(-1)}
202             while($y->getndims < 2) {$y = $y->dummy(-1)}
203             return ($c .= $x * $y) if( ($x->dim(0)==1 && $x->dim(1)==1) ||
204             ($y->dim(0)==1 && $y->dim(1)==1) );
205             barf sprintf 'Dim mismatch in matmult of [%1$dx%2$d] x [%3$dx%4$d]: %1$d != %4$d',$x->dim(0),$x->dim(1),$y->dim(0),$y->dim(1)
206             if $y->dim(1) != $x->dim(0);
207             PDL::_matmult_int($x,$y,$c);
208             $c;
209             }
210             EOPM
211             Code => <<'EOC',
212             PDL_Indx tsiz = 8 * sizeof(double) / sizeof($GENERIC());
213              
214             // Cache the dimincs to avoid constant lookups
215             PDL_Indx atdi = PDL_REPRINCS($PDL(a))[0];
216             PDL_Indx btdi = PDL_REPRINCS($PDL(b))[1];
217              
218             broadcastloop %{
219             // Loop over tiles
220             loop (h=::tsiz,w=::tsiz) %{
221             PDL_Indx h_outer = h, w_outer = w;
222             // Zero the output for this tile
223             loop (h=h_outer:h_outer+tsiz,w=w_outer:w_outer+tsiz) %{ $c() = 0; %}
224             loop (t=::tsiz,h=h_outer:h_outer+tsiz,w=w_outer:w_outer+tsiz) %{
225             // Cache the accumulated value for the output
226             $GENERIC() cc = $c();
227             PDL_IF_BAD(if ($ISBADVAR(cc,c)) continue;,)
228             // Cache data pointers before 't' run through tile
229             $GENERIC() *ad = &($a());
230             $GENERIC() *bd = &($b());
231             // Hotspot - run the 't' summation
232             PDL_Indx t_outer = t;
233             PDL_IF_BAD(char c_isbad = 0;,)
234             loop (t=t_outer:t_outer+tsiz) %{
235             PDL_IF_BAD(if ($ISBADVAR(*ad,a) || $ISBADVAR(*bd,b)) { c_isbad = 1; break; },)
236             cc += *ad * *bd;
237             ad += atdi;
238             bd += btdi;
239             %}
240             // put the output back to be further accumulated later
241             PDL_IF_BAD(if (c_isbad) { $SETBAD(c()); continue; },)
242             $c() = cc;
243             %}
244             %}
245             %}
246             EOC
247             Doc => <<'EOD'
248             =for ref
249              
250             Matrix multiplication
251              
252             Notionally, matrix multiplication $x x $y is equivalent to the
253             broadcasting expression
254              
255             $x->dummy(1)->inner($y->xchg(0,1)->dummy(2),$c);
256              
257             but for large matrices that breaks CPU cache and is slow. Instead,
258             matmult calculates its result in 32x32x32 tiles, to keep the memory
259             footprint within cache as long as possible on most modern CPUs.
260              
261             For usage, see L, a description of the overloaded 'x' operator
262              
263             EOD
264             );
265              
266             pp_def('innerwt',
267             HandleBad => 1,
268             Pars => 'a(n); b(n); c(n); [o]d();',
269             GenericTypes => [ppdefs_all],
270             Code => <<'EOF',
271             complex long double tmp = 0;
272             PDL_IF_BAD(int flag = 0;,)
273             loop(n) %{
274             PDL_IF_BAD(if ($ISBAD(a()) || $ISBAD(b()) || $ISBAD(c())) continue;flag = 1;,)
275             tmp += $a() * $b() * $c();
276             %}
277             PDL_IF_BAD(if (!flag) { $SETBAD(d()); }
278             else,) { $d() = tmp; }
279             EOF
280             Doc => '
281              
282             =for ref
283              
284             Weighted (i.e. triple) inner product
285              
286             d = sum_i a(i) b(i) c(i)
287             '
288             );
289              
290             pp_def('inner2',
291             HandleBad => 1,
292             Pars => 'a(n); b(n,m); c(m); [o]d();',
293             GenericTypes => [ppdefs_all],
294             Code => <<'EOF',
295             complex long double tmp = 0;
296             PDL_IF_BAD(int flag = 0;,)
297             loop(n,m) %{
298             PDL_IF_BAD(if ($ISBAD(a()) || $ISBAD(b()) || $ISBAD(c())) continue;flag = 1;,)
299             tmp += $a() * $b() * $c();
300             %}
301             PDL_IF_BAD(if (!flag) { $SETBAD(d()); }
302             else,) { $d() = tmp; }
303             EOF
304             Doc => '
305             =for ref
306              
307             Inner product of two vectors and a matrix
308              
309             d = sum_ij a(i) b(i,j) c(j)
310              
311             Note that you should probably not broadcast over C and C since that would be
312             very wasteful. Instead, you should use a temporary for C.
313             '
314             );
315              
316             pp_def('inner2d',
317             HandleBad => 1,
318             Pars => 'a(n,m); b(n,m); [o]c();',
319             GenericTypes => [ppdefs_all],
320             Code => <<'EOF',
321             complex long double tmp = 0;
322             PDL_IF_BAD(int flag = 0;,)
323             loop(n,m) %{
324             PDL_IF_BAD(if ($ISBAD(a()) || $ISBAD(b())) continue;flag = 1;,)
325             tmp += $a() * $b();
326             %}
327             PDL_IF_BAD(if (!flag) { $SETBAD(c()); }
328             else,) { $c() = tmp; }
329             EOF
330             Doc => '
331             =for ref
332              
333             Inner product over 2 dimensions.
334              
335             Equivalent to
336              
337             $c = inner($x->clump(2), $y->clump(2))
338             '
339             );
340              
341             pp_def('inner2t',
342             HandleBad => 1,
343             Pars => 'a(j,n); b(n,m); c(m,k); [t]tmp(n,k); [o]d(j,k);',
344             GenericTypes => [ppdefs_all],
345             Code => <<'EOF',
346             loop(n,k) %{
347             complex long double tmp0 = 0;
348             PDL_IF_BAD(int flag = 0;,)
349             loop(m) %{
350             PDL_IF_BAD(if ($ISBAD(b()) || $ISBAD(c())) continue;flag = 1;,)
351             tmp0 += $b() * $c();
352             %}
353             PDL_IF_BAD(if (!flag) { $SETBAD(tmp()); }
354             else,) { $tmp() = tmp0; }
355             %}
356             loop(j,k) %{
357             complex long double tmp1 = 0;
358             PDL_IF_BAD(int flag = 0;,)
359             loop(n) %{
360             PDL_IF_BAD(if ($ISBAD(a()) || $ISBAD(tmp())) continue;flag = 1;,)
361             tmp1 += $a() * $tmp();
362             %}
363             PDL_IF_BAD(if (!flag) { $SETBAD(d()); }
364             else,) { $d() = tmp1; }
365             %}
366             EOF
367             Doc => '
368             =for ref
369              
370             Efficient Triple matrix product C
371              
372             Efficiency comes from by using the temporary C. This operation only
373             scales as C whereas broadcasting using L would scale
374             as C.
375              
376             The reason for having this routine is that you do not need to
377             have the same broadcast-dimensions for C as for the other arguments,
378             which in case of large numbers of matrices makes this much more
379             memory-efficient.
380              
381             It is hoped that things like this could be taken care of as a kind of
382             closure at some point.
383             '
384             ); # pp_def inner2t()
385              
386             # a helper function for the cross product definition
387             sub crassgn {
388             "\$c(tri => $_[0]) = \$a(tri => $_[1])*\$b(tri => $_[2]) -
389             \$a(tri => $_[2])*\$b(tri => $_[1]);"
390             }
391              
392             pp_def('crossp',
393             Doc => <<'EOD',
394             =for ref
395              
396             Cross product of two 3D vectors
397              
398             After
399              
400             =for example
401              
402             $c = crossp $x, $y
403              
404             the inner product C<$c*$x> and C<$c*$y> will be zero, i.e. C<$c> is
405             orthogonal to C<$x> and C<$y>
406             EOD
407             Pars => 'a(tri=3); b(tri); [o] c(tri)',
408             GenericTypes => [ppdefs_all],
409             Code =>
410             crassgn(0,1,2)."\n".
411             crassgn(1,2,0)."\n".
412             crassgn(2,0,1),
413             );
414              
415             pp_def('norm',
416             HandleBad => 1,
417             Pars => 'vec(n); [o] norm(n)',
418             GenericTypes => [ppdefs_all],
419             Doc => 'Normalises a vector to unit Euclidean length
420              
421             See also L, L.
422             ',
423             Code => <<'EOF',
424             long double sum=0;
425             PDL_IF_BAD(int flag = 0;,)
426             loop(n) %{
427             PDL_IF_BAD(if ($ISBAD(vec())) continue; flag = 1;,)
428             sum += PDL_IF_GENTYPE_REAL(
429             $vec()*$vec(),
430             creall($vec())*creall($vec()) + cimagl($vec())*cimagl($vec())
431             );
432             %}
433             PDL_IF_BAD(if ( !flag ) {
434             loop(n) %{ $SETBAD(norm()); %}
435             continue;
436             },)
437             if (sum > 0) {
438             sum = sqrtl(sum);
439             loop(n) %{
440             PDL_IF_BAD(if ( $ISBAD(vec()) ) { $SETBAD(norm()); }
441             else ,) { $norm() = $vec()/sum; }
442             %}
443             } else {
444             loop(n) %{
445             PDL_IF_BAD(if ( $ISBAD(vec()) ) { $SETBAD(norm()); }
446             else ,) { $norm() = $vec(); }
447             %}
448             }
449             EOF
450             );
451              
452             # this one was motivated by the need to compute
453             # the circular mean efficiently
454             # without it could not be done efficiently or without
455             # creating large intermediates (check pdl-porters for
456             # discussion)
457             # see PDL::ImageND for info about the circ_mean function
458              
459             pp_def(
460             'indadd',
461             HandleBad => 1,
462             Pars => 'input(n); indx ind(n); [io] sum(m)',
463             GenericTypes => [ppdefs_all],
464             Code => <<'EOF',
465             loop(n) %{
466             register PDL_Indx this_ind = $ind();
467             PDL_IF_BAD(
468             if ($ISBADVAR(this_ind,ind)) $CROAK("bad index %"IND_FLAG, n);
469             if ($ISBAD(input())) { $SETBAD(sum(m => this_ind)); continue; },)
470             if (this_ind<0 || this_ind>=$SIZE(m))
471             $CROAK("invalid index %"IND_FLAG"; range 0..%"IND_FLAG, this_ind, $SIZE(m));
472             $sum(m => this_ind) += $input();
473             %}
474             EOF
475             BadDoc => 'The routine barfs on bad indices, and bad inputs set target outputs bad.',
476             Doc=>'
477             =for ref
478              
479             Broadcasting index add: add C to the C element of C, i.e:
480              
481             sum(ind) += input
482              
483             =for example
484              
485             Simple example:
486              
487             $x = 2;
488             $ind = 3;
489             $sum = zeroes(10);
490             indadd($x,$ind, $sum);
491             print $sum
492             #Result: ( 2 added to element 3 of $sum)
493             # [0 0 0 2 0 0 0 0 0 0]
494              
495             Broadcasting example:
496              
497             $x = pdl( 1,2,3);
498             $ind = pdl( 1,4,6);
499             $sum = zeroes(10);
500             indadd($x,$ind, $sum);
501             print $sum."\n";
502             #Result: ( 1, 2, and 3 added to elements 1,4,6 $sum)
503             # [0 1 0 0 2 0 3 0 0 0]
504              
505             =cut
506             ');
507              
508             # 1D convolution
509             # useful for broadcasted 1D filters
510             pp_def('conv1d',
511             Doc => << 'EOD',
512              
513             =for ref
514              
515             1D convolution along first dimension
516              
517             The m-th element of the discrete convolution of an input ndarray
518             C<$a> of size C<$M>, and a kernel ndarray C<$kern> of size C<$P>, is
519             calculated as
520              
521             n = ($P-1)/2
522             ====
523             \
524             ($a conv1d $kern)[m] = > $a_ext[m - n] * $kern[n]
525             /
526             ====
527             n = -($P-1)/2
528              
529             where C<$a_ext> is either the periodic (or reflected) extension of
530             C<$a> so it is equal to C<$a> on C< 0..$M-1 > and equal to the
531             corresponding periodic/reflected image of C<$a> outside that range.
532              
533              
534             =for example
535              
536             $con = conv1d sequence(10), pdl(-1,0,1);
537              
538             $con = conv1d sequence(10), pdl(-1,0,1), {Boundary => 'reflect'};
539              
540             By default, periodic boundary conditions are assumed (i.e. wrap around).
541             Alternatively, you can request reflective boundary conditions using
542             the C option:
543              
544             {Boundary => 'reflect'} # case in 'reflect' doesn't matter
545              
546             The convolution is performed along the first dimension. To apply it across
547             another dimension use the slicing routines, e.g.
548              
549             $y = $x->mv(2,0)->conv1d($kernel)->mv(0,2); # along third dim
550              
551             This function is useful for broadcasted filtering of 1D signals.
552              
553             Compare also L, L,
554             L
555              
556             =for bad
557              
558             WARNING: C processes bad values in its inputs as
559             the numeric value of C<< $pdl->badvalue >> so it is not
560             recommended for processing pdls with bad values in them
561             unless special care is taken.
562             EOD
563             Pars => 'a(m); kern(p); [o]b(m);',
564             GenericTypes => [ppdefs_all],
565             OtherPars => 'int reflect;',
566             HandleBad => 0,
567             PMCode => pp_line_numbers(__LINE__, <<'EOPM'),
568             sub PDL::conv1d {
569             my $opt = pop @_ if ref($_[-1]) eq 'HASH';
570             die 'Usage: conv1d( a(m), kern(p), [o]b(m), {Options} )'
571             if @_<2 || @_>3;
572             my($x,$kern) = @_;
573             my $c = @_ == 3 ? $_[2] : PDL->null;
574             PDL::_conv1d_int($x,$kern,$c,
575             !(defined $opt && exists $$opt{Boundary}) ? 0 :
576             lc $$opt{Boundary} eq "reflect");
577             return $c;
578             }
579             EOPM
580             CHeader => '
581             /* Fast Modulus with proper negative behaviour */
582             #define REALMOD(a,b) while ((a)>=(b)) (a) -= (b); while ((a)<0) (a) += (b);
583             ',
584             Code => '
585             int reflect = $COMP(reflect);
586             PDL_Indx m_size = $SIZE(m), p_size = $SIZE(p);
587             PDL_Indx poff = (p_size-1)/2;
588             loop(m) %{
589             complex long double tmp = 0;
590             loop(p) %{
591             PDL_Indx pflip = p_size - 1 - p, i2 = m+p - poff;
592             if (reflect && i2<0)
593             i2 = -i2;
594             if (reflect && i2>=m_size)
595             i2 = m_size-(i2-m_size+1);
596             REALMOD(i2,m_size);
597             tmp += $a(m=>i2) * $kern(p=>pflip);
598             %}
599             $b() = tmp;
600             %}
601             ');
602              
603              
604             # this can be achieved by
605             # ($x->dummy(0) == $y)->orover
606             # but this one avoids a larger intermediate and potentially shortcuts
607             pp_def('in',
608             Pars => 'a(); b(n); [o] c()',
609             GenericTypes => [ppdefs_all],
610             Code => '$c() = 0;
611             loop(n) %{ if ($a() == $b()) {$c() = 1; break;} %}',
612             Doc => <<'EOD',
613              
614             =for ref
615              
616             test if a is in the set of values b
617              
618             =for example
619              
620             $goodmsk = $labels->in($goodlabels);
621             print pdl(3,1,4,6,2)->in(pdl(2,3,3));
622             [1 0 0 0 1]
623              
624             C is akin to the I of set theory. In principle,
625             PDL broadcasting could be used to achieve its functionality by using a
626             construct like
627              
628             $msk = ($labels->dummy(0) == $goodlabels)->orover;
629              
630             However, C doesn't create a (potentially large) intermediate
631             and is generally faster.
632             EOD
633             );
634              
635             pp_add_exported ('', 'uniq');
636             pp_addpm (<< 'EOPM');
637             =head2 uniq
638              
639             =for ref
640              
641             return all unique elements of an ndarray
642              
643             The unique elements are returned in ascending order.
644              
645             =for example
646              
647             PDL> p pdl(2,2,2,4,0,-1,6,6)->uniq
648             [-1 0 2 4 6] # 0 is returned 2nd (sorted order)
649              
650             PDL> p pdl(2,2,2,4,nan,-1,6,6)->uniq
651             [-1 2 4 6 nan] # NaN value is returned at end
652              
653             Note: The returned pdl is 1D; any structure of the input
654             ndarray is lost. C values are never compare equal to
655             any other values, even themselves. As a result, they are
656             always unique. C returns the NaN values at the end
657             of the result ndarray. This follows the Matlab usage.
658              
659             See L if you need the indices of the unique
660             elements rather than the values.
661              
662             =for bad
663              
664             Bad values are not considered unique by uniq and are ignored.
665              
666             $x=sequence(10);
667             $x=$x->setbadif($x%3);
668             print $x->uniq;
669             [0 3 6 9]
670              
671             =cut
672              
673             *uniq = \&PDL::uniq;
674             # return unique elements of array
675             # find as jumps in the sorted array
676             # flattens in the process
677             sub PDL::uniq {
678             my ($arr) = @_;
679             return $arr if($arr->nelem == 0); # The null list is unique (CED)
680             return $arr->flat if($arr->nelem == 1); # singleton list is unique
681             my $aflat = $arr->flat;
682             my $srt = $aflat->where($aflat==$aflat)->qsort; # no NaNs or BADs for qsort
683             my $nans = $aflat->where($aflat!=$aflat);
684             my $uniq = ($srt->nelem > 1) ? $srt->where($srt != $srt->rotate(-1)) : $srt;
685             # make sure we return something if there is only one value
686             (
687             $uniq->nelem > 0 ? $uniq :
688             $srt->nelem == 0 ? $srt :
689             PDL::pdl( ref($srt), [$srt->index(0)] )
690             )->append($nans);
691             }
692             EOPM
693              
694             pp_add_exported ('', 'uniqind');
695             pp_addpm (<< 'EOPM');
696             =head2 uniqind
697              
698             =for ref
699              
700             Return the indices of all unique elements of an ndarray
701             The order is in the order of the values to be consistent
702             with uniq. C values never compare equal with any
703             other value and so are always unique. This follows the
704             Matlab usage.
705              
706             =for example
707              
708             PDL> p pdl(2,2,2,4,0,-1,6,6)->uniqind
709             [5 4 1 3 6] # the 0 at index 4 is returned 2nd, but...
710              
711             PDL> p pdl(2,2,2,4,nan,-1,6,6)->uniqind
712             [5 1 3 6 4] # ...the NaN at index 4 is returned at end
713              
714              
715             Note: The returned pdl is 1D; any structure of the input
716             ndarray is lost.
717              
718             See L if you want the unique values instead of the
719             indices.
720              
721             =for bad
722              
723             Bad values are not considered unique by uniqind and are ignored.
724              
725             =cut
726              
727             *uniqind = \&PDL::uniqind;
728             # return unique elements of array
729             # find as jumps in the sorted array
730             # flattens in the process
731             sub PDL::uniqind {
732             use PDL::Core 'barf';
733             my ($arr) = @_;
734             return $arr if($arr->nelem == 0); # The null list is unique (CED)
735             # Different from uniq we sort and store the result in an intermediary
736             my $aflat = $arr->flat;
737             my $nanind = which($aflat!=$aflat); # NaN indexes
738             my $good = PDL->sequence(indx, $aflat->dims)->where($aflat==$aflat); # good indexes
739             my $i_srt = $aflat->where($aflat==$aflat)->qsorti; # no BAD or NaN values for qsorti
740             my $srt = $aflat->where($aflat==$aflat)->index($i_srt);
741             my $uniqind;
742             if ($srt->nelem > 0) {
743             $uniqind = which($srt != $srt->rotate(-1));
744             $uniqind = $i_srt->slice('0') if $uniqind->isempty;
745             } else {
746             $uniqind = which($srt);
747             }
748             # Now map back to the original space
749             my $ansind = $nanind;
750             if ( $uniqind->nelem > 0 ) {
751             $ansind = ($good->index($i_srt->index($uniqind)))->append($ansind);
752             } else {
753             $ansind = $uniqind->append($ansind);
754             }
755             return $ansind;
756             }
757              
758             EOPM
759              
760             pp_add_exported ('', 'uniqvec');
761             pp_addpm (<< 'EOPM');
762             =head2 uniqvec
763              
764             =for ref
765              
766             Return all unique vectors out of a collection
767              
768             NOTE: If any vectors in the input ndarray have NaN values
769             they are returned at the end of the non-NaN ones. This is
770             because, by definition, NaN values never compare equal with
771             any other value.
772              
773             NOTE: The current implementation does not sort the vectors
774             containing NaN values.
775              
776             The unique vectors are returned in lexicographically sorted
777             ascending order. The 0th dimension of the input PDL is treated
778             as a dimensional index within each vector, and the 1st and any
779             higher dimensions are taken to run across vectors. The return
780             value is always 2D; any structure of the input PDL (beyond using
781             the 0th dimension for vector index) is lost.
782              
783             See also L for a unique list of scalars; and
784             L for sorting a list of vectors
785             lexicographcally.
786              
787             =for bad
788              
789             If a vector contains all bad values, it is ignored as in L.
790             If some of the values are good, it is treated as a normal vector. For
791             example, [1 2 BAD] and [BAD 2 3] could be returned, but [BAD BAD BAD]
792             could not. Vectors containing BAD values will be returned after any
793             non-NaN and non-BAD containing vectors, followed by the NaN vectors.
794              
795             =cut
796              
797             sub PDL::uniqvec {
798             my($pdl) = shift;
799              
800             return $pdl if ( $pdl->nelem == 0 || $pdl->ndims < 2 );
801             return $pdl if ( $pdl->slice("(0)")->nelem < 2 ); # slice isn't cheap but uniqvec isn't either
802              
803             my $pdl2d = $pdl->clump(1..$pdl->ndims-1);
804             my $ngood = $pdl2d->ngoodover;
805             $pdl2d = $pdl2d->mv(0,-1)->dice($ngood->which)->mv(-1,0); # remove all-BAD vectors
806              
807             my $numnan = ($pdl2d!=$pdl2d)->sumover; # works since no all-BADs to confuse
808              
809             my $presrt = $pdl2d->mv(0,-1)->dice($numnan->not->which)->mv(0,-1); # remove vectors with any NaN values
810             my $nanvec = $pdl2d->mv(0,-1)->dice($numnan->which)->mv(0,-1); # the vectors with any NaN values
811              
812             my $srt = $presrt->qsortvec->mv(0,-1); # BADs are sorted by qsortvec
813             my $srtdice = $srt;
814             my $somebad = null;
815             if ($srt->badflag) {
816             $srtdice = $srt->dice($srt->mv(0,-1)->nbadover->not->which);
817             $somebad = $srt->dice($srt->mv(0,-1)->nbadover->which);
818             }
819              
820             my $uniq = $srtdice->nelem > 0
821             ? ($srtdice != $srtdice->rotate(-1))->mv(0,-1)->orover->which
822             : $srtdice->orover->which;
823              
824             my $ans = $uniq->nelem > 0 ? $srtdice->dice($uniq) :
825             ($srtdice->nelem > 0) ? $srtdice->slice("0,:") :
826             $srtdice;
827             return $ans->append($somebad)->append($nanvec->mv(0,-1))->mv(0,-1);
828             }
829              
830             EOPM
831              
832             #####################################################################
833             # clipping routines
834             #####################################################################
835              
836             # clipping
837              
838             for my $opt (
839             ['hclip','PDLMIN'],
840             ['lclip','PDLMAX']
841             ) {
842             my $name = $opt->[0];
843             my $op = $opt->[1];
844             my $code = '$c() = '.$op.'($b(), $a());';
845             pp_def(
846             $name,
847             HandleBad => 1,
848             Pars => 'a(); b(); [o] c()',
849             Code =>
850             'PDL_IF_BAD(if ( $ISBAD(a()) || $ISBAD(b()) ) {
851             $SETBAD(c());
852             } else,) { '.$code.' }',
853             Doc => 'clip (threshold) C<$a> by C<$b> (C<$b> is '.
854             ($name eq 'hclip' ? 'upper' : 'lower').' bound)',
855             PMCode=>pp_line_numbers(__LINE__, <<"EOD"),
856             sub PDL::$name {
857             my (\$x,\$y) = \@_;
858             my \$c;
859             if (\$x->is_inplace) {
860             \$x->set_inplace(0); \$c = \$x;
861             } elsif (\@_ > 2) {\$c=\$_[2]} else {\$c=PDL->nullcreate(\$x)}
862             PDL::_${name}_int(\$x,\$y,\$c);
863             return \$c;
864             }
865             EOD
866             ); # pp_def $name
867              
868             } # for: my $opt
869              
870             pp_def('clip',
871             HandleBad => 1,
872             Pars => 'a(); l(); h(); [o] c()',
873             Code => <<'EOBC',
874             PDL_IF_BAD(
875             if( $ISBAD(a()) || $ISBAD(l()) || $ISBAD(h()) ) {
876             $SETBAD(c());
877             } else,) {
878             $c() = PDLMIN($h(), PDLMAX($l(), $a()));
879             }
880             EOBC
881             Doc => <<'EOD',
882             =for ref
883              
884             Clip (threshold) an ndarray by (optional) upper or lower bounds.
885              
886             =for usage
887              
888             $y = $x->clip(0,3);
889             $c = $x->clip(undef, $x);
890             EOD
891             PMCode=>pp_line_numbers(__LINE__, <<'EOPM'),
892             *clip = \&PDL::clip;
893             sub PDL::clip {
894             my($x, $l, $h) = @_;
895             my $d;
896             unless(defined($l) || defined($h)) {
897             # Deal with pathological case
898             if($x->is_inplace) {
899             $x->set_inplace(0);
900             return $x;
901             } else {
902             return $x->copy;
903             }
904             }
905              
906             if($x->is_inplace) {
907             $x->set_inplace(0); $d = $x
908             } elsif (@_ > 3) {
909             $d=$_[3]
910             } else {
911             $d = PDL->nullcreate($x);
912             }
913             if(defined($l) && defined($h)) {
914             PDL::_clip_int($x,$l,$h,$d);
915             } elsif( defined($l) ) {
916             PDL::_lclip_int($x,$l,$d);
917             } elsif( defined($h) ) {
918             PDL::_hclip_int($x,$h,$d);
919             } else {
920             die "This can't happen (clip contingency) - file a bug";
921             }
922              
923             return $d;
924             }
925             EOPM
926             ); # end of clip pp_def call
927              
928             ############################################################
929             # elementary statistics and histograms
930             ############################################################
931              
932             pp_def('wtstat',
933             HandleBad => 1,
934             Pars => 'a(n); wt(n); avg(); [o]b();',
935             GenericTypes => [ppdefs_all],
936             OtherPars => 'int deg',
937             Code => <<'EOF',
938             complex long double wtsum = 0;
939             complex long double statsum = 0;
940             PDL_IF_BAD(int flag = 0;,)
941             loop(n) %{
942             PDL_IF_BAD(if ($ISBAD(wt()) || $ISBAD(a()) || $ISBAD(avg())) continue;flag = 1;,)
943             PDL_Indx i;
944             wtsum += $wt();
945             complex long double tmp=1;
946             for(i=0; i<$COMP(deg); i++)
947             tmp *= $a();
948             statsum += $wt() * (tmp - $avg());
949             %}
950             PDL_IF_BAD(if (!flag) { $SETBAD(b()); $PDLSTATESETBAD(b); }
951             else,) { $b() = statsum / wtsum; }
952             EOF
953             Doc => '
954             =for ref
955              
956             Weighted statistical moment of given degree
957              
958             This calculates a weighted statistic over the vector C.
959             The formula is
960              
961             b() = (sum_i wt_i * (a_i ** degree - avg)) / (sum_i wt_i)
962             ',
963             BadDoc => '
964             Bad values are ignored in any calculation; C<$b> will only
965             have its bad flag set if the output contains any bad data.
966             ',
967             );
968              
969             pp_def('statsover',
970             HandleBad => 1,
971             Pars => 'a(n); w(n); float+ [o]avg(); float+ [o]prms(); int+ [o]min(); int+ [o]max(); float+ [o]adev(); float+ [o]rms()',
972             Code => <<'EOF',
973             PDL_IF_GENTYPE_REAL(PDL_LDouble,PDL_CLDouble) tmp = 0, tmp1 = 0, diff = 0;
974             $GENERIC(min) curmin = 0, curmax = 0;
975             $GENERIC(w) norm = 0;
976             int flag = 0;
977             loop(n) %{ /* Accumulate sum and summed weight. */
978             /* perhaps should check w() for bad values too ? */
979             PDL_IF_BAD(if (!( $ISGOOD(a()) )) continue;,)
980             tmp += $a()*$w();
981             norm += ($GENERIC(avg)) $w();
982             if (!flag) { curmin = $a(); curmax = $a(); flag=1; }
983             if ($a() < curmin) {
984             curmin = $a();
985             } else if ($a() > curmax) {
986             curmax = $a();
987             }
988             %}
989             /* have at least one valid point if flag true */
990             PDL_IF_BAD(if ( !flag ) {
991             $SETBAD(avg()); $PDLSTATESETBAD(avg);
992             $SETBAD(rms()); $PDLSTATESETBAD(rms);
993             $SETBAD(adev()); $PDLSTATESETBAD(adev);
994             $SETBAD(min()); $PDLSTATESETBAD(min);
995             $SETBAD(max()); $PDLSTATESETBAD(max);
996             $SETBAD(prms()); $PDLSTATESETBAD(prms);
997             continue;
998             },)
999             $avg() = tmp / norm; /* Find mean */
1000             $min() = curmin;
1001             $max() = curmax;
1002             /* Calculate the RMS and standard deviation. */
1003             tmp = 0;
1004             loop(n) %{
1005             PDL_IF_BAD(if (!$ISGOOD(a())) continue;,)
1006             diff = $a()-$avg();
1007             tmp += diff * diff * $w();
1008             tmp1 += PDL_IF_GENTYPE_REAL(fabsl,cabsl)(diff) * $w();
1009             %}
1010             $rms() = sqrtl( tmp/norm );
1011             if (norm>1)
1012             $prms() = sqrtl( tmp/(norm-1) );
1013             else
1014             PDL_IF_BAD($SETBAD(prms()),$prms() = 0);
1015             $adev() = tmp1 / norm ;
1016             EOF
1017             PMCode=>pp_line_numbers(__LINE__, <<'EOPM'),
1018             sub PDL::statsover {
1019             barf('Usage: ($mean,[$prms, $median, $min, $max, $adev, $rms]) = statsover($data,[$weights])') if @_>2;
1020             my ($data, $weights) = @_;
1021             $weights //= $data->ones();
1022             my $median = $data->medover;
1023             my $mean = PDL->nullcreate($data);
1024             my $rms = PDL->nullcreate($data);
1025             my $min = PDL->nullcreate($data);
1026             my $max = PDL->nullcreate($data);
1027             my $adev = PDL->nullcreate($data);
1028             my $prms = PDL->nullcreate($data);
1029             PDL::_statsover_int($data, $weights, $mean, $prms, $min, $max, $adev, $rms);
1030             wantarray ? ($mean, $prms, $median, $min, $max, $adev, $rms) : $mean;
1031             }
1032             EOPM
1033             Doc => '
1034             =for ref
1035              
1036             Calculate useful statistics over a dimension of an ndarray
1037              
1038             =for usage
1039              
1040             ($mean,$prms,$median,$min,$max,$adev,$rms) = statsover($ndarray, $weights);
1041              
1042             This utility function calculates various useful
1043             quantities of an ndarray. These are:
1044              
1045             =over 3
1046              
1047             =item * the mean:
1048              
1049             MEAN = sum (x)/ N
1050              
1051             with C being the number of elements in x
1052              
1053             =item * the population RMS deviation from the mean:
1054              
1055             PRMS = sqrt( sum( (x-mean(x))^2 )/(N-1) )
1056              
1057             The population deviation is the best-estimate of the deviation
1058             of the population from which a sample is drawn.
1059              
1060             =item * the median
1061              
1062             The median is the 50th percentile data value. Median is found by
1063             L, so WEIGHTING IS IGNORED FOR THE MEDIAN CALCULATION.
1064              
1065             =item * the minimum
1066              
1067             =item * the maximum
1068              
1069             =item * the average absolute deviation:
1070              
1071             AADEV = sum( abs(x-mean(x)) )/N
1072              
1073             =item * RMS deviation from the mean:
1074              
1075             RMS = sqrt(sum( (x-mean(x))^2 )/N)
1076              
1077             (also known as the root-mean-square deviation, or the square root of the
1078             variance)
1079              
1080             =back
1081              
1082             This operator is a projection operator so the calculation
1083             will take place over the final dimension. Thus if the input
1084             is N-dimensional each returned value will be N-1 dimensional,
1085             to calculate the statistics for the entire ndarray either
1086             use C directly on the ndarray or call C.
1087             ',
1088             BadDoc =>'
1089             Bad values are simply ignored in the calculation, effectively reducing
1090             the sample size. If all data are bad then the output data are marked bad.
1091             ',
1092             );
1093              
1094             pp_add_exported('','stats');
1095             pp_addpm(<<'EOD');
1096             =head2 stats
1097              
1098             =for ref
1099              
1100             Calculates useful statistics on an ndarray
1101              
1102             =for usage
1103              
1104             ($mean,$prms,$median,$min,$max,$adev,$rms) = stats($ndarray,[$weights]);
1105              
1106             This utility calculates all the most useful quantities in one call.
1107             It works the same way as L, except that the quantities are
1108             calculated considering the entire input PDL as a single sample, rather
1109             than as a collection of rows. See L for definitions of the
1110             returned quantities.
1111              
1112             =for bad
1113              
1114             Bad values are handled; if all input values are bad, then all of the output
1115             values are flagged bad.
1116              
1117             =cut
1118              
1119             *stats = \&PDL::stats;
1120             sub PDL::stats {
1121             barf('Usage: ($mean,[$rms]) = stats($data,[$weights])') if @_>2;
1122             my ($data,$weights) = @_;
1123              
1124             # Ensure that $weights is properly broadcasted over; this could be
1125             # done rather more efficiently...
1126             if(defined $weights) {
1127             $weights = pdl($weights) unless UNIVERSAL::isa($weights,'PDL');
1128             if( ($weights->ndims != $data->ndims) or
1129             (pdl($weights->dims) != pdl($data->dims))->or
1130             ) {
1131             $weights = $weights + zeroes($data)
1132             }
1133             $weights = $weights->flat;
1134             }
1135              
1136             return PDL::statsover($data->flat,$weights);
1137             }
1138             EOD
1139              
1140             my $histogram_doc = <<'EOD';
1141             =for ref
1142              
1143             Calculates a histogram for given stepsize and minimum.
1144              
1145             =for usage
1146              
1147             $h = histogram($data, $step, $min, $numbins);
1148             $hist = zeroes $numbins; # Put histogram in existing ndarray.
1149             histogram($data, $hist, $step, $min, $numbins);
1150              
1151             The histogram will contain C<$numbins> bins starting from C<$min>, each
1152             C<$step> wide. The value in each bin is the number of
1153             values in C<$data> that lie within the bin limits.
1154              
1155              
1156             Data below the lower limit is put in the first bin, and data above the
1157             upper limit is put in the last bin.
1158              
1159             The output is reset in a different broadcastloop so that you
1160             can take a histogram of C<$a(10,12)> into C<$b(15)> and get the result
1161             you want.
1162              
1163             For a higher-level interface, see L.
1164              
1165             =for example
1166              
1167             pdl> p histogram(pdl(1,1,2),1,0,3)
1168             [0 2 1]
1169              
1170             =cut
1171              
1172             EOD
1173              
1174             my $whistogram_doc = <<'EOD';
1175             =for ref
1176              
1177             Calculates a histogram from weighted data for given stepsize and minimum.
1178              
1179             =for usage
1180              
1181             $h = whistogram($data, $weights, $step, $min, $numbins);
1182             $hist = zeroes $numbins; # Put histogram in existing ndarray.
1183             whistogram($data, $weights, $hist, $step, $min, $numbins);
1184              
1185             The histogram will contain C<$numbins> bins starting from C<$min>, each
1186             C<$step> wide. The value in each bin is the sum of the values in C<$weights>
1187             that correspond to values in C<$data> that lie within the bin limits.
1188              
1189             Data below the lower limit is put in the first bin, and data above the
1190             upper limit is put in the last bin.
1191              
1192             The output is reset in a different broadcastloop so that you
1193             can take a histogram of C<$a(10,12)> into C<$b(15)> and get the result
1194             you want.
1195              
1196             =for example
1197              
1198             pdl> p whistogram(pdl(1,1,2), pdl(0.1,0.1,0.5), 1, 0, 4)
1199             [0 0.2 0.5 0]
1200              
1201             =cut
1202             EOD
1203              
1204             for(
1205             {Name => 'histogram',
1206             WeightPar => '',
1207             HistType => 'int+',
1208             HistOp => '+= 1',
1209             Doc => $histogram_doc,
1210             },
1211             {Name => 'whistogram',
1212             WeightPar => 'float+ wt(n);',
1213             HistType => 'float+',
1214             HistOp => '+= $wt()',
1215             Doc => $whistogram_doc,
1216             }
1217             )
1218             {
1219             pp_def($_->{Name},
1220             Pars => 'in(n); '.$_->{WeightPar}.$_->{HistType}. '[o] hist(m)',
1221             GenericTypes => [ppdefs_all],
1222             # set outdim by Par!
1223             OtherPars => 'double step; double min; IV msize => m',
1224             HandleBad => 1,
1225             RedoDimsCode => 'if ($SIZE(m) == 0) $CROAK("called with m dim of 0");',
1226             Code => pp_line_numbers(__LINE__-1, '
1227             register double min = $COMP(min), step = $COMP(step);
1228             broadcastloop %{
1229             loop(m) %{ $hist() = 0; %}
1230             loop(n) %{
1231             PDL_IF_BAD(if ( !$ISGOOD(in()) ) continue;,)
1232             PDL_Indx j = round((($in()-min)/step)-0.5);
1233             j = PDLMIN(PDLMAX(j, 0), $SIZE(m)-1);
1234             ($hist(m => j))'.$_->{HistOp}.';
1235             %}
1236             %}'),
1237             Doc=>$_->{Doc});
1238             }
1239              
1240             my $histogram2d_doc = <<'EOD';
1241             =for ref
1242              
1243             Calculates a 2d histogram.
1244              
1245             =for usage
1246              
1247             $h = histogram2d($datax, $datay, $stepx, $minx,
1248             $nbinx, $stepy, $miny, $nbiny);
1249             $hist = zeroes $nbinx, $nbiny; # Put histogram in existing ndarray.
1250             histogram2d($datax, $datay, $hist, $stepx, $minx,
1251             $nbinx, $stepy, $miny, $nbiny);
1252              
1253             The histogram will contain C<$nbinx> x C<$nbiny> bins, with the lower
1254             limits of the first one at C<($minx, $miny)>, and with bin size
1255             C<($stepx, $stepy)>.
1256             The value in each bin is the number of
1257             values in C<$datax> and C<$datay> that lie within the bin limits.
1258              
1259             Data below the lower limit is put in the first bin, and data above the
1260             upper limit is put in the last bin.
1261              
1262             =for example
1263              
1264             pdl> p histogram2d(pdl(1,1,1,2,2),pdl(2,1,1,1,1),1,0,3,1,0,3)
1265             [
1266             [0 0 0]
1267             [0 2 2]
1268             [0 1 0]
1269             ]
1270              
1271             =cut
1272             EOD
1273              
1274             my $whistogram2d_doc = <<'EOD';
1275             =for ref
1276              
1277             Calculates a 2d histogram from weighted data.
1278              
1279             =for usage
1280              
1281             $h = whistogram2d($datax, $datay, $weights,
1282             $stepx, $minx, $nbinx, $stepy, $miny, $nbiny);
1283             $hist = zeroes $nbinx, $nbiny; # Put histogram in existing ndarray.
1284             whistogram2d($datax, $datay, $weights, $hist,
1285             $stepx, $minx, $nbinx, $stepy, $miny, $nbiny);
1286              
1287             The histogram will contain C<$nbinx> x C<$nbiny> bins, with the lower
1288             limits of the first one at C<($minx, $miny)>, and with bin size
1289             C<($stepx, $stepy)>.
1290             The value in each bin is the sum of the values in
1291             C<$weights> that correspond to values in C<$datax> and C<$datay> that lie within the bin limits.
1292              
1293             Data below the lower limit is put in the first bin, and data above the
1294             upper limit is put in the last bin.
1295              
1296             =for example
1297              
1298             pdl> p whistogram2d(pdl(1,1,1,2,2),pdl(2,1,1,1,1),pdl(0.1,0.2,0.3,0.4,0.5),1,0,3,1,0,3)
1299             [
1300             [ 0 0 0]
1301             [ 0 0.5 0.9]
1302             [ 0 0.1 0]
1303             ]
1304              
1305             =cut
1306             EOD
1307              
1308             for(
1309             {Name => 'histogram2d',
1310             WeightPar => '',
1311             HistType => 'int+',
1312             HistOp => '+= 1',
1313             Doc => $histogram2d_doc,
1314             },
1315             {Name => 'whistogram2d',
1316             WeightPar => 'float+ wt(n);',
1317             HistType => 'float+',
1318             HistOp => '+= $wt()',
1319             Doc => $whistogram2d_doc,
1320             }
1321             )
1322             {
1323             pp_def($_->{Name},
1324             Pars => 'ina(n); inb(n); '.$_->{WeightPar}.$_->{HistType}. '[o] hist(ma,mb)',
1325             GenericTypes => [ppdefs_all],
1326             # set outdim by Par!
1327             OtherPars => 'double stepa; double mina; IV masize => ma;
1328             double stepb; double minb; IV mbsize => mb;',
1329             HandleBad => 1,
1330             RedoDimsCode => '
1331             if ($SIZE(ma) == 0) $CROAK("called with ma dim of 0");
1332             if ($SIZE(mb) == 0) $CROAK("called with mb dim of 0");
1333             ',
1334             Code => pp_line_numbers(__LINE__-1, '
1335             register double mina = $COMP(mina), minb = $COMP(minb), stepa = $COMP(stepa), stepb = $COMP(stepb);
1336             broadcastloop %{
1337             loop(ma,mb) %{ $hist() = 0; %}
1338             loop(n) %{
1339             PDL_IF_BAD(if (!( $ISGOOD(ina()) && $ISGOOD(inb()) )) continue;,)
1340             PDL_Indx ja = round((($ina()-mina)/stepa)-0.5);
1341             PDL_Indx jb = round((($inb()-minb)/stepb)-0.5);
1342             ja = PDLMIN(PDLMAX(ja, 0), $SIZE(ma)-1);
1343             jb = PDLMIN(PDLMAX(jb, 0), $SIZE(mb)-1);
1344             ($hist(ma => ja,mb => jb))'.$_->{HistOp}.';
1345             %}
1346             %}'),
1347             Doc=> $_->{Doc});
1348             }
1349              
1350              
1351             ###########################################################
1352             # a number of constructors: fibonacci, append, axisvalues &
1353             # random numbers
1354             ###########################################################
1355              
1356             pp_def('fibonacci',
1357             Pars => 'i(n); [o]x(n)',
1358             Inplace => 1,
1359             GenericTypes => [ppdefs_all],
1360             Doc=>'Constructor - a vector with Fibonacci\'s sequence',
1361             PMFunc=>'',
1362             PMCode=>pp_line_numbers(__LINE__, <<'EOD'),
1363             sub fibonacci { ref($_[0]) && ref($_[0]) ne 'PDL::Type' ? $_[0]->fibonacci : PDL->fibonacci(@_) }
1364             sub PDL::fibonacci{
1365             my $x = &PDL::Core::_construct;
1366             my $is_inplace = $x->is_inplace;
1367             my ($in, $out) = $x->flat;
1368             $out = $is_inplace ? $in->inplace : PDL->null;
1369             PDL::_fibonacci_int($in, $out);
1370             $out;
1371             }
1372             EOD
1373             Code => '
1374             PDL_Indx i=0;
1375             $GENERIC() x1, x2;
1376             x1 = 1; x2 = 0;
1377             loop(n) %{
1378             $x() = x1 + x2;
1379             if (i++>0) {
1380             x2 = x1;
1381             x1 = $x();
1382             }
1383             %}
1384             ');
1385              
1386             pp_def('append',
1387             Pars => 'a(n); b(m); [o] c(mn=CALC($SIZE(n)+$SIZE(m)))',
1388             GenericTypes => [ppdefs_all],
1389             PMCode => pp_line_numbers(__LINE__-1, '
1390             sub PDL::append {
1391             my ($i1, $i2, $o) = map PDL->topdl($_), @_;
1392             $_ = empty() for grep $_->isnull, $i1, $i2;
1393             my $nempty = grep $_->isempty, $i1, $i2;
1394             if ($nempty == 2) {
1395             my @dims = $i1->dims;
1396             $dims[0] += $i2->dim(0);
1397             return PDL->zeroes($i1->type, @dims);
1398             }
1399             $o //= PDL->null;
1400             $o .= $i1->isempty ? $i2 : $i1, return $o if $nempty == 1;
1401             PDL::_append_int($i1, $i2->convert($i1->type), $o);
1402             $o;
1403             }
1404             '),
1405             Code => 'PDL_Indx ns = $SIZE(n);
1406             broadcastloop %{
1407             loop(n) %{ $c(mn => n) = $a(); %}
1408             loop(mn=ns) %{ $c() = $b(m=>mn-ns); %}
1409             %}',
1410             Doc => '
1411             =for ref
1412              
1413             append two ndarrays by concatenating along their first dimensions
1414              
1415             =for example
1416              
1417             $x = ones(2,4,7);
1418             $y = sequence 5;
1419             $c = $x->append($y); # size of $c is now (7,4,7) (a jumbo-ndarray ;)
1420              
1421             C appends two ndarrays along their first dimensions. The rest of the
1422             dimensions must be compatible in the broadcasting sense. The resulting
1423             size of the first dimension is the sum of the sizes of the first dimensions
1424             of the two argument ndarrays - i.e. C.
1425              
1426             Similar functions include L (below), which can append more
1427             than two ndarrays along an arbitrary dimension, and
1428             L, which can append more than two ndarrays that all
1429             have the same sized dimensions.
1430             '
1431             );
1432              
1433             pp_addpm(<<'EOD');
1434             =head2 glue
1435              
1436             =for usage
1437              
1438             $c = $x->glue(,$y,...)
1439              
1440             =for ref
1441              
1442             Glue two or more PDLs together along an arbitrary dimension
1443             (N-D L).
1444              
1445             Sticks $x, $y, and all following arguments together along the
1446             specified dimension. All other dimensions must be compatible in the
1447             broadcasting sense.
1448              
1449             Glue is permissive, in the sense that every PDL is treated as having an
1450             infinite number of trivial dimensions of order 1 -- so C<< $x->glue(3,$y) >>
1451             works, even if $x and $y are only one dimensional.
1452              
1453             If one of the PDLs has no elements, it is ignored. Likewise, if one
1454             of them is actually the undefined value, it is treated as if it had no
1455             elements.
1456              
1457             If the first parameter is a defined perl scalar rather than a pdl,
1458             then it is taken as a dimension along which to glue everything else,
1459             so you can say C<$cube = PDL::glue(3,@image_list);> if you like.
1460              
1461             C is implemented in pdl, using a combination of L and
1462             L. It should probably be updated (one day) to a pure PP
1463             function.
1464              
1465             Similar functions include L (above), which appends
1466             only two ndarrays along their first dimension, and
1467             L, which can append more than two ndarrays that all
1468             have the same sized dimensions.
1469              
1470             =cut
1471              
1472             sub PDL::glue{
1473             my($x) = shift;
1474             my($dim) = shift;
1475              
1476             ($dim, $x) = ($x, $dim) if defined $x && !ref $x;
1477             confess 'dimension must be Perl scalar' if ref $dim;
1478              
1479             if(!defined $x || $x->nelem==0) {
1480             return $x unless(@_);
1481             return shift() if(@_<=1);
1482             $x=shift;
1483             return PDL::glue($x,$dim,@_);
1484             }
1485              
1486             if($dim - $x->dim(0) > 100) {
1487             print STDERR "warning:: PDL::glue allocating >100 dimensions!\n";
1488             }
1489             while($dim >= $x->ndims) {
1490             $x = $x->dummy(-1,1);
1491             }
1492             $x = $x->xchg(0,$dim) if 0 != $dim;
1493              
1494             while(scalar(@_)){
1495             my $y = shift;
1496             next unless(defined $y && $y->nelem);
1497              
1498             while($dim >= $y->ndims) {
1499             $y = $y->dummy(-1,1);
1500             }
1501             $y = $y->xchg(0,$dim) if 0 != $dim;
1502             $x = $x->append($y);
1503             }
1504             0 == $dim ? $x : $x->xchg(0,$dim);
1505             }
1506              
1507             EOD
1508              
1509             pp_def( 'axisvalues',
1510             Pars => 'i(n); [o]a(n)',
1511             Inplace => 1,
1512             Code => 'loop(n) %{ $a() = n; %}',
1513             GenericTypes => [ppdefs_all],
1514             Doc => undef,
1515             ); # pp_def: axisvalues
1516              
1517             pp_add_macros(
1518             CMPVEC => sub {
1519             my ($a, $b, $dim, $ret, $anybad) = @_;
1520             my $badbit = !defined $anybad ? '' : <
1521             PDL_IF_BAD(if (\$ISBAD($a) || \$ISBAD($b)) { $anybad = 1; break; } else,)
1522             EOF
1523             <
1524             $ret = 0;
1525             loop($dim) %{ $badbit if ($a != $b) { $ret = $a < $b ? -1 : 1; break; } %}
1526             EOF
1527             },
1528             );
1529              
1530             pp_def(
1531             'cmpvec',
1532             HandleBad => 1,
1533             Pars => 'a(n); b(n); sbyte [o]c();',
1534             Code => '
1535             PDL_IF_BAD(char anybad = 0;,)
1536             broadcastloop %{
1537             $CMPVEC($a(), $b(), n, $c(), anybad);
1538             PDL_IF_BAD(if (anybad) $SETBAD(c());,)
1539             %}
1540             PDL_IF_BAD(if (anybad) $PDLSTATESETBAD(c);,)
1541             ',
1542             Doc => '
1543             =for ref
1544              
1545             Compare two vectors lexicographically.
1546              
1547             Returns -1 if a is less, 1 if greater, 0 if equal.
1548             ',
1549             BadDoc => '
1550             The output is bad if any input values up to the point of inequality are
1551             bad - any after are ignored.
1552             ',
1553             );
1554              
1555             pp_def(
1556             'eqvec',
1557             HandleBad => 1,
1558             Pars => 'a(n); b(n); sbyte [o]c();',
1559             Code => '
1560             PDL_IF_BAD(char anybad = 0;,)
1561             broadcastloop %{
1562             $c() = 1;
1563             loop(n) %{
1564             PDL_IF_BAD(if ($ISBAD(a()) || $ISBAD(b())) { $SETBAD(c()); anybad = 1; break; }
1565             else,) if ($a() != $b()) { $c() = 0; PDL_IF_BAD(,break;) }
1566             %}
1567             %}
1568             PDL_IF_BAD(if (anybad) $PDLSTATESETBAD(c);,)
1569             ',
1570             Doc => 'Compare two vectors, returning 1 if equal, 0 if not equal.',
1571             BadDoc => 'The output is bad if any input values are bad.',
1572             );
1573              
1574             pp_def('enumvec',
1575             Pars => 'v(M,N); indx [o]k(N)',
1576             Code => pp_line_numbers(__LINE__, <<'EOC'),
1577             loop (N) %{
1578             PDL_Indx vn = N; /* preserve value of N into inner N loop */
1579             loop (N=vn) %{
1580             if (N == vn) { $k() = 0; continue; } /* no need to compare with self */
1581             char matches = 1;
1582             loop (M) %{
1583             if ($v(N=>vn) == $v()) continue;
1584             matches = 0;
1585             break;
1586             %}
1587             if (matches) {
1588             $k() = N-vn;
1589             if (N == $SIZE(N)-1) vn = N; /* last one matched, so stop */
1590             } else {
1591             vn = N-1;
1592             break;
1593             }
1594             %}
1595             N = vn; /* skip forward */
1596             %}
1597             EOC
1598             Doc =><<'EOD',
1599             =for ref
1600              
1601             Enumerate a list of vectors with locally unique keys.
1602              
1603             Given a sorted list of vectors $v, generate a vector $k containing locally unique keys for the elements of $v
1604             (where an "element" is a vector of length $M occurring in $v).
1605              
1606             Note that the keys returned in $k are only unique over a run of a single vector in $v,
1607             so that each unique vector in $v has at least one 0 (zero) index in $k associated with it.
1608             If you need global keys, see enumvecg().
1609              
1610             Contributed by Bryan Jurish Emoocow@cpan.orgE.
1611             EOD
1612             );
1613              
1614             ##------------------------------------------------------
1615             ## enumvecg()
1616             pp_def('enumvecg',
1617             Pars => 'v(M,N); indx [o]k(N)',
1618             Code =><<'EOC',
1619             if (!$SIZE(N)) return PDL_err;
1620             PDL_Indx Nprev = 0, ki = $k(N=>0) = 0;
1621             loop(N=1) %{
1622             loop (M) %{
1623             if ($v(N=>Nprev) == $v()) continue;
1624             ++ki;
1625             break;
1626             %}
1627             $k() = ki;
1628             Nprev = N;
1629             %}
1630             EOC
1631             Doc =><<'EOD',
1632             =for ref
1633              
1634             Enumerate a list of vectors with globally unique keys.
1635              
1636             Given a sorted list of vectors $v, generate a vector $k containing globally unique keys for the elements of $v
1637             (where an "element" is a vector of length $M occurring in $v).
1638             Basically does the same thing as:
1639              
1640             $k = $v->vsearchvec($v->uniqvec);
1641              
1642             ... but somewhat more efficiently.
1643              
1644             Contributed by Bryan Jurish Emoocow@cpan.orgE.
1645             EOD
1646             );
1647              
1648             pp_def('vsearchvec',
1649             Pars => 'find(M); which(M,N); indx [o]found();',
1650             Code => q(
1651             int carp=0;
1652             broadcastloop %{
1653             PDL_Indx n1=$SIZE(N)-1, nlo=-1, nhi=n1, nn;
1654             int cmpval, is_asc_sorted;
1655             //
1656             //-- get sort direction
1657             $CMPVEC($which(N=>n1),$which(N=>0),M,cmpval);
1658             is_asc_sorted = (cmpval > 0);
1659             //
1660             //-- binary search
1661             while (nhi-nlo > 1) {
1662             nn = (nhi+nlo) >> 1;
1663             $CMPVEC($find(),$which(N=>nn),M,cmpval);
1664             if ((cmpval > 0) == is_asc_sorted)
1665             nlo=nn;
1666             else
1667             nhi=nn;
1668             }
1669             if (nlo==-1) {
1670             nhi=0;
1671             } else if (nlo==n1) {
1672             $CMPVEC($find(),$which(N=>n1),M,cmpval);
1673             if (cmpval != 0) carp = 1;
1674             nhi = n1;
1675             } else {
1676             nhi = nlo+1;
1677             }
1678             $found() = nhi;
1679             %}
1680             if (carp) warn("some values had to be extrapolated");
1681             ),
1682             Doc=><<'EOD'
1683             =for ref
1684              
1685             Routine for searching N-dimensional values - akin to vsearch() for vectors.
1686              
1687             =for example
1688              
1689             $found = vsearchvec($find, $which);
1690             $nearest = $which->dice_axis(1,$found);
1691              
1692             Returns for each row-vector in C<$find> the index along dimension N
1693             of the least row vector of C<$which>
1694             greater or equal to it.
1695             C<$which> should be sorted in increasing order.
1696             If the value of C<$find> is larger
1697             than any member of C<$which>, the index to the last element of C<$which> is
1698             returned.
1699              
1700             See also: L.
1701             Contributed by Bryan Jurish Emoocow@cpan.orgE.
1702             EOD
1703             );
1704              
1705             pp_def('unionvec',
1706             Pars => 'a(M,NA); b(M,NB); [o]c(M,NC=CALC($SIZE(NA) + $SIZE(NB))); indx [o]nc()',
1707             PMCode=>pp_line_numbers(__LINE__, <<'EOD'),
1708             sub PDL::unionvec {
1709             my ($a,$b,$c,$nc) = @_;
1710             $c = PDL->null if (!defined($nc));
1711             $nc = PDL->null if (!defined($nc));
1712             PDL::_unionvec_int($a,$b,$c,$nc);
1713             return ($c,$nc) if (wantarray);
1714             return $c->slice(",0:".($nc->max-1));
1715             }
1716             EOD
1717             Code => q(
1718             PDL_Indx nai=0, nbi=0, nci=$SIZE(NC), sizeNA=$SIZE(NA), sizeNB=$SIZE(NB);
1719             int cmpval;
1720             loop (NC) %{
1721             if (nai < sizeNA && nbi < sizeNB) {
1722             $CMPVEC($a(NA=>nai),$b(NB=>nbi),M,cmpval);
1723             }
1724             else if (nai < sizeNA) { cmpval = -1; }
1725             else if (nbi < sizeNB) { cmpval = 1; }
1726             else { nci=NC; break; }
1727             //
1728             if (cmpval < 0) {
1729             //-- CASE: a < b
1730             loop (M) %{ $c() = $a(NA=>nai); %}
1731             nai++;
1732             }
1733             else if (cmpval > 0) {
1734             //-- CASE: a > b
1735             loop (M) %{ $c() = $b(NB=>nbi); %}
1736             nbi++;
1737             }
1738             else {
1739             //-- CASE: a == b
1740             loop (M) %{ $c() = $a(NA=>nai); %}
1741             nai++;
1742             nbi++;
1743             }
1744             %}
1745             $nc() = nci;
1746             //-- zero unpopulated outputs
1747             loop(NC=nci,M) %{ $c() = 0; %}
1748             ),
1749             Doc=><<'EOD'
1750             =for ref
1751              
1752             Union of two vector-valued PDLs.
1753              
1754             Input PDLs $a() and $b() B be sorted in lexicographic order.
1755             On return, $nc() holds the actual number of vector-values in the union.
1756              
1757             In scalar context, slices $c() to the actual number of elements in the union
1758             and returns the sliced PDL.
1759              
1760             Contributed by Bryan Jurish Emoocow@cpan.orgE.
1761             EOD
1762             );
1763              
1764             pp_def('intersectvec',
1765             Pars => 'a(M,NA); b(M,NB); [o]c(M,NC=CALC(PDLMIN($SIZE(NA),$SIZE(NB)))); indx [o]nc()',
1766             PMCode=>pp_line_numbers(__LINE__, <<'EOD'),
1767             sub PDL::intersectvec {
1768             my ($a,$b,$c,$nc) = @_;
1769             $c = PDL->null if (!defined($c));
1770             $nc = PDL->null if (!defined($nc));
1771             PDL::_intersectvec_int($a,$b,$c,$nc);
1772             return ($c,$nc) if (wantarray);
1773             my $nc_max = $nc->max;
1774             return ($nc_max > 0
1775             ? $c->slice(",0:".($nc_max-1))
1776             : $c->reshape($c->dim(0), 0, ($c->dims)[2..($c->ndims-1)]));
1777             }
1778             EOD
1779             Code => q(
1780             PDL_Indx nai=0, nbi=0, nci=0, sizeNA=$SIZE(NA), sizeNB=$SIZE(NB), sizeNC=$SIZE(NC);
1781             int cmpval;
1782             for ( ; nci < sizeNC && nai < sizeNA && nbi < sizeNB; ) {
1783             $CMPVEC($a(NA=>nai),$b(NB=>nbi),M,cmpval);
1784             //
1785             if (cmpval < 0) {
1786             //-- CASE: a < b
1787             nai++;
1788             }
1789             else if (cmpval > 0) {
1790             //-- CASE: a > b
1791             nbi++;
1792             }
1793             else {
1794             //-- CASE: a == b
1795             loop (M) %{ $c(NC=>nci) = $a(NA=>nai); %}
1796             nai++;
1797             nbi++;
1798             nci++;
1799             }
1800             }
1801             $nc() = nci;
1802             //-- zero unpopulated outputs
1803             loop(NC=nci,M) %{ $c() = 0; %}
1804             ),
1805             Doc=><<'EOD'
1806             =for ref
1807              
1808             Intersection of two vector-valued PDLs.
1809             Input PDLs $a() and $b() B be sorted in lexicographic order.
1810             On return, $nc() holds the actual number of vector-values in the intersection.
1811              
1812             In scalar context, slices $c() to the actual number of elements in the intersection
1813             and returns the sliced PDL.
1814              
1815             Contributed by Bryan Jurish Emoocow@cpan.orgE.
1816             EOD
1817             );
1818              
1819             pp_def('setdiffvec',
1820             Pars => 'a(M,NA); b(M,NB); [o]c(M,NC=CALC($SIZE(NA))); indx [o]nc()',
1821             PMCode=>pp_line_numbers(__LINE__, <<'EOD'),
1822             sub PDL::setdiffvec {
1823             my ($a,$b,$c,$nc) = @_;
1824             $c = PDL->null if (!defined($c));
1825             $nc = PDL->null if (!defined($nc));
1826             PDL::_setdiffvec_int($a,$b,$c,$nc);
1827             return ($c,$nc) if (wantarray);
1828             my $nc_max = $nc->max;
1829             return ($nc_max > 0
1830             ? $c->slice(",0:".($nc_max-1))
1831             : $c->reshape($c->dim(0), 0, ($c->dims)[2..($c->ndims-1)]));
1832             }
1833             EOD
1834             Code => q(
1835             PDL_Indx nai=0, nbi=0, nci=0, sizeNA=$SIZE(NA), sizeNB=$SIZE(NB), sizeNC=$SIZE(NC);
1836             int cmpval;
1837             for ( ; nci < sizeNC && nai < sizeNA && nbi < sizeNB ; ) {
1838             $CMPVEC($a(NA=>nai),$b(NB=>nbi),M,cmpval);
1839             //
1840             if (cmpval < 0) {
1841             //-- CASE: a < b
1842             loop (M) %{ $c(NC=>nci) = $a(NA=>nai); %}
1843             nai++;
1844             nci++;
1845             }
1846             else if (cmpval > 0) {
1847             //-- CASE: a > b
1848             nbi++;
1849             }
1850             else {
1851             //-- CASE: a == b
1852             nai++;
1853             nbi++;
1854             }
1855             }
1856             for ( ; nci < sizeNC && nai < sizeNA ; nai++,nci++ ) {
1857             loop (M) %{ $c(NC=>nci) = $a(NA=>nai); %}
1858             }
1859             $nc() = nci;
1860             //-- zero unpopulated outputs
1861             loop (NC=nci,M) %{ $c() = 0; %}
1862             ),
1863             Doc=><<'EOD'
1864             =for ref
1865              
1866             Set-difference ($a() \ $b()) of two vector-valued PDLs.
1867              
1868             Input PDLs $a() and $b() B be sorted in lexicographic order.
1869             On return, $nc() holds the actual number of vector-values in the computed vector set.
1870              
1871             In scalar context, slices $c() to the actual number of elements in the output vector set
1872             and returns the sliced PDL.
1873              
1874             Contributed by Bryan Jurish Emoocow@cpan.orgE.
1875             EOD
1876             );
1877              
1878             pp_add_macros(
1879             CMPVAL => sub {
1880             my ($val1, $val2) = @_;
1881             "(($val1) < ($val2) ? -1 : ($val1) > ($val2) ? 1 : 0)";
1882             },
1883             );
1884              
1885             pp_def('union_sorted',
1886             Pars => 'a(NA); b(NB); [o]c(NC=CALC($SIZE(NA) + $SIZE(NB))); indx [o]nc()',
1887             PMCode=>pp_line_numbers(__LINE__, <<'EOD'),
1888             sub PDL::union_sorted {
1889             my ($a,$b,$c,$nc) = @_;
1890             $c = PDL->null if (!defined($c));
1891             $nc = PDL->null if (!defined($nc));
1892             PDL::_union_sorted_int($a,$b,$c,$nc);
1893             return ($c,$nc) if (wantarray);
1894             return $c->slice("0:".($nc->max-1));
1895             }
1896             EOD
1897             Code => q(
1898             PDL_Indx nai=0, nbi=0, nci=$SIZE(NC), sizeNA=$SIZE(NA), sizeNB=$SIZE(NB);
1899             int cmpval;
1900             loop (NC) %{
1901             if (nai < sizeNA && nbi < sizeNB) {
1902             cmpval = $CMPVAL($a(NA=>nai), $b(NB=>nbi));
1903             }
1904             else if (nai < sizeNA) { cmpval = -1; }
1905             else if (nbi < sizeNB) { cmpval = 1; }
1906             else { nci = NC; break; }
1907             //
1908             if (cmpval < 0) {
1909             //-- CASE: a < b
1910             $c() = $a(NA=>nai);
1911             nai++;
1912             }
1913             else if (cmpval > 0) {
1914             //-- CASE: a > b
1915             $c() = $b(NB=>nbi);
1916             nbi++;
1917             }
1918             else {
1919             //-- CASE: a == b
1920             $c() = $a(NA=>nai);
1921             nai++;
1922             nbi++;
1923             }
1924             %}
1925             $nc() = nci;
1926             loop (NC=nci) %{
1927             //-- zero unpopulated outputs
1928             $c() = 0;
1929             %}
1930             ),
1931             Doc=><<'EOD'
1932             =for ref
1933              
1934             Union of two flat sorted unique-valued PDLs.
1935             Input PDLs $a() and $b() B be sorted in lexicographic order and contain no duplicates.
1936             On return, $nc() holds the actual number of values in the union.
1937              
1938             In scalar context, reshapes $c() to the actual number of elements in the union and returns it.
1939              
1940             Contributed by Bryan Jurish Emoocow@cpan.orgE.
1941             EOD
1942             );
1943              
1944             pp_def('intersect_sorted',
1945             Pars => 'a(NA); b(NB); [o]c(NC=CALC(PDLMIN($SIZE(NA),$SIZE(NB)))); indx [o]nc()',
1946             PMCode=>pp_line_numbers(__LINE__, <<'EOD'),
1947             sub PDL::intersect_sorted {
1948             my ($a,$b,$c,$nc) = @_;
1949             $c = PDL->null if (!defined($c));
1950             $nc = PDL->null if (!defined($nc));
1951             PDL::_intersect_sorted_int($a,$b,$c,$nc);
1952             return ($c,$nc) if (wantarray);
1953             my $nc_max = $nc->max;
1954             return ($nc_max > 0
1955             ? $c->slice("0:".($nc_max-1))
1956             : $c->reshape(0, ($c->dims)[1..($c->ndims-1)]));
1957             }
1958             EOD
1959             Code => q(
1960             PDL_Indx nai=0, nbi=0, nci=0, sizeNA=$SIZE(NA), sizeNB=$SIZE(NB), sizeNC=$SIZE(NC);
1961             int cmpval;
1962             for ( ; nci < sizeNC && nai < sizeNA && nbi < sizeNB; ) {
1963             cmpval = $CMPVAL($a(NA=>nai),$b(NB=>nbi));
1964             //
1965             if (cmpval < 0) {
1966             //-- CASE: a < b
1967             nai++;
1968             }
1969             else if (cmpval > 0) {
1970             //-- CASE: a > b
1971             nbi++;
1972             }
1973             else {
1974             //-- CASE: a == b
1975             $c(NC=>nci) = $a(NA=>nai);
1976             nai++;
1977             nbi++;
1978             nci++;
1979             }
1980             }
1981             $nc() = nci;
1982             for ( ; nci < sizeNC; nci++) {
1983             //-- zero unpopulated outputs
1984             $c(NC=>nci) = 0;
1985             }
1986             ),
1987             Doc=><<'EOD'
1988             =for ref
1989              
1990             Intersection of two flat sorted unique-valued PDLs.
1991             Input PDLs $a() and $b() B be sorted in lexicographic order and contain no duplicates.
1992             On return, $nc() holds the actual number of values in the intersection.
1993              
1994             In scalar context, reshapes $c() to the actual number of elements in the intersection and returns it.
1995              
1996             Contributed by Bryan Jurish Emoocow@cpan.orgE.
1997             EOD
1998             );
1999              
2000             pp_def('setdiff_sorted',
2001             Pars => 'a(NA); b(NB); [o]c(NC=CALC($SIZE(NA))); indx [o]nc()',
2002             PMCode=>pp_line_numbers(__LINE__, <<'EOD'),
2003             sub PDL::setdiff_sorted {
2004             my ($a,$b,$c,$nc) = @_;
2005             $c = PDL->null if (!defined($c));
2006             $nc = PDL->null if (!defined($nc));
2007             PDL::_setdiff_sorted_int($a,$b,$c,$nc);
2008             return ($c,$nc) if (wantarray);
2009             my $nc_max = $nc->max;
2010             return ($nc_max > 0
2011             ? $c->slice("0:".($nc_max-1))
2012             : $c->reshape(0, ($c->dims)[1..($c->ndims-1)]));
2013             }
2014             EOD
2015             Code => q(
2016             PDL_Indx nai=0, nbi=0, nci=0, sizeNA=$SIZE(NA), sizeNB=$SIZE(NB), sizeNC=$SIZE(NC);
2017             int cmpval;
2018             for ( ; nci < sizeNC && nai < sizeNA && nbi < sizeNB ; ) {
2019             cmpval = $CMPVAL($a(NA=>nai),$b(NB=>nbi));
2020             //
2021             if (cmpval < 0) {
2022             //-- CASE: a < b
2023             $c(NC=>nci) = $a(NA=>nai);
2024             nai++;
2025             nci++;
2026             }
2027             else if (cmpval > 0) {
2028             //-- CASE: a > b
2029             nbi++;
2030             }
2031             else {
2032             //-- CASE: a == b
2033             nai++;
2034             nbi++;
2035             }
2036             }
2037             for ( ; nci < sizeNC && nai < sizeNA ; nai++,nci++ ) {
2038             $c(NC=>nci) = $a(NA=>nai);
2039             }
2040             $nc() = nci;
2041             for ( ; nci < sizeNC; nci++) {
2042             //-- zero unpopulated outputs
2043             $c(NC=>nci) = 0;
2044             }
2045             ),
2046             Doc=><<'EOD'
2047             =for ref
2048              
2049             Set-difference ($a() \ $b()) of two flat sorted unique-valued PDLs.
2050              
2051             Input PDLs $a() and $b() B be sorted in lexicographic order and contain no duplicate values.
2052             On return, $nc() holds the actual number of values in the computed vector set.
2053              
2054             In scalar context, reshapes $c() to the actual number of elements in the difference set and returns it.
2055              
2056             Contributed by Bryan Jurish Emoocow@cpan.orgE.
2057             EOD
2058             );
2059              
2060             pp_def('vcos',
2061             Pars => join('',
2062             "a(M,N);", ##-- logical (D,T)
2063             "b(M);", ##-- logical (D,1)
2064             "float+ [o]vcos(N);", ##-- logical (T)
2065             ),
2066             GenericTypes => [ppdefs_all],
2067             HandleBad => 1,
2068             Code => pp_line_numbers(__LINE__, <<'EOF'),
2069             broadcastloop %{
2070             $GENERIC(vcos) bnorm = 0;
2071             loop(M) %{
2072             PDL_IF_BAD(if ($ISBAD(b())) continue;,)
2073             bnorm += $b() * $b();
2074             %}
2075             if (bnorm == 0) {
2076             /*-- null-vector b(): set all vcos()=NAN --*/
2077             loop (N) %{ $vcos() = NAN; %}
2078             continue;
2079             }
2080             bnorm = sqrtl(bnorm);
2081             /*-- usual case: compute values for N-slice of b() --*/
2082             loop (N) %{
2083             $GENERIC(vcos) anorm = 0, vval = 0;
2084             loop (M) %{
2085             PDL_IF_BAD(if ($ISBAD(a())) continue;,)
2086             $GENERIC(vcos) aval = $a();
2087             anorm += aval * aval;
2088             PDL_IF_BAD(if ($ISBAD(b())) continue;,)
2089             vval += aval * $b();
2090             %}
2091             /*-- normalize --*/
2092             anorm = sqrtl(anorm);
2093             if (anorm != 0) {
2094             /*-- usual case a(), b() non-null --*/
2095             $vcos() = vval / (anorm * bnorm);
2096             } else {
2097             /*-- null-vector a(): set vcos()=NAN --*/
2098             $vcos() = NAN;
2099             }
2100             %}
2101             %}
2102             if ( $PDLSTATEISBAD(a) || $PDLSTATEISBAD(b) )
2103             $PDLSTATESETBAD(vcos);
2104             EOF
2105             Doc => q{
2106             Computes the vector cosine similarity of a dense vector $b() with respect
2107             to each row $a(*,i) of a dense PDL $a(). This is basically the same
2108             thing as:
2109              
2110             inner($a, $b) / $a->magnover * $b->magnover
2111              
2112             ... but should be much faster to compute, and avoids allocating
2113             potentially large temporaries for the vector magnitudes. Output values
2114             in $vcos() are cosine similarities in the range [-1,1], except for
2115             zero-magnitude vectors which will result in NaN values in $vcos().
2116              
2117             You can use PDL broadcasting to batch-compute distances for multiple $b()
2118             vectors simultaneously:
2119              
2120             $bx = random($M, $NB); ##-- get $NB random vectors of size $N
2121             $vcos = vcos($a,$bx); ##-- $vcos(i,j) ~ sim($a(,i),$b(,j))
2122              
2123             Contributed by Bryan Jurish Emoocow@cpan.orgE.
2124             },
2125             BadDoc=> q{
2126             vcos() will set the bad status flag on the output $vcos() if
2127             it is set on either of the inputs $a() or $b(), but BAD values
2128             will otherwise be ignored for computing the cosine similarity.
2129             },
2130             );
2131              
2132             pp_addhdr(<<'EOH');
2133             extern int pdl_srand_threads;
2134             extern uint64_t *pdl_rand_state;
2135             void pdl_srand(uint64_t **s, uint64_t seed, int n);
2136             double pdl_drand(uint64_t *s);
2137             #define PDL_MAYBE_SRAND \
2138             if (pdl_srand_threads < 0) \
2139             pdl_srand(&pdl_rand_state, PDL->pdl_seed(), PDL->online_cpus());
2140             #define PDL_RAND_SET_OFFSET(v, thr, pdl) \
2141             if (v < 0) { \
2142             if (thr.mag_nthr >= 0) { \
2143             int thr_no = PDL->magic_get_thread(pdl); \
2144             if (thr_no < 0) return PDL->make_error_simple(PDL_EFATAL, "Invalid pdl_magic_get_thread!"); \
2145             v = thr_no == 0 ? thr_no : thr_no % PDL->online_cpus(); \
2146             } else { \
2147             v = 0; \
2148             } \
2149             }
2150             EOH
2151              
2152             pp_def(
2153             'srandom',
2154             Pars=>'a();',
2155             GenericTypes => ['Q'],
2156             Code => <<'EOF',
2157             pdl_srand(&pdl_rand_state, (uint64_t)$a(), PDL->online_cpus());
2158             EOF
2159             NoPthread => 1,
2160             HaveBroadcasting => 0,
2161             Doc=> <<'EOF',
2162             =for ref
2163              
2164             Seed random-number generator with a 64-bit int. Will generate seed data
2165             for a number of threads equal to the return-value of
2166             L.
2167             As of 2.062, the generator changed from Perl's generator to xoshiro256++
2168             (see L).
2169             Before PDL 2.090, this was called C, but was renamed to avoid
2170             clashing with Perl's built-in.
2171              
2172             =for usage
2173              
2174             srandom(); # uses current time
2175             srandom(5); # fixed number e.g. for testing
2176              
2177             EOF
2178             PMCode=>pp_line_numbers(__LINE__, <<'EOD'),
2179             *srandom = \&PDL::srandom;
2180             sub PDL::srandom { PDL::_srandom_int($_[0] // PDL::Core::seed()) }
2181             EOD
2182             );
2183              
2184             pp_def(
2185             'random',
2186             Pars=>'[o] a();',
2187             PMFunc => '',
2188             Code => <<'EOF',
2189             PDL_MAYBE_SRAND
2190             int rand_offset = -1;
2191             broadcastloop %{
2192             PDL_RAND_SET_OFFSET(rand_offset, $PRIV(broadcast), $PDL(a));
2193             $a() = pdl_drand(pdl_rand_state + 4*rand_offset);
2194             %}
2195             EOF
2196             Doc=> <<'EOF',
2197             =for ref
2198              
2199             Constructor which returns ndarray of random numbers, real data-types only.
2200              
2201             =for usage
2202              
2203             $x = random([type], $nx, $ny, $nz,...);
2204             $x = random $y;
2205              
2206             etc (see L).
2207              
2208             This is the uniform distribution between 0 and 1 (assumedly
2209             excluding 1 itself). The arguments are the same as C
2210             (q.v.) - i.e. one can specify dimensions, types or give
2211             a template.
2212              
2213             You can use the PDL function L to seed the random generator.
2214             If it has not been called yet, it will be with the current time.
2215             As of 2.062, the generator changed from Perl's generator to xoshiro256++
2216             (see L).
2217             EOF
2218             PMCode=>pp_line_numbers(__LINE__, <<'EOD'),
2219             sub random { ref($_[0]) && ref($_[0]) ne 'PDL::Type' ? $_[0]->random : PDL->random(@_) }
2220             sub PDL::random {
2221             splice @_, 1, 0, double() if !ref($_[0]) and @_<=1;
2222             my $x = &PDL::Core::_construct;
2223             PDL::_random_int($x);
2224             return $x;
2225             }
2226             EOD
2227             );
2228              
2229             pp_def(
2230             'randsym',
2231             Pars=>'[o] a();',
2232             PMFunc => '',
2233             Code => <<'EOF',
2234             PDL_MAYBE_SRAND
2235             int rand_offset = -1;
2236             broadcastloop %{
2237             PDL_RAND_SET_OFFSET(rand_offset, $PRIV(broadcast), $PDL(a));
2238             long double tmp;
2239             do tmp = pdl_drand(pdl_rand_state + 4*rand_offset); while (tmp == 0.0); /* 0 < tmp < 1 */
2240             $a() = tmp;
2241             %}
2242             EOF
2243             Doc=> <<'EOF',
2244             =for ref
2245              
2246             Constructor which returns ndarray of random numbers, real data-types only.
2247              
2248             =for usage
2249              
2250             $x = randsym([type], $nx, $ny, $nz,...);
2251             $x = randsym $y;
2252              
2253             etc (see L).
2254              
2255             This is the uniform distribution between 0 and 1 (excluding both 0 and
2256             1, cf L). The arguments are the same as C (q.v.) -
2257             i.e. one can specify dimensions, types or give a template.
2258              
2259             You can use the PDL function L to seed the random generator.
2260             If it has not been called yet, it will be with the current time.
2261             EOF
2262             PMCode=>pp_line_numbers(__LINE__, <<'EOD'),
2263             sub randsym { ref($_[0]) && ref($_[0]) ne 'PDL::Type' ? $_[0]->randsym : PDL->randsym(@_) }
2264             sub PDL::randsym {
2265             splice @_, 1, 0, double() if !ref($_[0]) and @_<=1;
2266             my $x = &PDL::Core::_construct;
2267             PDL::_randsym_int($x);
2268             return $x;
2269             }
2270             EOD
2271             );
2272              
2273             pp_add_exported('','grandom');
2274             pp_addpm(<<'EOD');
2275             =head2 grandom
2276              
2277             =for ref
2278              
2279             Constructor which returns ndarray of Gaussian random numbers
2280              
2281             =for usage
2282              
2283             $x = grandom([type], $nx, $ny, $nz,...);
2284             $x = grandom $y;
2285              
2286             etc (see L).
2287              
2288             This is generated using the math library routine C.
2289              
2290             Mean = 0, Stddev = 1
2291              
2292             You can use the PDL function L to seed the random generator.
2293             If it has not been called yet, it will be with the current time.
2294              
2295             =cut
2296              
2297             sub grandom { ref($_[0]) && ref($_[0]) ne 'PDL::Type' ? $_[0]->grandom : PDL->grandom(@_) }
2298             sub PDL::grandom {
2299             my $x = &PDL::Core::_construct;
2300             use PDL::Math 'ndtri';
2301             $x .= ndtri(randsym($x));
2302             return $x;
2303             }
2304             EOD
2305              
2306             ###############################################################
2307             # binary searches in an ndarray; various forms
2308             ###############################################################
2309              
2310             # generic front end; defaults to vsearch_sample for backwards compatibility
2311              
2312             pp_add_exported('','vsearch');
2313             pp_addpm(<<'EOD');
2314             =head2 vsearch
2315              
2316             =for sig
2317              
2318             Signature: ( vals(); xs(n); [o] indx(); [\%options] )
2319              
2320             =for ref
2321              
2322             Efficiently search for values in a sorted ndarray, returning indices.
2323              
2324             =for usage
2325              
2326             $idx = vsearch( $vals, $x, [\%options] );
2327             vsearch( $vals, $x, $idx, [\%options ] );
2328              
2329             B performs a binary search in the ordered ndarray C<$x>,
2330             for the values from C<$vals> ndarray, returning indices into C<$x>.
2331             What is a "match", and the meaning of the returned indices, are determined
2332             by the options.
2333              
2334             The C option indicates which method of searching to use, and may
2335             be one of:
2336              
2337             =over
2338              
2339             =item C
2340              
2341             invoke L|/vsearch_sample>, returning indices appropriate for sampling
2342             within a distribution.
2343              
2344             =item C
2345              
2346             invoke L|/vsearch_insert_leftmost>, returning the left-most possible
2347             insertion point which still leaves the ndarray sorted.
2348              
2349             =item C
2350              
2351             invoke L|/vsearch_insert_rightmost>, returning the right-most possible
2352             insertion point which still leaves the ndarray sorted.
2353              
2354             =item C
2355              
2356             invoke L|/vsearch_match>, returning the index of a matching element,
2357             else -(insertion point + 1)
2358              
2359             =item C
2360              
2361             invoke L|/vsearch_bin_inclusive>, returning an index appropriate for binning
2362             on a grid where the left bin edges are I of the bin. See
2363             below for further explanation of the bin.
2364              
2365             =item C
2366              
2367             invoke L|/vsearch_bin_exclusive>, returning an index appropriate for binning
2368             on a grid where the left bin edges are I of the bin. See
2369             below for further explanation of the bin.
2370              
2371             =back
2372              
2373             The default value of C is C.
2374              
2375             =for example
2376              
2377             use PDL;
2378              
2379             my @modes = qw( sample insert_leftmost insert_rightmost match
2380             bin_inclusive bin_exclusive );
2381              
2382             # Generate a sequence of 3 zeros, 3 ones, ..., 3 fours.
2383             my $x = zeroes(3,5)->yvals->flat;
2384              
2385             for my $mode ( @modes ) {
2386             # if the value is in $x
2387             my $contained = 2;
2388             my $idx_contained = vsearch( $contained, $x, { mode => $mode } );
2389             my $x_contained = $x->copy;
2390             $x_contained->slice( $idx_contained ) .= 9;
2391              
2392             # if the value is not in $x
2393             my $not_contained = 1.5;
2394             my $idx_not_contained = vsearch( $not_contained, $x, { mode => $mode } );
2395             my $x_not_contained = $x->copy;
2396             $x_not_contained->slice( $idx_not_contained ) .= 9;
2397              
2398             print sprintf("%-23s%30s\n", '$x', $x);
2399             print sprintf("%-23s%30s\n", "$mode ($contained)", $x_contained);
2400             print sprintf("%-23s%30s\n\n", "$mode ($not_contained)", $x_not_contained);
2401             }
2402              
2403             # $x [0 0 0 1 1 1 2 2 2 3 3 3 4 4 4]
2404             # sample (2) [0 0 0 1 1 1 9 2 2 3 3 3 4 4 4]
2405             # sample (1.5) [0 0 0 1 1 1 9 2 2 3 3 3 4 4 4]
2406             #
2407             # $x [0 0 0 1 1 1 2 2 2 3 3 3 4 4 4]
2408             # insert_leftmost (2) [0 0 0 1 1 1 9 2 2 3 3 3 4 4 4]
2409             # insert_leftmost (1.5) [0 0 0 1 1 1 9 2 2 3 3 3 4 4 4]
2410             #
2411             # $x [0 0 0 1 1 1 2 2 2 3 3 3 4 4 4]
2412             # insert_rightmost (2) [0 0 0 1 1 1 2 2 2 9 3 3 4 4 4]
2413             # insert_rightmost (1.5) [0 0 0 1 1 1 9 2 2 3 3 3 4 4 4]
2414             #
2415             # $x [0 0 0 1 1 1 2 2 2 3 3 3 4 4 4]
2416             # match (2) [0 0 0 1 1 1 2 9 2 3 3 3 4 4 4]
2417             # match (1.5) [0 0 0 1 1 1 2 2 9 3 3 3 4 4 4]
2418             #
2419             # $x [0 0 0 1 1 1 2 2 2 3 3 3 4 4 4]
2420             # bin_inclusive (2) [0 0 0 1 1 1 2 2 9 3 3 3 4 4 4]
2421             # bin_inclusive (1.5) [0 0 0 1 1 9 2 2 2 3 3 3 4 4 4]
2422             #
2423             # $x [0 0 0 1 1 1 2 2 2 3 3 3 4 4 4]
2424             # bin_exclusive (2) [0 0 0 1 1 9 2 2 2 3 3 3 4 4 4]
2425             # bin_exclusive (1.5) [0 0 0 1 1 9 2 2 2 3 3 3 4 4 4]
2426              
2427              
2428             Also see
2429             L|/vsearch_sample>,
2430             L|/vsearch_insert_leftmost>,
2431             L|/vsearch_insert_rightmost>,
2432             L|/vsearch_match>,
2433             L|/vsearch_bin_inclusive>, and
2434             L|/vsearch_bin_exclusive>
2435              
2436             =cut
2437              
2438             sub vsearch {
2439             my $opt = 'HASH' eq ref $_[-1]
2440             ? pop
2441             : { mode => 'sample' };
2442              
2443             croak( "unknown options to vsearch\n" )
2444             if ( ! defined $opt->{mode} && keys %$opt )
2445             || keys %$opt > 1;
2446              
2447             my $mode = $opt->{mode};
2448             goto
2449             $mode eq 'sample' ? \&vsearch_sample
2450             : $mode eq 'insert_leftmost' ? \&vsearch_insert_leftmost
2451             : $mode eq 'insert_rightmost' ? \&vsearch_insert_rightmost
2452             : $mode eq 'match' ? \&vsearch_match
2453             : $mode eq 'bin_inclusive' ? \&vsearch_bin_inclusive
2454             : $mode eq 'bin_exclusive' ? \&vsearch_bin_exclusive
2455             : croak( "unknown vsearch mode: $mode\n" );
2456             }
2457              
2458             *PDL::vsearch = \&vsearch;
2459              
2460             EOD
2461              
2462             use Text::Tabs qw[ expand ];
2463             sub undent {
2464             my $txt = expand( shift );
2465              
2466             $txt =~ s/^([ \t]+)-{4}.*$//m;
2467             $txt =~ s/^$1//mg
2468             if defined $1;
2469             $txt;
2470             }
2471              
2472             for my $func ( [
2473             vsearch_sample => {
2474             low => -1,
2475             high => '$SIZE(n)',
2476             up => '($x(n => n1) > $x(n => 0))',
2477             code => q[
2478             while ( high - low > 1 ) {
2479             mid = %MID%;
2480             if ( ( value > $x(n => mid ) ) == up ) low = mid;
2481             else high = mid;
2482             }
2483             $idx() = low >= n1 ? n1
2484             : up ? low + 1
2485             : PDLMAX(low, 0);
2486             ----
2487             ],
2488             ref =>
2489             'Search for values in a sorted array, return index appropriate for sampling from a distribution',
2490              
2491             doc_pre => q[
2492             B<%FUNC%> returns an index I for each value I of C<$vals> appropriate
2493             for sampling C<$vals>
2494             ----
2495             ],
2496             doc_incr => q[
2497             V <= x[0] : I = 0
2498             x[0] < V <= x[-1] : I s.t. x[I-1] < V <= x[I]
2499             x[-1] < V : I = $x->nelem -1
2500             ----
2501             ],
2502              
2503             doc_decr => q[
2504             V > x[0] : I = 0
2505             x[0] >= V > x[-1] : I s.t. x[I] >= V > x[I+1]
2506             x[-1] >= V : I = $x->nelem - 1
2507             ----
2508             ],
2509              
2510             doc_post => q[
2511             If all elements of C<$x> are equal, I<< I = $x->nelem - 1 >>.
2512              
2513             If C<$x> contains duplicated elements, I is the index of the
2514             leftmost (by position in array) duplicate if I matches.
2515              
2516             =for example
2517              
2518             This function is useful e.g. when you have a list of probabilities
2519             for events and want to generate indices to events:
2520              
2521             $x = pdl(.01,.86,.93,1); # Barnsley IFS probabilities cumulatively
2522             $y = random 20;
2523             $c = %FUNC%($y, $x); # Now, $c will have the appropriate distr.
2524              
2525             It is possible to use the L
2526             function to obtain cumulative probabilities from absolute probabilities.
2527             ----
2528             ],
2529              
2530             },
2531             ],
2532              
2533             [
2534             # return left-most possible insertion point.
2535             # lowest index where x[i] >= value
2536             vsearch_insert_leftmost => {
2537             low => 0,
2538             high => 'n1',
2539             code => q[
2540             while (low <= high ) {
2541             mid = %MID%;
2542             if ( ( $x(n => mid) >= value ) == up ) high = mid - 1;
2543             else low = mid + 1;
2544             }
2545             $idx() = up ? low : high;
2546             ],
2547             ref =>
2548             'Determine the insertion point for values in a sorted array, inserting before duplicates.',
2549              
2550             doc_pre => q[
2551             B<%FUNC%> returns an index I for each value I of
2552             C<$vals> equal to the leftmost position (by index in array) within
2553             C<$x> that I may be inserted and still maintain the order in
2554             C<$x>.
2555              
2556             Insertion at index I involves shifting elements I and higher of
2557             C<$x> to the right by one and setting the now empty element at index
2558             I to I.
2559             ----
2560             ],
2561             doc_incr => q[
2562             V <= x[0] : I = 0
2563             x[0] < V <= x[-1] : I s.t. x[I-1] < V <= x[I]
2564             x[-1] < V : I = $x->nelem
2565             ----
2566             ],
2567              
2568             doc_decr => q[
2569             V > x[0] : I = -1
2570             x[0] >= V >= x[-1] : I s.t. x[I] >= V > x[I+1]
2571             x[-1] >= V : I = $x->nelem -1
2572             ----
2573             ],
2574              
2575             doc_post => q[
2576             If all elements of C<$x> are equal,
2577              
2578             i = 0
2579              
2580             If C<$x> contains duplicated elements, I is the index of the
2581             leftmost (by index in array) duplicate if I matches.
2582             ----
2583             ],
2584              
2585             },
2586             ],
2587              
2588             [
2589             # return right-most possible insertion point.
2590             # lowest index where x[i] > value
2591             vsearch_insert_rightmost => {
2592             low => 0,
2593             high => 'n1',
2594             code => q[
2595             while (low <= high ) {
2596             mid = %MID%;
2597             if ( ( $x(n => mid) > value ) == up ) high = mid - 1;
2598             else low = mid + 1;
2599             }
2600             $idx() = up ? low : high;
2601             ],
2602             ref =>
2603             'Determine the insertion point for values in a sorted array, inserting after duplicates.',
2604              
2605             doc_pre => q[
2606             B<%FUNC%> returns an index I for each value I of
2607             C<$vals> equal to the rightmost position (by index in array) within
2608             C<$x> that I may be inserted and still maintain the order in
2609             C<$x>.
2610              
2611             Insertion at index I involves shifting elements I and higher of
2612             C<$x> to the right by one and setting the now empty element at index
2613             I to I.
2614             ----
2615             ],
2616             doc_incr => q[
2617             V < x[0] : I = 0
2618             x[0] <= V < x[-1] : I s.t. x[I-1] <= V < x[I]
2619             x[-1] <= V : I = $x->nelem
2620             ----
2621             ],
2622              
2623             doc_decr => q[
2624             V >= x[0] : I = -1
2625             x[0] > V >= x[-1] : I s.t. x[I] >= V > x[I+1]
2626             x[-1] > V : I = $x->nelem -1
2627             ----
2628             ],
2629              
2630             doc_post => q[
2631             If all elements of C<$x> are equal,
2632              
2633             i = $x->nelem - 1
2634              
2635             If C<$x> contains duplicated elements, I is the index of the
2636             leftmost (by index in array) duplicate if I matches.
2637             ----
2638             ],
2639              
2640             },
2641              
2642             ],
2643             [
2644             # return index of matching element, else -( insertion point + 1 )
2645             # patterned after the Java binarySearch interface; see
2646             # http://docs.oracle.com/javase/7/docs/api/java/util/Arrays.html
2647             vsearch_match => {
2648             low => 0,
2649             high => 'n1',
2650             code => q[
2651             int done = 0;
2652              
2653             while (low <= high ) {
2654             $GENERIC() mid_value;
2655              
2656             mid = %MID%;
2657              
2658             mid_value = $x(n=>mid);
2659              
2660             if ( up ) {
2661             if ( mid_value > value ) { high = mid - 1; }
2662             else if ( mid_value < value ) { low = mid + 1; }
2663             else { done = 1; break; }
2664             }
2665             else {
2666             if ( mid_value < value ) { high = mid - 1; }
2667             else if ( mid_value > value ) { low = mid + 1; }
2668             else { done = 1; break; }
2669             }
2670             }
2671             $idx() = done ? mid
2672             : up ? - ( low + 1 )
2673             : - ( high + 1 );
2674             ],
2675             ref => 'Match values against a sorted array.',
2676              
2677             doc_pre => q[
2678             B<%FUNC%> returns an index I for each value I of
2679             C<$vals>. If I matches an element in C<$x>, I is the
2680             index of that element, otherwise it is I<-( insertion_point + 1 )>,
2681             where I is an index in C<$x> where I may be
2682             inserted while maintaining the order in C<$x>. If C<$x> has
2683             duplicated values, I may refer to any of them.
2684             ----
2685             ],
2686              
2687             },
2688             ],
2689             [
2690             # x[i] is the INclusive left edge of the bin
2691             # return i, s.t. x[i] <= value < x[i+1].
2692             # returns -1 if x[0] > value
2693             # returns N-1 if x[-1] <= value
2694             vsearch_bin_inclusive => {
2695             low => 0,
2696             high => 'n1',
2697             code => q[
2698             while (low <= high ) {
2699             mid = %MID%;
2700             if ( ( $x(n => mid) <= value ) == up ) low = mid + 1;
2701             else high = mid - 1;
2702             }
2703             $idx() = up ? high: low;
2704             ],
2705             ref =>
2706             'Determine the index for values in a sorted array of bins, lower bound inclusive.',
2707              
2708             doc_pre => q[
2709             C<$x> represents the edges of contiguous bins, with the first and
2710             last elements representing the outer edges of the outer bins, and the
2711             inner elements the shared bin edges.
2712              
2713             The lower bound of a bin is inclusive to the bin, its outer bound is exclusive to it.
2714             B<%FUNC%> returns an index I for each value I of C<$vals>
2715             ----
2716             ],
2717             doc_incr => q[
2718             V < x[0] : I = -1
2719             x[0] <= V < x[-1] : I s.t. x[I] <= V < x[I+1]
2720             x[-1] <= V : I = $x->nelem - 1
2721             ----
2722             ],
2723              
2724             doc_decr => q[
2725             V >= x[0] : I = 0
2726             x[0] > V >= x[-1] : I s.t. x[I+1] > V >= x[I]
2727             x[-1] > V : I = $x->nelem
2728             ----
2729             ],
2730              
2731             doc_post => q[
2732             If all elements of C<$x> are equal,
2733              
2734             i = $x->nelem - 1
2735              
2736             If C<$x> contains duplicated elements, I is the index of the
2737             righmost (by index in array) duplicate if I matches.
2738             ----
2739             ],
2740              
2741             },
2742             ],
2743              
2744             [
2745             # x[i] is the EXclusive left edge of the bin
2746             # return i, s.t. x[i] < value <= x[i+1].
2747             # returns -1 if x[0] >= value
2748             # returns N-1 if x[-1] < value
2749             vsearch_bin_exclusive => {
2750             low => 0,
2751             high => 'n1',
2752             code => q[
2753             while (low <= high ) {
2754             mid = %MID%;
2755             if ( ( $x(n => mid) < value ) == up ) low = mid + 1;
2756             else high = mid - 1;
2757             }
2758             $idx() = up ? high: low;
2759             ],
2760             ref =>
2761             'Determine the index for values in a sorted array of bins, lower bound exclusive.',
2762              
2763             doc_pre => q[
2764             C<$x> represents the edges of contiguous bins, with the first and
2765             last elements representing the outer edges of the outer bins, and the
2766             inner elements the shared bin edges.
2767              
2768             The lower bound of a bin is exclusive to the bin, its upper bound is inclusive to it.
2769             B<%FUNC%> returns an index I for each value I of C<$vals>.
2770             ----
2771             ],
2772             doc_incr => q[
2773             V <= x[0] : I = -1
2774             x[0] < V <= x[-1] : I s.t. x[I] < V <= x[I+1]
2775             x[-1] < V : I = $x->nelem - 1
2776             ----
2777             ],
2778              
2779             doc_decr => q[
2780             V > x[0] : I = 0
2781             x[0] >= V > x[-1] : I s.t. x[I-1] >= V > x[I]
2782             x[-1] >= V : I = $x->nelem
2783             ----
2784             ],
2785              
2786             doc_post => q[
2787             If all elements of C<$x> are equal,
2788              
2789             i = $x->nelem - 1
2790              
2791             If C<$x> contains duplicated elements, I is the index of the
2792             righmost (by index in array) duplicate if I matches.
2793             ----
2794             ],
2795              
2796             }
2797             ],
2798              
2799             )
2800              
2801             {
2802             my ( $func, $algo ) = @$func;
2803              
2804             my %replace = (
2805             # calculate midpoint of range; ensure we don't overflow
2806             # (low+high)>>1 for large values of low + high
2807             # see sf.net bug #360
2808             '%MID%' => 'low + (( high - low )>> 1);',
2809              
2810             # determine which way the data are sorted. vsearch_sample
2811             # overrides this.
2812             '%UP%' => '$x(n => n1) >= $x(n => 0)',
2813              
2814             '%FUNC%' => $func,
2815              
2816             '%PRE%' => undent(
2817             q[
2818             %DOC_PRE%
2819             ----
2820             ]
2821             ),
2822              
2823              
2824              
2825             '%BODY%' => undent(
2826             q[
2827             I has the following properties:
2828              
2829             =over
2830              
2831             =item *
2832              
2833             if C<$x> is sorted in increasing order
2834              
2835             %DOC_INCR%
2836              
2837             =item *
2838              
2839             if C<$x> is sorted in decreasing order
2840              
2841             %DOC_DECR%
2842              
2843             =back
2844             ----
2845             ]
2846             ),
2847              
2848             '%POST%' => undent(
2849             q[
2850             %DOC_POST%
2851             ----
2852             ]
2853             ),
2854              
2855             map { ( "%\U$_%" => undent( $algo->{$_} ) ) } keys %$algo,
2856             );
2857              
2858             $replace{'%PRE%'} = '' unless defined $replace{'%DOC_PRE%'};
2859             $replace{'%BODY%'} = ''
2860             unless defined $replace{'%DOC_INCR%'} || defined $replace{'%DOC_DECR%'};
2861             $replace{'%POST%'} = '' unless defined $replace{'%DOC_POST%'};
2862              
2863              
2864             my $code = undent q[
2865             loop(n) %{
2866             if ( $ISGOOD(vals()) ) {
2867             PDL_Indx n1 = $SIZE(n)-1;
2868             PDL_Indx low = %LOW%;
2869             PDL_Indx high = %HIGH%;
2870             PDL_Indx mid;
2871              
2872             $GENERIC() value = $vals();
2873              
2874             /* determine sort order of data */
2875             int up = %UP%;
2876             %CODE%
2877             }
2878              
2879             else {
2880             $SETBAD(idx());
2881             }
2882             %}
2883             ----
2884             ];
2885              
2886             my $doc = undent q[
2887             =for ref
2888              
2889             %REF%
2890              
2891             =for usage
2892              
2893             $idx = %FUNC%($vals, $x);
2894              
2895             C<$x> must be sorted, but may be in decreasing or increasing
2896             order. if C<$x> is empty, then all values in C<$idx> will be
2897             set to the bad value.
2898              
2899             %PRE%
2900             %BODY%
2901             %POST%
2902             ----
2903             ];
2904              
2905              
2906             # redo until nothing changes
2907             for my $tref ( \$code, \$doc ) {
2908             1 while $$tref =~ s/(%[\w_]+%)/$replace{$1}/ge;
2909             }
2910              
2911             pp_def(
2912             $func,
2913             HandleBad => 1,
2914             BadDoc => 'bad values in vals() result in bad values in idx()',
2915             Pars => 'vals(); x(n); indx [o]idx()',
2916             GenericTypes => $F, # too restrictive ?
2917             Code => $code,
2918             Doc => $doc,
2919             );
2920             }
2921              
2922             ###############################################################
2923             # routines somehow related to interpolation
2924             ###############################################################
2925              
2926             pp_def('interpolate',
2927             HandleBad => 0,
2928             BadDoc => 'needs major (?) work to handles bad values',
2929             Pars => '!complex xi(); !complex x(n); y(n); [o] yi(); int [o] err()',
2930             GenericTypes => $AF,
2931             Code => pp_line_numbers(__LINE__, <<'EOF'),
2932             PDL_Indx n = $SIZE(n), n1 = n-1;
2933             broadcastloop %{
2934             PDL_Indx jl = -1, jh = n;
2935             int carp = 0, up = ($x(n => n1) > $x(n => 0));
2936             while (jh-jl > 1) { /* binary search */
2937             PDL_Indx m = (jh+jl) >> 1;
2938             if (($xi() > $x(n => m)) == up)
2939             jl = m;
2940             else
2941             jh = m;
2942             }
2943             if (jl == -1) {
2944             if ($xi() != $x(n => 0)) carp = 1;
2945             jl = 0;
2946             } else if (jh == n) {
2947             if ($xi() != $x(n => n1)) carp = 1;
2948             jl = n1-1;
2949             }
2950             jh = jl+1;
2951             $GENERIC() d = $x(n=>jh) - $x(n=>jl);
2952             if (d == 0) $CROAK("identical abscissas");
2953             d = ($x(n=>jh) - $xi())/d;
2954             $yi() = d*$y(n => jl) + (1-d)*$y(n => jh);
2955             $err() = carp;
2956             %}
2957             EOF
2958             Doc => <<'EOD',
2959             =for ref
2960              
2961             routine for 1D linear interpolation
2962              
2963             Given a set of points C<($x,$y)>, use linear interpolation
2964             to find the values C<$yi> at a set of points C<$xi>.
2965              
2966             C uses a binary search to find the suspects, er...,
2967             interpolation indices and therefore abscissas (ie C<$x>)
2968             have to be I ordered (increasing or decreasing).
2969             For interpolation at lots of
2970             closely spaced abscissas an approach that uses the last index found as
2971             a start for the next search can be faster (compare Numerical Recipes
2972             C routine). Feel free to implement that on top of the binary
2973             search if you like. For out of bounds values it just does a linear
2974             extrapolation and sets the corresponding element of C<$err> to 1,
2975             which is otherwise 0.
2976              
2977             See also L, which uses the same routine,
2978             differing only in the handling of extrapolation - an error message
2979             is printed rather than returning an error ndarray.
2980              
2981             Note that C can use complex values for C<$y> and C<$yi> but
2982             C<$x> and C<$xi> must be real.
2983             EOD
2984             );
2985              
2986             pp_add_exported('', 'interpol');
2987             pp_addpm(<<'EOD');
2988             =head2 interpol
2989              
2990             =for sig
2991              
2992             Signature: (xi(); x(n); y(n); [o] yi())
2993              
2994             =for ref
2995              
2996             routine for 1D linear interpolation
2997              
2998             =for usage
2999              
3000             $yi = interpol($xi, $x, $y)
3001              
3002             C uses the same search method as L,
3003             hence C<$x> must be I ordered (either increasing or decreasing).
3004             The difference occurs in the handling of out-of-bounds values; here
3005             an error message is printed.
3006              
3007             =cut
3008              
3009             # kept in for backwards compatibility
3010             sub interpol ($$$;$) {
3011             my $xi = shift;
3012             my $x = shift;
3013             my $y = shift;
3014             my $yi = @_ == 1 ? $_[0] : PDL->null;
3015             interpolate( $xi, $x, $y, $yi, my $err = PDL->null );
3016             print "some values had to be extrapolated\n"
3017             if any $err;
3018             return $yi if @_ == 0;
3019             } # sub: interpol()
3020             *PDL::interpol = \&interpol;
3021              
3022             EOD
3023              
3024             pp_add_exported('','interpND');
3025             pp_addpm(<<'EOD');
3026             =head2 interpND
3027              
3028             =for ref
3029              
3030             Interpolate values from an N-D ndarray, with switchable method
3031              
3032             =for example
3033              
3034             $source = 10*xvals(10,10) + yvals(10,10);
3035             $index = pdl([[2.2,3.5],[4.1,5.0]],[[6.0,7.4],[8,9]]);
3036             print $source->interpND( $index );
3037              
3038             InterpND acts like L,
3039             collapsing C<$index> by lookup
3040             into C<$source>; but it does interpolation rather than direct sampling.
3041             The interpolation method and boundary condition are switchable via
3042             an options hash.
3043              
3044             By default, linear or sample interpolation is used, with constant
3045             value outside the boundaries of the source pdl. No dataflow occurs,
3046             because in general the output is computed rather than indexed.
3047              
3048             All the interpolation methods treat the pixels as value-centered, so
3049             the C method will return C<< $a->(0) >> for coordinate values on
3050             the set [-0.5,0.5], and all methods will return C<< $a->(1) >> for
3051             a coordinate value of exactly 1.
3052              
3053             Recognized options:
3054              
3055             =over 3
3056              
3057             =item method
3058              
3059             Values can be:
3060              
3061             =over 3
3062              
3063             =item * 0, s, sample, Sample (default for integer source types)
3064              
3065             The nearest value is taken. Pixels are regarded as centered on their
3066             respective integer coordinates (no offset from the linear case).
3067              
3068             =item * 1, l, linear, Linear (default for floating point source types)
3069              
3070             The values are N-linearly interpolated from an N-dimensional cube of size 2.
3071              
3072             =item * 3, c, cube, cubic, Cubic
3073              
3074             The values are interpolated using a local cubic fit to the data. The
3075             fit is constrained to match the original data and its derivative at the
3076             data points. The second derivative of the fit is not continuous at the
3077             data points. Multidimensional datasets are interpolated by the
3078             successive-collapse method.
3079              
3080             (Note that the constraint on the first derivative causes a small amount
3081             of ringing around sudden features such as step functions).
3082              
3083             =item * f, fft, fourier, Fourier
3084              
3085             The source is Fourier transformed, and the interpolated values are
3086             explicitly calculated from the coefficients. The boundary condition
3087             option is ignored -- periodic boundaries are imposed.
3088              
3089             If you pass in the option "fft", and it is a list (ARRAY) ref, then it
3090             is a stash for the magnitude and phase of the source FFT. If the list
3091             has two elements then they are taken as already computed; otherwise
3092             they are calculated and put in the stash.
3093              
3094             =back
3095              
3096             =item b, bound, boundary, Boundary
3097              
3098             This option is passed unmodified into L,
3099             which is used as the indexing engine for the interpolation.
3100             Some current allowed values are 'extend', 'periodic', 'truncate', and 'mirror'
3101             (default is 'truncate').
3102              
3103             =item bad
3104              
3105             contains the fill value used for 'truncate' boundary. (default 0)
3106              
3107             =item fft
3108              
3109             An array ref whose associated list is used to stash the FFT of the source
3110             data, for the FFT method.
3111              
3112             =back
3113              
3114             =cut
3115              
3116             *interpND = *PDL::interpND;
3117             sub PDL::interpND {
3118             my $source = shift;
3119             my $index = shift;
3120             my $options = shift;
3121              
3122             barf 'Usage: interpND($source,$index[,{%options}])'
3123             if(defined $options and ref $options ne 'HASH');
3124              
3125             my $opt = defined $options ? $options : {};
3126              
3127             my $method = $opt->{m} || $opt->{meth} || $opt->{method} || $opt->{Method};
3128             $method //= $source->type->integer ? 'sample' : 'linear';
3129              
3130             my $boundary = $opt->{b} || $opt->{boundary} || $opt->{Boundary} || $opt->{bound} || $opt->{Bound} || 'extend';
3131             my $bad = $opt->{bad} || $opt->{Bad} || 0.0;
3132              
3133             return $source->range(PDL::Math::floor($index+0.5),0,$boundary)
3134             if $method =~ m/^s(am(p(le)?)?)?/i;
3135              
3136             if (($method eq 1) || $method =~ m/^l(in(ear)?)?/i) {
3137             ## key: (ith = index broadcast; cth = cube broadcast; sth = source broadcast)
3138             my $d = $index->dim(0);
3139             my $di = $index->ndims - 1;
3140              
3141             # Grab a 2-on-a-side n-cube around each desired pixel
3142             my $samp = $source->range($index->floor,2,$boundary); # (ith, cth, sth)
3143              
3144             # Reorder to put the cube dimensions in front and convert to a list
3145             $samp = $samp->reorder( $di .. $di+$d-1,
3146             0 .. $di-1,
3147             $di+$d .. $samp->ndims-1) # (cth, ith, sth)
3148             ->clump($d); # (clst, ith, sth)
3149              
3150             # Enumerate the corners of an n-cube and convert to a list
3151             # (the 'x' is the normal perl repeat operator)
3152             my $crnr = PDL::Basic::ndcoords( (2) x $index->dim(0) ) # (index,cth)
3153             ->mv(0,-1)->clump($index->dim(0))->mv(-1,0); # (index, clst)
3154             # a & b are the weighting coefficients.
3155             my($x,$y);
3156             $index->where( 0 * $index ) .= -10; # Change NaN to invalid
3157             {
3158             my $bb = PDL::Math::floor($index);
3159             $x = ($index - $bb) -> dummy(1,$crnr->dim(1)); # index, clst, ith
3160             $y = ($bb + 1 - $index) -> dummy(1,$crnr->dim(1)); # index, clst, ith
3161             }
3162              
3163             # Use 1/0 corners to select which multiplier happens, multiply
3164             # 'em all together to get sample weights, and sum to get the answer.
3165             my $out0 = ( ($x * ($crnr==1) + $y * ($crnr==0)) #index, clst, ith
3166             -> prodover #clst, ith
3167             );
3168              
3169             my $out = ($out0 * $samp)->sumover; # ith, sth
3170              
3171             # Work around BAD-not-being-contagious bug in PDL <= 2.6 bad handling code --CED 3-April-2013
3172             if ($source->badflag) {
3173             my $baddies = $samp->isbad->orover;
3174             $out = $out->setbadif($baddies);
3175             }
3176              
3177             $out = $out->convert($source->type->enum) if $out->type != $source->type;
3178             return $out;
3179              
3180             } elsif(($method eq 3) || $method =~ m/^c(u(b(e|ic)?)?)?/i) {
3181              
3182             my ($d,@di) = $index->dims;
3183             my $di = $index->ndims - 1;
3184              
3185             # Grab a 4-on-a-side n-cube around each desired pixel
3186             my $samp = $source->range($index->floor - 1,4,$boundary) #ith, cth, sth
3187             ->reorder( $di .. $di+$d-1, 0..$di-1, $di+$d .. $source->ndims-1 );
3188             # (cth, ith, sth)
3189              
3190             # Make a cube of the subpixel offsets, and expand its dims to
3191             # a 4-on-a-side N-1 cube, to match the slices of $samp (used below).
3192             my $y = $index - $index->floor;
3193             for my $i(1..$d-1) {
3194             $y = $y->dummy($i,4);
3195             }
3196              
3197             # Collapse by interpolation, one dimension at a time...
3198             for my $i(0..$d-1) {
3199             my $a0 = $samp->slice("(1)"); # Just-under-sample
3200             my $a1 = $samp->slice("(2)"); # Just-over-sample
3201             my $a1a0 = $a1 - $a0;
3202              
3203             my $gradient = 0.5 * ($samp->slice("2:3")-$samp->slice("0:1"));
3204             my $s0 = $gradient->slice("(0)"); # Just-under-gradient
3205             my $s1 = $gradient->slice("(1)"); # Just-over-gradient
3206              
3207             my $bb = $y->slice("($i)");
3208              
3209             # Collapse the sample...
3210             $samp = ( $a0 +
3211             $bb * (
3212             $s0 +
3213             $bb * ( (3 * $a1a0 - 2*$s0 - $s1) +
3214             $bb * ( $s1 + $s0 - 2*$a1a0 )
3215             )
3216             )
3217             );
3218              
3219             # "Collapse" the subpixel offset...
3220             $y = $y->slice(":,($i)");
3221             }
3222              
3223             $samp = $samp->convert($source->type->enum) if $samp->type != $source->type;
3224             return $samp;
3225              
3226             } elsif($method =~ m/^f(ft|ourier)?/i) {
3227              
3228             require PDL::FFT;
3229             my $fftref = $opt->{fft};
3230             $fftref = [] unless(ref $fftref eq 'ARRAY');
3231             if(@$fftref != 2) {
3232             my $x = $source->copy;
3233             my $y = zeroes($source);
3234             PDL::FFT::fftnd($x,$y);
3235             $fftref->[0] = sqrt($x*$x+$y*$y) / $x->nelem;
3236             $fftref->[1] = - atan2($y,$x);
3237             }
3238              
3239             my $i;
3240             my $c = PDL::Basic::ndcoords($source); # (dim, source-dims)
3241             for $i(1..$index->ndims-1) {
3242             $c = $c->dummy($i,$index->dim($i))
3243             }
3244             my $id = $index->ndims-1;
3245             my $phase = (($c * $index * 3.14159 * 2 / pdl($source->dims))
3246             ->sumover) # (index-dims, source-dims)
3247             ->reorder($id..$id+$source->ndims-1,0..$id-1); # (src, index)
3248              
3249             my $phref = $fftref->[1]->copy; # (source-dims)
3250             my $mag = $fftref->[0]->copy; # (source-dims)
3251              
3252             for $i(1..$index->ndims-1) {
3253             $phref = $phref->dummy(-1,$index->dim($i));
3254             $mag = $mag->dummy(-1,$index->dim($i));
3255             }
3256             my $out = cos($phase + $phref ) * $mag;
3257             $out = $out->clump($source->ndims)->sumover;
3258             $out = $out->convert($source->type->enum) if $out->type != $source->type;
3259             return $out;
3260             } else {
3261             barf("interpND: unknown method '$method'; valid ones are 'linear' and 'sample'.\n");
3262             }
3263             }
3264              
3265             EOD
3266              
3267             ##############################################################
3268             # things related to indexing: one2nd, which, where
3269             ##############################################################
3270              
3271             pp_add_exported("", 'one2nd');
3272             pp_addpm(<<'EOD');
3273             =head2 one2nd
3274              
3275             =for ref
3276              
3277             Converts a one dimensional index ndarray to a set of ND coordinates
3278              
3279             =for usage
3280              
3281             @coords=one2nd($x, $indices)
3282              
3283             returns an array of ndarrays containing the ND indexes corresponding to
3284             the one dimensional list indices. The indices are assumed to
3285             correspond to array C<$x> clumped using C. This routine is
3286             used in the old vector form of L, but is useful on
3287             its own occasionally.
3288              
3289             Returned ndarrays have the L datatype. C<$indices> can have
3290             values larger than C<< $x->nelem >> but negative values in C<$indices>
3291             will not give the answer you expect.
3292              
3293             =for example
3294              
3295             pdl> $x=pdl [[[1,2],[-1,1]], [[0,-3],[3,2]]]; $c=$x->clump(-1)
3296             pdl> $maxind=maximum_ind($c); p $maxind;
3297             6
3298             pdl> print one2nd($x, maximum_ind($c))
3299             0 1 1
3300             pdl> p $x->at(0,1,1)
3301             3
3302              
3303             =cut
3304              
3305             *one2nd = \&PDL::one2nd;
3306             sub PDL::one2nd {
3307             barf "Usage: one2nd \$array, \$indices\n" if @_ != 2;
3308             my ($x, $ind)=@_;
3309             my @dimension=$x->dims;
3310             $ind = indx($ind);
3311             my(@index);
3312             my $count=0;
3313             foreach (@dimension) {
3314             $index[$count++]=$ind % $_;
3315             $ind /= $_;
3316             }
3317             return @index;
3318             }
3319              
3320             EOD
3321              
3322             my $doc_which = <<'EOD';
3323              
3324             =for ref
3325              
3326             Returns indices of non-zero values from a 1-D PDL
3327              
3328             =for usage
3329              
3330             $i = which($mask);
3331              
3332             returns a pdl with indices for all those elements that are nonzero in
3333             the mask. Note that the returned indices will be 1D. If you feed in a
3334             multidimensional mask, it will be flattened before the indices are
3335             calculated. See also L for multidimensional masks.
3336              
3337             If you want to index into the original mask or a similar ndarray
3338             with output from C, remember to flatten it before calling index:
3339              
3340             $data = random 5, 5;
3341             $idx = which $data > 0.5; # $idx is now 1D
3342             $bigsum = $data->flat->index($idx)->sum; # flatten before indexing
3343              
3344             Compare also L for similar functionality.
3345              
3346             SEE ALSO:
3347              
3348             L returns separately the indices of both nonzero and zero
3349             values in the mask.
3350              
3351             L returns separately slices of both nonzero and zero
3352             values in the mask.
3353              
3354             L returns associated values from a data PDL, rather than
3355             indices into the mask PDL.
3356              
3357             L returns N-D indices into a multidimensional PDL.
3358              
3359             =for example
3360              
3361             pdl> $x = sequence(10); p $x
3362             [0 1 2 3 4 5 6 7 8 9]
3363             pdl> $indx = which($x>6); p $indx
3364             [7 8 9]
3365              
3366             =cut
3367              
3368             EOD
3369              
3370             my $doc_which_both = <<'EOD';
3371              
3372             =for ref
3373              
3374             Returns indices of nonzero and zero values in a mask PDL
3375              
3376             =for usage
3377              
3378             ($i, $c_i) = which_both($mask);
3379              
3380             This works just as L, but the complement of C<$i> will be in
3381             C<$c_i>.
3382              
3383             =for example
3384              
3385             pdl> p $x = sequence(10)
3386             [0 1 2 3 4 5 6 7 8 9]
3387             pdl> ($big, $small) = which_both($x >= 5); p "$big\n$small"
3388             [5 6 7 8 9]
3389             [0 1 2 3 4]
3390              
3391             See also L for the n-dimensional version.
3392              
3393             =cut
3394              
3395             EOD
3396              
3397             for (
3398             {Name=>'which',
3399             Pars => 'mask(n); indx [o] inds(n); indx [o]lastout()',
3400             Variables => 'PDL_Indx dm=0;',
3401             Elseclause => "",
3402             Outclause => '$lastout() = dm; loop(n=dm) %{ $inds() = -1; %}',
3403             Doc => $doc_which,
3404             PMCode=>pp_line_numbers(__LINE__, <<'EOD'),
3405             sub which { my ($this,$out) = @_;
3406             $this = $this->flat;
3407             $out //= $this->nullcreate;
3408             PDL::_which_int($this,$out,my $lastout = $this->nullcreate);
3409             my $lastoutmax = $lastout->max->sclr;
3410             $lastoutmax ? $out->slice('0:'.($lastoutmax-1))->sever : empty(indx);
3411             }
3412             *PDL::which = \&which;
3413             EOD
3414             },
3415             {Name => 'which_both',
3416             Pars => 'mask(n); indx [o] inds(n); indx [o]notinds(n); indx [o]lastout(); indx [o]lastoutn()',
3417             Variables => 'PDL_Indx dm=0; int dm2=0;',
3418             Elseclause => "else { \n \$notinds(n => dm2)=n; \n dm2++;\n }",
3419             Outclause => '$lastout() = dm; $lastoutn() = dm2; loop(n=dm) %{ $inds() = -1; %} loop(n=dm2) %{ $notinds() = -1; %}',
3420             Doc => $doc_which_both,
3421             PMCode=>pp_line_numbers(__LINE__, <<'EOD'),
3422             sub which_both { my ($this,$outi,$outni) = @_;
3423             $this = $this->flat;
3424             $outi //= $this->nullcreate;
3425             $outni //= $this->nullcreate;
3426             PDL::_which_both_int($this,$outi,$outni,my $lastout = $this->nullcreate,my $lastoutn = $this->nullcreate);
3427             my $lastoutmax = $lastout->max->sclr;
3428             $outi = $lastoutmax ? $outi->slice('0:'.($lastoutmax-1))->sever : empty(indx);
3429             return $outi if !wantarray;
3430             my $lastoutnmax = $lastoutn->max->sclr;
3431             ($outi, $lastoutnmax ? $outni->slice('0:'.($lastoutnmax-1))->sever : empty(indx));
3432             }
3433             *PDL::which_both = \&which_both;
3434             EOD
3435             }
3436             )
3437             {
3438             pp_def($_->{Name},
3439             HandleBad => 1,
3440             Doc => $_->{Doc},
3441             Pars => $_->{Pars},
3442             GenericTypes => [ppdefs_all],
3443             PMCode => $_->{PMCode},
3444             Code => $_->{Variables} .'
3445             loop(n) %{
3446             if ( $mask() PDL_IF_BAD(&& $ISGOOD($mask()),) ) {
3447             $inds(n => dm) = n;
3448             dm++;
3449             }'.$_->{Elseclause}.'
3450             %}'.$_->{Outclause},
3451             );
3452             }
3453              
3454             pp_def('whichover',
3455             HandleBad => 1,
3456             Inplace => 1,
3457             Pars => 'a(n); [o]o(n)',
3458             GenericTypes => [ppdefs_all],
3459             Code => <<'EOF',
3460             PDL_Indx last = 0;
3461             loop(n) %{
3462             if ($a() PDL_IF_BAD(&& $ISGOOD($a()),)) $o(n=>last++) = n;
3463             %}
3464             loop(n=last) %{ $o() = -1; %}
3465             EOF
3466             Doc => <<'EOF',
3467             =for ref
3468              
3469             Collects the coordinates of non-zero, non-bad values in each 1D mask in
3470             ndarray at the start of the output, and fills the rest with -1.
3471              
3472             By using L etc. it is possible to use I dimension.
3473              
3474             =for example
3475              
3476             my $a = pdl q[3 4 2 3 2 3 1];
3477             my $b = $a->uniq;
3478             my $c = +($a->dummy(0) == $b)->transpose;
3479             print $c, $c->whichover;
3480             # [
3481             # [0 0 0 0 0 0 1]
3482             # [0 0 1 0 1 0 0]
3483             # [1 0 0 1 0 1 0]
3484             # [0 1 0 0 0 0 0]
3485             # ]
3486             # [
3487             # [ 6 -1 -1 -1 -1 -1 -1]
3488             # [ 2 4 -1 -1 -1 -1 -1]
3489             # [ 0 3 5 -1 -1 -1 -1]
3490             # [ 1 -1 -1 -1 -1 -1 -1]
3491             # ]
3492              
3493             EOF
3494             );
3495              
3496             pp_def('approx_artol',
3497             Pars => 'got(); expected(); sbyte [o] result()',
3498             OtherPars => 'double atol; double rtol',
3499             OtherParsDefaults => { atol => 1e-6, rtol => 0 },
3500             GenericTypes => [ppdefs_all],
3501             ArgOrder => 1,
3502             HandleBad => 1,
3503             Code => pp_line_numbers(__LINE__, <<'EOF'),
3504             double atol2 = $COMP(atol)*$COMP(atol), rtol2 = $COMP(rtol)*$COMP(rtol);
3505             PDL_IF_BAD(char got_badflag = !!$PDLSTATEISBAD(got); char exp_badflag = !!$PDLSTATEISBAD(expected);,)
3506             broadcastloop %{
3507             $GENERIC() expctd = $expected();
3508             if (PDL_ISNAN_$PPSYM()($got()) && PDL_ISNAN_$PPSYM()(expctd)) { $result() = 1; continue; }
3509             if (PDL_ISNAN_$PPSYM()($got()) || PDL_ISNAN_$PPSYM()(expctd)) { $result() = 0; continue; }
3510             PDL_IF_BAD(
3511             if ((got_badflag && $ISBAD(got())) && (exp_badflag && $ISBADVAR(expctd,expected))) { $result() = 1; continue; }
3512             if ((got_badflag && $ISBAD(got())) || (exp_badflag && $ISBADVAR(expctd,expected))) { $result() = 0; continue; }
3513             ,)
3514             if ($got() == expctd) { $result() = 1; continue; }
3515             $GENERIC() diff = $got() - expctd;
3516             double abs_diff2 = PDL_IF_GENTYPE_REAL(
3517             diff * diff,
3518             (creall(diff) * creall(diff)) + (cimagl(diff) * cimagl(diff))
3519             );
3520             if (abs_diff2 <= atol2) { $result() = 1; continue; }
3521             double rel_diff2 = rtol2 * PDL_IF_GENTYPE_REAL(
3522             expctd * expctd,
3523             ((creall(expctd) * creall(expctd)) + (cimagl(expctd) * cimagl(expctd)))
3524             );
3525             if (abs_diff2 <= rel_diff2) { $result() = 1; continue; }
3526             $result() = 0;
3527             %}
3528             EOF
3529             Doc => <<'EOF',
3530             =for ref
3531              
3532             Returns C mask whether C<< abs($got()-$expected())> <= >> either
3533             absolute or relative (C * C<$expected()>) tolerances.
3534              
3535             Relative tolerance defaults to zero, and absolute tolerance defaults to
3536             C<1e-6>, for compatibility with L.
3537              
3538             Works with complex numbers, and to avoid expensive Cing uses the
3539             squares of the input quantities and differences. Bear this in mind for
3540             numbers outside the range (for C) of about C<1e-154..1e154>.
3541              
3542             Handles Cs by showing them approximately equal (i.e. true in the
3543             output) if both values are C, and not otherwise.
3544              
3545             Adapted from code by Edward Baudrez, test changed from C<< < >> to C<< <= >>.
3546             EOF
3547             BadDoc => <<'EOF',
3548             Handles bad values similarly to Cs, above. This includes if only
3549             one of the two input ndarrays has their badflag true.
3550             EOF
3551             );
3552              
3553             pp_add_exported("", 'where');
3554             pp_addpm(<<'EOD');
3555             =head2 where
3556              
3557             =for ref
3558              
3559             Use a mask to select values from one or more data PDLs
3560              
3561             C accepts one or more data ndarrays and a mask ndarray. It
3562             returns a list of output ndarrays, corresponding to the input data
3563             ndarrays. Each output ndarray is a 1-dimensional list of values in its
3564             corresponding data ndarray. The values are drawn from locations where
3565             the mask is nonzero.
3566              
3567             The output PDLs are still connected to the original data PDLs, for the
3568             purpose of dataflow.
3569              
3570             C combines the functionality of L and L
3571             into a single operation.
3572              
3573             BUGS:
3574              
3575             While C works OK for most N-dimensional cases, it does not
3576             broadcast properly over (for example) the (N+1)th dimension in data
3577             that is compared to an N-dimensional mask. Use C for that.
3578              
3579             =for example
3580              
3581             $i = $x->where($x+5 > 0); # $i contains those elements of $x
3582             # where mask ($x+5 > 0) is 1
3583             $i .= -5; # Set those elements (of $x) to -5. Together, these
3584             # commands clamp $x to a maximum of -5.
3585              
3586             It is also possible to use the same mask for several ndarrays with
3587             the same call:
3588              
3589             ($i,$j,$k) = where($x,$y,$z, $x+5>0);
3590              
3591             Note: C<$i> is always 1-D, even if C<$x> is E1-D.
3592              
3593             WARNING: The first argument
3594             (the values) and the second argument (the mask) currently have to have
3595             the exact same dimensions (or horrible things happen). You *cannot*
3596             broadcast over a smaller mask, for example.
3597              
3598             =cut
3599              
3600             sub PDL::where :lvalue {
3601             barf "Usage: where( \$pdl1, ..., \$pdlN, \$mask )\n" if @_ == 1;
3602             my $mask = pop->flat->which;
3603             @_ == 1 ? $_[0]->flat->index($mask) : map $_->flat->index($mask), @_;
3604             }
3605             *where = \&PDL::where;
3606              
3607             EOD
3608              
3609             pp_add_exported("", 'where_both');
3610             pp_addpm(<<'EOD');
3611             =head2 where_both
3612              
3613             =for ref
3614              
3615             Returns slices (non-zero in mask, zero) of an ndarray according to a mask
3616              
3617             =for usage
3618              
3619             ($match_vals, $non_match_vals) = where_both($pdl, $mask);
3620              
3621             This works like L, but (flattened) data-flowing slices
3622             rather than index-sets are returned.
3623              
3624             =for example
3625              
3626             pdl> p $x = sequence(10) + 2
3627             [2 3 4 5 6 7 8 9 10 11]
3628             pdl> ($big, $small) = where_both($x, $x > 5); p "$big\n$small"
3629             [6 7 8 9 10 11]
3630             [2 3 4 5]
3631             pdl> p $big += 2, $small -= 1
3632             [8 9 10 11 12 13] [1 2 3 4]
3633             pdl> p $x
3634             [1 2 3 4 8 9 10 11 12 13]
3635              
3636             =cut
3637              
3638             sub PDL::where_both {
3639             barf "Usage: where_both(\$pdl, \$mask)\n" if @_ != 2;
3640             my ($arr, $mask) = @_; # $mask has 0==false, 1==true
3641             my $arr_flat = $arr->flat;
3642             map $arr_flat->index1d($_), PDL::which_both($mask);
3643             }
3644             *where_both = \&PDL::where_both;
3645             EOD
3646              
3647             pp_add_exported("", 'whereND whereND_both');
3648             pp_addpm(<<'EOD');
3649             =head2 whereND
3650              
3651             =for ref
3652              
3653             C with support for ND masks and broadcasting
3654              
3655             C accepts one or more data ndarrays and a
3656             mask ndarray. It returns a list of output ndarrays,
3657             corresponding to the input data ndarrays. The values
3658             are drawn from locations where the mask is nonzero.
3659              
3660             C differs from C in that the mask
3661             dimensionality is preserved which allows for
3662             proper broadcasting of the selection operation over
3663             higher dimensions.
3664              
3665             As with C the output PDLs are still connected
3666             to the original data PDLs, for the purpose of dataflow.
3667              
3668             =for usage
3669              
3670             $sdata = whereND $data, $mask
3671             ($s1, $s2, ..., $sn) = whereND $d1, $d2, ..., $dn, $mask
3672              
3673             where
3674              
3675             $data is M dimensional
3676             $mask is N < M dimensional
3677             dims($data) 1..N == dims($mask) 1..N
3678             with broadcasting over N+1 to M dimensions
3679              
3680             =for example
3681              
3682             $data = sequence(4,3,2); # example data array
3683             $mask4 = (random(4)>0.5); # example 1-D mask array, has $n4 true values
3684             $mask43 = (random(4,3)>0.5); # example 2-D mask array, has $n43 true values
3685             $sdat4 = whereND $data, $mask4; # $sdat4 is a [$n4,3,2] pdl
3686             $sdat43 = whereND $data, $mask43; # $sdat43 is a [$n43,2] pdl
3687              
3688             Just as with C, you can use the returned value in an
3689             assignment. That means that both of these examples are valid:
3690              
3691             # Used to create a new slice stored in $sdat4:
3692             $sdat4 = $data->whereND($mask4);
3693             $sdat4 .= 0;
3694             # Used in lvalue context:
3695             $data->whereND($mask4) .= 0;
3696              
3697             SEE ALSO:
3698              
3699             L returns N-D indices into a multidimensional PDL, from a mask.
3700              
3701             =cut
3702              
3703             sub PDL::whereND :lvalue {
3704             barf "Usage: whereND( \$pdl1, ..., \$pdlN, \$mask )\n" if @_ == 1;
3705             my $mask = pop @_; # $mask has 0==false, 1==true
3706             my @to_return;
3707             my $n = PDL::sum($mask);
3708             my $maskndims = $mask->ndims;
3709             foreach my $arr (@_) {
3710             # count the number of dims in $mask and $arr
3711             # $mask = a b c d e f.....
3712             my @idims = dims($arr);
3713             splice @idims, 0, $maskndims; # pop off the number of dims in $mask
3714             if (!$n or $arr->isempty) {
3715             push @to_return, PDL->zeroes($arr->type, $n, @idims);
3716             next;
3717             }
3718             my $sub_i = $mask * ones($arr);
3719             my $where_sub_i = PDL::where($arr, $sub_i);
3720             my $ndim = 0;
3721             foreach my $id ($n, @idims[0..($#idims-1)]) {
3722             $where_sub_i = $where_sub_i->splitdim($ndim++,$id) if $n>0;
3723             }
3724             push @to_return, $where_sub_i;
3725             }
3726             return (@to_return == 1) ? $to_return[0] : @to_return;
3727             }
3728             *whereND = \&PDL::whereND;
3729              
3730             =head2 whereND_both
3731              
3732             =for ref
3733              
3734             C with support for ND masks and broadcasting
3735              
3736             This works like L, but data-flowing slices
3737             rather than index-sets are returned.
3738              
3739             C differs from C in that the mask
3740             dimensionality is preserved which allows for
3741             proper broadcasting of the selection operation over
3742             higher dimensions.
3743              
3744             As with C the output PDLs are still connected
3745             to the original data PDLs, for the purpose of dataflow.
3746              
3747             =for usage
3748              
3749             ($match_vals, $non_match_vals) = whereND_both($pdl, $mask);
3750              
3751             =cut
3752              
3753             sub PDL::whereND_both :lvalue {
3754             barf "Usage: whereND_both(\$pdl, \$mask)\n" if @_ != 2;
3755             my ($arr, $mask) = @_; # $mask has 0==false, 1==true
3756             map $arr->indexND($_), PDL::whichND_both($mask);
3757             }
3758             *whereND_both = \&PDL::whereND_both;
3759             EOD
3760              
3761             pp_add_exported("", 'whichND whichND_both');
3762             pp_addpm(<<'EOD');
3763             =head2 whichND
3764              
3765             =for ref
3766              
3767             Return the coordinates of non-zero values in a mask.
3768              
3769             =for usage
3770              
3771             WhichND returns the N-dimensional coordinates of each nonzero value in
3772             a mask PDL with any number of dimensions. The returned values arrive
3773             as an array-of-vectors suitable for use in
3774             L or L.
3775              
3776             $coords = whichND($mask);
3777              
3778             returns a PDL containing the coordinates of the elements that are non-zero
3779             in C<$mask>, suitable for use in L. The 0th dimension contains the
3780             full coordinate listing of each point; the 1st dimension lists all the points.
3781             For example, if $mask has rank 4 and 100 matching elements, then $coords has
3782             dimension 4x100.
3783              
3784             If no such elements exist, then whichND returns a structured empty PDL:
3785             an Nx0 PDL that contains no values (but matches, broadcasting-wise, with
3786             the vectors that would be produced if such elements existed).
3787              
3788             DEPRECATED BEHAVIOR IN LIST CONTEXT:
3789              
3790             whichND once delivered different values in list context than in scalar
3791             context, for historical reasons. In list context, it returned the
3792             coordinates transposed, as a collection of 1-PDLs (one per dimension)
3793             in a list. This usage is deprecated in PDL 2.4.10, and will cause a
3794             warning to be issued every time it is encountered. To avoid the
3795             warning, you can set the global variable "$PDL::whichND" to 's' to
3796             get scalar behavior in all contexts, or to 'l' to get list behavior in
3797             list context.
3798              
3799             In later versions of PDL, the deprecated behavior will disappear. Deprecated
3800             list context whichND expressions can be replaced with:
3801              
3802             @list = $x->whichND->mv(0,-1)->dog;
3803              
3804             SEE ALSO:
3805              
3806             L finds coordinates of nonzero values in a 1-D mask.
3807              
3808             L extracts values from a data PDL that are associated
3809             with nonzero values in a mask PDL.
3810              
3811             L can be fed the coordinates to return the values.
3812              
3813             =for example
3814              
3815             pdl> $s=sequence(10,10,3,4)
3816             pdl> ($x, $y, $z, $w)=whichND($s == 203); p $x, $y, $z, $w
3817             [3] [0] [2] [0]
3818             pdl> print $s->at(list(cat($x,$y,$z,$w)))
3819             203
3820              
3821             =cut
3822              
3823             sub _one2nd {
3824             my ($mask, $ind) = @_;
3825             my $ndims = my @mdims = $mask->dims;
3826             # In the empty case, explicitly return the correct type of structured empty
3827             return PDL->new_from_specification(indx, $ndims, 0) if !$ind->nelem;
3828             my $mult = ones(indx, $ndims);
3829             $mult->index($_+1) .= $mult->index($_) * $mdims[$_] for 0..$#mdims-1;
3830             for my $i (0..$#mdims) {
3831             my $s = $ind->index($i);
3832             $s /= $mult->index($i);
3833             $s %= $mdims[$i];
3834             }
3835             $ind;
3836             }
3837              
3838             *whichND = \&PDL::whichND;
3839             sub PDL::whichND {
3840             my $mask = PDL->topdl(shift);
3841              
3842             # List context: generate a perl list by dimension
3843             if(wantarray) {
3844             if(!defined($PDL::whichND)) {
3845             printf STDERR "whichND: WARNING - list context deprecated. Set \$PDL::whichND. Details in pod.";
3846             } elsif($PDL::whichND =~ m/l/i) {
3847             # old list context enabled by setting $PDL::whichND to 'l'
3848             return $mask->one2nd($mask->flat->which);
3849             }
3850             # if $PDL::whichND does not contain 'l' or 'L', fall through to scalar context
3851             }
3852              
3853             # Scalar context: generate an N-D index ndarray
3854             my $ndims = $mask->getndims;
3855             return PDL->new_from_specification(indx,$ndims,0) if !$mask->nelem;
3856             return $mask ? pdl(indx,0) : PDL->new_from_specification(indx,0) if !$ndims;
3857             _one2nd($mask, $mask->flat->which->dummy(0,$ndims)->sever);
3858             }
3859              
3860             =head2 whichND_both
3861              
3862             =for ref
3863              
3864             Return the coordinates of non-zero values in a mask.
3865              
3866             =for usage
3867              
3868             Like L, but returns the N-dimensional coordinates (like
3869             L) of the nonzero, zero values in the mask PDL. The
3870             returned values arrive as an array-of-vectors suitable for use in
3871             L or L.
3872             Added in 2.099.
3873              
3874             ($nonzero_coords, $zero_coords) = whichND_both($mask);
3875              
3876             SEE ALSO:
3877              
3878             L finds coordinates of nonzero values in a 1-D mask.
3879              
3880             L extracts values from a data PDL that are associated
3881             with nonzero values in a mask PDL.
3882              
3883             L can be fed the coordinates to return the values.
3884              
3885             =for example
3886              
3887             pdl> $s=sequence(10,10,3,4)
3888             pdl> ($x, $y, $z, $w)=whichND($s == 203); p $x, $y, $z, $w
3889             [3] [0] [2] [0]
3890             pdl> print $s->at(list(cat($x,$y,$z,$w)))
3891             203
3892              
3893             =cut
3894              
3895             *whichND_both = \&PDL::whichND_both;
3896             sub PDL::whichND_both {
3897             my $mask = PDL->topdl(shift);
3898             return ((PDL->new_from_specification(indx,$mask->ndims,0))x2) if !$mask->nelem;
3899             my $ndims = $mask->getndims;
3900             if (!$ndims) {
3901             my ($t, $f) = (pdl(indx,0), PDL->new_from_specification(indx,0));
3902             return $mask ? ($t,$f) : ($f,$t);
3903             }
3904             map _one2nd($mask, $_->dummy(0,$ndims)->sever), $mask->flat->which_both;
3905             }
3906             EOD
3907              
3908             #
3909             # Set operations suited for manipulation of the operations above.
3910             #
3911              
3912             pp_add_exported("", 'setops');
3913             pp_addpm(<<'EOD');
3914             =head2 setops
3915              
3916             =for ref
3917              
3918             Implements simple set operations like union and intersection
3919              
3920             =for usage
3921              
3922             Usage: $set = setops($x, , $y);
3923              
3924             The operator can be C, C or C. This is then applied
3925             to C<$x> viewed as a set and C<$y> viewed as a set. Set theory says
3926             that a set may not have two or more identical elements, but setops
3927             takes care of this for you, so C<$x=pdl(1,1,2)> is OK. The functioning
3928             is as follows:
3929              
3930             =over
3931              
3932             =item C
3933              
3934             The resulting vector will contain the elements that are either in C<$x>
3935             I in C<$y> or both. This is the union in set operation terms
3936              
3937             =item C
3938              
3939             The resulting vector will contain the elements that are either in C<$x>
3940             or C<$y>, but not in both. This is
3941              
3942             Union($x, $y) - Intersection($x, $y)
3943              
3944             in set operation terms.
3945              
3946             =item C
3947              
3948             The resulting vector will contain the intersection of C<$x> and C<$y>, so
3949             the elements that are in both C<$x> and C<$y>. Note that for convenience
3950             this operation is also aliased to L.
3951              
3952             =back
3953              
3954             It should be emphasized that these routines are used when one or both of
3955             the sets C<$x>, C<$y> are hard to calculate or that you get from a separate
3956             subroutine.
3957              
3958             Finally IDL users might be familiar with Craig Markwardt's C
3959             routine which has inspired this routine although it was written independently
3960             However the present routine has a few less options (but see the examples)
3961              
3962             =for example
3963              
3964             You will very often use these functions on an index vector, so that is
3965             what we will show here. We will in fact something slightly silly. First
3966             we will find all squares that are also cubes below 10000.
3967              
3968             Create a sequence vector:
3969              
3970             pdl> $x = sequence(10000)
3971              
3972             Find all odd and even elements:
3973              
3974             pdl> ($even, $odd) = which_both( ($x % 2) == 0)
3975              
3976             Find all squares
3977              
3978             pdl> $squares= which(ceil(sqrt($x)) == floor(sqrt($x)))
3979              
3980             Find all cubes (being careful with roundoff error!)
3981              
3982             pdl> $cubes= which(ceil($x**(1.0/3.0)) == floor($x**(1.0/3.0)+1e-6))
3983              
3984             Then find all squares that are cubes:
3985              
3986             pdl> $both = setops($squares, 'AND', $cubes)
3987              
3988             And print these (assumes that C is loaded!)
3989              
3990             pdl> p $x($both)
3991             [0 1 64 729 4096]
3992              
3993             Then find all numbers that are either cubes or squares, but not both:
3994              
3995             pdl> $cube_xor_square = setops($squares, 'XOR', $cubes)
3996              
3997             pdl> p $cube_xor_square->nelem()
3998             112
3999              
4000             So there are a total of 112 of these!
4001              
4002             Finally find all odd squares:
4003              
4004             pdl> $odd_squares = setops($squares, 'AND', $odd)
4005              
4006              
4007             Another common occurrence is to want to get all objects that are
4008             in C<$x> and in the complement of C<$y>. But it is almost always best
4009             to create the complement explicitly since the universe that both are
4010             taken from is not known. Thus use L if possible
4011             to keep track of complements.
4012              
4013             If this is impossible the best approach is to make a temporary:
4014              
4015             This creates an index vector the size of the universe of the sets and
4016             set all elements in C<$y> to 0
4017              
4018             pdl> $tmp = ones($n_universe); $tmp($y) .= 0;
4019              
4020             This then finds the complement of C<$y>
4021              
4022             pdl> $C_b = which($tmp == 1);
4023              
4024             and this does the final selection:
4025              
4026             pdl> $set = setops($x, 'AND', $C_b)
4027              
4028             =cut
4029              
4030             *setops = \&PDL::setops;
4031              
4032             sub PDL::setops {
4033              
4034             my ($x, $op, $y)=@_;
4035              
4036             # Check that $x and $y are 1D.
4037             if ($x->ndims() > 1 || $y->ndims() > 1) {
4038             warn 'setops: $x and $y must be 1D - flattening them!'."\n";
4039             $x = $x->flat;
4040             $y = $y->flat;
4041             }
4042              
4043             #Make sure there are no duplicate elements.
4044             $x=$x->uniq;
4045             $y=$y->uniq;
4046              
4047             my $result;
4048              
4049             if ($op eq 'OR') {
4050             # Easy...
4051             $result = uniq(append($x, $y));
4052             } elsif ($op eq 'XOR') {
4053             # Make ordered list of set union.
4054             my $union = append($x, $y)->qsort;
4055             # Index lists.
4056             my $s1=zeroes(byte, $union->nelem());
4057             my $s2=zeroes(byte, $union->nelem());
4058              
4059             # Find indices which are duplicated - these are to be excluded
4060             #
4061             # We do this by comparing x with x shifted each way.
4062             my $i1 = which($union != rotate($union, 1));
4063             my $i2 = which($union != rotate($union, -1));
4064             #
4065             # We then mark/mask these in the s1 and s2 arrays to indicate which ones
4066             # are not equal to their neighbours.
4067             #
4068             my $ts;
4069             ($ts = $s1->index($i1)) .= byte(1) if $i1->nelem() > 0;
4070             ($ts = $s2->index($i2)) .= byte(1) if $i2->nelem() > 0;
4071              
4072             my $inds=which($s1 == $s2);
4073              
4074             if ($inds->nelem() > 0) {
4075             return $union->index($inds);
4076             } else {
4077             return $inds;
4078             }
4079              
4080             } elsif ($op eq 'AND') {
4081             # The intersection of the arrays.
4082             return $x if $x->isempty;
4083             return $y if $y->isempty;
4084             # Make ordered list of set union.
4085             my $union = append($x, $y)->qsort;
4086             return $union->where($union == rotate($union, -1))->uniq;
4087             } else {
4088             print "The operation $op is not known!";
4089             return -1;
4090             }
4091              
4092             }
4093             EOD
4094              
4095             pp_add_exported("", 'intersect');
4096             pp_addpm(<<'EOD');
4097             =head2 intersect
4098              
4099             =for ref
4100              
4101             Calculate the intersection of two ndarrays
4102              
4103             =for usage
4104              
4105             Usage: $set = intersect($x, $y);
4106              
4107             This routine is merely a simple interface to L. See
4108             that for more information
4109              
4110             =for example
4111              
4112             Find all numbers less that 100 that are of the form 2*y and 3*x
4113              
4114             pdl> $x=sequence(100)
4115             pdl> $factor2 = which( ($x % 2) == 0)
4116             pdl> $factor3 = which( ($x % 3) == 0)
4117             pdl> $ii=intersect($factor2, $factor3)
4118             pdl> p $x($ii)
4119             [0 6 12 18 24 30 36 42 48 54 60 66 72 78 84 90 96]
4120              
4121             =cut
4122              
4123             *intersect = \&PDL::intersect;
4124              
4125             sub PDL::intersect { setops($_[0], 'AND', $_[1]) }
4126             EOD
4127              
4128             pp_add_macros(
4129             PCHDF => sub {my ($k, $x, $s, $out) = @_; '
4130             /* PDL version: K, X, S are var names, 4th param output */
4131             /* ***PURPOSE Computes divided differences for DPCHCE and DPCHSP */
4132             /* DPCHDF: DPCHIP Finite Difference Formula */
4133             /* Uses a divided difference formulation to compute a K-point approx- */
4134             /* imation to the derivative at X(K) based on the data in X and S. */
4135             /* Called by DPCHCE and DPCHSP to compute 3- and 4-point boundary */
4136             /* derivative approximations. */
4137             /* ---------------------------------------------------------------------- */
4138             /* On input: */
4139             /* K is the order of the desired derivative approximation. */
4140             /* K must be at least 3 (error return if not). */
4141             /* X contains the K values of the independent variable. */
4142             /* X need not be ordered, but the values **MUST** be */
4143             /* distinct. (Not checked here.) */
4144             /* S contains the associated slope values: */
4145             /* S(I) = (F(I+1)-F(I))/(X(I+1)-X(I)), I=1(1)K-1. */
4146             /* (Note that S need only be of length K-1.) */
4147             /* On return: */
4148             /* S will be destroyed. */
4149             /* IERR will be set to -1 if K.LT.2 . */
4150             /* DPCHDF will be set to the desired derivative approximation if */
4151             /* IERR=0 or to zero if IERR=-1. */
4152             /* ---------------------------------------------------------------------- */
4153             /* ***SEE ALSO DPCHCE, DPCHSP */
4154             /* ***REFERENCES Carl de Boor, A Practical Guide to Splines, Springer- */
4155             /* Verlag, New York, 1978, pp. 10-16. */
4156             /* CHECK FOR LEGAL VALUE OF K. */
4157             {
4158             /* Local variables */
4159             PDL_Indx i, j, k_cached = '.$k.';
4160             $GENERIC() *x = '.$x.', *s = '.$s.';
4161             if (k_cached < 3) $CROAK("K LESS THAN THREE");
4162             /* COMPUTE COEFFICIENTS OF INTERPOLATING POLYNOMIAL. */
4163             for (j = 2; j < k_cached; ++j) {
4164             PDL_Indx itmp = k_cached - j;
4165             for (i = 0; i < itmp; ++i)
4166             s[i] = (s[i+1] - s[i]) / (x[i + j] - x[i]);
4167             }
4168             /* EVALUATE DERIVATIVE AT X(K). */
4169             $GENERIC() value = s[0];
4170             for (i = 1; i < k_cached-1; ++i)
4171             value = s[i] + value * (x[k_cached-1] - x[i]);
4172             '.$out.' = value;
4173             }
4174             '},
4175             SIGN => sub {my ($a, $b) = @_; "(($b) >= 0 ? PDL_ABS($a) : -PDL_ABS($a))"},
4176             PCHST => sub {my ($a, $b) = @_;
4177             "((($a) == 0. || ($b) == 0.) ? 0. : \$SIGN(1, ($a)) * \$SIGN(1, ($b)))"
4178             },
4179             CHFIE => sub {my ($x1, $x2, $f1, $f2, $d1, $d2, $a, $b, $out) = @_; '
4180             /* ***PURPOSE Evaluates integral of a single cubic for DPCHIA */
4181             /* DCHFIE: Cubic Hermite Function Integral Evaluator. */
4182             /* Called by DPCHIA to evaluate the integral of a single cubic (in */
4183             /* Hermite form) over an arbitrary interval (A,B). */
4184             /* ---------------------------------------------------------------------- */
4185             /* Parameters: */
4186             /* VALUE -- (output) value of the requested integral. */
4187             /* X1,X2 -- (input) endpoints if interval of definition of cubic. */
4188             /* F1,F2 -- (input) function values at the ends of the interval. */
4189             /* D1,D2 -- (input) derivative values at the ends of the interval. */
4190             /* A,B -- (input) endpoints of interval of integration. */
4191             /* ***SEE ALSO DPCHIA */
4192             /* Programming notes: */
4193             /* 1. There is no error return from this routine because zero is */
4194             /* indeed the mathematically correct answer when X1.EQ.X2 . */
4195             do {
4196             if ('.$x1.' == '.$x2.') {
4197             '.$out.' = 0.; break;
4198             }
4199             $GENERIC() h = '.$x2.' - '.$x1.';
4200             $GENERIC() ta1 = ('.$a.' - '.$x1.') / h;
4201             $GENERIC() ta2 = ('.$x2.' - '.$a.') / h;
4202             $GENERIC() tb1 = ('.$b.' - '.$x1.') / h;
4203             $GENERIC() tb2 = ('.$x2.' - '.$b.') / h;
4204             /* Computing 3rd power */
4205             $GENERIC() ua1 = ta1 * (ta1 * ta1);
4206             $GENERIC() phia1 = ua1 * (2. - ta1);
4207             $GENERIC() psia1 = ua1 * (3. * ta1 - 4.);
4208             /* Computing 3rd power */
4209             $GENERIC() ua2 = ta2 * (ta2 * ta2);
4210             $GENERIC() phia2 = ua2 * (2. - ta2);
4211             $GENERIC() psia2 = -ua2 * (3. * ta2 - 4.);
4212             /* Computing 3rd power */
4213             $GENERIC() ub1 = tb1 * (tb1 * tb1);
4214             $GENERIC() phib1 = ub1 * (2. - tb1);
4215             $GENERIC() psib1 = ub1 * (3. * tb1 - 4.);
4216             /* Computing 3rd power */
4217             $GENERIC() ub2 = tb2 * (tb2 * tb2);
4218             $GENERIC() phib2 = ub2 * (2. - tb2);
4219             $GENERIC() psib2 = -ub2 * (3. * tb2 - 4.);
4220             $GENERIC() fterm = '.$f1.' * (phia2 - phib2) + '.$f2.' * (phib1 - phia1);
4221             $GENERIC() dterm = ('.$d1.' * (psia2 - psib2) + '.$d2.' * (psib1 - psia1)) * (h / 6.);
4222             '.$out.' = 0.5 * h * (fterm + dterm);
4223             } while(0)
4224             '},
4225             PCHID => sub {my ($ia, $ib, $out) = @_; '
4226             /* Programming notes: */
4227             /* 1. This routine uses a special formula that is valid only for */
4228             /* integrals whose limits coincide with data values. This is */
4229             /* mathematically equivalent to, but much more efficient than, */
4230             /* calls to DCHFIE. */
4231             /* VALIDITY-CHECK ARGUMENTS. */
4232             do {
4233             /* FUNCTION DEFINITION IS OK, GO ON. */
4234             $skip() = 1;
4235             if ('.$ia.' < 0 || '.$ia.' > $SIZE(n)-1 || '.$ib.' < 0 || '.$ib.' > $SIZE(n)-1) {
4236             $ierr() = -4;
4237             $CROAK("IA OR IB OUT OF RANGE");
4238             }
4239             $ierr() = 0;
4240             /* COMPUTE INTEGRAL VALUE. */
4241             if ('.$ia.' == '.$ib.') { '.$out.' = 0; continue; }
4242             PDL_Indx low = PDLMIN('.$ia.','.$ib.'), iup = PDLMAX('.$ia.','.$ib.');
4243             $GENERIC() sum = 0.;
4244             loop (n=low:iup) %{
4245             $GENERIC() h = $x(n=>n+1) - $x();
4246             sum += h * ($f() + $f(n=>n+1) + ($d() - $d(n=>n+1)) * (h / 6.));
4247             %}
4248             '.$out.' = 0.5 * ('.$ia.' > '.$ib.' ? -sum : sum);
4249             } while(0)
4250             '},
4251             CHFD => sub {my ($do_deriv) = @_; '
4252             /* Programming notes: */
4253             /* 2. Most of the coding between the call to DCHFDV and the end of */
4254             /* the IR-loop could be eliminated if it were permissible to */
4255             /* assume that XE is ordered relative to X. */
4256             /* 3. DCHFDV does not assume that X1 is less than X2. thus, it would */
4257             /* be possible to write a version of DPCHFD that assumes a strict- */
4258             /* ly decreasing X-array by simply running the IR-loop backwards */
4259             /* (and reversing the order of appropriate tests). */
4260             /* 4. The present code has a minor bug, which I have decided is not */
4261             /* worth the effort that would be required to fix it. */
4262             /* If XE contains points in [X(N-1),X(N)], followed by points .LT. */
4263             /* X(N-1), followed by points .GT.X(N), the extrapolation points */
4264             /* will be counted (at least) twice in the total returned in IERR. */
4265             /* VALIDITY-CHECK ARGUMENTS. */
4266             if (!$skip()) {
4267             loop (n=1) %{
4268             if ($x() > $x(n=>n-1)) continue;
4269             $ierr() = -3;
4270             $CROAK("X-ARRAY NOT STRICTLY INCREASING");
4271             %}
4272             }
4273             /* FUNCTION DEFINITION IS OK, GO ON. */
4274             $ierr() = 0;
4275             $skip() = 1;
4276             /* LOOP OVER INTERVALS. ( INTERVAL INDEX IS IL = IR-1 . ) */
4277             /* ( INTERVAL IS X(IL).LE.X.LT.X(IR) . ) */
4278             PDL_Indx n = $SIZE(n), ne = $SIZE(ne);
4279             PDL_Indx jfirst = 0, ir;
4280             for (ir = 1; ir < n && jfirst < ne; ++ir) {
4281             /* SKIP OUT OF LOOP IF HAVE PROCESSED ALL EVALUATION POINTS. */
4282             /* LOCATE ALL POINTS IN INTERVAL. */
4283             char located = 0;
4284             PDL_Indx j = jfirst;
4285             loop (ne=jfirst) %{
4286             j = ne;
4287             if ($xe() >= $x(n=>ir)) {
4288             located = 1;
4289             break;
4290             }
4291             %}
4292             if (!located || ir == n-1)
4293             j = ne;
4294             /* HAVE LOCATED FIRST POINT BEYOND INTERVAL. */
4295             PDL_Indx nj = j - jfirst;
4296             /* SKIP EVALUATION IF NO POINTS IN INTERVAL. */
4297             if (nj == 0)
4298             continue;
4299             /* EVALUATE CUBIC AT XE(I), I = JFIRST (1) J-1 . */
4300             /* ---------------------------------------------------------------- */
4301             PDL_Indx next[] = {0,0};
4302             do { /* inline dchfdv */
4303             /* Local variables */
4304             $GENERIC() x1 = $x(n=>ir-1), x2 = $x(n=>ir);
4305             $GENERIC() f1 = $f(n=>ir-1), f2 = $f(n=>ir);
4306             $GENERIC() d1 = $d(n=>ir-1), d2 = $d(n=>ir);
4307             $GENERIC() h = x2 - x1;
4308             if (h == 0.)
4309             $CROAK("INTERVAL ENDPOINTS EQUAL");
4310             /* INITIALIZE. */
4311             $GENERIC() xmi = PDLMIN(0.,h);
4312             $GENERIC() xma = PDLMAX(0.,h);
4313             /* COMPUTE CUBIC COEFFICIENTS (EXPANDED ABOUT X1). */
4314             $GENERIC() delta = (f2 - f1) / h;
4315             $GENERIC() del1 = (d1 - delta) / h;
4316             $GENERIC() del2 = (d2 - delta) / h;
4317             /* (DELTA IS NO LONGER NEEDED.) */
4318             $GENERIC() c2 = -(del1 + del1 + del2);
4319             '.($do_deriv ? '$GENERIC() c2t2 = c2 + c2;' : '').'
4320             $GENERIC() c3 = (del1 + del2) / h;
4321             /* (H, DEL1 AND DEL2 ARE NO LONGER NEEDED.) */
4322             '.($do_deriv ? '$GENERIC() c3t3 = c3 + c3 + c3;' : '').'
4323             /* EVALUATION LOOP. */
4324             loop (ne=:nj) %{
4325             $GENERIC() x = $xe(ne=>ne+jfirst) - x1;
4326             $fe(ne=>ne+jfirst) = f1 + x * (d1 + x * (c2 + x * c3));
4327             '.($do_deriv ? '$de(ne=>ne+jfirst) = d1 + x * (c2t2 + x * c3t3);' : '').'
4328             /* COUNT EXTRAPOLATION POINTS. */
4329             if (x < xmi)
4330             ++next[0];
4331             if (x > xma)
4332             ++next[1];
4333             /* (NOTE REDUNDANCY--IF EITHER CONDITION IS TRUE, OTHER IS FALSE.) */
4334             %}
4335             } while (0); /* end inline dchfdv */
4336             /* ---------------------------------------------------------------- */
4337             if (next[1] != 0) {
4338             /* IN THE CURRENT SET OF XE-POINTS, THERE ARE NEXT(2) TO THE */
4339             /* RIGHT OF X(IR). */
4340             /* THESE ARE ACTUALLY EXTRAPOLATION POINTS. */
4341             $ierr() += next[1];
4342             }
4343             if (next[0] != 0) {
4344             /* IN THE CURRENT SET OF XE-POINTS, THERE ARE NEXT(1) TO THE */
4345             /* LEFT OF X(IR-1). */
4346             if (ir < 2) {
4347             /* THESE ARE ACTUALLY EXTRAPOLATION POINTS. */
4348             $ierr() += next[0];
4349             jfirst = j;
4350             continue;
4351             }
4352             /* XE IS NOT ORDERED RELATIVE TO X, SO MUST ADJUST */
4353             /* EVALUATION INTERVAL. */
4354             /* FIRST, LOCATE FIRST POINT TO LEFT OF X(IR-1). */
4355             located = 0;
4356             PDL_Indx i = jfirst;
4357             loop (ne=jfirst:j) %{
4358             i = ne;
4359             if ($xe() < $x(n=>ir-1)) {
4360             located = 1;
4361             break;
4362             }
4363             %}
4364             if (!located) {
4365             /* NOTE-- CANNOT DROP THROUGH HERE UNLESS THERE IS AN ERROR */
4366             /* IN DCHFDV. */
4367             $ierr() = -5;
4368             $CROAK("ERROR RETURN FROM DCHFDV -- FATAL");
4369             }
4370             /* RESET J. (THIS WILL BE THE NEW JFIRST.) */
4371             j = i;
4372             /* NOW FIND OUT HOW FAR TO BACK UP IN THE X-ARRAY. */
4373             loop (n=:ir) %{
4374             i = n;
4375             if ($xe(ne=>j) < $x())
4376             break;
4377             %}
4378             /* NB-- CAN NEVER DROP THROUGH HERE, SINCE XE(J).LT.X(IR-1). */
4379             /* AT THIS POINT, EITHER XE(J) .LT. X(1) */
4380             /* OR X(I-1) .LE. XE(J) .LT. X(I) . */
4381             /* RESET IR, RECOGNIZING THAT IT WILL BE INCREMENTED BEFORE */
4382             /* CYCLING. */
4383             /* Computing MAX */
4384             ir = PDLMAX(0,i-1);
4385             }
4386             jfirst = j;
4387             /* END OF IR-LOOP. */
4388             }
4389             '},
4390             );
4391              
4392             pp_def('pchip_chim',
4393             Pars => 'x(n); f(n); [o]d(n); indx [o]ierr();',
4394             RedoDimsCode => <<'EOF',
4395             if ($SIZE(n) < 2) $CROAK("NUMBER OF DATA POINTS LESS THAN TWO");
4396             EOF
4397             Code => pp_line_numbers(__LINE__, <<'EOF'),
4398             /* Local variables */
4399             $GENERIC() dmax, hsumt3;
4400             PDL_Indx n = $SIZE(n);
4401             /* VALIDITY-CHECK ARGUMENTS. */
4402             loop (n=1) %{
4403             if ($x() > $x(n=>n-1)) continue;
4404             $ierr() = -1;
4405             $CROAK("X-ARRAY NOT STRICTLY INCREASING");
4406             %}
4407             /* FUNCTION DEFINITION IS OK, GO ON. */
4408             $ierr() = 0;
4409             $GENERIC() h1 = $x(n=>1) - $x(n=>0);
4410             $GENERIC() del1 = ($f(n=>1) - $f(n=>0)) / h1;
4411             $GENERIC() dsave = del1;
4412             /* SPECIAL CASE N=2 -- USE LINEAR INTERPOLATION. */
4413             if (n <= 2) {
4414             $d(n=>0) = $d(n=>1) = del1;
4415             continue;
4416             }
4417             /* NORMAL CASE (N .GE. 3). */
4418             $GENERIC() h2 = $x(n=>2) - $x(n=>1);
4419             $GENERIC() del2 = ($f(n=>2) - $f(n=>1)) / h2;
4420             /* SET D(1) VIA NON-CENTERED THREE-POINT FORMULA, ADJUSTED TO BE */
4421             /* SHAPE-PRESERVING. */
4422             $GENERIC() hsum = h1 + h2;
4423             $GENERIC() w1 = (h1 + hsum) / hsum;
4424             $GENERIC() w2 = -h1 / hsum;
4425             $d(n=>0) = w1 * del1 + w2 * del2;
4426             if ($PCHST($d(n=>0), del1) <= 0.) {
4427             $d(n=>0) = 0.;
4428             } else if ($PCHST(del1, del2) < 0.) {
4429             /* NEED DO THIS CHECK ONLY IF MONOTONICITY SWITCHES. */
4430             dmax = 3. * del1;
4431             if (PDL_ABS($d(n=>0)) > PDL_ABS(dmax)) {
4432             $d(n=>0) = dmax;
4433             }
4434             }
4435             /* LOOP THROUGH INTERIOR POINTS. */
4436             loop (n=1:-1) %{
4437             if (n != 1) {
4438             h1 = h2;
4439             h2 = $x(n=>n+1) - $x();
4440             hsum = h1 + h2;
4441             del1 = del2;
4442             del2 = ($f(n=>n+1) - $f()) / h2;
4443             }
4444             /* SET D(I)=0 UNLESS DATA ARE STRICTLY MONOTONIC. */
4445             $d() = 0.;
4446             $GENERIC() dtmp = $PCHST(del1, del2);
4447             if (dtmp <= 0) {
4448             if (dtmp == 0.) {
4449             /* COUNT NUMBER OF CHANGES IN DIRECTION OF MONOTONICITY. */
4450             if (del2 == 0.) {
4451             continue;
4452             }
4453             if ($PCHST(dsave, del2) < 0.) {
4454             ++($ierr());
4455             }
4456             dsave = del2;
4457             continue;
4458             }
4459             ++($ierr());
4460             dsave = del2;
4461             continue;
4462             }
4463             /* USE BRODLIE MODIFICATION OF BUTLAND FORMULA. */
4464             hsumt3 = hsum + hsum + hsum;
4465             w1 = (hsum + h1) / hsumt3;
4466             w2 = (hsum + h2) / hsumt3;
4467             /* Computing MAX */
4468             dmax = PDLMAX(PDL_ABS(del1),PDL_ABS(del2));
4469             /* Computing MIN */
4470             $GENERIC() dmin = PDLMIN(PDL_ABS(del1),PDL_ABS(del2));
4471             $GENERIC() drat1 = del1 / dmax;
4472             $GENERIC() drat2 = del2 / dmax;
4473             $d() = dmin / (w1 * drat1 + w2 * drat2);
4474             %}
4475             /* SET D(N) VIA NON-CENTERED THREE-POINT FORMULA, ADJUSTED TO BE */
4476             /* SHAPE-PRESERVING. */
4477             w1 = -h2 / hsum;
4478             w2 = (h2 + hsum) / hsum;
4479             $d(n=>n-1) = w1 * del1 + w2 * del2;
4480             if ($PCHST($d(n=>n-1), del2) <= 0.) {
4481             $d(n=>n-1) = 0.;
4482             } else if ($PCHST(del1, del2) < 0.) {
4483             /* NEED DO THIS CHECK ONLY IF MONOTONICITY SWITCHES. */
4484             dmax = 3. * del2;
4485             if (PDL_ABS($d(n=>n-1)) > PDL_ABS(dmax)) {
4486             $d(n=>n-1) = dmax;
4487             }
4488             }
4489             EOF
4490             ParamDesc => {
4491             x => 'ordinate data',
4492             f => <<'EOF',
4493             array of dependent variable values to be
4494             interpolated. F(I) is value corresponding to
4495             X(I). C is designed for monotonic data, but it will
4496             work for any F-array. It will force extrema at points where
4497             monotonicity switches direction. If some other treatment of
4498             switch points is desired, DPCHIC should be used instead.
4499             EOF
4500             d => <<'EOF',
4501             array of derivative values at the data
4502             points. If the data are monotonic, these values will
4503             determine a monotone cubic Hermite function.
4504             EOF
4505             ierr => <<'EOF',
4506             Error status:
4507              
4508             =over
4509              
4510             =item *
4511              
4512             0 if successful.
4513              
4514             =item *
4515              
4516             E 0 if there were C switches in the direction of
4517             monotonicity (data still valid).
4518              
4519             =item *
4520              
4521             -1 if C 2>.
4522              
4523             =item *
4524              
4525             -3 if C<$x> is not strictly increasing.
4526              
4527             =back
4528              
4529             (The D-array has not been changed in any of these cases.)
4530             NOTE: The above errors are checked in the order listed,
4531             and following arguments have B been validated.
4532             EOF
4533             },
4534             Doc => <<'EOF',
4535             =for ref
4536              
4537             Calculate the derivatives of (x,f(x)) using cubic Hermite interpolation.
4538              
4539             Calculate the derivatives needed to determine a monotone piecewise
4540             cubic Hermite interpolant to the given set of points (C<$x,$f>,
4541             where C<$x> is strictly increasing).
4542             The resulting set of points - C<$x,$f,$d>, referred to
4543             as the cubic Hermite representation - can then be used in
4544             other functions, such as L, L,
4545             and L.
4546              
4547             The boundary conditions are compatible with monotonicity,
4548             and if the data are only piecewise monotonic, the interpolant
4549             will have an extremum at the switch points; for more control
4550             over these issues use L.
4551              
4552             References:
4553              
4554             1. F. N. Fritsch and J. Butland, A method for constructing
4555             local monotone piecewise cubic interpolants, SIAM
4556             Journal on Scientific and Statistical Computing 5, 2
4557             (June 1984), pp. 300-304.
4558              
4559             F. N. Fritsch and R. E. Carlson, Monotone piecewise
4560             cubic interpolation, SIAM Journal on Numerical Analysis
4561             17, 2 (April 1980), pp. 238-246.
4562             EOF
4563             );
4564              
4565             pp_def('pchip_chic',
4566             Pars => 'sbyte ic(two=2); vc(two=2); mflag(); x(n); f(n);
4567             [o]d(n); indx [o]ierr();
4568             [t]h(nless1=CALC($SIZE(n)-1)); [t]slope(nless1);',
4569             GenericTypes => $F,
4570             RedoDimsCode => <<'EOF',
4571             if ($SIZE(n) < 2) $CROAK("NUMBER OF DATA POINTS LESS THAN TWO");
4572             EOF
4573             Code => pp_line_numbers(__LINE__, <<'EOF'),
4574             const $GENERIC() d1mach = $TFDE(FLT_EPSILON,DBL_EPSILON,LDBL_EPSILON);
4575             /* VALIDITY-CHECK ARGUMENTS. */
4576             loop (n=1) %{
4577             if ($x() > $x(n=>n-1)) continue;
4578             $ierr() = -1;
4579             $CROAK("X-ARRAY NOT STRICTLY INCREASING");
4580             %}
4581             PDL_Indx ibeg = $ic(two=>0), iend = $ic(two=>1), n = $SIZE(n);
4582             $ierr() = 0;
4583             if (PDL_ABS(ibeg) > 5)
4584             --($ierr());
4585             if (PDL_ABS(iend) > 5)
4586             $ierr() += -2;
4587             if ($ierr() < 0) {
4588             $ierr() += -3;
4589             $CROAK("IC OUT OF RANGE");
4590             }
4591             /* FUNCTION DEFINITION IS OK -- GO ON. */
4592             /* SET UP H AND SLOPE ARRAYS. */
4593             loop (nless1) %{
4594             $h() = $x(n=>nless1+1) - $x(n=>nless1);
4595             $slope() = $f(n=>nless1+1) - $f(n=>nless1);
4596             %}
4597             /* SPECIAL CASE N=2 -- USE LINEAR INTERPOLATION. */
4598             if ($SIZE(nless1) <= 1) {
4599             $d(n=>0) = $d(n=>1) = $slope(nless1=>0);
4600             } else {
4601             /* NORMAL CASE (N .GE. 3) . */
4602             /* SET INTERIOR DERIVATIVES AND DEFAULT END CONDITIONS. */
4603             do { /* inline dpchci */
4604             /* Local variables */
4605             $GENERIC() del1 = $slope(nless1=>0);
4606             /* SPECIAL CASE N=2 is dealt with in separate branch above */
4607             /* NORMAL CASE (N .GE. 3). */
4608             $GENERIC() del2 = $slope(nless1=>1);
4609             /* SET D(1) VIA NON-CENTERED THREE-POINT FORMULA, ADJUSTED TO BE */
4610             /* SHAPE-PRESERVING. */
4611             $GENERIC() hsum = $h(nless1=>0) + $h(nless1=>1);
4612             $GENERIC() w1 = ($h(nless1=>0) + hsum) / hsum;
4613             $GENERIC() w2 = -$h(nless1=>0) / hsum;
4614             $d(n=>0) = w1 * del1 + w2 * del2;
4615             if ($PCHST($d(n=>0), del1) <= 0.) {
4616             $d(n=>0) = 0.;
4617             } else if ($PCHST(del1, del2) < 0.) {
4618             /* NEED DO THIS CHECK ONLY IF MONOTONICITY SWITCHES. */
4619             $GENERIC() dmax = 3. * del1;
4620             if (PDL_ABS($d(n=>0)) > PDL_ABS(dmax))
4621             $d(n=>0) = dmax;
4622             }
4623             /* LOOP THROUGH INTERIOR POINTS. */
4624             loop (nless1=1) %{
4625             if (nless1 != 1) {
4626             hsum = $h(nless1=>nless1-1) + $h();
4627             del1 = del2;
4628             del2 = $slope();
4629             }
4630             /* SET D(I)=0 UNLESS DATA ARE STRICTLY MONOTONIC. */
4631             $d(n=>nless1) = 0.;
4632             if ($PCHST(del1, del2) <= 0.)
4633             continue;
4634             /* USE BRODLIE MODIFICATION OF BUTLAND FORMULA. */
4635             $GENERIC() hsumt3 = hsum + hsum + hsum;
4636             w1 = (hsum + $h(nless1=>nless1-1)) / hsumt3;
4637             w2 = (hsum + $h()) / hsumt3;
4638             /* Computing MAX */
4639             $GENERIC() dmax = PDLMAX(PDL_ABS(del1),PDL_ABS(del2));
4640             /* Computing MIN */
4641             $GENERIC() dmin = PDLMIN(PDL_ABS(del1),PDL_ABS(del2));
4642             $GENERIC() drat1 = del1 / dmax, drat2 = del2 / dmax;
4643             $d(n=>nless1) = dmin / (w1 * drat1 + w2 * drat2);
4644             %}
4645             /* SET D(N) VIA NON-CENTERED THREE-POINT FORMULA, ADJUSTED TO BE */
4646             /* SHAPE-PRESERVING. */
4647             w1 = -$h(nless1=>n-2) / hsum;
4648             w2 = ($h(nless1=>n-2) + hsum) / hsum;
4649             $d(n=>n-1) = w1 * del1 + w2 * del2;
4650             if ($PCHST($d(n=>n-1), del2) <= 0.) {
4651             $d(n=>n-1) = 0.;
4652             } else if ($PCHST(del1, del2) < 0.) {
4653             /* NEED DO THIS CHECK ONLY IF MONOTONICITY SWITCHES. */
4654             $GENERIC() dmax = 3. * del2;
4655             if (PDL_ABS($d(n=>n-1)) > PDL_ABS(dmax))
4656             $d(n=>n-1) = dmax;
4657             }
4658             } while (0); /* end inline dpchci */
4659             /* SET DERIVATIVES AT POINTS WHERE MONOTONICITY SWITCHES DIRECTION. */
4660             if ($mflag() != 0.) {
4661             do { /* inline dpchcs */
4662             /* ***PURPOSE Adjusts derivative values for DPCHIC */
4663             /* DPCHCS: DPCHIC Monotonicity Switch Derivative Setter. */
4664             /* Called by DPCHIC to adjust the values of D in the vicinity of a */
4665             /* switch in direction of monotonicity, to produce a more "visually */
4666             /* pleasing" curve than that given by DPCHIM . */
4667             static const $GENERIC() fudge = 4.;
4668             /* Local variables */
4669             PDL_Indx k;
4670             $GENERIC() del[3], fact, dfmx;
4671             $GENERIC() dext, dfloc, slmax, wtave[2];
4672             /* INITIALIZE. */
4673             /* LOOP OVER SEGMENTS. */
4674             loop (nless1=1) %{
4675             $GENERIC() dtmp = $PCHST($slope(nless1=>nless1-1), $slope());
4676             if (dtmp > 0.) {
4677             continue;
4678             }
4679             if (dtmp != 0.) {
4680             /* ....... SLOPE SWITCHES MONOTONICITY AT I-TH POINT ..................... */
4681             /* DO NOT CHANGE D IF 'UP-DOWN-UP'. */
4682             if (nless1 > 1) {
4683             if ($PCHST($slope(nless1=>nless1-2), $slope()) > 0.)
4684             continue;
4685             /* -------------------------- */
4686             }
4687             if (nless1 < $SIZE(nless1)-1 && $PCHST($slope(nless1=>nless1+1), $slope(nless1=>nless1-1)) > 0.)
4688             continue;
4689             /* ---------------------------- */
4690             /* ....... COMPUTE PROVISIONAL VALUE FOR D(1,I). */
4691             dext = $h() / ($h(nless1=>nless1-1) + $h()) * $slope(nless1=>nless1-1) +
4692             $h(nless1=>nless1-1) / ($h(nless1=>nless1-1) + $h()) * $slope();
4693             /* ....... DETERMINE WHICH INTERVAL CONTAINS THE EXTREMUM. */
4694             dtmp = $PCHST(dext, $slope(nless1=>nless1-1));
4695             if (dtmp == 0) {
4696             continue;
4697             }
4698             if (dtmp < 0.) {
4699             /* DEXT AND SLOPE(I-1) HAVE OPPOSITE SIGNS -- */
4700             /* EXTREMUM IS IN (X(I-1),X(I)). */
4701             k = nless1;
4702             /* SET UP TO COMPUTE NEW VALUES FOR D(1,I-1) AND D(1,I). */
4703             wtave[1] = dext;
4704             if (k > 1) {
4705             wtave[0] = $h(nless1=>k-1) / ($h(nless1=>k-2) + $h(nless1=>k-1)) * $slope(nless1=>k-2) +
4706             $h(nless1=>k-2) / ($h(nless1=>k-2) + $h(nless1=>k)) * $slope(nless1=>k-1);
4707             }
4708             } else {
4709             /* DEXT AND SLOPE(I) HAVE OPPOSITE SIGNS -- */
4710             /* EXTREMUM IS IN (X(I),X(I+1)). */
4711             k = nless1 + 1;
4712             /* SET UP TO COMPUTE NEW VALUES FOR D(1,I) AND D(1,I+1). */
4713             wtave[0] = dext;
4714             if (k < nless1) {
4715             wtave[1] = $h(nless1=>k) / ($h(nless1=>k-1) + $h(nless1=>k)) * $slope(nless1=>k-1) + $h(nless1=>k-1)
4716             / ($h(nless1=>k-1) + $h(nless1=>k)) * $slope(nless1=>k);
4717             }
4718             }
4719             } else {
4720             /* ....... AT LEAST ONE OF SLOPE(I-1) AND SLOPE(I) IS ZERO -- */
4721             /* CHECK FOR FLAT-TOPPED PEAK ....................... */
4722             if (nless1 == $SIZE(nless1)-1 || $PCHST($slope(nless1=>nless1-1), $slope(nless1=>nless1+1)) >= 0.)
4723             continue;
4724             /* ----------------------------- */
4725             /* WE HAVE FLAT-TOPPED PEAK ON (X(I),X(I+1)). */
4726             k = nless1+1;
4727             /* SET UP TO COMPUTE NEW VALUES FOR D(1,I) AND D(1,I+1). */
4728             wtave[0] = $h(nless1=>k-1) / ($h(nless1=>k-2) + $h(nless1=>k-1)) * $slope(nless1=>k-2) + $h(nless1=>k-2)
4729             / ($h(nless1=>k-2) + $h(nless1=>k-1)) * $slope(nless1=>k-1);
4730             wtave[1] = $h(nless1=>k) / ($h(nless1=>k-1) + $h(nless1=>k)) * $slope(nless1=>k-1) + $h(nless1=>k-1) / (
4731             $h(nless1=>k-1) + $h(nless1=>k)) * $slope(nless1=>k);
4732             }
4733             /* ....... AT THIS POINT WE HAVE DETERMINED THAT THERE WILL BE AN EXTREMUM */
4734             /* ON (X(K),X(K+1)), WHERE K=I OR I-1, AND HAVE SET ARRAY WTAVE-- */
4735             /* WTAVE(1) IS A WEIGHTED AVERAGE OF SLOPE(K-1) AND SLOPE(K), */
4736             /* IF K.GT.1 */
4737             /* WTAVE(2) IS A WEIGHTED AVERAGE OF SLOPE(K) AND SLOPE(K+1), */
4738             /* IF K.LT.N-1 */
4739             slmax = PDL_ABS($slope(nless1=>k-1));
4740             if (k > 1) {
4741             /* Computing MAX */
4742             slmax = PDLMAX(slmax,PDL_ABS($slope(nless1=>k-2)));
4743             }
4744             if (k < nless1) {
4745             /* Computing MAX */
4746             slmax = PDLMAX(slmax,PDL_ABS($slope(nless1=>k)));
4747             }
4748             if (k > 1) {
4749             del[0] = $slope(nless1=>k-2) / slmax;
4750             }
4751             del[1] = $slope(nless1=>k-1) / slmax;
4752             if (k < nless1) {
4753             del[2] = $slope(nless1=>k) / slmax;
4754             }
4755             if (k > 1 && k < nless1) {
4756             /* NORMAL CASE -- EXTREMUM IS NOT IN A BOUNDARY INTERVAL. */
4757             fact = fudge * PDL_ABS(del[2] * (del[0] - del[1]) * (wtave[1] / slmax));
4758             $d(n=>k-1) += PDLMIN(fact,1.) * (wtave[0] - $d(n=>k-1));
4759             fact = fudge * PDL_ABS(del[0] * (del[2] - del[1]) * (wtave[0] / slmax));
4760             $d(n=>k) += PDLMIN(fact,1.) * (wtave[1] - $d(n=>k));
4761             } else {
4762             /* SPECIAL CASE K=1 (WHICH CAN OCCUR ONLY IF I=2) OR */
4763             /* K=NLESS1 (WHICH CAN OCCUR ONLY IF I=NLESS1). */
4764             fact = fudge * PDL_ABS(del[1]);
4765             $d(n=>nless1) = PDLMIN(fact,1.) * wtave[nless1+1 - k];
4766             /* NOTE THAT I-K+1 = 1 IF K=I (=NLESS1), */
4767             /* I-K+1 = 2 IF K=I-1(=1). */
4768             }
4769             /* ....... ADJUST IF NECESSARY TO LIMIT EXCURSIONS FROM DATA. */
4770             if ($mflag() <= 0.) {
4771             continue;
4772             }
4773             dfloc = $h(nless1=>k-1) * PDL_ABS($slope(nless1=>k-1));
4774             if (k > 1) {
4775             /* Computing MAX */
4776             dfloc = PDLMAX(dfloc,$h(nless1=>k-2) * PDL_ABS($slope(nless1=>k-2)));
4777             }
4778             if (k < nless1) {
4779             /* Computing MAX */
4780             dfloc = PDLMAX(dfloc,$h(nless1=>k) * PDL_ABS($slope(nless1=>k)));
4781             }
4782             dfmx = $mflag() * dfloc;
4783             PDL_Indx indx = nless1 - k;
4784             /* INDX = 1 IF K=I, 2 IF K=I-1. */
4785             /* --------------------------------------------------------------- */
4786             do { /* inline dpchsw */
4787             /* NOTATION AND GENERAL REMARKS. */
4788             /* RHO IS THE RATIO OF THE DATA SLOPE TO THE DERIVATIVE BEING TESTED. */
4789             /* LAMBDA IS THE RATIO OF D2 TO D1. */
4790             /* THAT = T-HAT(RHO) IS THE NORMALIZED LOCATION OF THE EXTREMUM. */
4791             /* PHI IS THE NORMALIZED VALUE OF P(X)-F1 AT X = XHAT = X-HAT(RHO), */
4792             /* WHERE THAT = (XHAT - X1)/H . */
4793             /* THAT IS, P(XHAT)-F1 = D*H*PHI, WHERE D=D1 OR D2. */
4794             /* SIMILARLY, P(XHAT)-F2 = D*H*(PHI-RHO) . */
4795             /* Local variables */
4796             $GENERIC() cp, nu, phi, rho, hphi, that, sigma, small;
4797             $GENERIC() lambda, radcal;
4798             $GENERIC() d1 = $d(n=>k-1), d2 = $d(n=>k), h2 = $h(nless1=>k-1), slope2 = $slope(nless1=>k-1);
4799             /* Initialized data */
4800             static const $GENERIC() fact = 100.;
4801             /* THIRD SHOULD BE SLIGHTLY LESS THAN 1/3. */
4802             static const $GENERIC() third = .33333;
4803             /* SMALL SHOULD BE A FEW ORDERS OF MAGNITUDE GREATER THAN MACHEPS. */
4804             small = fact * d1mach;
4805             /* DO MAIN CALCULATION. */
4806             if (d1 == 0.) {
4807             /* SPECIAL CASE -- D1.EQ.ZERO . */
4808             /* IF D2 IS ALSO ZERO, THIS ROUTINE SHOULD NOT HAVE BEEN CALLED. */
4809             if (d2 == 0.) {
4810             $ierr() = -1;
4811             $CROAK("D1 AND/OR D2 INVALID");
4812             }
4813             rho = slope2 / d2;
4814             /* EXTREMUM IS OUTSIDE INTERVAL WHEN RHO .GE. 1/3 . */
4815             if (rho >= third) {
4816             $ierr() = 0; break;
4817             }
4818             that = 2. * (3. * rho - 1.) / (3. * (2. * rho - 1.));
4819             /* Computing 2nd power */
4820             phi = that * that * ((3. * rho - 1.) / 3.);
4821             /* CONVERT TO DISTANCE FROM F2 IF IEXTRM.NE.1 . */
4822             if (indx != 3) {
4823             phi -= rho;
4824             }
4825             /* TEST FOR EXCEEDING LIMIT, AND ADJUST ACCORDINGLY. */
4826             hphi = h2 * PDL_ABS(phi);
4827             if (hphi * PDL_ABS(d2) > dfmx) {
4828             /* AT THIS POINT, HPHI.GT.0, SO DIVIDE IS OK. */
4829             d2 = $SIGN(dfmx / hphi, d2);
4830             }
4831             } else {
4832             rho = slope2 / d1;
4833             lambda = -(d2) / d1;
4834             if (d2 == 0.) {
4835             /* SPECIAL CASE -- D2.EQ.ZERO . */
4836             /* EXTREMUM IS OUTSIDE INTERVAL WHEN RHO .GE. 1/3 . */
4837             if (rho >= third) {
4838             $ierr() = 0; break;
4839             }
4840             cp = 2. - 3. * rho;
4841             nu = 1. - 2. * rho;
4842             that = 1. / (3. * nu);
4843             } else {
4844             if (lambda <= 0.) {
4845             $ierr() = -1;
4846             $CROAK("D1 AND/OR D2 INVALID");
4847             }
4848             /* NORMAL CASE -- D1 AND D2 BOTH NONZERO, OPPOSITE SIGNS. */
4849             nu = 1. - lambda - 2. * rho;
4850             sigma = 1. - rho;
4851             cp = nu + sigma;
4852             if (PDL_ABS(nu) > small) {
4853             /* Computing 2nd power */
4854             radcal = (nu - (2. * rho + 1.)) * nu + sigma * sigma;
4855             if (radcal < 0.) {
4856             $ierr() = -2;
4857             $CROAK("NEGATIVE RADICAL");
4858             }
4859             that = (cp - sqrt(radcal)) / (3. * nu);
4860             } else {
4861             that = 1. / (2. * sigma);
4862             }
4863             }
4864             phi = that * ((nu * that - cp) * that + 1.);
4865             /* CONVERT TO DISTANCE FROM F2 IF IEXTRM.NE.1 . */
4866             if (indx != 3) {
4867             phi -= rho;
4868             }
4869             /* TEST FOR EXCEEDING LIMIT, AND ADJUST ACCORDINGLY. */
4870             hphi = h2 * PDL_ABS(phi);
4871             if (hphi * PDL_ABS(d1) > dfmx) {
4872             /* AT THIS POINT, HPHI.GT.0, SO DIVIDE IS OK. */
4873             d1 = $SIGN(dfmx / hphi, d1);
4874             d2 = -lambda * d1;
4875             }
4876             }
4877             $ierr() = 0;
4878             } while (0); /* end inline dpchsw */
4879             /* --------------------------------------------------------------- */
4880             if ($ierr() != 0) {
4881             break;
4882             }
4883             %} /* ....... END OF SEGMENT LOOP. */
4884             } while (0); /* end inline dpchcs */
4885             }
4886             }
4887             /* SET END CONDITIONS. */
4888             if (ibeg == 0 && iend == 0)
4889             continue;
4890             /* ------------------------------------------------------- */
4891             do { /* inline dpchce */
4892             /* Local variables */
4893             PDL_Indx j, k, ibeg = $ic(two=>0), iend = $ic(two=>1);
4894             $GENERIC() stemp[3], xtemp[4];
4895             /* SET TO DEFAULT BOUNDARY CONDITIONS IF N IS TOO SMALL. */
4896             if (PDL_ABS(ibeg) > n)
4897             ibeg = 0;
4898             if (PDL_ABS(iend) > n)
4899             iend = 0;
4900             /* TREAT BEGINNING BOUNDARY CONDITION. */
4901             if (ibeg != 0) {
4902             k = PDL_ABS(ibeg);
4903             if (k == 1) {
4904             /* BOUNDARY VALUE PROVIDED. */
4905             $d(n=>0) = $vc(two=>0);
4906             } else if (k == 2) {
4907             /* BOUNDARY SECOND DERIVATIVE PROVIDED. */
4908             $d(n=>0) = 0.5 * (3. * $slope(nless1=>0) - $d(n=>1) - 0.5 * $vc(two=>0) * $h(nless1=>0));
4909             } else if (k < 5) {
4910             /* USE K-POINT DERIVATIVE FORMULA. */
4911             /* PICK UP FIRST K POINTS, IN REVERSE ORDER. */
4912             for (j = 0; j < k; ++j) {
4913             PDL_Indx index = k - j;
4914             /* INDEX RUNS FROM K DOWN TO 1. */
4915             xtemp[j] = $x(n=>index+1);
4916             if (j < k-1) {
4917             stemp[j] = $slope(nless1=>index);
4918             }
4919             }
4920             /* ----------------------------- */
4921             $PCHDF(k, xtemp, stemp, $d(n=>0));
4922             /* ----------------------------- */
4923             } else {
4924             /* USE 'NOT A KNOT' CONDITION. */
4925             $d(n=>0) = (3. * ($h(nless1=>0) * $slope(nless1=>1) + $h(nless1=>1) * $slope(nless1=>0)) -
4926             2. * ($h(nless1=>0) + $h(nless1=>1)) * $d(n=>1) - $h(nless1=>0) * $d(n=>2)) / $h(nless1=>1);
4927             }
4928             /* CHECK D(1,1) FOR COMPATIBILITY WITH MONOTONICITY. */
4929             if (ibeg <= 0) {
4930             if ($slope(nless1=>0) == 0.) {
4931             if ($d(n=>0) != 0.) {
4932             $d(n=>0) = 0.;
4933             ++($ierr());
4934             }
4935             } else if ($PCHST($d(n=>0), $slope(nless1=>0)) < 0.) {
4936             $d(n=>0) = 0.;
4937             ++($ierr());
4938             } else if (PDL_ABS($d(n=>0)) > 3. * PDL_ABS($slope(nless1=>0))) {
4939             $d(n=>0) = 3. * $slope(nless1=>0);
4940             ++($ierr());
4941             }
4942             }
4943             }
4944             /* TREAT END BOUNDARY CONDITION. */
4945             if (iend == 0)
4946             break;
4947             k = PDL_ABS(iend);
4948             if (k == 1) {
4949             /* BOUNDARY VALUE PROVIDED. */
4950             $d(n=>n-1) = $vc(two=>1);
4951             } else if (k == 2) {
4952             /* BOUNDARY SECOND DERIVATIVE PROVIDED. */
4953             $d(n=>n-1) = 0.5 * (3. * $slope(nless1=>n-2) - $d(n=>n-2)
4954             + 0.5 * $vc(two=>1) * $h(nless1=>n-2));
4955             } else if (k < 5) {
4956             /* USE K-POINT DERIVATIVE FORMULA. */
4957             /* PICK UP LAST K POINTS. */
4958             for (j = 0; j < k; ++j) {
4959             PDL_Indx index = n - k + j;
4960             /* INDEX RUNS FROM N+1-K UP TO N. */
4961             xtemp[j] = $x(n=>index);
4962             if (j < k-1) {
4963             stemp[j] = $slope(nless1=>index);
4964             }
4965             }
4966             /* ----------------------------- */
4967             $PCHDF(k, xtemp, stemp, $d(n=>n-1));
4968             /* ----------------------------- */
4969             } else {
4970             /* USE 'NOT A KNOT' CONDITION. */
4971             $d(n=>n-1) = (3. * ($h(nless1=>n-2) * $slope(nless1=>n-3) +
4972             $h(nless1=>n-3) * $slope(nless1=>n-2)) - 2. * ($h(nless1=>n-2) + $h(nless1=>n-3)) *
4973             $d(n=>n-2) - $h(nless1=>n-2) * $d(n=>n-3)) / $h(nless1=>n-3);
4974             }
4975             if (iend > 0)
4976             break;
4977             /* CHECK D(1,N) FOR COMPATIBILITY WITH MONOTONICITY. */
4978             if ($slope(nless1=>n-2) == 0.) {
4979             if ($d(n=>n-1) != 0.) {
4980             $d(n=>n-1) = 0.;
4981             $ierr() += 2;
4982             }
4983             } else if ($PCHST($d(n=>n-1), $slope(nless1=>n-2)) < 0.) {
4984             $d(n=>n-1) = 0.;
4985             $ierr() += 2;
4986             } else if (PDL_ABS($d(n=>n-1)) > 3. * PDL_ABS($slope(nless1=>n-2))) {
4987             $d(n=>n-1) = 3. * $slope(nless1=>n-2);
4988             $ierr() += 2;
4989             }
4990             } while (0); /* end inlined dpchce */
4991             /* ------------------------------------------------------- */
4992             EOF
4993             ParamDesc => {
4994             ic => <<'EOF',
4995             The first and second elements of C<$ic> determine the boundary
4996             conditions at the start and end of the data respectively.
4997             If the value is 0, then the default condition, as used by
4998             L, is adopted.
4999             If greater than zero, no adjustment for monotonicity is made,
5000             otherwise if less than zero the derivative will be adjusted.
5001             The allowed magnitudes for C are:
5002              
5003             =over
5004              
5005             =item *
5006              
5007             1 if first derivative at C is given in C.
5008              
5009             =item *
5010              
5011             2 if second derivative at C is given in C.
5012              
5013             =item *
5014              
5015             3 to use the 3-point difference formula for C.
5016             (Reverts to the default b.c. if C 3>)
5017              
5018             =item *
5019              
5020             4 to use the 4-point difference formula for C.
5021             (Reverts to the default b.c. if C 4>)
5022              
5023             =item *
5024              
5025             5 to set C so that the second derivative is
5026             continuous at C.
5027             (Reverts to the default b.c. if C 4>)
5028             This option is somewhat analogous to the "not a knot"
5029             boundary condition provided by DPCHSP.
5030              
5031             =back
5032              
5033             The values for C are the same as above, except that
5034             the first-derivative value is stored in C for cases 1 and 2.
5035             The values of C<$vc> need only be set if options 1 or 2 are chosen
5036             for C<$ic>. NOTES:
5037              
5038             =over
5039              
5040             =item *
5041              
5042             Only in case C<$ic(n)> E 0 is it guaranteed that the interpolant
5043             will be monotonic in the first interval. If the returned value of
5044             D(start_or_end) lies between zero and 3*SLOPE(start_or_end), the
5045             interpolant will be monotonic. This is B checked if C<$ic(n)>
5046             E 0.
5047              
5048             =item *
5049              
5050             If C<$ic(n)> E 0 and D(0) had to be changed to achieve monotonicity,
5051             a warning error is returned.
5052              
5053             =back
5054              
5055             Set C<$mflag = 0> if interpolant is required to be monotonic in
5056             each interval, regardless of monotonicity of data. This causes C<$d> to be
5057             set to 0 at all switch points. NOTES:
5058              
5059             =over
5060              
5061             =item *
5062              
5063             This will cause D to be set to zero at all switch points, thus
5064             forcing extrema there.
5065              
5066             =item *
5067              
5068             The result of using this option with the default boundary conditions
5069             will be identical to using DPCHIM, but will generally cost more
5070             compute time. This option is provided only to facilitate comparison
5071             of different switch and/or boundary conditions.
5072              
5073             =back
5074             EOF
5075             vc => 'See ic for details',
5076             mflag => <<'EOF',
5077             Set to non-zero to
5078             use a formula based on the 3-point difference formula at switch
5079             points. If C<$mflag E 0>, then the interpolant at switch points
5080             is forced to not deviate from the data by more than C<$mflag*dfloc>,
5081             where C is the maximum of the change of C<$f> on this interval
5082             and its two immediate neighbours.
5083             If C<$mflag E 0>, no such control is to be imposed.
5084             EOF
5085             x => <<'EOF',
5086             array of independent variable values. The
5087             elements of X must be strictly increasing:
5088              
5089             X(I-1) .LT. X(I), I = 2(1)N.
5090              
5091             (Error return if not.)
5092             EOF
5093             f => <<'EOF',
5094             array of dependent variable values to be
5095             interpolated. F(I) is value corresponding to X(I).
5096             EOF
5097             d => <<'EOF',
5098             array of derivative values at the data
5099             points. These values will determine a monotone cubic
5100             Hermite function on each subinterval on which the data
5101             are monotonic, except possibly adjacent to switches in
5102             monotonicity. The value corresponding to X(I) is stored in D(I).
5103             No other entries in D are changed.
5104             EOF
5105             ierr => <<'EOF',
5106             Error status:
5107              
5108             =over
5109              
5110             =item *
5111              
5112             0 if successful.
5113              
5114             =item *
5115              
5116             1 if C 0> and C had to be adjusted for
5117             monotonicity.
5118              
5119             =item *
5120              
5121             2 if C 0> and C had to be adjusted
5122             for monotonicity.
5123              
5124             =item *
5125              
5126             3 if both 1 and 2 are true.
5127              
5128             =item *
5129              
5130             -1 if C 2>.
5131              
5132             =item *
5133              
5134             -3 if C<$x> is not strictly increasing.
5135              
5136             =item *
5137              
5138             -4 if C 5>.
5139              
5140             =item *
5141              
5142             -5 if C 5>.
5143              
5144             =item *
5145              
5146             -6 if both -4 and -5 are true.
5147              
5148             =item *
5149              
5150             -7 if C 2*(n-1)>.
5151              
5152             =back
5153              
5154             (The D-array has not been changed in any of these cases.)
5155             NOTE: The above errors are checked in the order listed,
5156             and following arguments have B been validated.
5157             EOF
5158             },
5159             Doc => <<'EOF',
5160             =for ref
5161              
5162             Set derivatives needed to determine a piecewise monotone piecewise
5163             cubic Hermite interpolant to given data. User control is available
5164             over boundary conditions and/or treatment of points where monotonicity
5165             switches direction.
5166              
5167             Calculate the derivatives needed to determine a piecewise monotone piecewise
5168             cubic interpolant to the data given in (C<$x,$f>,
5169             where C<$x> is strictly increasing).
5170             Control over the boundary conditions is given by the
5171             C<$ic> and C<$vc> ndarrays, and the value of C<$mflag> determines
5172             the treatment of points where monotonicity switches
5173             direction. A simpler, more restricted, interface is available
5174             using L.
5175             The resulting piecewise cubic Hermite function may be evaluated
5176             by L or L.
5177              
5178             References:
5179              
5180             1. F. N. Fritsch, Piecewise Cubic Hermite Interpolation
5181             Package, Report UCRL-87285, Lawrence Livermore National
5182             Laboratory, July 1982. [Poster presented at the
5183             SIAM 30th Anniversary Meeting, 19-23 July 1982.]
5184              
5185             2. F. N. Fritsch and J. Butland, A method for constructing
5186             local monotone piecewise cubic interpolants, SIAM
5187             Journal on Scientific and Statistical Computing 5, 2
5188             (June 1984), pp. 300-304.
5189              
5190             3. F. N. Fritsch and R. E. Carlson, Monotone piecewise
5191             cubic interpolation, SIAM Journal on Numerical
5192             Analysis 17, 2 (April 1980), pp. 238-246.
5193             EOF
5194             );
5195              
5196             pp_def('pchip_chsp',
5197             Pars => 'sbyte ic(two=2); vc(two=2); x(n); f(n);
5198             [o]d(n); indx [o]ierr();
5199             [t]dx(n); [t]dy_dx(n);
5200             ',
5201             GenericTypes => $F,
5202             RedoDimsCode => <<'EOF',
5203             if ($SIZE(n) < 2) $CROAK("NUMBER OF DATA POINTS LESS THAN TWO");
5204             EOF
5205             Code => pp_line_numbers(__LINE__, <<'EOF'),
5206             /* SINGULAR SYSTEM. */
5207             /* *** THEORETICALLY, THIS CAN ONLY OCCUR IF SUCCESSIVE X-VALUES *** */
5208             /* *** ARE EQUAL, WHICH SHOULD ALREADY HAVE BEEN CAUGHT (IERR=-3). *** */
5209             #define dpchsp_singular(x, ind) \
5210             $ierr() = -8; $CROAK("SINGULAR LINEAR SYSTEM(" #x ") at %td", ind);
5211             /* Local variables */
5212             $GENERIC() stemp[3], xtemp[4];
5213             PDL_Indx n = $SIZE(n), nm1 = n - 1;
5214             /* VALIDITY-CHECK ARGUMENTS. */
5215             loop (n=1) %{
5216             if ($x() > $x(n=>n-1)) continue;
5217             $ierr() = -1;
5218             $CROAK("X-ARRAY NOT STRICTLY INCREASING");
5219             %}
5220             PDL_Indx ibeg = $ic(two=>0), iend = $ic(two=>1), j;
5221             $ierr() = 0;
5222             if (PDL_ABS(ibeg) > 5)
5223             --($ierr());
5224             if (PDL_ABS(iend) > 5)
5225             $ierr() += -2;
5226             if ($ierr() < 0) {
5227             $ierr() += -3;
5228             $CROAK("IC OUT OF RANGE");
5229             }
5230             /* FUNCTION DEFINITION IS OK -- GO ON. */
5231             /* COMPUTE FIRST DIFFERENCES OF X SEQUENCE AND STORE IN WK(1,.). ALSO, */
5232             /* COMPUTE FIRST DIVIDED DIFFERENCE OF DATA AND STORE IN WK(2,.). */
5233             loop (n=1) %{
5234             $dx() = $x() - $x(n=>n-1);
5235             $dy_dx() = ($f() - $f(n=>n-1)) / $dx();
5236             %}
5237             /* SET TO DEFAULT BOUNDARY CONDITIONS IF N IS TOO SMALL. */
5238             if (ibeg > n) {
5239             ibeg = 0;
5240             }
5241             if (iend > n) {
5242             iend = 0;
5243             }
5244             /* SET UP FOR BOUNDARY CONDITIONS. */
5245             if (ibeg == 1 || ibeg == 2) {
5246             $d(n=>0) = $vc(two=>0);
5247             } else if (ibeg > 2) {
5248             /* PICK UP FIRST IBEG POINTS, IN REVERSE ORDER. */
5249             for (j = 0; j < ibeg; ++j) {
5250             PDL_Indx index = ibeg - j + 1;
5251             /* INDEX RUNS FROM IBEG DOWN TO 1. */
5252             xtemp[j] = $x(n=>index);
5253             if (j < ibeg-1)
5254             stemp[j] = $dy_dx(n=>index);
5255             }
5256             /* -------------------------------- */
5257             $PCHDF(ibeg, xtemp, stemp, $d(n=>0));
5258             /* -------------------------------- */
5259             ibeg = 1;
5260             }
5261             if (iend == 1 || iend == 2) {
5262             $d(n=>n-1) = $vc(two=>1);
5263             } else if (iend > 2) {
5264             /* PICK UP LAST IEND POINTS. */
5265             for (j = 0; j < iend; ++j) {
5266             PDL_Indx index = n - iend + j;
5267             /* INDEX RUNS FROM N+1-IEND UP TO N. */
5268             xtemp[j] = $x(n=>index);
5269             if (j < iend-1)
5270             stemp[j] = $dy_dx(n=>index+1);
5271             }
5272             /* -------------------------------- */
5273             $PCHDF(iend, xtemp, stemp, $d(n=>n-1));
5274             /* -------------------------------- */
5275             iend = 1;
5276             }
5277             /* --------------------( BEGIN CODING FROM CUBSPL )-------------------- */
5278             /* **** A TRIDIAGONAL LINEAR SYSTEM FOR THE UNKNOWN SLOPES S(J) OF */
5279             /* F AT X(J), J=1,...,N, IS GENERATED AND THEN SOLVED BY GAUSS ELIM- */
5280             /* INATION, WITH S(J) ENDING UP IN D(1,J), ALL J. */
5281             /* WK(1,.) AND WK(2,.) ARE USED FOR TEMPORARY STORAGE. */
5282             /* CONSTRUCT FIRST EQUATION FROM FIRST BOUNDARY CONDITION, OF THE FORM */
5283             /* WK(2,1)*S(1) + WK(1,1)*S(2) = D(1,1) */
5284             if (ibeg == 0) {
5285             if (n == 2) {
5286             /* NO CONDITION AT LEFT END AND N = 2. */
5287             $dy_dx(n=>0) = 1.;
5288             $dx(n=>0) = 1.;
5289             $d(n=>0) = 2. * $dy_dx(n=>1);
5290             } else {
5291             /* NOT-A-KNOT CONDITION AT LEFT END AND N .GT. 2. */
5292             $dy_dx(n=>0) = $dx(n=>2);
5293             $dx(n=>0) = $dx(n=>1) + $dx(n=>2);
5294             /* Computing 2nd power */
5295             $d(n=>0) = (($dx(n=>1) + 2. * $dx(n=>0)) * $dy_dx(n=>1) * $dx(n=>2) + $dx(n=>1) *
5296             $dx(n=>1) * $dy_dx(n=>2)) / $dx(n=>0);
5297             }
5298             } else if (ibeg == 1) {
5299             /* SLOPE PRESCRIBED AT LEFT END. */
5300             $dy_dx(n=>0) = 1.;
5301             $dx(n=>0) = 0.;
5302             } else {
5303             /* SECOND DERIVATIVE PRESCRIBED AT LEFT END. */
5304             $dy_dx(n=>0) = 2.;
5305             $dx(n=>0) = 1.;
5306             $d(n=>0) = 3. * $dy_dx(n=>1) - 0.5 * $dx(n=>1) * $d(n=>0);
5307             }
5308             /* IF THERE ARE INTERIOR KNOTS, GENERATE THE CORRESPONDING EQUATIONS AND */
5309             /* CARRY OUT THE FORWARD PASS OF GAUSS ELIMINATION, AFTER WHICH THE J-TH */
5310             /* EQUATION READS WK(2,J)*S(J) + WK(1,J)*S(J+1) = D(1,J). */
5311             if (n > 2) {
5312             loop (n=1:-1) %{
5313             if ($dy_dx(n=>n-1) == 0.) {
5314             dpchsp_singular(1, n-1);
5315             }
5316             $GENERIC() g = -$dx(n=>n+1) / $dy_dx(n=>n-1);
5317             $d() = g * $d(n=>n-1) + 3. * ($dx() * $dy_dx(n=>n+1) +
5318             $dx(n=>n+1) * $dy_dx());
5319             $dy_dx() = g * $dx(n=>n-1) + 2. * ($dx() + $dx(n=>n+1));
5320             %}
5321             }
5322             /* CONSTRUCT LAST EQUATION FROM SECOND BOUNDARY CONDITION, OF THE FORM */
5323             /* (-G*WK(2,N-1))*S(N-1) + WK(2,N)*S(N) = D(1,N) */
5324             /* IF SLOPE IS PRESCRIBED AT RIGHT END, ONE CAN GO DIRECTLY TO BACK- */
5325             /* SUBSTITUTION, SINCE ARRAYS HAPPEN TO BE SET UP JUST RIGHT FOR IT */
5326             /* AT THIS POINT. */
5327             if (iend != 1) {
5328             if (iend == 0 && n == 2 && ibeg == 0) {
5329             /* NOT-A-KNOT AT RIGHT ENDPOINT AND AT LEFT ENDPOINT AND N = 2. */
5330             $d(n=>1) = $dy_dx(n=>1);
5331             } else {
5332             $GENERIC() g;
5333             if (iend == 0) {
5334             if (n == 2 || (n == 3 && ibeg == 0)) {
5335             /* EITHER (N=3 AND NOT-A-KNOT ALSO AT LEFT) OR (N=2 AND *NOT* */
5336             /* NOT-A-KNOT AT LEFT END POINT). */
5337             $d(n=>n-1) = 2. * $dy_dx(n=>n-1);
5338             $dy_dx(n=>n-1) = 1.;
5339             if ($dy_dx(n=>n-2) == 0.) {
5340             dpchsp_singular(2, n-2);
5341             }
5342             g = -1. / $dy_dx(n=>n-2);
5343             } else {
5344             /* NOT-A-KNOT AND N .GE. 3, AND EITHER N.GT.3 OR ALSO NOT-A- */
5345             /* KNOT AT LEFT END POINT. */
5346             g = $dx(n=>n-2) + $dx(n=>n-1);
5347             /* DO NOT NEED TO CHECK FOLLOWING DENOMINATORS (X-DIFFERENCES). */
5348             /* Computing 2nd power */
5349             $GENERIC() dtmp = $dx(n=>n-1);
5350             $d(n=>n-1) = (($dx(n=>n-1) + 2. * g) * $dy_dx(n=>n-1) * $dx(n=>n-2) + dtmp * dtmp * ($f(n=>n-2) - $f(n=>n-3)) / $dx(n=>n-2)) / g;
5351             if ($dy_dx(n=>n-2) == 0.) {
5352             dpchsp_singular(3, n-2);
5353             }
5354             g /= -$dy_dx(n=>n-2);
5355             $dy_dx(n=>n-1) = $dx(n=>n-2);
5356             }
5357             } else {
5358             /* SECOND DERIVATIVE PRESCRIBED AT RIGHT ENDPOINT. */
5359             $d(n=>n-1) = 3. * $dy_dx(n=>n-1) + 0.5 * $dx(n=>n-1) * $d(n=>n-1);
5360             $dy_dx(n=>n-1) = 2.;
5361             if ($dy_dx(n=>n-2) == 0.) {
5362             dpchsp_singular(4, n-2);
5363             }
5364             g = -1. / $dy_dx(n=>n-2);
5365             }
5366             /* COMPLETE FORWARD PASS OF GAUSS ELIMINATION. */
5367             $dy_dx(n=>n-1) = g * $dx(n=>n-2) + $dy_dx(n=>n-1);
5368             if ($dy_dx(n=>n-1) == 0.) {
5369             dpchsp_singular(5, n-1);
5370             }
5371             $d(n=>n-1) = (g * $d(n=>n-2) + $d(n=>n-1)) / $dy_dx(n=>n-1);
5372             }
5373             }
5374             /* CARRY OUT BACK SUBSTITUTION */
5375             loop (n=nm1-1::-1) %{
5376             if ($dy_dx() == 0.) {
5377             dpchsp_singular(6, n);
5378             }
5379             $d() = ($d() - $dx() * $d(n=>n+1)) / $dy_dx();
5380             %}
5381             /* --------------------( END CODING FROM CUBSPL )-------------------- */
5382             #undef dpchsp_singular
5383             EOF
5384             ParamDesc => {
5385             ic => <<'EOF',
5386             The first and second elements determine the boundary
5387             conditions at the start and end of the data respectively.
5388             The allowed values for C are:
5389              
5390             =over
5391              
5392             =item *
5393              
5394             0 to set C so that the third derivative is
5395             continuous at C.
5396              
5397             =item *
5398              
5399             1 if first derivative at C is given in C).
5400              
5401             =item *
5402              
5403             2 if second derivative at C) is given in C.
5404              
5405             =item *
5406              
5407             3 to use the 3-point difference formula for C.
5408             (Reverts to the default b.c. if C 3>.)
5409              
5410             =item *
5411              
5412             4 to use the 4-point difference formula for C.
5413             (Reverts to the default b.c. if C 4>.)
5414              
5415             =back
5416              
5417             The values for C are the same as above, except that
5418             the first-derivative value is stored in C for cases 1 and 2.
5419             The values of C<$vc> need only be set if options 1 or 2 are chosen
5420             for C<$ic>.
5421              
5422             NOTES: For the "natural" boundary condition, use IC(n)=2 and VC(n)=0.
5423             EOF
5424             vc => 'See ic for details',
5425             ierr => <<'EOF',
5426             Error status:
5427              
5428             =over
5429              
5430             =item *
5431              
5432             0 if successful.
5433              
5434             =item *
5435              
5436             -1 if C 2>.
5437              
5438             =item *
5439              
5440             -3 if C<$x> is not strictly increasing.
5441              
5442             =item *
5443              
5444             -4 if C 0> or C 4>.
5445              
5446             =item *
5447              
5448             -5 if C 0> or C 4>.
5449              
5450             =item *
5451              
5452             -6 if both of the above are true.
5453              
5454             =item *
5455              
5456             -7 if C 2*n>.
5457              
5458             NOTE: The above errors are checked in the order listed,
5459             and following arguments have B been validated.
5460             (The D-array has not been changed in any of these cases.)
5461              
5462             =item *
5463              
5464             -8 in case of trouble solving the linear system
5465             for the interior derivative values.
5466             (The D-array may have been changed in this case. Do B use it!)
5467              
5468             =back
5469             EOF
5470             },
5471             Doc => <<'EOF',
5472             =for ref
5473              
5474             Calculate the derivatives of (x,f(x)) using cubic spline interpolation.
5475              
5476             Computes the Hermite representation of the cubic spline interpolant
5477             to the data given in (C<$x,$f>), with the specified boundary conditions.
5478             Control over the boundary conditions is given by the
5479             C<$ic> and C<$vc> ndarrays.
5480             The resulting values - C<$x,$f,$d> - can
5481             be used in all the functions which expect a cubic
5482             Hermite function, including L.
5483              
5484             References: Carl de Boor, A Practical Guide to Splines, Springer-Verlag,
5485             New York, 1978, pp. 53-59.
5486             EOF
5487             );
5488              
5489             pp_def('pchip_chfd',
5490             Pars => 'x(n); f(n); d(n); xe(ne);
5491             [o] fe(ne); [o] de(ne); indx [o] ierr(); int [o] skip()',
5492             RedoDimsCode => <<'EOF',
5493             if ($SIZE(n) < 2) $CROAK("NUMBER OF DATA POINTS LESS THAN TWO");
5494             if ($SIZE(ne) < 1) $CROAK("NUMBER OF EVALUATION POINTS LESS THAN ONE");
5495             EOF
5496             Code => pp_line_numbers(__LINE__, <<'EOF'),
5497             $CHFD(1);
5498             EOF
5499             GenericTypes => $F,
5500             ParamDesc => {
5501             skip => <<'EOF',
5502             Set to 1 to skip checks on the input data.
5503             This will save time in case these checks have already
5504             been performed (say, in L or L).
5505             Will be set to TRUE on normal return.
5506             EOF
5507             xe => <<'EOF',
5508             array of points at which the functions are to
5509             be evaluated. NOTES:
5510              
5511             =over
5512              
5513             =item 1
5514              
5515             The evaluation will be most efficient if the elements
5516             of XE are increasing relative to X;
5517             that is, XE(J) .GE. X(I)
5518             implies XE(K) .GE. X(I), all K.GE.J .
5519              
5520             =item 2
5521              
5522             If any of the XE are outside the interval [X(1),X(N)],
5523             values are extrapolated from the nearest extreme cubic,
5524             and a warning error is returned.
5525              
5526             =back
5527             EOF
5528             fe => <<'EOF',
5529             array of values of the cubic Hermite
5530             function defined by N, X, F, D at the points XE.
5531             EOF
5532             de => <<'EOF',
5533             array of values of the first derivative of the same function at the points XE.
5534             EOF
5535             ierr => <<'EOF',
5536             Error status:
5537              
5538             =over
5539              
5540             =item *
5541              
5542             0 if successful.
5543              
5544             =item *
5545              
5546             E0 if extrapolation was performed at C points
5547             (data still valid).
5548              
5549             =item *
5550              
5551             -1 if C 2>
5552              
5553             =item *
5554              
5555             -3 if C<$x> is not strictly increasing.
5556              
5557             =item *
5558              
5559             -4 if C 1>.
5560              
5561             =item *
5562              
5563             -5 if an error has occurred in a lower-level routine,
5564             which should never happen.
5565              
5566             =back
5567             EOF
5568             },
5569             Doc => <<'EOF',
5570             =for ref
5571              
5572             Evaluate a piecewise cubic Hermite function and its first derivative
5573             at an array of points. May be used by itself for Hermite interpolation,
5574             or as an evaluator for DPCHIM or DPCHIC.
5575              
5576             Given a piecewise cubic Hermite function - such as from
5577             L - evaluate the function (C<$fe>) and
5578             derivative (C<$de>) at a set of points (C<$xe>).
5579             If function values alone are required, use L.
5580             EOF
5581             );
5582              
5583             pp_def('pchip_chfe',
5584             Pars => 'x(n); f(n); d(n); xe(ne);
5585             [o] fe(ne); indx [o] ierr(); int [o] skip()',
5586             RedoDimsCode => <<'EOF',
5587             if ($SIZE(n) < 2) $CROAK("NUMBER OF DATA POINTS LESS THAN TWO");
5588             if ($SIZE(ne) < 1) $CROAK("NUMBER OF EVALUATION POINTS LESS THAN ONE");
5589             EOF
5590             Code => pp_line_numbers(__LINE__, <<'EOF'),
5591             $CHFD(0);
5592             EOF
5593             GenericTypes => $F,
5594             ParamDesc => {
5595             x => <<'EOF',
5596             array of independent variable values. The
5597             elements of X must be strictly increasing:
5598              
5599             X(I-1) .LT. X(I), I = 2(1)N.
5600              
5601             (Error return if not.)
5602             EOF
5603             f => <<'EOF',
5604             array of function values. F(I) is the value corresponding to X(I).
5605             EOF
5606             d => <<'EOF',
5607             array of derivative values. D(I) is the value corresponding to X(I).
5608             EOF
5609             skip => <<'EOF',
5610             Set to 1 to skip checks on the input data.
5611             This will save time in case these checks have already
5612             been performed (say, in L or L).
5613             Will be set to TRUE on normal return.
5614             EOF
5615             xe => <<'EOF',
5616             array of points at which the function is to be evaluated. NOTES:
5617              
5618             =over
5619              
5620             =item 1
5621              
5622             The evaluation will be most efficient if the elements
5623             of XE are increasing relative to X;
5624             that is, XE(J) .GE. X(I)
5625             implies XE(K) .GE. X(I), all K.GE.J .
5626              
5627             =item 2
5628              
5629             If any of the XE are outside the interval [X(1),X(N)],
5630             values are extrapolated from the nearest extreme cubic,
5631             and a warning error is returned.
5632              
5633             =back
5634             EOF
5635             fe => <<'EOF',
5636             array of values of the cubic Hermite
5637             function defined by N, X, F, D at the points XE.
5638             EOF
5639             ierr => <<'EOF',
5640             Error status returned by C<$>:
5641              
5642             =over
5643              
5644             =item *
5645              
5646             0 if successful.
5647              
5648             =item *
5649              
5650             E0 if extrapolation was performed at C points
5651             (data still valid).
5652              
5653             =item *
5654              
5655             -1 if C 2>
5656              
5657             =item *
5658              
5659             -3 if C<$x> is not strictly increasing.
5660              
5661             =item *
5662              
5663             -4 if C 1>.
5664              
5665             =back
5666              
5667             (The FE-array has not been changed in any of these cases.)
5668             NOTE: The above errors are checked in the order listed,
5669             and following arguments have B been validated.
5670             EOF
5671             },
5672             Doc => <<'EOF',
5673             =for ref
5674              
5675             Evaluate a piecewise cubic Hermite function at an array of points.
5676             May be used by itself for Hermite interpolation, or as an evaluator
5677             for L or L.
5678              
5679             Given a piecewise cubic Hermite function - such as from
5680             L - evaluate the function (C<$fe>) at
5681             a set of points (C<$xe>).
5682             If derivative values are also required, use L.
5683             EOF
5684             );
5685              
5686             pp_def('pchip_chia',
5687             Pars => 'x(n); f(n); d(n); la(); lb();
5688             [o]ans(); indx [o]ierr(); int [o]skip()',
5689             GenericTypes => $F,
5690             RedoDimsCode => <<'EOF',
5691             if ($SIZE(n) < 2) $CROAK("NUMBER OF DATA POINTS LESS THAN TWO");
5692             EOF
5693             Code => pp_line_numbers(__LINE__, <<'EOF'),
5694             PDL_Indx ia, ib, il;
5695             $GENERIC() a = $la(), b = $lb(), xa, xb;
5696             PDL_Indx ir, n = $SIZE(n);
5697             $GENERIC() value = 0.;
5698             if (!$skip()) {
5699             loop (n=1) %{
5700             if ($x() > $x(n=>n-1)) continue;
5701             $ierr() = -1;
5702             $CROAK("X-ARRAY NOT STRICTLY INCREASING");
5703             %}
5704             }
5705             /* FUNCTION DEFINITION IS OK, GO ON. */
5706             $skip() = 1;
5707             $ierr() = 0;
5708             if (a < $x(n=>0) || a > $x(n=>n-1)) {
5709             ++($ierr());
5710             }
5711             if (b < $x(n=>0) || b > $x(n=>n-1)) {
5712             $ierr() += 2;
5713             }
5714             /* COMPUTE INTEGRAL VALUE. */
5715             if (a != b) {
5716             xa = PDLMIN(a,b);
5717             xb = PDLMAX(a,b);
5718             if (xb <= $x(n=>1)) {
5719             /* INTERVAL IS TO LEFT OF X(2), SO USE FIRST CUBIC. */
5720             /* --------------------------------------- */
5721             $CHFIE($x(n=>0), $x(n=>1), $f(n=>0), $f(n=>1),
5722             $d(n=>0), $d(n=>1), a, b, value);
5723             /* --------------------------------------- */
5724             } else if (xa >= $x(n=>n-2)) {
5725             /* INTERVAL IS TO RIGHT OF X(N-1), SO USE LAST CUBIC. */
5726             /* ------------------------------------------ */
5727             $CHFIE($x(n=>n-2), $x(n=>n-1), $f(n=>n-2), $f(n=>n-1), $d(n=>n-2), $d(n=>n-1), a, b, value);
5728             /* ------------------------------------------ */
5729             } else {
5730             /* 'NORMAL' CASE -- XA.LT.XB, XA.LT.X(N-1), XB.GT.X(2). */
5731             /* ......LOCATE IA AND IB SUCH THAT */
5732             /* X(IA-1).LT.XA.LE.X(IA).LE.X(IB).LE.XB.LE.X(IB+1) */
5733             ia = 0;
5734             loop (n=:-1) %{
5735             if (xa > $x())
5736             ia = n + 1;
5737             %}
5738             /* IA = 1 IMPLIES XA.LT.X(1) . OTHERWISE, */
5739             /* IA IS LARGEST INDEX SUCH THAT X(IA-1).LT.XA,. */
5740             ib = n - 1;
5741             loop (n=:ia:-1) %{
5742             if (xb < $x())
5743             ib = n - 1;
5744             %}
5745             /* IB = N IMPLIES XB.GT.X(N) . OTHERWISE, */
5746             /* IB IS SMALLEST INDEX SUCH THAT XB.LT.X(IB+1) . */
5747             /* ......COMPUTE THE INTEGRAL. */
5748             if (ib <= ia) {
5749             /* THIS MEANS IB = IA-1 AND */
5750             /* (A,B) IS A SUBSET OF (X(IB),X(IA)). */
5751             /* ------------------------------------------- */
5752             $CHFIE($x(n=>ib), $x(n=>ia), $f(n=>ib),
5753             $f(n=>ia), $d(n=>ib), $d(n=>ia), a, b, value);
5754             /* ------------------------------------------- */
5755             } else {
5756             /* FIRST COMPUTE INTEGRAL OVER (X(IA),X(IB)). */
5757             /* (Case (IB .EQ. IA) is taken care of by initialization */
5758             /* of VALUE to ZERO.) */
5759             if (ib > ia-1) {
5760             /* --------------------------------------------- */
5761             $PCHID(ia, ib, value);
5762             /* --------------------------------------------- */
5763             }
5764             /* THEN ADD ON INTEGRAL OVER (XA,X(IA)). */
5765             if (xa < $x(n=>ia)) {
5766             /* Computing MAX */
5767             il = PDLMAX(0,ia - 1);
5768             ir = il + 1;
5769             /* ------------------------------------- */
5770             $GENERIC() chfie_ans = 0;
5771             $CHFIE($x(n=>il), $x(n=>ir), $f(n=>il), $f(n=>ir), $d(n=>il), $d(n=>ir), xa, $x(n=>ia), chfie_ans);
5772             value += chfie_ans;
5773             /* ------------------------------------- */
5774             }
5775             /* THEN ADD ON INTEGRAL OVER (X(IB),XB). */
5776             if (xb > $x(n=>ib)) {
5777             /* Computing MIN */
5778             ir = PDLMIN(ib + 1,n-1);
5779             il = ir - 1;
5780             /* ------------------------------------- */
5781             $GENERIC() chfie_ans = 0;
5782             $CHFIE($x(n=>il), $x(n=>ir), $f(n=>il), $f(n=>ir), $d(n=>il), $d(n=>ir), $x(n=>ib), xb, chfie_ans);
5783             value += chfie_ans;
5784             /* ------------------------------------- */
5785             }
5786             /* FINALLY, ADJUST SIGN IF NECESSARY. */
5787             if (a > b) {
5788             value = -value;
5789             }
5790             }
5791             }
5792             }
5793             $ans() = value;
5794             EOF
5795             ParamDesc => {
5796             x => <<'EOF',
5797             array of independent variable values. The elements
5798             of X must be strictly increasing (error return if not):
5799              
5800             X(I-1) .LT. X(I), I = 2(1)N.
5801             EOF
5802             f => <<'EOF',
5803             array of function values. F(I) is the value corresponding to X(I).
5804             EOF
5805             d => <<'EOF',
5806             should contain the derivative values, computed by L.
5807             See L if the integration limits are data points.
5808             EOF
5809             skip => <<'EOF',
5810             Set to 1 to skip checks on the input data.
5811             This will save time in case these checks have already
5812             been performed (say, in L or L).
5813             Will be set to TRUE on return with IERR E= 0.
5814             EOF
5815             la => <<'EOF',
5816             The values of C<$la> and C<$lb> do not have
5817             to lie within C<$x>, although the resulting integral
5818             value will be highly suspect if they are not.
5819             EOF
5820             lb => <<'EOF',
5821             See la
5822             EOF
5823             ierr => <<'EOF',
5824             Error status:
5825              
5826             =over
5827              
5828             =item *
5829              
5830             0 if successful.
5831              
5832             =item *
5833              
5834             1 if C<$la> lies outside C<$x>.
5835              
5836             =item *
5837              
5838             2 if C<$lb> lies outside C<$x>.
5839              
5840             =item *
5841              
5842             3 if both 1 and 2 are true. (Note that this means that either [A,B]
5843             contains data interval or the intervals do not intersect at all.)
5844              
5845             =item *
5846              
5847             -1 if C 2>
5848              
5849             =item *
5850              
5851             -3 if C<$x> is not strictly increasing.
5852              
5853             =item *
5854              
5855             -4 if an error has occurred in a lower-level routine,
5856             which should never happen.
5857              
5858             =back
5859             EOF
5860             },
5861             Doc => <<'EOF',
5862             =for ref
5863              
5864             Integrate (x,f(x)) over arbitrary limits.
5865              
5866             Evaluate the definite integral of a piecewise
5867             cubic Hermite function over an arbitrary interval, given by C<[$la,$lb]>.
5868             EOF
5869             );
5870              
5871             pp_def('pchip_chid',
5872             Pars => 'x(n); f(n); d(n);
5873             indx ia(); indx ib();
5874             [o]ans(); indx [o]ierr(); int [o]skip()',
5875             RedoDimsCode => <<'EOF',
5876             if ($SIZE(n) < 2) $CROAK("NUMBER OF DATA POINTS LESS THAN TWO");
5877             EOF
5878             Code => <<'EOF',
5879             if (!$skip()) {
5880             loop (n=1) %{
5881             if ($x() > $x(n=>n-1)) continue;
5882             $ierr() = -1;
5883             $CROAK("X-ARRAY NOT STRICTLY INCREASING");
5884             %}
5885             }
5886             $PCHID($ia(), $ib(), $ans());
5887             EOF
5888             GenericTypes => $F,
5889             ParamDesc => {
5890             x => <<'EOF',
5891             array of independent variable values. The
5892             elements of X must be strictly increasing:
5893              
5894             X(I-1) .LT. X(I), I = 2(1)N.
5895              
5896             (Error return if not.)
5897              
5898             It is a fatal error to pass in data with C E 2.
5899             EOF
5900             ia => <<'EOF',
5901             IA,IB -- (input) indices in X-array for the limits of integration.
5902             both must be in the range [0,N-1] (this is different from the Fortran
5903             version) - error return if not. No restrictions on their relative
5904             values.
5905             EOF
5906             ib => 'See ia for details',
5907             f => 'array of function values. F(I) is the value corresponding to X(I).',
5908             skip => <<'EOF',
5909             Set to 1 to skip checks on the input data.
5910             This will save time in case these checks have already
5911             been performed (say, in L or L).
5912             Will be set to TRUE on return with IERR of 0 or -4.
5913             EOF
5914             d => <<'EOF',
5915             should contain the derivative values, computed by L.
5916             EOF
5917             ierr => <<'EOF',
5918             Error status - this will be set, but an exception
5919             will also be thrown:
5920              
5921             =over
5922              
5923             =item *
5924              
5925             0 if successful.
5926              
5927             =item *
5928              
5929             -3 if C<$x> is not strictly increasing.
5930              
5931             =item *
5932              
5933             -4 if C<$ia> or C<$ib> is out of range.
5934              
5935             =back
5936              
5937             (VALUE will be zero in any of these cases.)
5938             NOTE: The above errors are checked in the order listed, and following
5939             arguments have B been validated.
5940             EOF
5941             },
5942             Doc => <<'EOF',
5943             =for ref
5944              
5945             Evaluate the definite integral of a piecewise cubic Hermite function
5946             over an interval whose endpoints are data points.
5947              
5948             Evaluate the definite integral of a a piecewise cubic Hermite
5949             function between C and C.
5950              
5951             See L for integration between arbitrary
5952             limits.
5953             EOF
5954             );
5955              
5956             pp_def('pchip_chbs',
5957             Pars => 'x(n); f(n); d(n); sbyte knotyp();
5958             [o]t(nknots=CALC(2*$SIZE(n)+4));
5959             [o]bcoef(ndim=CALC(2*$SIZE(n))); indx [o]ierr()',
5960             GenericTypes => $F,
5961             Code => pp_line_numbers(__LINE__, <<'EOF'),
5962             /* Local variables */
5963             PDL_Indx n = $SIZE(n), ndim = $SIZE(ndim);
5964             $ierr() = 0;
5965             /* Check argument validity. Set up knot sequence if OK. */
5966             if ($knotyp() > 2) {
5967             $ierr() = -1;
5968             $CROAK("KNOTYP GREATER THAN 2");
5969             }
5970             if ($knotyp() >= 0) {
5971             /* Set up knot sequence. */
5972             do { /* inline dpchkt */
5973             /* Set interior knots. */
5974             PDL_Indx j = 0;
5975             loop (n) %{
5976             j += 2;
5977             $t(nknots=>j+1) = $t(nknots=>j) = $x();
5978             %}
5979             /* Assertion: At this point T(3),...,T(NDIM+2) have been set and */
5980             /* J=NDIM+1. */
5981             /* Set end knots according to KNOTYP. */
5982             $GENERIC() hbeg = $x(n=>1) - $x(n=>0);
5983             $GENERIC() hend = $x(n=>n-1) - $x(n=>n-2);
5984             if ($knotyp() == 1) {
5985             /* Extrapolate. */
5986             $t(nknots=>1) = $x(n=>0) - hbeg;
5987             $t(nknots=>ndim+2) = $x(n=>n-1) + hend;
5988             } else if ($knotyp() == 2) {
5989             /* Periodic. */
5990             $t(nknots=>1) = $x(n=>0) - hend;
5991             $t(nknots=>ndim+2) = $x(n=>n-1) + hbeg;
5992             } else {
5993             /* Quadruple end knots. */
5994             $t(nknots=>1) = $x(n=>0);
5995             $t(nknots=>ndim+2) = $x(n=>n-1);
5996             }
5997             $t(nknots=>0) = $t(nknots=>1);
5998             $t(nknots=>ndim+3) = $t(nknots=>ndim+2);
5999             } while (0); /* end inline dpchkt */
6000             }
6001             /* Compute B-spline coefficients. */
6002             $GENERIC() hnew = $t(nknots=>2) - $t(nknots=>0);
6003             loop (n) %{
6004             $GENERIC() hold = hnew;
6005             /* The following requires mixed mode arithmetic. */
6006             $GENERIC() dov3 = $d() / 3;
6007             $bcoef(ndim=>2*n) = $f() - hold * dov3;
6008             /* The following assumes T(2*K+1) = X(K). */
6009             hnew = $t(nknots=>2*n+4) - $t(nknots=>2*n+2);
6010             $bcoef(ndim=>2*n+1) = $f() + hnew * dov3;
6011             %}
6012             EOF
6013             ParamDesc => {
6014             f => <<'EOF',
6015             the array of dependent variable values.
6016             C is the value corresponding to C.
6017             EOF
6018             d => <<'EOF',
6019             the array of derivative values at the data points.
6020             C is the value corresponding to C.
6021             EOF
6022             knotyp => <<'EOF',
6023             flag which controls the knot sequence.
6024             The knot sequence C is normally computed from C<$x>
6025             by putting a double knot at each C and setting the end knot pairs
6026             according to the value of C (where C):
6027              
6028             =over
6029              
6030             =item *
6031              
6032             0 - Quadruple knots at the first and last points.
6033              
6034             =item *
6035              
6036             1 - Replicate lengths of extreme subintervals:
6037             C and
6038             C
6039              
6040             =item *
6041              
6042             2 - Periodic placement of boundary knots:
6043             C and
6044             C
6045              
6046             =item *
6047              
6048             E0 - Assume the C and C were set in a previous call.
6049             This option is provided for improved efficiency when used
6050             in a parametric setting.
6051              
6052             =back
6053             EOF
6054             t => <<'EOF',
6055             the array of C<2*n+4> knots for the B-representation
6056             and may be changed by the routine.
6057             If C= 0>, C will be changed so that the
6058             interior double knots are equal to the x-values and the
6059             boundary knots set as indicated above,
6060             otherwise it is assumed that C was set by a
6061             previous call (no check is made to verify that the data
6062             forms a legitimate knot sequence).
6063             EOF
6064             bcoef => 'the array of 2*N B-spline coefficients.',
6065             ierr => <<'EOF',
6066             Error status:
6067              
6068             =over
6069              
6070             =item *
6071              
6072             0 if successful.
6073              
6074             =item *
6075              
6076             -4 if C 2>. (recoverable)
6077              
6078             =item *
6079              
6080             -5 if C 0> and C. (recoverable)
6081              
6082             =back
6083             EOF
6084             },
6085             Doc => <<'EOF',
6086             =for ref
6087              
6088             Piecewise Cubic Hermite function to B-Spline converter.
6089              
6090             Computes the B-spline representation of the PCH function
6091             determined by N,X,F,D. The output is the B-representation for the
6092             function: NKNOTS, T, BCOEF, NDIM, KORD.
6093              
6094             L, L, or L can be used to
6095             determine an interpolating PCH function from a set of data. The
6096             B-spline routine L can be used to evaluate the
6097             resulting B-spline representation of the data
6098             (i.e. C, C, C, C, and
6099             C).
6100              
6101             Caution: Since it is assumed that the input PCH function has been
6102             computed by one of the other routines in the package PCHIP,
6103             input arguments N, X are B checked for validity.
6104              
6105             Restrictions/assumptions:
6106              
6107             =over
6108              
6109             =item C<1>
6110              
6111             N.GE.2 . (not checked)
6112              
6113             =item C<2>
6114              
6115             X(i).LT.X(i+1), i=1,...,N . (not checked)
6116              
6117             =item C<4>
6118              
6119             KNOTYP.LE.2 . (error return if not)
6120              
6121             =item C<6>
6122              
6123             T(2*k+1) = T(2*k) = X(k), k=1,...,N . (not checked)
6124              
6125             * Indicates this applies only if KNOTYP.LT.0 .
6126              
6127             =back
6128              
6129             References: F. N. Fritsch, "Representations for parametric cubic
6130             splines," Computer Aided Geometric Design 6 (1989), pp.79-82.
6131             EOF
6132             );
6133              
6134             pp_def('pchip_bvalu',
6135             Pars => 't(nplusk); a(n); indx ideriv(); x();
6136             [o]ans(); indx [o] inbv();
6137             [t] work(k3=CALC(3*($SIZE(nplusk)-$SIZE(n))));',
6138             GenericTypes => $F,
6139             RedoDimsCode => <<'EOF',
6140             PDL_Indx k = $SIZE(nplusk) - $SIZE(n);
6141             if (k < 1) $CROAK("K DOES NOT SATISFY K.GE.1");
6142             if ($SIZE(n) < k) $CROAK("N DOES NOT SATISFY N.GE.K");
6143             EOF
6144             Code => pp_line_numbers(__LINE__, <<'EOF'),
6145 88         70903 PDL_Indx k = $SIZE(nplusk) - $SIZE(n);
6146 88 100       133 if ($ideriv() < 0 || $ideriv() >= k)
    100          
    100          
    50          
    0          
    0          
6147 70         2871 $CROAK("IDERIV DOES NOT SATISFY 0.LE.IDERIV.LT.K");
6148             PDL_Indx i;
6149             int mflag;
6150             /* *** FIND *I* IN (K,N) SUCH THAT T(I) .LE. X .LT. T(I+1) */
6151             /* (OR, .LE. T(I+1) IF T(I) .LT. T(I+1) = T(N+1)). */
6152             do { /* inlined dintrv */
6153 88         330 PDL_Indx ihi = $inbv() + 1, lxt = $SIZE(n);
6154 88 0       138 if (ihi >= lxt) {
    100          
    100          
6155 70 50       4271 if ($x() >= $t(nplusk=>lxt)) {
    50          
    100          
6156 70         50710 mflag = 1; i = lxt; break;
6157             }
6158 70 50       233 if (lxt <= 0) {
    100          
    50          
6159 70         2662 mflag = -1; i = 0; break;
6160             }
6161 70         2648 ihi = $inbv() = lxt;
6162             }
6163 88         133 char skipflag = 0;
6164 88 50       76800 if ($x() < $t(nplusk=>ihi)) {
    50          
    100          
6165 70         675 PDL_Indx inbv = $inbv();
6166 70 50       134 if ($x() >= $t(nplusk=>inbv)) {
    50          
    50          
6167 70         534 mflag = 0; i = $inbv(); break;
6168             }
6169             /* *** NOW X .LT. XT(IHI) . FIND LOWER BOUND */
6170 70         47286 PDL_Indx istep = 1;
6171 70         1380 while (1) {
6172 70         1201 ihi = $inbv();
6173 138         3819 $inbv() = ihi - istep;
6174 138 100       466 if ($inbv() <= 0) {
    50          
    50          
6175 138         673 break;
6176             }
6177 138         574 PDL_Indx inbv = $inbv();
6178 29 50       104 if ($x() >= $t(nplusk=>inbv)) {
    50          
    0          
6179 138         459 skipflag = 1;
6180 2         9 break;
6181             }
6182 138         1126 istep <<= 1;
6183             }
6184 135 50       577 if (!skipflag) {
    50          
    50          
6185 133         4601 $inbv() = 0;
6186 133 100       1044 if ($x() < $t(nplusk=>0)) {
    50          
    0          
6187 0         0 mflag = -1; i = 0; break;
6188             }
6189             }
6190 0         0 skipflag = 1;
6191             /* *** NOW X .GE. XT(ILO) . FIND UPPER BOUND */
6192             }
6193 18 50       0 if (!skipflag) {
    100          
    50          
6194 18         0 PDL_Indx istep = 1;
6195             while (1) {
6196 68         0 $inbv() = ihi;
6197 68         0 ihi = $inbv() + istep;
6198 119 50       150 if (ihi >= lxt) break;
    100          
    100          
6199 115 50       252 if ($x() < $t(nplusk=>ihi)) {
    100          
    100          
6200 60         136 skipflag = 1;
6201 48         92 break;
6202             }
6203 84         128 istep <<= 1;
6204             }
6205 52 100       254 if (!skipflag) {
    100          
    50          
6206 38 50       506 if ($x() >= $t(nplusk=>lxt)) {
    50          
    0          
6207 34         613 mflag = 1; i = lxt; break;
6208             }
6209 6         12 ihi = lxt;
6210             }
6211             }
6212             /* *** NOW XT(ILO) .LE. X .LT. XT(IHI) . NARROW THE INTERVAL */
6213 44         13 while (1) {
6214 62         7 PDL_Indx middle = ($inbv() + ihi) / 2;
6215 62 50       11 if (middle == $inbv()) {
    100          
    100          
6216 20         11 mflag = 0; i = $inbv(); break;
6217             }
6218             /* NOTE. IT IS ASSUMED THAT MIDDLE = ILO IN CASE IHI = ILO+1 */
6219 44 50       13 if ($x() < $t(nplusk=>middle))
    100          
    100          
6220 14         24 ihi = middle;
6221             else
6222 32         10 $inbv() = middle;
6223             }
6224             } while (0); /* end dintrv inlined */
6225 20 50       29 if ($x() < $t(nplusk=>k-1)) {
    100          
    100          
6226 2         37 $CROAK("X IS N0T GREATER THAN OR EQUAL TO T(K)");
6227             }
6228 20 50       46 if (mflag != 0) {
    100          
    100          
6229 0 100       0 if ($x() > $t(nplusk=>i)) {
    50          
    50          
6230 2         4 $CROAK("X IS NOT LESS THAN OR EQUAL TO T(N+1)");
6231             }
6232             while (1) {
6233 2 100       12 if (i == k-1) {
    100          
    50          
6234 2         27 $CROAK("A LEFT LIMITING VALUE CANNOT BE OBTAINED AT T(K)");
6235             }
6236 0         0 --i;
6237 2 50       42 if ($x() != $t(nplusk=>i)) {
    50          
    50          
6238 19         53 break;
6239             }
6240             }
6241             /* *** DIFFERENCE THE COEFFICIENTS *IDERIV* TIMES */
6242             /* WORK(I) = AJ(I), WORK(K+I) = DP(I), WORK(K+K+I) = DM(I), I=1.K */
6243             }
6244 37         244 PDL_Indx imk = i+1 - k, j;
6245 109 50       87 loop (k3=:k) %{
    100          
    100          
6246 90         224 $work() = $a(n=>imk+k3);
6247             %}
6248 36 50       17466 if ($ideriv() != 0) {
    100          
    50          
6249 18 100       237 for (j = 0; j < $ideriv(); ++j) {
    50          
    100          
6250 18         218 PDL_Indx kmj = k - j - 1;
6251 18         933 $GENERIC() fkmj = kmj;
6252 18 100       193 loop (k3=0:kmj) %{
    100          
    50          
6253 18         77878 PDL_Indx ihi = i+1 + k3;
6254 18         121 $work() = ($work(k3=>k3+1) - $work()) / ($t(nplusk=>ihi) - $t(nplusk=>ihi-kmj)) * fkmj;
6255             %}
6256             }
6257             /* *** COMPUTE VALUE AT *X* IN (T(I),T(I+1)) OF IDERIV-TH DERIVATIVE, */
6258             /* GIVEN ITS RELEVANT B-SPLINE COEFF. IN AJ(1),...,AJ(K-IDERIV). */
6259             }
6260 36         74 PDL_Indx km1 = k - 1;
6261 36 100       91 if ($ideriv() != km1) {
    100          
    50          
6262 18         0 PDL_Indx j, j1 = k, kpk = k + k, j2 = kpk, kmider = k - $ideriv();
6263 90 100       0 for (j = 0; j < kmider; ++j) {
    100          
    100          
6264 90         492 PDL_Indx ipj = i + j + 1;
6265 90         968 $work(k3=>j1) = $t(nplusk=>ipj) - $x();
6266 90         237 $work(k3=>j2) = $x() - $t(nplusk=>i-j);
6267 74         9 ++j1;
6268 74         5 ++j2;
6269             }
6270 74 100       52 for (j = $ideriv(); j < km1; ++j) {
    100          
    50          
6271 54         0 PDL_Indx kmj = k - j - 1, ilo = kpk + kmj - 1;
6272 162 50       0 loop (k3=0:kmj) %{
    100          
    50          
6273 108         0 $work() = ($work(k3=>k3+1) * $work(k3=>ilo) + $work() *
6274 110         16 $work(k3=>k+k3)) / ($work(k3=>ilo) + $work(k3=>k+k3));
6275 110         332 --ilo;
6276             %}
6277             }
6278             }
6279 20         60 $ans() = $work(k3=>0);
6280             EOF
6281             ParamDesc => {
6282             t => <<'EOF',
6283             knot vector of length N+K
6284             EOF
6285             a => <<'EOF',
6286             B-spline coefficient vector of length N,
6287             the number of B-spline coefficients; N = sum of knot multiplicities-K
6288             EOF
6289             ideriv => <<'EOF',
6290             order of the derivative, 0 .LE. IDERIV .LE. K-1
6291              
6292             IDERIV=0 returns the B-spline value
6293             EOF
6294             x => <<'EOF',
6295             T(K) .LE. X .LE. T(N+1)
6296             EOF
6297             inbv => <<'EOF',
6298             contains information for efficient processing after the initial
6299             call and INBV must not
6300             be changed by the user. Distinct splines require distinct INBV parameters.
6301             EOF
6302             ans => <<'EOF',
6303             value of the IDERIV-th derivative at X
6304             EOF
6305             },
6306             Doc => <<'EOF',
6307             =for ref
6308              
6309             Evaluate the B-representation of a B-spline at X for the
6310             function value or any of its derivatives.
6311              
6312             Evaluates the B-representation C<(T,A,N,K)> of a B-spline
6313             at C for the function value on C or any of its
6314             derivatives on C. Right limiting values
6315             (right derivatives) are returned except at the right end
6316             point C where left limiting values are computed. The
6317             spline is defined on C. BVALU returns
6318             a fatal error message when C is outside of this interval.
6319              
6320             To compute left derivatives or left limiting values at a
6321             knot C, replace C by C and set C, C.
6322              
6323             References: Carl de Boor, Package for calculating with B-splines,
6324             SIAM Journal on Numerical Analysis 14, 3 (June 1977), pp. 441-472.
6325             EOF
6326             );
6327              
6328             pp_addpm({At=>'Bot'},<<'EOD');
6329              
6330             =head1 AUTHOR
6331              
6332             Copyright (C) Tuomas J. Lukka 1997 (lukka@husc.harvard.edu). Contributions
6333             by Christian Soeller (c.soeller@auckland.ac.nz), Karl Glazebrook
6334             (kgb@aaoepp.aao.gov.au), Craig DeForest (deforest@boulder.swri.edu)
6335             and Jarle Brinchmann (jarle@astro.up.pt)
6336             All rights reserved. There is no warranty. You are allowed
6337             to redistribute this software / documentation under certain
6338             conditions. For details, see the file COPYING in the PDL
6339             distribution. If this file is separated from the PDL distribution,
6340             the copyright notice should be included in the file.
6341              
6342             Updated for CPAN viewing compatibility by David Mertens.
6343              
6344             =cut
6345              
6346             EOD
6347              
6348             pp_done();