Skip to content

Conversation

@AngPass
Copy link

@AngPass AngPass commented Sep 16, 2025

Proposed Changes

Hybrid RANS-LES methods aim to leverage the strengths of both Reynolds-Averaged Navier-Stokes (RANS) approaches and Large Eddy Simulation (LES), applying RANS near solid walls for computational efficiency and LES in outer flow regions for improved fidelity. A persistent challenge in these techniques is the appearance of the so-called grey area—the transitional region between RANS and LES zones—which is characterized by the absence of resolved turbulence. This leads to degraded accuracy in capturing free shear layers. The Stochastic Backscatter Model (Kok, 2017) aims to mitigate this issue. The model introduces stochastic forcing terms into the momentum and total energy equations that emulate the backscatter of energy from unresolved (subgrid) scales to resolved scales. The random forcing is formulated as the curl of a stochastic vector potential, say $\underline\xi$. The latter must be constructed using a three-step procedure:

  1. A vector of three mutually-uncorrelated normally-distributed random variables is generated at each node.
  2. A Laplacian smoothing in space (Passariello, 2025) is performed on each component of this vector.
  3. The smoothed fields are used as source terms in three stochastic differential equations (also known as Langevin equations). The latter are solved for the components of the vector potential $\underline\xi$.
  4. The curl of $\underline\xi$ is added to the momentum and total energy equations as source term. This can be done modifying the definition of the modeled stress tensor.

References:

  • Kok, J. C. (2017). A stochastic backscatter model for grey-area mitigation in detached eddy simulations. Flow, Turbulence and Combustion, 99(1), 119-150.
  • Passariello, A. (2025). Stochastic Backscatter Model for Unstructured Solvers. arXiv preprint arXiv:2508.17958.

Roadmap

  1. Modifying the definition of the modeled stress tensor using a stochastic contribution. As preliminary step, the vector of uncorrelated variables (point 1 in the procedure above) is employed.
  2. Performing a smoothing on the uncorrelated variables. This requires the solution of a Laplace equation at the beginning of each step.
  3. Implementing three additional equations for the components of the stochastic vector potential. These equations require the use of symmetry-preserving central scheme for the discretization of the convective terms (Kok, 2017).

Related Work

None.

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.

AngPass and others added 2 commits September 15, 2025 18:26
The Reynolds stress tensor definition is modified to include a random contribution, which is expressed as the curl of a normally-distributed stochastic vector potential.
AngPass and others added 2 commits September 23, 2025 10:12
Add Langevin equations to Spalart-Allmaras solver (uncorrelated random source term) + add stochastic contribution to Reynolds stress tensor (using conservative variables from Langevin equations).
@pcarruscag pcarruscag changed the title Stochastic Backscatter Model for Grey Area Mitigation in Hybrid RANS-LES Simulations [WIP] Stochastic Backscatter Model for Grey Area Mitigation in Hybrid RANS-LES Simulations Sep 23, 2025
AngPass and others added 22 commits September 24, 2025 11:05
Fix sanitizer warnings and regression test failures

- Address issues reported by clang sanitizers.
- Adjust implementation to ensure regression tests pass.
- Enhance readability.
- Ensure consistent declaration types.
- Extend implementation to 2D configurations.
- Improve robustness.
Enforce boundary conditions in Langevin equations.
- Implement a skew-symmetric scheme for the discretization of the convective terms in the Langevin equations.
- Fix the seed for the generation of the random source terms at the beginning of the simulation.
- Add the flag for the simulation of the Decaying Isotropic Homogeneous Turbulence.
- Generate the initial velocity field matching the experimental data by Comte-Bellot & Corrsin.
- Include a preliminary test case.
- Implement flag to enforce LES in the whole domain.
- Add option to set the LES filter width to a user-specified value.
- Add velocity divergence to volume outputs.
- Remove internal generation of initial velocity field for simulating the Decaying Isotropic Homogeneous Turbulence (DIHT).
- Add consistent evaluation of the turbulent kinetic energy in the random source term appearing in the momentum equations.
- Add backscatter intensity coefficient in the configuration file.
- Add random initialization of the Langevin variables.
- Add Laplacian smoothing of source terms in Langevin equations.
Remark: pseudo-time integration is employed (residuals are printed on screen with fixed frequency, the maximum number of time iterations can be defined by the user).
- Fix redundancy in virtual member definition.
- Add stochastic source term to Spalart-Allmaras turbulence model equation (to ensure exchange of modeled and resolved kinetic energy).
- Replace dual-time integration with Successive Over-Relaxation for Laplacian smoothing.
- Initialize stochastic vector potential as equal to the stochastic source terms in Langevin equations.
- Add random source term to main balance equations in LES zones exclusively.
- Blend RANS and LES turbulence timescales using the LES sensor.
- Scale source terms in Langevin equations using Bessel functions to preserve the variance.
-  Compute Bessel integral at the beginning of the simulation for optimization.
- Add variance monitoring to screen output.
AngPass and others added 22 commits November 26, 2025 14:56
- Add LD2 discretization for the inviscid terms in the main balance equations.
Remarks:
-- Valid only for the incompressible flow solver, with constant density fluid model.
-- JST scheme must be selected in the config file. The novel option LD2_SCHEME must be set to YES.
- Retrieve classic second-order central scheme at periodic boundaries.
Add option to include stochastic contribution to turbulence model equation.
- Use logarithmic approximation for Bessel function.
- Compute relative variance of the stochastic field for verification.
- Add option to print time-averaged skin friction coefficient in volume output.
- Minor syntax fix in CConfig.cpp
- Minor syntax fix in CConfig.cpp
- Include time-averaged skin friction coefficient and instantaneous energy backscatter ratio into volume output fields.
- Fix boundary conditions for the implicit smoothing of the stochastic source term in Langevin equations.
- Fix computation of the source term in turbulence model equation.
- Fix computation of the subgrid kinetic energy (divide eddy viscosity by flow density).
- Diagonalize Jacobian in turbulence equations for numerical robustness.
- Allow LD2 option only for JST scheme.
- Add option to include diagnostics of the stochastic source term
  smoothing.
- Add relaxation factor for the random forcing term in the main balanca
  equations.
- Add modeled and stochastic Reynolds stresses to volume output files.
- Random generators removed from static variables.
- Random generators re-initialized at every time step using
  deterministic seeds.
@AngPass AngPass requested a review from pcarruscag January 8, 2026 17:06
AngPass and others added 2 commits January 8, 2026 18:07
- Account for zero DES length scale in ghost cells.
- Fix threshold (=0.9) for the activation of the backscatter model.
} else {
for (unsigned long iPoint = 0; iPoint < nPoint; iPoint++) {
Solution_Old(iPoint, 0) = Solution(iPoint, 0) = val_nu_tilde;
for (unsigned short iVar = 1; iVar < nVar; iVar++) {

Check failure

Code scanning / CodeQL

Comparison of narrow type with wide type in loop condition High

Comparison between
iVar
of type unsigned short and
nVar
of wider type unsigned long.

if (backscatter && innerIter==0) {
SetLangevinSourceTerms(config, geometry);
const unsigned short maxIter = config->GetSBS_maxIterSmooth();

Check warning

Code scanning / CodeQL

Lossy function result cast Warning

Return value of type double is implicitly converted to unsigned short.
const su2double LES_FilterWidth = config->GetLES_FilterWidth();
const su2double constDES = config->GetConst_DES();
const su2double cDelta = config->GetSBS_Cdelta();
const unsigned short maxIter = config->GetSBS_maxIterSmooth();

Check warning

Code scanning / CodeQL

Lossy function result cast Warning

Return value of type double is implicitly converted to unsigned short.
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.

Some comments to start, I'll look more into the TurbSolver changes later

Comment on lines +2994 to +2995
/* DESCRIPTION: Specify if the LD2 scheme must be employed (incompressible flows, combined with JST discretization). */
addBoolOption("LD2_OPTION", LD2_Scheme, false);
Copy link
Member

Choose a reason for hiding this comment

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

Make this part of CONV_NUM_METHOD enum, ok to still re-use JST in the code, but the option to use the scheme should be consistent with all other schemes.

Comment on lines +659 to +664
* \param[in] velGrad - Velocity gradient matrix.
* \param[out] stochReynStress - Stochastic tensor (to be added to the Reynolds stress tensor).
*/
template<class Mat, class Scalar, class Vector>
NEVERINLINE static void ComputeStochReynStress(size_t nDim, Scalar density, Scalar tke,
Vector rndVec, Mat& stochReynStress, Scalar Cmag) {
Copy link
Member

Choose a reason for hiding this comment

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

Cmag before the output and add to the function doc

Comment on lines +883 to +886
inline void SetStochVar(su2double val_stochvar_i, su2double val_stochvar_j, unsigned short iDim) {
stochVar_i[iDim] = val_stochvar_i;
stochVar_j[iDim] = val_stochvar_j;
}
Copy link
Member

Choose a reason for hiding this comment

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

iDim first in these functions to be more consistent with others

const FlowIndices idx; /*!< \brief Object to manage the access to the flow primitives. */
su2double a0 = 0.0; /*!< \brief The maximum of the face-normal velocity and 0. */
su2double a1 = 0.0; /*!< \brief The minimum of the face-normal velocity and 0. */
su2double qij = 0.0; /*!< \brief The face-normal velocity (Langevin equations). */
Copy link
Member

Choose a reason for hiding this comment

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

it's not a qdot it's an mdot

Comment on lines +144 to +149
if (config->GetStochastic_Backscatter()) {
qij = 0.0;
for (unsigned short iDim = 0; iDim < nDim; iDim++) {
qij += 0.5 * (V_i[idx.Density()]*V_i[iDim + idx.Velocity()] + V_j[idx.Density()]*V_j[iDim + idx.Velocity()]) * Normal[iDim];
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Can't you use a0 and a1 for this?

Comment on lines +401 to +442
/*!
* \brief A virtual member.
* \param[in] iPoint - Point index.
*/
inline virtual su2double GetLES_Mode(unsigned long iPoint) const { return 0.0; }

/*!
* \brief A virtual member.
* \param[in] iPoint - Point index.
* \param[in] val_les_mode - Value of the LES sensor.
*/
inline virtual void SetLES_Mode(unsigned long iPoint, su2double val_les_mode) {}

/*!
* \brief A virtual member.
* \param[in] iPoint - Point index.
* \param[in] iDim - Dimension index.
*/
inline virtual su2double GetLangevinSourceTerms(unsigned long iPoint, unsigned short iDim) const { return 0.0; }

/*!
* \brief A virtual member.
* \param[in] iPoint - Point index.
* \param[in] iDim - Dimension index.
* \param[in] val_stochSource - Source term in Langevin equations.
*/
inline virtual void SetLangevinSourceTerms(unsigned long iPoint, unsigned short iDim, su2double val_stochSource) {}

/*!
* \brief A virtual member.
* \param[in] iPoint - Point index.
* \param[in] iDim - Dimension index.
*/
inline virtual su2double GetLangevinSourceTermsOld(unsigned long iPoint, unsigned short iDim) const { return 0.0; }

/*!
* \brief A virtual member.
* \param[in] iPoint - Point index.
* \param[in] iDim - Dimension index.
* \param[in] val_stochSource_old - Old value of source term in Langevin equations.
*/
inline virtual void SetLangevinSourceTermsOld(unsigned long iPoint, unsigned short iDim, su2double val_stochSource_old) {}
Copy link
Member

Choose a reason for hiding this comment

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

I don't think you need all of these to be virtual, try to remove what you can from CVariable because the turbulence solvers cast to CTurbVariable

Comment on lines +337 to +351
su2double velGrad_i[3][3] = {{0.0}};
su2double velGrad_j[3][3] = {{0.0}};
for (unsigned short jDim = 0; jDim < nDim; jDim++) {
for (iDim = 0; iDim < nDim; iDim++) {
velGrad_i[iDim][jDim] = PrimVar_Grad_i[iDim+1][jDim];
velGrad_j[iDim][jDim] = PrimVar_Grad_j[iDim+1][jDim];
}
}
for (iDim = 0; iDim < nDim; iDim++) {
Velocity_i[iDim] += alpha_LD2 * (velGrad_i[iDim][0] * d_ij[0] +
velGrad_i[iDim][1] * d_ij[1] +
velGrad_i[iDim][2] * d_ij[2]);
Velocity_j[iDim] -= alpha_LD2 * (velGrad_j[iDim][0] * d_ij[0] +
velGrad_j[iDim][1] * d_ij[1] +
velGrad_j[iDim][2] * d_ij[2]);
Copy link
Member

Choose a reason for hiding this comment

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

Is this enough to make it LD2? what about pressure and the dissipation terms?

Comment on lines +461 to +478
if (config->GetStochastic_Backscatter()) {
for (iVar = 0; iVar < nDim; iVar++)
Mean_StochVar[iVar] = 0.5*(stochVar_i[iVar] + stochVar_j[iVar]);
su2double SBS_Cmag = config->GetSBS_Cmag();
su2double SBS_RelaxFactor = config->GetStochSourceRelax();
su2double intensityCoeff = SBS_Cmag;
if (SBS_RelaxFactor > 0.0) {
su2double FS_Vel = config->GetModVel_FreeStream();
su2double ReynoldsLength = config->GetLength_Reynolds();
su2double timeScale = ReynoldsLength / FS_Vel;
unsigned long timeIter = config->GetTimeIter();
unsigned long restartIter = config->GetRestart_Iter();
su2double timeStep = config->GetTime_Step();
su2double currentTime = (timeIter - restartIter) * timeStep;
intensityCoeff = SBS_Cmag * (1.0 - exp(- currentTime / (timeScale*SBS_RelaxFactor)));
}
ComputeStochReynStress(nDim, Mean_PrimVar[nDim+2], Mean_turb_ke,
Mean_StochVar, stochReynStress, intensityCoeff);
Copy link
Member

Choose a reason for hiding this comment

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

Make a helper function that you can share across numerics

Comment on lines +237 to +238
AddVolumeOutput("VELOCITY_DIVERGENCE", "Velocity_Divergence", "DERIVED", "Divergence of the velocity field");

Copy link
Member

Choose a reason for hiding this comment

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

after primitives please

Comment on lines +330 to +335
const auto VelocityGradient = Node_Flow->GetVelocityGradient(iPoint);
su2double divVel = 0.0;
for (unsigned short iDim = 0; iDim < nDim; iDim++) {
divVel += VelocityGradient[iDim][iDim];
}
SetVolumeOutputValue("VELOCITY_DIVERGENCE", iPoint, divVel);
Copy link
Member

Choose a reason for hiding this comment

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

Move to common flow outputs

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