Ranges, arrays, and loops
A series of integers (1,2,3,4,5, for example), is called a range in Chapel. Ranges are generated with the ..
operator, and are useful, among other things, to declare arrays of variables. For example, the following variable
var stability: [1..n,1..n] int; // our target array to compute
is a 2D array (matrix) with n
rows and n
columns of integer numbers, all initialized as 0
. The two ranges 1..n
not only define the size and shape of the array, they stand for the indices with which we could access particular elements of the array using the [X,X]
notation. For example, stability[1,1]
is the integer variable located at the first row and first column of the array stability
, while stability[3,7]
sits at the 3rd row and 7th column; stability[2,3..15]
access columns 3 to 15 of the 2nd row, and stability[1..4,4]
corresponds to the first 4 rows on the 4th column of stability
.
We are now ready to start coding our computation … here is what we are going to do:
- write a function to compute a single pixel of the image: takes a complex point, returns its stability number
- iterate over all points in the image, calling
pixel()
for each - time the computation
- learn how to write the resulting 2D array to a NetCDF file and/or a PNG image
- parallelize this code, first on a single node, and then across multiple nodes
Using expressions to create arrays
In Chapel arrays can also be initialized with expressions (similarly to list comprehensions in Python):
writeln([i in 1..10] i**2); // prints 1 4 9 16 25 36 49 64 81 100
var x = [i in 1..10] if (i%2 == 0) then i else 0;
writeln(x); // prints 0 2 0 4 0 6 0 8 0 10
writeln(x.type:string); // 1D array of type int(64)
Structured iterations with for-loops
We want to iterate over all points in our image. When it comes to iterating over a given number of elements, the for-loop is what we want to use. The for-loop has the following general syntax:
for index in iterand do
instruction;
for index in iterand {
instruction1;
instruction2;
...
}
The iterand
is a statement that expresses an iteration, e.g. it could be a range 1..15
. index
is a variable that exists only in the context of the for-loop, and that will be taking the different values yielded by the iterand. The code flows as follows: index takes the first value yielded by the iterand, and keeps it until all the instructions inside the curly brackets are executed one by one; then, index takes the second value yielded by the iterand, and keeps it until all the instructions are executed again. This pattern is repeated until index takes all the different values exressed by the iterand.
In our case we iterate both over all rows and all columns in the image to compute every pixel. This can be done with nested for loops like this:
for i in 1..n { // process row i
y = 2*(i-0.5)/n - 1;
for j in 1..n { // process column j, row i
point = 2*(j-0.5)/n - 1 + y*1i; // rescale to -1:1 in the complex plane
stability[i,j] = pixel(point);
}
}
To be able to compile the code, we also need a prototype pixel()
function:
proc pixel(z0) {
return z0; // to be replaced with an actual calculation
}
Now let’s compile and execute our code again:
$ chpl juliaSetSerial.chpl
$ sbatch serial.sh
$ tail -f solution.out