Contributing to Mallard¶
Thank you for helping. Bug reports, questions, documentation fixes, examples and new physics are all welcome, from first-time contributors and CFD experts alike. This page is the source of truth for how changes land; if anything here is unclear, that is a documentation bug worth reporting.
Everyone taking part follows the Code of Conduct.
- Reporting bugs and asking questions
- Building and testing
- Code style
- Adding a feature, end to end
- Pull requests and CI
Reporting bugs and asking questions¶
- Bugs: open an issue with the Bug report template. The most useful reports have the smallest input file that shows the problem, the Mallard version or git commit, the build (2D/3D, backend, precision, MPI ranks) and the full error or log. Once Mallard writes a provenance block into its output files, paste that block: it records all of this.
- Features: open an issue with the Feature request template. Describe the problem first (the flow, the quantity, the case you cannot run), then the solution you have in mind and a reference (paper, other code) if there is one. For larger changes, agree on the design in the issue before writing code.
- Questions: check the user guide, the tutorials and every input key first; if they do not answer it, open an issue with the Question template. A question the docs should have answered is also a documentation bug.
- Security issues: do not open a public issue; contact @MatthewBonanni privately as described in the Code of Conduct.
Building and testing¶
The README covers building in full. For development:
git clone --recursive https://github.com/MatthewBonanni/mallard.git
cd mallard
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DUSE_SYSTEM_KOKKOS=OFF \
-DKokkos_ENABLE_THREADS=ON -DMallard_WARNINGS_AS_ERRORS=ON
cmake --build build -j
./build/src/mallard test
- Build 3D in a separate directory with
-DMallard_DIM=3; code that is not dimension specific must build and pass in both. - Run a subset while iterating:
./build/src/mallard test --gtest_filter='TENO*'.mallard testruns the test suite of a build (build/test/mallard-test2d, ormallard-test3din 3D) or every one of an install tree (--dim 2|3picks one). Run the whole suite, 2D and 3D, before asking for review. - With MPI (
-DMallard_ENABLE_MPI=ON), run the multi-rank tests on 2 to 4 ranks:mpirun -n 3 ./build/src/mallard test --gtest_filter='CommTest.*:MeshBlockTest.*:DistributedMeshTest.*:MPITest.*:MPI3DTest.*:ProbesTest.*'. CI also runs the whole suite on 3 ranks, so every test must pass on any number: a test that inspects one rank's cells runs its solver undistributed (set_distributed(false)) or reduces over the ranks, loops over owned cells (halo cells hold stale values), and leaves shared files to rank 0 (files that every rank writes on its own go inrank_scratch_dir()).MALLARD_TEST_TIMEOUT=<seconds>aborts a test that runs longer, naming it, as CI does for the MPI runs. - Python tools (
tools/) need numpy, scipy, matplotlib, imageio and imageio-ffmpeg; a virtual environment works.
Code style¶
- Style: the Google C++ Style Guide, in C++20. Match the surrounding code where the two differ.
- Comments: Doxygen (
/** ... */,@brief,@param,@return) on every public interface: classes, functions and members declared in headers. Elsewhere, keep comments minimal: say why when it is not obvious, never restate what the code does. Rationale and measurements belong in the pull request or indocs/design/. - Warnings are errors. CI builds with
-DMallard_WARNINGS_AS_ERRORS=ONon GCC, Clang and nvcc, with-Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wsign-conversion -Wdouble-promotion -Wold-style-castand more (seemallard_target_warningsin CMakeLists.txt). Fix the cause; use explicitstatic_casts for intended conversions, andrtype(notdouble) for floating-point values so single precision builds too. - Input keys are read only through
Input(src/common/input.h), nevertoml::valueortoml::tabledirectly (configuration fails if you do). Every key setup reads is then validated automatically: misspelled or unused keys are errors, with suggestions. - Error messages teach: an invalid value throws an
InputErrorbuilt from aDiagnostic(key, line fromline_of, what is wrong, the accepted values, the fix and the docs page);missing_key,not_acceptedandcannot_readcover the common cases. A run andmallard checkreport it the same way, andtest/error_messages_test.cppholds an example of each kind. - Kokkos device code must run on CUDA and HIP, not only on the CPU backends:
- Kernels (
KOKKOS_LAMBDA, functors) capture by value; never capturethisor references to host objects. Copy the members a kernel needs into locals first. - Inside kernels: no
std::containers or strings, no exceptions, no virtual calls, no allocation, no I/O. Functions called from kernels areKOKKOS_INLINE_FUNCTION; types captured by kernels are trivially copyable. - Data lives in
Kokkos::Views in the default memory space; copy to the host withKokkos::deep_copy(or a mirror) outside the time loop, not per step. - Results must not depend on the backend, thread count or rank count: reductions that feed the solution must be deterministic (the suite checks bitwise reproducibility across ranks and restarts).
- nvcc compiles every source, including host-only code, so a host-only change can still break the CUDA build; the CUDA compile jobs in CI catch this.
Adding a feature, end to end¶
A feature is done when a user can find it, use it and trust it. A pull request that adds or changes behavior includes:
- Code, in
src/, following the style above. New input keys are read throughInputwith explicit defaults. - Tests in
test/(GoogleTest, registered in test/CMakeLists.txt). Each test must fail for a plausible regression: prefer checks against an exact solution, a design order of accuracy, conservation, symmetry or an independent reference (an exact Riemann solver, Cantera) over restating the implementation. If you cannot name the bug a test would catch, it is not worth keeping. - Documentation, following docs/design/documentation.md:
- the user-facing explanation (when to use it, a minimal TOML snippet, keys with
defaults, pitfalls) in the topic's page of
docs/guide/; - every new key with its default in docs/input.md;
- equations and sources in
docs/theory/, design rationale and measurements indocs/design/, and new references in docs/references.md; - figures drawn by a script in
tools/docs/as a light/dark SVG pair (python tools/docs/make_figures.py --mallard build/src/mallard), never hand-edited images. - An example in
examples/<case>/with itsinput.tomland a row in examples/README.md, ideally compared with a reference (exact solution, experiment, DNS). Add it to the example list in test/example_inputs.h where that applies, so the suite keeps it running. - A changelog fragment: a PR that changes
src/addschangelog/<branch>.<added|changed|deprecated|removed|fixed|security>.mdwith one-bullet per change (see changelog/README.md). Changes that alter results go underchanged. CI fails without one; maintainers add theno-changeloglabel to PRs that do not need one. - README and website: if the feature belongs in the README's feature list, add
it there; the maintainer mirrors README changes on the
website, which builds its pages
from
docs/.
Small fixes need only what applies: a bug fix needs a test that failed before the
fix and a fixed fragment; a documentation fix needs neither.
Pull requests and CI¶
- Branch from
main(on a fork, or a branch if you have access) and open a pull request early as a draft if you want feedback. Keep each PR to one change. - Fill in the PR template: what and why, the exact commands you ran to validate it and their results, and whether it touches device code.
- Pull requests are squash-merged: the PR title becomes the commit subject on
main, so make it descriptive. Intermediate commits on the branch need not be tidy; push new commits rather than rewriting history once a review has started. - CI does not run on every push. When a PR is ready to land, a maintainer (or
you, with write access) runs
gh workflow run ci.yml --ref <branch>. The run sets the commit statusCIon the head commit: pending while it runs, then success or failure, and its Details link shows every job and its log.mainrequires a passingCIstatus on the PR's current head, so a new push needs a new run. CI builds with warnings as errors and runs the full suite in 2D and 3D, with OpenMP, serial, MPI on 1 to 4 ranks and single precision, plus a single-precision Clang build and the tutorials and case templates. Each job also shows on the PR as its own status,CI / <job>. Documentation-only changes skip the build jobs (the run takes about a minute).mainruns CI on every merge. - After resolving a merge conflict on a PR whose CI already passed, rerun only
what the resolution can affect:
gh workflow run ci.yml --ref <branch> -f only=openmp,mpi-3d(backend names,tutorials,device,fuzz). TheCIstatus records the subset. A resolution in build files, shared headers or anything the whole suite exercises needs the full run. - Device builds: CUDA, HIP (ROCm) and SYCL (oneAPI), 2D and 3D, compile only,
run when a branch's changes touch device code:
tools/dev/device_changes.shdecides (a changedsrc/file with Kokkos device constructs, or a Kokkos update).mainalso runs the CUDA builds on every push.-f device=trueforces all three. - GPU CI. Device code is also run on NVIDIA A100 GPUs by a maintainer-run GPU
CI, outside GitHub Actions: CUDA 12 and 13 builds in 2D and 3D, the full test suite
on the GPU (including the CUDA-graph and NCCL paths), the MPI tests on 2 ranks, and
the performance suite against per-hardware baselines. It
posts the commit statuses
gpu/testsandgpu/perfon the PR's head commit, so its results show on the PR next toCI. A PR that changes device code merges only with both passing. You do not need a GPU to contribute: say in the PR template whether device code changed, and a maintainer runs it. Performance claims need numbers from the performance suite or a real case, before and after. - Sanitizers (AddressSanitizer and UndefinedBehaviorSanitizer) run nightly on
main.
By contributing, you agree that your contributions are licensed under the Apache License, Version 2.0.