The PDL Book by PDL 2.006 Various Contributors - HTML preview

PLEASE NOTE: This is an HTML preview only and some elements such as links or page numbers may be incorrect.
Download the book in PDF, ePub, Kindle for a complete version.

The Code key specifies a Perl string with a quasi-C block of code that I am going to call PP code. This Perl string gets thoroughly transformed by PDL::PP and combined with other keys to produce the XS (and eventually C) code for your function. You can think of PP code as being regular C code with a few special macros and notations. The first example already demonstrates one such notation: to access the value in a piddle, you must prefix the name with a dollar-sign and you must postfix it with parentheses. In the next section we'll see just what sort of arguments you can put in those parentheses.

Best Practice: Use q{ } for Code Sections

When creating a string for the Code key (as well as the BadCode, BackCode, and BadBackCode keys), I strongly recommend that you use Perl's q quote operator with curly braces as delimiters, as I have used in the examples so far. Perl offers many ways to quote long blocks of text. Your first impulse may be to simply use normal Perl quotes like so:

Code => ' printf("%f\n", $a()); ',

 

For longer lines, you would probably pull out the ever-useful heredoc:

Code => <<EOCode,

 

     printf("%f\n", $a());

 EOCode

 

I have two reasons for recommending Perl's q operator. First, it makes your Code section look like a code block:

Code => q{

     printf("%f\n", $a());

 }

 

Second, PDL::PP's error reporting is not the greatest, and if you miss a curly brace, Perl's interpreter will catch it as a problem. This is not the case with the other delimiters. In this example, I forgot to include a closing brace:

Code => <<'EOCode',

     printf("Starting\n");

 

     for(i = 0; i < $SIZE(n); ++i) {

         printf("%d: %f\n", i, $a(n => i));

 

     printf("All done\n");

 EOCode

 

The C compiler will croak on the above example with an error that is likely to be obscure and only tangentially helpful. However, Perl will catch this typo at compile time if you use q{  }:

Code => q{

     printf("Starting\n");

     for(i = 0; i < $SIZE(n); ++i) {

         printf("%d: %f\n", i, $a(n => i));

     printf("All done\n");

 },

 

Also note that I do not recommend using the qq quoting operator. Almost all the PDL::PP code strings delimit piddles using dollar-signs (like $a() above) and you must escape each one of these unless you want Perl to interpolate a variable for you. Obviously qq has its uses occasionally, but in general I recommend sticking almost exclusively with q.

Let's now expand the example so that the function takes two arguments. Replace the original pp_def with this slightly more interesting code:

pp_def('printout_sum',

     Pars => 'a(); b()',

     Code => q{

         printf("%f + %f = %f\n", $a(), $b(), $a() + $b());

     },

 );

 

Change the line that reads

$a->printout;

 

to the following two lines:

 my $b = $a->random;

 $a->printout_sum($b);

 

and you should get output that looks like this:

> perl two-args.pl

 0.000000 + 0.690920 = 0.690920

 1.000000 + 0.907612 = 1.907612

 2.000000 + 0.479112 = 2.479112

 3.000000 + 0.421556 = 3.421556

 4.000000 + 0.431388 = 4.431388

 5.000000 + 0.022563 = 5.022563

 6.000000 + 0.014719 = 6.014719

 7.000000 + 0.354457 = 7.354457

 8.000000 + 0.705733 = 8.705733

 9.000000 + 0.827809 = 9.827809

 

The differences between this and the previous example are not complicated but deserve some discussion. A cosmetic difference is that I have used a different name for the function, but a more substantial difference is that the function now takes two arguments, a() and b(), as specified by the Pars key. The Code block makes use of these two piddles, printing out the two and their sum. Notice that I access the value in a with the expression $a(), and the value in b with $b(). Also notice that I can use those values in an arithmetic expression.

Returning Values

The examples I have used have all demonstrated their behavior by printing out their results to STDOUT. If you are like me, you will be glad to know that you can use printfs throughout your PP code when it comes time to debug, but these functions would be far more useful if they returned piddles with the calculated results. Fortunately, PDL::PP functions are really just C functions in disguise, and ultimately the data are passed around in C arrays, essentially by reference. This means that you can modify incoming piddles in-place. For example, this function increments a piddle:

use strict;

 use warnings;

 use PDL;

 use Inline 'Pdlpp';

 my $a = sequence(10);

 print "a is initially $a\n";

 $a->my_inc;

 print "a is now $a\n";

 

 __END__

 __Pdlpp__

 pp_def('my_inc',

     Pars => 'a()',

     Code => q{

         $a()++;

     },

 );

 

When I run that, I get this output:

a is initially [0 1 2 3 4 5 6 7 8 9]

 a is now [1 2 3 4 5 6 7 8 9 10]

 

If you want to modify a piddle in-place, PDL provides multiple mechanisms for handling this, depending on what you are trying to accomplish. In particular, there are ways to handle the inplace flag for a given piddle. But I'm getting a bit ahead of myself. Generally speaking, you shouldn't modify a piddle in-place: you should return a result instead. To do this, you simply mark the argument in the Pars key with the [o] qualifier. Here, I show how to return two arguments:

pp_def('my_sum_and_diff',

     Pars => 'left(); right(); [o] sum(); [o] diff()',

     Code => q{

         $sum() = $left() + $right();

         $diff() = $left() - $right();

     },

 );

 

This function takes $left and $right as input arguments (in that order) and it outputs $sum and $diff (also in that order, as a Perl list). For example, we could run the above pp-code with Perl code like this:

use strict;

 use warnings;

 use PDL;

 use Inline 'Pdlpp';

 my $left = sequence(10);

 my $right = $left->random;

 

 my ($sum, $diff) = $left->my_sum_and_diff($right);

 

 print "Left:  $left\n";

 print "Right: $right\n";

 print "Sum:   $sum\n";

 print "Diff:  $diff\n";

 

The functions defined using pp_def actually allow for you to pass in the output piddles as arguments, but I'll explore that in one of the exercises rather than boring you with more details.

Exercise Set 1

So far I have shown you how to write basic PP code that prints values to the screen or returns values. The great thing about PDL::PP is that this code actually allows for two different calling conventions, and it Does What You Mean when you give it all manner of piddles. Rather than bore you to death with more prose, I am going to give you a couple of exercises. Solutions to these exercises are in Appendix B.

1. Slices

Working with printout_sum, replace $b with a slice from some other piddle. Does it do what you expect?

2. Threading

With printout_sum, what if you replace $b with a two-dimensional piddle that is thread-compatible with $a? Try to guess the order of the output that you'll get before running the example. Did you guess correctly?

3. Orthogonal Piddles

What if $a has dimensions M and $b has dimensions (1, N) with printout_sum? What about my_sum_and_diff?

4. Varying Input Order

The PDL Book The PP code that I present puts all the output piddles at the end of the Pars section. What happens if you move them to the beginning of the section instead of the end?

5. Supplying Outputs in the Function Call

You can call pp_defined functions by supplying all the arguments to the function. For example, instead of calling my_sum_and_diff like this:

# No output piddles in function call

 my ($sum, $diff) = $left->my_sum_and_diff($right);

 

you can call it like this:

# All in function call, both outputs null

 my ($sum, $diff) = (PDL::null, PDL::null);

 $left->my_sum_and_diff($right, $sum, $diff);

 

What is the return value of this sort of invocation? How does the function call change if you alter the Pars order? There's a good reason for this capability, can you guess why PDL lets you do this?

Higher Dimensional Functions

So far I have shown how to write rudimentary functions that accept zero-dimensional piddles. In this section, I will explain how to write functions that accept higher-dimensional data.

Specifying Dimension