File Coverage

blib/lib/PDL/FuncND.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             # --8<--8<--8<--8<--
2             #
3             # Copyright (C) 2010 Smithsonian Astrophysical Observatory
4             #
5             # This file is part of PDL::FuncND
6             #
7             # PDL::FuncND is free software: you can redistribute it and/or modify
8             # it under the terms of the GNU General Public License as published by
9             # the Free Software Foundation, either version 3 of the License, or (at
10             # your option) any later version.
11             #
12             # This program is distributed in the hope that it will be useful,
13             # but WITHOUT ANY WARRANTY; without even the implied warranty of
14             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15             # GNU General Public License for more details.
16             #
17             # You should have received a copy of the GNU General Public License
18             # along with this program. If not, see .
19             #
20             # -->8-->8-->8-->8--
21              
22             package PDL::FuncND;
23              
24 1     1   23479 use strict;
  1         1  
  1         35  
25 1     1   3 use warnings;
  1         1  
  1         23  
26              
27              
28 1     1   250 use PDL::LiteF;
  0            
  0            
29             use PDL::Constants qw[ PI ];
30             use PDL::Math qw[ lgamma ];
31             use PDL::MatrixOps qw[ determinant identity ];
32             use PDL::Transform qw[];
33             use PDL::Options qw[ iparse ];
34              
35             use Scalar::Util qw[ looks_like_number ];
36              
37             use base 'PDL::Exporter';
38              
39             use Carp;
40              
41             our @EXPORT_OK = qw[
42             cauchyND
43             gaussND
44             lorentzND
45             mahalanobis
46             moffatND
47             ];
48              
49             our %EXPORT_TAGS = ( Func => [@EXPORT_OK], );
50              
51             # bizarre spurious errors are being thrown by this policy
52             ## no critic (ProhibitAccessOfPrivateData)
53              
54             our $VERSION = '0.11';
55              
56             # keep option handling uniform. barf if there's a problem
57             sub _handle_options {
58              
59              
60             # remove known arguments from @_ so can use new_from_specification
61             my $self = shift;
62             my $opt = shift;
63              
64             my ( $vectors, $output, $ndims, $center, $scale );
65              
66             if ( $opt->{vectors} ) {
67             croak( "first argument must be a piddle if vectors option is set\n" )
68             unless eval { $self->isa( 'PDL' ) };
69              
70             $output = shift;
71             $vectors = $self;
72              
73             croak( "wrong dimensions for input vectors; expected <= 2, got ",
74             $vectors->ndims )
75             unless $vectors->ndims <= 2;
76              
77             # transform 0D piddle into 1D
78             $vectors = $vectors->dummy( 0 )
79             if $vectors->ndims == 0;
80              
81             ( $ndims ) = $vectors->dims;
82              
83             $vectors = $opt->{transform}->apply( $vectors )
84             if defined $opt->{transform};
85              
86             if ( defined $opt->{center} ) {
87             croak( "cannot use center = 'auto' if vectors is set\n" )
88             if !ref $opt->{center} && $opt->{center} eq 'auto';
89              
90             croak(
91             "cannot use center = [ $opt->{center}[0], ... ] if vectors is set\n"
92             )
93             if 'ARRAY' eq ref $opt->{center}
94             && !ref $opt->{center}[0]
95             && !looks_like_number( $opt->{center}[0] );
96              
97             $center = PDL::Core::topdl( $opt->{center} );
98             }
99             }
100              
101             else {
102             $output
103             = @_
104             ? $self->new_from_specification( @_ )
105             : $self->new_or_inplace;
106              
107             $ndims = $output->ndims;
108              
109             $vectors = $output->ndcoords->reshape( $ndims, $output->nelem );
110              
111             $vectors->inplace->apply( $opt->{transform} )
112             if defined $opt->{transform};
113              
114             if ( defined $opt->{center} ) {
115             if ( !ref $opt->{center} && $opt->{center} eq 'auto' ) {
116             $center = ( pdl( [ $output->dims ] ) - 1 ) / 2;
117             $center->inplace->apply( $opt->{transform} )
118             if defined $opt->{transform};
119             }
120             elsif ('ARRAY' eq ref $opt->{center}
121             && !ref $opt->{center}[0]
122             && $opt->{center}[0] eq 'offset' )
123             {
124              
125             $center = ( pdl( [ $output->dims ] ) - 1 ) / 2;
126              
127             # inplace bug ( PDL == 2.4.11, at least) causes SEGV
128             # if this is done in place. so. don't.
129             $center = $center->apply( $opt->{transform} )
130             if defined $opt->{transform};
131              
132             # allow:
133             # offset => piddle
134             # offset => [ ... ]
135             # offset => ( list )
136              
137             # NOTE!!!! bug in topdl ( PDL == 2.4.11, at least )
138             # topdl only uses the first argument
139             my @offset = @{ $opt->{center} };
140             shift @offset;
141              
142             $center += PDL::Core::topdl( @offset > 1 ? \@offset : @offset );
143             }
144              
145             else {
146              
147             $center = PDL::Core::topdl( $opt->{center} );
148              
149             }
150             }
151             else {
152              
153             $center = zeroes( $vectors->type, $ndims )
154              
155             }
156             }
157              
158             # for 1D output $center may be a 0D PDL; this causes grief;
159             # promote it to a 1D
160             $center = $center->dummy( 0 ) if defined $center && $center->ndims == 0;
161              
162             croak( "center vector has wrong dimensions\n" )
163             if defined $center
164             && ( $center->ndims != 1
165             || ( $center->dims )[0] != $ndims );
166              
167             # handle scale
168             # scalar -> symmetric, independent
169             if ( !defined $opt->{scale} || !ref $opt->{scale} ) {
170              
171             $scale = identity( $ndims )
172             * ( defined $opt->{scale} ? $opt->{scale}**2 : 1 );
173             }
174              
175             # 1D piddle of length N
176             elsif ( 'ARRAY' eq ref $opt->{scale}
177             && @{ $opt->{scale} } == $ndims )
178             {
179             $scale = identity( $ndims ) * pdl( $opt->{scale} )**2;
180             }
181              
182             # 1D piddle of length N
183             elsif (eval { $opt->{scale}->isa( 'PDL' ) }
184             && $opt->{scale}->ndims == 1
185             && ( $opt->{scale}->dims )[0] == $ndims )
186             {
187             $scale = identity( $ndims ) * $opt->{scale}**2;
188             }
189              
190             # full matrix N^N piddle
191             elsif (eval { $opt->{scale}->isa( 'PDL' ) }
192             && $opt->{scale}->ndims == 2
193             && all( pdl( $opt->{scale}->dims ) == pdl( $ndims, $ndims ) ) )
194             {
195             $scale = $opt->{scale};
196             }
197              
198             else {
199             croak(
200             "scale argument is not a scalar, an array of length $ndims,",
201             " or a piddle of shape ($ndims) or shape ($ndims,$ndims)\n"
202             );
203             }
204              
205             # apply a rotation to the scale matrix
206             if ( defined $opt->{theta} ) {
207             croak( "theta may only be used for 2D PDFs\n" )
208             if $ndims != 2;
209              
210             my $R = pdl(
211             [ cos( $opt->{theta} ), -sin( $opt->{theta} ) ],
212             [ sin( $opt->{theta} ), cos( $opt->{theta} ) ] );
213             $scale = $R->transpose x $scale x $R;
214             }
215              
216              
217              
218             return (
219             vectors => $vectors,
220             output => $output,
221             ndims => $ndims,
222             center => $center,
223             ndims => $ndims,
224             scale => $scale
225             );
226              
227              
228              
229             }
230              
231              
232             ############################################################################
233              
234             sub _gamma { exp( ( lgamma( @_ ) )[0] ) }
235              
236             ############################################################################
237              
238             sub _genericND {
239              
240             my ( $xopts, $sub ) = ( shift, shift );
241              
242             # handle being called as a method or a function
243             my $self = eval { ref $_[0] && $_[0]->isa( 'PDL' ) } ? shift @_ : 'PDL';
244              
245             # handle options.
246             my $opt = 'HASH' eq ref $_[-1] ? pop( @_ ) : {};
247             my %opt = iparse( {
248             center => undef,
249             scale => 1,
250             transform => undef,
251             vectors => 0,
252             theta => undef,
253             %$xopts,
254             },
255             $opt
256             );
257              
258             my %par = _handle_options( $self, \%opt, @_ );
259              
260             my $d2 = mahalanobis(
261             $par{vectors},
262             $par{scale},
263             {
264             squared => 1,
265             center => $par{center},
266             } );
267              
268             my $retval = $sub->( $d2, \%opt, \%par );
269              
270             my $output = $par{output};
271              
272             if ( $opt{vectors} ) {
273             $output = $retval;
274             }
275              
276             else {
277             $output .= $retval->reshape( $output->dims );
278             }
279              
280             return wantarray
281             ? (
282             vals => $output,
283             center => $par{center},
284             scale => $par{scale},
285             transform => $par{transform} )
286             : $output;
287              
288             }
289              
290             ############################################################################
291              
292             # from http://en.wikipedia.org/wiki/Cauchy_distribution#Multivariate_Cauchy_distribution
293              
294             # 1 + k
295             # Gamma(-----)
296             # 2
297             # ------------------------------------------------------------
298             # 1 + k
299             # -----
300             # 1 k/2 1/2 T -1 2
301             # Gamma(-) pi |S| (1 + (x - mu) S (x - mu))
302             # 2
303             #
304              
305              
306             sub _cauchyND {
307              
308             my ( $d2, $opt, $par ) = @_;
309              
310             my $k = $par->{ndims};
311              
312             my $pdf
313             = _gamma( ( 1 + $k ) / 2 )
314             / ( _gamma( 1 / 2 )
315             * PI**( $k / 2 )
316             * sqrt( determinant( $par->{scale} ) )
317             * ( 1 + $d2 )**( ( 1 + $k ) / 2 ) );
318              
319             return $opt->{log} ? log( $pdf ) : $pdf;
320             }
321              
322             sub cauchyND {
323              
324             unshift @_, { log => 0 }, \&_cauchyND;
325             goto \&_genericND;
326             }
327              
328             *PDL::cauchyND = \&cauchyND;
329              
330             ############################################################################
331              
332             sub _gaussND {
333              
334             my ( $d2, $opt, $par ) = @_;
335              
336             my $tmp = $d2;
337              
338             if ( $opt->{norm} == 1 ) {
339              
340             $tmp += $par->{ndims} * log( 2 * PI )
341             + log( determinant( $par->{scale} ) )
342              
343             }
344              
345             my $log_pdf = -$tmp / 2;
346              
347             return $opt->{log} ? $log_pdf : exp( $log_pdf );
348              
349             }
350              
351             sub gaussND {
352              
353             unshift @_, {
354             log => 0,
355             norm => 1,
356             }, \&_gaussND;
357             goto \&_genericND;
358             }
359              
360              
361             *PDL::gaussND = \&gaussND;
362              
363             ############################################################################
364              
365              
366             sub _lorentzND {
367              
368             my ( $d2, $opt, $par ) = @_;
369              
370             return 1 / ( 1 + $d2 );
371              
372             }
373              
374             sub lorentzND {
375              
376             unshift @_, {}, \&_lorentzND;
377             goto \&_genericND;
378             }
379              
380              
381             *PDL::lorentzND = \&lorentz;
382              
383             ############################################################################
384              
385              
386             sub _moffatND {
387              
388             my ( $d2, $opt, $par ) = @_;
389              
390             croak( "missing beta parameter\n" )
391             unless defined $opt->{beta};
392              
393             my $n = $par->{ndims};
394              
395             my $tmp = ( 1 + $d2 )**-$opt->{beta};
396              
397             if ( $opt->{norm} == 1 ) {
398              
399             # scale is a matrix with elements ~alpha**2
400             # det(scale) ~ (alpha**2)**$n
401             # sqrt(det(scale)) ~ alpha**$n
402              
403             my $alpha_n = sqrt( determinant( $par->{scale} ) );
404              
405             $tmp
406             *= _gamma( $opt->{beta} )
407             / _gamma( $opt->{beta} - $n / 2 )
408             / ( PI**( $n / 2 ) * $alpha_n );
409              
410             }
411              
412             return $tmp;
413             }
414              
415             sub moffatND {
416              
417             unshift @_,
418             {
419             alpha => undef,
420             beta => undef,
421             norm => 1,
422             },
423             \&_moffatND;
424             goto \&_genericND;
425              
426             }
427              
428             *PDL::moffatND = \&moffatND;
429              
430             ############################################################################
431              
432              
433              
434             sub mahalanobis {
435              
436             # handle options.
437             my $opt = 'HASH' eq ref $_[-1] ? pop( @_ ) : {};
438             my %opt = PDL::Options::iparse( {
439             center => undef,
440             inverted => 0,
441             squared => 0,
442             },
443             $opt
444             );
445              
446             my ( $x, $scale, $out ) = @_;
447              
448             my $xc;
449             if ( defined $opt{center} ) {
450             my $c = PDL::Core::topdl( $opt{center} );
451             $xc = $x - $c;
452             }
453             else {
454             $xc = $x;
455             }
456              
457             # may be passed a single vector; be nice
458             my $m = $x->ndims > 1 ? ($x->dims)[-1] : 1;
459              
460             $out = zeroes( double, $m )
461             unless defined $out;
462              
463             # if this is 1D, $scale may come in with shapes [], [1], or [1][1]
464             # we want the latter
465             $scale = $scale->dummy(1) if $scale->dims < 2;
466              
467             # invert the matrix if it hasn't already been inverted
468             $scale = $scale->inv
469             unless $opt{inverted};
470              
471             inner2( $xc, $scale, $xc, $out );
472              
473             $out->inplace->sqrt unless $opt{squared};
474              
475             return $out;
476             }
477              
478             *PDL::mahalanobis = \&mahalanobis;
479              
480             1;