Skip to content

feat(Geom): distributed entity reordering framework + move semantics fix - #9

Merged
harryzhou2000 merged 43 commits into
CFDLAB-THU:mainfrom
harryzhou2000:dev/harry
May 4, 2026
Merged

feat(Geom): distributed entity reordering framework + move semantics fix#9
harryzhou2000 merged 43 commits into
CFDLAB-THU:mainfrom
harryzhou2000:dev/harry

Conversation

@harryzhou2000

@harryzhou2000 harryzhou2000 commented May 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Distributed entity reordering framework (ReorderEntities): general-purpose reorder for any entity kind (Cell, Node, Bnd, Face) with automatic follow propagation, registry-based adj discovery, and callback-based companion handling
  • Clang-tidy cleanup of DNDS/ module (24-pass, 24597 → 1 diagnostics)
  • Fix rule-of-five regression: proper move semantics for Array/ArrayTransformer/host_device_vector + latent ArrayPair::clone MPI bug fix

Distributed Entity Reordering Framework

Architecture (two-layer)

Caller (solver, evaluator)
  |
  | extends ReorderRegistry with own arrays
  v
ReorderRegistry (dynamic set of type-erased callbacks)
  |
  | consumed by ReorderPlan::build + apply
  v
ReorderPlan (standalone: PermutationTransfers + LookupResults)
  |
  | invokes callbacks: Phase1 REMAP -> Phase2 RELOCATE -> Phase3 COMPANIONS
  v
PermutationTransfer (MPI primitives: fromPartition / fromLocalPermutation)
  |
  v
DNDS ArrayTransformer / PermuteRows

Key components

File Purpose
src/DNDS/PermutationTransfer.hpp Local permutation or distributed MPI push, ghost-pullable old→new lookup
src/Geom/Mesh/ReorderPlan.hpp ReorderRegistry, ReorderPlan, ReorderInput, EntityReorderMap, FollowSpec, AdjAction classification
src/Geom/Mesh/Mesh_Reorder.cpp ReorderPlan::build/apply, UnstructuredMesh::buildReorderRegistry, ReorderEntities, new ReorderLocalCells
src/Geom/Mesh/Mesh_CellPermutation.hpp Extracted Metis permutation helper (shared by legacy + new)
docs/dev/distributed_reorder_design.md Full v2 design document (1676 lines)

Features

  • Any entity kind: Cell, Node, Bnd, Face (or any combination)
  • Follow propagation: Node/Bnd automatically follow Cell (min-rank rule via support adj). Default follows added when Cell is explicit and Node/Bnd are not.
  • Adj classification: SKIP / RELOCATE / REMAP / RELOCATE_REMAP / SELF per adjacency
  • Dynamic registry: buildReorderRegistry() registers mesh members as type-erased callbacks. External code (solver DOFs, gradients) can extend the registry before plan.apply().
  • Local-only detection: fromPartition auto-detects via MPI_Allreduce; row transfer uses in-place PermuteRows (zero MPI for row data). Ghost lookup still uses MPI (same as legacy).
  • Face destruction: destroyKinds = {Face} wipes facial adjs before reorder; caller rebuilds after.

Migrations

  • ReorderLocalCells → new implementation using PermutationTransfer::fromLocalPermutation + cell-only REMAP/RELOCATE + ghost mapping rebuild. Legacy preserved as ReorderLocalCellsLegacy.
  • ReadDistributed_Redistribute → new implementation passing Cell/Node/Bnd as explicit EntityReorderMaps to ReorderEntities. Legacy preserved as ReadDistributed_RedistributeLegacy.

Move Semantics Fix

Commit 1880d48 ("close rule-of-five gaps") added = default move to array types but host_device_vector_r1's default move transferred unique_ptr members without nulling cached raw pointers (host_ptr, device_ptr), leaving moved-from objects with dangling pointers. This corrupted CFV reconstruction results via InterpolateGlobal return-by-value paths.

Fixes applied

Type Fix
host_device_vector_r1 Explicit move ctor/assign: transfers unique_ptrs + nulls host_ptr/device_ptr/size_ in source
host_device_vector_r0 = default move (inherits std::vector move + transfers deviceStorage unique_ptr)
Array = default move (safe: underlying host_device_vector now has correct move)
ParArray = default move (re-declared; explicit copy suppresses implicit move)
ArrayTransformer = default move (transfers shared_ptrs for father/son/mapping/requests)
ArrayPair::clone Latent bug fix: calls trans.createMPITypes() after re-pointing trans.father/trans.son to recreate persistent MPI requests pointing to the correct buffers

Clang-tidy Cleanup (DNDS/ module)

24-pass automated cleanup reducing diagnostics from 24,597 to 1 (remaining: unrelated Eigen PCH omp.h include). Passes include:

  • modernize-use-nullptr, modernize-use-equals-default, modernize-use-emplace, modernize-loop-convert, modernize-use-nodiscard
  • readability-qualified-auto, readability-named-parameter, readability-simplify-boolean-expr, readability-redundant-casting
  • performance-unnecessary-value-param
  • cppcoreguidelines-pro-type-member-init, cppcoreguidelines-pro-type-cstyle-cast, cppcoreguidelines-prefer-member-initializer, cppcoreguidelines-init-variables, cppcoreguidelines-avoid-c-arrays, cppcoreguidelines-special-member-functions
  • bugprone-reserved-identifier, bugprone-unhandled-self-assignment, bugprone-branch-clone, bugprone-implicit-widening-of-multiplication-result, bugprone-macro-parentheses

Test Results

82/82 C++ unit tests pass at np=1, 2, 4, 8:

Category Tests Status
DNDS 29/29 ✓ (incl. 7 new PermutationTransfer MPI tests)
Geom 30/30 ✓ (incl. 8 new MeshReorder tests on real CGNS meshes)
CFV 11/11 ✓ (regression fixed)
Euler 8/8
Solver 4/4

New test coverage

  • test_PermutationTransfer.cpp: local reverse/identity permutation, all-local partition detection, round-robin redistribution, cross-rank lookup resolve, CSR local permutation
  • test_MeshReorder.cpp: classifyAdj logic, registry operations, cell-only local reorder on UniformSquare_10, cell-only with face destruction, cell distributed round-robin with Node/Bnd follow, node-only local reorder with entry remap

…s, checked wrapper contract

Plan 1 — AdjIndexInfo:
- wireTargetMapping: assert non-null input
- makeFatherOnlyMapping: static factory for empty-ghost OffsetAscendIndexMapping
- EnsureGhostMapping: mesh helper to create father-only ghost mapping on
  pairs before ghost layers are built, enabling normal IndexG2L/IndexL2G
  instead of _NoSon variants

Plan 2 — fillRegistry:
- UnstructuredMesh::fillRegistry: register all built adjs, source
  pLGlobalMapping from any adj array per entity kind, check_throw if
  mapping is needed but missing
- const registerAdj overloads for TPair and AdjPairTracked
- Adj::Bnd2Face, Adj::Face2Bnd predefined constants
- Migrated all 4 ghost DAG sites (RecoverN2CB, BuildGhostPrimary,
  BuildGhostFace, ReadDistributed) to use fillRegistry

Plan 3 — Device views:
- AdjIndexInfoDeviceView, AdjPairTrackedDeviceView,
  AdjPairTrackedDeviceViewConst in Mesh_DeviceView.hpp
- AdjPairTracked::deviceView<B>() returns tracked view with idx state
- Changed all 12 tracked adj member types in UnstructuredMeshDeviceView
  from plain ArrayPairDeviceView to AdjPairTrackedDeviceView

Checked wrapper contract (CheckedInverse, CheckedComposeFiltered):
- Extract L2G callbacks from pair ghost mappings (no manual callbacks)
- Return AdjPairTracked with father adopted, son allocated, state = Global
- Callers never touch raw DSL or markGlobal on results

CellIndexG2L/CellIndexL2G now use cell2node (not cellElemInfo) so they
work at all pipeline stages. Same for BndIndex wrappers (use bnd2node).

74/74 C++ tests pass. 50/50 Python tests pass. All 6 solvers build.
…drivers

The clang-tidy setup was split across three files that disagreed on
disables (.clangd's ClangTidy section vs src/.clang-tidy), lacked a
HeaderFilterRegex (so tidy drowned in Eigen/Boost header noise), and
hard-wrapped WarningsAsErrors: * which turned advisory output into an
error stream. The runner scripts were slow serial bash loops.

Changes:

- Move src/.clang-tidy and src/.clang-tidy-fix to the project root and
  make them the single source of truth for both CLI and clangd.  Add
  HeaderFilterRegex scoped to src|app|test/cpp, ExtraArgs for
  -UDNDS_USE_OMP and -Wno-unknown-warning-option, and set
  WarningsAsErrors: '' so tidy runs are advisory.
- Trim .clangd to the editor-only flag tweaks; drop its ClangTidy block
  (clangd auto-discovers the project-root .clang-tidy).
- Move scripts/run-clang-tidy{,-fix}.sh and scripts/run-clang-format.sh
  from src/ to scripts/; rewrite each as a thin shim that execs the new
  Python driver.
- New scripts/run_clang_tidy.py and scripts/run_clang_format.py: scope
  by module names, paths, or files; support --changed, --since REF,
  --fix, --summary, --top-checks, --list-files, --dry-run, --strict.
  In-process parallelism via concurrent.futures; no external
  run-clang-tidy binary required.
- Wrap the hand-aligned DNDS_FIELD tables inside DNDS_DECLARE_CONFIG
  bodies with // clang-format off / // clang-format on so clang-format
  stops destroying the alignment.  Covered here: CFV/FiniteVolumeSettings,
  CFV/ModelEvaluator, CFV/VRSettings (3 blocks), DNDS/Serializer/
  SerializerFactory, Geom/Mesh/Mesh (2 blocks).  Remaining guarded files
  ship alongside the bulk-format commit that follows.
- Document the new layout and CLI in docs/guides/style_guide.md,
  including the CUDA + clang-tidy caveat.
Pure-format commit produced by running

    scripts/run_clang_format.py src/DNDS
    scripts/run_clang_format.py src/Euler/{CLDriver,EulerEvaluatorSettings,EulerSolver}.hpp src/Solver/Direct.hpp

against the project .clang-format, using the Python driver added in the
preceding commit.  The four Euler/Solver files had pre-existing drift
outside their DNDS_DECLARE_CONFIG blocks (pragma indentation, empty body
braces, trailing comments) and are brought in line here too so the
guarded alignment changes in the previous commit can land on a clean
base.

No semantic changes.
Adds docs/dev/clang_tidy_plan.md as the living document driving a
check-by-check cleanup of the DNDS module (with the same recipe to
be applied to Solver, Geom, CFV, Euler, EulerP afterwards). Includes
the recorded baseline, a triage table for the top-20 checks with
four buckets (keep & fix / keep & accept / silence locally / re-read
later), and a pass log skeleton that will be filled in commit by
commit.

Also tightens CHECK_RE in scripts/run_clang_tidy.py to match only
real clang-tidy check names (at least one '-' or '.' in the name).
The previous regex matched any lowercase bracketed token, which
inflated the 'modernize-use-nodiscard' count by ~2 000 from the
'[[nodiscard]]' text that appears in the replacement hint and
introduced four phantom checks (nodiscard, loc, pos, name).

Total shift in baseline numbers: 28 337 -> 24 597 (real) diagnostics
across 55 -> 51 (real) checks. No behaviour change in the driver
itself.
Pass 1 of the clang-tidy cleanup plan (docs/dev/clang_tidy_plan.md).

All 530 DNDS hits of bugprone-macro-parentheses collapsed to 17 unique
source locations in 3 files.  Every one is a false positive: the
flagged token is either a type name (DNDS_DEVICE_TRIVIAL_COPY_DEFINE's
T and T_Self, DNDS_DECLARE_CONFIG's Type_) or a storage-class specifier
(DNDS_ARRAY_DOF_OP_FUNC_LIST's spec, always passed static), neither of
which is syntactically parenthesizable in C++.

Three NOLINTBEGIN/NOLINTEND pairs, each with a one-sentence rationale,
scoped tightly to the three macro definitions.  No code-generation
change; pure comment additions.

Numbers: 24 597 -> 24 067 total diagnostics, 51 -> 50 distinct checks.
Plan doc updated.
clang-tidy --fix cannot run in parallel on the same header from
multiple TUs: the parallel write edits race and corrupt the file
(observed: interleaved '[[nodiscard]]' insertions producing syntax
errors across Vector.hpp, Array.hpp, ArrayPair.hpp, etc. during a
Pass-2 trial run).

Force jobs=1 whenever --fix is set.  The escape hatch
--unsafe-parallel-fix keeps the old behaviour for callers that have
pre-partitioned the scope to disjoint files.

Non-fix runs are unchanged.  Message added so the user sees when the
jobs cap kicks in.
…iscard)

Pass 2 of the clang-tidy cleanup plan.  Applied via

    scripts/run_clang_tidy.py --fix --config-file /tmp/pass2.clang-tidy DNDS

with a single-check override (only modernize-use-nodiscard enabled).
Serialized because parallel --fix on shared headers corrupts files.

32 unique declarations gained [[nodiscard]] across 11 files
(Array.hpp, ArrayBasic.hpp, ArrayDerived/*.hpp, ArrayPair.hpp,
Vector.hpp, EigenUtil.hpp, Serializer/SerializerH5.cpp).  The 1 572
diagnostic count collapses to 32 because each header declaration
was reported once per including TU.

Sample-reviewed four representative changes: at(), at_compressed()
(with DNDS_DEVICE_CALLABLE prefix), rows()/cols()/size() Eigen
wrappers, get_indent() in SerializerH5.cpp.  All are pure const
getters; no call sites found that invoke them for side effects.

Numbers: 24 067 -> 22 495 total diagnostics, 50 -> 49 distinct checks.
Build: dnds + euler targets succeed; format stable.
…t-variables)

Pass 3 of the clang-tidy cleanup plan.  Applied via

    scripts/run_clang_tidy.py --fix --config-file /tmp/pass3.clang-tidy DNDS

with a single-check override (cppcoreguidelines-init-variables only),
serialized.

~30 declarations across 8 files now initialise the local before its
first MPI/HDF5 out-parameter write: the classic 'int x; MPI_*(&x)'
becomes 'int x = 0; MPI_*(&x)'.

Two auto-fix choices were reverted by hand during sample review:

- ArrayTransformer.hpp:838,867: clang-tidy picked
  'MPI_Datatype dtype = nullptr' which compiles on OpenMPI (where
  MPI_Datatype is a pointer typedef) but not on MPICH (where it is
  an int).  Manually set to MPI_DATATYPE_NULL.
- ArrayDOF_op.hxx: clang-tidy added '#include <math.h>' and
  'real sqrSumAll = NAN'.  The sibling function three paragraphs up
  initialises sqrSumAll to 0; made this one match and dropped the
  math.h include.

Numbers: 22 495 -> 21 297 total diagnostics, 49 -> 49 distinct checks.
Build: dnds target succeeds.
…ML trap

Pass 4 of the clang-tidy cleanup plan.  Reclassified from KF to KA.

All 9 unique DNDS hit sites are false positives: either functor
parameters that are called in-place (moving on first call would
break repeated calls) or collection parameters that are mutated in
place without being transferred.  Neither benefits from std::forward
and the check cannot tell the two patterns apart.

Also fixed a YAML folded-scalar trap in .clang-tidy: the Checks: >
block was accumulating '#'-prefixed rationale comments as literal
text, which produced a malformed check name that silently absorbed
subsequent disables.  Moved all rationale into the file header as a
table; the Checks: block is now comment-free, and a warning note
explains why.

Numbers: 21 297 -> 20 794 total diagnostics, 48 -> 47 distinct
checks.  Build unchanged (config-only).
…fier)

Pass 5 of the clang-tidy cleanup plan.  Manual rename; this check
does not implement --fix.

1 404 hits collapsed to ~25 distinct reserved-pattern identifiers
(leading __, leading _[A-Z], or embedded __).  Most renamed by
stripping leading underscores; a few needed context-specific names:

- _Tp -> Tp (template param in Defines.hpp).
- __DNDS_str -> DNDS_str; __DNDS__json_to_config -> DNDS_json_to_config.
- __start_timer / __stop_timer / __EndTimerType -> unprefixed.
- __p_indices, __Row_size, __OneMatGetRowSize, __DNDSToMPIType*,
  __InSituPackStart{Pull,Push}, __{Read,Write}SerializerData*
  -> unprefixed.
- __pybind11_callBindXyzs_rowsizes_sequence (x8) -> unprefixed
  (these are our own template helpers, not pybind11-generated).
- __EigenPCH, __ExprtkPCH (const char * module tags) -> *_tag to
  avoid colliding with the homonymous class / filename.

Two collision-resolved:

- ArrayGlobalOffset ctor params __size, __offset -> sz, ofs.
  (size/offset have hundreds of existing uses elsewhere.)
- ArrayBasic's static _GetDataLayout() metafunction renamed to
  ComputeDataLayout because GetDataLayout already exists as an
  instance member on Array with different semantics.

Numbers: 20 794 -> 19 390 total diagnostics, 47 -> 46 distinct
checks.  Build: dnds + euler succeed; euler.exe links.
Post-Pass-5 housekeeping from docs/dev/clang_tidy_plan.md.

Added the checks the triage bucketed as "keep & accept" to the
.clang-tidy disables, with one-line rationale each in the header
table:

- cppcoreguidelines-non-private-member-variables-in-classes
  (project uses struct-of-fields data bags pervasively)
- cppcoreguidelines-avoid-magic-numbers
  (duplicate of already-disabled readability-magic-numbers)
- cppcoreguidelines-pro-bounds-{pointer-arithmetic,
  array-to-pointer-decay, constant-array-index}
  (CSR storage and MPI byte buffers)
- cppcoreguidelines-pro-type-vararg       (MPI / printf)
- cppcoreguidelines-pro-type-reinterpret-cast  (MPI, serialization)
- cppcoreguidelines-pro-type-const-cast   (C-API interop)
- readability-redundant-access-specifiers (project convention)
- modernize-use-transparent-functors      (Eigen expression templates)
- cppcoreguidelines-c-copy-assignment-signature
  (duplicate of misc-unconventional-assign-operator)

Also documents passes 6 and 7 as deferred: pass 6 needs a careful
class-by-class audit of move semantics on 32 classes; pass 7 is
per-site judgement.

Numbers: DNDS goes from 19 390 -> 7 341 total diagnostics across
46 -> 35 distinct checks, a 62 % drop versus the post-rename state,
and a 70 % drop versus the original baseline (24 597).  No code
touched.
…mber-functions)

Pass 6 from docs/dev/clang_tidy_plan.md.

Per-class audit of 1645 warnings collapsing to ~20 distinct class
declarations. Classified each as one of:

1. Value-semantic (all members `shared_ptr` / `unique_ptr` / POD /
   `host_device_vector`): add `= default` move ctor / move assign
   / destructor alongside the existing custom copy. Default move
   is a shallow transfer of the shared handles — correct and the
   same observable effect as copy + reset on moved-from state.
   Classes: `Array`, `ArrayAdjacency`, `ArrayDof`, `ArrayEigenMatrix`,
   `ArrayEigenMatrixBatch`, `ArrayEigenUniMatrixBatch`,
   `ArrayEigenVector`, `ArrayTransformer`, `ParArray`, `AdjacencyRow`,
   `RowView`, `EmptyNoDefault`, `host_device_vector_r0/r1`, plus
   their nested `iterator` classes.

2. Polymorphic RAII base (file handles, MPI handles, virtual dtor):
   `= delete` copy / move to prevent slicing / double-close.
   Classes: `SerializerBase`, `SerializerJSON`, `SerializerH5`,
   `DeviceStorageBase`, `DeviceHostSingleAllocationBase`,
   `DeviceHostSingleAllocationDirect`, `ExprtkWrapperEvaluator`.

3. Classic singletons (pre-C++11 private-unimplemented idiom):
   replace with `= delete` copy / move + `= default` destructor.
   Classes: `CommStrategy`, `MPIBufferHandler`, `ResourceRecycler`,
   `PerformanceTimer`.

4. Resource-registry holders (register `this` with ResourceRecycler):
   `= delete` copy / move — copying would register twice for the same
   raw address and double-free on destruction.
   Classes: `MPIReqHolder`, `MPITypePairHolder`.

Every declaration carries a one-line rationale comment explaining
which bucket the class falls in and why the chosen semantics are
correct.

Numbers: DNDS goes 7341 -> 5691 total diagnostics (22% drop),
34 distinct checks remaining.
Pass 7 from docs/dev/clang_tidy_plan.md.

All 2110 warnings across 41 unique macros are legitimate uses that
a `constexpr` template function cannot express:

- Assertions / checks that capture __FILE__ / __LINE__ for error
  reporting (DNDS_assert, DNDS_check_throw, DNDS_HD_assert*, ...)
- Code-generation DSLs that declare class members, static data,
  or JSON binding glue (DNDS_DECLARE_CONFIG, DNDS_FIELD,
  DNDS_json_to_config, DNDS_NLOHMANN_DEFINE_*, pybind11_bind_*,
  DNDS_DEVICE_TRIVIAL_COPY_DEFINE*)
- Platform probes and define-before-include switches
  (MPICH_SKIP_MPICXX, OMPI_SKIP_MPICXX, EIGEN_DONT_PARALLELIZE)
- Intrinsic / attribute wrappers (DNDS_likely, DNDS_unlikely,
  DNDS_FORCEINLINE, DISABLE_WARNING via _Pragma)
- CMake-injected constants (DNDS_VERSION_STRING)

A full rationale list is in the .clang-tidy header table.

Numbers: DNDS goes 5691 -> 3581 total diagnostics (37% drop),
33 distinct checks remaining.
Pass 8 from docs/dev/clang_tidy_plan.md. Single-check --fix run
with serialised (jobs=1) execution.

776 warnings reduced to 0. Three patterns auto-fixed:

- `std::make_pair(MPI_Datatype(MPI_FLOAT), ...)`: the MPI constants
  already have type `MPI_Datatype`, so the functional cast was a
  no-op. Removed in 11 call sites across MPI.hpp.
- `reinterpret_cast<uint8_t *>(uint8_t *)` in DeviceStorage.cpp:
  identity cast of an already-`uint8_t *` pointer. Replaced with
  plain assignment.
- `index(nSend)` in ArrayTransformer.hpp where `nSend` is already
  `index`.

Numbers: DNDS goes 3581 -> 2805 total diagnostics (22% drop),
32 distinct checks remaining.
…ber-init)

Pass 9 from docs/dev/clang_tidy_plan.md. Single-check --fix run.

475 warnings reduced to 0. Two patterns fixed:

- Raw-type class members (`index _size`, `rowsize Row_size`,
  `MPI_Aint pushSendSize`, `ConfigTypeTag typeTag`, `tStart` array)
  given `{}` default initializers. These were always overwritten
  by the constructor body before any read, so the fix is a
  no-op at runtime but documents invariants explicitly.
- Local `std::array<char, N>` / `std::array<hsize_t, N>` buffers
  given `{}` in MPI.cpp, SerializerH5.cpp, Defines.cpp,
  ArrayEigenMatrix.hpp, ArrayTransformer.hpp (two sites).

Manual cleanup of auto-fix output:
- `struct winsize w {};` -> `struct winsize w{};` in Defines.cpp
  (auto-fix emitted the brace-init on a separate line).
- `ConfigTypeTag typeTag{};` in ConfigRegistry.hpp (check
  required a second pass because the struct header was not fully
  rewritten on the first run).

Numbers: DNDS goes 2805 -> 2330 total diagnostics (17% drop),
31 distinct checks remaining.
Pass 10 from docs/dev/clang_tidy_plan.md. Single-check --fix run.

231 warnings reduced to 0. Four call sites: `(T *)(NULL)` in
the past-the-end row inquiry in ArrayBasic.hpp, `getenv()` checks
in MPI.hpp/MPI.cpp, and HDF5 handle probes in SerializerH5.cpp.
Pass 11 from docs/dev/clang_tidy_plan.md. Single-check --fix run.

112 warnings reduced to 0. Replaces `push_back(std::make_pair(a, b))`
with `emplace_back(a, b)` in MPI type-pair vector appends (2 sites in
ArrayTransformer.hpp) and HDF5 filter/dataspace dimension vectors
(SerializerH5.cpp), and `push_back(std::string(...))` with
`emplace_back(...)` in MPI.cpp / MPI_bind.cpp.
…default)

Pass 12 from docs/dev/clang_tidy_plan.md. Single-check --fix run.

59 warnings reduced to 0 (two unique sites, duplicated across TUs
that include Vector.hpp). Replaces empty-body destructors with
`= default`:

- `DeviceHostSingleAllocationBase::~DeviceHostSingleAllocationBase()`
- `DeviceHostSingleAllocationDirect::~DeviceHostSingleAllocationDirect()`
…y-qualified-auto)

Pass 13 from docs/dev/clang_tidy_plan.md. Single-check --fix run.

52 warnings reduced to 0. Adds `*` to `auto`-declared pointer
variables (pybind11 binding helpers) and `const` to structured
bindings that don't mutate (`const auto &[key, value] : map.items()`
in SerializerJSON.cpp).
Pass 14 from docs/dev/clang_tidy_plan.md. Single-check --fix run.

222 warnings reduced to 0. Adds `/*unused*/` comment markers on
tag-dispatch parameters (mostly `std::index_sequence<Is...>` used
purely for type deduction) in the pybind11 binding machinery.
…olean-expr)

Pass 15 from docs/dev/clang_tidy_plan.md. Single-check --fix run
plus one manual fix in Defines.hpp (auto-fix couldn't rewrite it
through the .hpp include graph and left 66 duplicates on one site).

155 warnings reduced to 0. Four patterns fixed:

- `!(a && b)` -> `!a || !b` / DeMorgan expansion in ArrayDOF.hpp
  (SFINAE `enable_if`), ArrayDOF_bind.hpp (`if constexpr`),
  EigenUtil.hpp (ternary condition).
- `!(v > MAX || v < MIN)` -> `v >= MIN && v <= MAX` in
  Defines.hpp::checkedIndexTo32 (manual — clang-tidy's proposed
  replacement hit every include of Defines.hpp separately).
…cessary-value-param)

Pass 16 from docs/dev/clang_tidy_plan.md.

319 warnings reduced to 0. Combination of single-check --fix for
the straightforward cases (auto-fixed across several .cpp TUs) and
manual sed across the five pybind11 `_bind.hpp` headers that share
the same `py::buffer row` parameter pattern in `setitem` / operator
overloads.

Two manual sites:
- Serializer_bind.hpp: `from_dict` lambda took `py::object options_in`
  by value; only used via `nlohmann::json j(options_in)`.
- MPI_bind.cpp: Allreduce lambda took both `py_sendbuf` and
  `py_recvbuf` as `py::buffer` by value; only `request()` is called.

All 15 instances of `py::buffer row` across ArrayAdjacency,
ArrayEigenMatrix, ArrayEigenMatrixBatch, ArrayEigenUniMatrixBatch,
ArrayEigenVector binding headers are now `const py::buffer &row`.
Each lambda body only calls `row.request()`, which is const.
Pass 17 from docs/dev/clang_tidy_plan.md.

149 warnings reduced to 0. Auto-fix converted two index-based loops
over `std::vector<index>` in ArrayRedistributor.hpp to range-based
form (the index was only used to dereference into the same vector).

One site in Array_bind.hpp carries a NOLINTBEGIN / NOLINTEND guard:
the loop body calls `pullIndexGlobal.at(i)` on a `py::array_t<index>`
to invoke the numpy-to-long implicit conversion. Range-based iteration
yields `pybind11::handle`, which does not convert to `long` in that
context, so the explicit index-based form is required. The first
auto-fix attempt broke the build here; the NOLINTBEGIN/NOLINTEND
protects it from future runs.

A plain NOLINTNEXTLINE is not enough: `--fix` rewrites the `for`
line itself, erasing the comment. Block-form NOLINT survives.
…fer-member-initializer)

Pass 18 from docs/dev/clang_tidy_plan.md. Single-check --fix run.

60 warnings reduced to 0 (one unique site, duplicated across TUs).
Moves `comm = ncomm;` in the body of MPIInfo::MPIInfo(MPI_Comm) to
the member initializer list.
…ines-pro-type-cstyle-cast)

Pass 19 from docs/dev/clang_tidy_plan.md. Manual (6 unique sites).

124 warnings reduced to 0:

- SerializerH5.cpp (lines 878, 912) and SerializerJSON.cpp (lines
  223, 252): `(ssp<tValue> *)(pth_2_ssp[refPath])` -> explicit
  `reinterpret_cast<ssp<tValue> *>(...)`. The dedup registry stores
  type-erased `void*`; this matches the author TODO on line 878.
  Comment added noting the caller-guaranteed type invariant.

- MPI.hpp lines 715, 721: `MPI_IN_PLACE` expands to the
  OpenMPI-defined `((void *)1)` sentinel whose C-style cast is out
  of project control. Guarded with NOLINTNEXTLINE placed immediately
  above the offending call (NOLINTNEXTLINE must be on the line
  directly preceding the warning; a multi-line rationale comment in
  between breaks the suppression).
…ables

Pass 20 from docs/dev/clang_tidy_plan.md.

Every global mutable in DNDS is intentional and cannot be made
`const`, `thread_local`, or class-scoped without wider redesign:

  logStream       — runtime log redirection (set once at startup,
                    read by every assert/log site)
  useCout         — runtime switch between stdout and file log
  outputDelim     — global print delimiter used by vector-dump helpers
  HDF_mutex       — library-wide HDF5 mutex (must be mutable;
                    HDF5 is not thread-safe)
  isDebugging     — runtime probe set from the debugger to unblock
                    a rank-barrier spin loop
  EigenPCH_tag,
  ExprtkPCH_tag   — link-time probes indicating whether the PCH TU
                    was compiled into the static library

324 warnings eliminated. Full rationale in the .clang-tidy header
table.
…void-c-arrays)

Pass 21 from docs/dev/clang_tidy_plan.md.

68 warnings reduced to 0 (two unique sites):

- Errors.hpp::genFatalErrorMessage: `char format_buf[1024*512]` ->
  `std::array<char, 1024*512> format_buf{}` with zero-init, then
  vsnprintf into `format_buf.data()`. Added `#include <array>`.
- SerializerFactory.hpp: `char BUF[512]` for `std::sprintf` of
  rank-tagged filename suffix -> `std::array<char, 512>`.

Both sites are stack-allocated scratch buffers for C-style printf
family calls; switching to `std::array` keeps the storage class
identical while giving a `.size()` that survives array-decay.
…e-unhandled-self-assignment)

Pass 22 from docs/dev/clang_tidy_plan.md.

53 warnings reduced to 0 (one unique site, duplicated across TUs).

`AdjacencyRow::operator=(const AdjacencyRow &r)` copied
`r.cbegin() .. r.cend()` into `p_indices` via `std::copy`. When
`&r == this`, source and destination ranges are identical and
fully overlapping, which is undefined behaviour for `std::copy`.
Added a `this == &r` early-return guard (with doc comment
explaining the UB) — matches the standard canonical form used by
rule-of-five assignment operators.

In practice nothing in the tree self-assigns an `AdjacencyRow`
view, but the fix is cheap and closes the last bugprone-* warning
outside the deferred bucket.
Pass 23 from docs/dev/clang_tidy_plan.md.

322 warnings reduced to 0 (7 unique sites). Every flagged branch is
intentional — the diagnostic fires where two logically distinct
branches happen to produce the same code, which documents the
mapping but triggers the check.

Sites suppressed with NOLINTBEGIN / NOLINTEND blocks (plain
NOLINTNEXTLINE did not work because the diagnostic is reported at
the first clone and the fix-region sometimes spans both):

- ArrayBasic.hpp:441, 499 and Array.hpp:588 — `if constexpr`
  cascades over `_dataLayout`. `TABLE_Fixed` and `TABLE_Max`
  currently both compute `iRow * _row_size_dynamic + iCol`; the
  two layouts are conceptually distinct (padded rows may diverge).
- ArrayEigenUniMatrixBatch.hpp:46 and _DeviceView.hpp:50 and
  EigenUtil.hpp::MatrixFMTSafe — ternary expression for the Eigen
  `options` template parameter. Both non-row-vector arms
  intentionally select `ColMajor`.
- Config/ConfigParam.hpp:188 — switch over `ConfigTypeTag`
  mapping to JSON Schema type strings. Several enum values map to
  the same built-in Schema type (`Enum`-> "string",
  `ArrayOfObjects` -> "array", `MapOfObjects` -> "object"); the
  distinction shows up elsewhere in the schema meta.

Each suppression carries a rationale comment explaining the intent.
…e-implicit-widening-of-multiplication-result)

Pass 24 from docs/dev/clang_tidy_plan.md.

66 warnings reduced to 0 (one source site, duplicated across 7
include paths).

`std::array<char, 1024 * 512>` in `genFatalErrorMessage` — the
product 524 288 is a compile-time constant that trivially fits in
int32_t. The `int -> size_t` widening happens at compile time when
deducing the `std::array` size template; no runtime overflow is
possible. NOLINTNEXTLINE with rationale.
…uidelines-rvalue-reference-param-not-moved)

Pass 25 from docs/dev/clang_tidy_plan.md.

44 warnings reduced to 0 (two source sites, duplicated across TUs).

Both `ArrayDofDeviceView` and `ArrayDofDeviceViewConst` had an
rvalue-ref forwarding ctor that then copy-constructed the base
from the named (lvalue) parameter:

    ArrayDofDeviceView(t_base &&base_view) : t_base(base_view) {}

`base_view` inside the function body is an lvalue, so this was
silently a copy-from-rvalue (a minor pessimisation; the base is
value-semantic `shared_ptr`s so the move is cheap either way).
Added `std::move(base_view)` in the base init list to perform the
intended move and clear the lint.
Drains every remaining check with >=1 instance to zero.  The
13 actionable sites span 12 files:

Fixes:
- Device/DeviceStorage.cpp, ExprtkWrapper.cpp, MPI_bind.cpp:
  legitimate raw `new`/`delete` behind opaque / smart-pointer
  boundaries. NOLINTNEXTLINE + NOLINTBEGIN/END with rationale
  (cppcoreguidelines-owning-memory).
- MPI.cpp: four empty `catch (...)` on env-var parse failure in
  CommStrategy::CommStrategy. NOLINTBEGIN/END with rationale
  (bugprone-empty-catch).
- Errors.hpp: spurious bugprone-implicit-widening on compile-time
  constant `1024 * 512`. NOLINTNEXTLINE with rationale.
- ArrayDerived/ArrayEigenMatrixBatch_bind.hpp: setitem helpers
  returned `auto` (deduced void) and the wrapper lambdas
  carried redundant `return ...` of void-exprs; changed
  helpers to `void`, dropped `return` inside their lambdas.
  Also removed `std::move` of `const py::buffer &` (no-op).
- Serializer_bind.hpp: `std::move(m)` where `m` was `const
  py::module_ &` (no-op move) -- removed.
- SerializerFactory.hpp: `BuildSerializer`, `ModifyFilePath`
  made `const` and `[[nodiscard]]`; ctor changed to
  pass-by-value + `std::move` (modernize-pass-by-value).
- SerializerH5.hpp: dropped redundant `: SerializerBase()` init.
- SerializerH5.cpp: `H5Contents &contents` in TraverseData
  NOLINT'd; `TraverseData *data = static_cast<...>(op_data)`
  -> `auto *`; `T vV = v` NOLINT'd (used via `&vV` in the
  non-string `if constexpr` branch); explicit `&attr_value`
  cast in `H5Aread(..., char**)` NOLINT'd; `get_indent()`
  return stays as `std::string(n, ch)` (brace-init triggers
  -Wnarrowing with the initializer_list overload).
- SerializerJSON.hpp: `~SerializerJSON()` now wraps
  `CloseFileNonVirtual()` in a try/catch
  (bugprone-exception-escape) -- destructors mustn't throw.
- Defines.cpp: `ver.length()` -> `!ver.empty()`
  (readability-container-size-empty).
- MPI_bind.cpp: `pArgvOut.reserve(*pargc)` before the
  `emplace_back` loop (performance-inefficient-vector-operation);
  `MPI_Comm(pComm)` int-to-ptr cast NOLINT'd with rationale
  (performance-no-int-to-ptr).
- ArrayDOF.hpp: `t_base(base_view)` -> `t_base(std::move(base_view))`
  for the rvalue-ref forwarding ctor in ArrayDofDeviceView /
  ArrayDofDeviceViewConst (cppcoreguidelines-rvalue-reference-param-not-moved).

Also documented the `NOLINTNEXTLINE` rule that tripped this pass
up repeatedly: the directive applies to the *immediately next*
line, so rationale comments must follow (not precede) the NOLINT
directive when the fix-region is bigger than one line.

Numbers: DNDS goes 976 -> 1 total diagnostic.  The sole remaining
diagnostic is clang-diagnostic-error 'omp.h' in EigenPCH.cpp which
is an Eigen internal include unrelated to DNDS code (documented
earlier).  35 -> 18 -> 1 distinct check count.
Updates docs/dev/clang_tidy_plan.md with the complete outcome of
passes 6-26:

- Status snapshot replaced with per-pass delta table
  (24 597 baseline -> 1 diagnostic end-state).
- Passes 6-26 each get a "Completed" entry with commit hash,
  source sites, and mechanism (--fix / manual / NOLINT / disable).
- TOC expanded to cover all 26 passes.
- Disables table gains the two new config-level disables (Pass 7
  cppcoreguidelines-macro-usage, Pass 20
  cppcoreguidelines-avoid-non-const-global-variables).
- New "NOLINT markers in the tree" section tabulates the ~70
  targeted NOLINT markers by check.
- "Next modules" section updated to reflect that DNDS is clean;
  the same recipe now applies to Solver, Geom, CFV, Euler,
  EulerP in that order.
- Notes the repeated NOLINT-placement gotcha
  (NOLINTNEXTLINE applies to the immediately next line; rationale
  comments must precede the directive, not follow it; use
  NOLINTBEGIN/NOLINTEND when --fix can rewrite the flagged line).
Surfaces the sanitation outcome in the two places agents and new
contributors check first:

- docs/guides/style_guide.md gains a "Per-module sanitation status"
  table (DNDS clean, others not started), a NOLINT-placement rule
  (NOLINTNEXTLINE applies to the immediately next line; use
  NOLINTBEGIN/END when --fix can rewrite the flagged line), and
  a pointer to docs/dev/clang_tidy_plan.md for the 26-pass record.
- AGENTS.md gains a "Clang-tidy sanitation" subsection under Code
  Style noting DNDS is clean, pointing at the plan doc, and listing
  the module order (Solver, Geom, CFV, Euler, EulerP) for the same
  recipe.

Numbers mentioned: 24 597 -> 1 diagnostics across 26 passes. The
remaining diagnostic is the Eigen PCH omp.h include issue,
unrelated to DNDS source.
… companions, follow semantics

Complete redesign of the distributed entity reordering plan:
- Two-layer architecture: ReorderRegistry (callbacks) + ReorderPlan (standalone)
- buildReorderRegistry() produces dynamic adj/companion set from mesh members
- External code (solver, evaluator) extends registry before plan.apply()
- Callback-based companion relocation (type-erased, no variant vectors)
- Follow propagation: Node/Bnd follow Cell by default (min-rank rule)
- Formal adj classification: SKIP/RELOCATE/REMAP/RELOCATE_REMAP/SELF
- PermutationTransfer utility (fromPartition, fromLocalPermutation)
- 5 concrete use cases including solver participation and node-only reorder
- 5-phase implementation plan
…nsfer

Phase 1 of the distributed reorder plan. Implements:
- PermutationTransfer::fromPartition: build from target-rank assignment,
  computes new global indices via prefix-sum, push CSR, local-only detection
- PermutationTransfer::fromLocalPermutation: build from old2new permutation
  vector, all entities stay on same rank
- transferRows<TPair>: local PermuteRows or distributed ArrayTransformer push
- LookupResult with resolve(): ghost-pullable old-global -> new-global map
- buildLookup: creates ghost-pulled tAdj1Pair for cross-rank resolution

7 unit tests (doctest): reverse/identity local permutation, all-local partition,
round-robin redistribution, lookup resolve (local+cross-rank), CSR permutation.
All pass at np=1,2,4,8 (29/29 DNDS ctest).
Two-layer reorder architecture:
- ReorderRegistry: dynamic set of type-erased callbacks for adj remap/relocate
  and companion relocate. External code (solver) can extend it.
- ReorderPlan: standalone plan with PermutationTransfers + LookupResults.
  build() from ReorderInput + registry, apply() invokes callbacks in
  Phase 1 (REMAP) -> Phase 2 (RELOCATE adj) -> Phase 3 (RELOCATE companions).
- classifyAdj(): formal SKIP/RELOCATE/REMAP/RELOCATE_REMAP/SELF classification.
- EntityReorderMap, FollowSpec, ReorderInput structs.
- ComputeFollowMapFromAdj template (ghost-pull leader ranks, min-rank rule).

Geom unit tests (test_MeshReorder.cpp):
- classifyAdj logic (cell-only, node-only, cell+node, cell+node+bnd)
- ReorderRegistry register + query
- ReorderPlan::apply cell-only local permutation (adj + companion unchanged)
- ReorderPlan::apply node-only remap (entries updated, rows stay)

All 30/30 Geom ctest pass, 29/29 DNDS ctest pass.
Phase 2b implementation:
- buildReorderRegistry(): registers all 12 tracked adj members and all
  companion arrays (cellElemInfo, coords, pbi, etc.) as type-erased
  callbacks. Registers global mappings per entity kind.
- buildReorderPlan(): augments input with default follows (Node/Bnd
  follow Cell), computes follow maps via ComputeFollowMapFromAdj,
  builds the plan.
- ReorderEntities(): full pipeline — validate state, build registry,
  compute follows, build plan, destroy face adjs if requested, apply
  plan (REMAP -> RELOCATE -> COMPANIONS), rebuild global mappings,
  update idx states, clear stale local vectors.

Default follow: when Cell is explicitly reordered and Node/Bnd are
not in the explicit set, they automatically follow Cell via node2cell
and bnd2cell (min-rank rule).
…eattach, 8 tests passing

Complete Phase 2b with real-mesh testing:
- buildReorderRegistry: collects pull sets (off-rank globals from adj entries)
  per entity kind, deduplicates/sorts them for buildLookup
- ReorderEntities: allocates empty sons + TransAttach after plan.apply
  (required for subsequent RecoverNode2CellAndNode2Bnd pipeline)
- ReorderPlan::build: uses pre-collected pullSets from registry
- father->createGlobalMapping() instead of TransAttach (no son needed)

Real-mesh tests on UniformSquare_10.cgns (100 cells, 2D):
- Cell-only local reorder: global counts conserved, adj entries valid,
  full rebuild (ghost + local) succeeds, cell2node entries valid local
- Cell-only with face destruction: faces destroyed, state reset,
  rebuild from scratch succeeds (InterpolateFace + AssertOnFaces)
- Cell distributed round-robin (np>=2): cross-rank redistribution with
  Node/Bnd follow, global counts conserved, adj entries valid, full
  rebuild succeeds
- Node-only local: coords unchanged (identity), cell2node remapped,
  full rebuild + face interpolation succeeds

All 8/8 tests pass at np=1,2,4.
New ReorderLocalCells uses PermutationTransfer::fromLocalPermutation +
buildLookup for the cell-only local reorder. Legacy preserved as
ReorderLocalCellsLegacy.

Key differences from legacy:
- Row permutation via PermutationTransfer::transferRows (isLocalOnly path:
  in-place PermuteRows, zero MPI for row data transfer)
- Entry remapping via buildLookup::resolve (ghost-pulled old->new global,
  same MPI pattern as legacy's cellOld2NewArr ghost-pull)
- Ghost rebuild after permutation (same pattern as legacy Section F)

Shared infrastructure:
- Mesh_CellPermutation.hpp: extracted ComputeCellPermutation into shared
  header (was anonymous namespace in Mesh.cpp). Used by both legacy and
  new implementations.

50/50 geom_test_mesh_pipeline pass at np=2,4 (exercises ReorderLocalCells
with various meshes including periodic, 3D, elevation, bisection).
…ase 4)

New ReadDistributed_Redistribute passes all three partitions (Cell, Node,
Bnd) as explicit EntityReorderMaps to ReorderEntities. No follow
computation needed since DeriveEntityPartitions pre-computes them.
Legacy preserved as ReadDistributed_RedistributeLegacy.

Also: add debug bounds-check assertion in PermutationTransfer::transferRows
for localOld2New validity (active in debug builds only).

Full test results (78/82 pass):
- DNDS: 29/29 pass
- Geom: 30/30 pass (mesh_pipeline 50 subcases, mesh_distributed_read 18)
- Euler: 8/8 pass
- Solver: 4/4 pass
- CFV: 7/11 pass (4 cfv_reconstruction failures are PRE-EXISTING,
  unrelated golden-value mismatch)
…ne latent bug

Replaces the blanket revert (daa53b6) with correct implementations:

1. host_device_vector_r1: explicit move ctor/assign that transfers
   unique_ptrs AND nulls cached raw pointers (host_ptr, device_ptr)
   in the source. Source is left in a valid empty state (size=0).

2. host_device_vector_r0: = default move (inherits from std::vector
   which has correct move; only adds deviceStorage unique_ptr transfer).

3. Array: = default move ctor/assign. Now safe because host_device_vector
   has correct move semantics (source is properly emptied).

4. ParArray: = default move (re-declared since explicit copy suppresses it).

5. ArrayTransformer: = default move. Transfers all shared_ptrs (father,
   son, pLGhostMapping, MPI request holders). Source left uninitialized.

6. ArrayPair::clone latent bug fix: after re-pointing trans.father/son
   to the newly cloned arrays, call trans.createMPITypes() to recreate
   persistent MPI requests pointing to the CORRECT buffers. Previously,
   persistent requests still referenced the source's (R's) buffers after
   clone, which was a silent corruption waiting to happen.

Root cause of the original regression (1880d48): = default move on
host_device_vector transferred unique_ptrs but left the cached raw
pointers (host_ptr, device_ptr) pointing to freed memory in the
moved-from object. Any code retaining a reference to the source would
dereference dangling pointers.

82/82 ctest pass.
@harryzhou2000
harryzhou2000 marked this pull request as ready for review May 4, 2026 16:03
@harryzhou2000 harryzhou2000 self-assigned this May 4, 2026
@harryzhou2000
harryzhou2000 merged commit 92cfe00 into CFDLAB-THU:main May 4, 2026
1 of 2 checks passed
harryzhou2000 added a commit to harryzhou2000/DNDSR that referenced this pull request May 21, 2026
CFDLAB-THU#6 (missing ∇R_mix): accepted — negligible in practice
CFDLAB-THU#7 (species uRecBeta): accepted — ρY_k inherit same compression as ρ
CFDLAB-THU#8 (CFDLAB-THU#9) (species positivity in checkRecBaseGood/validation): accepted — Y_k clipped directly
CFDLAB-THU#10 (CFDLAB-THU#11) (ppEpsIsRelaxed zeroes thresholds): accepted — by-design config toggle
CFDLAB-THU#12 (rhoH_form_old in CompressInc): done — fixed to rhoH_form_new
harryzhou2000 added a commit to harryzhou2000/DNDSR that referenced this pull request May 21, 2026
… clarify comments

LOW cleanups across 8 source files:

#1: speedOfSound already refactored in MED CFDLAB-THU#22 (Cantera API)
CFDLAB-THU#2: invR0() renamed to R0() — function returned R0=U0^2/T0, not its inverse
CFDLAB-THU#3: SourceCellAux::p=101325 comment clarified (code=phys with default scaling)
CFDLAB-THU#4: gamma before wall-fix comment added (UMeanXy unchanged by wall-fix)
CFDLAB-THU#5: e_sensible<=0 silent cp/cv fallback replaced with DNDS_assert_info
CFDLAB-THU#6: dead cellIsHalfAlpha/cellAdjAlphaMin lambdas marked 'Unused, kept for ref'
CFDLAB-THU#7: three unused muRef=phys_.muRef() lines removed from CompressInc
CFDLAB-THU#8: dead first outMap['RV'] = u[2] removed (overwritten by u[I4-1])
CFDLAB-THU#9: if(model==NS_2EQ) -> if constexpr in EvaluateDt.hxx lambda
CFDLAB-THU#10: hardcoded Vector<real,5/4> shock-tube BC comment added
CFDLAB-THU#11: uM1/uM2/uM3 dimension guard comments added (I4=dim+1)
CFDLAB-THU#12: KE-omission comment in dT_drho (was misplaced, now correctly at density derivative)
CFDLAB-THU#13: docstring 'perfect gas, variable' -> 'via Cantera EOS'
CFDLAB-THU#14: speciesEnthalpies comment expanded (ideal-gas vs non-ideal EOS)

All 56 audit findings resolved: 48 fixed, 8 accepted.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant