> "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.
In This Chapter
- Overview
- Learning Paths
- 16.1 LAPACK and BLAS: The Floor Everything Stands On
- 16.2 The Other Pillars: FFTW, NetCDF, HDF5, and MPI
- 16.3 fpm: The Fortran Package Manager
- 16.4 stdlib and the fortran-lang Community
- 16.5 Tooling: Editors, Documentation, Tests, and Builds
- Project Checkpoint
- Summary
- Spaced Review
- What's Next
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 fpm —
fpm new,fpm build,fpm run— and pull in a dependency through anfpm.tomlmanifest. - 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'sdgesv, sitting on a BLASdgemm. 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-clibrary and thenetcdf-fortranlibrary 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 oniso_c_bindingandbind(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-langcommunity. 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 namedfpm.toml. If you have used Rust'scargoor Python'spip/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). (Thefortran-lang/stdlibrepository 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
pipplus 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 buildfails 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 agitkey. The fix is one pair of braces:
toml [dependencies] stdlib = { git = "https://github.com/fortran-lang/stdlib" }The lesson generalizes: in TOML,
key = "value"andkey = { … }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 sandboxand look at what it created. Openapp/main.f90— it is a "hello, world" — and runfpm run. Then add a module tosrc/with a function,useit fromapp/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 likestdlib_stats(means, variances, correlations),stdlib_linalg(convenience wrappers over LAPACK-style operations),stdlib_sorting(sorting),stdlib_strings(string utilities),stdlib_io(loadtxt/savetxtfor text tables), andstdlib_math(helpers likelinspaceandarange). 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 plaingfortran -std=f2018 -Wallcommand — 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
usesstdlib_statsbe compiled with a baregfortran file.f90command, and what tool fixes that? 2. What is the difference between a Fortran intrinsic (likesum) and a stdlib routine (likemean) 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) Thestdlib_statsmodule must itself be compiled and linked first; a lonegfortrancommand has no knowledge of it. fpm fixes this: declare stdlib as a dependency infpm.tomlandfpm buildfetches, 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.
-
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?
Answer
fpm 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. -
A module you put in
src/exposes some procedures withpublicand hides helpers withprivate. 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?
Answer
A 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. -
Your fpm-built solver reads its grid size from a
namelistconfig. 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?
Answer
Validate 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. -
When
fpm buildfails 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?
Answer
Check 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. -
Why is
error stop(Chapter 13) a better failure for a batch simulation launched by a script than a plainstopor 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.