Quiz Bank

A pool of additional self-check questions — separate from, and beyond, each chapter's own quiz.md — organized by Part (I–X) so an instructor can pull a few for a pop quiz, a reading check, or a warm-up. Every question targets a load-bearing idea from that part of Introduction to Fortran Programming: The Language of Supercomputers, and each carries its answer inline in a collapsible block, so the same bank doubles as a student self-check.

How to use it. For a reading check, hand students 3–5 questions from the part they just finished and have them justify each answer before opening the block. For a mixed review, draw one "what does this print?" and one true/false-with-justification from each recent part. Questions span multiple choice, true/false (justify in a sentence), short answer, and hand-traced code output.

Conventions in this bank (matching the book).

  • No code was executed. Every "what does this print?" answer was worked out by hand — that is the exercise. Encourage students to predict the output before revealing the answer.
  • Snippets assume implicit none and modern free-form style, and that dp is the book's double-precision kind, integer, parameter :: dp = selected_real_kind(15, 307) (equivalently real64 from iso_fortran_env), already in scope. Boilerplate (program/end program, use) is elided to keep the focus on the idea.
  • The one deliberately legacy snippet (Part IV) is labeled as fixed-form FORTRAN 77 and is there to be read, not imitated. Everywhere else, modern style is the standard.
  • Math is standard LaTeX ($...$); code is fenced.

Suggested passing bar for a drawn set: 80%, with every "justify" question earning its point only when the justification is correct — not just the T/F letter.


Part I — Foundations

Types and kinds, integer division, arrays and column-major order, procedures and intent, I/O.

I-1 · Multiple choice

The dp kind is conventionally defined as selected_real_kind(15, 307). What does the second argument, 307, request?

  • A. 307 bits of precision
  • B. A decimal exponent range reaching at least $10^{307}$
  • C. 307 significant decimal digits
  • D. A kind number equal to 307
Answer **B.** `selected_real_kind(p, r)` asks the compiler for a real kind with **at least `p` significant decimal digits** and a representable range to **at least $10^{r}$**. Here that is ≥ 15 digits and range to $10^{307}$ — the properties of IEEE double precision. You state the *requirement*; the compiler picks the kind number (which is not portable and is why we never write `real(8)`).

I-2 · What does this print?

integer  :: total = 7, count = 2
real(dp) :: avg
avg = total / count
print '(f6.2)', avg
Answer Prints ` 3.00` (two leading spaces). This is the **integer-division trap**: `total / count` has two integer operands, so the division happens in integer arithmetic *first* — `7 / 2 = 3` — and only *then* is the integer `3` assigned to the real `avg`, giving `3.0`. The decimal part is gone before `avg` ever sees it. The fix is `avg = real(total, dp) / real(count, dp)`, which prints ` 3.50`.

I-3 · True or false (justify)

For real(dp) :: a(1000, 1000), the nest below gives the cache-friendly access pattern.

do j = 1, 1000        ! outer: column index
  do i = 1, 1000      ! inner: row index
    a(i, j) = 0.0_dp
  end do
end do
Answer **True.** Fortran stores arrays in **column-major** order: the *first* index varies fastest through memory. Running the inner loop over the first index `i` walks contiguous, unit-stride memory, so each cache line is fully used before the next is loaded. Swapping the loops (inner over `j`) would stride by 1000 elements each step and thrash the cache — often several times slower for large arrays.

I-4 · Multiple choice

Given integer :: v(5) = [10, 20, 30, 40, 50], what is sum(v(2:4))?

  • A. 60
  • B. 90
  • C. 100
  • D. 150
Answer **B, 90.** Arrays are **1-based**, and a section `2:4` includes *both* endpoints: elements 2, 3, 4 — that is `20 + 30 + 40 = 90`. (A common slip is to read `2:4` as Python's half-open `[2:4)`, which would drop element 4.)

I-5 · Short answer

In one sentence, what does intent(out) promise, and what is the hazard it creates for the caller?

Answer `intent(out)` declares that the procedure **will set** the argument, and the hazard is that on entry the argument is **undefined** — the caller's previous value is not available inside the procedure, so the procedure must assign to it before reading it (and the caller must not rely on the old value surviving the call).

I-6 · What does this print?

integer :: m(2, 3) = reshape([1, 2, 3, 4, 5, 6], [2, 3])
print '(i0)', m(2, 3)
Answer Prints `6`. `reshape` fills the target in **column-major** order, so the columns receive the values in turn: column 1 = `(1, 2)`, column 2 = `(3, 4)`, column 3 = `(5, 6)`. Element `m(2, 3)` is row 2 of column 3 — the last value stored — which is `6`. (If you wanted row-major fill you would pass `order=[2, 1]`.)

I-7 · Multiple choice

Which edit descriptor writes a real with exactly one nonzero digit before the decimal point, regardless of the number's magnitude (e.g., 1.2345E+03)?

  • A. F (fixed)
  • B. ES (scientific)
  • C. EN (engineering)
  • D. A (character)
Answer **B, `ES`.** The `ES` (scientific) descriptor normalizes to a single nonzero digit before the point. `EN` (engineering) is close but constrains the exponent to a **multiple of 3** (so it shows 1–3 digits before the point); plain `E` uses a leading `0.` mantissa (`0.1234E+04`); `F` is plain fixed-point; `A` is for character data.

I-8 · True or false (justify)

real(1/3, dp) and real(1, dp) / 3 produce the same value.

Answer **False.** In `real(1/3, dp)` the argument `1/3` is evaluated **first**, in integer arithmetic — it is `0` — and converting `0` to real gives `0.0`. In `real(1, dp) / 3` the `1` is converted to real *before* the division, so it is `1.0_dp / 3 = 0.3333…`. Convert-then-divide and divide-then-convert are not the same; the order of the integer division relative to the type conversion is what matters.

Part II — Modern Fortran Features

Modules, derived types, object orientation, allocatable vs pointer, strings, error handling.

II-1 · Multiple choice

What does use kinds, only: dp accomplish that a bare use kinds does not?

  • A. It makes the module load faster
  • B. It imports only dp, avoiding name clashes and documenting the dependency
  • C. It makes dp private in the using unit
  • D. It is required whenever you use a module
Answer **B.** The `only:` clause restricts the imported names to exactly those listed. That prevents accidental clashes with other `use`d modules and *documents* precisely what this unit depends on — a habit that pays off in large codes where a bare `use` can pull in dozens of public names.

II-2 · What does this print?

type :: point_t
  real(dp) :: x, y
end type
type(point_t) :: p = point_t(3.0_dp, 4.0_dp)
print '(f6.2)', hypot(p%x, p%y)
Answer Prints ` 5.00`. The structure constructor `point_t(3.0_dp, 4.0_dp)` sets `p%x = 3`, `p%y = 4`; the intrinsic `hypot(3, 4)` computes $\sqrt{3^2 + 4^2} = \sqrt{25} = 5$. Component access uses the `%` operator.

II-3 · True or false (justify)

In modern Fortran you should reach for pointer rather than allocatable for a dynamically sized array, because pointers optimize better.

Answer **False.** The default is `allocatable`. It **deallocates automatically** when it goes out of scope (no leaks), **cannot alias** other objects, and is simpler to reason about — and *because* it cannot alias, the compiler often optimizes it **better**, not worse, than a pointer. Reserve `pointer` for cases that genuinely need aliasing or linked structures (trees, callbacks, some C interop).

II-4 · Multiple choice

character(:), allocatable :: s
s = 'Fortran'

What do len(s) and len_trim(s) return?

  • A. 7 and 7
  • B. 20 and 7
  • C. 7 and 6
  • D. 0 and 0
Answer **A, 7 and 7.** With a **deferred-length** allocatable string, assignment (re)allocates `s` to the exact length of the right-hand side. `'Fortran'` is 7 characters with no trailing blanks, so both `len` (total length) and `len_trim` (length ignoring trailing blanks) are 7. Contrast a fixed `character(len=20)`, where `len` would be 20 and `len_trim` 7.

II-5 · Short answer

Distinguish stop from error stop, including the exit-status consequence.

Answer `stop` ends the program **normally** — exit status 0 by default (or a code you supply) — and reads as an intentional "we're finished" halt. `error stop` signals **error termination**: it returns a nonzero error status, so scripts and CI see the run as *failed*, and in a parallel (coarray) program it is required to terminate **all images**. Use `error stop` for "this must not continue" conditions (a bad grid, a failed allocation).

II-6 · What does this print?

character(len=20) :: name
integer :: step = 42
write(name, '(a, i5.5)') 'heat_', step
print '(a)', trim(name)
Answer Prints `heat_00042`. This is an **internal write** — formatting *into* a character variable. The descriptor `i5.5` writes the integer in a field of width 5 with a minimum of 5 digits, **zero-padded**, giving `00042`. Concatenated after `'heat_'`, `name` holds `heat_00042` followed by blanks to length 20; `trim` strips those trailing blanks. This is exactly how the solver builds indexed output filenames.

II-7 · Multiple choice

A dummy argument declared class(shape_t), intent(in) :: s, where shape_t is an extensible type, accepts:

  • A. Only objects of exactly type shape_t
  • B. Objects of shape_t or any type that extends it, resolved at run time (polymorphism)
  • C. Only integer arguments
  • D. Nothing — class is not valid on a dummy argument
Answer **B.** `class(...)` is **polymorphic**: it binds to the declared type or any of its extensions, with the actual type discovered at run time (`select type`, dynamic dispatch of type-bound procedures). Writing `type(shape_t)` instead would restrict the argument to *exactly* `shape_t`, disabling polymorphism.

II-8 · True or false (justify)

After allocate(a(n), stat=ierr), if ierr is nonzero the array a was still successfully allocated.

Answer **False.** A **nonzero** `stat` means the allocation **failed** (typically out of memory); `a` is *not* allocated and must not be used. `stat == 0` is success. The companion `errmsg=` returns a human-readable reason. Checking `stat` on large allocations is exactly the kind of defensive programming that turns a mysterious crash into a clear, handled error.

Part III — Interoperability and Ecosystem

iso_c_binding and bind(c), f2py and array order, the ecosystem (LAPACK/BLAS, fpm).

III-1 · Multiple choice

Which kind constant from iso_c_binding corresponds to a C double?

  • A. c_int
  • B. c_float
  • C. c_double
  • D. c_ptr
Answer **C, `c_double`.** `iso_c_binding` supplies interoperable kinds: `c_int` ↔ `int`, `c_float` ↔ `float`, `c_double` ↔ `double`, `c_ptr` ↔ `void *`. Declaring `real(c_double)` guarantees the Fortran real has the same representation as the C `double` on the other side of the boundary.

III-2 · True or false (justify)

Adding bind(c) to a Fortran procedure lets a C caller link to it by a predictable symbol name, without the compiler's usual name mangling (such as a trailing underscore).

Answer **True.** `bind(c)` gives the procedure a **C-compatible external binding name**, bypassing the compiler-specific mangling (e.g., gfortran's trailing underscore or module-name prefixing). C code can then call it by the expected symbol, and an optional `name='...'` clause sets the exact linker name if you need something specific.

III-3 · Multiple choice

When you pass a NumPy array to an f2py-wrapped Fortran routine, the array should be laid out in:

  • A. C order (row-major), always
  • B. Fortran order (column-major) — pass order='F'/np.asfortranarray, or accept a silent copy
  • C. Any order; f2py converts it with no cost
  • D. A Python list, not an array
Answer **B.** Fortran expects **column-major** memory. NumPy defaults to **C-contiguous** (row-major), so f2py must either receive an F-contiguous array (`np.asfortranarray`, or `order='F'`) or **make a copy** to reorder it. That copy is real, per-call overhead — worth knowing about when you are wrapping a routine you call in a tight loop.

III-4 · Short answer

Why does the book advise calling LAPACK's dgesv instead of shipping your own Gaussian-elimination solver?

Answer LAPACK is **decades-tuned and validated**: it is blocked to exploit cache and an optimized BLAS, it pivots for numerical stability, and it ships in platform-tuned builds (OpenBLAS, MKL). A hand-rolled solver is almost always slower *and* less robust. The lesson recurs throughout the book — reuse the validated library; a naive triple-loop cannot compete with a tuned BLAS.

III-5 · Multiple choice

In the LAPACK routine name dgesv, the leading d denotes:

  • A. "determinant"
  • B. Double-precision real data
  • C. "decomposition"
  • D. "dense"
Answer **B.** LAPACK names encode a pattern: the **first letter is the data type** — `s` single, `d` double, `c` complex, `z` double complex — then the matrix type (`ge` = general) and the operation (`sv` = solve). So `dgesv` is *double-precision, general matrix, solve* $A\mathbf{x} = \mathbf{b}$. Reading the name tells you the routine.

III-6 · True or false (justify)

fpm (the Fortran Package Manager) can build a project, run it, and fetch dependencies declared in fpm.toml.

Answer **True.** fpm is a modern, convention-over-configuration build tool: `fpm build` compiles, `fpm run` runs, `fpm test` runs tests, and dependencies (including git-hosted ones) listed in `fpm.toml` are resolved and built automatically. It is the tool the book uses to turn the loose solver files into a real project.

III-7 · What does this print?

use, intrinsic :: iso_c_binding, only: c_int
integer(c_int) :: n = 5
print '(i0)', n * 2
Answer Prints `10`. `integer(c_int)` is simply an integer of the kind that matches C's `int`; it behaves like any other Fortran integer in arithmetic. `5 * 2 = 10`. The C-interoperable kind matters only at the language boundary, not in ordinary computation.

III-8 · True or false (justify)

When a 2D array crosses the Fortran↔C boundary via bind(c), C effectively sees the indices transposed, because Fortran is column-major and C is row-major.

Answer **True.** The two languages share the same flat memory but *interpret* it differently. A Fortran element `a(i, j)` occupies the same address as C's `a[j-1][i-1]` (with the C array's dimensions declared in swapped order). If you ignore this you get a silently transposed array. The fix is to account for the storage-order swap — transpose the logical indexing or swap the loop bounds — at the boundary.

Part IV — Legacy Fortran

Fixed-form source, COMMON, EQUIVALENCE, GOTO, and the modernization recipe.

IV-1 · Multiple choice

In fixed-form FORTRAN 77 source, a statement label goes in columns:

  • A. 1–5
  • B. 6 only
  • C. 7–72
  • D. 73–80
Answer **A, columns 1–5.** Fixed-form layout is column-sensitive: labels in 1–5; **column 6** is the continuation marker; the statement body in **7–72**; a character (`C` or `*`) in **column 1** marks a comment line; and 73–80 were historically the card sequence number, ignored by the compiler. This rigid layout is a direct descendant of the 80-column punch card.

IV-2 · Multiple choice

The modern replacement for a COMMON block that shared global variables across routines is:

  • A. EQUIVALENCE
  • B. A module with module variables, used where needed
  • C. A GOTO
  • D. A statement function
Answer **B.** A module with public module variables provides shared state **with type checking and explicit interfaces** — replacing the untyped, position-dependent memory overlay that `COMMON` gave you (where a mismatch in the variable list between two routines silently reinterpreted the bytes). Modules are the single biggest safety upgrade in migrating F77.

IV-3 · True or false (justify)

EQUIVALENCE(a, b) makes a and b two names for the same storage, so writing one changes the other.

Answer **True.** `EQUIVALENCE` overlays variables at the same memory location — a deliberate form of **aliasing**, often used in the old days to save scarce memory or to reinterpret bytes. It is dangerous (hidden coupling, type punning, defeated optimization) and in modern code is replaced by derived types or the `transfer` intrinsic when a genuine bit-reinterpretation is required.

IV-4 · What does this print / do? (legacy — read, do not imitate)

This is fixed-form FORTRAN 77 with implicit typing. Read it and answer: what value does X hold, and which label does the arithmetic IF branch to?

      K = 5
      X = K / 2
      IF (X) 10, 20, 30
Answer `X` holds **2.0**, and control branches to **label 30**. Two legacy traps combine here. First, **implicit typing**: names beginning `I`–`N` are integers, everything else is real, so `K` is integer (5) and `X` is real. Second, **integer division**: `K / 2` is `5 / 2 = 2` in integer arithmetic *before* being assigned to the real `X`, giving `2.0` (not `2.5`). The **arithmetic IF** branches on sign — negative → 10, zero → 20, positive → 30 — and `2.0 > 0`, so it jumps to 30. Modern Fortran has none of this: `implicit none` forces declarations, and structured `if`/`select case` replaces the three-way `GOTO`.

IV-5 · Multiple choice

In the modernization recipe, the step usually applied first to a legacy routine is:

  • A. Convert GOTO to structured control
  • B. Add implicit none and explicit declarations
  • C. Parallelize with OpenMP
  • D. Replace EQUIVALENCE with transfer
Answer **B.** Add `implicit none` and declare every variable **first**. It immediately surfaces typos and implicit-typing surprises (an undeclared variable that was silently a real, a misspelled name that was silently a new variable), giving you a compiler-checked safety net before you attempt any deeper restructuring like untangling `GOTO`s or lifting `COMMON` into modules.

IV-6 · Short answer

Why does the modernization chapter insist on a regression test before and after each change?

Answer To prove the refactor **preserved the science**. A regression test compares the modernized code's output against the trusted original — either **bit-for-bit** or within a stated **tolerance** — so you can modernize *incrementally* and catch any accidental behavior change the moment it appears. The value of legacy scientific code is that it is *validated*; the whole point of modernizing (rather than rewriting) is to keep that validation intact.

IV-7 · True or false (justify)

The common GOTO idioms in F77 code can be replaced by structured constructs such as do, exit, cycle, if, and select case.

Answer **True** (for the patterns F77 actually used). Loops built from a label and a conditional `GOTO` become `do`/`do while`; early jumps out of a loop become `exit`; skips to the next iteration become `cycle`; multi-way `GOTO`/computed `GOTO` become `if`/`select case`; and error jumps become structured error handling. The structured versions express the same control flow far more readably. (Genuinely tangled spaghetti may need restructuring, but the modern constructs are expressively sufficient.)

Part V — Numerical Methods in Fortran

Floating point, LAPACK, quadrature, ODEs/RK4, the five-point stencil and the CFL condition.

V-1 · What does this print?

real(dp) :: x
x = 0.1_dp + 0.2_dp
print '(l1)', (x == 0.3_dp)
Answer Prints `F`. Each of `0.1`, `0.2`, and `0.3` is a **non-terminating fraction in binary**, so each is stored rounded. The rounded sum of the first two lands **one ULP away** from the separately rounded `0.3`, so the exact equality is false. The lesson: never compare reals with `==`; test `abs(x - 0.3_dp) < tol` instead.

V-2 · Multiple choice

For the explicit 2D FTCS heat scheme with equal spacing $h$, the stability limit on the diffusion number $r = \alpha\,\Delta t / h^2$ is:

  • A. $r \le 1$
  • B. $r \le 1/2$
  • C. $r \le 1/4$
  • D. no limit — the heat equation is unconditionally stable
Answer **C, $r \le 1/4$.** In 2D the update touches **four** neighbors, which tightens the 1D limit ($r \le 1/2$) to $r \le 1/4$. Exceed it and the highest-frequency (checkerboard) mode is amplified each step — the solution blows up. It is an *explicit-scheme* limit; implicit schemes are unconditionally stable but require solving a system each step.

V-3 · What does this print?

real(dp) :: alpha = 1.0_dp, h = 0.1_dp, dt
dt = 0.25_dp * h**2 / alpha        ! largest CFL-safe step, 2D explicit
print '(f8.5)', dt
Answer Prints ` 0.00250` (one leading space in the width-8 field). With $r_{\max} = 1/4$, the stability bound is $\Delta t \le r_{\max}\, h^2 / \alpha = 0.25 \times (0.1)^2 / 1.0 = 0.25 \times 0.01 = 0.0025$. Note the **$h^2$**: halving the grid spacing quarters the safe timestep, so refining a diffusion simulation gets expensive fast.

V-4 · Multiple choice

Simpson's rule integrates exactly every polynomial up to degree:

  • A. 1
  • B. 2
  • C. 3
  • D. 4
Answer **C, degree 3.** Simpson's rule is built by fitting a **parabola** (degree 2) through three points, but by symmetry it also integrates **cubics** exactly — a free extra order. Its error term is proportional to the **fourth** derivative, so it is exact for anything of degree ≤ 3. That is why it converges much faster than the trapezoidal rule.

V-5 · What does this print?

! One trapezoid for the integral of f(x) = x^2 on [0, 2]
real(dp) :: a = 0.0_dp, b = 2.0_dp, approx
approx = 0.5_dp * (b - a) * (a**2 + b**2)
print '(f6.3)', approx
Answer Prints ` 4.000`. The trapezoidal rule is $\tfrac{b-a}{2}\,(f(a) + f(b)) = \tfrac{2}{2}\,(0 + 4) = 4$. The **exact** integral is $\int_0^2 x^2\,dx = 8/3 \approx 2.667$, so a single trapezoid overestimates badly — it approximates the curve by a straight line. Refining into many subintervals (or switching to Simpson) closes the gap.

V-6 · Short answer

Why is RK4 usually preferred over Euler's method, despite doing four function evaluations per step?

Answer Because of **accuracy order**. Euler is first order (global error $O(\Delta t)$); RK4 is fourth order ($O(\Delta t^4)$). To reach a target accuracy, RK4 can take **far larger** steps — so few that the four evaluations per step are repaid many times over by the drastically smaller step count, and RK4 also has a larger stability region. For smooth non-stiff problems it is the workhorse.

V-7 · True or false (justify)

Computing $\sqrt{x+1} - \sqrt{x}$ directly for large $x$ loses accuracy, and rewriting it as $\dfrac{1}{\sqrt{x+1} + \sqrt{x}}$ fixes it.

Answer **True.** For large $x$ the two square roots are nearly equal, so subtracting them cancels their shared leading digits — **catastrophic cancellation** — leaving a result dominated by rounding noise. Multiplying by $\frac{\sqrt{x+1}+\sqrt{x}}{\sqrt{x+1}+\sqrt{x}}$ gives the algebraically identical $1/(\sqrt{x+1}+\sqrt{x})$, which replaces the dangerous subtraction with an addition and a division and keeps full precision. Rearranging the algebra to avoid the near-equal subtraction is the standard cure.

V-8 · Multiple choice

After a call to dgesv, the info argument is 0 on success. A positive value $i$ means:

  • A. The solution has $i$ correct digits
  • B. $U(i, i)$ is exactly zero — the matrix is singular, so the solve failed
  • C. The $i$-th argument had an illegal value
  • D. $i$ iterations were required
Answer **B.** LAPACK's `info` convention: `0` = success; `info < 0` = the $|info|$-th argument was illegal (a calling mistake); `info > 0` = the factorization found a **zero pivot** $U(i, i) = 0$, so the matrix is singular and no solution was produced. Always check `info` — a silent nonzero is a wrong answer waiting to happen.

V-9 · What does this print?

! Five-point Laplacian at one interior node, grid spacing h = 1
real(dp) :: c = 5.0_dp, up = 4.0_dp, dn = 6.0_dp, lf = 3.0_dp, rt = 7.0_dp
real(dp) :: lap
lap = up + dn + lf + rt - 4.0_dp*c
print '(f6.2)', lap
Answer Prints ` 0.00`. The five-point stencil is $\nabla^2 u \approx (u_{\uparrow} + u_{\downarrow} + u_{\leftarrow} + u_{\rightarrow} - 4u_c)/h^2$; with $h = 1$ the numerator is $4 + 6 + 3 + 7 - 4(5) = 20 - 20 = 0$. A zero discrete Laplacian means the center equals the **average** of its four neighbors — the node is in local steady state, and the explicit heat update would leave it unchanged this step.

Part VI — File I/O and Data Management

Why text does not scale, NetCDF and HDF5, VTK output for visualization.

VI-1 · Multiple choice

The main reason large scientific simulations avoid plain-text output is:

  • A. Text files cannot store numbers
  • B. Text is bulky, slow to parse, and can lose precision on round-trip; binary self-describing formats are compact and exact
  • C. Text files are never portable
  • D. ParaView cannot read any text format
Answer **B.** Formatted text **bloats** file size, is **slow** to write and re-parse, and a decimal round-trip can **lose precision** unless you print enough digits. Formats like NetCDF and HDF5 store exact binary, compress, carry metadata, and read back fast — which is why they are standard for large gridded output.

VI-2 · Multiple choice

NetCDF is especially the community standard for:

  • A. Image editing
  • B. Climate, weather, and ocean gridded data (typically with CF conventions)
  • C. Relational databases
  • D. Web page markup
Answer **B.** NetCDF (self-describing, portable) is the lingua franca of **climate/weather/ocean** data, usually carrying **CF** metadata conventions. HDF5 is the more general hierarchical format for large simulation output — and modern **NetCDF-4 is built on top of HDF5**, so the two are closely related rather than rivals.

VI-3 · True or false (justify)

A self-describing file format stores its metadata (dimensions, units, variable names) alongside the data, so a reader needs no external documentation to interpret it.

Answer **True.** That is the *defining* property of NetCDF and HDF5: the dimensions, attributes, units, and variable names travel **inside** the file. It is what makes the data portable across tools and machines and reproducible years later — you can open the file cold and know what every array means without a separate README.

VI-4 · Multiple choice

To animate the heat solver's evolution in ParaView, the solver writes:

  • A. One VTK file holding all timesteps' averages
  • B. One VTK/.vti file per timestep, forming a time series ParaView plays as an animation
  • C. A single PNG image
  • D. A CSV of the final temperatures only
Answer **B.** A structured-grid VTK file (legacy `.vtk` or XML `.vti`) written **each step** forms a numbered time series that ParaView or VisIt load together and play as an animation. This is the project's visualization increment: `write_vtk(field, filename, step)` called inside the time loop.

VI-5 · What does this print?

character(len=32) :: fname
integer :: step = 7
write(fname, '(a, i6.6, a)') 'heat_', step, '.vti'
print '(a)', trim(fname)
Answer Prints `heat_000007.vti`. The `i6.6` descriptor writes `7` in a width-6 field with a minimum of 6 digits, zero-padded → `000007`, bracketed by the literals `'heat_'` and `'.vti'`. Zero-padding the step keeps the filenames **lexicographically sortable**, so `heat_000007.vti` sorts before `heat_000012.vti` and the animation plays in the right order.

VI-6 · True or false (justify)

HDF5 can chunk and transparently compress a dataset, so a large field can be stored smaller and read back in pieces.

Answer **True.** HDF5 supports **chunked** storage with optional **compression** filters (e.g., gzip). Data is compressed on write and decompressed transparently on read, and the chunking lets you read or write **sub-regions** of a huge array without touching the whole thing — essential when a single field is larger than memory.

VI-7 · Multiple choice

CF (Climate and Forecast) conventions, used with NetCDF, primarily standardize:

  • A. The compression algorithm
  • B. Metadata — standard variable names, units, and coordinate semantics — so tools and people interpret the data consistently
  • C. The programming language the writer must use
  • D. The file extension
Answer **B.** CF conventions standardize the **metadata**: agreed "standard names," units, and the meaning of coordinate/axis variables. They do not dictate compression or language. Following CF is what lets a climate dataset from one center be read and understood correctly by another center's tools.

Part VII — Performance

The no-aliasing advantage, column-major loop order, profile-first, optimization and compiler flags.

VII-1 · True or false (justify)

Because Fortran assumes two dummy arguments passed to a procedure do not overlap in memory, the compiler may keep values in registers and reorder work that a C compiler — fearing aliasing — must reload.

Answer **True.** Fortran's **no-aliasing** rule for dummy arguments is a *promise to the optimizer*: distinct arguments name distinct storage. Freed of the worry that a write through one pointer changed another, the compiler can hold values in registers, vectorize, and reorder freely. C must assume the worst unless you add `restrict`. This is a core reason equivalent Fortran numerical loops often beat C.

VII-2 · What does this print / which is faster?

Both nests zero the same $n \times n$ array. Which is cache-friendly in Fortran, and why?

! Version A
do i = 1, n
  do j = 1, n
    a(i, j) = 0.0_dp
  end do
end do

! Version B
do j = 1, n
  do i = 1, n
    a(i, j) = 0.0_dp
  end do
end do
Answer Neither prints anything — the difference is **speed**, and **Version B** is the cache-friendly one. Fortran is **column-major** (first index fastest), so B's inner loop over `i` walks memory with **unit stride**, using each cache line fully. Version A's inner loop over `j` jumps `n` elements per step, touching a new cache line almost every iteration; for large `n` it can be several times slower. Same result, very different performance — the payoff of understanding memory order.

VII-3 · Multiple choice

The rule "profile before you optimize" exists mainly because:

  • A. Profilers make code faster automatically
  • B. Programmers routinely guess the hot spot wrong; measurement shows where the time actually goes
  • C. Optimization is not allowed without a profile
  • D. A profiler can replace the compiler
Answer **B.** Intuition about bottlenecks is unreliable — the slow part is often not where you expect. A profile **measures** it, so you spend effort where it pays. The empirical rule of thumb is that most of the time lives in a small fraction of the code (the 80/20 rule); optimizing anything else is wasted work (and often makes the code less readable for no gain).

VII-4 · Multiple choice

To measure the wall-clock time of a parallel region, you should use:

  • A. cpu_time, which is the elapsed real time
  • B. system_clock, which measures elapsed real time
  • C. neither — just estimate it
  • D. the -O3 flag
Answer **B, `system_clock`.** It measures **elapsed wall time**. `cpu_time` returns processor time, which for a multi-threaded region **sums across cores** — so a region that took 1 second on 8 threads might report ~8 CPU-seconds, badly overstating the wall time. For speedup and scaling numbers you want wall time.

VII-5 · True or false (justify)

SIMD vectorization means the compiler runs your loop on multiple CPU cores at once.

Answer **False.** SIMD (*single instruction, multiple data*) processes **several array elements per instruction within one core**, using vector registers. Running across **multiple cores** is thread-level parallelism (OpenMP, coarrays). They are **orthogonal** and combine well: vectorize the inner loop *and* spread the outer loop across threads.

VII-6 · Short answer

Why can -Ofast change the printed result of a floating-point reduction when -O2 does not?

Answer `-Ofast` turns on `-ffast-math`, which lets the compiler **relax IEEE rules** — most relevantly, to **reassociate** (reorder) arithmetic. Floating-point addition is **not associative**, so a reordered sum can land a few ULPs away from the strictly left-to-right sum, changing the printed value. `-O2` preserves IEEE semantics, so the result is reproducible. `-Ofast` trades that strict reproducibility (and NaN/Inf handling) for speed — use it knowingly.

VII-7 · Multiple choice

The gfortran flag -fopt-info is used to:

  • A. Turn off optimization
  • B. Report which loops were vectorized/inlined/unrolled — and often why one was not
  • C. Link LAPACK
  • D. Enable OpenMP
Answer **B.** Optimization-report flags (`-fopt-info` for gfortran, `-qopt-report` for Intel) tell you what the optimizer actually did — and, crucially, when a loop was **not** vectorized, often *why* (a dependency, a non-unit stride, a function call). That feedback is how you restructure code so the compiler can help you.

VII-8 · True or false (justify)

Cache blocking (tiling) helps because it reprocesses a small block of data that fits in cache before moving on, raising the reuse of each loaded cache line.

Answer **True.** Tiling restructures loops so a **block small enough to fit in cache** is fully reused before it is evicted, cutting the number of times data must be reloaded from main memory. It is a classic win for matrix multiply and stencil sweeps on data far larger than cache, where naive traversal reloads the same values many times.

Part VIII — Parallel Programming

Amdahl's Law, coarrays, OpenMP and reduction, MPI, GPU offload.

VIII-1 · What does this print?

real(dp) :: p = 0.8_dp
print '(f6.2)', 1.0_dp / (1.0_dp - p)
Answer Prints ` 5.00`. This is the **Amdahl ceiling** — the maximum speedup on *unlimited* cores — which is $1/(1 - p)$. With $p = 0.8$ (80% parallel), that is $1/0.2 = 5$. No matter how many cores you add, the remaining 20% serial work caps the speedup at 5×.

VIII-2 · What does this print?

real(dp) :: p = 0.75_dp        ! 75% parallelizable
integer  :: n = 4              ! 4 cores
print '(f6.3)', 1.0_dp / ((1.0_dp - p) + p / real(n, dp))
Answer Prints ` 2.286`. Amdahl's Law for finite $N$ is $S(N) = 1 / \big((1 - p) + p/N\big)$. Here $(1 - 0.75) + 0.75/4 = 0.25 + 0.1875 = 0.4375$, and $1/0.4375 = 16/7 = 2.2857\ldots$, which `f6.3` rounds to `2.286`. Note it is already well short of 4× on 4 cores — the serial quarter dominates.

VIII-3 · Multiple choice

A code is 99% parallelizable. Its maximum speedup on unlimited cores is:

  • A. 99×
  • B. 100×
  • C. 1000×
  • D. unbounded
Answer **B, 100×.** Ceiling $= 1/(1 - p) = 1/0.01 = 100$. The remaining **1% serial** fraction caps everything. This is why shrinking the serial fraction matters so much near the top: going from 99% to 99.9% raises the ceiling from 100× to 1000×.

VIII-4 · Multiple choice

In !$omp parallel do reduction(*:prod), each thread's private copy of prod is initialized to:

  • A. 0
  • B. 1 (the identity for multiplication)
  • C. the value prod had before the region
  • D. huge(prod)
Answer **B, 1.** A reduction initializes each private copy to the **identity** of the operator — `0` for `+`, **`1` for `*`** — so the per-thread partial products are correct and combine cleanly. The variable's pre-region value is folded into the final result exactly **once** at the end, not into every copy.

VIII-5 · What does this print?

real(dp) :: s = 0.0_dp
integer  :: i
!$omp parallel do reduction(+:s)
do i = 1, 5
  s = s + real(i*i, dp)
end do
!$omp end parallel do
print '(f6.1)', s
Answer Prints ` 55.0`. The loop sums $i^2$ for $i = 1\ldots5$: $1 + 4 + 9 + 16 + 25 = 55$. The **`reduction(+:s)`** clause gives each thread a private partial sum and combines them safely, so despite the parallelism the printed result is **deterministic** — `55.0`. (Only the invisible *schedule* — which thread did which iterations — is nondeterministic; the value is not.)

VIII-6 · True or false (justify)

A coarray declared real(dp) :: u(nx)[*] gives each image its own local u, and image 2 can read image 1's copy with u(3)[1].

Answer **True.** A coarray exists **once per image** (the SPMD model), and the cosubscript in `[ ]` selects *which image's* copy you mean. `u(3)` (no brackets) is the local element on the current image; `u(3)[1]` reaches across to element 3 on **image 1**. This one-notation remote access is exactly how the solver's halo exchange is written between neighboring subdomains.

VIII-7 · Multiple choice

mpi_allreduce differs from mpi_reduce in that:

  • A. It is slower and never used in practice
  • B. Every process receives the reduced result, not just the root
  • C. It only works for summation
  • D. It requires a GPU
Answer **B.** `mpi_reduce` delivers the combined value to the **root** rank only; `mpi_allreduce` combines *and* distributes the result to **all** ranks (a reduce followed by a broadcast). It is the natural choice when every process needs the global quantity — for example a global residual norm that all ranks must test to decide whether to stop iterating.

VIII-8 · Short answer

In an MPI-parallel stencil solver, what is a ghost (halo) cell and why is it needed?

Answer A ghost/halo cell is a **local copy of a neighboring subdomain's boundary** row or column, stored just outside a process's owned region. The five-point stencil updating an edge cell needs values from *outside* that process's data; the halo holds them. Each timestep, neighboring processes **exchange** their halos (send/recv, ideally non-blocking `mpi_isend`/`mpi_irecv`) so every interior update has the neighbor data it requires. It is the price of splitting a grid across distributed memory.

VIII-9 · True or false (justify)

On a GPU offload, the host↔device data transfer is often the real bottleneck, so you want to move data over once and keep it resident across many kernel launches.

Answer **True.** The host-to-device link (e.g., PCIe) is **slow** relative to on-device compute, so copying the field back and forth every timestep can erase — or reverse — any speedup. The winning pattern is a **persistent data region**: transfer the field to the device once, run many kernel launches (timesteps) on it there, and copy back only when you actually need the data on the host (e.g., to write output). Minimize the transfers, not just the compute.

Part IX — Real-World Fortran

Code anatomy, testing and reproducibility, capstone verification and validation.

IX-1 · Multiple choice

In a well-organized scientific Fortran code, the unit that owns the time loop and orchestrates the others is best described as the:

  • A. utility module
  • B. driver (the main program)
  • C. physics module
  • D. I/O module
Answer **B, the driver.** The driver/program does setup, runs the time loop, and calls into the solver, physics, I/O, and utility modules. Separating these architectural roles — driver vs solver vs physics vs I/O vs utility — is what keeps a hundred-thousand-line code navigable, and it is the layout the project reorganizes into.

IX-2 · True or false (justify)

Verification asks "are we solving the equations right?" and validation asks "are we solving the right equations?"

Answer **True.** **Verification** = the code correctly solves the *intended mathematics* (e.g., it converges at the expected order; it matches an analytical solution). **Validation** = the model matches *reality* (experiment or observation). The capstone does verification against an analytical steady state; validation would compare the simulation to measured data. Confusing the two is a classic computational-science error.

IX-3 · Multiple choice

A good unit test for the solver's Laplacian routine checks that it:

  • A. runs without crashing
  • B. returns the known analytical value on a simple input (e.g., zero for a linear field, or a manufactured solution)
  • C. compiles
  • D. uses OpenMP
Answer **B.** A numerical unit test asserts against a **known answer** — a case where you can derive the exact result (a manufactured/analytical solution) — within a tolerance. "Doesn't crash" and "compiles" are necessary but far too weak: a routine can compile, run, and still return numerically wrong values. Test the *numbers*.

IX-4 · What does this print?

real(dp) :: got = 0.99997_dp, expected = 1.0_dp, tol = 1.0e-3_dp
print '(l1)', abs(got - expected) < tol
Answer Prints `T`. This is a **tolerance-based regression check**: $|0.99997 - 1.0| = 3\times10^{-5}$, which is less than the tolerance $10^{-3}$, so the test passes. Numerical tests compare within a tolerance rather than with `==`, because floating-point results rarely reproduce bit-for-bit across compilers, flags, and optimization levels.

IX-5 · Short answer

Name three things a computational scientist must record so a run is reproducible.

Answer Any three of: the **compiler and version**; the exact **compiler flags**; the **input files/parameters** (and any random **seeds**); the **code version** (git commit); and the relevant **library versions** (e.g., which BLAS) and hardware. Recording these is what defeats the "works on my machine" problem and lets someone else — including future you — reproduce the numbers.

IX-6 · True or false (justify)

A convergence study that halves the grid spacing and sees the error drop by about a factor of four is consistent with a second-order-accurate spatial scheme.

Answer **True.** Second-order accuracy means error $\sim O(h^2)$, so halving $h$ multiplies the error by $(1/2)^2 = 1/4$. Observing that **4× error reduction per halving** is exactly the check that verifies the five-point stencil is achieving its designed order — a standard piece of code verification, and precisely what the capstone's convergence study demonstrates.

IX-7 · Multiple choice

Continuous integration (CI) for a Fortran project most directly gives you:

  • A. a faster executable
  • B. automatic building and running of the test suite (often across several compilers) on every push
  • C. a GPU
  • D. a NetCDF file
Answer **B.** CI (e.g., GitHub Actions) **rebuilds and runs the tests automatically on every commit**, frequently across multiple compilers and versions. It catches breakage and portability problems early — before they reach a collaborator or a production run — and is a cornerstone of treating scientific code as real software.

Part X — The Fortran Future

Fortran 2023, the standards process and the fortran-lang renaissance, and Fortran careers.

X-1 · Multiple choice

Which of these is a Fortran 2023 addition?

  • A. Coarrays
  • B. Conditional expressions (an inline if-then-else expression) and degree-argument trig (sind, cosd)
  • C. implicit none
  • D. Modules
Answer **B.** Fortran 2023 added **conditional expressions**, **degree** trig intrinsics (`sind`/`cosd`/`tand`), **enumeration types**, `typeof`/`classof`, and better C interoperability. Coarrays arrived in 2008, and both `implicit none` and modules came with Fortran 90 — the language keeps evolving on a roughly decadal cycle.

X-2 · Multiple choice

The Fortran standard evolves through:

  • A. one vendor's unilateral decisions
  • B. the ISO committee WG5 and its US body J3, via proposals and formal votes
  • C. popular vote on social media
  • D. nothing — it has been frozen since 1977
Answer **B.** **WG5** (the ISO working group) and **J3** (the US technical committee) develop the standard through submitted proposals, discussion, and formal votes — an open, deliberate process. Far from frozen, the language has shipped 95, 2003, 2008, 2018, and 2023, with work ongoing.

X-3 · True or false (justify)

LFortran is a modern compiler that can run Fortran interactively, like a REPL or Jupyter experience.

Answer **True.** LFortran is a modern **LLVM-based** compiler with an **interactive** mode (including Jupyter), so you can evaluate Fortran expression-by-expression rather than only through the edit-compile-run cycle. It is part of the `fortran-lang` renaissance alongside **stdlib**, **fpm**, and the online **playground** — concrete evidence that "modern Fortran is a modern language."

X-4 · Multiple choice

Which employers most classically hire Fortran programmers?

  • A. National labs, weather/climate services, aerospace, and computational-science academia
  • B. only video-game studios
  • C. only web-startup front-end teams
  • D. nobody — the language is dead
Answer **A.** The classic employers are **national labs** (Oak Ridge, Livermore, Los Alamos, Argonne, Sandia), **weather/climate centers** (NOAA, ECMWF, the Met Office), **aerospace**, **energy**, and academic **HPC** groups — anywhere large, long-lived numerical simulations run on serious hardware.

X-5 · True or false (justify)

Modern Fortran skill is best hidden on a résumé, because it signals that you only know an old language.

Answer **False.** The book's stance is the opposite: in HPC and scientific-computing sectors, Fortran is an **asset**, and there is a genuine **shortage** of people who can write and maintain it. Present it as a strength — ideally paired with Python/C and HPC experience (MPI, OpenMP, GPUs) — because that combination is exactly what those employers struggle to hire.

X-6 · Short answer

By the end of the book, what portfolio artifact can the reader present, and why is it compelling?

Answer The complete **2D heat-equation solver** — modular, **validated** against an analytical solution, **profiled and optimized**, **parallelized** (OpenMP/coarrays/MPI), producing **ParaView** visualizations, and written up like a short paper. It is compelling because it demonstrates the **entire scientific-software lifecycle** — physics → numerics → software engineering → HPC → communication — in one real, runnable code, which is precisely what a computational-science employer wants to see.

X-7 · True or false (justify)

Because few new programmers learn Fortran, maintaining and modernizing critical legacy scientific codes has become a sought-after, well-paid niche.

Answer **True.** Enormous amounts of validated infrastructure — weather, climate, engineering, defense, energy — are written in Fortran, and with fewer people learning it, those who **can** read, maintain, and modernize these codes are in demand. The book frames this shortage not as a warning but as an **opportunity**: rare, valuable, and durable skills sit at the intersection of Fortran, numerical methods, and HPC.