File Coverage

lib/PDL/Ops.pd
Criterion Covered Total %
statement 16 16 100.0
branch 17 64 26.5
condition n/a
subroutine n/a
pod n/a
total 33 80 41.2


line stmt bran cond sub pod time code
1             use strict;
2             use warnings;
3             use PDL::Types qw(types ppdefs ppdefs_all ppdefs_complex);
4             require PDL::Core::Dev;
5              
6             my $A = [ppdefs_all];
7             my $C = [ppdefs_complex];
8             my $F = [map $_->ppsym, grep $_->real && !$_->integer, types];
9             $F = [(grep $_ ne 'D', @$F), 'D']; # so defaults to D if non-float given
10             my $AF = [map $_->ppsym, grep !$_->integer, types];
11             $AF = [(grep $_ ne 'D', @$AF), 'D']; # so defaults to D if non-float given
12             my $T = [map $_->ppsym, grep $_->integer, types];
13             my $U = [map $_->ppsym, grep $_->unsigned, types];
14             my $S = [map $_->ppsym, grep $_->real && !$_->unsigned, types];
15             my %is_real; @is_real{ppdefs()} = ();
16             my @Rtypes = grep $_->real, types();
17             my @Ctypes = grep !$_->real, types();
18             my @Ftypes = grep !$_->integer, types();
19              
20             pp_addpm({At=>'Top'},<<'EOD');
21              
22             use strict;
23             use warnings;
24              
25             =head1 NAME
26              
27             PDL::Ops - Fundamental mathematical operators
28              
29             =head1 DESCRIPTION
30              
31             This module provides the functions used by PDL to
32             overload the basic mathematical operators (C<+ - / *>
33             etc.) and functions (C etc.)
34              
35             It also includes the function C, which should
36             be a perl function so that we can overload it!
37              
38             Matrix multiplication (the operator C) is handled
39             by the module L.
40              
41             =head1 SYNOPSIS
42              
43             none
44              
45             =cut
46              
47             EOD
48              
49             pp_addpm({At=>'Bot'},<<'EOPM');
50              
51             =head1 AUTHOR
52              
53             Tuomas J. Lukka (lukka@fas.harvard.edu),
54             Karl Glazebrook (kgb@aaoepp.aao.gov.au),
55             Doug Hunt (dhunt@ucar.edu),
56             Christian Soeller (c.soeller@auckland.ac.nz),
57             Doug Burke (burke@ifa.hawaii.edu),
58             and Craig DeForest (deforest@boulder.swri.edu).
59              
60             =cut
61              
62             EOPM
63              
64             pp_addhdr('
65             #include
66              
67             #define MOD(X,N) (((N) == 0) ? 0 : ( (X) - (PDL_ABS(N)) * ((long long)((X)/(PDL_ABS(N))) + ( ( ((N) * ((long long)((X)/(N)))) != (X) ) ? ( ( ((N)<0) ? 1 : 0 ) + ( (((X)<0) ? -1 : 0))) : 0 ))))
68             #define BU_MOD(X,N)(((N) == 0) ? 0 : ( (X)-(N)*((uint64_t)((X)/(N))) ))
69             #define SPACE(A,B) ( ((A)<(B)) ? -1 : ((A)!=(B)) )
70             ');
71              
72             my %char2escape = ('>'=>'E','<'=>'E');
73             my $chars = '(['.join('', map quotemeta, sort keys %char2escape).'])';
74             sub protect_chars {
75             my ($txt) = @_;
76             $txt =~ s/$chars/$char2escape{$1}/g;
77             return $txt;
78             }
79              
80             # simple binary operators
81              
82             pp_addhdr(pp_line_numbers(__LINE__, <<'EOF'));
83             #define PDL_BADVAL_WARN_X(datatype, ctype, ppsym, ...) \
84             bad_anyval.type = datatype; bad_anyval.value.ppsym = PDL->bvals.ppsym;
85             #define PDL_BADVAL_WARN(var) \
86             { \
87             PDL_Anyval bad_anyval = { PDL_INVALID, {0} }; \
88             if (!(var->has_badvalue && var->badvalue.type != var->datatype)) { \
89             if (var->has_badvalue) \
90             bad_anyval = var->badvalue; \
91             else { \
92             PDL_GENERICSWITCH(PDL_TYPELIST_ALL, var->datatype, PDL_BADVAL_WARN_X, ) \
93             } \
94             } \
95             if (bad_anyval.type < 0) \
96             barf("Error getting badvalue, type=%d", bad_anyval.type); \
97             complex double bad_c; \
98             ANYVAL_TO_CTYPE(bad_c, complex double, bad_anyval); \
99             if( bad_c == 0 || bad_c == 1 ) \
100             warn(#var " badvalue is set to 0 or 1. This will cause data loss when using badvalues for comparison operators."); \
101             }
102             EOF
103             sub biop {
104             my ($name,$op,$mutator,$doc,%extra) = @_;
105             my $optxt = protect_chars ref $op eq 'ARRAY' ? $op->[1] : $op;
106             $op = $op->[0] if ref $op eq 'ARRAY';
107             $extra{HdrCode} = << 'EOH';
108             if (swap) {
109             pdl *tmp = a;
110             a = b;
111             b = tmp;
112             }
113             EOH
114             # handle exceptions
115             if ( exists $extra{Exception} ) {
116             # NOTE This option is unused.
117             # See also `ufunc()`.
118             delete $extra{Exception};
119             }
120             if ($extra{Comparison}) {
121             my $first_complex = $Ctypes[0]->sym;
122             $extra{HdrCode} .= < 1;
123             if ((a->datatype >= $first_complex) || (b->datatype >= $first_complex))
124             barf("Can't compare complex numbers");
125             EOF
126             $extra{HdrCode} .= " PDL_BADVAL_WARN(a)\n PDL_BADVAL_WARN(b)\n";
127             delete $extra{Comparison};
128             }
129              
130             my $bitwise = delete $extra{Bitwise};
131             pp_def($name,
132             Pars => 'a(); b(); [o]c();',
133             OtherPars => 'int $swap',
134             OtherParsDefaults => { swap => 0 },
135             HandleBad => 1,
136             NoBadifNaN => 1,
137             Inplace => [ 'a' ],
138             Overload => [$op, $mutator, $bitwise],
139             NoExport => 1,
140             Code => pp_line_numbers(__LINE__, <
141             PDL_IF_BAD(char anybad = 0;,)
142             broadcastloop %{
143             PDL_IF_BAD(if ( ( \$PDLSTATEISBAD(a) && \$ISBAD(a()) )
144             || ( \$PDLSTATEISBAD(b) && \$ISBAD(b()) )) { \$SETBAD(c()); anybad = 1; } else,)
145             \$c() = \$a() $op \$b();
146             %}
147             PDL_IF_BAD(if (anybad) \$PDLSTATESETBAD(c);,)
148             EOF
149             %extra,
150             Doc => $doc,
151             );
152             }
153              
154             #simple binary functions
155             sub bifunc {
156             my ($name,$func,$mutator,$doc,%extra) = @_;
157             my $funcov = ref $func eq 'ARRAY' ? $func->[1] : $func;
158             my $isop = $funcov =~ s/^op//;
159             my $funcovp = protect_chars $funcov;
160             $func = $func->[0] if ref $func eq 'ARRAY';
161             my $got_complex = PDL::Core::Dev::got_complex_version($func, 2);
162             $extra{GenericTypes} = [ grep exists $is_real{$_}, @{$extra{GenericTypes}} ]
163             if !$got_complex and $extra{GenericTypes};
164             $extra{HdrCode} .= << 'EOH';
165             if (swap) {
166             pdl *tmp = a;
167             a = b;
168             b = tmp;
169             }
170             EOH
171             # is this one to be used as a function or operator ?
172              
173             my $codestr;
174             if ($extra{unsigned}){
175             #a little dance to avoid the MOD macro warnings for byte & ushort datatypes
176             my $t = join '', map $_->ppsym, grep $_->real, types();
177             my $v = join ',', map
178             $_->unsigned ? 'BU_' : '',
179             grep $_->real, types();
180             $codestr = << "ENDCODE";
181             \$c() = (\$GENERIC(c))\$T$t($v)$func(\$a(),\$b());
182             ENDCODE
183             #end dance
184             } else {
185             $codestr = '$c() = ($GENERIC(c))'.$func.'($a(),$b());';
186             }
187             delete $extra{unsigned}; #remove the key so it doesn't get added in pp_def.
188              
189             pp_def($name,
190             HandleBad => 1,
191             NoBadifNaN => 1,
192             Pars => 'a(); b(); [o]c();',
193             OtherPars => 'int $swap',
194             OtherParsDefaults => { swap => 0 },
195             Inplace => [ 'a' ],
196             Overload => [$funcov, $mutator],
197             NoExport => 1,
198             Code => pp_line_numbers(__LINE__, <
199             PDL_IF_BAD(char anybad = 0;,)
200             broadcastloop %{
201             PDL_IF_BAD(if ( \$ISBAD(a()) || \$ISBAD(b()) ) { anybad = 1; \$SETBAD(c()); } else ,) {
202             $codestr
203             }
204             %}
205             PDL_IF_BAD(if (anybad) \$PDLSTATESETBAD(c);,)
206             EOF
207             %extra,
208             Doc => $doc,
209             );
210             }
211              
212             # simple unary functions and operators
213             sub ufunc {
214             my ($name,$func,$overload,$doc,%extra) = @_;
215             my $funcov = ref $func eq 'ARRAY' ? $func->[1] : $func;
216             my $funcovp = protect_chars $funcov;
217             $func = $func->[0] if ref $func eq 'ARRAY';
218             my $got_complex = PDL::Core::Dev::got_complex_version($func, 1);
219             $extra{GenericTypes} = [ grep exists $is_real{$_}, @{$extra{GenericTypes}} ]
220             if !$got_complex and $extra{GenericTypes};
221              
222             # handle exceptions
223             if ( exists $extra{Exception} ) {
224             # print "Warning: ignored exception for $name\n";
225             # NOTE This option is unused.
226             # See also `biop()`.
227             delete $extra{Exception};
228             }
229             my $codestr = '$b() = ($GENERIC(b))'.$func.'($a());';
230             if (delete $extra{NoTgmath} and $got_complex) {
231             # don't bother if not got complex version
232             $codestr = join "\n",
233             'types('.join('', map $_->ppsym, @Rtypes).') %{'.$codestr.'%}',
234             (map 'types('.$_->ppsym.') %{$b() = c'.$func.$_->floatsuffix.'($a());%}', @Ctypes),
235             ;
236             }
237             # do not have to worry about propagation of the badflag when
238             # inplace since only input ndarray is a, hence its badflag
239             # won't change
240             # UNLESS an exception occurs...
241             pp_def($name,
242             Pars => 'a(); [o]b()',
243             HandleBad => 1,
244             NoBadifNaN => 1,
245             Inplace => 1,
246             !$overload ? () : (Overload => $funcov),
247             NoExport => 1,
248             Code => pp_line_numbers(__LINE__, <
249             PDL_IF_BAD(if ( \$ISBAD(a()) ) \$SETBAD(b()); else {,)
250             $codestr
251             PDL_IF_BAD(},)
252             EOF
253             %extra,
254             Doc => $doc,
255             );
256             }
257              
258             ######################################################################
259              
260             # we trap some illegal operations here -- see the Exception option
261             # note, for the ufunc()'s, the checks do not work too well
262             # for unsigned integer types (ie < 0)
263             #
264             # XXX needs thinking about
265             # - have to integrate into Code section as well (so
266             # 12/pdl(2,4,0,3) is trapped and flagged bad)
267             # --> complicated
268             # - perhaps could use type %{ %} ?
269             #
270             # ==> currently have commented out the exception code, since
271             # want to see if can use NaN/Inf for bad values
272             # (would solve many problems for F,D types)
273             #
274             # there is an issue over how we handle comparison operators
275             # - see Primitive/primitive.pd/zcover() for more discussion
276             #
277              
278             ## arithmetic ops
279             biop('plus','+',1,'add two ndarrays',GenericTypes => $A);
280             biop('mult','*',1,'multiply two ndarrays',GenericTypes => $A);
281             biop('minus','-',1,'subtract two ndarrays',GenericTypes => $A);
282             biop('divide','/',1,'divide two ndarrays', Exception => '$b() == 0', GenericTypes => $A);
283              
284             ## note: divide should perhaps trap division by zero as well
285              
286             ## comparison ops
287             # not defined for complex numbers
288             biop('gt','>',0,'the binary E (greater than) operation', Comparison => 2);
289             biop('lt','<',0,'the binary E (less than) operation', Comparison => 2);
290             biop('le','<=',0,'the binary E= (less equal) operation', Comparison => 2);
291             biop('ge','>=',0,'the binary E= (greater equal) operation', Comparison => 2);
292             biop('eq','==',0,'binary I operation (C<==>)', Comparison => 1, GenericTypes => $A);
293             biop('ne','!=',0,'binary I operation (C)', Comparison => 1, GenericTypes => $A);
294              
295             ## bit ops
296             # those need to be limited to the right types
297             biop('shiftleft','<<',1,'bitwise leftshift C<$a> by C<$b>',GenericTypes => $T);
298             biop('shiftright','>>',1,'bitwise rightshift C<$a> by C<$b>',GenericTypes => $T);
299             biop('or2','|',1,'bitwise I of two ndarrays',GenericTypes => $T,
300             Bitwise => 1);
301             biop('and2','&',1,'bitwise I of two ndarrays',GenericTypes => $T,
302             Bitwise => 1);
303             biop('xor','^',1,'bitwise I of two ndarrays',GenericTypes => $T,
304             Bitwise => 1);
305              
306             pp_addpm(
307             "=head2 xor2\n\n=for ref\n\nSynonym for L.\n\n=cut\n
308             *PDL::xor2 = *xor2 = \\&PDL::xor;"
309             );
310              
311             # some standard binary functions
312             bifunc('power',['pow','op**'],1,'raise ndarray C<$a> to the power C<$b>',GenericTypes => [@$C, @$F]);
313             bifunc('atan2','atan2',0,'elementwise C of two ndarrays',GenericTypes => $F);
314             bifunc('modulo',['MOD','op%'],1,'elementwise C operation',unsigned=>1);
315             bifunc('spaceship',['SPACE','op<=>'],0,'elementwise "<=>" operation');
316              
317             # some standard unary functions
318             ufunc('bitnot','~',1,'unary bitwise negation',GenericTypes => $T);
319             ufunc('sqrt','sqrt',1,'elementwise square root', GenericTypes => $A); # Exception => '$a() < 0');
320             ufunc('sin','sin',1,'the sin function', GenericTypes => $A);
321             ufunc('cos','cos',1,'the cos function', GenericTypes => $A);
322             ufunc('not','!',1,'the elementwise I operation');
323             ufunc('exp','exp',1,'the exponential function',GenericTypes => [@$C, @$F]);
324             ufunc('log','log',1,'the natural logarithm',GenericTypes => [@$C, @$F], Exception => '$a() <= 0');
325              
326             # no export these because clash with Test::Deep (re) or internal (_*abs)
327             cfunc('re', 'creal', 1, 0, 'Returns the real part of a complex number.',
328             '$complexv() = $b() + I * cimag($complexv());'
329             );
330             cfunc('im', 'cimag', 1, 0, 'Returns the imaginary part of a complex number.',
331             '$complexv() = creal($complexv()) + I * $b();'
332             );
333             cfunc('_cabs', 'fabs', 1, 0, 'Returns the absolute (length) of a complex number.', undef,
334             PMFunc=>'',
335             );
336             my $rabs_code = '
337             types('.join('', @$U).') %{ $b()=$a(); %}
338             types('.join('', @$S).') %{ $b()=PDL_ABS($a()); %}
339             ';
340             pp_def ( '_rabs',
341             Pars=>'a(); [o]b()',
342             HandleBad => 1,
343             NoBadifNaN => 1,
344             Inplace => 1,
345             NoExport => 1,
346             Code => pp_line_numbers(__LINE__-1, qq{
347             PDL_IF_BAD(if ( \$ISBAD(a()) ) \$SETBAD(b()); else,)
348             $rabs_code
349             }),
350             Doc=>undef,
351             PMFunc=>'',
352             );
353              
354             # make log10() work on scalars (returning scalars)
355             # as well as ndarrays
356             ufunc('log10','log10',0,'the base 10 logarithm', GenericTypes => $A,
357             Exception => '$a() <= 0',
358             NoTgmath => 1, # glibc for at least GCC 8.3.0 won't tgmath log10 though 7.1.0 did
359             NoExport => 0,
360             PMCode => <<'EOF',
361             sub PDL::log10 {
362             my ($x, $y) = @_;
363             return log($x) / log(10) if !UNIVERSAL::isa($x,"PDL");
364             barf "inplace but output given" if $x->is_inplace and defined $y;
365             if ($x->is_inplace) { $x->set_inplace(0); $y = $x; }
366             elsif (!defined $y) { $y = $x->initialize; }
367             &PDL::_log10_int( $x, $y );
368             $y;
369             };
370             EOF
371             );
372              
373             pp_def(
374             'assgn',
375             HandleBad => 1,
376             GenericTypes => $A,
377             Pars => 'a(); [o]b();',
378             Code => pp_line_numbers(__LINE__-1, q{
379             PDL_IF_BAD(char anybad = 0;,)
380             broadcastloop %{
381             PDL_IF_BAD(if ($ISBAD(a())) { anybad = 1; $SETBAD(b()); continue; },)
382             $b() = $a();
383             %}
384             PDL_IF_BAD(if (anybad) $PDLSTATESETBAD(b);,)
385             }),
386             Doc =>
387             'Plain numerical assignment. This is used to implement the ".=" operator',
388             );
389              
390             # special functions for complex data types that don't work well with
391             # the ufunc/bifunc logic
392             sub cfunc {
393             my ($name, $func, $make_real, $force_complex, $doc, $backcode, %extra) = @_;
394             my $codestr = pp_line_numbers(__LINE__-1,"\$b() = $func(\$complexv());");
395             pp_def($name,
396             GenericTypes=>$C,
397             Pars => ($force_complex ? '!real ' : '').'complexv(); '.($make_real ? 'real' : '').' [o]b()',
398             HandleBad => 1,
399             NoBadifNaN => 1,
400             (($make_real || $force_complex) ? () : (Inplace => 1)),
401             NoExport => 1,
402             Code => pp_line_numbers(__LINE__-1, qq{
403             PDL_IF_BAD(if ( \$ISBAD(complexv()) ) \$SETBAD(b()); else,)
404             $codestr
405             }),
406             !$backcode ? () : (
407             DefaultFlow => 1,
408             TwoWay => 1,
409             BackCode => pp_line_numbers(__LINE__-1, qq{
410             PDL_IF_BAD(if ( \$ISBAD(b()) ) \$SETBAD(complexv()); else {,)
411             $backcode
412             PDL_IF_BAD(},)
413             }),
414             ),
415             %extra,
416             Doc => $doc . (!$backcode ? '' : ' Flows data back & forth.'),
417             );
418             }
419              
420             cfunc('carg', 'carg', 1, 1, 'Returns the polar angle of a complex number.', undef, NoExport => 0);
421             cfunc('conj', 'conj', 0, 0, 'complex conjugate.', undef, NoExport => 0);
422              
423             pp_def('czip',
424             Pars => '!complex r(); !complex i(); complex [o]c()',
425             Doc => <<'EOF',
426             convert real, imaginary to native complex, (sort of) like LISP zip
427             function. Will add the C ndarray to "i" times the C ndarray. Only
428             takes real ndarrays as input.
429             EOF
430             Code => '$c() = $r() + $i() * I;'
431             );
432              
433             pp_def('ipow',
434             Inplace => [qw(a ans)],
435             Doc => qq{
436             =for ref
437              
438             raise ndarray C<\$a> to integer power C<\$b>
439              
440             Algorithm from L
441             },
442             Pars => 'a(); longlong b(); [o] ans()',
443             GenericTypes => [qw(P Q), @$AF],
444             Code => pp_line_numbers(__LINE__-1, q{
445 3294         30674 $GENERIC(b) n = $b();
446 3294 50       132 if (n == 0) {
    100          
    100          
    100          
    0          
    0          
    0          
    0          
447 1480         2966 $ans() = 1;
448 1480         381 continue;
449             }
450 1884         138 $GENERIC() y = 1;
451 1884         111894 $GENERIC() x = $a();
452 1884 0       597 if (n < 0) {
    50          
    0          
    50          
    0          
    0          
    0          
    0          
453 70         143 x = 1 / x;
454 70         346 n = -n;
455             }
456 8310 0       32273 while (n > 1) {
    100          
    0          
    100          
    0          
    0          
    0          
    0          
457 6496 0       556653 if (n % 2) {
    100          
    0          
    100          
    0          
    0          
    0          
    0          
458 5833         24162 y *= x;
459 426         328 n -= 1;
460             }
461 1070         7780 x *= x;
462 968         0 n /= 2;
463             }
464 2082         1464 $ans() = x * y;
465             })
466             );
467              
468             pp_addpm(<<'EOPM');
469              
470             =head2 abs
471              
472             =for ref
473              
474             Returns the absolute value of a number.
475              
476             =cut
477              
478             sub PDL::abs { $_[0]->type->real ? goto &PDL::_rabs : goto &PDL::_cabs }
479             EOPM
480              
481             pp_def('abs2',
482             GenericTypes=>$A,
483             HandleBad => 1,
484             Pars => 'a(); real [o]b()',
485             Doc => 'Returns the square of the absolute value of a number.',
486             Code => <<'EOF',
487             PDL_IF_BAD(if ($ISBAD(a())) { $SETBAD(b()); continue; },)
488             $b() = PDL_IF_GENTYPE_REAL(
489             $a()*$a(),
490             creall($a())*creall($a()) + cimagl($a())*cimagl($a())
491             );
492             EOF
493             );
494              
495             pp_def('r2C',
496             GenericTypes=>$AF,
497             Pars => 'r(); complex [o]c()',
498             Doc => 'convert real to native complex, with an imaginary part of zero',
499             PMCode => << 'EOF',
500             sub PDL::r2C ($) {
501             return $_[0] if UNIVERSAL::isa($_[0], 'PDL') and !$_[0]->type->real;
502             my $r = $_[1] // PDL->nullcreate($_[0]);
503             PDL::_r2C_int($_[0], $r);
504             $r;
505             }
506             EOF
507             Code => '$c() = $r();'
508             );
509              
510             pp_def('i2C',
511             GenericTypes=>$AF,
512             Pars => 'i(); complex [o]c()',
513             Doc => 'convert imaginary to native complex, with a real part of zero',
514             PMCode => << 'EOF',
515             sub PDL::i2C ($) {
516             return $_[0] if UNIVERSAL::isa($_[0], 'PDL') and !$_[0]->type->real;
517             my $r = $_[1] // PDL->nullcreate($_[0]);
518             PDL::_i2C_int($_[0], $r);
519             $r;
520             }
521             EOF
522             Code => '$c() = $i() * I;'
523             );
524              
525             pp_addpm(<<'EOF');
526             # This is to used warn if an operand is non-numeric or non-PDL.
527             sub warn_non_numeric_op_wrapper {
528             require Scalar::Util;
529             my ($cb, $op_name) = @_;
530             return sub {
531             my ($op1, $op2) = @_;
532             warn "'$op2' is not numeric nor a PDL in operator $op_name"
533             unless Scalar::Util::looks_like_number($op2)
534             || ( Scalar::Util::blessed($op2) && $op2->isa('PDL') );
535             $cb->(@_);
536             }
537             }
538              
539             { package # hide from MetaCPAN
540             PDL;
541             use overload
542             "eq" => PDL::Ops::warn_non_numeric_op_wrapper(\&PDL::eq, 'eq'),
543             ".=" => sub {
544             my @args = !$_[2] ? @_[1,0] : @_[0,1];
545             PDL::Ops::assgn(@args);
546             return $args[1];
547             },
548             'abs' => sub { PDL::abs($_[0]) },
549             '++' => sub { $_[0] += ($PDL::Core::pdl_ones[$_[0]->get_datatype]//barf "Couldn't find 'one' for type ", $_[0]->get_datatype) },
550             '--' => sub { $_[0] -= ($PDL::Core::pdl_ones[$_[0]->get_datatype]//barf "Couldn't find 'one' for type ", $_[0]->get_datatype) },
551             ;
552             }
553             EOF
554              
555             pp_done();