1BROADCASTING(1)       User Contributed Perl Documentation      BROADCASTING(1)
2
3
4

NAME

6       PDL::Broadcasting - Tutorial for PDL's Broadcasting feature
7

INTRODUCTION

9       One of the most powerful features of PDL is broadcasting, which can
10       produce very compact and very fast PDL code by avoiding multiple nested
11       for loops that C and BASIC users may be familiar with. The trouble is
12       that it can take some getting used to, and new users may not appreciate
13       the benefits of broadcasting.
14
15       Other vector based languages, such as MATLAB, use a subset of
16       broadcasting techniques, but PDL shines by completely generalizing them
17       for all sorts of vector-based applications.
18

TERMINOLOGY: NDARRAY

20       MATLAB typically refers to vectors, matrices, and arrays. Perl already
21       has arrays, and the terms "vector" and "matrix" typically refer to one-
22       and two-dimensional collections of data. Having no good term to
23       describe their object, PDL developers coined the term "ndarray" to give
24       a name to their data type.
25
26       An ndarray consists of a series of numbers organized as an
27       N-dimensional data set. ndarrays provide efficient storage and fast
28       computation of large N-dimensional matrices. They are highly optimized
29       for numerical work.
30

THINKING IN TERMS OF BROADCASTING

32       If you have used PDL for a little while already, you may have been
33       using broadcasting without realising it. Start the PDL shell (type
34       "perldl" or "pdl2" on a terminal). Most examples in this tutorial use
35       the PDL shell.  Make sure that PDL::NiceSlice and PDL::AutoLoader are
36       enabled. For example:
37
38         % pdl2
39         perlDL shell v1.352
40         ...
41         ReadLines, NiceSlice, MultiLines  enabled
42        ...
43         Note: AutoLoader not enabled ('use PDL::AutoLoader' recommended)
44
45         pdl>
46
47       In this example, NiceSlice was automatically enabled, but AutoLoader
48       was not.  To enable it, type "use PDL::AutoLoader".
49
50       Let's start with a two-dimensional ndarray:
51
52         pdl> $x = sequence(11,9)
53         pdl> p $x
54         [
55           [ 0  1  2  3  4  5  6  7  8  9 10]
56           [11 12 13 14 15 16 17 18 19 20 21]
57           [22 23 24 25 26 27 28 29 30 31 32]
58           [33 34 35 36 37 38 39 40 41 42 43]
59           [44 45 46 47 48 49 50 51 52 53 54]
60           [55 56 57 58 59 60 61 62 63 64 65]
61           [66 67 68 69 70 71 72 73 74 75 76]
62           [77 78 79 80 81 82 83 84 85 86 87]
63           [88 89 90 91 92 93 94 95 96 97 98]
64         ]
65
66       The "info" method gives you basic information about an ndarray:
67
68         pdl> p $x->info
69         PDL: Double D [11,9]
70
71       This tells us that $x is an 11 x 9 ndarray composed of double precision
72       numbers. If we wanted to add 3 to all elements in an "n x m" ndarray, a
73       traditional language would use two nested for-loops:
74
75         # Pseudo-code. Traditional way to add 3 to an array.
76         for (i=0; i < n; i++) {
77             for (j=0; j < m; j++) {
78                 a(i,j) = a(i,j) + 3
79             }
80         }
81
82       Note: Notice that indices start at 0, as in Perl, C and Java (and
83       unlike MATLAB and IDL).
84
85       But with PDL, we can just write:
86
87         pdl> $y = $x + 3
88         pdl> p $y
89         [
90           [  3   4   5   6   7   8   9  10  11  12  13]
91           [ 14  15  16  17  18  19  20  21  22  23  24]
92           [ 25  26  27  28  29  30  31  32  33  34  35]
93           [ 36  37  38  39  40  41  42  43  44  45  46]
94           [ 47  48  49  50  51  52  53  54  55  56  57]
95           [ 58  59  60  61  62  63  64  65  66  67  68]
96           [ 69  70  71  72  73  74  75  76  77  78  79]
97           [ 80  81  82  83  84  85  86  87  88  89  90]
98           [ 91  92  93  94  95  96  97  98  99 100 101]
99         ]
100
101       This is the simplest example of broadcasting, and it is something that
102       all numerical software tools do. The "+ 3" operation was automatically
103       applied along two dimensions. Now suppose you want to to subtract a
104       line from every row in $x:
105
106         pdl> $line = sequence(11)
107         pdl> p $line
108         [0 1 2 3 4 5 6 7 8 9 10]
109         pdl> $c = $x - $line
110         pdl> p $c
111         [
112          [ 0  0  0  0  0  0  0  0  0  0  0]
113          [11 11 11 11 11 11 11 11 11 11 11]
114          [22 22 22 22 22 22 22 22 22 22 22]
115          [33 33 33 33 33 33 33 33 33 33 33]
116          [44 44 44 44 44 44 44 44 44 44 44]
117          [55 55 55 55 55 55 55 55 55 55 55]
118          [66 66 66 66 66 66 66 66 66 66 66]
119          [77 77 77 77 77 77 77 77 77 77 77]
120          [88 88 88 88 88 88 88 88 88 88 88]
121         ]
122
123       Two things to note here: First, the value of $x is still the same. Try
124       "p $x" to check. Second, PDL automatically subtracted $line from each
125       row in $x. Why did it do that? Let's look at the dimensions of $x,
126       $line and $c:
127
128         pdl> p $line->info  =>  PDL: Double D [11]
129         pdl> p $x->info     =>  PDL: Double D [11,9]
130         pdl> p $c->info     =>  PDL: Double D [11,9]
131
132       So, both $x and $line have the same number of elements in the 0th
133       dimension! What PDL then did was broadcast over the higher dimensions
134       in $x and repeated the same operation 9 times to all the rows on $x.
135       This is PDL broadcasting in action.
136
137       What if you want to subtract $line from the first line in $x only?  You
138       can do that by specifying the line explicitly:
139
140         pdl> $x(:,0) -= $line
141         pdl> p $x
142         [
143          [ 0  0  0  0  0  0  0  0  0  0  0]
144          [11 12 13 14 15 16 17 18 19 20 21]
145          [22 23 24 25 26 27 28 29 30 31 32]
146          [33 34 35 36 37 38 39 40 41 42 43]
147          [44 45 46 47 48 49 50 51 52 53 54]
148          [55 56 57 58 59 60 61 62 63 64 65]
149          [66 67 68 69 70 71 72 73 74 75 76]
150          [77 78 79 80 81 82 83 84 85 86 87]
151          [88 89 90 91 92 93 94 95 96 97 98]
152         ]
153
154       See PDL::Indexing and PDL::NiceSlice to learn more about specifying
155       subsets from ndarrays.
156
157       The true power of broadcasting comes when you realise that the ndarray
158       can have any number of dimensions! Let's make a 4 dimensional ndarray:
159
160         pdl> $ndarray_4D = sequence(11,3,7,2)
161         pdl> $c = $ndarray_4D - $line
162
163       Now $c is an ndarray of the same dimension as $ndarray_4D.
164
165         pdl> p $ndarray_4D->info  =>  PDL: Double D [11,3,7,2]
166         pdl> p $c->info          =>  PDL: Double D [11,3,7,2]
167
168       This time PDL has broadcasted over three higher dimensions
169       automatically, subtracting $line all the way.
170
171       But, maybe you don't want to subtract from the rows (dimension 0), but
172       from the columns (dimension 1). How do I subtract a column of numbers
173       from each column in $x?
174
175         pdl> $cols = sequence(9)
176         pdl> p $x->info      =>  PDL: Double D [11,9]
177         pdl> p $cols->info   =>  PDL: Double D [9]
178
179       Naturally, we can't just type "$x - $cols". The dimensions don't match:
180
181         pdl> p $x - $cols
182         PDL: PDL::Ops::minus(a,b,c): Parameter 'b'
183         PDL: Mismatched implicit broadcast dimension 0: should be 11, is 9
184
185       How do we tell PDL that we want to subtract from  dimension 1 instead?
186

MANIPULATING DIMENSIONS

188       There are many PDL functions that let you rearrange the dimensions of
189       PDL arrays. They are mostly covered in PDL::Slices. The three most
190       common ones are:
191
192        xchg
193        mv
194        reorder
195
196   Method: "xchg"
197       The "xchg" method "exchanges" two dimensions in an ndarray:
198
199         pdl> $x = sequence(6,7,8,9)
200         pdl> $x_xchg = $x->xchg(0,3)
201
202         pdl> p $x->info       =>  PDL: Double D [6,7,8,9]
203         pdl> p $x_xchg->info  =>  PDL: Double D [9,7,8,6]
204                                                  |     |
205                                                  V     V
206                                              (dim 0) (dim 3)
207
208       Notice that dimensions 0 and 3 were exchanged without affecting the
209       other dimensions. Notice also that "xchg" does not alter $x. The
210       original variable $x remains untouched.
211
212   Method: "mv"
213       The "mv" method "moves" one dimension, in an ndarray, shifting other
214       dimensions as necessary.
215
216         pdl> $x = sequence(6,7,8,9)         (dim 0)
217         pdl> $x_mv = $x->mv(0,3)               |
218         pdl>                                   V _____
219         pdl> p $x->info     =>  PDL: Double D [6,7,8,9]
220         pdl> p $x_mv->info  =>  PDL: Double D [7,8,9,6]
221                                                 ----- |
222                                                       V
223                                                     (dim 3)
224
225       Notice that when dimension 0 was moved to position 3, all the other
226       dimensions had to be shifted as well. Notice also that "mv" does not
227       alter $x. The original variable $x remains untouched.
228
229   Method: "reorder"
230       The "reorder" method is a generalization of the "xchg" and "mv"
231       methods.  It "reorders" the dimensions in any way you specify:
232
233         pdl> $x = sequence(6,7,8,9)
234         pdl> $x_reorder = $x->reorder(3,0,2,1)
235         pdl>
236         pdl> p $x->info          =>  PDL: Double D [6,7,8,9]
237         pdl> p $x_reorder->info  =>  PDL: Double D [9,6,8,7]
238                                                     | | | |
239                                                     V V v V
240                                        dimensions:  0 1 2 3
241
242       Notice what happened. When we wrote "reorder(3,0,2,1)" we instructed
243       PDL to:
244
245        * Put dimension 3 first.
246        * Put dimension 0 next.
247        * Put dimension 2 next.
248        * Put dimension 1 next.
249
250       When you use the "reorder" method, all the dimensions are shuffled.
251       Notice that "reorder" does not alter $x. The original variable $x
252       remains untouched.
253

GOTCHA: LINKING VS ASSIGNMENT

255   Linking
256       By default, ndarrays are linked together so that changes on one will go
257       back and affect the original as well.
258
259         pdl> $x = sequence(4,5)
260         pdl> $x_xchg = $x->xchg(1,0)
261
262       Here, $x_xchg is not a separate object. It is merely a different way of
263       looking at $x. Any change in $x_xchg will appear in $x as well.
264
265         pdl> p $x
266         [
267          [ 0  1  2  3]
268          [ 4  5  6  7]
269          [ 8  9 10 11]
270          [12 13 14 15]
271          [16 17 18 19]
272         ]
273         pdl> $x_xchg += 3
274         pdl> p $x
275         [
276          [ 3  4  5  6]
277          [ 7  8  9 10]
278          [11 12 13 14]
279          [15 16 17 18]
280          [19 20 21 22]
281         ]
282
283   Assignment
284       Some times, linking is not the behaviour you want. If you want to make
285       the ndarrays independent, use the "copy" method:
286
287         pdl> $x = sequence(4,5)
288         pdl> $x_xchg = $x->copy->xchg(1,0)
289
290       Now $x and $x_xchg are completely separate objects:
291
292         pdl> p $x
293         [
294          [ 0  1  2  3]
295          [ 4  5  6  7]
296          [ 8  9 10 11]
297          [12 13 14 15]
298          [16 17 18 19]
299         ]
300         pdl> $x_xchg += 3
301         pdl> p $x
302         [
303          [ 0  1  2  3]
304          [ 4  5  6  7]
305          [ 8  9 10 11]
306          [12 13 14 15]
307          [16 17 18 19]
308         ]
309         pdl> $x_xchg
310         [
311          [ 3  7 11 15 19]
312          [ 4  8 12 16 20]
313          [ 5  9 13 17 21]
314          [ 6 10 14 18 22]
315         ]
316

PUTTING IT ALL TOGETHER

318       Now we are ready to solve the problem that motivated this whole
319       discussion:
320
321         pdl> $x = sequence(11,9)
322         pdl> $cols = sequence(9)
323         pdl>
324         pdl> p $x->info     =>  PDL: Double D [11,9]
325         pdl> p $cols->info  =>  PDL: Double D [9]
326
327       How do we tell PDL to subtract $cols along dimension 1 instead of
328       dimension 0?  The simplest way is to use the "xchg" method and rely on
329       PDL linking:
330
331         pdl> p $x
332         [
333          [ 0  1  2  3  4  5  6  7  8  9 10]
334          [11 12 13 14 15 16 17 18 19 20 21]
335          [22 23 24 25 26 27 28 29 30 31 32]
336          [33 34 35 36 37 38 39 40 41 42 43]
337          [44 45 46 47 48 49 50 51 52 53 54]
338          [55 56 57 58 59 60 61 62 63 64 65]
339          [66 67 68 69 70 71 72 73 74 75 76]
340          [77 78 79 80 81 82 83 84 85 86 87]
341          [88 89 90 91 92 93 94 95 96 97 98]
342         ]
343         pdl> $x->xchg(1,0) -= $cols
344         pdl> p $x
345         [
346          [ 0  1  2  3  4  5  6  7  8  9 10]
347          [10 11 12 13 14 15 16 17 18 19 20]
348          [20 21 22 23 24 25 26 27 28 29 30]
349          [30 31 32 33 34 35 36 37 38 39 40]
350          [40 41 42 43 44 45 46 47 48 49 50]
351          [50 51 52 53 54 55 56 57 58 59 60]
352          [60 61 62 63 64 65 66 67 68 69 70]
353          [70 71 72 73 74 75 76 77 78 79 80]
354          [80 81 82 83 84 85 86 87 88 89 90]
355         ]
356
357       General Strategy:
358            Move the dimensions you want to operate on to the start of your
359            ndarray's dimension list. Then let PDL broadcast over the higher
360            dimensions.
361

EXAMPLE: CONWAY'S GAME OF LIFE

363       Okay, enough theory. Let's do something a bit more interesting: We'll
364       write Conway's Game of Life in PDL and see how powerful PDL can be!
365
366       The Game of Life is a simulation run on a big two dimensional grid.
367       Each cell in the grid can either be alive or dead (represented by 1 or
368       0). The next generation of cells in the grid is calculated with simple
369       rules according to the number of living cells in it's immediate
370       neighbourhood:
371
372       1) If an empty cell has exactly three neighbours, a living cell is
373       generated.
374
375       2) If a living cell has less than two neighbours, it dies of
376       overfeeding.
377
378       3) If a living cell has 4 or more neighbours, it dies from starvation.
379
380       Only the first generation of cells is determined by the programmer.
381       After that, the simulation runs completely according to these rules. To
382       calculate the next generation, you need to look at each cell in the 2D
383       field (requiring two loops), calculate the number of live cells
384       adjacent to this cell (requiring another two loops) and then fill the
385       next generation grid.
386
387   Classical implementation
388       Here's a classic way of writing this program in Perl. We only use PDL
389       for addressing individual cells.
390
391         #!/usr/bin/perl -w
392         use PDL;
393         use PDL::NiceSlice;
394
395         # Make a board for the game of life.
396         my $nx = 20;
397         my $ny = 20;
398
399         # Current generation.
400         my $a1 = zeroes($nx, $ny);
401
402         # Next generation.
403         my $n = zeroes($nx, $ny);
404
405         # Put in a simple glider.
406         $a1(1:3,1:3) .= pdl ( [1,1,1],
407                              [0,0,1],
408                              [0,1,0] );
409
410         for (my $i = 0; $i < 100; $i++) {
411           $n = zeroes($nx, $ny);
412           $new_a = $a1->copy;
413           for ($x = 0; $x < $nx; $x++) {
414               for ($y = 0; $y < $ny; $y++) {
415
416                   # For each cell, look at the surrounding neighbours.
417                   for ($dx = -1; $dx <= 1; $dx++) {
418                       for ($dy = -1; $dy <= 1; $dy++) {
419                            $px = $x + $dx;
420                            $py = $y + $dy;
421
422                            # Wrap around at the edges.
423                            if ($px < 0) {$px = $nx-1};
424                            if ($py < 0) {$py = $ny-1};
425                            if ($px >= $nx) {$px = 0};
426                            if ($py >= $ny) {$py = 0};
427
428                           $n($x,$y)  .= $n($x,$y) + $a1($px,$py);
429                       }
430                   }
431                   # Do not count the central cell itself.
432                   $n($x,$y) -= $a1($x,$y);
433
434                   # Work out if cell lives or dies:
435                   #   Dead cell lives if n = 3
436                   #   Live cell dies if n is not 2 or 3
437                   if ($a1($x,$y) == 1) {
438                       if ($n($x,$y) < 2) {$new_a($x,$y) .= 0};
439                       if ($n($x,$y) > 3) {$new_a($x,$y) .= 0};
440                   } else {
441                       if ($n($x,$y) == 3) {$new_a($x,$y) .= 1}
442                   }
443               }
444           }
445
446           print $a1;
447
448           $a1 = $new_a;
449         }
450
451       If you run this, you will see a small glider crawl diagonally across
452       the grid of zeroes. On my machine, it prints out a couple of
453       generations per second.
454
455   Broadcasting PDL implementation
456       And here's the broadcasted version in PDL. Just four lines of PDL code,
457       and one of those is printing out the latest generation!
458
459         #!/usr/bin/perl -w
460         use PDL;
461         use PDL::NiceSlice;
462
463         my $x = zeroes(20,20);
464
465         # Put in a simple glider.
466         $x(1:3,1:3) .= pdl ( [1,1,1],
467                              [0,0,1],
468                              [0,1,0] );
469
470         my $n;
471         for (my $i = 0; $i < 100; $i++) {
472           # Calculate the number of neighbours per cell.
473           $n = $x->range(ndcoords($x)-1,3,"periodic")->reorder(2,3,0,1);
474           $n = $n->sumover->sumover - $x;
475
476           # Calculate the next generation.
477           $x = ((($n == 2) + ($n == 3))* $x) + (($n==3) * !$x);
478
479           print $x;
480         }
481
482       The broadcasted PDL version is much faster:
483
484         Classical => 32.79 seconds.
485         Broadcasting  =>  0.41 seconds.
486
487   Explanation
488       How does the broadcasted version work?
489
490       There are many PDL functions designed to help you carry out PDL
491       broadcasting.  In this example, the key functions are:
492
493       Method: "range"
494
495       At the simplest level, the "range" method is a different way to select
496       a portion of an ndarray. Instead of using the "$x(2,3)" notation, we
497       use another ndarray.
498
499         pdl> $x = sequence(6,7)
500         pdl> p $x
501         [
502          [ 0  1  2  3  4  5]
503          [ 6  7  8  9 10 11]
504          [12 13 14 15 16 17]
505          [18 19 20 21 22 23]
506          [24 25 26 27 28 29]
507          [30 31 32 33 34 35]
508          [36 37 38 39 40 41]
509         ]
510         pdl> p $x->range( pdl [1,2] )
511         13
512         pdl> p $x(1,2)
513         [
514          [13]
515         ]
516
517       At this point, the "range" method looks very similar to a regular PDL
518       slice.  But the "range" method is more general. For example, you can
519       select several components at once:
520
521         pdl> $index = pdl [ [1,2],[2,3],[3,4],[4,5] ]
522         pdl> p $x->range( $index )
523         [13 20 27 34]
524
525       Additionally, "range" takes a second parameter which determines the
526       size of the chunk to return:
527
528         pdl> $size = 3
529         pdl> p $x->range( pdl([1,2]) , $size )
530         [
531          [13 14 15]
532          [19 20 21]
533          [25 26 27]
534         ]
535
536       We can use this to select one or more 3x3 boxes.
537
538       Finally, "range" can take a third parameter called the "boundary"
539       condition.  It tells PDL what to do if the size box you request goes
540       beyond the edge of the ndarray. We won't go over all the options. We'll
541       just say that the option "periodic" means that the ndarray "wraps
542       around". For example:
543
544         pdl> p $x
545         [
546          [ 0  1  2  3  4  5]
547          [ 6  7  8  9 10 11]
548          [12 13 14 15 16 17]
549          [18 19 20 21 22 23]
550          [24 25 26 27 28 29]
551          [30 31 32 33 34 35]
552          [36 37 38 39 40 41]
553         ]
554         pdl> $size = 3
555         pdl> p $x->range( pdl([4,2]) , $size , "periodic" )
556         [
557          [16 17 12]
558          [22 23 18]
559          [28 29 24]
560         ]
561         pdl> p $x->range( pdl([5,2]) , $size , "periodic" )
562         [
563          [17 12 13]
564          [23 18 19]
565          [29 24 25]
566         ]
567
568       Notice how the box wraps around the boundary of the ndarray.
569
570       Method: "ndcoords"
571
572       The "ndcoords" method is a convenience method that returns an
573       enumerated list of coordinates suitable for use with the "range"
574       method.
575
576         pdl> p $ndarray = sequence(3,3)
577         [
578          [0 1 2]
579          [3 4 5]
580          [6 7 8]
581         ]
582         pdl> p ndcoords($ndarray)
583         [
584          [
585           [0 0]
586           [1 0]
587           [2 0]
588          ]
589          [
590           [0 1]
591           [1 1]
592           [2 1]
593          ]
594          [
595           [0 2]
596           [1 2]
597           [2 2]
598          ]
599         ]
600
601       This can be a little hard to read. Basically it's saying that the
602       coordinates for every element in $ndarray is given by:
603
604          (0,0)     (1,0)     (2,0)
605          (1,0)     (1,1)     (2,1)
606          (2,0)     (2,1)     (2,2)
607
608       Combining "range" and "ndcoords"
609
610       What really matters is that "ndcoords" is designed to work together
611       with "range", with no $size parameter, you get the same ndarray back.
612
613         pdl> p $ndarray
614         [
615          [0 1 2]
616          [3 4 5]
617          [6 7 8]
618         ]
619         pdl> p $ndarray->range( ndcoords($ndarray) )
620         [
621          [0 1 2]
622          [3 4 5]
623          [6 7 8]
624         ]
625
626       Why would this be useful? Because now we can ask for a series of
627       "boxes" for the entire ndarray. For example, 2x2 boxes:
628
629         pdl> p $ndarray->range( ndcoords($ndarray) , 2 , "periodic" )
630
631       The output of this function is difficult to read because the "boxes"
632       along the last two dimension. We can make the result more readable by
633       rearranging the dimensions:
634
635         pdl> p $ndarray->range( ndcoords($ndarray) , 2 , "periodic" )->reorder(2,3,0,1)
636         [
637          [
638           [
639            [0 1]
640            [3 4]
641           ]
642           [
643            [1 2]
644            [4 5]
645           ]
646           ...
647         ]
648
649       Here you can see more clearly that
650
651         [0 1]
652         [3 4]
653
654       Is the 2x2 box starting with the (0,0) element of $ndarray.
655
656       We are not done yet. For the game of life, we want 3x3 boxes from $x:
657
658         pdl> p $x
659         [
660          [ 0  1  2  3  4  5]
661          [ 6  7  8  9 10 11]
662          [12 13 14 15 16 17]
663          [18 19 20 21 22 23]
664          [24 25 26 27 28 29]
665          [30 31 32 33 34 35]
666          [36 37 38 39 40 41]
667         ]
668         pdl> p $x->range( ndcoords($x) , 3 , "periodic" )->reorder(2,3,0,1)
669         [
670          [
671           [
672            [ 0  1  2]
673            [ 6  7  8]
674            [12 13 14]
675           ]
676           ...
677         ]
678
679       We can confirm that this is the 3x3 box starting with the (0,0) element
680       of $x.  But there is one problem. We actually want the 3x3 box to be
681       centered on (0,0). That's not a problem. Just subtract 1 from all the
682       coordinates in ndcoords($x). Remember that the "periodic" option takes
683       care of making everything wrap around.
684
685         pdl> p $x->range( ndcoords($x) - 1 , 3 , "periodic" )->reorder(2,3,0,1)
686         [
687          [
688           [
689            [41 36 37]
690            [ 5  0  1]
691            [11  6  7]
692           ]
693           [
694            [36 37 38]
695            [ 0  1  2]
696            [ 6  7  8]
697           ]
698           ...
699
700       Now we see a 3x3 box with the (0,0) element in the centre of the box.
701
702       Method: "sumover"
703
704       The "sumover" method adds along only the first dimension. If we apply
705       it twice, we will be adding all the elements of each 3x3 box.
706
707         pdl> $n = $x->range(ndcoords($x)-1,3,"periodic")->reorder(2,3,0,1)
708         pdl> p $n
709         [
710          [
711           [
712            [41 36 37]
713            [ 5  0  1]
714            [11  6  7]
715           ]
716           [
717            [36 37 38]
718            [ 0  1  2]
719            [ 6  7  8]
720           ]
721           ...
722         pdl> p $n->sumover->sumover
723         [
724          [144 135 144 153 162 153]
725          [ 72  63  72  81  90  81]
726          [126 117 126 135 144 135]
727          [180 171 180 189 198 189]
728          [234 225 234 243 252 243]
729          [288 279 288 297 306 297]
730          [216 207 216 225 234 225]
731         ]
732
733       Use a calculator to confirm that 144 is the sum of all the elements in
734       the first 3x3 box and 135 is the sum of all the elements in the second
735       3x3 box.
736
737       Counting neighbours
738
739       We are almost there!
740
741       Adding up all the elements in a 3x3 box is not quite what we want. We
742       don't want to count the center box. Fortunately, this is an easy fix:
743
744         pdl> p $n->sumover->sumover - $x
745         [
746          [144 134 142 150 158 148]
747          [ 66  56  64  72  80  70]
748          [114 104 112 120 128 118]
749          [162 152 160 168 176 166]
750          [210 200 208 216 224 214]
751          [258 248 256 264 272 262]
752          [180 170 178 186 194 184]
753         ]
754
755       When applied to Conway's Game of Life, this will tell us how many
756       living neighbours each cell has:
757
758         pdl> $x = zeroes(10,10)
759         pdl> $x(1:3,1:3) .= pdl ( [1,1,1],
760         ..(    >                  [0,0,1],
761         ..(    >                  [0,1,0] )
762         pdl> p $x
763         [
764          [0 0 0 0 0 0 0 0 0 0]
765          [0 1 1 1 0 0 0 0 0 0]
766          [0 0 0 1 0 0 0 0 0 0]
767          [0 0 1 0 0 0 0 0 0 0]
768          [0 0 0 0 0 0 0 0 0 0]
769          [0 0 0 0 0 0 0 0 0 0]
770          [0 0 0 0 0 0 0 0 0 0]
771          [0 0 0 0 0 0 0 0 0 0]
772          [0 0 0 0 0 0 0 0 0 0]
773          [0 0 0 0 0 0 0 0 0 0]
774         ]
775         pdl> $n = $x->range(ndcoords($x)-1,3,"periodic")->reorder(2,3,0,1)
776         pdl> $n = $n->sumover->sumover - $x
777         pdl> p $n
778         [
779          [1 2 3 2 1 0 0 0 0 0]
780          [1 1 3 2 2 0 0 0 0 0]
781          [1 3 5 3 2 0 0 0 0 0]
782          [0 1 1 2 1 0 0 0 0 0]
783          [0 1 1 1 0 0 0 0 0 0]
784          [0 0 0 0 0 0 0 0 0 0]
785          [0 0 0 0 0 0 0 0 0 0]
786          [0 0 0 0 0 0 0 0 0 0]
787          [0 0 0 0 0 0 0 0 0 0]
788          [0 0 0 0 0 0 0 0 0 0]
789         ]
790
791       For example, this tells us that cell (0,0) has 1 living neighbour,
792       while cell (2,2) has 5 living neighbours.
793
794       Calculating the next generation
795
796       At this point, the variable $n has the number of living neighbours for
797       every cell. Now we apply the rules of the game of life to calculate the
798       next generation.
799
800       If an empty cell has exactly three neighbours, a living cell is
801       generated.
802            Get a list of cells that have exactly three neighbours:
803
804              pdl> p ($n == 3)
805              [
806               [0 0 1 0 0 0 0 0 0 0]
807               [0 0 1 0 0 0 0 0 0 0]
808               [0 1 0 1 0 0 0 0 0 0]
809               [0 0 0 0 0 0 0 0 0 0]
810               [0 0 0 0 0 0 0 0 0 0]
811               [0 0 0 0 0 0 0 0 0 0]
812               [0 0 0 0 0 0 0 0 0 0]
813               [0 0 0 0 0 0 0 0 0 0]
814               [0 0 0 0 0 0 0 0 0 0]
815               [0 0 0 0 0 0 0 0 0 0]
816              ]
817
818            Get a list of empty cells that have exactly three neighbours:
819
820              pdl> p ($n == 3) * !$x
821
822       If a living cell has less than 2 or more than 3 neighbours, it dies.
823            Get a list of cells that have exactly 2 or 3 neighbours:
824
825              pdl> p (($n == 2) + ($n == 3))
826              [
827               [0 1 1 1 0 0 0 0 0 0]
828               [0 0 1 1 1 0 0 0 0 0]
829               [0 1 0 1 1 0 0 0 0 0]
830               [0 0 0 1 0 0 0 0 0 0]
831               [0 0 0 0 0 0 0 0 0 0]
832               [0 0 0 0 0 0 0 0 0 0]
833               [0 0 0 0 0 0 0 0 0 0]
834               [0 0 0 0 0 0 0 0 0 0]
835               [0 0 0 0 0 0 0 0 0 0]
836               [0 0 0 0 0 0 0 0 0 0]
837              ]
838
839            Get a list of living cells that have exactly 2 or 3 neighbours:
840
841              pdl> p (($n == 2) + ($n == 3)) * $x
842
843       Putting it all together, the next generation is:
844
845         pdl> $x = ((($n == 2) + ($n == 3)) * $x) + (($n == 3) * !$x)
846         pdl> p $x
847         [
848          [0 0 1 0 0 0 0 0 0 0]
849          [0 0 1 1 0 0 0 0 0 0]
850          [0 1 0 1 0 0 0 0 0 0]
851          [0 0 0 0 0 0 0 0 0 0]
852          [0 0 0 0 0 0 0 0 0 0]
853          [0 0 0 0 0 0 0 0 0 0]
854          [0 0 0 0 0 0 0 0 0 0]
855          [0 0 0 0 0 0 0 0 0 0]
856          [0 0 0 0 0 0 0 0 0 0]
857          [0 0 0 0 0 0 0 0 0 0]
858         ]
859
860   Bonus feature: Graphics!
861       If you have PDL::Graphics::TriD installed, you can make a graphical
862       version of the program by just changing three lines:
863
864         #!/usr/bin/perl
865         use PDL;
866         use PDL::NiceSlice;
867         use PDL::Graphics::TriD;
868
869         my $x = zeroes(20,20);
870
871         # Put in a simple glider.
872         $x(1:3,1:3) .= pdl ( [1,1,1],
873                              [0,0,1],
874                              [0,1,0] );
875
876         my $n;
877         for (my $i = 0; $i < 100; $i++) {
878             # Calculate the number of neighbours per cell.
879             $n = $x->range(ndcoords($x)-1,3,"periodic")->reorder(2,3,0,1);
880             $n = $n->sumover->sumover - $x;
881
882             # Calculate the next generation.
883             $x = ((($n == 2) + ($n == 3))* $x) + (($n==3) * !$x);
884
885             # Display.
886             nokeeptwiddling3d();
887             imagrgb [$x];
888         }
889
890       But if we really want to see something interesting, we should make a
891       few more changes:
892
893       1) Start with a random collection of 1's and 0's.
894
895       2) Make the grid larger.
896
897       3) Add a small timeout so we can see the game evolve better.
898
899       4) Use a while loop so that the program can run as long as it needs to.
900
901         #!/usr/bin/perl
902         use PDL;
903         use PDL::NiceSlice;
904         use PDL::Graphics::TriD;
905         use Time::HiRes qw(usleep);
906
907         my $x = random(100,100);
908         $x = ($x < 0.5);
909
910         my $n;
911         while (1) {
912             # Calculate the number of neighbours per cell.
913             $n = $x->range(ndcoords($x)-1,3,"periodic")->reorder(2,3,0,1);
914             $n = $n->sumover->sumover - $x;
915
916             # Calculate the next generation.
917             $x = ((($n == 2) + ($n == 3))* $x) + (($n==3) * !$x);
918
919             # Display.
920             nokeeptwiddling3d();
921             imagrgb [$x];
922
923             # Sleep for 0.1 seconds.
924             usleep(100000);
925         }
926

CONCLUSION: GENERAL STRATEGY

928       The general strategy is: Move the dimensions you want to operate on to
929       the start of your ndarray's dimension list. Then let PDL broadcast over
930       the higher dimensions.
931
932       Broadcasting is a powerful tool that helps eliminate for-loops and can
933       make your code more concise. Hopefully this tutorial has shown why it
934       is worth getting to grips with broadcasting in PDL.
935
937       Copyright 2010 Matthew Kenworthy (kenworthy@strw.leidenuniv.nl) and
938       Daniel Carrera (dcarrera@gmail.com). You can distribute and/or modify
939       this document under the same terms as the current Perl license.
940
941       See: http://dev.perl.org/licenses/
942
943
944
945perl v5.38.0                      2023-07-21                   BROADCASTING(1)
Impressum