Guidance for coding agents working in this repository.
NFFT3 is a C library for computing nonequispaced fast Fourier transforms and related transforms. It is a mature, research-grade numerical library.
Transforms implemented (each is its own module under kernel/ with a public
API in include/nfft3.h):
- NFFT — nonequispaced FFT (forward + adjoint), the core module (
kernel/nfft) - NNFFT — nonequispaced in both time and frequency (
kernel/nnfft) - NFCT / NFST — real-valued (co)sine transforms (
kernel/nfct,kernel/nfst) - NFSFT — transform on the sphere S² (
kernel/nfsft) - NFSOFT — transform on the rotation group SO(3) (
kernel/nfsoft) - NSFFT — sparse/hyperbolic-cross FFT (
kernel/nsfft) - FPT — fast polynomial transform (
kernel/fpt) - solver — generalised inverse transforms via iterative methods (CGNR/CGNE)
- shared helpers in
kernel/util
Application/example programs live in applications/ (fastsum, fast Gauss
transform, MRI, polar FFT, Radon, S² quadrature, …) and examples/.
- Language: C (C99). Benchmarks are C++17.
- Build system: GNU Autotools (autoconf/automake/libtool) +
make. Experimental CMake support. - Hard dependency: FFTW3 (
libfftw3-dev). Single, double, and long-double FFTW variants are all used depending on precision. - Tests: CUnit (
libcunit1-dev). - Benchmarks: CodSpeed C++ integration built on google_benchmark (CI-oriented; see §4).
- Interfaces: Julia (
julia/), Matlab/Octave (matlab/). - The dev container (
.devcontainer/) is Ubuntu 24.04 with gcc-14, clang, FFTW (all precisions), CUnit, Octave, Julia, autotools, and clang-format preinstalled. Assume these are available on the local host.
The library is precision-agnostic: the same C sources compile in float,
double, or long-double precision. Read CONVENTIONS.md before editing C code.
Key rules:
- Name mangling: use
Y(foo)for names exported library-wide (expands tonfftf_/nfft_/nfftl_by precision),X(foo)for module-local names (e.g.nfct_fooinside the NFCT module), andFFTW(foo)for FFTW names. Never hard-code anfft_/nfftf_/nfftl_prefix. - Use the type aliases
R(real),E(real, possibly extended precision),C(complex);A(...)for assert,CK(...)for check. - Indentation is 2 spaces, BSD brace style. A
.clang-formatis provided. - New code must keep the float/double/long-double build matrix working.
This is a source checkout, so the configure script must be generated first.
# One-time (and after editing any Makefile.am / configure.ac):
./bootstrap.sh # runs libtoolize + autoreconf
# Configure. Build ALL modules, tests, examples, and applications:
./configure --enable-all --enable-openmp
# Compile:
make -jUseful ./configure flags:
--enable-all— build all transform modules (plus examples & applications).--enable-openmp— multithreaded (OpenMP) build; also produces the*_omplibrary and thecheckall_threadstest binary.--enable-float/--enable-long-double— change precision (default is double). These are mutually exclusive. Build each precision in a separate configured tree.--with-window=ARG— window function:kaiserbessel(default),gaussian,bspline,sinc,dirac.--enable-exhaustive-unit-tests— larger, slower test cases (CI uses this).--enable-julia— build the Julia interface (double precision + shared libs only).--with-matlab=/path/--with-octave=/path— build the MATLAB/Octave interface.--enable-tests- build the CUnit test programs.--enable-benchmarks --with-codspeed=<path>— Autotools benchmark build; benchmarks are now built in CI with CMake, easier to integrate (see §4)../configure --helplists everything.
A representative "build everything" configure line matching CI:
./configure --with-window=kaiserbessel --enable-all \
--enable-exhaustive-unit-tests --enable-openmp --enable-julia
make -jThe build produces libnfft3<suffix>.la (and ..._omp.la with OpenMP) in the
top directory, where <suffix> is empty for double, f for float, l for
long-double.
Tests use CUnit and only build when the tests are enabled and CUnit is detected at configure time.
make check # builds + runs the test suite, prints PASS/FAIL summaryWhat this runs:
tests/checkall— the full single-threaded suite.tests/checkall_threads— the same suite against the OpenMP library (only when configured with--enable-openmp).
Checking which tests pass/fail:
-
The
make checkconsole output gives the per-test and overall pass/fail summary, and writestests/checkall.log/tests/checkall.trs(automake test logs). -
CUnit also emits a detailed XML report at
tests/CUnitAutomated-Results.xml(and..._threads-Results.xml). This can be converted to JUnit with the bundled stylesheettests/cunit2junit.xsl. -
To re-run a single binary directly and capture full output:
tests/checkall # or: tests/checkall_threads
The C tests (tests/nfft.c, nfct.c, nfst.c, check_nfsft.c, …) validate
transforms against reference data in tests/data/ and against the direct
(slow) transforms. Use --enable-exhaustive-unit-tests for the thorough set.
The tests/data/ reference data is produced by a Python generator (tests/refgen/);
see docs/agents/test-methodology.md for the test
methodology and how to regenerate it.
tests/window.c bounds the window evaluations themselves against reference
values. The B-spline and sinc-power tables are printed by tests/windowref for
pasting into that file; see the same document.
Test accuracy figures (each case's err vs bound) are rendered into an
in-tree HTML accuracy report (vector heatmaps, served via GitHub Pages) and a
per-module PR comment; set NFFT_BENCH_OUT=<file> when running tests/checkall to
emit them. See docs/agents/accuracy-tracking.md
and ADR-0004.
Benchmarks (benchmarks/bench_nfft_direct.cpp) use the CodSpeed C++ integration
and run in CI for continuous regression tracking. Build them with the CMake build
(self-contained: FetchContent fetches/builds codspeed-cpp, submodules and all). A
single knob, -DNFFT_BENCHMARK_MODE=, both enables the benchmark build and picks the
measurement mode — baked in at build time:
off(default) — benchmarks not built.simulation— deterministic instruction count; the metric CI gates on. The binary only measures under callgrind (valgrind --tool=callgrind …, readI refs); run directly it is inert ("unknown environment").walltime— wall-clock timing; the binary writes a local stats JSON to$CODSPEED_PROFILE_FOLDER/results/<pid>.json(offline, no valgrind/runner).
cmake -S . -B build-cmake -DNFFT_BENCHMARK_MODE=walltime -DNFFT_ENABLE_OPENMP=ON \
-DCMAKE_C_FLAGS="-O3 -g -fomit-frame-pointer -fstrict-aliasing -ffast-math"
cmake --build build-cmake -j # binaries in build-cmake/benchmarks/
CODSPEED_PROFILE_FOLDER=/tmp/wt build-cmake/benchmarks/bench_nfft_directSwitching mode = reconfigure the tree with a different -DNFFT_BENCHMARK_MODE (or use
a second build dir). Other options: -DBENCHMARKS_PREFIX=, -DNFFT_AGNOSTIC_BENCHMARKS=
("window:1,openmp:0,precision:1"), -DFETCHCONTENT_SOURCE_DIR_CODSPEED=<path> (reuse
a checkout / offline). CodSpeeds valgrind fork and the codspeed CLI are preinstalled
in the dev container.
Continuous tracking happens via CodSpeed in GitHub Actions (same CMake build,
.github/workflows/bench-linux.yml).
Legacy: the Autotools benchmark path (
./configure --enable-benchmarks --with-codspeed=<path>+make bench) predates CodSpeed; it needs a hand-builtcodspeed-cppand prints no measurements. Use the CMake build.
Two window kernels rest on fitted coefficients. Both tables are generated by a
deterministic mpmath minimax fitter under tests/, and neither header may be
hand-edited.
Bessel I0 (kernel/util/bessel_i0.c), behind the Kaiser-Bessel window.
Table in kernel/util/bessel_i0_data.h, produced by tests/besselgen, which
replaced the former Mathematica notebook:
uv run --with mpmath==1.3.0 python -m tests.besselgen.generatelog|sinc| (kernel/util/sinc.c), behind the B-spline and sinc-power
windows, which raise sinc to the 2m-th power. Table in
kernel/util/sinc_data.h, produced by tests/sincgen:
uv run --with mpmath==1.3.0 python -m tests.sincgen.generateThe generators' own notes are in tests/besselgen/README.md and
tests/sincgen/README.md. tests/sincgen reuses the minimax fitter in
tests/besselgen/remez.py.
| Task | Command |
|---|---|
| Regenerate build system | ./bootstrap.sh |
| Configure (everything) | ./configure --enable-all --enable-openmp |
| Build | make -j |
| Run tests | make check |
| Test results (detail) | tests/checkall, tests/CUnitAutomated-Results.xml |
| Build benchmarks | cmake -S . -B build-cmake -DNFFT_BENCHMARK_MODE=walltime && cmake --build build-cmake -j (§4) |
| Measure (walltime) | CODSPEED_PROFILE_FOLDER=/tmp/wt build-cmake/benchmarks/bench_nfft_direct → /tmp/wt/results/*.json |
| Clean | make clean / full reset: make distclean |
| Format C code | clang-format -i <file> (uses repo .clang-format) |
| Regenerate Bessel I0 coefficients | uv run --with mpmath==1.3.0 python -m tests.besselgen.generate (§5) |
| Regenerate log sinc coefficients | uv run --with mpmath==1.3.0 python -m tests.sincgen.generate (§5) |
| Regenerate window reference values | uv run --with mpmath==1.3.0 python -m tests.windowref.generate (§3) |
CI mirrors these steps in .github/workflows/build-linux.yml, bench-linux.yml,
build-macos.yml, and build-windows.yml across different window × precision × OpenMP
matrices — consult those files when reproducing a CI configuration exactly.
Issues and PRDs live as markdown files under .scratch/<feature>/. See docs/agents/issue-tracker.md.
Five canonical triage roles using their default strings (needs-triage, needs-info, ready-for-agent, ready-for-human, wontfix). See docs/agents/triage-labels.md.
Single-context: one CONTEXT.md + docs/adr/ at the repo root. See docs/agents/domain.md.