Skip to content

Conversation

@Bot-Enigma-0
Copy link
Contributor

Proposed Changes

Give a brief overview of your contribution here in a few sentences.
The current discretisation of the diffusion terms in the SA model are not consistent with the recommendation of S-A from their original paper [1]. The current implementation treats the non-linear term ($c_{b2}(\tilde{\nu})^2$) as a cross-production source term. By applying the product rule and simplifying, the diffusion term can be written as:
image

Then discretising gives:
image

Note that S-A assume that $\nabla\nu=0$, which does not hold in compressible cases.

The current changes are only for the Base model, I am extending it to the others. Would appreciate any feedback!

[1]: Spalart, Philippe, and Steven Allmaras. "A one-equation turbulence model for aerodynamic flows." 30th aerospace sciences meeting and exhibit. 1992.

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.

@YairMO
Copy link

YairMO commented May 20, 2025

Hi,

Thank you for addressing this issue. The source diffusion (please note that this term is known as source diffusion rather than cross production source term) term in the SU2 code is currently treated explicitly (which may limit the CFL numbers of the SA model). One simple way is to adopt the original proposal by Spalart & Allmaras, as you pointed out. One should verify that the total sum of the coefficients is positive, resulting in a diffusive flux. Fortunately, using simple arithmetic averaging will result in a diffusive flux (as opposed to an anti-diffusive flux). Please bear in mind that for the negative version, some care should be taken: please follow the recent paper by Boris Diskin et al:

Revised USM3D-ME Implementation of Negative Version of Spalart–Allmaras Turbulence Model

AIAA Journal in press.

@Bot-Enigma-0
Copy link
Contributor Author

Thank you for the paper. When extending this to the negative model I applied a very similar approach to the standard version. But after reading the positivity and boundedness discussion, I don't think the implementation I had would not be very stable. Rather the formulation the authors provide increases the stability, particularly due to the treatment of the $f_n$ term

@YairMO
Copy link

YairMO commented May 20, 2025

To make sure that the formulation is stable, it should be verified that the sum of the coefficients is always positive. Diskin addressed this issue in great detail.

SolverSpecificNumerics(iPoint, jPoint);

/*--- Compute residual, and Jacobians ---*/
numerics->ComputeResidual(Residual_i, Residual_j, Jacobian_ii, Jacobian_ij, Jacobian_ji, Jacobian_jj, const_cast<CConfig*>(config));
Copy link
Member

Choose a reason for hiding this comment

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

Residual_* and Jacobian_* are solver members that are not allocated for most solvers anymore.
The easiest way to test this is to compute the residual twice with the previous version you had, but swaping the order of ij (and flipping the normal direction) when setting things in the numerics class.

Suggested change
numerics->ComputeResidual(Residual_i, Residual_j, Jacobian_ii, Jacobian_ij, Jacobian_ji, Jacobian_jj, const_cast<CConfig*>(config));
numerics->ComputeResidual(Residual_i, Residual_j, Jacobian_ii, Jacobian_ij, Jacobian_ji, Jacobian_jj, const_cast<CConfig*>(config));

auto jPoint = geometry->edges->GetNode(iEdge, 1);

/*--- Helper function to compute the flux ---*/
auto ComputeFlux = [&](unsigned long point_i, unsigned long point_j, const su2double* normal) {
Copy link
Member

Choose a reason for hiding this comment

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

Nice use of lambdas 👍

Comment on lines 335 to 336
auto residual_ij = ComputeFlux(iPoint, jPoint, normal);
auto residual_ji = ComputeFlux(jPoint, iPoint, flipped_normal);
Copy link
Member

Choose a reason for hiding this comment

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

The type returned by the numerics is just a view into their internal state. It's not a copy of the residuals and Jacobians.
So if you make these call back to back like this residual_ij and residual_ji will be pointing to the same values because they come from the same numerics class.
You need to compute one, use it to update the residual, then compute the other.

Comment on lines 339 to 344
EdgeFluxes.SubtractBlock(iEdge, residual_ij);
EdgeFluxes.AddBlock(iEdge, residual_ji);
if (implicit) {
Jacobian.UpdateBlocksSub(iEdge, residual_ij.jacobian_i, residual_ij.jacobian_j);
Jacobian.UpdateBlocks(iEdge, residual_ji.jacobian_i, residual_ji.jacobian_j);
}
Copy link
Member

Choose a reason for hiding this comment

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

Don't worry about this reducer stuff for now, it's not used unless you use OpenMP.

Comment on lines 347 to 351
LinSysRes.SubtractBlock(iPoint, residual_ij);
LinSysRes.AddBlock(jPoint, residual_ji);
if (implicit) {
Jacobian.UpdateBlocksSub(iEdge, iPoint, jPoint, residual_ij.jacobian_i, residual_ij.jacobian_j);
Jacobian.UpdateBlocks(iEdge, iPoint, jPoint, residual_ji.jacobian_i, residual_ji.jacobian_j);
Copy link
Member

Choose a reason for hiding this comment

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

Use only one of the residual results to update the Jacobians for now, it doesn't have to be super exact.

Comment on lines 189 to 191
if (implicit) {
Jacobian_i[0][0] = (0.5*Proj_Mean_GradScalarVar[0]-nu_ij*proj_vector_ij)/sigma;
Jacobian_j[0][0] = (0.5*Proj_Mean_GradScalarVar[0]+nu_ij*proj_vector_ij)/sigma;
Copy link
Member

Choose a reason for hiding this comment

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

This seems too approximate. nu_ij can be much smaller that the term multiplying the projected gradient.

Comment on lines 183 to 184
su2double term_1 = (nu_ij + (1 + cb2)*nu_tilde_ij*fn_i) * Proj_Mean_GradScalarVar[0]/sigma;
su2double term_2 = cb2*nu_tilde_i*fn_i* Proj_Mean_GradScalarVar[0]/sigma;
Copy link
Member

Choose a reason for hiding this comment

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

You can try limiting the overall diffusion coefficient (the value that multiplies Proj_Mean_GradScalarVar) to be positive.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I exported the coefficient values to a buffer file, many of them appear to be negative. using the exact Jacobians reduces the negativeness, but still causes the solver to diverge.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

its the projected gradient (Proj_Mean_GradScalarVar[0]) which is causing the negativity. The diffusion coefficient always is positive but the product is not, which is causing the solver to diverge.

@YairMO
Copy link

YairMO commented May 29, 2025

Sorry, but I didn't follow through with all your work. Did you also modify the Jacobian due to the new additional diffusion flux (actually diffusion and anti-diffusion fluxes)?

@Bot-Enigma-0
Copy link
Contributor Author

Sorry, but I didn't follow through with all your work. Did you also modify the Jacobian due to the new additional diffusion flux (actually diffusion and anti-diffusion fluxes)?

yes, i derived the exact Jacobians for the new discretisation

@YairMO
Copy link

YairMO commented May 29, 2025

Do you have a written draft?

@Bot-Enigma-0
Copy link
Contributor Author

Bot-Enigma-0 commented May 29, 2025

for the jacobians? i'll type them up and post them in a moment

@YairMO heres the derivation. I've written it in the way it appears in the code.

jacobain_derivation_of_neg_model.pdf

Copy link
Member

@pcarruscag pcarruscag left a comment

Choose a reason for hiding this comment

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

You also need to modify CScalarSolver<VariableType>::SumEdgeFluxes
The convective fluxes still use EdgeFluxes, so one option is to store the difference between ij and ji in the second EdgeFlux vector.
Something like:

  for (auto iEdge : geometry->nodes->GetEdges(iPoint)) {
      if (iPoint == geometry->edges->GetNode(iEdge, 0)) {
        // Flux i-j.
        LinSysRes.AddBlock(iPoint, EdgeFluxes.GetBlock(iEdge));
      } else {
        // Flux j-i.
        LinSysRes.SubtractBlock(iPoint, EdgeFluxes.GetBlock(iEdge));
        if (EdgeFluxesDiff.size() > 0) {
          LinSysRes.SubtractBlock(iPoint, EdgeFluxesDiff.GetBlock(iEdge));
        }
      }
    }

Comment on lines 214 to 215
CSysVector<su2double> EdgeFluxes_ij; /*!< \brief Flux across each edge ij (non-conservative). */
CSysVector<su2double> EdgeFluxes_ji; /*!< \brief Flux across each edge ji (non-conservative). */
Copy link
Member

Choose a reason for hiding this comment

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

You don't need this change on the flow solver, just the scalar and turbulence.

Suggested change
CSysVector<su2double> EdgeFluxes_ij; /*!< \brief Flux across each edge ij (non-conservative). */
CSysVector<su2double> EdgeFluxes_ji; /*!< \brief Flux across each edge ji (non-conservative). */

Comment on lines 297 to 310
if (config->GetKind_Solver() == MAIN_SOLVER::RANS) {
for (unsigned long iPoint = 0; iPoint < nPoint; ++iPoint) {

LinSysRes.SetBlock_Zero(iPoint);

for (auto iEdge : geometry->nodes->GetEdges(iPoint)) {
if (iPoint == geometry->edges->GetNode(iEdge,0))
LinSysRes.AddBlock(iPoint, EdgeFluxes_ij.GetBlock(iEdge));
else
LinSysRes.SubtractBlock(iPoint, EdgeFluxes_ji.GetBlock(iEdge));
}
}
}
else SumEdgeFluxes(geometry);
Copy link
Member

Choose a reason for hiding this comment

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

This can also be reverted

Comment on lines 362 to 366
if (ReducerStrategy) {
EdgeFluxes.Initialize(geometry.GetnEdge(), geometry.GetnEdge(), nVar, nullptr);
EdgeFluxes_ij.Initialize(geometry.GetnEdge(), geometry.GetnEdge(), nVar, nullptr); //initializing only when solver=rans, has no effect
EdgeFluxes_ji.Initialize(geometry.GetnEdge(), geometry.GetnEdge(), nVar, nullptr);
}
Copy link
Member

Choose a reason for hiding this comment

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

same

Comment on lines 79 to 81
CSysVector<su2double> EdgeFluxes; /*!< \brief Flux across each edge. */
CSysVector<su2double> EdgeFluxes_ij; /*!< \brief Flux across each edge ij (non-conservative). */
CSysVector<su2double> EdgeFluxes_ji; /*!< \brief Flux across each edge ji (non-conservative). */
Copy link
Member

Choose a reason for hiding this comment

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

I think it's better to keep using EdgeFluxes as before and just add another one where needed (instead of 2).

Suggested change
CSysVector<su2double> EdgeFluxes; /*!< \brief Flux across each edge. */
CSysVector<su2double> EdgeFluxes_ij; /*!< \brief Flux across each edge ij (non-conservative). */
CSysVector<su2double> EdgeFluxes_ji; /*!< \brief Flux across each edge ji (non-conservative). */
CSysVector<su2double> EdgeFluxes; /*!< \brief Flux across each edge. */
CSysVector<su2double> EdgeFluxes_ij; /*!< \brief Flux across each edge ij (non-conservative). */

Comment on lines 336 to 338
if (implicit) {
Jacobian.UpdateBlocksSub(iEdge, residual_ij.jacobian_i, residual_ij.jacobian_j);
}
Copy link
Member

Choose a reason for hiding this comment

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

For the reducer strategy we need to compromise and use an "average" jacobian, i.e. assuming a conservative flux.
With what you have now the Jacobian is getting doubled

}

/*--- Compute fluxes and jacobians j->i ---*/
su2double flipped_normal[3];
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
su2double flipped_normal[3];
su2double flipped_normal[MAXNDIM];

Copy link
Member

@pcarruscag pcarruscag left a comment

Choose a reason for hiding this comment

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

You can change the regression.yaml file to use your testcases branch with the updated restarts.

Comment on lines 32 to 33
#include "../scalar/scalar_diffusion.hpp"

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
#include "../scalar/scalar_diffusion.hpp"

@pcarruscag pcarruscag changed the title [WIP] Updating SA-Diffusion term Discretisation Updating SA-Diffusion term Discretisation Jun 27, 2025
@pcarruscag pcarruscag marked this pull request as ready for review June 27, 2025 20:02
@pcarruscag pcarruscag changed the base branch from develop to feature_sa_diffusion June 28, 2025 19:05
@pcarruscag pcarruscag merged commit 5b56b32 into su2code:feature_sa_diffusion Jun 28, 2025
217 of 233 checks passed
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.

4 participants