File Coverage

lib/PDL/Slices.pd
Criterion Covered Total %
statement 30 30 100.0
branch 23 24 95.8
condition n/a
subroutine n/a
pod n/a
total 53 54 98.1


line stmt bran cond sub pod time code
1             use PDL::Types qw(ppdefs_all);
2             use strict;
3             use warnings;
4              
5             pp_addpm({At => 'Top'},<<'EOD');
6              
7             =head1 NAME
8              
9             PDL::Slices -- Indexing, slicing, and dicing
10              
11             =head1 SYNOPSIS
12              
13             use PDL;
14             $x = ones(3,3);
15             $y = $x->slice('-1:0,(1)');
16             $c = $x->dummy(2);
17              
18             =head1 DESCRIPTION
19              
20             This package provides many of the powerful PerlDL core index
21             manipulation routines. These routines mostly allow two-way data flow,
22             so you can modify your data in the most convenient representation.
23             For example, you can make a 1000x1000 unit matrix with
24              
25             $x = zeroes(1000,1000);
26             $x->diagonal(0,1) ++;
27              
28             which is quite efficient. See L and L for
29             more examples. As of 2.090, backward dataflow will be turned off if any
30             input has inward-only dataflow, to avoid creating "loops". See
31             L for more.
32              
33             Slicing is so central to the PDL language that a special compile-time
34             syntax has been introduced to handle it compactly; see L
35             for details.
36              
37             PDL indexing and slicing functions usually include two-way data flow,
38             so that you can separate the actions of reshaping your data structures
39             and modifying the data themselves. Two special methods, L and
40             L, help you control the data flow connection between related
41             variables.
42              
43             $y = $x->slice("1:3"); # Slice maintains a link between $x and $y.
44             $y += 5; # $x is changed!
45              
46             If you want to force a physical copy and no data flow, you can copy or
47             sever the slice expression:
48              
49             $y = $x->slice("1:3")->copy;
50             $y += 5; # $x is not changed.
51              
52             $y = $x->slice("1:3")->sever;
53             $y += 5; # $x is not changed.
54              
55             The difference between C and C is that sever acts on (and
56             returns) its argument, while copy produces a disconnected copy. If you
57             say
58              
59             $y = $x->slice("1:3");
60             $c = $y->sever;
61              
62             then the variables C<$y> and C<$c> point to the same object but with
63             C<-Ecopy> they would not.
64              
65             =cut
66              
67             use strict;
68             use warnings;
69             use PDL::Core ':Internal';
70             use Scalar::Util 'blessed';
71              
72             EOD
73              
74             # $::PP_VERBOSE=1;
75              
76             my $doc = <<'EOD';
77             =for ref
78              
79             C, C, and C provide rudimentary index indirection.
80              
81             =for example
82              
83             $c = index($source,$ind);
84             $c = index1d($source,$ind);
85             $c = index2d($source2,$ind1,$ind2);
86              
87             use the C<$ind> variables as indices to look up values in C<$source>.
88             The three routines broadcast slightly differently.
89              
90             =over 3
91              
92             =item *
93              
94             C uses direct broadcasting for 1-D indexing across the 0 dim
95             of C<$source>. It can broadcast over source broadcast dims or index broadcast
96             dims, but not (easily) both: If C<$source> has more than 1
97             dimension and C<$ind> has more than 0 dimensions, they must agree in
98             a broadcasting sense.
99              
100             =item *
101              
102             C uses a single active dim in C<$ind> to produce a list of
103             indexed values in the 0 dim of the output - it is useful for
104             collapsing C<$source> by indexing with a single row of values along
105             C<$source>'s 0 dimension. The output has the same number of dims as
106             C<$source>. The 0 dim of the output has size 1 if C<$ind> is a
107             scalar, and the same size as the 0 dim of C<$ind> if it is not. If
108             C<$ind> and C<$source> both have more than 1 dim, then all dims higher
109             than 0 must agree in a broadcasting sense.
110              
111             =item *
112              
113             C works like C but uses separate ndarrays for X and Y
114             coordinates. For more general N-dimensional indexing, see the
115             L syntax or L (in particular C,
116             C, and C).
117              
118             =back
119              
120             These functions are two-way, i.e. after
121              
122             $c = $x->index(pdl[0,5,8]);
123             $c .= pdl [0,2,4];
124              
125             the changes in C<$c> will flow back to C<$x>.
126              
127             C provids simple broadcasting: multiple-dimensioned arrays are treated
128             as collections of 1-D arrays, so that
129              
130             $x = xvals(10,10)+10*yvals(10,10);
131             $y = $x->index(3);
132             $c = $x->index(9-xvals(10));
133              
134             puts a single column from C<$x> into C<$y>, and puts a single element
135             from each column of C<$x> into C<$c>. If you want to extract multiple
136             columns from an array in one operation, see L or
137             L.
138              
139             =cut
140              
141             EOD
142              
143             my $index_init =
144             'register PDL_Indx this_ind = $ind();
145             if( PDL_IF_BAD($ISBADVAR(this_ind,ind) ||,) this_ind<0 || this_ind>=$SIZE(n) ) {
146             $CROAK("invalid index %"IND_FLAG" (valid range 0..%"IND_FLAG")",
147             this_ind,$SIZE(n)-1);
148             }';
149             pp_def('index',
150             GenericTypes => [ppdefs_all],
151             HandleBad => 1,
152             DefaultFlow => 1,
153             TwoWay => 1,
154             Lvalue => 1,
155             Pars => 'a(n); indx ind(); [oca] c();',
156             Code =>
157             $index_init . ' $c() = $a(n => this_ind);',
158             BackCode =>
159             $index_init . ' $a(n => this_ind) = $c();',
160             Doc => $doc,
161             BadDoc =>
162             'index barfs if any of the index values are bad.',
163             );
164              
165             pp_def('index1d',
166             GenericTypes => [ppdefs_all],
167             HandleBad => 1,
168             DefaultFlow => 1,
169             TwoWay => 1,
170             Lvalue => 1,
171             Pars => 'a(n); indx ind(m); [oca] c(m);',
172             Code => pp_line_numbers(__LINE__, <<'EOF'),
173             loop(m) %{
174             PDL_Indx this_ind = $ind();
175             PDL_IF_BAD(if( $ISBADVAR(this_ind, ind) ) {
176             $SETBAD(c(m=>m));
177             } else,) {
178             if( this_ind<0 || this_ind >= $SIZE(n) ) {
179             $CROAK("invalid index %"IND_FLAG" at pos %"IND_FLAG" (valid range 0..%"IND_FLAG")",
180             this_ind, m, $SIZE(n)-1);
181             }
182             $c() = $a(n=>this_ind);
183             }
184             %}
185             EOF
186             BackCode => pp_line_numbers(__LINE__, <<'EOF'),
187             loop(m) %{
188             PDL_Indx this_ind = $ind();
189             PDL_IF_BAD(if( $ISBADVAR(this_ind, ind) ) {
190             /* do nothing */
191             } else,) {
192             if( this_ind<0 || this_ind >= $SIZE(n) ) {
193             $CROAK("invalid index %"IND_FLAG" at pos %"IND_FLAG" (valid range 0..%"IND_FLAG")",
194             this_ind, m, $SIZE(n)-1);
195             }
196             $a(n=>this_ind) = $c();
197             }
198             %}
199             EOF
200             Doc => $doc,
201             BadDoc =>
202             'index1d propagates BAD index elements to the output variable.'
203             );
204              
205             my $index2d_init = pp_line_numbers(__LINE__, <<'EOF');
206             register PDL_Indx this_ind_a = $inda(),this_ind_b = $indb();
207             if( PDL_IF_BAD($ISBADVAR(this_ind_a,inda) ||,) this_ind_a<0 || this_ind_a>=$SIZE(na) )
208             $CROAK("invalid x-index %"IND_FLAG" (valid range 0..%"IND_FLAG")",
209             this_ind_a,$SIZE(na)-1);
210             if( PDL_IF_BAD($ISBADVAR(this_ind_b,indb) ||,) this_ind_b<0 || this_ind_b>=$SIZE(nb) )
211             $CROAK("invalid y-index %"IND_FLAG" (valid range 0..%"IND_FLAG")",
212             this_ind_b,$SIZE(nb)-1);
213             EOF
214             pp_def('index2d',
215             GenericTypes => [ppdefs_all],
216             HandleBad => 1,
217             DefaultFlow => 1,
218             TwoWay => 1,
219             Lvalue => 1,
220             Pars => 'a(na,nb); indx inda(); indx indb(); [oca] c();',
221             Code =>
222             $index2d_init . pp_line_numbers(__LINE__-1, ' $c() = $a(na => this_ind_a, nb => this_ind_b);'),
223             BackCode =>
224             $index2d_init . pp_line_numbers(__LINE__-1, ' $a(na => this_ind_a, nb => this_ind_b) = $c();'),
225             Doc => $doc,
226             BadDoc =>
227             'index2d barfs if either of the index values are bad.',
228             );
229              
230             pp_add_exported('','indexND indexNDb');
231             pp_addpm(<<'EOD');
232             =head2 indexNDb
233              
234             =for ref
235              
236             Backwards-compatibility alias for indexND
237              
238             =head2 indexND
239              
240             =for ref
241              
242             Find selected elements in an N-D ndarray, with optional boundary handling
243              
244             =for example
245              
246             $out = $source->indexND( $index, [$method] )
247              
248             $source = 10*xvals(10,10) + yvals(10,10);
249             $index = pdl([[2,3],[4,5]],[[6,7],[8,9]]);
250             print $source->indexND( $index );
251              
252             [
253             [23 45]
254             [67 89]
255             ]
256              
257             IndexND collapses C<$index> by lookup into C<$source>. The
258             0th dimension of C<$index> is treated as coordinates in C<$source>, and
259             the return value has the same dimensions as the rest of C<$index>.
260             The returned elements are looked up from C<$source>. Dataflow
261             works -- propagated assignment flows back into C<$source>.
262              
263             IndexND and IndexNDb were originally separate routines but they are both
264             now implemented as a call to L, and have identical syntax to
265             one another.
266              
267             SEE ALSO:
268              
269             L returns N-D indices into a multidimensional
270             PDL, suitable for feeding to this.
271              
272             =cut
273              
274             sub PDL::indexND :lvalue {
275             my($source,$index, $boundary) = @_;
276             return PDL::range($source,$index,undef,$boundary);
277             }
278              
279             *PDL::indexNDb = \&PDL::indexND;
280              
281             sub PDL::range :lvalue {
282             my($source,$ind,$sz,$bound) = @_;
283             # Convert to indx type up front (also handled in rangeb if necessary)
284             my $index = (ref $ind && UNIVERSAL::isa($ind,'PDL') && $ind->type eq 'indx') ? $ind : indx($ind);
285             my $size = defined($sz) ? PDL->pdl($sz) : undef;
286             # Handle empty PDL case: return a properly constructed Empty.
287             if ($index->isempty) {
288             my @sdims = $source->dims;
289             splice @sdims, 0, $index->dim(0) + ($index->dim(0)==0); # added term is to treat Empty[0] like a single empty coordinate
290             unshift @sdims, $size->list if defined $size;
291             my @index_dims = ((0) x ($index->ndims-1));
292             @index_dims = 0 if !@index_dims; # always at least one 0
293             return PDL->new_from_specification(@index_dims, @sdims);
294             }
295             $index = $index->dummy(0,1) unless $index->ndims;
296             # Pack boundary string if necessary
297             if (defined $bound) {
298             if (ref $bound eq 'ARRAY') {
299             my ($s,$el);
300             foreach $el (@$bound) {
301             barf "Illegal boundary value '$el' in range"
302             unless $el =~ m/^([0123fFtTeEpPmM])/;
303             $s .= $1;
304             }
305             $bound = $s;
306             }
307             elsif ($bound !~ m/^[0123ftepx]+$/ && $bound =~ m/^([0123ftepx])/i) {
308             $bound = $1;
309             }
310             }
311             no warnings; # shut up about passing undef into rangeb
312             $source->rangeb($index,$size,$bound);
313             }
314              
315             =head2 range
316              
317             =for ref
318              
319             Extract selected chunks from a source ndarray, with boundary conditions
320              
321             =for example
322              
323             $out = $source->range($index,[$size,[$boundary]])
324              
325             Returns elements or rectangular slices of the original ndarray, indexed by
326             the C<$index> ndarray. C<$source> is an N-dimensional ndarray, and C<$index> is
327             an ndarray whose first dimension has size up to N. Each row of C<$index> is
328             treated as coordinates of a single value or chunk from C<$source>, specifying
329             the location(s) to extract.
330              
331             If you specify a single index location, then range is essentially an expensive
332             slice, with controllable boundary conditions.
333              
334             B
335              
336             C<$index> and C<$size> can be ndarrays or array refs such as you would
337             feed to L and its ilk. If C<$index>'s 0th dimension
338             has size higher than the number of dimensions in C<$source>, then
339             C<$source> is treated as though it had trivial dummy dimensions of
340             size 1, up to the required size to be indexed by C<$index> -- so if
341             your source array is 1-D and your index array is a list of 3-vectors,
342             you get two dummy dimensions of size 1 on the end of your source array.
343              
344             You can extract single elements or N-D rectangular ranges from C<$source>,
345             by setting C<$size>. If C<$size> is undef or zero, then you get a single
346             sample for each row of C<$index>. This behavior is similar to
347             L, which is in fact implemented as a call to L.
348              
349             If C<$size> is positive then you get a range of values from C<$source> at
350             each location, and the output has extra dimensions allocated for them.
351             C<$size> can be a scalar, in which case it applies to all dimensions, or an
352             N-vector, in which case each element is applied independently to the
353             corresponding dimension in C<$source>. See below for details.
354              
355             C<$boundary> is a number, string, or list ref indicating the type of
356             boundary conditions to use when ranges reach the edge of C<$source>. If you
357             specify no boundary conditions the default is to forbid boundary violations
358             on all axes. If you specify exactly one boundary condition, it applies to
359             all axes. If you specify more (as elements of a list ref, or as a packed
360             string, see below), then they apply to dimensions in the order in which they
361             appear, and the last one applies to all subsequent dimensions. (This is
362             less difficult than it sounds; see the examples below).
363              
364             =over 3
365              
366             =item 0 (synonyms: 'f','forbid') B<(default)>
367              
368             Ranges are not allowed to cross the boundary of the original PDL. Disallowed
369             ranges throw an error. The errors are thrown at evaluation time, not
370             at the time of the range call (this is the same behavior as L).
371              
372             =item 1 (synonyms: 't','truncate')
373              
374             Values outside the original ndarray get BAD if you've got bad value
375             support compiled into your PDL and set the badflag for the source PDL;
376             or 0 if you haven't (you must set the badflag if you want BADs for out
377             of bound values, otherwise you get 0). Reverse dataflow works OK for
378             the portion of the child that is in-bounds. The out-of-bounds part of
379             the child is reset to (BAD|0) during each dataflow operation, but
380             execution continues.
381              
382             =item 2 (synonyms: 'e','x','extend')
383              
384             Values that would be outside the original ndarray point instead to the
385             nearest allowed value within the ndarray. See the CAVEAT below on
386             mappings that are not single valued.
387              
388             =item 3 (synonyms: 'p','periodic')
389              
390             Periodic boundary conditions apply: the numbers in $index are applied,
391             strict-modulo the corresponding dimensions of $source. This is equivalent to
392             duplicating the $source ndarray throughout N-D space. See the CAVEAT below
393             about mappings that are not single valued.
394              
395             =item 4 (synonyms: 'm','mirror')
396              
397             Mirror-reflection periodic boundary conditions apply. See the CAVEAT
398             below about mappings that are not single valued.
399              
400             =back
401              
402             The boundary condition identifiers all begin with unique characters, so
403             you can feed in multiple boundary conditions as either a list ref or a
404             packed string. (The packed string is marginally faster to run). For
405             example, the four expressions [0,1], ['forbid','truncate'], ['f','t'],
406             and 'ft' all specify that violating the boundary in the 0th dimension
407             throws an error, and all other dimensions get truncated.
408              
409             If you feed in a single string, it is interpreted as a packed boundary
410             array if all of its characters are valid boundary specifiers (e.g. 'pet'),
411             but as a single word-style specifier if they are not (e.g. 'forbid').
412              
413             Where the source PDL is empty, all non-barfing boundary conditions
414             are changed to truncation, since there is no data to reflect, extend,
415             or mirror.
416              
417             B
418              
419             The output broadcasts over both C<$index> and C<$source>. Because implicit
420             broadcasting can happen in a couple of ways, a little thought is needed. The
421             returned dimension list is stacked up like this:
422              
423             (index broadcast dims), (index dims (size)), (source broadcast dims)
424              
425             The first few dims of the output correspond to the extra dims of
426             C<$index> (beyond the 0 dim). They allow you to pick out individual
427             ranges from a large, broadcasted collection.
428              
429             The middle few dims of the output correspond to the size dims
430             specified in C<$size>, and contain the range of values that is extracted
431             at each location in C<$source>. Every nonzero element of C<$size> is copied to
432             the dimension list here, so that if you feed in (for example) C<$size
433             = [2,0,1]> you get an index dim list of C<(2,1)>.
434              
435             The last few dims of the output correspond to extra dims of C<$source> beyond
436             the number of dims indexed by C<$index>. These dims act like ordinary
437             broadcast dims, because adding more dims to C<$source> just tacks extra dims
438             on the end of the output. Each source broadcast dim ranges over the entire
439             corresponding dim of C<$source>.
440              
441             B: Dataflow is bidirectional.
442              
443             B:
444             Here are basic examples of C operation, showing how to get
445             ranges out of a small matrix. The first few examples show extraction
446             and selection of individual chunks. The last example shows
447             how to mark loci in the original matrix (using dataflow).
448              
449             pdl> $src = 10*xvals(10,5)+yvals(10,5)
450             pdl> print $src->range([2,3]) # Cut out a single element
451             23
452             pdl> print $src->range([2,3],1) # Cut out a single 1x1 block
453             [
454             [23]
455             ]
456             pdl> print $src->range([2,3], [2,1]) # Cut a 2x1 chunk
457             [
458             [23 33]
459             ]
460             pdl> print $src->range([[2,3]],[2,1]) # Trivial list of 1 chunk
461             [
462             [
463             [23]
464             [33]
465             ]
466             ]
467             pdl> print $src->range([[2,3],[0,1]], [2,1]) # two 2x1 chunks
468             [
469             [
470             [23 1]
471             [33 11]
472             ]
473             ]
474             pdl> # A 2x2 collection of 2x1 chunks
475             pdl> print $src->range([[[1,1],[2,2]],[[2,3],[0,1]]],[2,1])
476             [
477             [
478             [
479             [11 22]
480             [23 1]
481             ]
482             [
483             [21 32]
484             [33 11]
485             ]
486             ]
487             ]
488             pdl> $src = xvals(5,3)*10+yvals(5,3)
489             pdl> print $src->range(3,1) # Broadcast over y dimension in $src
490             [
491             [30]
492             [31]
493             [32]
494             ]
495              
496             pdl> $src = zeroes(5,4);
497             pdl> $src->range(pdl([2,3],[0,1]),pdl(2,1)) .= xvals(2,2,1) + 1
498             pdl> print $src
499             [
500             [0 0 0 0 0]
501             [2 2 0 0 0]
502             [0 0 0 0 0]
503             [0 0 1 1 0]
504             ]
505              
506             B: It's quite possible to select multiple ranges that
507             intersect. In that case, modifying the ranges doesn't have a
508             guaranteed result in the original PDL -- the result is an arbitrary
509             choice among the valid values. For some things that's OK; but for
510             others it's not. In particular, this doesn't work:
511              
512             pdl> $photon_list = PDL::RandVar->new->sample(500)->reshape(2,250)*10
513             pdl> $histogram = zeroes(10,10)
514             pdl> $histogram->range($photon_list,1)++; #not what you wanted
515              
516             The reason is that if two photons land in the same bin, then that bin
517             doesn't get incremented twice. (That may get fixed in a later version...)
518              
519             B: If C<$index> has too many dimensions compared
520             to C<$source>, then $source is treated as though it had dummy
521             dimensions of size 1, up to the required number of dimensions. These
522             virtual dummy dimensions have the usual boundary conditions applied to
523             them.
524              
525             If the 0 dimension of C<$index> is ludicrously large (if its size is
526             more than 5 greater than the number of dims in the source PDL) then
527             range will insist that you specify a size in every dimension, to make
528             sure that you know what you're doing. That catches a common error with
529             range usage: confusing the initial dim (which is usually small) with another
530             index dim (perhaps of size 1000).
531              
532             If the index variable is Empty, then range() always returns the Empty PDL.
533             If the index variable is not Empty, indexing it always yields a boundary
534             violation. All non-barfing conditions are treated as truncation, since
535             there are no actual data to return.
536              
537             B: Because C isn't an affine transformation (it
538             involves lookup into a list of N-D indices), it is somewhat
539             memory-inefficient for long lists of ranges, and keeping dataflow open
540             is much slower than for affine transformations (which don't have to copy
541             data around).
542              
543             Doing operations on small subfields of a large range is inefficient
544             because the engine must flow the entire range back into the original
545             PDL with every atomic perl operation, even if you only touch a single element.
546             One way to speed up such code is to sever your range, so that PDL
547             doesn't have to copy the data with each operation, then copy the
548             elements explicitly at the end of your loop. Here's an example that
549             labels each region in a range sequentially, using many small
550             operations rather than a single xvals assignment:
551              
552             ### How to make a collection of small ops run fast with range...
553             $x = $data->range($index, $sizes, $bound)->sever;
554             $aa = $data->range($index, $sizes, $bound);
555             $x($_ - 1) .= $_ for 1..$x->nelem; # Lots of little ops
556             $aa .= $x;
557              
558             C is a perl front-end to a PP function, C. Calling
559             C is marginally faster but requires that you include all arguments.
560              
561             DEVEL NOTES
562              
563             * index broadcast dimensions are effectively clumped internally. This
564             makes it easier to loop over the index array but a little more brain-bending
565             to tease out the algorithm.
566              
567             =cut
568             EOD
569              
570             pp_def('rangeb',
571             OtherPars => 'pdl *ind_pdl; SV *size_sv; SV *boundary_sv',
572             Doc => <<'EOD',
573             =for ref
574              
575             Engine for L
576              
577             =for example
578              
579             Same calling convention as L, but you must supply all
580             parameters. C is marginally faster as it makes a direct PP call,
581             avoiding the perl argument-parsing step.
582             EOD
583             HandleBad => 1,
584             TwoWay => 1,
585             Lvalue => 1,
586             P2Child => 1,
587              
588             # rdim: dimensionality of each range (0 dim of index PDL)
589             # nitems: total number of list elements (product of itdims)
590             # itdim: number of index broadcast dimensions
591             # ntsize: number of nonzero size dimensions
592             # bsize: Number of independently specified boundary conditions
593             # nsizes: Number of independently specified range dim sizes
594             # sizes: array of range sizes, indexed (0..rdim-1). A zero element means
595             # that the dimension is omitted from the child dim list.
596             # itdims: Size of each index broadcast dimension, indexed (0..itdim-1)
597             # corners: parent coordinates of each corner, running fastest over coord index.
598             # (indexed 0 .. (nitems-1)*(rdim)+rdim-1)
599             # boundary: Array containing all the boundary condition specs
600              
601             Comp => 'PDL_Indx rdim;
602             PDL_Indx nitems;
603             PDL_Indx itdim;
604             PDL_Indx ntsize;
605             PDL_Indx bsize;
606             PDL_Indx nsizes;
607             PDL_Indx sizes[$COMP(rdim)];
608             PDL_Indx itdims[$COMP(itdim)];
609             PDL_Indx corners[$COMP(rdim) * $COMP(nitems)];
610             char boundary[$COMP(rdim)];
611             char size_pdl_destroy;
612             char ind_pdl_destroy;
613             ',
614             MakeComp => pp_line_numbers(__LINE__, <<'EOD-MakeComp'),
615             pdl *size_pdl = NULL;
616             PDL_RETERROR(PDL_err, PDL->make_physdims(ind_pdl));
617             $COMP(size_pdl_destroy) = $COMP(ind_pdl_destroy) = 0;
618              
619             /* Generalized empties are ok, but not in the special 0 dim (the index vector) */
620             if(ind_pdl->dims[0] == 0)
621             { $CROAK("can't handle Empty indices -- call range instead"); }
622              
623             /***
624             * Ensure that the index is a PDL_Indx. If there's no loss of information,
625             * just upgrade it -- otherwise, make a temporary copy.
626             */
627             switch(ind_pdl->datatype) {
628             default: /* Most types: */
629             ind_pdl = PDL->hard_copy(ind_pdl); /* copy and fall through */
630             if (!ind_pdl) $CROAK("Error in hard_copy");
631             $COMP(ind_pdl_destroy) = 1;
632             case PDL_SB: case PDL_B: case PDL_S: case PDL_US: case PDL_L: case PDL_UL: case PDL_LL: case PDL_ULL:
633             PDL_RETERROR(PDL_err, PDL->converttype(ind_pdl,PDL_IND)); /* convert in place. */
634             break;
635             case PDL_IND:
636             PDL_RETERROR(PDL_err, PDL->make_physical(ind_pdl));
637             break;
638             }
639              
640             /***
641             * Figure sizes of the COMP arrays and allocate them.
642             */
643             {
644             PDL_Indx i,nitems;
645              
646             $COMP(rdim) = ind_pdl->ndims ? ind_pdl->dims[0] : 1;
647             for(i=nitems=1; i < ind_pdl->ndims; i++) /* Accumulate item list size */
648             nitems *= ind_pdl->dims[i];
649             $COMP(nitems) = nitems;
650             $COMP(itdim) = ind_pdl->ndims ? ind_pdl->ndims - 1 : 0;
651             $DOCOMPALLOC();
652             }
653              
654             /***
655             * Fill in the boundary condition array
656             */
657             {
658             char *bstr;
659             STRLEN blen;
660             bstr = SvPV(boundary_sv,blen);
661              
662             if(blen == 0) {
663             /* If no boundary is specified then every dim gets forbidden */
664             PDL_Indx i;
665             for (i=0;i<$COMP(rdim);i++)
666             $COMP(boundary)[i] = 0;
667             } else {
668             PDL_Indx i;
669             for(i=0;i<$COMP(rdim);i++) {
670             switch(bstr[PDLMIN(i, blen-1)]) {
671             case '0': case 'f': case 'F': /* forbid */
672             $COMP(boundary)[i] = 0;
673             break;
674             case '1': case 't': case 'T': /* truncate */
675             $COMP(boundary)[i] = 1;
676             break;
677             case '2': case 'e': case 'E': /* extend */
678             $COMP(boundary)[i] = 2;
679             break;
680             case '3': case 'p': case 'P': /* periodic */
681             $COMP(boundary)[i] = 3;
682             break;
683             case '4': case 'm': case 'M': /* mirror */
684             $COMP(boundary)[i] = 4;
685             break;
686             default:
687             {
688             /* No need to check if i < blen -- this will barf out the
689             * first time it gets hit.
690             */
691             $CROAK("Unknown boundary condition '%c' in range",bstr[i]);
692             }
693             break;
694             } // end of switch
695             }
696             }
697             }
698             /***
699             * Store the sizes of the index-broadcast dims
700             */
701             {
702             PDL_Indx i;
703             PDL_Indx nd = ind_pdl->ndims - 1;
704             for(i=0; i < nd ; i++)
705             $COMP(itdims)[i] = ind_pdl->dims[i+1];
706             }
707              
708             /***
709             * Check and condition the size ndarray, and store sizes of the ranges
710             */
711             {
712             PDL_Indx i,ntsize;
713              
714             if( (size_sv == NULL) || !SvOK(size_sv) ) {
715             // NO size was passed in, not normally executed even if you passed in no size to range(),
716             // as range() generates a size array...
717             for(i=0;i<$COMP(rdim);i++)
718             $COMP(sizes)[i] = 0;
719              
720             } else {
721             /* Normal case with sizes present in a PDL */
722              
723             if(!(size_pdl = PDL->SvPDLV(size_sv))) /* assignment */
724             $CROAK("Unable to convert size to a PDL");
725              
726             if(size_pdl->nvals == 0) {
727             // no values in the size_pdl - Empty or Null. Just copy 0s to all the range dims
728             for(i=0;i<$COMP(rdim);i++)
729             $COMP(sizes)[i] = 0;
730              
731             } else {
732              
733             // Convert size PDL to PDL_IND to support indices
734             switch(size_pdl->datatype) {
735             default: /* Most types: */
736             size_pdl = PDL->hard_copy(size_pdl); /* copy and fall through */
737             if (!size_pdl) $CROAK("Error in hard_copy");
738             $COMP(size_pdl_destroy) = 1;
739             case PDL_SB: case PDL_B: case PDL_S: case PDL_US: case PDL_L: case PDL_UL: case PDL_LL: case PDL_ULL:
740             PDL_RETERROR(PDL_err, PDL->converttype(size_pdl,PDL_IND)); /* convert in place. */
741             break;
742             case PDL_IND:
743             PDL_RETERROR(PDL_err, PDL->make_physical(size_pdl));
744             break;
745             }
746              
747             $COMP(nsizes) = size_pdl->nvals; /* Store for later permissiveness check */
748              
749             /* Copy the sizes, or die if they're the wrong shape */
750             if(size_pdl->nvals == 1) {
751             for(i=0;i<$COMP(rdim);i++) {
752             $COMP(sizes)[i] = *((PDL_Indx *)(size_pdl->data));
753             }
754              
755             /* Check for nonnegativity of sizes. The rdim>0 mask ensures that */
756             /* we don't barf on the Empty PDL (as an index). */
757             if( $COMP(rdim) > 0 && $COMP(sizes)[0] < 0 ) {
758             $CROAK("Negative range size is not allowed\n");
759             }
760             }
761             else if( size_pdl->nvals <= $COMP(rdim) && size_pdl->ndims == 1) {
762             for(i=0;i<$COMP(rdim);i++) {
763             $COMP(sizes)[i] = ( (i < size_pdl->nvals) ?
764             ((PDL_Indx *)(size_pdl->data))[i] :
765             0
766             );
767             if($COMP(sizes)[i] < 0)
768             $CROAK("Negative range sizes are not allowed\n");
769             }
770             }
771             else {
772             $CROAK("Size must match index's 0th dim\n");
773             }
774              
775             } /* end of nonempty size-ndarray code */
776             } /* end of defined-size-ndarray code */
777              
778             /* Insert the number of nontrivial sizes (these get output dimensions) */
779             for(i=ntsize=0;i<$COMP(rdim);i++)
780             if($COMP(sizes)[i])
781             ntsize++;
782             $COMP(ntsize) = ntsize;
783             }
784              
785             /***
786             * Stash coordinates of the corners
787             */
788              
789             PDL_Indx j,k,ioff, iter[$COMP(itdim)];
790             /* initialize iterator to loop over index broadcasts */
791             PDL_Indx *cptr = iter;
792             for(k=0;k<$COMP(itdim);k++)
793             *(cptr++) = 0;
794             cptr = $COMP(corners);
795             do {
796             /* accumulate offset into the index from the iterator */
797             for(k=ioff=0;k<$COMP(itdim);k++)
798             ioff += iter[k] * ind_pdl->dimincs[k+1];
799             /* Loop over the 0th dim of index, copying coords. */
800             /* This is the natural place to check for permissive ranging; too */
801             /* bad we don't have access to the parent ndarray here... */
802             for(j=0;j<$COMP(rdim);j++)
803             *(cptr++) = ((PDL_Indx *)(ind_pdl->data))[ioff + ind_pdl->dimincs[0] * j];
804             /* Increment the iterator -- the test increments, the body carries. */
805             for(k=0; k<$COMP(itdim) && (++(iter[k]))>=($COMP(itdims)[k]) ;k++)
806             iter[k] = 0;
807             } while(k<$COMP(itdim));
808             if ($COMP(ind_pdl_destroy))
809             PDL->destroy(ind_pdl); /* finished with our copy */
810             if ($COMP(size_pdl_destroy))
811             PDL->destroy(size_pdl); /* finished with our copy */
812             EOD-MakeComp
813              
814             RedoDims => pp_line_numbers(__LINE__, <<'EOD-RedoDims'),
815             PDL_Indx stdim = $PDL(PARENT)->ndims - $COMP(rdim);
816             PDL_Indx dim,inc;
817             PDL_Indx i,rdvalid;
818              
819             // Speed bump for ludicrous cases
820             if( $COMP(rdim) > $PDL(PARENT)->ndims+5 && $COMP(nsizes) != $COMP(rdim)) {
821             $CROAK(
822             "Ludicrous number of extra dims in range index; leaving child null.\n"
823             " (%"IND_FLAG" implicit dims is > 5; index has %"IND_FLAG" dims; source has %"IND_FLAG" dim%s.)\n"
824             " This often means that your index PDL is incorrect.\n"
825             " To avoid this message, allocate dummy dims in\n"
826             " the source or use %"IND_FLAG" dims in range's size field.\n",
827             $COMP(rdim)-$PDL(PARENT)->ndims,$COMP(rdim),$PDL(PARENT)->ndims,
828             $PDL(PARENT)->ndims>1?"s":"",$COMP(rdim)
829             );
830             }
831              
832             if(stdim < 0)
833             stdim = 0;
834              
835             /* Set dimensionality of child */
836             $SETNDIMS($COMP(itdim)+$COMP(ntsize)+stdim);
837              
838             inc = 1;
839             /* Copy size dimensions to child, crunching as we go. */
840             dim = $COMP(itdim);
841             for(i=rdvalid=0;i<$COMP(rdim);i++) {
842             if($COMP(sizes)[i]) {
843             rdvalid++;
844             $PDL(CHILD)->dimincs[dim] = inc;
845             inc *= ($PDL(CHILD)->dims[dim++] = $COMP(sizes)[i]); /* assignment */
846             }
847             }
848              
849             /* Copy index broadcast dimensions to child */
850             for(dim=0; dim<$COMP(itdim); dim++) {
851             $PDL(CHILD)->dimincs[dim] = inc;
852             inc *= ($PDL(CHILD)->dims[dim] = $COMP(itdims)[dim]); /* assignment */
853             }
854              
855             /* Copy source broadcast dimensions to child */
856             dim = $COMP(itdim) + rdvalid;
857             for(i=0;i
858             $PDL(CHILD)->dimincs[dim] = inc;
859             inc *= ($PDL(CHILD)->dims[dim++] = $PDL(PARENT)->dims[i+$COMP(rdim)]); /* assignment */
860             }
861              
862             /* Cover bizarre case where the source PDL is empty - in that case, change */
863             /* all non-barfing boundary conditions to truncation, since we have no data */
864             /* to reflect, extend, or mirror. */
865             if($PDL(PARENT)->dims[0] == 0) {
866             for(dim=0; dim<$COMP(rdim); dim++) {
867             if($COMP(boundary)[dim])
868             $COMP(boundary)[dim] = 1; // force truncation
869             }
870             }
871              
872             $PDL(CHILD)->datatype = $PDL(PARENT)->datatype;
873             $SETDIMS();
874             EOD-RedoDims
875              
876             EquivCPOffsCode => pp_line_numbers(__LINE__, <<'EOD-EquivCPOffsCode'),
877             PDL_Indx *ip; /* vector iterator */
878             PDL_Indx *sp; /* size vector including stdims */
879             PDL_Indx *coords; /* current coordinates */
880              
881             PDL_Indx k; /* index */
882             PDL_Indx item; /* index broadcast iterator */
883             PDL_Indx pdim = $PDL(PARENT)->ndims;
884             PDL_Indx rdim = $COMP(rdim);
885             PDL_Indx prdim = PDLMIN(rdim,pdim);
886             PDL_Indx iter2[pdim * 2 + rdim];
887             PDL_Indx *sizes = iter2 + pdim;
888             coords = sizes + pdim;
889              
890             /* Figure out size vector */
891             for(ip = $COMP(sizes), sp = sizes, k=0; k
892             *(sp++) = *(ip++);
893             for(; k < pdim; k++)
894             *(sp++) = $PDL(PARENT)->dims[k];
895              
896              
897             /* Loop over all the ranges in the index list */
898             for(item=0; item<$COMP(nitems); item++) {
899              
900             /* initialize in-range iterator to loop within each range */
901             for(ip = iter2, k=0; k
902             *(ip++) = 0;
903              
904             do {
905             PDL_Indx poff = 0;
906             PDL_Indx coff;
907             PDL_Indx k2;
908             char trunc = 0; /* Flag used to skip truncation case */
909              
910             /* Collect and boundary-check the current N-D coords */
911             for(k=0; k < prdim; k++){
912              
913             PDL_Indx ck = iter2[k] + $COMP(corners)[ item * rdim + k ] ;
914              
915             /* normal case */
916             if(ck < 0 || ck >= $PDL(PARENT)->dims[k]) {
917             switch($COMP(boundary)[k]) {
918             case 0: /* no boundary breakage allowed */
919             $CROAK("index out-of-bounds in range (index vector #%ld)",item);
920             break;
921             case 1: /* truncation */
922             trunc = 1;
923             break;
924             case 2: /* extension -- crop */
925             ck = (ck >= $PDL(PARENT)->dims[k]) ? $PDL(PARENT)->dims[k]-1 : 0;
926             break;
927             case 3: /* periodic -- mod it */
928             ck %= $PDL(PARENT)->dims[k];
929             if(ck < 0) /* Fix mod breakage in C */
930             ck += $PDL(PARENT)->dims[k];
931             break;
932             case 4: /* mirror -- reflect off the edges */
933             ck += $PDL(PARENT)->dims[k];
934             ck %= ($PDL(PARENT)->dims[k] * 2);
935             if(ck < 0) /* Fix mod breakage in C */
936             ck += $PDL(PARENT)->dims[k]*2;
937             ck -= $PDL(PARENT)->dims[k];
938             if(ck < 0) {
939             ck *= -1;
940             ck -= 1;
941             }
942             break;
943             default:
944             $CROAK("Unknown boundary condition in range -- bug alert!");
945             break;
946             }
947             }
948              
949             coords[k] = ck;
950              
951             }
952              
953             /* Check extra dimensions -- pick up where k left off... */
954             for( ; k < rdim ; k++) {
955             /* Check for indexing off the end of the dimension list */
956              
957             PDL_Indx ck = iter2[k] + $COMP(corners)[ item * rdim + k ] ;
958              
959             switch($COMP(boundary)[k]) {
960             case 0: /* No boundary breakage allowed -- nonzero corners cause barfage */
961             if(ck != 0)
962             $CROAK("Too many dims in range index (and you've forbidden boundary violations)");
963             break;
964             case 1: /* truncation - just truncate if the corner is nonzero */
965             trunc |= (ck != 0);
966             break;
967             case 2: /* extension -- ignore the corner (same as 3) */
968             case 3: /* periodic -- ignore the corner */
969             case 4: /* mirror -- ignore the corner */
970             ck = 0;
971             break;
972             default:
973             $CROAK("Unknown boundary condition in range -- bug alert!");
974             break;
975             }
976             }
977              
978             /* Find offsets into the child and parent arrays, from the N-D coords */
979             /* Note we only loop over real source dims (prdim) to accumulate -- */
980             /* because the offset is trivial and/or we're truncating for virtual */
981             /* dims caused by permissive ranging. */
982             coff = $PDL(CHILD)->dimincs[0] * item;
983             for(k2 = $COMP(itdim), poff = k = 0;
984             k < prdim;
985             k++) {
986             poff += coords[k]*$PDL(PARENT)->dimincs[k];
987             if($COMP(sizes)[k])
988             coff += iter2[k] * $PDL(CHILD)->dimincs[k2++];
989             }
990              
991             /* Loop the copy over all the source broadcast dims (above rdim). */
992             do {
993             PDL_Indx poff1 = poff;
994             PDL_Indx coff1 = coff;
995              
996             /* Accumulate the offset due to source broadcasting */
997             for(k2 = $COMP(itdim) + $COMP(ntsize), k = rdim;
998             k < pdim;
999             k++) {
1000             poff1 += iter2[k] * $PDL(PARENT)->dimincs[k];
1001             coff1 += iter2[k] * $PDL(CHILD)->dimincs[k2++];
1002             }
1003              
1004             /* Finally -- make the copy
1005             * EQUIVCPTRUNC works like EQUIVCPOFFS but with checking for
1006             * out-of-bounds conditions.
1007             */
1008             $EQUIVCPTRUNC(coff1,poff1,trunc);
1009              
1010             /* Increment the source broadcast iterator */
1011             for( k=$COMP(rdim);
1012             k < pdim && (++(iter2[k]) >= $PDL(PARENT)->dims[k]);
1013             k++)
1014             iter2[k] = 0;
1015             } while(k < pdim); /* end of source-broadcast iteration */
1016              
1017             /* Increment the in-range iterator */
1018             for(k = 0;
1019             k < $COMP(rdim) && (++(iter2[k]) >= $COMP(sizes)[k]);
1020             k++)
1021             iter2[k] = 0;
1022             } while(k < $COMP(rdim)); /* end of main iteration */
1023             } /* end of item do loop */
1024             EOD-EquivCPOffsCode
1025             );
1026              
1027             pp_def('rld',
1028             GenericTypes => [ppdefs_all],
1029             Pars=>'indx a(n); b(n); [o]c(m);',
1030             OtherPars=>'IV sumover_max => m',
1031             PMCode =>pp_line_numbers(__LINE__, <<'EOD'),
1032             sub PDL::rld {
1033             my ($x,$y) = @_;
1034             my ($c,$sm) = @_ == 3 ? ($_[2], $_[2]->dim(0)) : (PDL->null, $x->sumover->max->sclr);
1035             PDL::_rld_int($x,$y,$c,$sm);
1036             $c;
1037             }
1038             EOD
1039             Code => pp_line_numbers(__LINE__, <<'EOF'),
1040             PDL_Indx j=0;
1041             loop (n) %{
1042             PDL_Indx jlim = j + $a();
1043             $GENERIC(b) bv = $b();
1044             loop (m=j:jlim) %{ $c() = bv; %}
1045             j = jlim;
1046             %}
1047             EOF
1048             Doc => <<'EOD'
1049             =for ref
1050              
1051             Run-length decode a vector
1052              
1053             Given a vector C<$x> of the numbers of instances of values C<$y>, run-length
1054             decode to C<$c>.
1055             EOD
1056             );
1057              
1058             pp_def('rle',
1059             GenericTypes => [ppdefs_all],
1060             Pars=>'c(n); indx [o]a(m=CALC($SIZE(n))); [o]b(m);',
1061             PMCode =>pp_line_numbers(__LINE__, <<'EOC'),
1062             sub PDL::rle {
1063             my $c = shift;
1064             my ($x,$y) = @_==2 ? @_ : (null,null);
1065             PDL::_rle_int($c,$x,$y);
1066             my $max_ind = ($c->ndims<2) ? ($x!=0)->sumover-1 :
1067             ($x!=0)->clump(1..$x->ndims-1)->sumover->max->sclr-1;
1068             return ($x->slice("0:$max_ind"),$y->slice("0:$max_ind"));
1069             }
1070             EOC
1071             Code =>pp_line_numbers(__LINE__, <<'EOF'),
1072             PDL_Indx j=0;
1073             $GENERIC(c) cv, clv = $c(n=>0);
1074             $b(m=>0) = clv;
1075             $a(m=>0) = 0;
1076             loop (n) %{
1077             cv = $c();
1078             if (cv == clv) {
1079             $a(m=>j)++;
1080             } else {
1081             j++;
1082             $b(m=>j) = clv = cv;
1083             $a(m=>j) = 1;
1084             }
1085             %}
1086             loop (m=j+1) %{
1087             $a() = 0;
1088             $b() = 0;
1089             %}
1090             EOF
1091             Doc => <<'EOD'
1092             =for ref
1093              
1094             Run-length encode a vector
1095              
1096             Given vector C<$c>, generate a vector C<$x> with the number of each
1097             element, and a vector C<$y> of the unique values. New in PDL 2.017,
1098             only the elements up to the first instance of C<0> in C<$x> are
1099             returned, which makes the common use case of a 1-dimensional C<$c> simpler.
1100             For broadcast operation, C<$x> and C<$y> will be large enough
1101             to hold the largest row of C<$y>, and only the elements up to the
1102             first instance of C<0> in each row of C<$x> should be considered.
1103              
1104             =for example
1105              
1106             $c = floor(4*random(10));
1107             rle($c,$x=null,$y=null);
1108             #or
1109             ($x,$y) = rle($c);
1110              
1111             #for $c of shape [10, 4]:
1112             $c = floor(4*random(10,4));
1113             ($x,$y) = rle($c);
1114              
1115             #to see the results of each row one at a time:
1116             foreach (0..$c->dim(1)-1){
1117             my ($as,$bs) = ($x(:,($_)),$y(:,($_)));
1118             my ($ta,$tb) = where($as,$bs,$as!=0); #only the non-zero elements of $x
1119             print $c(:,($_)) . " rle==> " , ($ta,$tb) , "\trld==> " . rld($ta,$tb) . "\n";
1120             }
1121              
1122             # the inverse of (chance of all 6 3d6 rolls being >= each possible sum)
1123             ($nrolls, $ndice, $dmax) = (6, 3, 6);
1124             ($x, $x1) = (allaxisvals(($dmax) x $ndice)+1)->sumover->flat->qsort->rle;
1125             $y = $x->cumusumover;
1126             $yprob1x = $y->slice('-1:0')->double / $y->slice('(-1)');
1127             $z = cat($x1, 1 / $yprob1x**$nrolls)->transpose;
1128              
1129             =cut
1130              
1131             EOD
1132             );
1133              
1134             pp_def('rlevec',
1135             Pars => "c(M,N); indx [o]a(N); [o]b(M,N)",
1136             Code =>pp_line_numbers(__LINE__, <<'EOC'),
1137             PDL_Indx bn=0;
1138             loop (M) %{ $b(N=>0)=$c(N=>0); %}
1139             $a(N=>0) = 1;
1140             loop (N=1) %{
1141             char matches=1;
1142             loop (M) %{
1143             if ($c() != $b(N=>bn)) {
1144             matches=0;
1145             break;
1146             }
1147             %}
1148             if (matches) {
1149             $a(N=>bn)++;
1150             } else {
1151             bn++;
1152             loop (M) %{ $b(N=>bn) = $c(); %}
1153             $a(N=>bn) = 1;
1154             }
1155             %}
1156             loop (N=bn+1) %{
1157             $a() = 0;
1158             loop (M) %{ $b() = 0; %}
1159             %}
1160             EOC
1161             Doc =><<'EOD',
1162             =for ref
1163              
1164             Run-length encode a set of vectors.
1165              
1166             Higher-order rle(), for use with qsortvec().
1167              
1168             Given set of vectors $c, generate a vector $a with the number of occurrences of each element
1169             (where an "element" is a vector of length $M occurring in $c),
1170             and a set of vectors $b containing the unique values.
1171             As for rle(), only the elements up to the first instance of 0 in $a should be considered.
1172              
1173             Can be used together with clump() to run-length encode "values" of arbitrary dimensions.
1174             Can be used together with rotate(), cat(), append(), and qsortvec() to count N-grams
1175             over a 1d PDL.
1176              
1177             See also: L, L, L
1178             Contributed by Bryan Jurish Emoocow@cpan.orgE.
1179              
1180             EOD
1181             );
1182              
1183             pp_def('rldvec',
1184             Pars => 'indx a(uniqvals); b(M,uniqvals); [o]c(M,decodedvals)',
1185             OtherPars=>'IV sumover_max => decodedvals',
1186             PMCode =>pp_line_numbers(__LINE__, <<'EOC'),
1187             sub PDL::rldvec {
1188             my ($a,$b,$c) = @_;
1189             $c = PDL->null unless defined $c;
1190             my $sm = $c->isnull ? $a->sumover->max->sclr : $c->dim(1);
1191             PDL::_rldvec_int($a,$b,$c,$sm);
1192             return $c;
1193             }
1194             EOC
1195             Code =>pp_line_numbers(__LINE__, <<'EOC'),
1196             PDL_Indx cn=0;
1197             loop (uniqvals) %{
1198             PDL_Indx cnlim = cn + $a();
1199             loop (decodedvals=cn:cnlim,M) %{ $c() = $b(); %}
1200             cn = cnlim;
1201             %}
1202             EOC
1203             Doc =><<'EOD'
1204             =for ref
1205              
1206             Run-length decode a set of vectors, akin to a higher-order rld().
1207              
1208             Given a vector $a() of the number of occurrences of each row, and a set $b()
1209             of row-vectors each of length $M, run-length decode to $c().
1210              
1211             Can be used together with clump() to run-length decode "values" of arbitrary dimensions.
1212              
1213             Note: $sumover_max is used internally only. Any value provided will be
1214             ignored.
1215              
1216             See also: L.
1217             Contributed by Bryan Jurish Emoocow@cpan.orgE.
1218             EOD
1219             );
1220              
1221             pp_def('rleseq',
1222             Pars => "c(N); indx [o]a(N); [o]b(N)",
1223             Code =>pp_line_numbers(__LINE__, <<'EOC'),
1224             PDL_Indx j=0;
1225             $GENERIC(c) coff;
1226             coff = $c(N=>0);
1227             $b(N=>0) = coff;
1228             $a(N=>0) = 0;
1229             loop (N) %{
1230             if ($c() == coff+$a(N=>j)) {
1231             $a(N=>j)++;
1232             } else {
1233             j++;
1234             $b(N=>j) = coff = $c();
1235             $a(N=>j) = 1;
1236             }
1237             %}
1238             loop (N=j+1) %{
1239             $a() = 0;
1240             $b() = 0;
1241             %}
1242             EOC
1243             Doc =><<'EOD',
1244             =for ref
1245              
1246             Run-length encode a vector of subsequences.
1247              
1248             Given a vector of $c() of concatenated variable-length, variable-offset subsequences,
1249             generate a vector $a containing the length of each subsequence
1250             and a vector $b containing the subsequence offsets.
1251             As for rle(), only the elements up to the first instance of 0 in $a should be considered.
1252              
1253             See also L.
1254             Contributed by Bryan Jurish Emoocow@cpan.orgE.
1255             EOD
1256             );
1257              
1258             pp_def('rldseq',
1259             Pars => 'indx a(N); b(N); [o]c(M)',
1260             OtherPars=>'IV sumover_max => M',
1261             PMCode =>pp_line_numbers(__LINE__, <<'EOC'),
1262             sub PDL::rldseq {
1263             my ($a,$b,$c) = @_;
1264             $c = PDL->null unless defined $c;
1265             my $sm = $c->isnull ? $a->sumover->max->sclr : $c->dim(1);
1266             PDL::_rldseq_int($a,$b,$c,$sm);
1267             return $c;
1268             }
1269             EOC
1270             Code =>pp_line_numbers(__LINE__, <<'EOC'),
1271             PDL_Indx mi=0;
1272             loop (N) %{
1273             PDL_Indx milim = mi + $a(), li = 0;
1274             loop (M=mi:milim) %{ $c() = $b() + li++; %}
1275             mi = milim;
1276             %}
1277             EOC
1278             Doc =><<'EOD'
1279             =for ref
1280              
1281             Run-length decode a subsequence vector.
1282              
1283             Given a vector $a() of sequence lengths
1284             and a vector $b() of corresponding offsets,
1285             decode concatenation of subsequences to $c(),
1286             as for:
1287              
1288             $c = null;
1289             $c = $c->append($b($_)+sequence($a->type,$a($_))) foreach (0..($N-1));
1290              
1291             Note: $sumover_max is used internally only. Any value provided will be
1292             ignored.
1293              
1294             See also: L.
1295             Contributed by Bryan Jurish Emoocow@cpan.orgE.
1296             EOD
1297             );
1298              
1299             pp_add_exported('','rleND rldND');
1300             pp_addpm(<<'EOF');
1301             =head2 rleND
1302              
1303             =for sig
1304              
1305             Signature: (data(@vdims,N); int [o]counts(N); [o]elts(@vdims,N))
1306              
1307             =for ref
1308              
1309             Run-length encode a set of (sorted) n-dimensional values.
1310              
1311             Generalization of rle() and vv_rlevec():
1312             given set of values $data, generate a vector $counts with the number of occurrences of each element
1313             (where an "element" is a matrix of dimensions @vdims occurring as a sequential run over the
1314             final dimension in $data), and a set of vectors $elts containing the elements which begin a run.
1315             Really just a wrapper for clump() and rlevec().
1316              
1317             See also: L, L.
1318             Contributed by Bryan Jurish Emoocow@cpan.orgE.
1319              
1320             =cut
1321              
1322             *PDL::rleND = \&rleND;
1323             sub rleND {
1324             my $data = shift;
1325             my @vdimsN = $data->dims;
1326              
1327             ##-- construct output pdls
1328             my $counts = $#_ >= 0 ? $_[0] : PDL->null;
1329             my $elts = $#_ >= 1 ? $_[1] : zeroes($data->type, @vdimsN);
1330              
1331             ##-- guts: call rlevec()
1332             rlevec($data->clump($#vdimsN), $counts, $elts->clump($#vdimsN));
1333              
1334             return ($counts,$elts);
1335             }
1336              
1337             =head2 rldND
1338              
1339             =for sig
1340              
1341             Signature: (int counts(N); elts(@vdims,N); [o]data(@vdims,N);)
1342              
1343             =for ref
1344              
1345             Run-length decode a set of (sorted) n-dimensional values.
1346              
1347             Generalization of rld() and rldvec():
1348             given a vector $counts() of the number of occurrences of each @vdims-dimensioned element,
1349             and a set $elts() of @vdims-dimensioned elements, run-length decode to $data().
1350              
1351             Really just a wrapper for clump() and rldvec().
1352              
1353             See also: L, L.
1354             Contributed by Bryan Jurish Emoocow@cpan.orgE.
1355              
1356             =cut
1357              
1358             *PDL::rldND = \&rldND;
1359             sub rldND {
1360             my ($counts,$elts) = (shift,shift);
1361             my @vdimsN = $elts->dims;
1362              
1363             ##-- allocate output pdl if none given
1364             my $data = @_ ? $_[0] : PDL->null;
1365             my $have_null = $data->isnull;
1366              
1367             ##-- guts: call rldvec()
1368             rldvec($counts, $elts->clump($#vdimsN),
1369             $have_null ? $data : $data->clump($#vdimsN));
1370              
1371             ##-- reshape output according to $elts
1372             if ($have_null) {
1373             my @dims = $data->dims;
1374             pop(@vdimsN);
1375             splice(@dims, 0, 1, @vdimsN);
1376             $data->reshape(@dims);
1377             }
1378             return $data;
1379             }
1380             EOF
1381              
1382             # the perl wrapper clump is now defined in Core.pm
1383             # this is just the low level interface
1384             pp_def('_clump_int',
1385             OtherPars => 'PDL_Indx n',
1386             P2Child => 1,
1387             RedoDims => pp_line_numbers(__LINE__, <<'EOF'),
1388             /* truncate overly long clumps to just clump existing dimensions */
1389             if($COMP(n) > $PDL(PARENT)->ndims)
1390             $COMP(n) = $PDL(PARENT)->ndims;
1391             if($COMP(n) < -1)
1392             $COMP(n) = $PDL(PARENT)->ndims + $COMP(n) + 1;
1393             PDL_Indx nrem = ($COMP(n) == -1 ? $PDL(PARENT)->broadcastids[0] : $COMP(n));
1394             $SETNDIMS($PDL(PARENT)->ndims - nrem + 1);
1395             PDL_Indx i, d1=1;
1396             for(i=0; i
1397             d1 *= $PDL(PARENT)->dims[i];
1398             }
1399             $PDL(CHILD)->dims[0] = d1;
1400             for(; i<$PDL(PARENT)->ndims; i++) {
1401             $PDL(CHILD)->dims[i-nrem+1] = $PDL(PARENT)->dims[i];
1402             }
1403             $SETDIMS();
1404             $SETDELTABROADCASTIDS(1-nrem);
1405             EOF
1406             EquivCPOffsCode => pp_line_numbers(__LINE__, <<'EOF'),
1407             PDL_Indx i;
1408             for(i=0; i<$PDL(CHILD)->nvals; i++) {
1409             $EQUIVCPOFFS(i,i);
1410             }
1411             EOF
1412             TwoWay => 1,
1413             Doc => 'internal',
1414             );
1415              
1416             pp_def('xchg',
1417             OtherPars => 'PDL_Indx n1; PDL_Indx n2;',
1418             TwoWay => 1,
1419             Lvalue => 1,
1420             P2Child => 1,
1421             AffinePriv => 1,
1422             EquivDimCheck =>pp_line_numbers(__LINE__, <<'EOF'),
1423             if ($COMP(n1) <0)
1424             $COMP(n1) += $PDL(PARENT)->broadcastids[0];
1425             if ($COMP(n2) <0)
1426             $COMP(n2) += $PDL(PARENT)->broadcastids[0];
1427             if (PDLMIN($COMP(n1),$COMP(n2)) <0 ||
1428             PDLMAX($COMP(n1),$COMP(n2)) >= $PDL(PARENT)->broadcastids[0])
1429             $CROAK("One of dims %"IND_FLAG", %"IND_FLAG" out of range: should be 0<=dim<%"IND_FLAG"",
1430             $COMP(n1),$COMP(n2),$PDL(PARENT)->broadcastids[0]);
1431             EOF
1432             EquivPDimExpr =>pp_line_numbers(__LINE__, <<'EOF'),
1433             (($CDIM == $COMP(n1)) ? $COMP(n2) : ($CDIM == $COMP(n2)) ? $COMP(n1) : $CDIM)
1434             EOF
1435             Doc => <<'EOD',
1436             =for ref
1437              
1438             exchange two dimensions
1439              
1440             Negative dimension indices count from the end.
1441              
1442             The command
1443              
1444             =for example
1445              
1446             $y = $x->xchg(2,3);
1447              
1448             creates C<$y> to be like C<$x> except that the dimensions 2 and 3
1449             are exchanged with each other i.e.
1450              
1451             $y->at(5,3,2,8) == $x->at(5,3,8,2)
1452              
1453             =cut
1454              
1455             EOD
1456             );
1457              
1458             pp_addpm(<<'EOD');
1459              
1460             =head2 reorder
1461              
1462             =for ref
1463              
1464             Re-orders the dimensions of a PDL based on the supplied list.
1465              
1466             Similar to the L method, this method re-orders the dimensions
1467             of a PDL. While the L method swaps the position of two dimensions,
1468             the reorder method can change the positions of many dimensions at
1469             once.
1470              
1471             =for usage
1472              
1473             # Completely reverse the dimension order of a 6-Dim array.
1474             $reOrderedPDL = $pdl->reorder(5,4,3,2,1,0);
1475              
1476             The argument to reorder is an array representing where the current dimensions
1477             should go in the new array. In the above usage, the argument to reorder
1478             C<(5,4,3,2,1,0)>
1479             indicates that the old dimensions (C<$pdl>'s dims) should be re-arranged to make the
1480             new pdl (C<$reOrderPDL>) according to the following:
1481              
1482             Old Position New Position
1483             ------------ ------------
1484             5 0
1485             4 1
1486             3 2
1487             2 3
1488             1 4
1489             0 5
1490              
1491             You do not need to specify all dimensions, only a complete set
1492             starting at position 0. (Extra dimensions are left where they are).
1493             This means, for example, that you can reorder() the X and Y dimensions of
1494             an image, and not care whether it is an RGB image with a third dimension running
1495             across color plane.
1496              
1497             =for example
1498              
1499             Example:
1500              
1501             pdl> $x = sequence(5,3,2); # Create a 3-d Array
1502             pdl> p $x
1503             [
1504             [
1505             [ 0 1 2 3 4]
1506             [ 5 6 7 8 9]
1507             [10 11 12 13 14]
1508             ]
1509             [
1510             [15 16 17 18 19]
1511             [20 21 22 23 24]
1512             [25 26 27 28 29]
1513             ]
1514             ]
1515             pdl> p $x->reorder(2,1,0); # Reverse the order of the 3-D PDL
1516             [
1517             [
1518             [ 0 15]
1519             [ 5 20]
1520             [10 25]
1521             ]
1522             [
1523             [ 1 16]
1524             [ 6 21]
1525             [11 26]
1526             ]
1527             [
1528             [ 2 17]
1529             [ 7 22]
1530             [12 27]
1531             ]
1532             [
1533             [ 3 18]
1534             [ 8 23]
1535             [13 28]
1536             ]
1537             [
1538             [ 4 19]
1539             [ 9 24]
1540             [14 29]
1541             ]
1542             ]
1543              
1544             The above is a simple example that could be duplicated by calling
1545             C<$x-Exchg(0,2)>, but it demonstrates the basic functionality of reorder.
1546              
1547             As this is an index function, any modifications to the
1548             result PDL will change the parent.
1549              
1550             =cut
1551              
1552             sub PDL::reorder :lvalue {
1553             my ($pdl,@newDimOrder) = @_;
1554              
1555             my $arrayMax = $#newDimOrder;
1556              
1557             #Error Checking:
1558             if( $pdl->getndims < scalar(@newDimOrder) ){
1559             my $errString = "PDL::reorder: Number of elements (".scalar(@newDimOrder).") in newDimOrder array exceeds\n";
1560             $errString .= "the number of dims in the supplied PDL (".$pdl->getndims.")";
1561             barf($errString);
1562             }
1563              
1564             # Check to make sure all the dims are within bounds
1565             for my $i(0..$#newDimOrder) {
1566             my $dim = $newDimOrder[$i];
1567             if($dim < 0 || $dim > $#newDimOrder) {
1568             my $errString = "PDL::reorder: Dim index $newDimOrder[$i] out of range in position $i\n(range is 0-$#newDimOrder)";
1569             barf($errString);
1570             }
1571             }
1572              
1573             # Checking that they are all present and also not duplicated is done by broadcast() [I think]
1574              
1575             # a quicker way to do the reorder
1576             return $pdl->broadcast(@newDimOrder)->unbroadcast(0);
1577             }
1578              
1579             EOD
1580              
1581             pp_def('mv',
1582             OtherPars => 'PDL_Indx n1; PDL_Indx n2;',
1583             TwoWay => 1,
1584             Lvalue => 1,
1585             P2Child => 1,
1586             AffinePriv => 1,
1587             CHeader => <<'EOF',
1588             #define EQUIVDIM(dima,dimb,cdim,inc) \
1589             ((cdim < PDLMIN(dima,dimb) || cdim > PDLMAX(dima,dimb)) ? \
1590             cdim : ((cdim == dimb) ? dima : cdim + inc))
1591             EOF
1592             EquivDimCheck =>pp_line_numbers(__LINE__, <<'EOF'),
1593             if ($COMP(n1) <0)
1594             $COMP(n1) += $PDL(PARENT)->broadcastids[0];
1595             if ($COMP(n2) <0)
1596             $COMP(n2) += $PDL(PARENT)->broadcastids[0];
1597             if (PDLMIN($COMP(n1),$COMP(n2)) <0 ||
1598             PDLMAX($COMP(n1),$COMP(n2)) >= $PDL(PARENT)->broadcastids[0])
1599             $CROAK("One of dims %"IND_FLAG", %"IND_FLAG" out of range: should be 0<=dim<%"IND_FLAG"",
1600             $COMP(n1),$COMP(n2),$PDL(PARENT)->broadcastids[0]);
1601             EOF
1602             EquivPDimExpr =>pp_line_numbers(__LINE__, <<'EOF'),
1603             (
1604             $COMP(n1) == $COMP(n2) ? $CDIM :
1605             $COMP(n1) < $COMP(n2) ? EQUIVDIM($COMP(n1),$COMP(n2),$CDIM,1) :
1606             EQUIVDIM($COMP(n1),$COMP(n2),$CDIM,-1)
1607             )
1608             EOF
1609             Doc => <<'EOD',
1610             =for ref
1611              
1612             move a dimension to another position
1613              
1614             The command
1615              
1616             =for example
1617              
1618             $y = $x->mv(4,1);
1619              
1620             creates C<$y> to be like C<$x> except that the dimension 4 is moved to the
1621             place 1, so:
1622              
1623             $y->at(1,2,3,4,5,6) == $x->at(1,5,2,3,4,6);
1624              
1625             The other dimensions are moved accordingly.
1626             Negative dimension indices count from the end.
1627             =cut
1628             EOD
1629             );
1630              
1631             pp_addpm(<<'EOD'
1632             =head2 using
1633              
1634             =for ref
1635              
1636             Returns list of columns requested
1637              
1638             =for usage
1639              
1640             line $pdl->using(1,2);
1641              
1642             Plot, as a line, column 1 of C<$pdl> vs. column 2
1643              
1644             =for example
1645              
1646             pdl> $pdl = rcols("file");
1647             pdl> line $pdl->using(1,2);
1648              
1649             =cut
1650              
1651             *using = \&PDL::using;
1652             sub PDL::using {
1653             my ($x,@ind)=@_;
1654             @ind = list $ind[0] if (blessed($ind[0]) && $ind[0]->isa('PDL'));
1655             foreach (@ind) {
1656             $_ = $x->slice("($_)");
1657             }
1658             @ind;
1659             }
1660              
1661             =head2 meshgrid
1662              
1663             =for ref
1664              
1665             Returns list of given 1-D vectors, but each expanded to match dims using
1666             L.
1667              
1668             =for usage
1669              
1670             meshgrid($vec1, $vec2, $vec3);
1671              
1672             =for example
1673              
1674             print map $_->info, meshgrid(xvals(3), xvals(4), xvals(2));
1675             # PDL: Double D [3,4,2] PDL: Double D [3,4,2] PDL: Double D [3,4,2]
1676              
1677             =cut
1678              
1679             *meshgrid = \&PDL::meshgrid;
1680             sub PDL::meshgrid {
1681             barf "meshgrid: only 1-dimensional inputs" if grep $_->ndims != 1, @_;
1682             return @_ if @_ == 1;
1683             my @dims = map $_->dims, @_;
1684             my @out;
1685             for my $ind (0..$#_) {
1686             push @out, $_[$ind]->slice(join ',', map $_==$ind ? '' : "*$dims[$_]", 0..$#_);
1687             }
1688             @out;
1689             }
1690             EOD
1691             );
1692              
1693             pp_add_exported('', 'using meshgrid');
1694              
1695             pp_def('lags',
1696             Doc => <<'EOD',
1697             =for ref
1698              
1699             Returns an ndarray of lags to parent.
1700              
1701             Usage:
1702              
1703             I.e. if C<$x> contains
1704              
1705             [0,1,2,3,4,5,6,7]
1706              
1707             then
1708              
1709             =for example
1710              
1711             $y = $x->lags(0,2,2);
1712              
1713             is a (6,2) matrix
1714              
1715             [2,3,4,5,6,7]
1716             [0,1,2,3,4,5]
1717              
1718             This order of returned indices is kept because the function is
1719             called "lags" i.e. the nth lag is n steps behind the original.
1720              
1721             C<$step> and C<$nlags> must be positive. C<$nthdim> can be
1722             negative and will then be counted from the last dim backwards
1723             in the usual way (-1 = last dim).
1724             =cut
1725             EOD
1726             P2Child => 1,
1727             TwoWay => 1,
1728             AffinePriv => 1,
1729             OtherPars => join('', map "PDL_Indx $_;", qw(nthdim step nlags)),
1730             RedoDims => pp_line_numbers(__LINE__, <<'EOF'),
1731             PDL_Indx i;
1732             if ($COMP(nthdim) < 0) /* the usual conventions */
1733             $COMP(nthdim) += $PDL(PARENT)->ndims;
1734             if ($COMP(nthdim) < 0 || $COMP(nthdim) >= $PDL(PARENT)->ndims)
1735             $CROAK("dim out of range");
1736             if ($COMP(nlags) < 1)
1737             $CROAK("number of lags must be positive");
1738             if ($COMP(step) < 1)
1739             $CROAK("step must be positive");
1740             $PRIV(offs) = 0;
1741             $SETNDIMS($PDL(PARENT)->ndims+1);
1742             $DOPRIVALLOC();
1743             for(i=0; i<$COMP(nthdim); i++) {
1744             $PDL(CHILD)->dims[i] = $PDL(PARENT)->dims[i];
1745             $PRIV(incs)[i] = $PDL(PARENT)->dimincs[i];
1746             }
1747             $PDL(CHILD)->dims[i] = $PDL(PARENT)->dims[i] - $COMP(step) * ($COMP(nlags)-1);
1748             if ($PDL(CHILD)->dims[i] < 1)
1749             $CROAK("product of step size and number of lags too large");
1750             $PDL(CHILD)->dims[i+1] = $COMP(nlags);
1751             $PRIV(incs)[i] = ($PDL(PARENT)->dimincs[i]);
1752             $PRIV(incs)[i+1] = - $PDL(PARENT)->dimincs[i] * $COMP(step);
1753             $PRIV(offs) += ($PDL(CHILD)->dims[i+1] - 1) * (-$PRIV(incs)[i+1]);
1754             i++;
1755             for(; i<$PDL(PARENT)->ndims; i++) {
1756             $PDL(CHILD)->dims[i+1] = $PDL(PARENT)->dims[i];
1757             $PRIV(incs)[i+1] = $PDL(PARENT)->dimincs[i];
1758             }
1759             $SETDIMS();
1760             EOF
1761             );
1762              
1763             pp_def('splitdim',
1764             Doc => <<'EOD',
1765             =for ref
1766              
1767             Splits a dimension in the parent ndarray (opposite of L).
1768             As of 2.076, throws exception if non-divisible C given, and can
1769             give negative C which then counts backwards.
1770              
1771             =for example
1772              
1773             After
1774              
1775             $y = $x->splitdim(2,3);
1776              
1777             the expression
1778              
1779             $y->at(6,4,m,n,3,6) == $x->at(6,4,m+3*n)
1780              
1781             is always true (C has to be less than 3).
1782             =cut
1783             EOD
1784             P2Child => 1,
1785             TwoWay => 1,
1786             OtherPars => join('', map "PDL_Indx $_;", qw(nthdim nsp)),
1787             AffinePriv => 1,
1788             RedoDims => pp_line_numbers(__LINE__, <<'EOF'),
1789             PDL_Indx i = $COMP(nthdim);
1790             PDL_Indx nsp = $COMP(nsp);
1791             if(nsp == 0) {$CROAK("Cannot split to 0\n");}
1792             if (i < 0)
1793             i = $COMP(nthdim) += $PDL(PARENT)->ndims;
1794             if (i < 0 || i >= $PDL(PARENT)->ndims)
1795             $CROAK("nthdim %"IND_FLAG" after adjusting for negative must not be negative or greater or equal to number of dims %"IND_FLAG"\n",
1796             i, $PDL(PARENT)->ndims);
1797             if (nsp > $PDL(PARENT)->dims[i])
1798             $CROAK("nsp %"IND_FLAG" cannot be greater than dim %"IND_FLAG"\n",
1799             nsp, $PDL(PARENT)->dims[i]);
1800             if (($PDL(PARENT)->dims[i] % nsp) != 0)
1801             $CROAK("nsp %"IND_FLAG" non-divisible into dim %"IND_FLAG"\n",
1802             nsp, $PDL(PARENT)->dims[i]);
1803             $PRIV(offs) = 0;
1804             $SETNDIMS($PDL(PARENT)->ndims+1);
1805             $DOPRIVALLOC();
1806             for(i=0; i<$COMP(nthdim); i++) {
1807             $PDL(CHILD)->dims[i] = $PDL(PARENT)->dims[i];
1808             $PRIV(incs)[i] = $PDL(PARENT)->dimincs[i];
1809             }
1810             $PDL(CHILD)->dims[i] = $COMP(nsp);
1811             $PDL(CHILD)->dims[i+1] = $PDL(PARENT)->dims[i] / $COMP(nsp);
1812             $PRIV(incs)[i] = $PDL(PARENT)->dimincs[i];
1813             $PRIV(incs)[i+1] = $PDL(PARENT)->dimincs[i] * $COMP(nsp);
1814             i++;
1815             for(; i<$PDL(PARENT)->ndims; i++) {
1816             $PDL(CHILD)->dims[i+1] = $PDL(PARENT)->dims[i];
1817             $PRIV(incs)[i+1] = $PDL(PARENT)->dimincs[i];
1818             }
1819             $SETDIMS();
1820             EOF
1821             );
1822              
1823             my $rotate_code = pp_line_numbers(__LINE__, <<'EOF');
1824             PDL_Indx n_size = $SIZE(n), j = $shift() % n_size;
1825             if (j < 0) j += n_size;
1826             loop (n) %{
1827             if (j == n_size) j = 0;
1828             EOF
1829             pp_def('rotate',
1830             GenericTypes => [ppdefs_all],
1831             Doc => 'Shift vector elements along with wrap.',
1832             Pars=>'x(n); indx shift(); [oca]y(n)',
1833             DefaultFlow => 1,
1834             TwoWay => 1,
1835             RedoDimsCode=>'if ($SIZE(n) == 0) $CROAK("can not shift zero size ndarray");',
1836             Code=>$rotate_code.'
1837             $y(n=>j++) = $x();
1838             %}',
1839             BackCode=>$rotate_code.'
1840             $x() = $y(n=>j++);
1841             %}
1842             '
1843             );
1844              
1845             # This is a bit tricky. Hope I haven't missed any cases.
1846             pp_def('broadcastI',
1847             Doc => <<'EOD',
1848             =for ref
1849              
1850             internal
1851              
1852             Put some dimensions to a broadcastid.
1853              
1854             =for example
1855              
1856             $y = $x->broadcastI(0,1,5); # broadcast over dims 1,5 in id 1
1857             EOD
1858             P2Child => 1,
1859             TwoWay => 1,
1860             Lvalue => 1,
1861             AffinePriv => 1,
1862             OtherPars => "PDL_Indx id; PDL_Indx whichdims[]",
1863             Comp => 'PDL_Indx nrealwhichdims',
1864             MakeComp => pp_line_numbers(__LINE__, <<'EOF'),
1865             PDL_Indx i,j;
1866             $COMP(nrealwhichdims) = 0;
1867             for(i=0; i<$COMP(whichdims_count); i++) {
1868             for(j=i+1; j<$COMP(whichdims_count); j++)
1869             if($COMP(whichdims)[i] == $COMP(whichdims)[j] &&
1870             $COMP(whichdims)[i] != -1) {
1871             $CROAK("duplicate arg %"IND_FLAG" %"IND_FLAG" %"IND_FLAG"",
1872             i,j,$COMP(whichdims)[i]);
1873             }
1874             if($COMP(whichdims)[i] != -1) {
1875             $COMP(nrealwhichdims) ++;
1876             }
1877             }
1878             EOF
1879             RedoDims => pp_line_numbers(__LINE__, <<'EOF'),
1880             PDL_Indx nthc,i,j,flag;
1881             $SETNDIMS($PDL(PARENT)->ndims);
1882             $DOPRIVALLOC();
1883             $PRIV(offs) = 0;
1884             nthc=0;
1885             for(i=0; i<$PDL(PARENT)->ndims; i++) {
1886             flag=0;
1887             if($PDL(PARENT)->nbroadcastids > $COMP(id) && $COMP(id) >= 0 &&
1888             i == $PDL(PARENT)->broadcastids[$COMP(id])) {
1889             nthc += $COMP(whichdims_count);
1890             }
1891             for(j=0; j<$COMP(whichdims_count); j++) {
1892             if($COMP(whichdims)[j] == i) {flag=1; break;}
1893             }
1894             if(flag) {
1895             continue;
1896             }
1897             $PDL(CHILD)->dims[nthc] = $PDL(PARENT)->dims[i];
1898             $PRIV(incs)[nthc] = $PDL(PARENT)->dimincs[i];
1899             nthc++;
1900             }
1901             for(i=0; i<$COMP(whichdims_count); i++) {
1902             PDL_Indx cdim,pdim;
1903             cdim = i +
1904             ($PDL(PARENT)->nbroadcastids > $COMP(id) && $COMP(id) >= 0?
1905             $PDL(PARENT)->broadcastids[$COMP(id)] : $PDL(PARENT)->ndims)
1906             - $COMP(nrealwhichdims);
1907             pdim = $COMP(whichdims)[i];
1908             if(pdim == -1) {
1909             $PDL(CHILD)->dims[cdim] = 1;
1910             $PRIV(incs)[cdim] = 0;
1911             } else {
1912             $PDL(CHILD)->dims[cdim] = $PDL(PARENT)->dims[pdim];
1913             $PRIV(incs)[cdim] = $PDL(PARENT)->dimincs[pdim];
1914             }
1915             }
1916             $SETDIMS();
1917             PDL_RETERROR(PDL_err, PDL->reallocbroadcastids($PDL(CHILD),
1918             PDLMAX($COMP(id)+1, $PDL(PARENT)->nbroadcastids)));
1919             for(i=0; i<$PDL(CHILD)->nbroadcastids-1; i++) {
1920             $PDL(CHILD)->broadcastids[i] =
1921             ($PDL(PARENT)->nbroadcastids > i ?
1922             $PDL(PARENT)->broadcastids[i] : $PDL(PARENT)->ndims) +
1923             (i <= $COMP(id) ? - $COMP(nrealwhichdims) :
1924             $COMP(whichdims_count) - $COMP(nrealwhichdims));
1925             }
1926             $PDL(CHILD)->broadcastids[$PDL(CHILD)->nbroadcastids-1] = $PDL(CHILD)->ndims;
1927             EOF
1928             );
1929              
1930             pp_def('unbroadcast',
1931             Doc => <<'EOD',
1932             =for ref
1933              
1934             All broadcasted dimensions are made real again.
1935              
1936             See [TBD Doc] for details and examples.
1937             =cut
1938             EOD
1939             P2Child => 1,
1940             TwoWay => 1,
1941             Lvalue => 1,
1942             AffinePriv => 1,
1943             OtherPars => 'PDL_Indx atind;',
1944             RedoDims => pp_line_numbers(__LINE__, <<'EOF'),
1945             PDL_Indx i;
1946             $SETNDIMS($PDL(PARENT)->ndims);
1947             $DOPRIVALLOC();
1948             $PRIV(offs) = 0;
1949             for(i=0; i<$PDL(PARENT)->ndims; i++) {
1950             PDL_Indx corc;
1951             if(i<$COMP(atind)) {
1952             corc = i;
1953             } else if(i < $PDL(PARENT)->broadcastids[0]) {
1954             corc = i + $PDL(PARENT)->ndims-$PDL(PARENT)->broadcastids[0];
1955             } else {
1956             corc = i - $PDL(PARENT)->broadcastids[0] + $COMP(atind);
1957             }
1958             $PDL(CHILD)->dims[corc] = $PDL(PARENT)->dims[i];
1959             $PRIV(incs)[corc] = $PDL(PARENT)->dimincs[i];
1960             }
1961             $SETDIMS();
1962             EOF
1963             );
1964              
1965              
1966             pp_add_exported('', 'dice dice_axis');
1967             pp_addpm(<<'EOD');
1968              
1969             =head2 dice
1970              
1971             =for ref
1972              
1973             Dice rows/columns/planes out of a PDL using indexes for
1974             each dimension.
1975              
1976             This function can be used to extract irregular subsets
1977             along many dimension of a PDL, e.g. only certain rows in an image,
1978             or planes in a cube. This can of course be done with
1979             the usual dimension tricks but this saves having to
1980             figure it out each time!
1981              
1982             This method is similar in functionality to the L
1983             method, but L requires that contiguous ranges or ranges
1984             with constant offset be extracted. ( i.e. L requires
1985             ranges of the form C<1,2,3,4,5> or C<2,4,6,8,10>). Because of this
1986             restriction, L is more memory efficient and slightly faster
1987             than dice
1988              
1989             =for usage
1990              
1991             $slice = $data->dice([0,2,6],[2,1,6]); # Dicing a 2-D array
1992              
1993             The arguments to dice are arrays (or 1D PDLs) for each dimension
1994             in the PDL. These arrays are used as indexes to which rows/columns/cubes,etc
1995             to dice-out (or extract) from the C<$data> PDL.
1996              
1997             Use C to select all indices along a given dimension (compare also
1998             L). As usual (in slicing methods) trailing
1999             dimensions can be omitted implying C'es for those.
2000              
2001             =for example
2002              
2003             pdl> $x = sequence(10,4)
2004             pdl> p $x
2005             [
2006             [ 0 1 2 3 4 5 6 7 8 9]
2007             [10 11 12 13 14 15 16 17 18 19]
2008             [20 21 22 23 24 25 26 27 28 29]
2009             [30 31 32 33 34 35 36 37 38 39]
2010             ]
2011             pdl> p $x->dice([1,2],[0,3]) # Select columns 1,2 and rows 0,3
2012             [
2013             [ 1 2]
2014             [31 32]
2015             ]
2016             pdl> p $x->dice(X,[0,3])
2017             [
2018             [ 0 1 2 3 4 5 6 7 8 9]
2019             [30 31 32 33 34 35 36 37 38 39]
2020             ]
2021             pdl> p $x->dice([0,2,5])
2022             [
2023             [ 0 2 5]
2024             [10 12 15]
2025             [20 22 25]
2026             [30 32 35]
2027             ]
2028              
2029             As this is an index function, any modifications to the
2030             slice will change the parent (use the C<.=> operator).
2031              
2032             =cut
2033              
2034             sub PDL::dice :lvalue {
2035              
2036             my $self = shift;
2037             my @dim_indexes = @_; # array of dimension indexes
2038              
2039             # Check that the number of dim indexes <=
2040             # number of dimensions in the PDL
2041             my $no_indexes = scalar(@dim_indexes);
2042             my $noDims = $self->getndims;
2043             barf("PDL::dice: Number of index arrays ($no_indexes) not equal to the dimensions of the PDL ($noDims")
2044             if $no_indexes > $noDims;
2045             my $index;
2046             my $pdlIndex;
2047             my $outputPDL=$self;
2048             my $indexNo = 0;
2049              
2050             # Go thru each index array and dice the input PDL:
2051             foreach $index(@dim_indexes){
2052             $outputPDL = $outputPDL->dice_axis($indexNo,$index)
2053             unless !ref $index && $index eq 'X';
2054              
2055             $indexNo++;
2056             }
2057              
2058             return $outputPDL;
2059             }
2060             *dice = \&PDL::dice;
2061              
2062             =head2 dice_axis
2063              
2064             =for ref
2065              
2066             Dice rows/columns/planes from a single PDL axis (dimension)
2067             using index along a specified axis
2068              
2069             This function can be used to extract irregular subsets
2070             along any dimension, e.g. only certain rows in an image,
2071             or planes in a cube. This can of course be done with
2072             the usual dimension tricks but this saves having to
2073             figure it out each time!
2074              
2075             =for usage
2076              
2077             $slice = $data->dice_axis($axis,$index);
2078              
2079             =for example
2080              
2081             pdl> $x = sequence(10,4)
2082             pdl> $idx = pdl(1,2)
2083             pdl> p $x->dice_axis(0,$idx) # Select columns
2084             [
2085             [ 1 2]
2086             [11 12]
2087             [21 22]
2088             [31 32]
2089             ]
2090             pdl> $t = $x->dice_axis(1,$idx) # Select rows
2091             pdl> $t.=0
2092             pdl> p $x
2093             [
2094             [ 0 1 2 3 4 5 6 7 8 9]
2095             [ 0 0 0 0 0 0 0 0 0 0]
2096             [ 0 0 0 0 0 0 0 0 0 0]
2097             [30 31 32 33 34 35 36 37 38 39]
2098             ]
2099              
2100             The trick to using this is that the index selects
2101             elements along the dimensions specified, so if you
2102             have a 2D image C will select certain C values
2103             - i.e. extract columns
2104              
2105             As this is an index function, any modifications to the
2106             slice will change the parent.
2107              
2108             =cut
2109              
2110             sub PDL::dice_axis :lvalue {
2111             my($self,$axis,$idx) = @_;
2112             my $ix = PDL->topdl($idx);
2113             barf("dice_axis: index must be <=1D") if $ix->getndims > 1;
2114             return $self->mv($axis,0)->index1d($ix)->mv(0,$axis);
2115             }
2116             *dice_axis = \&PDL::dice_axis;
2117             EOD
2118              
2119             ##############################
2120             # 'slice' is now implemented as a small Perl wrapper around
2121             # a PP call. This permits unification of the former slice,
2122             # and dice into a single call. At the moment, dicing
2123             # is implemented a bit kludgily (it is detected in the Perl
2124             # front-end), but it is serviceable.
2125             # --CED 12-Sep-2013
2126             pp_def('slice',
2127             Doc => <<'EOD-slice',
2128             =for example
2129              
2130             $slice = $data->slice([2,3],'x',[2,2,0],"-1:1:-1", "*3");
2131              
2132             =for ref
2133              
2134             Extract rectangular slices of an ndarray, from a string specifier,
2135             an array ref specifier, or a combination.
2136              
2137             C is the main method for extracting regions of PDLs and
2138             manipulating their dimensionality. You can call it directly or
2139             via the L source prefilter that extends
2140             Perl syntax to include array slicing.
2141              
2142             C can extract regions along each dimension of a source PDL,
2143             subsample or reverse those regions, dice each dimension by selecting a
2144             list of locations along it, or basic PDL indexing routine. The
2145             selected subfield remains connected to the original PDL via dataflow.
2146             In most cases this neither allocates more memory nor slows down
2147             subsequent operations on either of the two connected PDLs.
2148              
2149             You pass in a list of arguments. Each term in the list controls
2150             the disposition of one axis of the source PDL and/or returned PDL.
2151             Each term can be a string-format cut specifier, a list ref that
2152             gives the same information without recourse to string manipulation,
2153             or a PDL with up to 1 dimension giving indices along that axis that
2154             should be selected.
2155              
2156             If you want to pass in a single string specifier for the entire
2157             operation, you can pass in a comma-delimited list as the first
2158             argument. C detects this condition and splits the string
2159             into a regular argument list. This calling style is fully
2160             backwards compatible with C calls from before PDL 2.006.
2161              
2162             B
2163              
2164             If a particular argument to C is a string, it is parsed as a
2165             selection, an affine slice, or a dummy dimension depending on the
2166             form. Leading or trailing whitespace in any part of each specifier is
2167             ignored (though it is not ignored within numbers).
2168              
2169             =over 3
2170              
2171             =item C<< '' >>, C<< : >>, or C<< X >> -- keep
2172              
2173             The empty string, C<:>, or C cause the entire corresponding
2174             dimension to be kept unchanged.
2175              
2176              
2177             =item C<< >> -- selection
2178              
2179             A single number alone causes a single index to be selected from the
2180             corresponding dimension. The dimension is kept (and reduced to size
2181             1) in the output.
2182              
2183             =item C<< () >> -- selection and collapse
2184              
2185             A single number in parenthesis causes a single index to be selected
2186             from the corresponding dimension. The dimension is discarded
2187             (completely eliminated) in the output.
2188              
2189             =item C<< : >> -- select an inclusive range
2190              
2191             Two numbers separated by a colon selects a range of values from the
2192             corresponding axis, e.g. C<< 3:4 >> selects elements 3 and 4 along the
2193             corresponding axis, and reduces that axis to size 2 in the output.
2194             Both numbers are regularized so that you can address the last element
2195             of the axis with an index of C< -1 >. If, after regularization, the
2196             two numbers are the same, then exactly one element gets selected (just
2197             like the C<< >> case). If, after regulariation, the second number
2198             is lower than the first, then the resulting slice counts down rather
2199             than up -- e.g. C<-1:0> will return the entire axis, in reversed
2200             order.
2201              
2202             =item C<< :: >> -- select a range with explicit step
2203              
2204             If you include a third parameter, it is the stride of the extracted
2205             range. For example, C<< 0:-1:2 >> will sample every other element
2206             across the complete dimension. Specifying a stride of 1 prevents
2207             autoreversal -- so to ensure that your slice is *always* forward
2208             you can specify, e.g., C<< 2:$n:1 >>. In that case, an "impossible"
2209             slice gets an Empty PDL (with 0 elements along the corresponding
2210             dimension), so you can generate an Empty PDL with a slice of the
2211             form C<< 2:1:1 >>.
2212              
2213             =item C<< * >> -- insert a dummy dimension
2214              
2215             Dummy dimensions aren't present in the original source and are
2216             "mocked up" to match dimensional slots, by repeating the data
2217             in the original PDL some number of times. An asterisk followed
2218             by a number produces a dummy dimension in the output, for
2219             example C<< *2 >> will generate a dimension of size 2 at
2220             the corresponding location in the output dim list. Omitting
2221             the number (and using just an asterisk) inserts a dummy dimension
2222             of size 1.
2223              
2224             =back
2225              
2226             B
2227              
2228             If you feed in an ARRAY ref as a slice term, then it can have
2229             0-3 elements. The first element is the start of the slice along
2230             the corresponding dim; the second is the end; and the third is
2231             the stepsize. Different combinations of inputs give the same
2232             flexibility as the string syntax.
2233              
2234             =over 3
2235              
2236             =item C<< [] >> - keep dim intact
2237              
2238             An empty ARRAY ref keeps the entire corresponding dim
2239              
2240             =item C<< [ 'X' ] >> - keep dim intact
2241              
2242             =item C<< [ '*',$n ] >> - generate a dummy dim of size $n
2243              
2244             If $n is missing, you get a dummy dim of size 1.
2245              
2246             =item C<< [ $dex, 0, 0 ] >> - collapse and discard dim
2247              
2248             C<$dex> must be a single value. It is used to index
2249             the source, and the corresponding dimension is discarded.
2250              
2251             =item C<< [ $start, $end ] >> - collect inclusive slice
2252              
2253             In the simple two-number case, you get a slice that runs
2254             up or down (as appropriate) to connect $start and $end.
2255              
2256             =item C<< [ $start, $end, $inc ] >> - collect inclusive slice
2257              
2258             The three-number case works exactly like the three-number
2259             string case above.
2260              
2261             =back
2262              
2263             B
2264              
2265             If you pass in a 0- or 1-D PDL as a slicing argument, the
2266             corresponding dimension is "diced" -- you get one position
2267             along the corresponding dim, per element of the indexing PDL,
2268             e.g. C<< $x->slice( pdl(3,4,9)) >> gives you elements 3, 4, and
2269             9 along the 0 dim of C<< $x >>.
2270              
2271             Because dicing is not an affine transformation, it is slower than
2272             direct slicing even though the syntax is convenient.
2273              
2274             =for example
2275              
2276             $x->slice('1:3'); # return the second to fourth elements of $x
2277             $x->slice('3:1'); # reverse the above
2278             $x->slice('-2:1'); # return last-but-one to second elements of $x
2279              
2280             $x->slice([1,3]); # Same as above three calls, but using array ref syntax
2281             $x->slice([3,1]);
2282             $x->slice([-2,1]);
2283             EOD-slice
2284             PMCode => pp_line_numbers(__LINE__, <<'EOD-slice'),
2285             sub PDL::slice :lvalue {
2286             my ($source, @others) = @_;
2287             for my $i(0..$#others) {
2288             my $idx = $others[$i];
2289             if (ref $idx eq 'ARRAY') {
2290             my @arr = map UNIVERSAL::isa($_, 'PDL') ? $_->flat->at(0) : $_, @{$others[$i]};
2291             $others[$i] = \@arr;
2292             next;
2293             }
2294             next if !( blessed($idx) && $idx->isa('PDL') );
2295             # Deal with dicing. This is lame and slow compared to the
2296             # faster slicing, but works okay. We loop over each argument,
2297             # and if it's a PDL we dispatch it in the most straightforward
2298             # way. Single-element and zero-element PDLs are trivial and get
2299             # converted into slices for faster handling later.
2300             barf("slice: dicing parameters must be at most 1D (arg $i)\n")
2301             if $idx->ndims > 1;
2302             my $nlm = $idx->nelem;
2303             if($nlm > 1) {
2304             #### More than one element - we have to dice (darn it).
2305             $source = $source->mv($i,0)->index1d($idx)->mv(0,$i);
2306             $others[$i] = '';
2307             }
2308             elsif($nlm) {
2309             #### One element - convert to a regular slice.
2310             $others[$i] = $idx->flat->at(0);
2311             }
2312             else {
2313             #### Zero elements -- force an extended empty.
2314             $others[$i] = "1:0:1";
2315             }
2316             }
2317             PDL::_slice_int($source,my $o=$source->initialize,\@others);
2318             $o;
2319             }
2320             EOD-slice
2321             P2Child => 1,
2322             OtherPars => 'pdl_slice_args *arglist;',
2323             #
2324             # Comp stash definitions:
2325             # nargs - number of args in original call
2326             # odim[] - maps argno to output dim (or -1 for squished dims)
2327             # idim[] - maps argno to input dim (or -1 for dummy dims)
2328             # odim_top - one more than the highest odim encountered
2329             # idim_top - one more than the highest idim encountered
2330             # start[] - maps argno to start index of slice range (inclusive)
2331             # inc[] - maps argno to increment of slice range
2332             # end[] - maps argno to end index of slice range (inclusive)
2333             #
2334             Comp => 'PDL_Indx nargs;
2335             PDL_Indx odim[$COMP(nargs)];
2336             PDL_Indx idim[$COMP(nargs)];
2337             PDL_Indx idim_top;
2338             PDL_Indx odim_top;
2339             PDL_Indx start[$COMP(nargs)];
2340             PDL_Indx inc[$COMP(nargs)];
2341             PDL_Indx end[$COMP(nargs)];
2342             ',
2343             AffinePriv => 1,
2344             TwoWay => 1,
2345             MakeComp => pp_line_numbers(__LINE__, <<'SLICE-MC'),
2346             PDL_Indx nargs = 0;
2347             pdl_slice_args *argsptr = arglist;
2348             while (argsptr) nargs++, argsptr = argsptr->next;
2349             $COMP(nargs) = nargs;
2350             $DOCOMPALLOC();
2351             PDL_Indx i, idim, odim;
2352             argsptr = arglist;
2353             for(odim=idim=i=0; i
2354             /* Copy parsed values into the limits */
2355             $COMP(start)[i] = argsptr->start;
2356             $COMP(end)[i] = argsptr->end;
2357             $COMP(inc)[i] = argsptr->inc;
2358             /* Deal with dimensions */
2359             $COMP(odim)[i] = argsptr->squish ? -1 : odim++;
2360             $COMP(idim)[i] = argsptr->dummy ? -1 : idim++;
2361             argsptr = argsptr->next;
2362             } /* end of arg-parsing loop */
2363             $COMP(idim_top) = idim;
2364             $COMP(odim_top) = odim;
2365             SLICE-MC
2366             RedoDims => pp_line_numbers(__LINE__, <<'EOF'),
2367             PDL_Indx i;
2368             PDL_Indx o_ndims_extra = PDLMAX(0, $PDL(PARENT)->ndims - $COMP(idim_top));
2369              
2370             /* slurped dims from the arg parsing, plus any extra broadcast dims */
2371             $SETNDIMS( $COMP(odim_top) + o_ndims_extra );
2372             $DOPRIVALLOC();
2373             $PRIV(offs) = 0; /* Offset vector to start of slice */
2374              
2375             for(i=0; i<$COMP(nargs); i++) {
2376             /** Belt-and-suspenders **/
2377             if (($COMP(idim)[i] < 0) && ($COMP(odim)[i] < 0))
2378             $CROAK("Hmmm, both dummy and squished -- this can never happen. I quit.");
2379              
2380             /** First handle dummy dims since there's no input from the parent **/
2381             if ($COMP(idim)[i] < 0) {
2382             /* dummy dim - offset or diminc. */
2383             $PDL(CHILD)->dims[ $COMP(odim)[i] ] = $COMP(end)[i] - $COMP(start)[i] + 1;
2384             $PRIV(incs)[ $COMP(odim)[i] ] = 0;
2385             } else {
2386             /** This is not a dummy dim -- deal with a regular slice along it. **/
2387             /** Get parent dim size for this idim, and/or allow permissive slicing **/
2388             PDL_Indx pdsize = $COMP(idim)[i] < $PDL(PARENT)->ndims ?
2389             $PDL(PARENT)->dims[$COMP(idim)[i]] : 1;
2390             PDL_Indx start = $COMP(start)[i];
2391             PDL_Indx end = $COMP(end)[i];
2392             if (
2393             /** Trap special case: full slices of an empty dim are empty **/
2394             (pdsize==0 && start==0 && end==-1 && ($COMP(inc)[i] == 0 || $COMP(inc)[i] == 1))
2395             ||
2396             /* the values given when PDL::slice gets empty ndarray for index */
2397             (start==1 && end==0 && $COMP(inc)[i] == 1)
2398             ) {
2399             $PDL(CHILD)->dims[$COMP(odim)[i]] = 0;
2400             $PRIV(incs)[$COMP(odim)[i]] = 0;
2401             } else {
2402             /** Regularize and bounds-check the start location **/
2403             if (start < 0)
2404             start += pdsize;
2405             if (start < 0 || start >= pdsize) {
2406             if (i >= $PDL(PARENT)->ndims) {
2407             $CROAK("slice has too many dims (indexes dim %"IND_FLAG"; highest is %"IND_FLAG")",i,$PDL(PARENT)->ndims-1);
2408             } else {
2409             $CROAK("slice starts out of bounds in pos %"IND_FLAG" (start is %"IND_FLAG"; end is %"IND_FLAG"; inc is %"IND_FLAG"; source dim %"IND_FLAG" runs 0 to %"IND_FLAG")",i,start,end,$COMP(inc)[i],$COMP(idim)[i],pdsize-1);
2410             }
2411             }
2412             if ($COMP(odim)[i] < 0) {
2413             /* squished dim - just update the offset. */
2414             /* start is always defined and regularized if we are here here, since */
2415             /* both idim[i] and odim[i] can't be <0 */
2416             $PRIV(offs) += start * $PDL(PARENT)->dimincs[ $COMP(idim)[i] ];
2417             } else /* normal operation */ {
2418             /** Regularize and bounds-check the end location **/
2419             if (end<0) end += pdsize;
2420             if (end < 0 || end >= pdsize)
2421             $CROAK("slice ends out of bounds in pos %"IND_FLAG" (end is %"IND_FLAG"; source dim %"IND_FLAG" runs 0 to %"IND_FLAG")",i,end,$COMP(idim)[i],pdsize-1);
2422             /* regularize inc */
2423             PDL_Indx inc = $COMP(inc)[i];
2424             if (!inc)
2425             inc = (start <= end) ? 1 : -1;
2426             $PDL(CHILD)->dims[ $COMP(odim)[i] ] = PDLMAX(0, (end - start + inc) / inc);
2427             $PRIV(incs)[ $COMP(odim)[i] ] = $PDL(PARENT)->dimincs[ $COMP(idim)[i] ] * inc;
2428             $PRIV(offs) += start * $PDL(PARENT)->dimincs[ $COMP(idim)[i] ];
2429             } /* end of normal slice case */
2430             } /* end of trapped strange slice case */
2431             } /* end of non-dummy slice case */
2432             } /* end of nargs loop */
2433              
2434             /* Now fill in broadcast dimensions as needed. idim and odim persist from the parsing loop */
2435             /* up above. */
2436             for(i=0; i
2437             $PDL(CHILD)->dims[ $COMP(odim_top) + i ] = $PDL(PARENT)->dims[ $COMP(idim_top) + i ];
2438             $PRIV(incs)[ $COMP(odim_top) + i ] = $PDL(PARENT)->dimincs[ $COMP(idim_top) + i ];
2439             }
2440              
2441             $SETDIMS();
2442             EOF
2443             );
2444              
2445             pp_def('diagonal',
2446             P2Child => 1,
2447             TwoWay => 1,
2448             AffinePriv => 1,
2449             OtherPars => 'PDL_Indx whichdims[]',
2450             MakeComp => pp_line_numbers(__LINE__-1, '
2451 348 100       602 if ($COMP(whichdims_count) < 1)
2452 70         126 $CROAK("must have at least 1 dimension");
2453 348         2578 qsort($COMP(whichdims), $COMP(whichdims_count), sizeof(PDL_Indx),
2454             cmp_pdll);
2455             '),
2456             CHeader => <<'END',
2457             static int cmp_pdll(const void *a_,const void *b_) {
2458             PDL_Indx *a = (PDL_Indx *)a_; PDL_Indx *b=(PDL_Indx *)b_;
2459             if(*a>*b) return 1;
2460             else if(*a==*b) return 0;
2461             else return -1;
2462             }
2463             END
2464             RedoDims => pp_line_numbers(__LINE__-1, '
2465 348         334 PDL_Indx nthp,nthc,nthd, cd = $COMP(whichdims)[0];
2466 348 100       2153 $SETNDIMS($PDL(PARENT)->ndims-$COMP(whichdims_count)+1);
2467 348         3848 $DOPRIVALLOC();
2468             $PRIV(offs) = 0;
2469 348         341 if ($COMP(whichdims)[$COMP(whichdims_count)-1] >= $PDL(PARENT)->ndims ||
2470 348 100       146 $COMP(whichdims)[0] < 0)
2471 348 100       509 $CROAK("dim out of range");
2472 70         436 nthd=0; nthc=0;
2473 348         149 for(nthp=0; nthp<$PDL(PARENT)->ndims; nthp++)
2474 915 100       50670 if (nthd < $COMP(whichdims_count) &&
2475 637 100       545 nthp == $COMP(whichdims)[nthd]) {
2476 626 100       141 if (!nthd) {
2477 626 100       187438 $PDL(CHILD)->dims[cd] = $PDL(PARENT)->dims[cd];
2478 301         1554 nthc++;
2479 301         64 $PRIV(incs)[cd] = 0;
2480 366         827 }
2481             if (nthd && $COMP(whichdims)[nthd] ==
2482 644 100       814 $COMP(whichdims)[nthd-1])
2483 366 100       420 $CROAK("dims must be unique");
2484 88         279 nthd++; /* advance pointer into whichdims */
2485 562         30 if($PDL(CHILD)->dims[cd] !=
2486 562         66 $PDL(PARENT)->dims[nthp]) {
2487 562 50       22 $CROAK("Different dims %"IND_FLAG" and %"IND_FLAG"",
2488 6         26 $PDL(CHILD)->dims[cd],
2489             $PDL(PARENT)->dims[nthp]);
2490 562         19 }
2491             $PRIV(incs)[cd] += $PDL(PARENT)->dimincs[nthp];
2492 17         115 } else {
2493 93         753 $PRIV(incs)[nthc] = $PDL(PARENT)->dimincs[nthp];
2494 93         258 $PDL(CHILD)->dims[nthc] = $PDL(PARENT)->dims[nthp];
2495             nthc++;
2496 324 100       464 }
2497             $SETDIMS();
2498             '),
2499             PMCode =>pp_line_numbers(__LINE__, <<'EOD'),
2500             sub PDL::diagonal :lvalue { shift->_diagonal_int(my $o=PDL->null, \@_); $o }
2501             EOD
2502             Doc => <<'EOD',
2503             =for ref
2504              
2505             Returns the multidimensional diagonal over the specified dimensions.
2506              
2507             The diagonal is placed at the first (by number) dimension that is
2508             diagonalized.
2509             The other diagonalized dimensions are removed. So if C<$x> has dimensions
2510             C<(5,3,5,4,6,5)> then after
2511              
2512             =for usage
2513              
2514             $d = $x->diagonal(dim1, dim2,...)
2515              
2516             =for example
2517              
2518             $y = $x->diagonal(0,2,5);
2519              
2520             the ndarray C<$y> has dimensions C<(5,3,4,6)> and
2521             C<$y-Eat(2,1,0,1)> refers
2522             to C<$x-Eat(2,1,2,0,1,2)>.
2523              
2524             NOTE: diagonal doesn't handle broadcastids correctly. XXX FIX
2525              
2526             pdl> $x = zeroes(3,3,3);
2527             pdl> ($y = $x->diagonal(0,1))++;
2528             pdl> p $x
2529             [
2530             [
2531             [1 0 0]
2532             [0 1 0]
2533             [0 0 1]
2534             ]
2535             [
2536             [1 0 0]
2537             [0 1 0]
2538             [0 0 1]
2539             ]
2540             [
2541             [1 0 0]
2542             [0 1 0]
2543             [0 0 1]
2544             ]
2545             ]
2546             =cut
2547             EOD
2548             );
2549              
2550             pp_addpm({At => 'Bot'},<<'EOD');
2551              
2552             =head1 BUGS
2553              
2554             For the moment, you can't slice one of the zero-length dims of an
2555             empty ndarray. It is not clear how to implement this in a way that makes
2556             sense.
2557              
2558             Many types of index errors are reported far from the indexing
2559             operation that caused them. This is caused by the underlying architecture:
2560             slice() sets up a mapping between variables, but that mapping isn't
2561             tested for correctness until it is used (potentially much later).
2562              
2563             =head1 AUTHOR
2564              
2565             Copyright (C) 1997 Tuomas J. Lukka. Contributions by
2566             Craig DeForest, deforest@boulder.swri.edu.
2567             Documentation contributions by David Mertens.
2568             All rights reserved. There is no warranty. You are allowed
2569             to redistribute this software / documentation under certain
2570             conditions. For details, see the file COPYING in the PDL
2571             distribution. If this file is separated from the PDL distribution,
2572             the copyright notice should be included in the file.
2573              
2574             =cut
2575              
2576             EOD
2577              
2578              
2579             pp_done();