Skip to content

Fix correctness, AD, type-stability, and MPI-efficiency bugs from full audit#26

Merged
subhk merged 2 commits into
mainfrom
fix/audit-correctness-mpi-ad
Jul 7, 2026
Merged

Fix correctness, AD, type-stability, and MPI-efficiency bugs from full audit#26
subhk merged 2 commits into
mainfrom
fix/audit-correctness-mpi-ad

Conversation

@subhk

@subhk subhk commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Summary

Two audit passes over the codebase — first serial + AD, then the MPI/distributed layer — surfaced a set of confirmed defects. This PR fixes all of them. Every fix is verified: the serial suite passes 67253/0/0, distributed AD is FD-verified single-rank and on 2 and 3 ranks via real mpiexec, and the rotation/batch AD fixes ship with new finite-difference tests.

Correctness

  • Distributed AD crashed on first use. The rrules in SHTnsKitParallelADExt called bare globalindices/communicator, which live only in the sibling SHTnsKitParallelExt module and are not exported by PencilArrays → UndefVarError. Added local wrappers. (FD-verified 1.1e-10.)
  • MPI deadlock. adaptive_spectral_communication! selected its collective branch from rank-local sparsity → ranks diverged → hang. The decision is now made collectively via Allreduce.
  • Divergence guard hung instead of erroring. _validate_cfg_replicated threw on only the mismatched ranks, leaving rank 0 stuck at later collectives. It now Allreduces the mismatch so all ranks raise together.
  • NaN Legendre tables at poles. prepare_plm_tables! divided by sinθ=0 on pole-inclusive grids. It now detects pole nodes, warns, and keeps the pole-safe on-the-fly path.
  • Packed storage + non-default norm crashed (MethodError calling the matrix-only convert_alm_norm! on a packed vector) — now scales the packed vector in place. Also guards first([]) on a rank owning zero longitude columns.
  • dist_*_packed_cplx now stores the Hermitian −m value (was verbatim +m) and documents that truly-complex fields are unsupported.
  • shtns_init now honors the documented SHT_NO_CS_PHASE / SHT_REAL_NORM flags (were silently ignored).
  • fdgrad_* distributed-array guard was unreachable for 2-D inputs (AbstractMatrix is more specific); moved onto the matrix methods.
  • Normalization scale-matrix cache is invalidated on norm/cs_phase change (was keyed on size only).
  • Documented SH_mul_mx no-alias and @sht_loop field-access limitations; SH_to_lat_cplx now guards mres==1.

Automatic differentiation

  • All packed rotation adjoints were wrong. SH_Zrotate/SH_Yrotate/SH_Yrotate90/SH_Xrotate90 (in both AdvancedADExt and ZygoteExt) returned incorrect Qlm-gradients. The correct standard-inner-product adjoint is Q̄ = W·R⁻¹·(W⁻¹ȳ) with W=diag(wm), wm=2 for m>0; the code used the field-inner-product form (Zrotate also conjugated the cotangent, Xrotate90 used non-inverse ZYZ angles). New test/serial/test_rotation_gradients.jl (24 FD checks).
  • synthesis_batch rrule used analysis as the synthesis adjoint (off by Gauss weights); now uses _adjoint_synthesis per slice. New batch-AD FD test.
  • Distributed sphtor pullback skipped per-element unthunking on the Tuple-cotangent branch → Matrix(::Thunk) failure; now always unthunks.

Type stability (Float32 / ForwardDiff.Dual)

  • Derived accumulator/buffer eltype instead of hardcoding ComplexF64/Float64 in _adjoint_synthesis, _adjoint_analysis, batch synthesis, LoopVec few-m analysis, batch AD rrules, and the energy diagnostics.

Efficiency

  • efficient_spectral_reduce! routed to a "hierarchical" reducer that wraps the same MPI.Allreduce! in two per-call Comm_split collectives for zero algorithmic gain (tree_reduce! is literally Allreduce!). Now calls Allreduce! directly.
  • Skips the per-call reduce-comm Comm_split for the common θ-decomposition.
  • Batches the 3-way qst reduces and the sphtor-pullback / reduce into single collectives; hoists the Wigner log-factorial buffer in the truncated Y-rotation gather.
  • Collapses the ±m Legendre recompute to once per |m| in the complex-packed synthesis/analysis/point evaluators.

Verification

serial suite            67253 passed / 0 failed / 0 errored
distributed AD (1 rank) FD 1.1e-10
2 & 3 ranks (mpiexec):
  dist_analysis vs serial   1e-16   OK
  dist scalar AD vs FD       0.0    OK
  dist sphtor AD vs FD       1e-11  OK
  divergence guard  all ranks throw OK (no hang)
rotation AD FD (24)     pass
batch AD FD             pass

Notes

  • No version bump and no Project.toml change.
  • Design note (not fixed here): the PencilArrays default Pencil((nlat,nlon),comm) splits φ, which routes through the anti-scaling Allgatherv path; scalable runs need explicit θ-decomposition Pencil((nlat,nlon),(1,),comm) or the transpose plan. The code already warns.

🤖 Generated with Claude Code

subhk and others added 2 commits July 7, 2026 07:01
…l audit

Two audit passes (serial+AD, then MPI/distributed) surfaced a set of
confirmed defects; each fix below is verified (serial suite 67253/0/0;
distributed AD FD-verified single-rank and on 2/3 ranks via mpiexec).

Correctness
- ext: distributed AD rrules called bare `globalindices`/`communicator`
  (defined only in the sibling ParallelExt module, not exported by
  PencilArrays) -> UndefVarError on first use; add local wrappers.
- ParallelExt `adaptive_spectral_communication!` chose its MPI collective
  branch from rank-local sparsity -> divergent collectives -> deadlock;
  decide it collectively via Allreduce.
- ParallelPlans `_validate_cfg_replicated` threw on only the mismatched
  ranks -> matching ranks hung at later collectives; Allreduce the
  mismatch so all ranks raise together.
- config `prepare_plm_tables!` divided by sinθ at pole nodes -> NaN tables
  on pole-inclusive grids; detect poles, warn, keep on-the-fly path.
- ParallelTransforms packed-storage + non-default norm called the
  matrix-only `convert_alm_norm!` on a packed Vector -> MethodError; scale
  the packed vector in place instead. Also guard `first([])` on a rank
  owning zero longitude columns.
- ParallelLocal `dist_*_packed_cplx` filled the -m slot verbatim; store the
  Hermitian value and document that truly-complex fields are unsupported.
- api_compat `shtns_init` ignored the documented SHT_NO_CS_PHASE /
  SHT_REAL_NORM flags; parse and thread them through.
- ForwardDiffExt `fdgrad_*` distributed-array guard was unreachable for 2-D
  inputs (AbstractMatrix is more specific); move the guard onto the matrix
  methods.
- normalization cache (`_ensure_norm_scale_matrix!`) was keyed on size
  only; invalidate it in setproperty! on norm/cs_phase change.
- SH_mul_mx aliasing and @sht_loop field-access limitations documented;
  SH_to_lat_cplx now guards mres==1.

Automatic differentiation
- All packed rotation adjoints (SH_Zrotate/Yrotate/Yrotate90/Xrotate90, in
  both AdvancedADExt and ZygoteExt) returned wrong Qlm-gradients: the
  standard-inner-product adjoint is Q̄ = W·R⁻¹·(W⁻¹ȳ) with W=diag(wm),
  wm=2 for m>0; the code used the field-inner-product form (and Zrotate
  conjugated the cotangent, Xrotate90 used non-inverse angles). Add a new
  FD test (test_rotation_gradients.jl, 24 checks).
- `synthesis_batch` rrule used `analysis` as the synthesis adjoint (off by
  Gauss weights); use `_adjoint_synthesis` per slice. Add batch-AD FD test.
- Distributed sphtor pullback skipped per-element unthunking on the Tuple
  cotangent branch -> Matrix(::Thunk) failure; always unthunk.

Type stability (breaks Float32 / ForwardDiff.Dual)
- Derive accumulator/buffer eltype instead of hardcoding ComplexF64/Float64
  in `_adjoint_synthesis`, `_adjoint_analysis`, batch synthesis, LoopVec
  few-m analysis, batch AD rrules, and the energy diagnostics.

Efficiency
- ParallelExt `efficient_spectral_reduce!` routed to a "hierarchical" path
  that wraps the same MPI.Allreduce! in two per-call Comm_split collectives
  for zero gain (tree_reduce! == Allreduce!); call Allreduce directly.
- Skip the per-call reduce-comm Comm_split for the common theta
  decomposition (reduce over the full comm).
- Batch the 3-way qst reduces and the sphtor pullback S̄/T̄ reduce into one
  collective; hoist the Wigner log-factorial buffer in the truncated
  Y-rotation gather.
- Collapse the +/-m Legendre recompute to once per |m| in the complex
  packed synthesis/analysis/point evaluators.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… division

The pole-NaN fix disabled precomputed tables entirely on pole-inclusive
regular/DH grids, which regressed `create_regular_config(...,
precompute_plm=true, include_poles=true)` (test asserts use_plm_tables).

Value tables (plm/NP) have no sinθ division and are correct at poles, so
table use stays enabled. Only the derivative tables' `-(dP̄/dθ)/sinθ`
needs guarding: at an exact pole sinθ=0 gives 0/0 -> NaN. Set the pole
entry to 0, which is kernel-consistent (the kernel's ×sinθ makes
dθY = -0·NdP = 0 at the pole regardless) and avoids NaN propagation.

Verified: DH pole grid keeps use_plm_tables=true, derivative tables are
NaN-free, scalar roundtrip err 2.9e-15; full test/runtests.jl passes
(serial 67253, parallel-grid 1673).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 58.00000% with 63 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
ext/ParallelTransforms.jl 0.00% 14 Missing ⚠️
ext/SHTnsKitParallelADExt.jl 0.00% 9 Missing ⚠️
ext/SHTnsKitParallelExt.jl 0.00% 9 Missing ⚠️
ext/ParallelLocal.jl 20.00% 8 Missing ⚠️
src/config.jl 40.00% 6 Missing ⚠️
ext/SHTnsKitAdvancedADExt.jl 72.22% 5 Missing ⚠️
ext/SHTnsKitLoopVecExt.jl 0.00% 4 Missing ⚠️
ext/ParallelPlans.jl 0.00% 3 Missing ⚠️
ext/ParallelRotationsPencil.jl 0.00% 2 Missing ⚠️
src/batch_transforms.jl 77.77% 2 Missing ⚠️
... and 1 more
Files with missing lines Coverage Δ
ext/SHTnsKitForwardDiffExt.jl 37.03% <100.00%> (+2.42%) ⬆️
ext/SHTnsKitZygoteExt.jl 92.21% <100.00%> (+27.36%) ⬆️
src/api_compat.jl 94.63% <100.00%> (+0.04%) ⬆️
src/complex_packed.jl 99.08% <100.00%> (+2.92%) ⬆️
src/energy_diagnostics.jl 100.00% <100.00%> (ø)
src/local.jl 93.97% <100.00%> (+0.02%) ⬆️
src/loop.jl 80.48% <ø> (ø)
src/operators.jl 100.00% <ø> (ø)
src/core_transforms.jl 74.51% <75.00%> (+0.07%) ⬆️
ext/ParallelRotationsPencil.jl 34.44% <0.00%> (-0.17%) ⬇️
... and 9 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@subhk subhk merged commit 7c164fa into main Jul 7, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant