Skip to content

Fix the incompressible moving-grid Jacobian and several smaller mean-flow bugs - #2945

Open
rois1995 wants to merge 17 commits into
fix_roe_3d_wave_strengthsfrom
fix_meanflow_small
Open

rois1995 wants to merge 17 commits into
fix_roe_3d_wave_strengthsfrom
fix_meanflow_small

Conversation

@rois1995

@rois1995 rois1995 commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Proposed Changes

Each fix is in its own commit.

  • Incompressible solver on moving grids. The Jacobian term of the grid motion was added once per variable instead of once. The results of each step do not change, but the inner iterations of unsteady cases converge much worse. In a pitching airfoil, the lift reached values of several hundred because the time steps did not converge. The term is now added once, including the effect of a variable density.

    for (iVar = 0; iVar < nVar; iVar++) {
    Flux[iVar] -= ProjVelocity * 0.5*(U_i[iVar]+U_j[iVar]);
    /*--- Jacobian contributions ---*/
    /*--- Implicit terms ---*/
    if (implicit) {
    for (iDim = 0; iDim < nDim; iDim++){
    Jacobian_i[iDim+1][iDim+1] -= 0.5*ProjVelocity*DensityInc_i;
    Jacobian_j[iDim+1][iDim+1] -= 0.5*ProjVelocity*DensityInc_j;
    }
    Jacobian_i[nDim+1][nDim+1] -= 0.5*ProjVelocity*DensityInc_i;
    Jacobian_j[nDim+1][nDim+1] -= 0.5*ProjVelocity*DensityInc_j;
    }
    }

  • Source terms that cannot be combined. Some source terms share one internal object, so when two are active, one silently replaces the other. For example, body force + gravity gave twice the body force and no gravity. The first source slot is one object for all of them:

    numerics[iMGlevel][FLOW_SOL][source_first_term] = new CSourceIncBodyForce(nDim, nVar_Flow, config);
    else
    numerics[iMGlevel][FLOW_SOL][source_first_term] = new CSourceBodyForce(nDim, nVar_Flow, config);
    }
    else if (incompressible && (config->GetKind_Streamwise_Periodic() != ENUM_STREAMWISE_PERIODIC::NONE)) {
    numerics[iMGlevel][FLOW_SOL][source_first_term] = new CSourceIncStreamwise_Periodic(nDim, nVar_Flow, config);
    }
    else if (incompressible && (config->GetKind_DensityModel() == INC_DENSITYMODEL::BOUSSINESQ)) {
    numerics[iMGlevel][FLOW_SOL][source_first_term] = new CSourceBoussinesq(nDim, nVar_Flow, config);
    }
    else if (config->GetRotating_Frame() == YES) {
    if (incompressible)
    numerics[iMGlevel][FLOW_SOL][source_first_term] = new CSourceIncRotatingFrame_Flow(nDim, nVar_Flow, config);
    else
    numerics[iMGlevel][FLOW_SOL][source_first_term] = new CSourceRotatingFrame_Flow(nDim, nVar_Flow, config);

    These combinations now stop with a clear error.

  • Turbomachinery Riemann boundary. It read the settings of the Giles boundary instead of its own, so it crashed when no Giles marker was present.

    switch (config->GetKind_Data_Giles(Marker_Tag)){

    It now reads its own settings.

  • Reconstructed face states (MUSCL). Pressure and density are now checked to be positive before the fluid model uses them. Before, the fluid model was called first:

    if (!ideal_gas || low_mach_corr) {
    ComputeConsistentExtrapolation(GetFluidModel(), nDim, Primitive_i, Secondary_i);
    ComputeConsistentExtrapolation(GetFluidModel(), nDim, Primitive_j, Secondary_j);
    }
    /*--- Low-Mach number correction. ---*/
    if (low_mach_corr) {
    LowMachPrimitiveCorrection(GetFluidModel(), nDim, Primitive_i, Primitive_j);
    }
    /*--- Check for non-physical solutions after reconstruction. If found, use the
    cell-average value of the solution. This is a locally 1st order approximation,
    which is typically only active during the start-up of a calculation. ---*/
    bool neg_pres_or_rho_i = (Primitive_i[prim_idx.Pressure()] < 0.0) || (Primitive_i[prim_idx.Density()] < 0.0);
    bool neg_pres_or_rho_j = (Primitive_j[prim_idx.Pressure()] < 0.0) || (Primitive_j[prim_idx.Density()] < 0.0);

  • Incompressible minimum pressure. With MPI it took the largest of the local minima.

    SU2_MPI::Allreduce(&minP, &MinP, 1, MPI_DOUBLE, MPI_MAX, SU2_MPI::GetComm());

    It now takes the smallest.

  • Axisymmetric source. One Jacobian term was always zero, because 1/2 is an integer division in C++.

    jacobian[3][2] = Gamma*U_i[3]/U_i[0] - 1/2*(Gamma-1)*( (U_i[1]*U_i[1]+U_i[2]*U_i[2])/(U_i[0]*U_i[0]) + 2*U_i[2]*U_i[2]/(U_i[0]*U_i[0]) );

  • Green-Gauss gradient. A fixed-size array had no size check (mixtures with many species could overflow it).

    static constexpr size_t MAXNVAR = 20;

    It is now as large as the one in the limiters, with an error above it.

  • MGLEVEL above 10 could exceed the fixed multigrid arrays. Configuration preprocessing now rejects these settings with the supported range. Unit tests preserve levels 0 and 10 and verify the diagnostic for 11.

  • Compressible moving slip walls. The density/momentum/energy projection is now applied consistently to the state, residual, Jacobian and multigrid paths. A boundary/multigrid frame-covariance regression covers the correction. The associated convergence checks and separate outlet work are linked from Fix the HLLC scheme on moving grids and its Jacobians #2943.

The test cases are in the comments below.

This PR builds on #2944. It is part of the split of #2941.

Related Work

PR Checklist

  • 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.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LEL91DW5WPbPwgFtCvHga6

rois1995 and others added 8 commits September 30, 2026 13:46
…ensity terms

FDS added the grid-motion term inside a loop over the variables (nVar times). The term is now added once, as
the exact derivative with respect to (p, u, h), including d(rho)/dh; the same terms are added to the
incompressible JST and Lax-Friedrichs schemes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LEL91DW5WPbPwgFtCvHga6
SetRangePressure reduced the local minima with MPI_MAX, so with more than one rank MinP was the largest of
the local minima (used by the mass-flow outlet).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LEL91DW5WPbPwgFtCvHga6
The flow solvers build one numerics object per source slot, so two active sources of the same slot overwrote
each other (e.g. body force + gravity gave twice the body force). First slot: body force, rotating frame,
axisymmetric, gravity, Boussinesq, streamwise periodic. Second slot: radiation, periodic outlet, vorticity
confinement.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LEL91DW5WPbPwgFtCvHga6
It read the Giles kind of the marker (copied from BC_Giles), which reads past the end of the Giles list and
crashes when the case has no Giles marker.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LEL91DW5WPbPwgFtCvHga6
The boundary flux array had 20 entries and no bound check (NEMO with 10 or more species in 3D). Use 32, as the
limiters, and stop with an error above it. The error of both checks is raised by the master thread only, since
these functions run inside OpenMP parallel regions (MPI_THREAD_FUNNELED).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LEL91DW5WPbPwgFtCvHga6
The multigrid arrays hold at most 10 coarse levels (MAX_MG_LEVELS); MGLEVEL= 12 with MG_MIN_MESHSIZE= 1
crashed with stack smashing. Clamp with a warning, MG_MIN_MESHSIZE can still reduce the levels.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LEL91DW5WPbPwgFtCvHga6
…e Jacobian

The term 1/2*(Gamma-1) of d(v (rho E + p))/d(rho v) was 0 (implicit Jacobian only).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LEL91DW5WPbPwgFtCvHga6
The consistent extrapolation and the low-Mach correction called the fluid model with the reconstructed
pressure and density before checking that they are positive (possible NaN or fluid-model errors with real-gas
models at start-up). Check them first, also rejecting zero and NaN, and check again after the low-Mach
correction. Valid states give the same result as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LEL91DW5WPbPwgFtCvHga6
@rois1995

rois1995 commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor Author

Test cases

All runs: the build before this PR (PR 3) against this PR, 2 MPI ranks. Files and scripts: pr4.

Incompressible pitching airfoil. NACA0012, pitching by 5 deg, 60 inner iterations per time step. Before this PR the inner iterations stop converging (worst end-of-step pressure residual -2.05) and the lift reaches -178 and +388. With this PR the worst residual is -3.85 and the lift follows the motion.

incompressible pitching airfoil

Other fixes:

  • Source terms: inviscid NACA0012 with a body force of (0, -50, 0) and gravity. Before this PR it gives exactly the same history as a body force of (0, -100, 0) alone, so gravity is lost. With this PR the run stops with an error that explains which options cannot be combined.
  • Turbomachinery Riemann boundary: the 2D transonic stator with Riemann boundaries instead of Giles. Before this PR it crashes (segmentation fault); with it, it runs.
  • MGLEVEL: turbulent flat plate with MGLEVEL= 12. Before this PR it crashes (stack smashing). The current implementation rejects unsupported levels during configuration preprocessing with a clear error. Unit tests check that levels 0 and 10 are retained and that 11 is rejected.

@joshkellyjak joshkellyjak self-assigned this Oct 12, 2026
Comment thread Common/src/CConfig.cpp Outdated
/*--- The multigrid arrays hold at most 10 coarse levels (CMultiGridIntegration::MAX_MG_LEVELS). Clamp instead of
stopping, MG_MIN_MESHSIZE can still reduce the number of levels during the agglomeration. ---*/
if (nMGLevels > 10) {
if (rank == MASTER_NODE) cout << "WARNING: MGLEVEL > 10 is not supported, using MGLEVEL= 10." << endl;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If this is in preprocessing I think it is better to throw an error rather than clamp with a warning to the screen

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed. MGLEVEL > 10 now fails during configuration preprocessing with a diagnostic giving the supported range, instead of warning and clamping. Tests verify that levels 0 and 10 are retained and that 11 terminates with the diagnostic. All three configured test suites pass.

if (implicit) {
auto addGridJacobian = [&](su2double **Jac, su2double rho, const su2double *vel, su2double h, su2double T,
su2double cp) {
const su2double dRhodh = variable_density ? su2double(-rho / (T * cp)) : su2double(0.0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can you update the variable definition for variable_density so they agree in this file

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Both centered-flow constructors now use GetVariable_Density_Model() for variable_density.

if (implicit) {
auto addGridJacobian = [&](su2double **Jac, su2double rho, const su2double *vel, su2double h, su2double T,
su2double cp) {
const su2double dRhodh = variable_density ? su2double(-rho / (T * cp)) : su2double(0.0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

const auto

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Changed both dRhodh declarations to const auto.

@rois1995

rois1995 commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor Author

The additional fixes are now in place:

  • Compressible moving slip walls: the wall projection is applied consistently to the solution, residual, Jacobian and multigrid operations. The tests check that changing the reference frame gives equivalent boundary behaviour. The moving-grid convergence findings and their limitations are discussed in #2943.
  • HLLC supersonic Jacobian: this PR includes the correction from Fix the HLLC scheme on moving grids and its Jacobians #2943 to the derivative of the face-enthalpy term. The regression covers both the exact and approximate Jacobian modes. Explanation and tests.
  • AD density lifetime: this PR also includes the fix from Fix the internal energy used by the under-relaxation factor #2942 that keeps the density intermediate alive during differentiation. The two previously failing file-comparison tests now pass with their original reference files. Forward-mode tests and the focused reverse-mode test with AddressSanitizer pass. Details.

The branch includes the updated parent PRs and develop changes. The wall and HLLC unit checks pass, and CI is currently passing on this PR's latest commit.

The pressure-outlet reconstruction is handled separately in #2975; it is not required by the wall correction here.

bigfooted added a commit that referenced this pull request Oct 13, 2026
…and limiters (#2961)

## Proposed Changes

This is one of **seven related periodic-boundary bug-fix PRs**.

| PR | Scope |
|---|---|
| #2961 (this PR) | Rotational Jacobian, multigrid velocity rotation and
limiters |
| #2963 | Axis points, slip-wall normals and moving donor volumes |
| #2964 | Periodic volumes, neighbor counts, sensors, stencils and
coarse flags |
| #2965 | Streamwise periodicity, auxiliary gradients and flamelet
metadata |
| #2966 | Inverse affine transforms in the parser and FEM |
| #2968 | Diagnostics for unsupported periodic solver combinations |
| #2969 | Translated wall-distance images |

The other bug-fix PRs are drafts with their remaining validation and
implementation limits documented. Their grouping does not imply a linear
merge order; #2963 depends on this PR.

The fully coupled implicit operator proposed in #2967 is a separate
experimental enhancement. It has been closed and deferred because no
overall benefit has been established and convergence/validation issues
remain. This PR retains the existing implicit approximation.

Three fixes for rotational periodic boundaries, one commit each. The
test cases are in the comment below.

- **Implicit runs:** the Jacobian block that a periodic point gets from
its match was rotated on one side only. It is now rotated on both sides.
- **Multigrid:** the coarse levels did not rotate the velocity, so the
run did not converge. The velocity is now rotated on all levels.
- **Limiters:** the limiters of the velocity components were rotated
like a vector, but they are separate numbers. On the periodic boundary
one velocity component became first order, and limiters could be
negative. Each side now sends the velocity bounds and reconstruction
increments in the frame of the other side, so each limiter uses the
complete stencil. Nothing changes for a translation.

New tests `periodic2d_no_limiter` and `periodic2d_multigrid`;
`periodic2d` is now also in the MPI regression. Their x86 reference
values pass in CI.

Existing reference values that change: `aachen_turbine_restart`,
`jones_turbocharger_restart` and `multi_interface` (rotational periodic,
the Jacobian block), and the hybrid `periodic2d` (the Jacobian block and
the limiters). The x86 periodic tests and the other changed cases pass
in CI; the Jones restart residual references were refreshed from the
serial, MPI and hybrid CI logs. The aarch64 references still need the CI
runs.

No conflict with #2945 and #2959. #2956 also changes the reference
values of `jones_turbocharger_restart`; whichever is merged second takes
them from the CI.

## Related Work

- #652 added the present implementation of the periodic boundaries.
- #1563 fixed the rotation in 2D and added the `periodic2D` case used
here.
- #1467, #763 and #1585 discuss the incomplete linear system at periodic
points; this PR keeps the existing approximation.

## 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: rois1995 <rauxa1995@hotmail.it>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Josh Kelly <81244680+joshkellyjak@users.noreply.github.com>
Co-authored-by: Nijso <bigfootedrockmidget@hotmail.com>
@rois1995
rois1995 marked this pull request as ready for review October 13, 2026 15:51
joshkellyjak added a commit that referenced this pull request Oct 15, 2026
## Proposed Changes

The implicit compressible solver limits the update of each point with an
under-relaxation factor. To decide how much to limit, it compares the
internal energy before and after the update. This internal energy was
computed wrongly: the kinetic energy was not divided by the density.


https://github.com/su2code/SU2/blob/a6b7496b740ac0e63807ee1b066a4badd902da91/SU2_CFD/src/solvers/CEulerSolver.cpp#L2555-L2556

In cases run with dimensional values the wrong internal energy can
become negative. When it does, the point gets no update at all and stays
frozen. In an air nozzle about a quarter of the points were frozen, so
the case could not converge.

The fix is 5 lines: the kinetic energy is divided by the density, using
the old and the new density.

Almost all compressible implicit regression cases converge a little
differently, so most regression values change. They are updated from the
CI.

This is the first of four PRs that replace #2941, to make the review
easier. The other three build on this one:
- #2943: HLLC on moving grids;
- #2944: Roe-type schemes in 3D;
- #2945: smaller fixes.

## Related Work

- #2830 added the under-relaxation factor.
- #2941 (replaced by this PR and the three that follow).

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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01LEL91DW5WPbPwgFtCvHga6

---------

Co-authored-by: rois1995 <rauxa1995@hotmail.it>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: rois1995 <rauxa1995@gmail.com>
Co-authored-by: Josh Kelly <81244680+joshkellyjak@users.noreply.github.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants