Skip to content

Register SURFACE_SPECIES_VARIANCE as a coefficient in the Python history map - #2930

Closed
ManasBagul23 wants to merge 61 commits into
su2code:masterfrom
ManasBagul23:fix-species-variance-history-fields
Closed

ManasBagul23 wants to merge 61 commits into
su2code:masterfrom
ManasBagul23:fix-species-variance-history-fields

Conversation

@ManasBagul23

Copy link
Copy Markdown

SURFACE_SPECIES_VARIANCE is registered in C++ as HistoryFieldType::COEFFICIENT, but its description string contains a comma, which broke updateHistoryMap.py's parsing: it read TYPE as DEFAULT, truncated the description at the comma, and never created the corresponding D_/TAVG_ entries. As a result SU2_PY skipped the adjoint path for this field (gradients.py:96), skipped it in aerodynamics (functions.py:94), and left it out of opt_names (functions.py:212).

This was the only field across all 11 output files affected by this problem. Fixed by removing the comma from the description in CFlowOutput.cpp:90 and hand-adding the 4 corresponding history-map entries. Verified they are byte-identical to what the (fixed) generator emits, black-clean, and the surrounding key order is unchanged.

Prepared with AI assistance; I reviewed the changes.

pcarruscag and others added 30 commits April 29, 2026 17:31
* allow fsi+cht

* Apply suggestions from code review

Co-authored-by: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com>

* set regression values

* back to develop
…#2806)

* Reduce redundant CUDA Jacobian uploads

* Move CUDA Jacobian upload into CSysMatrixVectorProduct

* Defer CUDA matrix upload to first matvec use
* flexible BC for flamelet equations
* add jacobian of the split source terms, rename FLAMELET->FLAME
* add python wrapper to flamelet enthalpy

---------

Co-authored-by: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com>
su2code#2815)

* allow adjoin-only, fix residual and objective reporting in single and multizone solvers

* save main recording for functions that don't need it

* update ref
…tion for ARM (su2code#2819)

* add option

* define single precision type, mpi, avoid repeated instantiations in mixed precision

* use correct tecplot function

* option 1 to handle double precision literals: replace min/max with fmin/fmax

* Revert "option 1 to handle double precision literals: replace min/max with fmin/fmax"

This reverts commit 4e4d9e1.

* option 2: overload std::min/max for float/double mix, handle blas and lapack functions, fix some FPEs

* fix some FPEs

* vectorization for floats

* arm vectorization

* arm fix and use cpu arch for arm, fix some pass by value warnings

* remove commented code

* remove debug stuff

* update regressions
Co-authored-by: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com>
Base early exit on proper residual
fix some implicit line agglomeration
add more debugging info
---------

Co-authored-by: Pedro Gomes <pcarruscag@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
* Change nested classes to friend classes

* Include discrete adjoint multizone driver in python wrapper

* Re-define AdjointProduct and Identity as template classes

* address comments

---------

Co-authored-by: Pedro Gomes <pcarruscag@gmail.com>
* do not compute vorticity when it's not necessary

* fix

* fix restarts
… T <= 0 (su2code#2830)

* detect temperature outliers

* non simd too

* fix

* fix AD OMP build

* avoid zero-ing the whole matrix

* update regressions

* file diffs
… preconditioners (su2code#2838)

* ldu

* orientation not necessary

* sort to simplify edge map

* apply to ILU, cleanup

* cleanup

* cleanup

* cleanup pastix wrapper

* avoid two copies in pastix wrapper

* missing ZONE_SCOPED

* fix UB

* update regressions

* fix tests

* Apply suggestion from @pcarruscag

* last update
* Implemented the reading of a binary SU2 format

* Ran pre-commit

* Addressing issues

* Forgot to run pre-commit

* Addressing comments

* Set up the infrastructure for the writing of a binary grid file

* Made sure to save these changes

* review

* writer and test

* Added the number of DOFs per element when writing the file of the Jacobian matrix

* fix

---------

Co-authored-by: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com>
Co-authored-by: Pedro Gomes <pcarruscag@gmail.com>
…u2code#2844)

* bounded scalar for compressible

* tests

* fix windows build, update tests
…code#2846)

* Move BC_Riemann definition from CTurbSolver to CScalarSolver

This allows BC_Riemann to be used with all scalar solvers, particularily
the species solver.

* Add Riemann boundary check to CSpeciesSolver::SetUniformInlet

* Add regression test for species transport with Riemann BC
…ion (su2code#2829)

* Enable viscous flux contribution at supersonic outlet boundary condition

The viscous flux contribution in BC_Supersonic_Outlet was previously
commented out due to reported convergence issues. However, for viscous
NS simulations this omission is physically incorrect and causes severe
convergence problems at outlet boundary cells, which also prevents the
use of second-order MUSCL reconstruction.

For supersonic flows, downstream disturbances cannot propagate upstream,
so the outlet state can be safely extrapolated from the domain interior.
Using SetPrimitive(V_domain, V_domain) instead of (V_domain, V_outlet)
resolves the convergence problems that motivated the original commenting.

* Fix: declare Point_Normal in BC_Supersonic_Outlet viscous section

* Add laminar flat plate test case config (Ma=4.0, Re=1.2e4)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Remove test case files from PR

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Add supersonic flat plate test case configuration (Ma=4.0, Re=1.2e4)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Add flatplate_supersonic to parallel regression tests

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Update TestCases/navierstokes/flatplate/flatplate_supersonic.cfg

Co-authored-by: Nijso <nijso@hotmail.com>

* Update TestCases/navierstokes/flatplate/flatplate_supersonic.cfg

Co-authored-by: Nijso <nijso@hotmail.com>

* Update TestCases/navierstokes/flatplate/flatplate_supersonic.cfg

Co-authored-by: Nijso <nijso@hotmail.com>

* Update TestCases/navierstokes/flatplate/flatplate_supersonic.cfg

Co-authored-by: Nijso <nijso@hotmail.com>

* Update supersonic flat plate config

* Apply suggestion from @bigfooted

* Apply suggestion from @bigfooted

* Add Yilun Tan to AUTHORS.md

---------

Co-authored-by: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com>
Co-authored-by: Nijso <bigfootedrockmidget@hotmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Nijso <nijso@hotmail.com>
…ions (su2code#2836)

* int8 quantization

* [skip ci]

* [skip ci]

* full crazy

* micro optimize

* compress more [skip ci]

* consistency [skip ci]

* productize quantization as a new preconditioner

* cleanup

* cleanup overloads

* reduce potential incompatibility with quantization

* typedef int type

* address more compatibility

* final fixes for non-simd numerics

* heat solver fix

* need to check da_sp_pinArray_cht_2d_dp_hf

* add comp and incomp tests

* unit test

* unit tests + updates

* updates

* multizone bin test

* fix binary reader

* fix

* fix

* again
## Proposed Changes

This PR adds an end-to-end CUDA linear solve path: the Krylov solvers
keep their host control flow, and `CSysVector` operations, the SpMV and
the Jacobi preconditioner are dispatched to CUDA kernels when
`ENABLE_CUDA=YES`.

Transfers are explicit and owned by the object responsible for the data,
with no coherency or dirty-flag tracking in `CSysVector` / `CSysMatrix`:
- `CSysMatrixVectorProduct` uploads the matrix on construction
- the preconditioner uploads its data in `Build()`
- `CSysSolve` uploads `b` and `x` and downloads `x` in
`HandleTemporariesIn/Out`, which is also where device evaluation is
switched on and off
- preconditioners without a device implementation (ILU, LU-SGS, Linelet,
PaStiX) download the input, apply on the host, and upload the result

A solve is therefore fully device resident for Identity and Jacobi
preconditioners: **2 uploads and 1 download per linear system**,
independent of the Krylov subspace size.

Implementation notes:
- `cuBLAS` for `dot` / `norm`, custom kernels for the block-LDU SpMV,
the Jacobi apply, and `CSysVector` expression assignment
- `CSysVector` operands are captured in expressions by value, so an
arbitrary expression tree is trivially copyable into the assignment
kernel; the required expression shapes are explicitly instantiated in
`CSysVectorGPU.cu`, consistent with how `CSysMatrix` is instantiated
- device work is issued by a single thread with the OpenMP team
synchronized around it, so the GPU path is usable from inside the
existing parallel regions; this is internal to the linear algebra layer
- the CUDA translation units are only linked into the primal libraries,
since they cannot be compiled with the CoDiPack defines; device dispatch
is compiled out of the AD builds
- adds `LINEAR_SOLVER_PREC= NONE` (identity)

## Related Work

Follows the review direction in su2code#2822 (show a working end-to-end GPU
linear solve before splitting out infrastructure) and the implementation
preferences in su2code#2816.

## Validation

- CPU vs GPU on inviscid NACA0012, 25 iterations, RTX 4070 Ti SUPER
(sm_89), for FGMRES + `JACOBI`, FGMRES + `NONE`, FGMRES + `ILU` (host
preconditioner path) and BCGSTAB. Results agree to the printed precision
in double, and to ~6 significant figures in mixed precision. BCGSTAB
agrees exactly once the linear system is converged
(`LINEAR_SOLVER_ERROR=1e-10`); at loose tolerances the two paths diverge
through BCGSTAB's own sensitivity, not a difference in the algebra.
- Mixed, normal and single precision builds all compile and run; device
dispatch confirmed live in each (kernel launch counts track the subspace
size).
- `OMP_NUM_THREADS=1` and `4` give bit-identical results on the GPU
path.
- Builds verified: primal, primal + AD + directdiff, `enable-cuda` +
`with-omp`, mixed / normal / single precision.
- Transfer counts measured per solve: 2 H2D + 1 D2H + 1 matrix upload
for Jacobi and `NONE`, plus one download/upload pair per preconditioner
application for ILU.

Earlier validation of the original design (6 representative cases,
`nsys` / `ncu` profiling) predates the rework of the transfer and
dispatch model and should be repeated.

## PR Checklist

- [x] I am submitting my contribution to the develop branch.
- [x] My contribution generates no new compiler warnings (try with
--warnlevel=3 when using meson).
- [x] My contribution is commented and consistent with SU2 style
(https://su2code.github.io/docs_v7/Style-Guide/).
- [x] 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.
- [ ] I have updated appropriate documentation (Tutorials, Docs Page,
config_template.cpp), if necessary.

---------

Co-authored-by: Pedro Gomes <pcarruscag@gmail.com>
…e#2738) (su2code#2855)

## Proposed Changes

The QCR2000 quadratic constitutive relation (Spalart, *Int. J. Heat and
Fluid Flow*, 2000) modifies the turbulent (Boussinesq) stresses, but
`CNumerics::AddQCR` was applied to the total stress tensor at all call
sites: laminar + eddy in the viscous fluxes (scalar and SIMD paths), and
the **purely laminar** tensor in `Friction_Forces`, where the eddy
viscosity is zero and the correction should vanish identically. For a
pure-shear wall state this injects spurious normal stresses of
±0.6·τ_wall into the wall traction, which redistributes drag between the
pressure and friction components while leaving total drag nearly
unchanged — the exact signature reported in su2code#2738.

This PR scales the correction by the turbulent fraction of the viscosity
used to build the tensor, `mu_t / (mu_l + mu_t)`, which recovers the
turbulent-only correction exactly because the stress tensor is linear in
the viscosity (SA carries no turbulent-kinetic-energy term). The
wall-force site passes a zero fraction on smooth walls. A unit test
covers both properties (zero correction at `mu_t = 0`; scaled-total ≡
separate-turbulent-tensor).

**Validation** (Ubuntu 24.04, source build at `de8c5015`, the su2code#2738
reporter's Joukowski R2 grid, 12000 iters, rms[Rho] ≈ −11):

| run | CDp | CDv | CD |
|---|---|---|---|
| SA | 0.003180 | 0.004735 | 0.007915 |
| SA-QCR2000, before | 0.003482 | 0.004432 | 0.007914 |
| SA-QCR2000, after | 0.003183 | 0.004716 | 0.007898 |
| SA on patched build | 0.003179 | 0.004735 | 0.007915 |

Before the change, QCR2000 shifts CDp +9.5% / CDv −6.4%; after it, the
split returns to within 0.1% of SA, consistent with CFL3D/FUN3D/Fluent
behavior for this attached flow ([HiFi CFD verification workshop
references](https://highfidelitycfdverificationworkshop.github.io/papers/rans.pdf)).
With QCR disabled the patched build is behaviorally unchanged.

Note: the `turb_SA_QCR_RAE2822` reference values in
`parallel_regression.py` shift by 1e-4…4e-3 (verified on a same-platform
before/after pair); I will update them from this PR's CI output, since
reference values are runner-specific. Opening as a draft until then.

## Related Work

Fixes su2code#2738.

## PR Checklist

- [x] I am submitting my contribution to the develop branch.
- [x] My contribution generates no new compiler warnings (try with
--warnlevel=3 when using meson).
- [x] My contribution is commented and consistent with SU2 style
(https://su2code.github.io/docs_v7/Style-Guide/).
- [x] I used the pre-commit hook to prevent dirty commits and used
`pre-commit run --all` to format old commits.
- [x] I have added a test case that demonstrates my contribution, if
necessary.
- [ ] I have updated appropriate documentation (Tutorials, Docs Page,
config_template.cpp), if necessary.

---------

Co-authored-by: Boateng Opoku-Yeboah <bigboateng@users.noreply.github.com>
## Proposed Changes
Currently, the species solver crashes when restarting a multi-grid
simulation due to mismatched grid levels in two calls in
`CSpeciesSolver::LoadRestart`. This PR fixes that, and modifies a test
to exercise this code path.

It'd be great if someone could also double-check whether this code makes
sense. Given this bug, I suspect these lines have never been run since
they were committed 5 years ago.

## Related Work
I'll send an accompanying PR for the new restart file needed for the
test case.

## PR Checklist

- [x] I am submitting my contribution to the develop branch.
- [x] My contribution generates no new compiler warnings (try with
--warnlevel=3 when using meson).
- [x] My contribution is commented and consistent with SU2 style
(https://su2code.github.io/docs_v7/Style-Guide/).
- [x] I used the pre-commit hook to prevent dirty commits and used
`pre-commit run --all` to format old commits.
- [x] I have added a test case that demonstrates my contribution, if
necessary.
- [ ] I have updated appropriate documentation (Tutorials, Docs Page,
config_template.cpp), if necessary.

---------

Co-authored-by: Nijso <bigfootedrockmidget@hotmail.com>
## PR Checklist
- [X] I am submitting my contribution to the develop branch.
- [X] My contribution generates no new compiler warnings (try with
--warnlevel=3 when using meson).
- [X] My contribution is commented and consistent with SU2 style
(https://su2code.github.io/docs_v7/Style-Guide/).
- [X] 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.
- [X] I have updated appropriate documentation (Tutorials, Docs Page,
config_template.cpp), if necessary.

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…de#2641)

## Proposed Changes
Implements variable density treatment for unsteady incompressible flow
simulations. Currently, SU2 uses constant density in transient
simulations, which is inaccurate for combustion cases where density
varies significantly due to heat release and species composition
changes. This contribution enables proper density updates during
time-stepping for flamelet-based combustion modeling. For now, only 1st
order time marching is implemented

## Related Work
Related to incompressible flow solver and flamelet combustion modeling.
No specific issue linked yet.



## PR Checklist

- [X] I am submitting my contribution to the develop branch.
- [X] My contribution generates no new compiler warnings (try with
--warnlevel=3 when using meson).
- [X] My contribution is commented and consistent with SU2 style
(https://su2code.github.io/docs_v7/Style-Guide/).
- [x] I used the pre-commit hook to prevent dirty commits and used
`pre-commit run --all` to format old commits.
- [x] I have added a test case that demonstrates my contribution, if
necessary.
- [ ] I have updated appropriate documentation (Tutorials, Docs Page,
config_template.cpp), if necessary.

---------

Co-authored-by: Tahsin Berk Kiymaz <tberk@int6.local.snellius.surf.nl>
Co-authored-by: Tahsin Berk Kiymaz <tberk@int5.local.snellius.surf.nl>
Co-authored-by: Nijso <bigfootedrockmidget@hotmail.com>
Co-authored-by: Cristopher Morales <98025159+Cristopher-Morales@users.noreply.github.com>
Co-authored-by: bigfooted <nijso@hotmail.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: Nijso Beishuizen <nijso.beishuizen@nlr.nl>
Co-authored-by: Tahsin Berk Kiymaz <tberk@tcn222.local.snellius.surf.nl>
Co-authored-by: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com>
Co-authored-by: Berk Kıymaz <tkiymaz@berks-air.home>
Co-authored-by: Berk Kıymaz <tkiymaz@Berks-MacBook-Air.local>
Co-authored-by: Berk Kıymaz <tkiymaz@berks-macbook-air.home>
…nditioner on GPU and CUDA Unified preconditiong matrix) (su2code#2843)

## Proposed Changes
This PR introduces improves the GPU implementation of the multiDot
product between Krylov vectors used in the FGMRES solver. It moves from
a looped instantiation of pairwise dot products to a batched one-time
kernel launch. A custom CUDA kernel and the batched gemm using
cublas<t>gemm are benchmarked, the latter being eventually selected.

## Original proposal for the GSoC
This PR introduces CUDA Unified memory and Managed memory allocation,
and memory management for the CSysVector class and the preconditioning
matrix inside the Jacobi preconditioner. This allows for a benchmark
between the two memory strategies within the FGMRES solver.

Also, this draft PR extends the section of GPU execution inside the
FGMRES solver
Custom CUDA kernels are implemented for the preconditioning matrix, the
multi dot product and the linear combination (inside the Modified
Gram-Schmidt orthogonalization), and the vector norm calculation. Unary
vector-scalar operations based on templates are also offloaded to GPU
through a generic kernel. Moreover, abstract Syntax Tree are deployed to
offload vector-vector binary operations to the GPU through a generic
kernel based on a runtime evaluation of the tree.

The solver logic is not modified, and the GPU path is hidden inside the
specific methods.

### Unified memory approach
su2code@5fe25b8

A custom data() method recovers the CSysVector Unified pointer inside
the CUDA logic, cleaning the logic from the double host/device pointers.
All memory explicit memory copies are also removed from the CUDA logic,
but explicit device synchronizations are introduced around MPI calls and
at the end of the CUDA section.

### Managed memory approach
su2code@4b43fa0

Operators accessing the CSysVector on Host after GPU operations demand
synchronization, which is introduced explicitly in each.

## Performance evaluation (on-going)
For the considered test-case (rae2822), the CPU execution expresses an
**Avg. s/iter: 0.198681**.
The GPU execution with Unified memory expresses an **Avg. s/iter:
0.238953**.
The GPU execution with Managed memory expresses an **Avg. s/iter:
0.372046**.
The CPU is a Intel(R) Xeon(R) E-2276M CPU @ 2.80GHz with 12 cores. The
GPU is a Quadro P620. Tests on more advanced hardware are ongoing.
Profiling results comparing the Unified Memory (left column) against the
Managed Memory (right column) are available in the attached pdf:

[SU2_ra2822_GPU_MA_vs_UM_nsys_prof.pdf](https://github.com/user-attachments/files/30520807/SU2_ra2822_GPU_MA_vs_UM_nsys_prof.pdf)

## Asynchronous pre-fetching:
The Jacobi preconditioner calculations are performed on GPU through a
new custom CUDA kernel under the preconditioner abstraction.
The preconditioning matrix is selected to test CUDA Unified Memory
asynchronous prefetching to the GPU.
For simplicity, the double CPU/GPU pointer is still maintained in the
current logic, although the device pointer reduces to an alias for the
Unified Memory pointer when this kind of allocation is adopted.
 
**This strategy introduces a simple context to test the CUDA Unified
Memory usage and study the possibility of overlapping memory transfers
and calculations without the need to introduce CUDA streams.**

Concretely, this PR:
- introduces new CUDA Unified Memory allocation methods and asynchronous
prefetching;
- introduces the GPU logic for the Jacobi preconditioner;
- introduces the GPU logic for the multi dot product and the linear
combination (Modified Gram-Schmidt orthogonalization);
- introduces the GPU logic for the vector norm operation;
- introduces the GPU logic for generic scalar-vector unary operations
through templates and generic vector/scalar-vector binary operations
through Abstract Syntax Tree evaluated at runtime;
- finally falls back to CUDA Managed memory as highlighted in the
following discussion. This introduces the need for explicity
synchronization in all the custom setter/getter methods of the
CSysVector.

This work is part of my ongoing contribution during the Google Summer of
Code 2026 program.

## Validation
Validated locally with:
- serial CUDA build compilation
- serial CPU build compilation
- CPU/GPU numerical comparison on 1 representative case (rae2822) tested
with LINEAR_SOLVER_PREC=JACOBI with both CUDA Unified and Managed memory
approaches.

Nsys profiling was performed to confirm the asynchronous prefetching of
the CUDA Unified Memory preconditioning matrix on my local GPU. Partial
prefetching is observed, although page faults were reported during the
preconditioner CUDA kernel, indicating that the calculations were slowed
down by the prefetching matrix still being transferred to the GPU. This
overlap is expected to largely improve on more modern hardware; tests
are ongoing in the cloud.

## Related Work
The Jacobi preconditioner kernels come from the PR su2code#2825.

## Observed Issues
Some issues were observed during this first period of GSoC:
- PR su2code#2825 compiles with CUDA 13.3 but CUSPARSE calls raise an unknown
operation at the first matrix-vector product.
- the build.meson file has a hard-coded CUDA arch.
- the HAVE_MPI flag is not being passed to the nvcc compiler in the
develop branch. This raises a linking error if MPI operations are
included within the *.cu files. That is not the case in the master
branch.

## Next steps
I propose to continue working on the following steps:
- [X] extend the CUDA Unified Memory allocation to the CSysVector class
- [X] evaluate if it might be of interest to extend the CUDA Unified
Memory allocation to the CSysMatrix class; Update: discussion with
mantainer indicates preference for Managed Memory approach;
- [X] benchmark the multiDot approached: looped vs custom kernel batched
vs cublas<t>gemm batched: batched approaches are convenient due to just
one kernel launch overhead, cublas implementation shows moderate
speed-up (0.01s) against the custom CUDA kernel on local P620 but
ensures less code complexity, thus it is selected.

## PR Checklist

- [X] I am submitting my contribution to the develop branch.
- [X] My contribution generates no new compiler warnings (try with
--warnlevel=3 when using meson).
- [X] My contribution is commented and consistent with SU2 style
(https://su2code.github.io/docs_v7/Style-Guide/).
- [X] 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.
- [ ] I have updated appropriate documentation (Tutorials, Docs Page,
config_template.cpp), if necessary.

---------

Co-authored-by: Pedro Gomes <pcarruscag@gmail.com>
## Proposed Changes
Also compatible with GPU


## PR Checklist
- [X] I am submitting my contribution to the develop branch.
- [X] My contribution generates no new compiler warnings (try with
--warnlevel=3 when using meson).
- [X] My contribution is commented and consistent with SU2 style
(https://su2code.github.io/docs_v7/Style-Guide/).
- [X] 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.
- [X] I have updated appropriate documentation (Tutorials, Docs Page,
config_template.cpp), if necessary.

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
joshkellyjak and others added 27 commits August 24, 2026 10:48
## Proposed Changes
Fix type comparison error introduced by turbo adjoints

## 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.*

- [X] I am submitting my contribution to the develop branch.
- [X] My contribution generates no new compiler warnings (try with
--warnlevel=3 when using meson).
- [X] My contribution is commented and consistent with SU2 style
(https://su2code.github.io/docs_v7/Style-Guide/).
- [X] I used the pre-commit hook to prevent dirty commits and used
`pre-commit run --all` to format old commits.
- [X] I have added a test case that demonstrates my contribution, if
necessary.
- [X] I have updated appropriate documentation (Tutorials, Docs Page,
config_template.cpp), if necessary.

Co-authored-by: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com>
Co-authored-by: Josh Kelly <J.Kelly7@liv.ac.uk>
## Proposed Changes
The original trapezoidal map implementation needs excessive memory for
large lookup tables (LUT) (e.g., 300+ MB for tables with ~80k points).
This PR introduces a new memory-efficient trapezoidal map implementation
(LUT_FAST) for Flamelet-Generated Manifold (FGM) lookup tables, based on
LUT algorithm of Pedro Gomes. The new implementation reduces memory
usage while maintaining query performance. A new unit test is created
and all tests are passed.


## Related Work

This code is an implementation of https://github.com/pcarruscag/LUT to
SU2.



## 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.*

- [ x] I am submitting my contribution to the develop branch.
- [ x] 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.
- [x ] I have added a test case that demonstrates my contribution, if
necessary.
- [ ] I have updated appropriate documentation (Tutorials, Docs Page,
config_template.cpp), if necessary.

---------

Co-authored-by: tkiymaz <berkmetume@gmail>
Co-authored-by: Nijso <bigfootedrockmidget@hotmail.com>
Co-authored-by: Berk Kıymaz <tkiymaz@berks-air.home>
Co-authored-by: Berk Kıymaz <tkiymaz@berks-macbook-air.home>
## Proposed Changes
This is a split from su2code#2863 to clean up the different implementations.
This PR does not really improve convergence, it merely implements some
configuration options and makes it work robustly. An improvement comes
from the CFL ramping: every level of the startup phase in FMG gets its
own scaled CFL, then for this level CFL increases from the max CFL of
the previous level to the new CFL of this level over MG_STARTUP_ITER
iterations. This does mean some CFL jumps when you converge early. Also,
when you exit the startup phase and enter the final v-cycle phase, you
immediately jump to CFL_NUMBER, no ramping here.
The real future improvement is probably stepping away from the piecewise
constant interpolation. At the moment there is no improvement because
the coarser levels do not result in a better starting point for the
final v-cycle.

Implementation details:

MG_STARTUP_ITER= 100 %Full multigrid iterations per level for the warmup
phase.
MG_CFL_SCALING= 0.5, 0.5, 0.5 %Full multigrid now has CFL scaling for
the coarser levels during warmup phase
MG_STARTUP_CONVERGENCE= -2 %Full multigrid now exits the current level
during the warmup phase when convergence dropped.
MG_STARTUP_STAGNATION_ITER= 0.99 %Full multigrid now exits the current
level during the warmup phase when the level residual stalls.
MG_STARTUP_STAGNATION_ITER= 5 % 5 consecutive iteration of stagnation
before exiting.


## Related Work
su2code#2863 

## 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.*

- [x] I am submitting my contribution to the develop branch.
- [x] My contribution generates no new compiler warnings (try with
--warnlevel=3 when using meson).
- [x] My contribution is commented and consistent with SU2 style
(https://su2code.github.io/docs_v7/Style-Guide/).
- [x] I used the pre-commit hook to prevent dirty commits and used
`pre-commit run --all` to format old commits.
- [x] I have added a test case that demonstrates my contribution, if
necessary.
- [ ] I have updated appropriate documentation (Tutorials, Docs Page,
config_template.cpp), if necessary.

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com>
This PR ports the LU-SGS preconditioner to GPU

## Proposed Changes
This PR introduces a build phase for the LU-SGS preconditioner where the
inverse of the diagonal D is calculated to facilitate the compute phase.
Then, forward and backward sweeps are implemented through custom CUDA
kernels matching the logic of the CPU section (except for the inverse of
D, which is directly multiplied in the GPU logic). These kernels are
managed by the compute method on GPU which maintains the MPI
communications.

## Related Work
This PR follows the discussion of su2code#2843 . Kernel level dependencies are
managed as for the ILU preconditioner proposed in su2code#2858 .

## Validation
CPU and GPU implementations of the LU-SGS preconditioner are compared
for validation of this PR on the rae2822 test-case. After one iteration,
the first, preconditioned Krylov vector is compared finding matching
results down to machine precision. Residuals are compared after 100
iterations finding matching results.

## TO-DO
- [X] complete with the quantized_mode option path;
- [ ] run benchmarks on modern GPUs. Current evaluations on a P620 are
inconclusive;
- [ ] profiling of the custom kernels;

## 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.*

- [X] I am submitting my contribution to the develop branch.
- [X] My contribution generates no new compiler warnings (try with
--warnlevel=3 when using meson).
- [X] My contribution is commented and consistent with SU2 style
(https://su2code.github.io/docs_v7/Style-Guide/).
- [X] 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.
- [ ] I have updated appropriate documentation (Tutorials, Docs Page,
config_template.cpp), if necessary.

---------

Co-authored-by: Nijso <bigfootedrockmidget@hotmail.com>
…u2code#2721)

## Proposed Changes
Continuation of su2code#2442. 



## Related Work
- General improvements
- Adds the zone number to tags so that more mismatches/errors can occur
whenever a dependency is wrong or is not behaving as expected



## PR Checklist

- [x] I am submitting my contribution to the develop branch.
- [x] My contribution generates no new compiler warnings (try with
--warnlevel=3 when using meson).
- [x] My contribution is commented and consistent with SU2 style
(https://su2code.github.io/docs_v7/Style-Guide/).
- [x] 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.
- [ ] I have updated appropriate documentation (Tutorials, Docs Page,
config_template.cpp), if necessary.
…ients (su2code#2886)

## Proposed Changes
Fall back to no reconstruction for variables that are not stored.

## PR Checklist
- [X] I am submitting my contribution to the develop branch.
- [X] My contribution generates no new compiler warnings (try with
--warnlevel=3 when using meson).
- [X] My contribution is commented and consistent with SU2 style
(https://su2code.github.io/docs_v7/Style-Guide/).
- [X] I used the pre-commit hook to prevent dirty commits and used
`pre-commit run --all` to format old commits.
- [X] I have added a test case that demonstrates my contribution, if
necessary.
- [X] I have updated appropriate documentation (Tutorials, Docs Page,
config_template.cpp), if necessary.
…ock in Newton-Krylov (su2code#2887)

## Proposed Changes
All in the title.

## PR Checklist
- [X] I am submitting my contribution to the develop branch.
- [X] My contribution generates no new compiler warnings (try with
--warnlevel=3 when using meson).
- [X] My contribution is commented and consistent with SU2 style
(https://su2code.github.io/docs_v7/Style-Guide/).
- [X] I used the pre-commit hook to prevent dirty commits and used
`pre-commit run --all` to format old commits.
- [X] I have added a test case that demonstrates my contribution, if
necessary.
- [X] I have updated appropriate documentation (Tutorials, Docs Page,
config_template.cpp), if necessary.

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…u2code#2880)

## Proposed Changes

`CSU2TCLib` validates the user-supplied freestream composition with `if
(mf != 1.0)`, an exact floating-point equality on an accumulated sum, at
four gas-model sites (ARGON, N2, AIR-5, AIR-7). A physically exact
composition need not sum to bit-exact unity in double precision. For
example

```
GAS_COMPOSITION= ( 0.999, 0.00025, 0.00025, 0.00025, 0.00025 )
```

sums to `1 - 1.1e-16` and is rejected with *"Intial gas mass fractions
do not sum to 1!"*, which reads to the user as a typo in a composition
that visibly sums to one. Any case seeding trace species can hit this.

This PR compares against a `1e-10` roundoff tolerance and reports the
offending sum in the message. The tolerance sits about six orders above
the roundoff in the example (`1.1e-16`) and four orders below the
`1.e-6` already used for the same "is this roundoff?" question in
`Common/src/fem/fem_geometry_structure.cpp`, so genuine input errors are
still rejected. The constant is `constexpr passivedouble` per the
convention in `option_structure.hpp`, and the message uses
`SU2_TYPE::GetValue` so the change is safe under AD builds. The sum is
printed with 16 significant digits through a small static helper shared
by the four sites, because `to_string` would print `1.000000` for any
sum that fails the tolerance by less than `5e-7`; a composition off by
`3e-7` now reports `sum = 0.9999997` and a real typo reports `sum =
0.99`. The pre-existing "Intial" typo is corrected in the same lines.

Verification with actual solver launches on the public viscous-cone mesh
(`ITER= 0`, exit code as the verdict): the roundoff-unity AIR-5 and
AIR-7 compositions are now accepted; exact compositions for ARGON, N2,
AIR-5 and AIR-7 remain accepted; real typos such as `( 0.75, 0.24, 0, 0,
0 )` remain rejected for every gas model. The regression baselines are
unchanged, serial and MPI, so no regression vectors are modified.

The contributor entry is added to `AUTHORS.md`. A dedicated constructor
unit test was removed in response to maintainer review; the
acceptance/rejection matrix above exercises the behavior through actual
solver launches.

## Related Work

The confusing failure mode may account for some reports of NEMO setup
difficulty; it surfaces whenever trace species are seeded rather than
left at exact zero. No open issue or PR is known to cover it.

## 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 a guide for you to know what the reviewers will be looking for in
your contribution.*

- [x] I am submitting my contribution to the develop branch.
- [x] My contribution generates no new compiler warnings (try with
--warnlevel=3 when using meson).
- [x] My contribution is commented and consistent with SU2 style
(https://su2code.github.io/docs_v7/Style-Guide/).
- [x] I used the pre-commit hook to prevent dirty commits and used
`pre-commit run --all` to format old commits.
- [x] A dedicated unit test is not included at maintainer request;
actual solver launches cover accepted roundoff and rejected invalid
compositions.
- [x] Documentation is not applicable; this corrects input validation
and its diagnostic without changing a configuration keyword or public
API.

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: BlueChips RunPod Integration <runpod-integration@bluechips.local>
…ion of the code (su2code#2889)

## Proposed Changes
The 4th int of the header was written as 0 and never read, it now holds
the size in bytes of the floating point data, so single and double
precision builds can read each other's restart files. Files written
before this field existed have a 0 there and were always double.

The MPI-IO read paths were hardcoded to MPI_DOUBLE, which is redefined
to MPI_FLOAT in single precision builds and was therefore consistent
within a build but not across builds. They now describe the payload as
blocks of bytes of the size given by the header, and convert to the
precision of the build when the two differ.

## PR Checklist
- [X] I am submitting my contribution to the develop branch.
- [X] My contribution generates no new compiler warnings (try with
--warnlevel=3 when using meson).
- [X] My contribution is commented and consistent with SU2 style
(https://su2code.github.io/docs_v7/Style-Guide/).
- [X] I used the pre-commit hook to prevent dirty commits and used
`pre-commit run --all` to format old commits.
- [X] I have added a test case that demonstrates my contribution, if
necessary.
- [X] I have updated appropriate documentation (Tutorials, Docs Page,
config_template.cpp), if necessary.

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
## Proposed Changes
Bit of a hack to make it compile, but I think it's ok since no adjoint
code is done on GPU, just the linear systems in plain types.
Also fixes an issue with adjoint Krylov and OpenMP.

## PR Checklist
- [X] I am submitting my contribution to the develop branch.
- [X] My contribution generates no new compiler warnings (try with
--warnlevel=3 when using meson).
- [X] My contribution is commented and consistent with SU2 style
(https://su2code.github.io/docs_v7/Style-Guide/).
- [X] I used the pre-commit hook to prevent dirty commits and used
`pre-commit run --all` to format old commits.
- [X] I have added a test case that demonstrates my contribution, if
necessary.
- [X] I have updated appropriate documentation (Tutorials, Docs Page,
config_template.cpp), if necessary.
…rupted runs (su2code#2881)

## Proposed Changes

Interrupt-driven termination is not limited to a user's Ctrl-C. SIGTERM
is the normal way batch schedulers terminate a job at its wall-clock
limit: SLURM sends SIGTERM when a job reaches its `TimeLimit` and on
`scancel` (waiting `KillWait` before SIGKILL), and PBS/Torque and LSF
behave equivalently. Today every SU2 job that exhausts its allocation on
a cluster ends with a log stating "All convergence criteria satisfied."
directly above a Solver Exit table whose every criterion reads "No". Any
parameter sweep or optimization loop that classifies runs by scraping
that banner silently admits timed-out, unconverged runs as converged,
and a human skimming the log is misled the same way. That population is
far larger than the manual-interrupt case.

Mechanism: on SIGTERM the signal handler sets `STOP`, and
`COutput::ConvergenceMonitoring` forces `convergence = true` so the run
stops and saves. Both `CSinglezoneDriver::Monitor` and
`CMultizoneDriver::Monitor` then read that flag as `InnerConvergence`
and print the satisfied banner.

Fix: a new `convergenceInterrupted` member of `COutput` records that the
stop came from the interrupt path rather than from the criteria. It is
propagated across ranks in the existing convergence Allreduce as a
count-2 element-wise `MPI_MAX` (SU2's serial MPI stub does not define
`MPI_BOR`, and the AD layer maps only SUM/MIN/MAX/PROD), exposed through
`GetConvergenceInterrupted()`, and both drivers print

```
Interrupt signal received, exiting before the convergence criteria were satisfied.
```

on that path. Stop-and-save behavior, exit code 0, and the
genuine-convergence and maximum-iteration messages are unchanged.

Files: `SU2_CFD/include/output/COutput.hpp`,
`SU2_CFD/src/output/COutput.cpp`,
`SU2_CFD/src/drivers/CSinglezoneDriver.cpp`,
`SU2_CFD/src/drivers/CMultizoneDriver.cpp` (+29/-8).

Verification (QuickStart `inv_NACA0012.cfg`, serial release build of
develop `07aa46b1`):
- SIGTERM sent to the exact `SU2_CFD` process about 8 s into the run:
the new interrupt message is printed, the satisfied banner does not
appear, restart and solution files are written, exit code 0.
- Run to genuine convergence and run to maximum iterations: output
byte-identical to the unpatched build (checked when the change was first
made on develop `81ce6a68f9`).
- Touched files re-checked with `-Wall -Wextra`: no new warnings.

## Related Work

The defect is present at v8.5.0 and at the current develop tip. I found
no existing upstream issue or PR that addresses it. The change is
independent of the other NEMO-related PRs from the same qualification
campaign; it touches only the output and driver exit path.

## 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.*

- [x] I am submitting my contribution to the develop branch.
- [x] My contribution generates no new compiler warnings (try with
--warnlevel=3 when using meson).
- [x] My contribution is commented and consistent with SU2 style
(https://su2code.github.io/docs_v7/Style-Guide/).
- [x] 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.
- [ ] I have updated appropriate documentation (Tutorials, Docs Page,
config_template.cpp), if necessary.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
## Proposed Changes
`CSU2FileWriter` takes its header from
`dataSorter->GetRequiredFieldNames()` but reads data columns by
positional index, so the sorter it's handed must be the one whose layout
those names describe. The `RESTART_BINARY` branch directly below already
does this correctly with `volumeDataSorterCompact` but for ASCII
restarts where the user has specified `VOLUME_OUTPUT` fields outside the
compact set, the columns become shifted and the outputted values are
wrong.

## Related Work
N/A

## 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.*

- [X] I am submitting my contribution to the develop branch.
- [X] My contribution generates no new compiler warnings (try with
--warnlevel=3 when using meson).
- [X] My contribution is commented and consistent with SU2 style
(https://su2code.github.io/docs_v7/Style-Guide/).
- [X] I used the pre-commit hook to prevent dirty commits and used
`pre-commit run --all` to format old commits.
- [X] I have added a test case that demonstrates my contribution, if
necessary.
- [X] I have updated appropriate documentation (Tutorials, Docs Page,
config_template.cpp), if necessary.

---------

Co-authored-by: Josh Kelly <J.Kelly7@liv.ac.uk>
Simulations of flames in large domains is difficult because volumetric
source terms blow up in large cells, causing the species solver to
diverge. I implemented a damping term which scales the species source
terms and diffusion in cells with a length scale above a user-defined threshold. This
allows for the flame to propagate through regions of the domain with
large cells (such as in the far-field) without the solver diverging, while the
source terms in refined regions remain unaffected.


Co-authored-by: Nijso <bigfootedrockmidget@hotmail.com>
## Related Work
su2code#2893

## PR Checklist
- [X] I am submitting my contribution to the develop branch.
- [X] My contribution generates no new compiler warnings (try with
--warnlevel=3 when using meson).
- [X] My contribution is commented and consistent with SU2 style
(https://su2code.github.io/docs_v7/Style-Guide/).
- [X] I used the pre-commit hook to prevent dirty commits and used
`pre-commit run --all` to format old commits.
- [X] I have added a test case that demonstrates my contribution, if
necessary.
- [X] I have updated appropriate documentation (Tutorials, Docs Page,
config_template.cpp), if necessary.
## Proposed Changes
*Give a brief overview of your contribution here in a few sentences.*

continuation of su2code#2891 
SURFACE_CSV still has a compact restart issue. Since surface_csv is not
a restart file, we do not apply compactness to it.

## Related Work
*Resolve any issues (bug fix or feature request), note any related PRs,
or mention interactions with the work of others, if any.*



## 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.*

- [x] 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.
- [ ] I have updated appropriate documentation (Tutorials, Docs Page,
config_template.cpp), if necessary.
## Proposed Changes
In some cases we use relative frame for rotor zone to perform URANS,
e.g., in aeroelasticity analysis, we want the grid movement comes only
from blade deformation. The problem is that the sliding plane in SU2
works only for absolute frame. In relative frame, the grid is not
rotating, so the interpolation at sliding interface is not changed as
time step is marching. The proposed changes rotate the sliding interface
in accordance with physical time steps to perform interpolation. This
function is activated only for relative frame. The grid itself is not
rotating.
Currently, this is only implemented in CNearestneighbor class, other
interpolation method should be changed as well in the near future.



## Related Work
*Resolve any issues (bug fix or feature request), note any related PRs,
or mention interactions with the work of others, if any.*



## 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.*

- [x] I am submitting my contribution to the develop branch.
- [x] My contribution generates no new compiler warnings (try with
--warnlevel=3 when using meson).
- [x] My contribution is commented and consistent with SU2 style
(https://su2code.github.io/docs_v7/Style-Guide/).
- [x] I used the pre-commit hook to prevent dirty commits and used
`pre-commit run --all` to format old commits.
- [x] I have added a test case that demonstrates my contribution, if
necessary.
- [x] I have updated appropriate documentation (Tutorials, Docs Page,
config_template.cpp), if necessary.

---------

Co-authored-by: chuanxiang yan <yancx20@mails.tsinghua.edu.cn>
Co-authored-by: Nijso <bigfootedrockmidget@hotmail.com>
Co-authored-by: Pedro Gomes <pcarruscag@gmail.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
### Proposed Changes
 
Fixes two defects in `SU2_PY/SU2/io/data.py`. Both make documented
public API paths of `SU2.io` raise immediately on any input.
 
**1. `save_data(..., append=True)` raises `NameError`.**
 
The append branch calls `load(...)`, which does not exist in the module.
The function is `load_data()`. The stale end-of-function markers (`#:
def load()`, `#: def save()`) suggest the functions were renamed at some
point and this internal call site was missed. `git log -S` dates it to
the v2.0.2 import.
 
Simply calling `load_data()` here is **not** a correct fix. `filelock`
is not reentrant (it is an `O_CREAT | O_EXCL` lock on a `.lock` sidecar
file), and `save_data` makes this call from inside its own `with
filelock(file_name)` block. The naive change therefore swaps an instant
`NameError` for a 10 second stall followed by `FileLockException`, which
is strictly worse.
 
Instead the lock-free read body is extracted into a private
`_read_data()` helper. `load_data()` wraps it in the filelock, and
`save_data()` calls it directly under the lock it already holds. This
keeps the read-modify-write atomic, rather than hoisting the read
outside the lock and introducing a TOCTOU race.
 
**2. `load_data(var_names=...)` raises `RuntimeError`.**
 
The filter deletes keys while iterating `input_data.keys()`. This was
safe on Python 2, where `.keys()` returned a list copy, and has been
broken since the Python 3 migration. It only triggers when at least one
key is actually dropped, which is why a single-key call appears to work.
Fixed by iterating a copy of the keys.
 
Both fixes sit in the same pair of functions and share one root cause,
namely leftovers from a rename and from the Python 2 to 3 migration, so
I have kept them in a single PR. Happy to split them if reviewers
prefer.
 
Also removes a `scipy` import probe in `load_data()` that becomes dead
once the read moves into the helper, and corrects the two stale marker
comments.
 
## Related Work
 
No open issue.
 
No in-tree caller currently passes `append=True` or `var_names`, so
there is no regression risk to the optimization drivers. These are
broken paths in a documented public API rather than a live crash in the
design loop.

Out of scope, flagged for a possible follow-up: the matlab (`.mat`) path
of `save_data` fails with `TypeError: 'method' object does not support
item assignment` inside `mat_bunch`. I verified this behaves identically
before and after this change, and did not touch it, in order to keep
this PR to one thing.

Could a maintainer please add the `changelog:fix` label.]
 
## Verification
 
`SU2_PY` has no Python test harness (`UnitTests/` is C++/Catch2 only),
so no test file is added. The following reproduces both failures on
`develop` and passes on this branch:
 
```python
import sys, os, tempfile
sys.path.insert(0, "SU2_PY")
os.environ.setdefault("SU2_RUN", tempfile.mkdtemp())
from SU2.io.data import save_data, load_data
 
os.chdir(tempfile.mkdtemp())
save_data("z.pkl", {"DRAG": 0.1, "LIFT": 0.9, "MOMENT_Z": 0.02})
save_data("z.pkl", {"CD": 1.0}, append=True)   # was NameError
print(sorted(load_data("z.pkl")))               # ['CD', 'DRAG', 'LIFT', 'MOMENT_Z']
print(load_data("z.pkl", var_names="DRAG"))     # was RuntimeError
```
 
Before and after on `develop`:
 
| Call | Before | After |
| --- | --- | --- |
| `save_data(append=True)` | `NameError` | OK |
| `load_data(var_names="DRAG")` | `RuntimeError` | OK |
| `load_data(var_names=["DRAG", "LIFT"])` | `RuntimeError` | OK |
| `load_data()` plain (regression check) | OK | OK |
 
`pre-commit run --files SU2_PY/SU2/io/data.py` passes.
 
## PR Checklist
 
- [x] I am submitting my contribution to the develop branch.
- [ ] My contribution generates no new compiler warnings (try with
--warnlevel=3 when using meson). *(N/A, Python only)*
- [x] My contribution is commented and consistent with SU2 style
(https://su2code.github.io/docs_v7/Style-Guide/).
- [x] 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. *(No Python test harness exists in the repo; a runnable
reproduction is included above.)*
- [ ] I have updated appropriate documentation (Tutorials, Docs Page,
config_template.cpp), if necessary. *(N/A, internal bug fix with no
user-facing API change.)*

Co-authored-by: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com>
## Proposed Changes 

The current work (part of GSoC) provides a working version of a
pressure-based algorithm for the incompressible flow solver as an
alternative to the existing Density-based solver. Below, the reader may
find the algorithm which has been implemented, as well as the current
progress of the code and challenges. All the way at the bottom one can
find performance comparisons between the DB and PB solvers for some test
cases.

### Algorithm

A lot of versions of pressure-based algorithms exist, and many different
versions can be implemented. Here, we opt for versions of the original
SIMPLE/PISO algorithm, it is briefly defined here for clarity.

First, the momentum equations are solved, starting from the previous
time step's velocity $\vec{u}^{(0)}$, pressure $p^{(0)}$, and face
velocity $\vec{u}_f^{(0)}$. The resulting momentum is the predicted
momentum, here its discretized form is shown, as its coefficients are
used in the subsequent equations

$A_p(\rho\vec{u})_p^{(1)}+\sum_n A_n (\rho\vec{u})_n^{(1)}=-V\nabla
p^{(0)}+S_m$

The subsequent momentum is not necessarily incompressible, the pressure
correction equation can be derived by rewriting it as follows, using a
term often called H by A

$(\rho\vec{u})_p^{(1)}=-\frac{\sum_n A_n
(\rho\vec{u})_n^{(1)}}{A_p}-\frac{V}{A_p}\nabla
p+S_m=\frac{H((\rho\vec{u})^{(1)})}{A}-\frac{V}{A_p}\nabla p^{(0)}+S_m$


$(\rho\vec{u})_p^{(2)}=\frac{H((\rho\vec{u})^{(1)})}{A}-\frac{V}{A_p}\nabla
p^{(1)}+S_m$

Note how a simplification is used here where the HbyA term is neglected.
Subtracting these two equations yields the first pressure correction
equation for $p'$ as

$\nabla \cdot \left( \frac{V}{A_p}\nabla p'\right)=\nabla \cdot \left(
\rho \vec{u}^{(1)}\right)$

Make note that for the divergence here, we require the face mass fluxes,
which are computed using Rhie-Chow interpolation to avoid odd-even
decoupling. After the equation is solved, using the pressure correction
$p'$, the pressure and momentum are corrected according to

$p^{(1)} = p^{(0)} + p' ,\quad (\rho u)^{(2)} = (\rho u)^{(1)}+(\rho
u)'.\quad (\rho u)'= -\frac{V}{A_p}\nabla p'$

So far, this is equal to a pseudo-transient version of the SIMPLE
algorithm. This algorithm however suffers from a very tight stability
condition on the time-step size. Therefore, multiple pressure
corrections can be applied, which for two corrections is originally
called the PISO algorithm.

The second pressure correction does not neglect the HbyA term, which
then results in the equation

$\nabla \cdot \left( \frac{V}{A_p}\nabla p'\right)=\nabla \cdot \left(
\rho \vec{u}^{(2)}\right)+\nabla\cdot\left(\frac{H((\rho
u)')}{A}\right)$

And the new correction equations are defined as

$p^{(2)} = p^{(1)} + p' ,\quad (\rho u)^{(3)} = (\rho u)^{(2)}+(\rho
u)'.\quad (\rho u)'= \frac{H((\rho u)')}{A}-\frac{V}{A_p}\nabla p'$

Note that HbyA here uses the previous velocity correction and is thus
the same quantity as the one used in the second pressure correction
equation. Later pressure correction equations follow analogously.


### Progress:

- Pressure-based solver added as alternative to density-based solver for
the incompressible flow equations.
- The pressure-based solver has only been tested for constant density
cases for basic Navier-Stokes and Euler flow.
- The pressure-based solver is implemented based on a pseudo
time-stepping approach to remain consistent with the other solvers in
SU2. The pressure-based solver is currently set to the SIMPLE algorithm
by default, with options for SIMPLEC and PISO available.
- The Poisson solver is a major bottleneck in the computation speed. To
account for this, an option was added to use a different linear solver
and preconditioner for the poisson solver.
- For details on the implementation of the algorithm and the
responsibility distribution please see the file CPBFluidIteration.cpp.
- A tutorial showcasing the different options of the PB solver for the
lid driven cavity flow problem has been added, please see
su2code/Tutorials#86 and su2code/su2code.github.io#218
- Regression tests have been  added.

### Future work:
#### Performance:

- The Poisson solver can sometimes struggle a lot due to high Reynolds
numbers and fine meshes, and thus require a ridiculous number of
iterations to converge reasonably. Possible fixes include adding
multigrid support or a DIC preconditioner (far less efficient).
Multigrid support is tricky as SU2 currently only considers multigrid
for the main (flow) solver and not for auxiliary solvers.

- Convergence issues with RANS (SA and SST) on fine meshes with high
Reynolds numbers. Tests have shown that cases such as flow over a flat
plate converges fine. However, external aerodynamic cases such as the
naca0012 RANS test case do not converge well at all. The convergence
does slightly improve when we switch out the mesh for a more uniform
unstructured mesh without large aspect ratio cells in the wake of the
airfoil, although this only slightly helps. The flat plate turbulence
test case also uses large aspect ratio cells so this is not the sole
issue. The RANS solver also often requires many iterations of the
Poisson solver to converge reasonably, this is however not the reason
for the lack of convergence.

- Periodic boundary conditions have not been implemented/tested at all
as of yet.
- Any code related to adjoints has not been considered at all either.

#### Code:

- Parallelization with OMP gives wrong results due to a unknown issue in
the Poisson solver, MPI however does work as expected.
- The restart solution currently does not write the edge mass fluxes
which are used by the pressure-based solver. Therefore restarted
solutions have to estimate these mass fluxes based on the average of the
nodal solutions. This results in the restarted solutions starting from
slightly different residuals. A fix for this issue can be to let the
edge mass fluxes be stored in the restart file.
- Support for the energy equation is added trough both the enthalpy
equation as well as trough the weakly coupled heat solver. This support
has however not yet been verified trough actual test cases.
- The numerics class for the convective residuals write the continuity
parts of the flux and jacobians. This will allow for a coupled solver in
the future, but as it stands now this code is _not_ used.

### TODO list 

- Fix issues mentioned above (left for future work)

## Related Work

This work is based on earlier attempts by Nitish Anand (2024) and Akshay
Koodly (2021), see feature branches feature_PBFlow_V8 and
feature_Pressure_based respectively. Also see PR su2code#2210

## PR Checklist

- [x] I am submitting my contribution to the develop branch.
- [x] My contribution generates no new compiler warnings (try with
--warnlevel=3 when using meson).
- [x] My contribution is commented and consistent with SU2 style
(https://su2code.github.io/docs_v7/Style-Guide/).
- [x] I used the pre-commit hook to prevent dirty commits and used
`pre-commit run --all` to format old commits.
- [x] I have added a test case that demonstrates my contribution, if
necessary.
- [x] I have updated appropriate documentation (Tutorials, Docs Page,
config_template.cpp), if necessary.

## Result showcase 

### Inviscid Hydrofoil

<img
src="https://github.com/user-attachments/assets/2178b449-557d-4ec5-89b6-0d49ace6b612"
width="600">

*Convergence history of the inviscid flow around a hydrofoil at a 5
degree aoa.*

<img
src="https://github.com/user-attachments/assets/cd28de03-1c0a-4927-a5a3-567c454eb5c2"
width="600">

*The pressure coefficient along the surface of the hydrofoil at a 5
degree aoa and the corresponding lift coefficients, X-FOIL predicts
C_L=0.6.*

### Lid Driven Cavity 

<img
src="https://github.com/user-attachments/assets/0433d13c-b76d-4b9d-ac2c-615539877c9e"
width="600">

*Convergence history of the lid driven cavity problem, note that CFL=60
is the highest stable CFL for the PB solver, whereas the DB solver does
not have this CFL related stability issue.*

### Flatplate RANS

<img
src="https://github.com/user-attachments/assets/b8db128d-e919-46a5-9b9d-aa17c1b94219"
width="600">

*The skin friction coefficient for turbulent flow over a (rough) flat
plate with SA.*

---------

Co-authored-by: Pedro Gomes <pcarruscag@gmail.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Pedro Gomes <38071223+pcarruscag@users.noreply.github.com>
… edge assembly (su2code#2885)

## Proposed Changes

The gradient-corrected NEMO viscous routine returns the right Jacobian
block twice, and the solver assembles viscous Jacobians with additive
signs despite subtracting the flux at node i and adding it at node j.
This patch returns the distinct left and right blocks and uses
`UpdateBlocksSub` for the matching assembly signs. The residual
equations are unchanged.

For the controlled constant density and viscosity momentum test,
Newtonian stress gives raw flux derivatives of -8/15 at i and +8/15 at
j. The opposite signs inside numerics come from the velocity difference;
they do not include the residual assembly sign. The assembled blocks
must therefore be `(-Ji, -Jj, +Ji, +Jj)`. The general viscous Jacobian
remains a TSL approximation.

## Tests

- The numerics unit test checks the independently derived values and
central differences of the returned momentum flux at both endpoints,
using two perturbation sizes. The unused gradient reconstruction helper
is removed.
- The solver assembly test checks the signs and placement of the
supplied blocks, with explicit finite checks. It is an integration test,
not an independent proof of the full Jacobian.
- The existing `ion_gy` regression now runs through iteration 99 using
`test_iter`, the existing mesh and the same stored restart. Its residual
threshold is lowered to prevent the previous stop at iteration 10. The
duplicate case and config are removed, and the output restart has a
separate filename so the input seed stays unchanged.
- The installed unit-test container now gets the Mutation++ library path
required by the new NEMO coverage.

In the earlier captured two-rank comparison, the final log10 energy
residual was 8.78 with the original code and 4.54 with the patch. The
patched trajectory stayed bounded but still reported nonphysical states.
This is regression stability evidence, not convergence or physical
validation. The existing 0.01 tolerance is retained.

In the [focused Linux
run](https://github.com/babybluechips/SU2/actions/runs/34157605477),
both MPI and NoMPI builds passed the full normal unit suite: 36 cases
and 73,848 assertions each, including MLPCpp. `super_cat` passed, and
both fresh two-rank `ion_gy` repeats passed at iteration 99 with
identical printed rows and unchanged input seeds. Native checks also
confirmed that restoring either original bug makes its relevant test
fail.

The cone references were refreshed from identical results in the fork
and [upstream
runs](https://github.com/su2code/SU2/actions/runs/34156557441), keeping
the existing 1e-5 tolerance. The original regression comparisons failed
only those stored cone references. The focused solver runs used
`cd8a39d`; the final commit `50e3343` changes only the two reference
arrays and the unit container's library path. Compiled source, unit
tests and solver configs are identical.

[Final upstream
CI](https://github.com/su2code/SU2/actions/runs/34159628295) now passes
the installed normal unit suite (36 cases, 73,848 assertions), AD/DD
unit suites, and all 157 serial regression tests. The parallel cone,
`super_cat`, and `ion_gy` at iteration 99 also pass; the rest of the
parallel suite is still running. Both cone comparisons have zero printed
difference.

The overlapping `visc_cone`, `super_cat`, and `ion_gy` expectations must
be refreshed if another NEMO PR merges first.

## Related Work

Possibly relevant to su2code#2717. The inviscid case in su2code#2026 is outside this
patch's scope. Earlier matched Mach 7.95 cone runs showed the original
code aborting at iteration 1,406 and this patch completing 3,000 bounded
iterations; those runs did not establish convergence.

## PR Checklist

- [x] I am submitting my contribution to the develop branch.
- [x] My contribution is commented and consistent with SU2 style.
- [x] I have added a test case that demonstrates my contribution.
- [x] Documentation is not applicable; no user-facing configuration
option or API is added.
- [x] Pre-commit formatting checks pass.
- [x] The focused Linux checks and final upstream results listed above
are verified.

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…bian (su2code#2883)

Use `GeometryToolbox::Norm` instead of `SquaredNorm` when passing edge
distance to the NEMO approximate viscous Jacobian. This corrects one
production line; the physical flux calculation is unchanged. The
duplicated fixture remains removed.

## Historical convergence comparison

The published comparison tested head
`b8504d8de172a20a0493391d3f3a3fd572b0cfbd` against base
`07aa46b1868655ec01f534bf3ac84ba5fbb6b822`. Each Mach 2 and 2.5 pair
used the same 37,192 point sphere mesh, identical settings and cold
starts: AIR5, 6 Pa, T=Tve=220 K, first order AUSM, implicit Euler, an
adiabatic noncatalytic wall, CFL1, MG1 with the default coarse CFL ratio
of 0.25, and two MPI ranks per arm.

| Condition | Base | Historical PR head |
|---|---|---|
| Mach 2 | Guard stopped at 34 rows; maximum 4 nonphysical points |
Natural convergence in 1,341 rows; zero nonphysical points |
| Mach 2.5 | Guard stopped at 23 rows; maximum 9 nonphysical points |
Natural convergence in 1,243 rows; zero nonphysical points |

Counts include row zero. Both patched runs met all ten log10 RMS
residual limits ≤−6 and all three absolute CD/CL/HF Cauchy limits <1e−6
over a window of 100 samples, with clean natural exits before the 10,000
row cap. Each saved final field also passed a separate MG0 residual
audit on the same mesh. Independent verification passed.

The controls were stopped by the guard for nonphysical points. These two
operating conditions on one geometry demonstrate improved robustness
under the stated settings; they do not establish a speedup or improved
physical accuracy.

[Evidence ZIP with configs, mesh, logs, results, plots and reproduction
instructions](https://github.com/user-attachments/files/31923042/PR2883-supersonic-reproduction-v1.zip).
[Reply to
Pedro](su2code#2883 (comment)).

## Validation after merging develop

Source revision `a3a60bfa6d25f60066f2a892ea85841df986862d` includes
develop `56df7491c9027fd28c83d8b09617c2852ccd078e` and su2code#2885. The
historical convergence cases above were not rerun on this revision.

[Supplemental Linux
run](https://github.com/babybluechips/SU2/actions/runs/34925451837)
tested that exact source on x86_64 and arm64, with separate serial and
MPI builds. Full normal unit suites passed, including MLPCpp and
Mutation++. Each selected regression ran twice from clean directories.
The autotest configs change only `ITER` to `test_iter + 1`; all other
settings and protected restart inputs are preserved. All 52 solver runs
completed cleanly, repeated rows were identical, and the refreshed
references accept every captured row under the original tolerances.
Archive digests and all 680 sealed files for this PR were independently
verified.

Reference commit `c0519aa5f310f65f453da24bb7d955f65d6ee158` updates ten
arrays from that evidence. The subsequent E221 cleanup changes only
assignment spacing on 37 lines; the Python syntax trees and all
numerical arrays are unchanged.

**Head after the whitespace cleanup:**
`dc81add7a6ba9a42dc4b18d2e41bda94451db9ee`.

**Full upstream CI at that head:**
[pending](https://github.com/su2code/SU2/actions/runs/34929371327).

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…2884)

The total energy check was nested inside the species block, so it could
never execute. This makes the energy check independent and applies the
tighter species or total energy limit.

The loop logic is now the small `ComputeUnderRelaxationFactor` helper
requested in review. Its direct test with manufactured data covers small
and excessive energy updates, opposing species updates, which limit
wins, and cancellation for a tiny factor.

## Validation after the latest develop merge

Updated with develop `8118ee0f507b5fb0fd70b819e6f1351c1b3c6d28` after
su2code#2883 merged. The approved helper and its direct tests are unchanged.

The [combined source
validation](https://github.com/babybluechips/SU2/actions/runs/35018055822)
tested merge commit `7e89cb6be1a869a61f9425bd069dc1ac6aa3d699` on Linux
x86 and ARM with separate serial and MPI builds. All 52 solver runs
completed, and every repeated row agreed within its architecture and
build. All four full normal unit suites passed. Both MPI builds included
Mutation++ support.

That validation run failed its comparisons against the old cone
references. Commit `53b8b75affdf3b1c29ff56691fc2333dd32cbd88` updates
four cone arrays from the repeated results, with three residual entries
changed in each. All 52 recorded rows pass the updated references under
the original tolerances. Other references, configurations and restart
inputs are unchanged.

Full upstream CI on `53b8b75affdf3b1c29ff56691fc2333dd32cbd88` passed:
[all 31 regression workflow
jobs](https://github.com/su2code/SU2/actions/runs/35022817008),
including unit tests and both sanitizer suites. Code Style, CodeQL,
labels and CodeFactor also passed.

## PR Checklist

- [x] I am submitting my contribution to the develop branch.
- [x] My contribution generates no new compiler warnings (try with
--warnlevel=3 when using meson).
- [x] My contribution is commented and consistent with SU2 style
(https://su2code.github.io/docs_v7/Style-Guide/).
- [x] I used the pre-commit hook to prevent dirty commits and used
`pre-commit run --all` to format old commits.
- [x] I have added a test case that demonstrates my contribution, if
necessary.
- [ ] I have updated appropriate documentation (Tutorials, Docs Page,
config_template.cpp), if necessary.

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…u2code#2900)

addDoubleArrayOption expects a su2double*, but std::array::begin()
returns a checked iterator type under the MSVC STL instead of decaying
to a pointer as it effectively does with libstdc++. This broke native
Windows builds with MSVC. Use .data() instead, which returns a raw
pointer on every implementation.

## Proposed Changes
*Give a brief overview of your contribution here in a few sentences.*



## Related Work
*Resolve any issues (bug fix or feature request), note any related PRs,
or mention interactions with the work of others, if any.*



## 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.
- [ ] I have updated appropriate documentation (Tutorials, Docs Page,
config_template.cpp), if necessary.
[skip ci]
## Proposed Changes
In `SU2_PY/SU2_Nastran/pysu2_nastran.py`, the mesh summary prints
`"Number of points in the moving marker".format(len(...))`. The string
has no `{}` placeholder, so the count is computed but never shown. This
adds the missing placeholder.

Before: `Number of points in the moving marker`
After: `Number of points in the moving marker: 123`

Found with pyflakes (`'...'.format(...) has unused arguments at
position(s): 0`).

The exception for an unknown imposed motion type referenced `self.tipo`,
which does not exist, so it raised `AttributeError: 'ImposedMotionClass'
object has no attribute 'tipo'` instead of the intended message. It now
uses `self.typeOfMotion`.

## Related Work
None. This does not touch the SET1 parsing discussed in su2code#2313.

## PR Checklist
- [x] I am submitting my contribution to the develop branch.
- [x] My contribution generates no new compiler warnings (try with
--warnlevel=3 when using meson).
- [x] My contribution is commented and consistent with SU2 style
(https://su2code.github.io/docs_v7/Style-Guide/).
- [x] 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.
- [ ] I have updated appropriate documentation (Tutorials, Docs Page,
config_template.cpp), if necessary
[skip ci]
## Proposed Changes
Eleven objectives that `CConfig::GetObjFunc_Extension` gives an adjoint
file suffix were missing from `get_adjointSuffix` in
`SU2_PY/SU2/io/tools.py`, so the Python scripts stop with `Unrecognized
adjoint function name` when one of them is used:

`INVERSE_DESIGN_HEATFLUX`, `AVG_TEMPERATURE`,
`SURFACE_STATIC_TEMPERATURE`, `SURFACE_SPECIES_0`,
`SURFACE_SPECIES_VARIANCE`, `REFERENCE_GEOMETRY`, `REFERENCE_NODE`,
`VOLUME_FRACTION`, `TOPOL_DISCRETENESS`, `TOPOL_COMPLIANCE`,
`STRESS_PENALTY`

`INVERSE_DESIGN_HEATFLUX` was listed as `INVERSE_DESIGN_HEAT`, which is
not an objective name in SU2, so that entry is renamed. The suffixes are
the ones from `GetObjFunc_Extension`. After this change every objective
with a suffix in CConfig is found by `get_adjointSuffix`.

## Related Work
Complements su2code#2907, which corrects the suffixes of `TOTAL_PRESSURE_LOSS`
and `KINETIC_ENERGY_LOSS`. The two PRs do not conflict.

## PR Checklist
- [x] I am submitting my contribution to the develop branch.
- [x] My contribution generates no new compiler warnings (try with
--warnlevel=3 when using meson).
- [x] My contribution is commented and consistent with SU2 style
(https://su2code.github.io/docs_v7/Style-Guide/).
- [x] 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.
- [ ] I have updated appropriate documentation (Tutorials, Docs Page,
config_template.cpp), if necessary.
## Proposed Changes

Apply the design of the SIMD numerics to scalars (but without
vectorization because it does not pay off for scalars). Performance is
still about 2 times better.

Every transported-scalar equation (SA, SST, LM, species, flamelet, heat,
pressure correction) now gets its convective and diffusive flux from a
**single statically-sized inline kernel**, used by both the interior
edge loop and the boundary conditions. The old `CNumerics`-based scalar
convection/diffusion classes are deleted, along with their `CDriver`
registration.

Deleted: `scalar_convection.hpp`, `scalar_diffusion.hpp`,
`turb_convection.hpp`, `turb_diffusion.hpp`, `trans_convection.hpp`,
`trans_diffusion.hpp`, `species_convection.hpp`,
`species_diffusion.hpp`, `heat.hpp`.
Added: `numerics/util.hpp`, `numerics/scalar/scalar_edge_flux.hpp` and
one `*_edge_flux.hpp` per model.

### Key points of the architecture

**One class per model, a three-layer CRTP chain.** Convection and
diffusion are not separate objects; they accumulate into one residual
under one preaccumulation region.

| Layer | Written | Holds |
| --- | --- | --- |
| `CUpwScalarBase` | once | upwinding weights, MUSCL reconstruction,
preaccumulation region, both `ComputeFlux` overloads |
| `CUpwScalarFlux` / `CAvgGradScalarBase` | once | the generic
convective expression; the edge geometry, projected gradient,
contraction with the coefficient matrix and the TSL Jacobians |
| Model (`CScalarFlux_SA`, `_SST`, `_TransLM`, `_Species`, `_Flamelet`,
`_Heat`, `_Poisson`) | per model | `Conservative`, `DiagonalDiffusion`,
and one `coefficients()` function |

**Runtime flags, not separate instantiations.** `ScalarFluxOptions`
carries `convective`, `viscous`, `oneSided`, `muscl`, `implicit`,
`boundedScalar`, `dynamicGrid`, `correctGradient` and
`accurateJacobians`. They are loop-invariant and unswitched by the
compiler. They are built through named constructors (`Interior`,
`BoundaryConvective`, `BoundaryFull`, `BoundaryDiffusive`) so that no
flag is passed positionally. A boundary clears `viscous`; a solid zone
(heat) and the pressure correction clear `convective`.

**Boundaries use the same kernel through ghost containers.** `EdgeSide`
locates one endpoint (scalar nodes, flow nodes, coordinates, grid
velocity). The interior loop binds one `EdgeSide` as both endpoints; a
boundary binds the solver's containers for `i` and per-marker ghost
containers for `j` — a `CGhostFlowVariable` (same primitive layout as
the flow solver) plus an instance of the solver's own `VariableType`,
indexed by vertex. Each BC is a fill pass followed by the shared flux
pass; there is no boundary special case inside any kernel.

**Non-conservative diffusion is supported.** `EdgeResidual` carries two
independent row contributions (`flux_i`, `flux_j`) and four Jacobian
blocks (`ii`, `ij`, `ji`, `jj`). `CSysMatrix` gains a four-block
`SetBlocks` and a `SetOffDiagBlocks`, both quantization-aware, covered
by a new unit test
(`UnitTests/Common/linear_algebra/edge_residual_blocks_tests.cpp`).

**The value type is a template parameter.** `numerics_simd/util.hpp` was
promoted to `numerics/util.hpp` with its value and index types
parameterized (`CLaneTraits`, `CValueTraits`) and its alignment behind a
trait; the MUSCL helpers moved with it and are now shared by the flow
and scalar paths. The solvers bind `su2double` today; a SIMD binding
would be the same code with `simd::Array`.

**`nVar` is a template parameter, or `Dynamic`.** Species and flamelet
pass `Dynamic`: the static arrays are backed by `MaxScalarVar`, the
loops run to a count read from the solver, and reconstruction takes a
runtime width.

**Shared loops in `CScalarSolver`:** `EdgeFluxResidual` (both the
coloring and the reducer strategy, one loop for both terms — the solvers
no longer have a `Viscous_Residual`), `BoundaryFluxResidual`, and
`FluidInterfaceFluxResidual`.

### Adding a new scalar model

1. **Write the flux class** in `numerics/.../<model>_edge_flux.hpp`:

```cpp
template <class Double, class FlowIndices, int nDim, size_t nVar = 2>
class CScalarFlux_Foo final
    : public CUpwScalarBase<Double, CScalarFlux_Foo<Double, FlowIndices, nDim, nVar>, FlowIndices, nDim, nVar> {
 public:
  static constexpr bool Conservative = true;      // density-weighted transport
  static constexpr bool DiagonalDiffusion = true; // vector of coefficients instead of a matrix
  using Base = CUpwScalarBase<Double, CScalarFlux_Foo, FlowIndices, nDim, nVar>;
  using Base::Base;

  template <class VariableType>
  FORCEINLINE CPair<Vector<Double, nVar>> coefficients(const FlowIndices& idx, Int iPoint,
      const EdgeSide<VariableType>& side_i, Int jPoint, const EdgeSide<VariableType>& side_j,
      const CPair<Double>& rho) const { ... }
};
```

That is the whole model in the common case. The optional hooks, each
inherited empty or generic, are:
- `finalizeFlux` — only if the convective term is not `a0*w_i*phi_i +
a1*w_j*phi_j` (SA overrides it for the backscatter equations).
- `coefficientJacobians` — extra Jacobian terms when the coefficients
depend on the transported variables (SA, SST).
- `extraDiffusionTerms` — extra gradients of synthesised states
(flamelet preferential diffusion).
- `DiffusionReadsDensity = true` — if the coefficients need the density
and the model is not `Conservative`; otherwise the density is never
gathered.

2. **Dispatch from the solver.** `CScalarSolver::DispatchScheme<Model,
nVarList...>(config, f)` resolves the flow indices
(compressible/incompressible), `nDim` and the equation count, and calls
a generic lambda with a type tag. There is nothing else to touch: no
`CDriver` registration and no `CNumerics` allocation.

```cpp
const auto opt = ScalarFluxOptions::Interior(*config, /*bounded*/ false);
DispatchScheme<CScalarFlux_Foo, 2>(config, [&](auto tag) {
  EdgeFluxResidual<typename decltype(tag)::type>(geometry, solver_container, config, opt);
});
```

Use `Dynamic` in the `nVarList` for a runtime equation count, and list
several counts (SA uses `1, 4`) to instantiate variants.

3. **Boundaries.** Write the fill pass (ghost solution,
`SetGhostPrimitives`, `SetGhostGeometry`, `SetGhostDiffusionState` for
the diffusion sites, `ghostSkip` for vertices that contribute nothing),
then call `BoundaryFluxResidual` through the same `DispatchScheme`. Most
boundaries of the `CScalarSolver` family are already generic and need no
per-model code.

The instantiation cost is `regime x nDim x |nVarList|` per model.

### Behavior changes and fixes

- NEMO with a turbulence model is now rejected at configuration; the
dead NEMO scalar-numerics branches and instantiations are gone.
- SST and species had no `BC_Far_Field` of their own (crash on
`MARKER_FAR`); it is now inherited from `CScalarSolver`.
- `BC_Inlet_MixingPlane` / `BC_Inlet_Turbo` never applied the
bounded-scalar mass-flux correction.
- The heat solver gained far-field and fluid-interface boundaries, and
its diffusion coefficients are now visible to AD.
- SA-neg keeps its `fn`-corrected diffusion coefficient and frozen
Jacobians; exact turbulent Jacobians stay behind
`USE_ACCURATE_TURB_JACOBIANS`.
- The unused `CAvgGrad_Species` `visc_bound_term` allocation is deleted.

### Validation

Last-digit changes are expected: the two terms now accumulate in one
`EdgeResidual` and each Jacobian block is cast to `su2mixedfloat` once.
The reference values were refreshed from CI across the serial, parallel
and hybrid suites, the AD suites, `tutorials.py` and `vandv.py`; a
flamelet CFD case was added to `hybrid_regression.py`.


## PR Checklist
- [X] I am submitting my contribution to the develop branch.
- [X] My contribution generates no new compiler warnings (try with
--warnlevel=3 when using meson).
- [X] My contribution is commented and consistent with SU2 style
(https://su2code.github.io/docs_v7/Style-Guide/).
- [X] I used the pre-commit hook to prevent dirty commits and used
`pre-commit run --all` to format old commits.
- [X] I have added a test case that demonstrates my contribution, if
necessary.
- [X] I have updated appropriate documentation (Tutorials, Docs Page,
config_template.cpp), if necessary.

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Comment thread SU2_PY/SU2/io/data.py

#: if file_format

return input_data
@bigfooted bigfooted closed this Sep 24, 2026
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.