File Coverage

blib/lib/PDL/NDBin.pm
Criterion Covered Total %
statement 299 316 94.6
branch 119 154 77.2
condition 37 43 86.0
subroutine 43 43 100.0
pod 13 13 100.0
total 511 569 89.8


line stmt bran cond sub pod time code
1             package PDL::NDBin;
2             # ABSTRACT: Multidimensional binning & histogramming
3             $PDL::NDBin::VERSION = '0.020';
4 3     3   761769 use strict;
  3         15  
  3         89  
5 3     3   17 use warnings;
  3         3  
  3         78  
6 3     3   14 use Exporter;
  3         8  
  3         116  
7 3     3   21 use List::Util qw( reduce );
  3         6  
  3         175  
8 3     3   1837 use List::MoreUtils qw( pairwise );
  3         38308  
  3         18  
9 3     3   4686 use Math::Round qw( nlowmult nhimult );
  3         23978  
  3         183  
10 3     3   455 use PDL::Lite; # do not import any functions into this namespace
  3         98833  
  3         24  
11 3     3   1716 use PDL::NDBin::Iterator;
  3         9  
  3         109  
12 3     3   1396 use PDL::NDBin::Actions_PP;
  3         8  
  3         36  
13 3     3   1806 use PDL::NDBin::Utils_PP;
  3         7  
  3         21  
14 3     3   1838 use Log::Any qw( $log );
  3         26083  
  3         16  
15 3     3   8317 use Data::Dumper;
  3         21182  
  3         186  
16 3     3   1557 use UUID::Tiny qw( :std );
  3         40069  
  3         584  
17 3     3   22 use POSIX qw( ceil );
  3         6  
  3         29  
18 3     3   1149 use Params::Validate qw( validate validate_pos validate_with ARRAYREF CODEREF HASHREF SCALAR );
  3         6  
  3         208  
19 3     3   20 use Carp;
  3         6  
  3         158  
20 3     3   1536 use Class::Load qw( load_class );
  3         35950  
  3         12954  
21              
22              
23             our @ISA = qw( Exporter );
24             our @EXPORT = qw( );
25             our @EXPORT_OK = qw( ndbinning ndbin );
26             our %EXPORT_TAGS = ( all => [ qw( ndbinning ndbin ) ] );
27              
28             # the list of valid keys
29             my %valid_key = map { $_ => 1 } qw( axes vars );
30              
31              
32             my @axis_params = qw( max min n round step grid );
33             my ( %axis_params, %axis_flags );
34             @axis_params{@axis_params} = (1) x @axis_params;
35             @axis_flags{@axis_params} = map { 1<<$_ } 0..@axis_params-1;
36              
37             my %axis_allowed =
38             map { reduce( sub { $a | $b }, 0, @axis_flags{@$_} ) => 1 }
39             [ ],
40             [ qw( n ) ],
41             [ qw( min ) ],
42             [ qw( max ) ],
43             [ qw( step ) ],
44             [ qw( min step ) ],
45             [ qw( max step ) ],
46             [ qw( min n ) ],
47             [ qw( max n ) ],
48             [ qw( round n ) ],
49             [ qw( round step ) ],
50             [ qw( n step ) ],
51             [ qw( n step round ) ],
52             [ qw( min n step ) ],
53             [ qw( max n step ) ],
54             [ qw( min max n ) ],
55             [ qw( min max step ) ],
56             [ qw( grid ) ];
57              
58             sub add_axis
59             {
60 272     272 1 7715 my $self = shift;
61 272         5450 my %params = validate( @_, {
62             max => 0,
63             min => 0,
64             n => 0,
65             name => 1,
66             pdl => 0,
67             round => 0,
68             step => 0,
69             grid => 0,
70             } );
71 269         2082 $log->tracef( 'adding axis with specs %s', \%params );
72              
73 269   100 954   2656 my $pmask = reduce { $a | ($b||0) } 0, @axis_flags{ keys %params };
  954         2404  
74             croak( "inconsistent or incomplete parameters: ", keys %params )
75 269 50       1171 unless $axis_allowed{ $pmask };
76              
77 269         409 push @{ $self->{axes} }, \%params;
  269         1174  
78             }
79              
80              
81             sub add_var
82             {
83 211     211 1 2210 my $self = shift;
84 211         3529 my %params = validate( @_, {
85             action => { type => CODEREF | HASHREF | SCALAR },
86             name => 1,
87             pdl => 0,
88             } );
89 211         1311 $log->tracef( 'adding variable with specs %s', \%params );
90 211         666 push @{ $self->{vars} }, \%params;
  211         651  
91             }
92              
93              
94             sub new
95             {
96 244     244 1 314242 my $class = shift;
97 244         4471 my %params = validate( @_, {
98             axes => { optional => 1, type => ARRAYREF },
99             vars => { optional => 1, type => ARRAYREF },
100             } );
101 244 50       1642 $log->debug( 'new: arguments = ' . Dumper \%params ) if $log->is_debug;
102 244         2681 my $self = bless { axes => [], vars => [] }, $class;
103             # axes
104 244   100     835 $params{axes} ||= []; # be sure we can dereference
105 244         392 my @axes = @{ $params{axes} };
  244         506  
106 244         526 for my $axis ( @axes ) {
107 187         337 my @pat = ( 1 ); # one mandatory argument
108 187 100       394 if( @$axis > 1 ) { push @pat, (0) x (@$axis - 1) } # followed by n-1 optional arguments
  177         456  
109 187         1326 my( $name ) = validate_pos( @$axis, @pat );
110 186         479 shift @$axis; # remove name
111 186         463 $self->add_axis( name => $name, @$axis );
112             }
113             # vars
114 240   100     774 $params{vars} ||= []; # be sure we can dereference
115 240         340 my @vars = @{ $params{vars} };
  240         495  
116 240         443 for my $var ( @vars ) {
117 108         666 my( $name, $action ) = validate_pos( @$var, 1, 1 );
118 108         322 $self->add_var( name => $name, action => $action );
119             }
120 240         1041 return $self;
121             }
122              
123              
124 1419 100   1419 1 20929 sub axes { wantarray ? @{ $_[0]->{axes} } : $_[0]->{axes} }
  1187         2495  
125 1595 100   1595 1 8868 sub vars { wantarray ? @{ $_[0]->{vars} } : $_[0]->{vars} }
  1133         2435  
126              
127             sub _make_instance_hashref
128             {
129 198     198   3015 my %params = validate_with(
130             params => \@_,
131             spec => {
132             N => 1,
133             class => 1,
134             coderef => 0,
135             },
136             allow_extra => 1,
137             );
138 198         1023 my $short_class = delete $params{class};
139 198 100       712 my $full_class = substr( $short_class, 0, 1 ) eq '+'
140             ? substr( $short_class, 1 )
141             : "PDL::NDBin::Action::$short_class";
142 198         661 load_class( $full_class );
143 198         14606 return $full_class->new( %params );
144             }
145              
146             sub _make_instance
147             {
148 198     198   2652 my %params = validate( @_, {
149             action => 1,
150             N => 1,
151             } );
152 198 100       1079 if( ref $params{action} eq 'CODE' ) {
    100          
153             return _make_instance_hashref(
154             class => '+PDL::NDBin::Action::CodeRef',
155             N => $params{N},
156             coderef => $params{action},
157 37         93 );
158             }
159             elsif( ref $params{action} eq 'HASH' ) {
160             return _make_instance_hashref(
161 4         16 %{ $params{action} },
162             N => $params{N},
163 4         8 );
164             }
165             else {
166             return _make_instance_hashref(
167             class => $params{action},
168             N => $params{N},
169 157         332 );
170             }
171             }
172              
173              
174             sub feed
175             {
176 242     242 1 11788 my $self = shift;
177 242         566 my %pdls = @_;
178 242         810 while( my( $name, $pdl ) = each %pdls ) {
179 266         514 for my $v ( $self->axes, $self->vars ) {
180 588 100       1806 $v->{pdl} = $pdl if $v->{name} eq $name;
181             }
182             }
183             }
184              
185             sub _check_all_pdls_present
186             {
187 232     232   304 my $self = shift;
188 232         303 my %warned_for;
189 232         379 for my $v ( $self->axes, $self->vars ) {
190 492 100       1003 next if defined $v->{pdl};
191 76 50       192 next if $v->{action} eq 'Count'; # those variables don't need data
192 0         0 my $name = $v->{name};
193 0 0       0 next if $warned_for{ $name };
194 0         0 $log->error( "no data for $name" );
195 0         0 $warned_for{ $name }++;
196             }
197             }
198              
199             sub _check_pdl_length
200             {
201 232     232   310 my $self = shift;
202             # checking whether the lengths of all axes and variables are equal can
203             # only be done here (in a loop), and not in autoscale_axis()
204 232         277 my $length;
205 232         373 for my $v ( $self->axes, $self->vars ) {
206 492 100       1454 $length = $v->{pdl}->nelem unless defined $length;
207             # variables don't always need a pdl, or may be happy with a
208             # null pdl; let the action figure it out.
209             # note that the test isempty() is not a good test for null
210             # pdls, but until I have a better one, this will have to do
211 492 100 100     1595 next if $v->{action} && ( ! defined $v->{pdl} || $v->{pdl}->isempty );
      100        
212 400 50       2032 if( $v->{pdl}->nelem != $length ) {
213             croak( join '', 'number of elements (',
214 0         0 $v->{pdl}->nelem, ") of '$v->{name}'",
215             " is different from previous ($length)" );
216             }
217             }
218             }
219              
220              
221             sub autoscale_axis
222             {
223 262     262 1 667 my $axis = shift;
224             # return early if step, min, and n have already been calculated
225 262 100 100     974 if( defined $axis->{step} && defined $axis->{min} && defined $axis->{n} ) {
      100        
226 157         475 $log->tracef( 'step, min, n already calculated for %s; not recalculating', $axis );
227 157         576 return;
228             }
229             # first get & sanify the arguments
230 105 50       244 croak( 'need coordinates' ) unless defined $axis->{pdl};
231             # return if axis is empty
232 105 100       283 if( $axis->{pdl}->isempty ) {
233 1         13 $axis->{n} = 0;
234 1         3 return;
235             }
236              
237             # return early if a grid has been supplied
238 104 100       690 if( defined $axis->{grid} ) {
239              
240 9         26 $axis->{grid} = PDL::Core::topdl( $axis->{grid} );
241             croak( "grid supplied for %s must be one-dimensional with at least two elements", $axis )
242 9 50 33     169 if $axis->{grid}->nelem < 2 || $axis->{grid}->ndims > 1;
243 9         81 _validate_grid( $axis->{grid} );
244             # number of bins is one less than number of bin edges
245 6         23 $axis->{n} = $axis->{grid}->nelem - 1;
246 6         21 $log->tracef( 'grid supplied for %s; no need to autoscale', $axis );
247 6         25 return;
248             }
249              
250              
251 95 100       299 $axis->{min} = $axis->{pdl}->min unless defined $axis->{min};
252              
253              
254 95 100       4202 $axis->{max} = $axis->{pdl}->max unless defined $axis->{max};
255 95 100 66     3183 if( defined $axis->{round} and $axis->{round} > 0 ) {
256 2         8 $axis->{min} = nlowmult( $axis->{round}, $axis->{min} );
257 2         34 $axis->{max} = nhimult( $axis->{round}, $axis->{max} );
258             }
259 95 50       252 croak( 'max < min is invalid' ) if $axis->{max} < $axis->{min};
260 95 100       231 if( $axis->{pdl}->type >= PDL::float ) {
261 64 100       2339 croak( 'cannot bin with min = max' ) if $axis->{min} == $axis->{max};
262             }
263             # calculate the range
264             # for floating-point data, we need to augment the range by 1 unit - see
265             # the discussion under IMPLEMENTATION NOTES for more details
266 94         1263 my $range = $axis->{max} - $axis->{min};
267 94 100       201 if( $axis->{pdl}->type < PDL::float ) {
268 31         809 $range += 1;
269             }
270             # if step size has been supplied by user, check it
271 94 100       1855 if( defined $axis->{step} ) {
272 23 50       57 croak( 'step size must be > 0' ) unless $axis->{step} > 0;
273 23 50 66     59 if( $axis->{pdl}->type < PDL::float && $axis->{step} < 1 ) {
274 0         0 croak( "step size = $axis->{step} < 1 is not allowed when binning integral data" );
275             }
276             }
277             # number of bins I
278 94 100       799 if( defined $axis->{n} ) {
279 46 50       102 croak( 'number of bins must be > 0' ) unless $axis->{n} > 0;
280 46 50       183 croak( 'number of bins must be integral' ) if ceil( $axis->{n} ) - $axis->{n} > 0;
281             }
282             else {
283 48 100       96 if( defined $axis->{step} ) {
284             # data range and step size were verified above,
285             # so the result of this calculation is
286             # guaranteed to be > 0
287 23         112 $axis->{n} = ceil( $range / $axis->{step} );
288             }
289             else {
290             # if neither number of bins nor step size is defined,
291             # use some reasonable default (which used to be the
292             # behaviour of hist() in versions of PDL inferior to
293             # 2.4.12) (see F)
294 25 100       102 $axis->{n} = $axis->{pdl}->nelem > 100 ? 100 : $axis->{pdl}->nelem;
295             }
296             }
297             # step size I
298             # if we get here, the data range is certain to be larger than
299             # zero, and I is sure to be defined and valid (either
300             # because it was supplied explicitly and verified to be valid,
301             # or because it was calculated automatically)
302 94 100       240 if( ! defined $axis->{step} ) {
303             # result of this calculation is guaranteed to be > 0
304 71         161 $axis->{step} = $range / $axis->{n};
305 71 100       160 if( $axis->{pdl}->type < PDL::float ) {
306 23 100       718 croak( 'there are more bins than distinct values' ) if $axis->{step} < 1;
307             }
308             }
309             }
310              
311              
312             sub autoscale
313             {
314 232     232 1 367 my $self = shift;
315 232         513 $self->feed( @_ );
316 232         531 $self->_check_all_pdls_present;
317 232         533 $self->_check_pdl_length;
318 232         535 autoscale_axis( $_ ) for $self->axes;
319             }
320              
321              
322             sub labels
323             {
324 8     8 1 585 my $self = shift;
325 8         23 $self->autoscale( @_ );
326             my @list = map {
327 8         49 my $axis = $_;
  10         94  
328              
329 10 100       19 if ( defined $axis->{grid} ) {
330              
331             [ map {
332 8         97 { range => [ $axis->{grid}->at($_), $axis->{grid}->at($_+1) ] }
333 3         11 } 0..$axis->{grid}->nelem -2
334             ]
335              
336             }
337              
338             else {
339              
340 7         13 my ( $pdl, $min, $step ) = @{ $axis }{ qw( pdl min step ) };
  7         18  
341             [ map {
342             { # anonymous hash
343 20 100       488 range => $pdl->type() >= PDL::float()
    100          
344             ? [ $min + $step*$_, $min + $step*($_+1) ]
345             : $step > 1
346             ? [ nhimult( 1, $min + $step*$_ ), nhimult( 1, $min + $step*($_+1) - 1 ) ]
347             : $min + $step*$_
348             }
349 7         19 } 0 .. $axis->{n}-1 ];
350              
351             }
352              
353             } $self->axes;
354              
355 16 50       273 return wantarray ? @list : \@list;
356             }
357              
358              
359             sub process
360             {
361 222     222 1 3504 my $self = shift;
362              
363             # sanity check
364 222 100       321 croak( 'no axes supplied' ) unless @{ $self->axes };
  222         415  
365             # default action, when no variables are given, is to produce a histogram
366 220 100       313 $self->add_var( name => 'histogram', action => 'Count' ) unless @{ $self->vars };
  220         395  
367              
368             #
369 220         605 $self->autoscale( @_ );
370              
371             # process axes
372 215         1270 my $idx = 0; # flattened bin number
373 215         318 my @n; # number of bins in each direction
374             # find the last axis and flatten all axes into one dimension, working
375             # our way backwards from the last to the first axis
376 215         378 for my $axis ( reverse $self->axes ) {
377 242 50       671 if ( $log->is_debug ) {
378 0         0 $log->debug( 'input (' . $axis->{pdl}->info . ') = ' . $axis->{pdl} );
379 0 0       0 if ( ! defined $axis->{grid} ) {
380 0         0 $log->debug( "bin with parameters step=$axis->{step}, min=$axis->{min}, n=$axis->{n}" );
381             }
382             else {
383 0         0 $log->debug( "bin with parameters grid=$axis->{grid}" );
384             }
385             }
386 242 100       2095 croak( 'I cannot bin unless n > 0' ) unless $axis->{n} > 0;
387 241         529 unshift @n, $axis->{n}; # remember that we are working backwards!
388 241 100       483 if ( defined $axis->{grid} ) {
389 3         160 $idx = $axis->{pdl}->_flatten_into_grid( $idx, $axis->{grid} );
390             }
391             else {
392 238         5024 $idx = $axis->{pdl}->_flatten_into( $idx, $axis->{step}, $axis->{min}, $axis->{n} );
393             }
394             }
395 214 50       804 $log->debug( 'idx (' . $idx->info . ') = ' . $idx ) if $log->is_debug;
396 214         1780 $self->{n} = \@n;
397              
398 214     27   1421 my $N = reduce { $a * $b } @n; # total number of bins
  27         64  
399 214 50       804 croak( 'I need at least one bin' ) unless $N;
400 214         454 my @vars = map $_->{pdl}, $self->vars;
401 214   100     832 $self->{instances} ||= [ map { _make_instance( N => $N, action => $_->{action} ) } $self->vars ];
  198         417  
402              
403             #
404             {
405 214         344 local $Data::Dumper::Terse = 1;
  214         354  
406 214         604 $log->trace( 'process: $self = ' . Dumper $self );
407             }
408              
409             # now visit all the bins
410 214         29960 my $iter = PDL::NDBin::Iterator->new( bins => \@n, array => \@vars, idx => $idx );
411 214         637 $log->debug( 'iterator object created: ' . Dumper $iter );
412 214         19833 while( $iter->advance ) {
413 396         940 my $i = $iter->var;
414 396         1056 $self->{instances}->[ $i ]->process( $iter );
415             }
416              
417 211         1322 return $self;
418             }
419              
420              
421             sub output
422             {
423 187     187 1 1105 my $self = shift;
424 187 50       417 return unless defined wantarray;
425 187 100       416 unless( defined $self->{result} ) {
426             # reshape output
427 181         272 my $n = $self->{n};
428 181         233 my @output = map { $_->result } @{ $self->{instances} };
  186         432  
  181         402  
429 181         430 for my $pdl ( @output ) { $pdl->reshape( @$n ) }
  186         734  
430 181 50       7567 if( $log->is_debug ) { $log->debug( 'output: output (' . $_->info . ') = ' . $_ ) for @output }
  0         0  
431 181     186   2043 $self->{result} = { pairwise { $a->{name} => $b } @{ $self->vars }, @output };
  186         779  
  181         416  
432 181 50       825 if( $log->is_debug ) { $log->debug( 'output: result = ' . Dumper $self->{result} ) }
  0         0  
433             }
434 187 50       2118 return wantarray ? %{ $self->{result} } : $self->{result};
  0         0  
435             }
436              
437              
438             sub _consume (&\@)
439             {
440 94     94   173 my ( $f, $list ) = @_;
441 94         275 for my $i ( 0 .. $#$list ) {
442 310         540 local *_ = \$list->[$i];
443 310 100       466 if( not $f->() ) { return splice @$list, 0, $i }
  32         166  
444             }
445             # If we get here, either the list is empty, or all values in the list
446             # meet the condition. In either case, splicing the entire list does
447             # what we want.
448 62         243 return splice @$list;
449             }
450              
451             sub _expand_axes
452             {
453 52     52   79 my ( @out, $hash, @num );
454 52         114 while( @_ ) {
455 97 100       122 if( eval { $_[0]->isa('PDL') } ) {
  97 100       399  
    100          
456             # a new axis; push the existing one on the output list
457 58 50       109 push @out, $hash if $hash;
458 58         175 $hash = { pdl => shift };
459             }
460             elsif( ref $_[0] eq 'HASH' ) {
461             # the user has supplied a hash directly, which may or
462             # may not yet contain a key-value pair pdl => $pdl
463 26 50       60 $hash = { } unless $hash;
464 26         57 push @out, { %$hash, %{ +shift } };
  26         74  
465 26         86 undef $hash; # do not collapse consecutive hashes into one, too confusing
466             }
467 30     30   174 elsif( @num = _consume { /^[-+]?(\d+(\.\d*)?|\.\d+)([Ee][-+]?\d+)?$/ } @_ ) {
468 8 50       18 croak( 'no axis given' ) unless $hash;
469 8 100       30 croak( "too many arguments to axis in `@num'" ) if @num > 3;
470             # a series of floating-point numbers
471 7 50       23 $hash->{min} = $num[0] if @num > 0;
472 7 50       18 $hash->{max} = $num[1] if @num > 1;
473 7 50       33 $hash->{step} = $num[2] if @num > 2;
474             }
475             #elsif( @num = ( $_[0] =~ m{^((?:\d+(?:\.\d*)?|\.\d+)(?:[Ee][-+]?\d+)?/)+$}g ) and shift ) {
476             # DOES NOT WORK YET - TODO
477             # print "GMT-style axis spec found! (@num)\n";
478             # croak( 'no axis given' ) unless $hash;
479             # croak( "too many arguments to axis in `@num'" ) if @num > 3;
480             # # a string specification of the form 'min/max/step', a la GMT
481             # $hash->{min} = $num[0] if @num > 0;
482             # $hash->{max} = $num[1] if @num > 1;
483             # $hash->{step} = $num[2] if @num > 2;
484             #}
485             else {
486 5         61 croak( "while expanding axes: invalid argument at `@_'" );
487             }
488             }
489 46 100       160 push @out, $hash if $hash;
490 46         108 return @out;
491             }
492              
493              
494 115     115   324 sub _random_name { create_uuid( UUID_RANDOM ) }
495              
496              
497             sub ndbinning
498             {
499             #
500 26     26 1 16748 my $binner = __PACKAGE__->new;
501              
502             # leading arguments are axes and axis specifications
503             #
504             # PDL overloads the `eq' and `ne' operators; by checking for a PDL
505             # first, we avoid (invalid) comparisons between piddles and strings in
506             # the `grep'
507 26 100   148   110 my @leading = _consume { eval { $_->isa('PDL') } || ! $valid_key{ $_ } } @_;
  148         183  
  148         863  
508              
509             # consume and process axes
510             # axes require three numerical specifications following it
511 26   66     115 while( @leading > 3 && eval { $leading[0]->isa('PDL') } && ! grep ref, @leading[ 1 .. 3 ] ) {
  32   100     232  
512 31         102 my( $pdl, $step, $min, $n ) = splice @leading, 0, 4;
513 31         69 $binner->add_axis( name => _random_name, pdl => $pdl, step => $step, min => $min, n => $n );
514             }
515 26 100       58 if( @leading ) { croak( "error parsing arguments in `@leading'" ) }
  7         34  
516              
517             # remaining arguments are key => value pairs
518 19         40 my $args = { @_ };
519 19         65 my @invalid_keys = grep ! $valid_key{ $_ }, keys %$args;
520 19 50       41 croak( "invalid key(s) @invalid_keys" ) if @invalid_keys;
521              
522             # axes
523 19   50     97 $args->{axes} ||= [];
524 19         26 my @axes = @{ $args->{axes} };
  19         35  
525 19         38 for my $axis ( @axes ) {
526 0         0 my $pdl = shift @$axis;
527 0         0 $binner->add_axis( name => _random_name, pdl => $pdl, @$axis );
528             }
529              
530             # variables
531 19   100     53 $args->{vars} ||= [];
532 19         27 for my $var ( @{ $args->{vars} } ) {
  19         36  
533 13 50       30 if( @$var == 2 ) {
534 13         26 my( $pdl, $action ) = @$var;
535 13         27 $binner->add_var( name => _random_name, pdl => $pdl, action => $action );
536             }
537 0         0 else { croak( "wrong number of arguments for var: @$var" ) }
538             }
539              
540             #
541 19         55 $binner->process;
542 18         41 my $output = $binner->output;
543 18         28 my @result = map $output->{ $_->{name} }, @{ $binner->vars };
  18         29  
544 18 50       213 return wantarray ? @result : $result[0];
545             }
546              
547              
548             sub ndbin
549             {
550             #
551 55     55 1 43567 my $binner = __PACKAGE__->new;
552              
553             # leading arguments are axes and axis specifications
554             #
555             # PDL overloads the `eq' and `ne' operators; by checking for a PDL
556             # first, we avoid (invalid) comparisons between piddles and strings in
557             # the `grep'
558 55 100   132   236 if( my @leading = _consume { eval { $_->isa('PDL') } || ! $valid_key{ $_ } } @_ ) {
  132 100       173  
  132         974  
559 52         119 my @axes = _expand_axes( @leading );
560 46         156 $binner->add_axis( name => _random_name, %$_ ) for @axes;
561             }
562              
563             # remaining arguments are key => value pairs
564 49         177 my $args = { @_ };
565 49         150 my @invalid_keys = grep ! $valid_key{ $_ }, keys %$args;
566 49 50       114 croak( "invalid key(s) @invalid_keys" ) if @invalid_keys;
567              
568             # axes
569 49   100     201 $args->{axes} ||= [];
570 49         74 my @axes = @{ $args->{axes} };
  49         79  
571 49         93 for my $axis ( @axes ) {
572 3         6 my $pdl = shift @$axis;
573 3         6 $binner->add_axis( name => _random_name, pdl => $pdl, @$axis );
574             }
575              
576             # variables
577 49   100     156 $args->{vars} ||= [];
578 49         61 for my $var ( @{ $args->{vars} } ) {
  49         91  
579 16 50       34 if( @$var == 2 ) {
580 16         33 my( $pdl, $action ) = @$var;
581 16         34 $binner->add_var( name => _random_name, pdl => $pdl, action => $action );
582             }
583 0         0 else { croak( "wrong number of arguments for var: @$var" ) }
584             }
585              
586 49         123 $binner->process;
587 42         115 my $output = $binner->output;
588 42         63 my @result = map $output->{ $_->{name} }, @{ $binner->vars };
  42         78  
589 42 50       429 return wantarray ? @result : $result[0];
590             }
591              
592             1;
593              
594             __END__