Style Guide

This page collects the detailed coding conventions for DNDSR. It complements the short guardrails in AGENTS.md.

C++

Formatting

  • Use Allman braces (opening brace on its own line).

  • Use #pragma once in headers; no #ifndef guards.

  • Preserve include order; do not auto-sort includes.

The clang-format configuration lives at src/.clang-format.

clang-format -i src/DNDS/SomeFile.hpp

Include Order

  1. Project macros ("DNDS/Macros.hpp", "DNDS/Defines.hpp")

  2. Standard library headers

  3. External library headers (Eigen, fmt, nlohmann_json, etc.)

  4. Project headers (quoted, relative to src/): "DNDS/Array.hpp", "Geom/Mesh.hpp"

Naming Conventions

Element

Convention

Example

Namespace

PascalCase

DNDS, DNDS::Geom, DNDS::CFV

Class / Struct

PascalCase

MPIInfo, VariationalReconstruction

Public method

PascalCase

Resize(), ConstructMetrics()

Private/protected member

_ prefix

_size, _data, _pRowStart

Type alias

t_ prefix or using

t_IndexVec, tDiFj

Template parameter

T-prefix or PascalCase

T, TOut, _row_size

Constant

PascalCase

UnInitReal, DynamicSize

Macro

DNDS_ALL_CAPS

DNDS_INDEX_MAX, DNDS_MPI_REAL

Enum value

PascalCase

UnknownElem, Line2, Roe

Core Type Aliases

From Defines.hpp:

using real = double;
using index = int64_t;
using rowsize = int32_t;
template <typename T> using ssp = std::shared_ptr<T>;

Error Handling

Use the project’s assert/check macros from DNDS/Errors.hpp:

DNDS_assert(expr);                         // debug-only, calls std::abort()
DNDS_assert_info(expr, info_string);       // debug-only with message
DNDS_assert_infof(expr, fmt_string, ...);  // debug-only with printf format
DNDS_check_throw(expr);                    // always active, throws std::runtime_error
DNDS_check_throw_info(expr, info_string);  // always active with message

Avoid raw assert(). Use DNDS_assert for debug checks and DNDS_check_throw for runtime validation that must remain in release builds.

Templates and Eigen

  • Prefer Eigen::Matrix<real, ...> with the project’s real type.

  • Use if constexpr for compile-time branching on template parameters.

  • Explicit template instantiation goes in _explicit_instantiation/ subdirectories.

clang-tidy and clang-format

Configuration lives at the project root:

  • /.clang-tidy — enabled check groups: modernize-*, readability-*, bugprone-*, performance-*, cppcoreguidelines-*, google-build-using-namespace, mpi-*, openmp-* (with a curated list of disabled checks). Used by both command-line clang-tidy and clangd in editors.

  • /.clang-tidy-fix — narrow subset intended to be run with --fix.

  • /.clang-format — formatting rules.

  • /.clangd — editor-only flag tweaks (CUDA/OpenMP handling); intentionally does not duplicate the tidy check list.

Run the checkers via the scripts in scripts/:

# clang-tidy
scripts/run_clang_tidy.py                  # all default modules
scripts/run_clang_tidy.py Geom CFV         # selected modules
scripts/run_clang_tidy.py src/Geom/Mesh    # any path
scripts/run_clang_tidy.py --changed        # only files dirty vs HEAD
scripts/run_clang_tidy.py --summary        # just the per-check totals
scripts/run_clang_tidy.py --fix src/DNDS   # apply .clang-tidy-fix

# clang-format
scripts/run_clang_format.py                # format all default modules
scripts/run_clang_format.py --check        # CI-style: exit 1 if any drift
scripts/run_clang_format.py --changed      # only files dirty vs HEAD

Both scripts use concurrent.futures internally to parallelize across cores; no external run-clang-tidy helper is needed. The legacy scripts/run-clang-tidy.sh, run-clang-tidy-fix.sh, and run-clang-format.sh still work — they are thin shims that forward to the Python drivers.

Column-aligned macro blocks. Some DNDS_FIELD(...) blocks inside DNDS_DECLARE_CONFIG bodies are hand-aligned for readability. These blocks must be wrapped in // clang-format off / // clang-format on so that automated formatting does not destroy the alignment.

CUDA note. clang-tidy cannot parse nvcc-driven .cu compile commands (nvcc-only flags break clang’s CUDA frontend). The runner excludes .cu files by default; headers included from CUDA TUs are still tidied transitively via their .cpp includers. Pass --include-cu to opt in anyway.

NOLINT placement. NOLINTNEXTLINE(check) applies to the immediately following line. Put any rationale comment before the NOLINT directive, not between it and the offending code. When --fix can rewrite the flagged line, use block-form NOLINTBEGIN(check) ... NOLINTEND(check) so the directive survives the rewrite. Every NOLINT marker in the tree is paired with a rationale comment explaining why the check is wrong or inapplicable at that site.

Per-module sanitation status.

Module

Status

Diagnostics

src/DNDS/

Clean (2026-04-29)

1 (unrelated Eigen PCH omp.h)

src/Solver/

Not started

src/Geom/

Not started

src/CFV/

Not started

src/Euler/

Not started

src/EulerP/

Not started

The full cleanup history for DNDS — 26 passes, 24 597 → 1 diagnostics — is recorded in docs/dev/clang_tidy_plan.md. That document is the reference for the per-pass recipe, the .clang-tidy disable rationales, and the NOLINT placement gotchas. Use the same recipe for the other modules in the order Solver → Geom → CFV → Euler → EulerP. The existing .clang-tidy disables carry forward unchanged; any new module-specific disables go in the file-header table, not inside the Checks: folded scalar.

C++ Docstrings (Doxygen)

Source comments are processed by Doxygen (for the standalone C++ API docs) and by Breathe (to embed them in the Sphinx site). Both systems read the same /// and /** */ comment blocks. The rules below ensure cross-references work in both outputs.

Basic structure

Use /// for single-line doc comments and /** */ for multi-line blocks. Always document the why, not just the what.

/// @brief One-line summary of the function.
void simpleFunction();

/**
 * @brief Compress the CSR storage into a flat buffer.
 *
 * @details After this call, data is contiguous in `_data` and
 * indexed by `_pRowStart`.  Required before MPI communication,
 * CUDA transfer, or serialization.
 *
 * @pre  Array must be in decompressed state.
 * @post `IsCompressed()` returns true.
 *
 * @sa Decompress, ResizeRow
 */
void Compress();

Cross-referencing classes and structs

Use [DNDS](#DNDS)::ClassName "ClassName" for fully-qualified references. Doxygen resolves these as hyperlinks in its HTML output. Breathe (Sphinx) also resolves them when the qualified name matches a documented symbol.

/// @brief MPI-aware [DNDS](#DNDS)::Array "Array" with global index mapping.
///
/// Layers an [DNDS](#DNDS)::MPIInfo "MPIInfo" context and a
/// [DNDS](#DNDS)::GlobalOffsetsMapping "GlobalOffsetsMapping" on top of
/// [DNDS](#DNDS)::Array "Array".
template <class T, rowsize _row_size = 1>
class ParArray : public Array<T, _row_size> { ... };

Why the "DisplayText" part? Without it, Doxygen renders the full qualified name DNDS::MPIInfo as the link text, which is verbose. [DNDS](#DNDS)::MPIInfo "MPIInfo" shows just MPIInfo as the link label.

Cross-referencing methods, macros, and enums

For members of the same class or symbols in the same namespace, use unqualified [Name](#Name):

/// @brief Layout-polymorphic compress: no-op for non-CSR,
/// calls [CSRCompress](#CSRCompress) for CSR.
/// After this, [ResizeRow](#ResizeRow) and [ReserveRow](#ReserveRow) may be used.
void Compress();

Doxygen resolves unqualified names contextually (within the enclosing class/namespace). Breathe cannot resolve these, so they render as plain text in Sphinx — but the Doxygen HTML (served at /doxygen/) has working links.

For macros, use [DNDS_assert](#DNDS_assert) (macros live in the global scope):

/// Uses [DNDS_assert](#DNDS_assert) for debug-only bounds checking.
/// In release builds, use [DNDS_check_throw](#DNDS_check_throw) instead.

What NOT to cross-reference

Keep these as backtick inline code (no @ref):

  • External API names: `MPI_Allreduce`, `Eigen::Matrix`, `std::vector`

  • Layout names used as prose: `TABLE_Fixed`, `CSR`, `TABLE_StaticMax`

  • Template parameters: `_row_size`, `T`

  • Code snippets: `Resize(100)`, `operator()`

/// @brief Width used by row `iRow` in number of `T` elements.
/// - `TABLE_*Fixed`: returns the uniform row width;
/// - `TABLE_*Max`:   returns `_pRowSizes->at(iRow)`;
/// - `CSR`:          returns `pRowStart[iRow+1] - pRowStart[iRow]`.

Common Doxygen commands

Command

Purpose

Example

@brief

One-line summary

@brief Resize the array.

@details

Extended description

Multi-paragraph explanation

@param name

Document a parameter

@param mpi  The MPI context.

@tparam T

Document a template param

@tparam T  Element type.

@return

Document the return value

@return Number of rows.

@pre / @post

Pre/post conditions

@pre Array is compressed.

@sa

“See also” links

@sa Compress, Decompress

[DNDS](#DNDS)::Foo "Foo"

Cross-ref to class/struct

See above

[Bar](#Bar)

Cross-ref to local symbol

See above

@note

Highlighted note

@note Thread-safe.

@warning

Highlighted warning

@warning Not MPI-safe.

@code{.cpp}@endcode

Code block

Fenced example

Python

Naming

  • Functions/variables: snake_case (test_all_reduce_scalar, meshFile).

  • Classes wrapping C++ types: match the C++ name (MPIInfo, VariationalReconstruction_2).

  • Private helpers: _ prefix (_pre_import, _init_mpi).

Imports

from __future__ import annotations  # when used
import sys, os
from DNDSR import DNDS, Geom, CFV, EulerP
import numpy as np
import pytest

Testing Patterns

  • Use @pytest.fixture for MPI setup.

  • Use plain assert statements (not unittest-style).

  • Tests may run standalone via if __name__ == "__main__": blocks.

  • Use numpy for array comparisons: assert np.all(...), assert (arr == val).all().

Doctest and POSIX index()

When writing new C++ tests with using namespace DNDS;, qualify DNDS::index, DNDS::real, and DNDS::rowsize in declarations to avoid ambiguity with POSIX index() from <strings.h> (pulled in by doctest).