Skip to content

Fix NEMO history MAX_/BGS_ residual groups: species-first indexing and finite maximum residuals - #2882

Open
bellonarts wants to merge 2 commits into
su2code:developfrom
babybluechips:fix_nemo_max_bgs_residual_output
Open

Fix NEMO history MAX_/BGS_ residual groups: species-first indexing and finite maximum residuals#2882
bellonarts wants to merge 2 commits into
su2code:developfrom
babybluechips:fix_nemo_max_bgs_residual_output

Conversation

@bellonarts

@bellonarts bellonarts commented Sep 5, 2026

Copy link
Copy Markdown

Proposed Changes

Two coupled defects in the NEMO history output, fixed in SU2_CFD/src/output/CNEMOCompOutput.cpp and SU2_CFD/src/solvers/CSolver.cpp. Please note the breaking change first: for NEMO solvers the history fields MAX_DENSITY and BGS_DENSITY are renamed to per-species fields MAX_DENSITY_0..._{nSpecies-1} / BGS_DENSITY_0..._{nSpecies-1} (screen and CSV headers max[Rho_i] / bgs[Rho_i]), and the previously missing MAX_ENERGY_VE / BGS_ENERGY_VE fields are added. NEMO configs that name MAX_DENSITY or BGS_DENSITY in CONV_FIELD or HISTORY_OUTPUT need updating. The old names reported wrong data for NEMO, so keeping them compatible would preserve the defect. The compressible (non-NEMO) output classes and their field names are untouched.

Defect 1: the MAX_RES and BGS_RES groups index the wrong variables. The NEMO solution vector is species-first (rho_0..rho_{n-1}, rhoU, rhoV, [rhoW,] rhoE, rhoEve) and the RMS_RES group indexes it that way, but the MAX_RES and BGS_RES groups in CNEMOCompOutput used the calorically-perfect layout (GetRes_Max(0) as density, GetRes_Max(1) as x-momentum, and so on). For AIR-5 the column labeled max[RhoV] is therefore the maximum residual of species 2, and the momentum, energy, and vibrational-electronic maxima are never written at all. This was verified bit-level on a cold-start AIR-5 case, where the pre-fix max[Rho], max[RhoU], and max[RhoE] values reappear identically as the post-fix max[Rho_0], max[Rho_1], and max[Rho_4]:

pre-fix column value (row 1) post-fix column value (row 1)
max[Rho] -0.7628021795 max[Rho_0] -0.7628021795
max[RhoU] -1.280241622 max[Rho_1] -1.280241622
max[RhoV] -inf max[Rho_2] -32
max[RhoW] -52.16156135 max[Rho_3] -32
max[RhoE] -23.85849377 max[Rho_4] -23.85849377
(never written) max[RhoU] -10.5940418
(never written) max[RhoV] -12.37755778
(never written) max[RhoW] -12.64325982
(never written) max[RhoE] 5.23432541
(never written) max[RhoEve] -13.07747482

Each newly written maximum satisfies max[.] >= rms[.] for its variable, as it must for two reductions of the same residual vector. Fix: register and load MAX_RES and BGS_RES with the same species-first layout as RMS_RES.

Defect 2: an exactly-zero maximum residual prints a literal -inf. CSolver::SetResidual_RMS floors the RMS reduction at EPS*EPS, but did not floor Residual_Max. A bitwise-zero residual, which is routine for an inert trace species at a cold start (zero freestream mass fraction, chemistry frozen at the cold freestream temperature), therefore reached the output as log10(0) and SU2 wrote the token -inf into history.csv and the screen table. That token breaks numeric consumers of the history file (CSV-to-float parsers, JSON pipelines with allow_nan=false, plotting tools). Fix: floor Residual_Max at EPS*EPS immediately after its global reduction, matching the RMS convention. BGS history reads Residual_BGS, which is already floored by SetResidual_BGS; the distinct Residual_Max_BGS storage is not consumed by this output path and is intentionally unchanged.

Neither change touches solver arithmetic. Verification:

  • Cold-start AIR-5 NEMO_NAVIER_STOKES case (3D, Ma 7.95, 55 K, GAS_COMPOSITION with three zero trace species), serial release build, 3 iterations. Pre-fix reproduces the defect exactly (max[RhoV] = -inf paired with rms[Rho_2] = -32). Post-fix: full 10-column max group, no nonfinite tokens, bit-level column mapping as tabled above, and all 32 non-max[*] columns (rms, forces, heat, CFL, linear solver, nonphysical points) bit-identical pre/post across all rows.
  • Compressible QuickStart (inv_NACA0012.cfg, 160 iterations to convergence): history.csv byte-identical pre/post.
  • Permanent unit coverage in UnitTests/SU2_CFD/output/CNEMOCompOutput_tests.cpp: 183 assertions exercise 2-D and 3-D field registration and loading, every species-first MAX/BGS offset, a zero maximum through the real SetResidual_RMS reduction (finite -32), and the multizone SetResidual_BGS path through LoadHistoryData.
  • No new compiler warnings in the touched translation units.

Related Work

No open issue or PR was found for either defect. The defect is present at v8.5.0 and on the develop tip. This PR is independent of the mass-fraction tolerance fix in #2880 and the interrupt-exit reporting fix in #2881; it changes NEMO history output only.

PR Checklist

Put an X by all that apply. You can fill this out after submitting the PR. If you have any questions, don't hesitate to ask! We want to help. These are a guide for you to know what the reviewers will be looking for in your contribution.

  • I am submitting my contribution to the develop branch.
  • My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson).
  • My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/).
  • I used the pre-commit hook to prevent dirty commits and used pre-commit run --all to format old commits.
  • I have added a test case that demonstrates my contribution, if necessary.
  • Documentation is not applicable; the renamed NEMO history fields and compatibility impact are documented in this PR description.

bellonarts and others added 2 commits September 3, 2026 13:08
…d finite maximum residuals

The NEMO solution vector is species-first, but CNEMOCompOutput wrote the
MAX_RES and BGS_RES history groups with calorically-perfect indexing:
max[RhoV] read Res_Max(2), which is the residual of species 2, and the
momentum, energy, and vibrational-electronic maxima were never written at
all. Register and load both groups with the same species-first layout as
RMS_RES (max[Rho_0..n], max[RhoU], max[RhoV], max[RhoW], max[RhoE],
max[RhoEve]; likewise bgs[*]).

Res_Max and Res_Max_BGS also had no floor, unlike the EPS^2 floor applied
to the RMS reductions in CSolver::SetResidual_RMS, so an exactly-zero
residual, for example an inert trace species at a cold start, printed a
literal "-inf" into history.csv and the screen table, breaking numeric
consumers of the history file. Floor both maxima at EPS^2, matching the
RMS convention (log10 floor of -32).

Verified on a cold-start AIR-5 NEMO case: the pre-fix max[Rho], max[RhoU],
and max[RhoE] values reappear bit-identically as max[Rho_0], max[Rho_1],
and max[Rho_4]; the inert-species -inf cell prints -32; the newly written
momentum and energy maxima satisfy max >= rms; every previously existing
non-max column is bit-identical. The compressible QuickStart history is
byte-identical before and after the fix. GetRes_Max has no in-tree
consumer besides the log10 output sites.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Exercise species-first MAX/BGS output in 2D and 3D, the zero-MAX finite log floor, and the real multizone BGS load path.

Remove the unused Residual_Max_BGS floor: BGS output reads Residual_BGS, which is already floored.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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