Exercises: Arrays

This is the first heavily code chapter, so most of these exercises want you at a keyboard: predict an output, then compile and check; port a snippet; hunt a bug; extend the solver. That predict-then-verify loop is the whole discipline — a result you computed in your head and then confirmed on the machine is a result you own.

Difficulty: ⭐ warm-up · ⭐⭐ standard · ⭐⭐⭐ deeper. Solutions: worked solutions to the daggered (†) and odd-numbered problems are in appendices/answers-to-selected.md; the computational ones also appear as compilable code in code/exercise-solutions.f90. Try every problem before you look. Compile everything with gfortran -std=f2018 -Wall.


Part A — Declaration and Indexing ⭐

5.1 † For each declaration, state the rank, the shape, and the total size: (a) real(dp) :: v(10); (b) integer :: g(5, 5); (c) real(dp) :: s(-3:3); (d) real(dp) :: cube(4, 4, 4).

5.2 What is the index of the first and the last element of real(dp) :: w(0:99), and how many elements does it hold?

5.3 † Given integer :: a(4) = [10, 20, 30, 40], write down the values of a(1), a(4), size(a), and sum(a). What would a(0) and a(5) do?

5.4 Write a do loop that fills real(dp) :: t(100) so that t(i) holds 0.1 * i (i.e. 0.1, 0.2, …, 10.0). Watch the types — why is 0.1 * i a trap, and what is the fix?


Part B — Sections and Whole-Array Operations ⭐⭐

5.5 † Let integer :: m(4,4) be filled with m(i,j) = 10*i + j. Write the array section for each, and give its values: (a) row 3; (b) column 1; (c) the top-left 2×2 block; (d) every other element of row 2.

5.6 Given real(dp) :: a(5) = [1.0_dp, 2.0_dp, 3.0_dp, 4.0_dp, 5.0_dp], what does a contain after the single statement a(2:4) = 0.0_dp?

5.7 † Predict the exact output, then compile to confirm:

real(dp) :: a(4) = [1.0_dp, 2.0_dp, 3.0_dp, 4.0_dp]
real(dp) :: b(4) = [4.0_dp, 3.0_dp, 2.0_dp, 1.0_dp]
print '(4f6.1)', a + b
print '(4f6.1)', a * b
print '(f6.1)',  sum(a * b)

5.8 Rewrite the loop do i = 1, n; c(i) = a(i)*b(i) + 1.0_dp; end do as a single whole-array statement. What does the array form tell the compiler that the loop form hides?


Part C — Intrinsic Functions (predict the output) ⭐⭐

5.9 † For integer :: v(6) = [5, 3, 8, 1, 9, 2], give the value of sum(v), product(v), maxval(v), minval(v), maxloc(v, dim=1), and count(v > 4).

5.10 Let A = [[2, 0], [1, 3]] and B = [[1, 4], [5, 6]] (each row-by-row). Compute matmul(A, B) by hand, showing all four entries.

5.11 † For the same A and B as 5.10, compute the elementwise product A * B. Why is it different from matmul(A, B), and when would each be the right one to use?

5.12 Compute dot_product([2, 0, -1], [3, 4, 5]) by hand. Then state the shape of matmul(M, x) when M is 3×3 and x has length 3.


Part D — Port It ⭐⭐

Translate the snippet to modern Fortran, and note where the array thinking differs.

5.13 † Port this NumPy to Fortran using a masked reduction (no explicit loop):

import numpy as np
r = np.array([-2.0, 4.0, 6.0, -1.0, 8.0, 2.0])
mean_pos = r[r > 0].mean()      # mean of the positive entries only

5.14 In MATLAB, C = A*B is a matrix product and C = A.*B is elementwise. Write the Fortran for both, given 3×3 real matrices A and B, and say which intrinsic or operator each maps to.

5.15 † Port this pure-Python loop to a single Fortran whole-array statement, and explain in one sentence why the Python version is slow while the Fortran version is fast:

y = [0.0] * n
for i in range(n):
    y[i] = 2.0 * x[i] + 3.0

Part E — Find the Bug ⭐⭐

Each snippet is wrong. Say what happens (compile error or wrong/slow result) and fix it.

5.16 †

real(dp) :: a(3), b(4)
a = [1.0_dp, 2.0_dp, 3.0_dp]
b = a

5.17 A programmer coming from C writes, for real(dp) :: v(n):

do i = 0, n-1
  v(i) = 0.0_dp
end do

5.18 † This compiles and gives the right answer, but a reviewer flags it. Why — and what is the fix?

real(dp) :: a(1000, 1000)
do i = 1, 1000          ! i = row
  do j = 1, 1000        ! j = column
    a(i, j) = 0.0_dp
  end do
end do

5.19

real(dp), allocatable :: data(:)
data(1) = 3.14_dp       ! before any allocate

Part F — Design It and Back of the Envelope ⭐⭐⭐

5.20 † (Design it — solver.) Extend the Chapter 5 project checkpoint. After computing the interior Laplacian lap, add one statement that computes maxval(abs(lap(2:n-1, 2:n-1))) — the largest-magnitude interior value, the kind of quantity a solver watches to decide it has converged. For the u(i,j) = i**3 field on the 4×4 grid, what value should it print?

5.21 (Design it — solver.) The plate's edges are held at fixed temperatures. Using array-section assignments (no loops), write the four statements that set the top row of u(n, n) to 100.0_dp and the other three edges to 0.0_dp. (Order matters at the corners — decide and justify who wins.)

5.22 † (Back of the envelope.) A simulation stores a temperature field of $1000 \times 1000 \times 100$ cells as `real(dp)` (8 bytes each). How many bytes is that? Express it in GiB ($1\ \text{GiB} = 1024^3$ bytes). Would it fit in a 16 GiB laptop? What about two such fields (current and next step)?

5.23 (Back of the envelope.) Multiplying two $n \times n$ matrices costs about $2n^3$ floating-point operations. For $n = 1000$, how many operations is that? At a sustained $10^{10}$ operations per second, how long does one such matmul take? (This is why §5.4 says matmul is fine for small matrices — and why Chapter 21's LAPACK matters for large ones.)


Part G — Interleaved (Chapters 3 and 4) ⭐⭐

5.24 † (Ch. 3.) Predict the output and explain it: integer :: k(4) = [1, 2, 3, 4]; print *, sum(k) / 2. Then show the change that makes it a true (real) average.

5.25 (Ch. 4.) Rewrite this masked assignment as an equivalent do loop with an if. Which form do we prefer in Fortran, and why?

where (a < 0.0_dp) a = 0.0_dp

5.26 † (Ch. 3 + 5.) Given integer :: counts(5) = [7, 4, 9, 2, 8], write a single expression that computes their mean as a real(dp) value (not truncated). What is the numeric answer?

5.27 (Ch. 4.) Using a named do loop with exit, write a linear search that stops at the first element of real(dp) :: v(n) that exceeds a threshold thr, recording its index. Then name the intrinsic that does the same job in one call.

5.28 † (Synthesis.) Write a small program that: declares an allocatable real(dp) array, allocates it to size m = 5, fills it with 1.0, 2.0, …, 5.0 via an implied-do constructor, prints sum and the mean, and deallocates it. Give the expected output.


Solutions to the daggered and odd-numbered problems are in appendices/answers-to-selected.md; the numeric ones are worked as runnable code in code/exercise-solutions.f90. Design problems 5.20–5.21 have a model answer plus room for your own variations — as long as your sections are conformable and your output checks out by hand, you are right.