33 min read

> "Write programs that do one thing and do it well. Write programs to work together."

Prerequisites

  • 2
  • 3
  • 5
  • 8
  • 13

Learning Objectives

  • Name the foundational numerical libraries — LAPACK and BLAS — explain that they are written in Fortran, and describe how they are layered so that portable math sits on top of hardware-tuned kernels.
  • Identify the roles of FFTW, NetCDF, HDF5, and MPI in scientific computing, and say which later chapter puts each to work.
  • Create, build, and run a project with fpm, the Fortran Package Manager, and add an external dependency through an fpm.toml manifest.
  • Use the Fortran standard library (stdlib) for common tasks, and locate the fortran-lang community's resources — the Discourse forum, the online playground, and LFortran.
  • Choose appropriate tooling — a language server (fortls), a documentation generator (FORD), a unit-test framework (pFUnit), and a build system — for a real Fortran project.

Chapter 16: The Fortran Ecosystem — Libraries, Tools, and the Community

"Write programs that do one thing and do it well. Write programs to work together." — Doug McIlroy, on the Unix philosophy

Overview

A language is more than its grammar. When you reach for Python you are not really reaching for for loops and list comprehensions — you are reaching for NumPy, pandas, pip, pytest, and a hundred thousand packages on PyPI. The grammar is the smallest part of what you actually use. The same is true of Fortran, and this chapter is the map of everything around the language: the numerical libraries that the rest of scientific computing quietly stands on, the package manager and standard library that a generation of new tooling has built, and the editors, documentation generators, and test frameworks that make a Fortran project a pleasant place to work rather than a lonely one.

There is a story people tell — you have heard it — that Fortran has no ecosystem, that it is a language you write alone in a text editor with a Makefile you inherited and do not understand. That story was never quite true, and in the last several years it has become emphatically false. The libraries were always there; what is new is the modern connective tissue — a real package manager, a growing standard library, a language server, an online playground — most of it built in the open by the fortran-lang community since roughly 2020. This chapter closes Part III by showing you that tissue, so that when you leave the interoperability chapters and head into the numerical and performance work of Parts V through VIII, you know exactly which library to call and which tool to install.

In this chapter, you will learn to:

  • Name LAPACK and BLAS, explain why they are the load-bearing floor of numerical computing, and understand the layering that makes them both portable and fast — the groundwork for Chapter 21, where you will actually call them.
  • Place FFTW, NetCDF, HDF5, and MPI in the landscape, and know which later chapter teaches each one properly.
  • Stand up a real project with fpmfpm new, fpm build, fpm run — and pull in a dependency through an fpm.toml manifest.
  • Use the stdlib standard library for statistics, I/O, and string handling instead of writing those routines yourself, and find the community that maintains it.
  • Pick the right tooling: a language server for your editor, FORD for documentation, pFUnit for tests, and the right build system for the job.

Learning Paths

How to read this chapter by track. - 🔬 Scientist ("I just want to compute") — §16.1 tells you what you are already standing on; §16.3 and §16.4 (fpm and stdlib) will save you the most time day to day. Read those closely. - 📖 Standard — read straight through; this is the orientation to the whole ecosystem, and every section forward-references the chapter that develops it. - 🔧 Legacy ("I inherited old code with a hand-rolled build") — §16.3 (fpm) and §16.5 (build systems) are your modernization toolkit for the build, the same spirit as Part IV applied to the code itself. - ⚡ HPC ("I need scale") — note the forward pointers in §16.2 to MPI (Chapter 34) and the parallel test story in §16.5; skim §16.4.


16.1 LAPACK and BLAS: The Floor Everything Stands On

Start with the two libraries you have almost certainly used already — probably today, and probably without knowing it. When your Python calls numpy.linalg.solve, when MATLAB inverts a matrix, when R fits a linear model, when your spreadsheet computes a regression, the actual arithmetic very often happens inside compiled Fortran that has been refined for four decades. Those two libraries are BLAS and LAPACK, and they are the reason a chapter on the ecosystem has to begin here: they are the floor that the rest of numerical computing stands on, and that floor is made of Fortran.

Definition (BLAS). The Basic Linear Algebra Subprograms: a standardized set of low-level routines for the fundamental operations of linear algebra — scaling and adding vectors, multiplying a matrix by a vector, and multiplying a matrix by a matrix. BLAS is organized into three levels: Level 1 operates on vectors (work proportional to $n$), Level 2 on a matrix and a vector (work proportional to $n^2$), and Level 3 on two matrices (work proportional to $n^3$). The reference implementation is written in Fortran; it defines the interface that everyone else implements.

Definition (LAPACK). The Linear Algebra PACKage: a large, higher-level library — also written in Fortran — that solves the problems scientists actually pose. It factors matrices, solves systems of equations $A\mathbf{x} = \mathbf{b}$, computes eigenvalues and eigenvectors, and computes singular value decompositions. LAPACK is built on top of BLAS: its algorithms are structured to spend as much of their time as possible inside Level-3 BLAS calls, because that is where the hardware runs fastest.

You will not call these libraries yourself until Chapter 21, which is where the LAPACK anchor of this book reaches its climax — you will solve a linear system with the routine dgesv and an eigenproblem with dsyev, link against a real BLAS, and read a LAPACK man page without flinching. The job here is only to name them, to show you why they matter, and to teach you to read their famously cryptic names, so that when Chapter 21 arrives you are meeting an old acquaintance rather than a stranger.

Reading a LAPACK name is a genuinely useful skill, because the names look like line noise until you know the code. Every classic LAPACK routine name packs a precision, a matrix type, and an operation into a few characters:

   D  GE  SV
   │  │   └─ operation:  SV = solve (factor, then solve A x = b)
   │  └───── matrix type: GE = general (no special structure)
   └──────── precision:   D = double, real       (S single, C complex, Z double complex)

So dgesv is double-precision, general matrix, solve — exactly the routine you would use to solve a dense linear system in real(dp). By the same code, dsyev is double, symmetric, eigenvalues, and dgesvd is double, general, singular value decomposition. Once you see the pattern, a whole wall of routines becomes readable at a glance.

💡 Intuition: Think of BLAS as the assembly language of numerical linear algebra and LAPACK as a standard library written in that assembly language. You almost never write BLAS calls by hand for serious work — you call LAPACK, and LAPACK calls BLAS for you. But the split matters enormously for speed, as we are about to see.

Why does everything depend on these two, specifically? The answer is one of the most elegant pieces of engineering in scientific computing, and it is worth internalizing now.

🚪 Threshold Concept — separation of concerns is how numerical code gets fast. BLAS defines a fixed interface — a promise about what dgemm (matrix-matrix multiply) does — while leaving the implementation open. That single decision splits the world in two. On one side, hardware vendors and specialists write BLAS implementations tuned to the last cache line for their particular chip: OpenBLAS, Intel's MKL, BLIS, Apple's Accelerate, ARM's performance libraries. On the other side, LAPACK — and NumPy, and your code — is written once, portably, against the BLAS interface, and automatically inherits whatever tuned kernel is installed. You do not hand-optimize your matrix multiply; you call a matrix multiply that someone spent a career optimizing, and you get their work for free. This is why, in Chapter 29, a tuned BLAS still beats the hand-rolled loop you will write in Chapter 21 — not because you are careless, but because you are one person and they are a library.

The performance gap between the two BLAS implementations is not subtle. The reference BLAS from Netlib — the Fortran source that defines the interface — is correct but deliberately simple, and a cache-tuned implementation like OpenBLAS or MKL can be many times faster on the same hardware for a large matrix multiply, because it blocks the computation to reuse data in cache and issues vector instructions by hand. That is why the advice you will hear everywhere is: never ship the reference BLAS for production; install a tuned one.

⚡ Performance Note: The whole point of Level-3 BLAS is its arithmetic intensity — the ratio of flops to bytes moved. A matrix-matrix multiply does $O(n^3)$ arithmetic on $O(n^2)$ data, so each number loaded from memory gets reused $O(n)$ times. That reuse is what lets a tuned Level-3 kernel run close to the processor's peak rate, while a Level-1 vector operation — one flop per number loaded — is starved by memory bandwidth. LAPACK's designers restructured the classic algorithms specifically to convert as much work as possible into Level-3 calls. We return to arithmetic intensity when we optimize the solver in Part VII.

You have a small taste of this floor already, from Chapter 5: the intrinsic matmul multiplies two matrices with a single call. For small matrices matmul is fine, and gfortran even offers a flag, -fexternal-blas, that routes large matmul calls to an external BLAS dgemm for you. But matmul only multiplies; the moment you need to solve a system or find eigenvalues, you are in LAPACK's territory, and that is Chapter 21.

🔗 Connection: Recall from Chapter 1 that when you call numpy.linalg.solve, the number-crunching happens in Fortran. Now you can name the Fortran: it is LAPACK's dgesv, sitting on a BLAS dgemm. The Python you wrote in Chapter 15 and the Fortran you will write in Chapter 21 are two windows onto the same compiled library.

📜 From History — how two Fortran libraries came to run the numerical world. The story begins in the 1970s with two Fortran packages, LINPACK (for linear systems) and EISPACK (for eigenvalues), which were the state of the art on the vector supercomputers of the day. LINPACK is also the ancestor of the LINPACK benchmark — still the yardstick the TOP500 uses to rank the world's fastest machines. But those early packages were built around Level-1 BLAS (vector operations), and as processors grew deep cache hierarchies in the 1980s, memory bandwidth — not arithmetic — became the bottleneck, and the old codes could no longer reach the hardware's peak. The response, delivered in the early 1990s, was LAPACK: a ground-up redesign that expressed its algorithms in terms of blocked operations calling Level-3 BLAS, so that vendors could supply one hyper-tuned matrix-multiply kernel and the entire library would run fast on their machine. That architectural bet — portable algorithms on top of a tuned, swappable kernel — is why LAPACK and BLAS became universal, and why a routine designed for a 1990s Cray still delivers on the chip in your laptop. The names are cryptic and the Fortran is old, and both facts are a feature: the interface has been stable long enough that everything in numerical computing could safely build on it.

🔄 Check Your Understanding. 1. Decode the LAPACK routine name sposv. (Precision? Matrix type? Operation?) 2. Why is a Level-3 BLAS operation (matrix-matrix) able to run closer to peak speed than a Level-1 operation (vector-vector)? 3. LAPACK is written once, portably, yet runs fast on many different chips. What makes that possible?

Answers (1) s = single-precision real, po = symmetric positive-definite matrix, sv = solve — so: single-precision solve of a symmetric positive-definite system (via Cholesky). (2) Level-3 does $O(n^3)$ flops on $O(n^2)$ data, so each loaded number is reused $O(n)$ times and the operation is compute-bound rather than memory-bound; the tuned kernel can keep the arithmetic units busy. (3) LAPACK calls the BLAS interface; whichever tuned BLAS implementation is installed (OpenBLAS, MKL, …) supplies the fast machine-specific kernels, so the portable LAPACK code inherits that speed automatically.


16.2 The Other Pillars: FFTW, NetCDF, HDF5, and MPI

LAPACK and BLAS are the deepest floor, but a working scientific code rests on a handful of other libraries so standard that you should know their names and their jobs now, even though each gets its own proper treatment later. Think of this section as a set of signposts: what the library is for, and which chapter teaches it.

FFTW — the Fastest Fourier Transform in the West. Whenever a computation moves between real space and frequency space — signal processing, spectral methods for PDEs, image analysis, digital filtering — it needs a fast Fourier transform, the algorithm that turns an $O(n^2)$ transform into an $O(n \log n)$ one. FFTW is the de-facto standard implementation. It happens to be written in C rather than Fortran, but it ships a first-class Fortran interface and is called from Fortran codes constantly; it is a fine example of the C interoperability you met in Chapter 14 working quietly in production. If your science has a spectrum in it, FFTW is probably in your future.

NetCDF and HDF5 — the data formats that scale. Once a simulation produces more than a few megabytes of output, plain text files stop being viable: they are bulky, slow to parse, not self-describing, and not portable across machines with different byte orders. Two portable binary formats dominate scientific output. NetCDF (Network Common Data Form) is the standard for climate, weather, and ocean data — self-describing, with the metadata conventions that let one lab read another's files decades later. HDF5 (Hierarchical Data Format) is the standard for large, structured simulation output — hierarchical like a filesystem inside one file, with built-in compression and chunking. Both provide official Fortran interfaces. We treat them properly, with working code, in Chapter 25; for now, simply file away that when your text output gets too big, these are where you go.

MPI — the standard for talking across a cluster. No single computer is large enough to run the biggest simulations, so they run across hundreds or thousands of machines that coordinate by passing messages. MPI (the Message Passing Interface) is the standard that defines how. It is not a single library but a specification with several implementations — MPICH, Open MPI, and vendor builds — and modern Fortran talks to it through the mpi_f08 module. MPI is the backbone of distributed-memory HPC, and it is the whole subject of Chapter 34, where you will decompose the heat solver across processes and exchange halo cells. Fortran has an even more distinctive option here — coarrays, a parallel model built into the language itself, which you will meet in Chapter 32 — but MPI is the workhorse of the field.

⚠️ Common Pitfall — the Fortran interface is a separate thing. For several of these libraries, the core is written in C and the Fortran bindings live in a separate package that must be installed and linked in addition to the C core. NetCDF is the classic example: the netcdf-c library and the netcdf-fortran library are distributed separately, and forgetting the second one produces baffling "undefined reference" errors at link time even though "NetCDF is installed." When a link fails on a library you are sure you have, check whether you have its Fortran interface too.

You will notice a common shape across all four libraries: each does one hard thing extremely well, exposes a stable interface, and expects to be composed with the others — LAPACK for the algebra, FFTW for the spectra, NetCDF or HDF5 for the output, MPI for the scale. That composability is McIlroy's Unix philosophy from this chapter's epigraph, applied to numerical computing. The remaining question, and the one the fortran-lang community spent the last few years answering, is how you assemble such pieces into a project without a hand-written Makefile and an afternoon of frustration. That is where fpm comes in.

🔄 Check Your Understanding. 1. Your simulation writes 40 GB of gridded temperature data per run and another group needs to read it on a different machine years from now. Text file, NetCDF, or HDF5 — and why not text? 2. FFTW is written in C. How can a Fortran program call it, and which chapter's techniques make that possible?

Answers (1) NetCDF or HDF5 — both are portable, self-describing binary formats with compression, so they are compact, fast, and readable across machines and time; text is bulky, slow to parse, and not self-describing at that scale. NetCDF is especially idiomatic for gridded climate/weather/ocean data. (2) Through a Fortran interface built on iso_c_binding and bind(c) — the C-interoperability machinery of Chapter 14.


16.3 fpm: The Fortran Package Manager

For most of Fortran's history there was no standard way to build a project or to depend on someone else's code. You wrote a Makefile by hand, you tracked the module compilation order yourself, and if you wanted to use a library you found it, built it, and figured out the link flags on your own. Every project was a small act of archaeology. fpm, the Fortran Package Manager, exists to end that.

Definition (fpm). The Fortran Package Manager: a build tool and dependency manager for Fortran, developed in the open by the fortran-lang community. It builds your project with a single command, it works out the module compilation order automatically, and it downloads and builds the libraries your project depends on — all driven by a small manifest file named fpm.toml. If you have used Rust's cargo or Python's pip/poetry, fpm is the same idea for Fortran.

fpm's great simplification is convention over configuration. You do not tell it where your source lives; it knows, because a project has a fixed shape. Run fpm new heat-solver and it scaffolds this:

heat-solver/
├── fpm.toml          the manifest: name, version, dependencies
├── README.md
├── app/
│   └── main.f90      the program (becomes the executable)
├── src/
│   └── heat_solver.f90   library modules live here
└── test/
    └── check.f90     test programs live here

The rule is simple and worth memorizing: app/ holds programs (each program there becomes an executable), src/ holds the library (your modules), and test/ holds tests. fpm discovers every .f90 file under those directories, figures out which module uses which — the dependency graph you had to track by hand back in Chapter 8 — and compiles them in the correct order. Three commands do almost everything:

$ fpm build          # compile the library, apps, and tests (in dependency order)
$ fpm run            # build if needed, then run the executable in app/
$ fpm test           # build if needed, then run the programs in test/

The manifest, fpm.toml, is written in TOML — a simple key/value format — and for a basic project it is short:

name = "heat-solver"
version = "0.1.0"
license = "MIT"
author = "Your Name"
maintainer = "you@example.com"

[build]
auto-executables = true
auto-tests = true

The real power appears when you add a dependency. Suppose you want the statistics routines from the Fortran standard library (the subject of §16.4). You do not download it, build it, and hunt for link flags; you add one entry to the manifest:

[dependencies]
stdlib = { git = "https://github.com/fortran-lang/stdlib" }

and the next fpm build clones that repository, builds it, and links it for you. Dependencies are named by their git URL, and you can pin them to a specific tag, branch, or commit rev for reproducibility — which, as Chapter 37 will insist, is exactly what you should do for any result you intend to publish.

⚠️ Common Pitfall — pin your dependencies, and check the current spec. The single line above pulls the latest commit of a moving repository, which means your build could change under you tomorrow. For anything reproducible, pin a tag: stdlib = { git = "...", tag = "v0.7.0" }. And because these packages evolve, the exact dependency line a library expects (a particular branch, say) can change between releases — always take the current form from the library's own README rather than from memory (including this book's). (The fortran-lang/stdlib repository URL is stable and canonical; a specific tag string is the kind of detail to verify at the source.)

🐍 Python Comparison: fpm is to Fortran roughly what pip plus a build backend is to Python — but with a crucial difference rooted in the languages. Python is interpreted, so "installing a package" mostly means copying files; Fortran is compiled, so fpm must build every dependency from source, in the right order, with a compatible compiler and flags. That is more work under the hood, and it is why fpm's automatic handling of module compilation order — the thing that made hand-written Fortran Makefiles so tedious — is such a relief. Where Python has PyPI with its hundreds of thousands of packages, Fortran's registry is young and small; but for the first time, the mechanism to share and consume Fortran libraries exists and is pleasant to use.

🐛 Find the Bug. A newcomer adds stdlib to their manifest and fpm build fails to find it:

toml [dependencies] stdlib = "https://github.com/fortran-lang/stdlib"

What is wrong? A bare string value is interpreted as a version requirement from a registry, not a git URL — so fpm looks for a package literally versioned "https://…" and fails. A git dependency must be an inline table with a git key. The fix is one pair of braces:

toml [dependencies] stdlib = { git = "https://github.com/fortran-lang/stdlib" }

The lesson generalizes: in TOML, key = "value" and key = { … } mean different things, and fpm's two ways of naming a dependency — a version string versus a source table — are not interchangeable.

🧩 Try It Yourself. If you have fpm installed, run fpm new sandbox and look at what it created. Open app/main.f90 — it is a "hello, world" — and run fpm run. Then add a module to src/ with a function, use it from app/main.f90, and run again. Notice that you never told fpm about the new file or the dependency between them; it simply worked. That "it simply worked" is the entire point.

We will turn the heat solver itself into an fpm project in this chapter's Project Checkpoint. First, though, meet the most useful dependency you can add.


16.4 stdlib and the fortran-lang Community

Every mature language has a standard library — a batteries-included collection of routines for the things everyone needs: statistics, sorting, string manipulation, file reading, common mathematical helpers. For most of its life, Fortran did not have one. It had a superb set of intrinsic functions for arrays and math (you have used sum, matmul, maxval, and friends since Chapter 5), but if you wanted the mean and standard deviation of a dataset, or to sort an array, or to load a table of numbers from a text file, you wrote it yourself — again, in every project. stdlib is the community's answer.

Definition (stdlib). The Fortran standard library: a community-developed library of general-purpose routines that the language's intrinsics do not cover, maintained under fortran-lang. It is organized into modules with names like stdlib_stats (means, variances, correlations), stdlib_linalg (convenience wrappers over LAPACK-style operations), stdlib_sorting (sorting), stdlib_strings (string utilities), stdlib_io (loadtxt/savetxt for text tables), and stdlib_math (helpers like linspace and arange). It is not part of the ISO Fortran standard — it is a package you depend on — but it fills the same role a standard library fills in other languages.

Using it feels like using any other module. To compute the mean and standard deviation of a dataset, you use the relevant module and call the routine:

use stdlib_stats, only: mean, var
real(dp) :: samples(100)
real(dp) :: mu, variance
! ... fill samples ...
mu       = mean(samples)
variance = var(samples)

That is code you do not have to write, test, or maintain — someone in the community already did, and their version is reviewed and tested across compilers. The catch, of course, is that this snippet needs stdlib present to compile: it will not build with a bare gfortran file.f90 command, because the stdlib_stats module has to be compiled and linked first. That is precisely the problem fpm solves — you add the one-line dependency from §16.3, and fpm build takes care of the rest.

⚠️ Common Pitfall — stdlib code needs the dependency. Because the honesty policy of this book forbids running code, the compilable example files in this chapter's code/ folder use only intrinsics — so they build with a plain gfortran -std=f2018 -Wall command — and show the equivalent stdlib call in a comment. That mirrors the real trade-off: intrinsics compile anywhere with no setup; stdlib gives you more, but it is a dependency you must declare (via fpm) and build. Reach for stdlib in a real project; reach for intrinsics in a throwaway script.

Behind stdlib sits the community that makes the modern Fortran ecosystem worth talking about at all: the fortran-lang project. It is worth knowing what it offers, because it is where you go when you are stuck, curious, or ready to contribute:

  • fortran-lang.org — the hub. Tutorials, a catalogue of packages, news about the language, and links to everything below.
  • The Fortran Discourse forum (fortran-lang.discourse.group) — an active, unusually welcoming place where beginners' questions are answered generously and the standards committee's work is discussed in the open. If you have a "why won't this compile" question, this is where to ask it.
  • The online playground (play.fortran-lang.org) — a Fortran compiler in your browser. You can type a program and run it with no local install at all, which is a genuinely new thing for Fortran and a wonderful teaching tool. It is powered by LFortran.
  • LFortran — a modern, LLVM-based Fortran compiler with an unusual trick: it can run Fortran interactively, statement by statement, the way you would use a Python REPL, and it powers the browser playground. It is still maturing — not yet a drop-in replacement for gfortran on a large production code — so treat it today as a superb exploratory and teaching tool rather than your build compiler. We come back to LFortran, and to what interactive Fortran means for the language's future, in Chapter 39.

The existence of all this — a package manager, a standard library, a forum, a browser playground, a new interactive compiler, most of it built since around 2020 — is the strongest possible rebuttal to the "dead language" myth we opened the book with. Dead languages do not grow standard libraries. This is the theme modern Fortran is a modern language, made concrete not in the syntax but in the ecosystem around it.

🔄 Check Your Understanding. 1. Why can't a program that uses stdlib_stats be compiled with a bare gfortran file.f90 command, and what tool fixes that? 2. What is the difference between a Fortran intrinsic (like sum) and a stdlib routine (like mean) in terms of what you must do to use each? 3. What is LFortran, and why is it — today — better described as an exploratory and teaching tool than as a production build compiler?

Answers (1) The stdlib_stats module must itself be compiled and linked first; a lone gfortran command has no knowledge of it. fpm fixes this: declare stdlib as a dependency in fpm.toml and fpm build fetches, builds, and links it. (2) An intrinsic is part of the language — always available, nothing to install; a stdlib routine lives in an external package you must declare as a dependency and build (via fpm). (3) LFortran is a modern LLVM-based compiler that can run Fortran interactively and powers the online playground; it is still maturing and not yet a drop-in replacement for gfortran on large codes, so use it to explore and teach, and build production code with an established compiler.


16.5 Tooling: Editors, Documentation, Tests, and Builds

The last piece of an ecosystem is the day-to-day tooling — the things that make writing, understanding, documenting, and testing code comfortable. Modern Fortran has real answers here, and adopting them early will make everything that follows more pleasant.

A language server: fortls. If you have used a modern editor with autocompletion, hover-for-docs, jump-to-definition, and live error underlining, you have used a language server — a background program, speaking the editor-agnostic Language Server Protocol (LSP), that understands your code. For Fortran that program is fortls, the Fortran Language Server. Installed once and paired with an editor extension (the "Modern Fortran" extension for VS Code is the common pairing, but Vim, Neovim, and Emacs all speak LSP), it gives you completion for module names and procedures, go-to-definition across files, and errors flagged as you type. It transforms Fortran from "edit in a plain text file and find out at compile time" into the responsive experience you expect from any modern language. This is the single highest-value tool to install first.

Documentation: FORD. Code that no one can understand is code no one can maintain, and the standard way to document a Fortran project is FORD.

Definition (FORD). FORtran Documenter: a tool that generates browsable HTML documentation automatically from your source code and from specially marked comments within it. You annotate a procedure with documentation comments — FORD reads comments that begin with a doubled marker, !! after a declaration or !> before one — and FORD produces a linked website describing every module, type, and procedure, complete with call graphs. It is the Fortran counterpart to tools like Doxygen or Sphinx.

A documented procedure looks like ordinary Fortran with slightly special comments:

!> Compute the arithmetic mean of a 1-D array.
pure function mean_value(x) result(m)
  real(dp), intent(in) :: x(:)   !! the input samples
  real(dp) :: m                  !! the returned mean
  m = sum(x) / real(size(x), dp)
end function mean_value

Because those markers are still just Fortran comments, the code compiles exactly as before — but run ford over the project and you get a searchable manual. We put FORD to work documenting the finished solver in Chapter 37.

Testing: pFUnit (and lighter options). Numerical code that is not tested is numerical code you do not actually trust, and Fortran has dedicated unit-testing frameworks.

Definition (pFUnit). A unit-testing framework for Fortran, developed at NASA's Goddard Space Flight Center, designed with scientific computing in mind — it can even run tests in parallel under MPI, which matters when the thing under test is itself a parallel routine. You write small test procedures that assert expected results (for example, that a computed value equals an analytical one within a tolerance), and pFUnit runs them and reports pass or fail. For smaller projects, the community also maintains lighter, fpm-native frameworks such as test-drive, which needs no special setup beyond an fpm dependency.

The idea common to both is one you will lean on heavily in Chapter 37: pin the code's behavior with automated checks, so that when you optimize the solver in Part VII or parallelize it in Part VIII, a single fpm test tells you instantly whether you changed the numbers you were not supposed to change.

Build systems: Make, CMake, Meson, and fpm. Finally, the question of how a project is built. You have four common answers, and the right one depends on the project:

Build system Best when Notes
fpm new projects, libraries, anything you can shape to its conventions simplest; handles module order and dependencies automatically
CMake large or mixed-language projects, especially with C/C++ ubiquitous in HPC; verbose but very capable
Meson modern projects wanting CMake's power with less ceremony good, growing Fortran support
Make small projects, or legacy codes that already use it universal and simple, but you track module dependencies

For your own new work, start with fpm; it is the least friction by a wide margin. You will meet CMake, Meson, and Make again in the wild in Chapter 36, where we tour how large, real scientific codes are actually organized and built — because the moment you contribute to an existing project, you build it their way.

🔗 Connection: One more tool you already met belongs on this shelf: Compiler Explorer (godbolt.org), which lets you paste a Fortran snippet, pick gfortran, and watch the assembly it generates. It is bookmarked from Chapter 1 and becomes genuinely useful once you can read what the optimizer did in Chapter 27.

🔄 Check Your Understanding. 1. What does a language server like fortls give your editor, and which one file do you install first for the biggest day-to-day improvement? 2. FORD reads special comments to build documentation. Why does adding those comments never break the build? 3. You are starting a brand-new Fortran library from scratch. Which build system is the path of least resistance, and why?

Answers (1) fortls gives autocompletion, hover documentation, jump-to-definition, and live error diagnostics over the Language Server Protocol; installing fortls (with your editor's LSP/Modern-Fortran extension) is the highest-value first step. (2) FORD's markers (!!, !>) are ordinary Fortran comments, so the compiler ignores them entirely — only FORD reads them. (3) fpm — it needs no hand-written build script, discovers your sources, resolves module compilation order, and manages dependencies through one manifest.


Project Checkpoint

Through Part I and Part II, your heat solver grew from a one-line banner into a real modular program: a kinds module owning dp, a heat_solver module with the field update, a heat_io module reading a namelist config, a field_t derived type, and the input validation and error stop you added in Chapter 13. Until now you built it with a gfortran command (or a Makefile) that you maintained by hand. This checkpoint turns the solver into a proper fpm project.

The move is mechanical and satisfying. Scaffold a project, drop your existing modules into src/, put the driver in app/, and describe it in fpm.toml:

$ fpm new heat-solver
$ # move kinds.f90, heat_solver.f90, heat_io.f90 into src/
$ # move the driver (program heat) into app/main.f90
$ fpm build
$ fpm run

Your fpm.toml declares the project and — the point of the exercise — pulls in a dependency, stdlib, so you can replace a hand-rolled statistic (say, the mean interior temperature you print as a sanity check) with a stdlib call later:

name = "heat-solver"
version = "0.1.0"
license = "MIT"

[dependencies]
stdlib = { git = "https://github.com/fortran-lang/stdlib" }

To keep this checkpoint honest under the no-execution rule — and buildable with a bare compiler — the code/project-checkpoint.f90 file is a single-file stand-in for what fpm run would execute: a minimal module plus a driver that does one relaxation step of the update and prints the interior temperature. In the real fpm project the module lives in src/ and the driver in app/, but the arithmetic is identical. On a 3×3 plate with a hot top edge held at 100 and the other edges at 0, one averaging step sets the single interior cell to the mean of its four neighbours, $\tfrac{1}{4}(100 + 0 + 0 + 0) = 25$:

call jacobi_step(u)          ! u starts 0 with a hot top edge = 100
print '(a, f6.2)', 'interior temperature u(2,2) = ', u(2,2)
! Expected: interior temperature u(2,2) =  25.00

How this feeds the capstone. From here on, the solver is an fpm package: reproducible, dependency-aware, and ready for the tests and documentation of Part IX. Every later checkpoint — the real finite-difference core in Chapter 24, the OpenMP and MPI parallelism of Part VIII, the pFUnit tests and FORD docs of Chapter 37 — slots into this same app/src/test/ skeleton. And note the anchor you are laying down for later: when Chapter 21 adds an implicit time step, it will solve a linear system by calling the LAPACK you named in §16.1 — dgesv, linked through this same manifest. You have met the library here; there you will put it to work.


Summary

This chapter mapped the world around the Fortran language — the libraries you stand on, the package manager and standard library the community built, and the tools that make a project comfortable.

Piece What it is Where it's used properly
BLAS low-level linear-algebra kernels (Levels 1/2/3), Fortran reference impl; tuned versions from vendors Ch. 21, Ch. 29
LAPACK solvers, eigenvalues, SVD — Fortran, built on Level-3 BLAS Ch. 21
FFTW fast Fourier transforms (C library, Fortran interface) spectral methods; interface via Ch. 14
NetCDF / HDF5 portable, self-describing scientific data formats Ch. 25
MPI message passing across a cluster (a standard, several implementations) Ch. 34
fpm the package manager: fpm new/build/run/test, fpm.toml manifest, git dependencies this chapter
stdlib the community standard library (stats, sorting, strings, I/O, math) this chapter, Ch. 37
fortls the Language Server — editor autocomplete, go-to-def, live errors your editor, now
FORD auto-generated HTML docs from !!/!> comments Ch. 37
pFUnit unit-testing framework (parallel-capable); test-drive is a lighter option Ch. 37

A fuller, standalone quick-reference for every library and tool named in this chapter — with the link flags, the module names, and the manifest keys collected in one place — is Appendix H (Libraries and Tools Reference).

Reading LAPACK names (memorize the code): a name is precision · matrix-type · operation, e.g. dgesv = double, general, solve; dsyev = double, symmetric, eigenvalues.

The two rules worth remembering: first, numerical performance comes from separation of concerns — portable code (LAPACK, your program) calling a hardware-tuned kernel (the installed BLAS) — which is why you call the tuned library instead of hand-optimizing. Second, fpm's convention over configuration (app/ programs, src/ library, test/ tests) is what finally makes building and sharing Fortran code easy: declare a dependency in fpm.toml, and fpm build does the rest.

Spaced Review

Revisiting Chapter 8 (Modules) and Chapter 13 (Error Handling and Debugging), the foundations this chapter's tooling builds on.

  1. fpm automatically compiles your modules in the correct order. What is it working out for you, and what Chapter 8 fact makes that ordering necessary in the first place?

    Answerfpm computes the module dependency graph — which module `use`s which — and compiles in that order. It is necessary because compiling a module produces a `.mod` file that any module `use`-ing it needs *before* it can be compiled; you cannot compile a user of a module before the module itself. Chapter 8's compilation-order rule is exactly what fpm automates.

  2. A module you put in src/ exposes some procedures with public and hides helpers with private. Why does fpm (and the compiler) not need you to write a separate header or interface file for the public ones, the way C would?

    AnswerA Fortran module provides an *explicit interface* for its public procedures automatically — the compiler checks every call against it. That is one of the core benefits from Chapter 8: modules give you type-checked interfaces "for free," with no separate header to keep in sync.

  3. Your fpm-built solver reads its grid size from a namelist config. Following Chapter 13's discipline, what should happen if the file specifies a non-positive grid size, and which statement ends the run cleanly with a non-zero exit code?

    AnswerValidate the input and refuse to continue: report the bad value and halt with `error stop` (optionally with a message and a non-zero code), rather than allocating a nonsensical array and producing garbage. This is the "validate the config; `error stop` on a bad grid" pattern from Chapter 13.

  4. When fpm build fails while compiling a stdlib dependency, the errors mention files you never wrote. Using Chapter 13's debugging mindset, what is the first thing to check before you suspect your own code?

    AnswerCheck the *boundary* first: your compiler version and the dependency's required version/branch, and whether the dependency spec in `fpm.toml` matches what the library's README currently expects. As in Chapter 13, isolate where the failure actually originates — a dependency's build failing is not your source's bug — before changing your own code.

  5. Why is error stop (Chapter 13) a better failure for a batch simulation launched by a script than a plain stop or letting the program limp on?

    Answer`error stop` terminates with a *non-zero* exit status, which a driving script (or `fpm test`, or CI) can detect to know the run failed; a plain `stop` signals success, and limping on can produce plausible-looking but wrong output — the worst outcome for scientific code.

What's Next

You have now seen the whole language and its ecosystem: the modern features of Part II, the interoperability and libraries of Part III. It is time to look backward. Part IV opens with Chapter 17, where you will learn to read the FORTRAN 77 you will inevitably inherit — fixed-form source, COMMON blocks, GOTO — not as a museum piece but as validated science waiting to be modernized. The tools you just met (fpm, fortls, tests) are exactly what you will use to bring that old code safely into the present.