PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
Macros | Functions | Variables
les.c File Reference

Implements the Large Eddy Simulation (LES) subgrid-scale closure. More...

#include "les.h"
#include "statistics_target.h"
Include dependency graph for les.c:

Go to the source code of this file.

Macros

#define __FUNCT__   "SymTensorSelfOuter"
 
#define __FUNCT__   "SymTensorCombine"
 
#define __FUNCT__   "SymTensorTrace"
 
#define __FUNCT__   "SymTensorDeviator"
 
#define __FUNCT__   "SymTensorContract"
 
#define __FUNCT__   "SymTensorTimesVector"
 
#define __FUNCT__   "SymTensorNormSq"
 
#define __FUNCT__   "StrainRateFromGradients"
 
#define __FUNCT__   "ComputeCellFilterWidth"
 
#define __FUNCT__   "LeonardStress"
 
#define __FUNCT__   "GermanoModelTensor"
 
#define __FUNCT__   "ResolveLESAveragingDirections"
 
#define __FUNCT__   "ClipModelCoefficient"
 
#define __FUNCT__   "EddyViscosityFromCoefficient"
 
#define __FUNCT__   "WALEEddyViscosity"
 
#define __FUNCT__   "VremanEddyViscosity"
 
#define __FUNCT__   "SubgridKineticEnergy"
 
#define __FUNCT__   "ComputeSmagorinskyConstant"
 
#define __FUNCT__   "ComputeEddyViscosityLES"
 
#define __FUNCT__   "LogLESDiagnostics"
 

Functions

static PetscErrorCode FinalizeSmagorinskyConstantField (UserCtx *user)
 Synchronizes the completed Smagorinsky coefficient field.
 
static PetscErrorCode LESResolveDomain (UserCtx *user, SpatialTargetPlan *plan)
 Resolves the iteration domain the closure computes over.
 
static PetscErrorCode BuildWallModelExclusionMask (UserCtx *user, Vec inclusion)
 Marks the near-wall cells a wall model overwrites, so the procedure skips them.
 
static void LESPeriodicAxes (const SimCtx *simCtx, PetscBool periodic[3])
 Reports the block's per-axis periodicity in (xi, eta, zeta) order.
 
SymTensor SymTensorSelfOuter (Cmpnts v)
 Implementation of SymTensorSelfOuter().
 
SymTensor SymTensorCombine (PetscReal a, SymTensor x, PetscReal b, SymTensor y)
 Implementation of SymTensorCombine().
 
PetscReal SymTensorTrace (SymTensor t)
 Implementation of SymTensorTrace().
 
SymTensor SymTensorDeviator (SymTensor t)
 Implementation of SymTensorDeviator().
 
PetscReal SymTensorContract (SymTensor a, SymTensor b)
 Implementation of SymTensorContract().
 
Cmpnts SymTensorTimesVector (SymTensor t, Cmpnts v)
 Implementation of SymTensorTimesVector().
 
PetscReal SymTensorNormSq (SymTensor t)
 Implementation of SymTensorNormSq().
 
PetscErrorCode StrainRateFromGradients (Cmpnts dudx, Cmpnts dvdx, Cmpnts dwdx, SymTensor *strain, PetscReal *magnitude)
 Implementation of StrainRateFromGradients().
 
PetscErrorCode ComputeCellFilterWidth (LESFilterWidthModel model, PetscReal aj, Cmpnts csi, Cmpnts eta, Cmpnts zet, PetscReal *delta)
 Implementation of ComputeCellFilterWidth().
 
SymTensor LeonardStress (Cmpnts velocity_filtered, SymTensor velocity_product_filtered)
 Implementation of LeonardStress().
 
SymTensor GermanoModelTensor (PetscReal delta, PetscReal alpha, PetscReal strain_magnitude_filtered, SymTensor strain_filtered, SymTensor strain_product_filtered)
 Implementation of GermanoModelTensor().
 
PetscErrorCode ResolveLESAveragingDirections (UserCtx *user, PetscBool direction[3])
 Implementation of ResolveLESAveragingDirections().
 
PetscReal ClipModelCoefficient (PetscReal coefficient, const LESConfig *config, PetscBool *limited)
 Implementation of ClipModelCoefficient().
 
PetscReal EddyViscosityFromCoefficient (PetscReal coefficient, PetscReal delta, PetscReal strain_magnitude, PetscReal molecular_viscosity, PetscReal min_viscosity_ratio)
 Implementation of EddyViscosityFromCoefficient().
 
PetscReal WALEEddyViscosity (Cmpnts dudx, Cmpnts dvdx, Cmpnts dwdx, PetscReal delta, PetscReal coefficient)
 Implementation of WALEEddyViscosity().
 
PetscReal VremanEddyViscosity (Cmpnts dudx, Cmpnts dvdx, Cmpnts dwdx, const Cmpnts edges[3], PetscReal coefficient)
 Implementation of VremanEddyViscosity().
 
PetscReal SubgridKineticEnergy (PetscReal yoshizawa_ci, PetscReal delta, PetscReal strain_magnitude)
 Implementation of SubgridKineticEnergy().
 
static PetscInt LESStrainSampleIndex (PetscInt index, PetscInt extent, PetscBool periodic)
 Maps a cell index to the index whose strain rate represents it.
 
static PetscErrorCode ComputeStrainRateField (UserCtx *user, Cmpnts ***ucat, Vec strain_diagonal, Vec strain_offdiagonal, Vec strain_magnitude)
 Fills the strain-rate scratch fields across the computed range and its halo.
 
static SymTensor LESStrainAt (Cmpnts diagonal, Cmpnts offdiagonal)
 Rebuilds a symmetric strain tensor from its two scratch storage vectors.
 
PetscErrorCode ComputeSmagorinskyConstant (UserCtx *user)
 Implementation of ComputeSmagorinskyConstant().
 
PetscErrorCode ComputeEddyViscosityLES (UserCtx *user)
 Implementation of ComputeEddyViscosityLES().
 
PetscErrorCode LogLESDiagnostics (UserCtx *user)
 Implementation of LogLESDiagnostics().
 

Variables

static const double LES_EPSILON = 1.0e-12
 

Detailed Description

Implements the Large Eddy Simulation (LES) subgrid-scale closure.

The closure is built from small kernels that each do one thing, so the physics is readable at the call sites and testable without a running solve:

Two routines drive them. ComputeSmagorinskyConstant() runs the Germano-Lilly procedure for the dynamic model, and ComputeEddyViscosityLES() turns whichever coefficient is in force into the eddy viscosity the momentum equations add to the molecular one. LogLESDiagnostics() reports the coefficient's time history.

The coefficient stored in UserCtx::CS is C, the factor multiplying Delta^2 |S|, which is Cs^2 in the classical notation and is signed when backscatter is admitted. Conversion to the familiar Cs happens only where a human reads the number.

Definition in file les.c.

Macro Definition Documentation

◆ __FUNCT__ [1/20]

#define __FUNCT__   "SymTensorSelfOuter"

Definition at line 137 of file les.c.

◆ __FUNCT__ [2/20]

#define __FUNCT__   "SymTensorCombine"

Definition at line 137 of file les.c.

◆ __FUNCT__ [3/20]

#define __FUNCT__   "SymTensorTrace"

Definition at line 137 of file les.c.

◆ __FUNCT__ [4/20]

#define __FUNCT__   "SymTensorDeviator"

Definition at line 137 of file les.c.

◆ __FUNCT__ [5/20]

#define __FUNCT__   "SymTensorContract"

Definition at line 137 of file les.c.

◆ __FUNCT__ [6/20]

#define __FUNCT__   "SymTensorTimesVector"

Definition at line 137 of file les.c.

◆ __FUNCT__ [7/20]

#define __FUNCT__   "SymTensorNormSq"

Definition at line 137 of file les.c.

◆ __FUNCT__ [8/20]

#define __FUNCT__   "StrainRateFromGradients"

Definition at line 137 of file les.c.

◆ __FUNCT__ [9/20]

#define __FUNCT__   "ComputeCellFilterWidth"

Definition at line 137 of file les.c.

◆ __FUNCT__ [10/20]

#define __FUNCT__   "LeonardStress"

Definition at line 137 of file les.c.

◆ __FUNCT__ [11/20]

#define __FUNCT__   "GermanoModelTensor"

Definition at line 137 of file les.c.

◆ __FUNCT__ [12/20]

#define __FUNCT__   "ResolveLESAveragingDirections"

Definition at line 137 of file les.c.

◆ __FUNCT__ [13/20]

#define __FUNCT__   "ClipModelCoefficient"

Definition at line 137 of file les.c.

◆ __FUNCT__ [14/20]

#define __FUNCT__   "EddyViscosityFromCoefficient"

Definition at line 137 of file les.c.

◆ __FUNCT__ [15/20]

#define __FUNCT__   "WALEEddyViscosity"

Definition at line 137 of file les.c.

◆ __FUNCT__ [16/20]

#define __FUNCT__   "VremanEddyViscosity"

Definition at line 137 of file les.c.

◆ __FUNCT__ [17/20]

#define __FUNCT__   "SubgridKineticEnergy"

Definition at line 137 of file les.c.

◆ __FUNCT__ [18/20]

#define __FUNCT__   "ComputeSmagorinskyConstant"

Definition at line 137 of file les.c.

◆ __FUNCT__ [19/20]

#define __FUNCT__   "ComputeEddyViscosityLES"

Definition at line 137 of file les.c.

◆ __FUNCT__ [20/20]

#define __FUNCT__   "LogLESDiagnostics"

Definition at line 137 of file les.c.

Function Documentation

◆ FinalizeSmagorinskyConstantField()

static PetscErrorCode FinalizeSmagorinskyConstantField ( UserCtx *  user)
static

Synchronizes the completed Smagorinsky coefficient field.

Definition at line 40 of file les.c.

41{
42 const FieldId fields[] = {FIELD_ID_CS};
43
44 PetscFunctionBeginUser;
45 PetscCall(SynchronizePeriodicCellFields(user, 1, fields));
46 PetscCall(UpdateLocalGhosts(user, FIELD_ID_CS));
47 PetscFunctionReturn(0);
48}
PetscErrorCode SynchronizePeriodicCellFields(UserCtx *user, PetscInt num_fields, const FieldId field_ids[])
Synchronizes periodic endpoint cells for a list of cell-centered fields.
FieldId
Compile-time identity for a catalogued Eulerian field.
@ FIELD_ID_CS
PetscErrorCode UpdateLocalGhosts(UserCtx *user, FieldId field_id)
Updates the local vector (including ghost points) from its corresponding global vector.
Definition setup.c:2489
Here is the call graph for this function:
Here is the caller graph for this function:

◆ LESResolveDomain()

static PetscErrorCode LESResolveDomain ( UserCtx *  user,
SpatialTargetPlan *  plan 
)
static

Resolves the iteration domain the closure computes over.

Delegates to the shared spatial target so the closure, the statistics pipeline, and anything else looping over cell-centred data agree on which indices are physical. The domain excludes PETSc halo storage and the solver's boundary, dummy, and duplicate-periodic indices; the last of those matters here, because counting a duplicate plane in a spatial average would weight a real cell twice.

The plan is resolved for CS as a representative cell-centred field. The bounds depend on the layout and the block's periodicity, not on which field is being iterated, so the scratch vectors this module allocates share them.

Definition at line 63 of file les.c.

64{
65 PetscFunctionBeginUser;
67 PetscFunctionReturn(0);
68}
@ PICURV_STATISTICS_MASK_FLUID
PetscErrorCode SpatialTargetPlanCreate(UserCtx *user, FieldId field_id, PicurvStatisticsMask mask, SpatialTargetPlan *plan)
Resolves the iteration domain for one field on one block.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ BuildWallModelExclusionMask()

static PetscErrorCode BuildWallModelExclusionMask ( UserCtx *  user,
Vec  inclusion 
)
static

Marks the near-wall cells a wall model overwrites, so the procedure skips them.

A wall model replaces the first interior cell's velocity with an analytic profile. The Germano identity evaluated there measures that imposed profile rather than resolved turbulence, so the cell is excluded from both contraction sums. This is not a modelling preference: the data at those cells is not a measurement, and averaging it in biases whatever set it belongs to. Under global averaging that set is the whole domain; under homogeneous averaging retaining the wall-normal direction it is the wall plane, whose value would otherwise be entirely imposed.

A cell left with no data receives a zero coefficient, which is the right answer where a wall model is already supplying the stress.

Only meaningful while a wall model is active; with none, nothing is imposed and every cell carries a real measurement.

Definition at line 87 of file les.c.

88{
89 const DMDALocalInfo info = user->info;
90 const BCFace negative[3] = {BC_FACE_NEG_X, BC_FACE_NEG_Y, BC_FACE_NEG_Z};
91 const BCFace positive[3] = {BC_FACE_POS_X, BC_FACE_POS_Y, BC_FACE_POS_Z};
92 const PetscInt extent[3] = {info.mx, info.my, info.mz};
93 PetscReal ***mask = NULL;
95
96 PetscFunctionBeginUser;
97 PetscCall(VecSet(inclusion, 1.0));
98 PetscCall(LESResolveDomain(user, &plan));
99 PetscCall(DMDAVecGetArray(user->da, inclusion, &mask));
100 for (PetscInt k = plan.start[2]; k < plan.end[2]; k++)
101 for (PetscInt j = plan.start[1]; j < plan.end[1]; j++)
102 for (PetscInt i = plan.start[0]; i < plan.end[0]; i++) {
103 const PetscInt coord[3] = {i, j, k};
104
105 for (PetscInt axis = 0; axis < 3; axis++) {
106 /* Index 1 and extent-2 are the first interior cells the wall model writes. */
107 if ((user->boundary_faces[negative[axis]].mathematical_type == WALL &&
108 coord[axis] == 1) ||
109 (user->boundary_faces[positive[axis]].mathematical_type == WALL &&
110 coord[axis] == extent[axis] - 2)) {
111 mask[k][j][i] = 0.0;
112 break;
113 }
114 }
115 }
116 PetscCall(DMDAVecRestoreArray(user->da, inclusion, &mask));
117 PetscFunctionReturn(0);
118}
static PetscErrorCode LESResolveDomain(UserCtx *user, SpatialTargetPlan *plan)
Resolves the iteration domain the closure computes over.
Definition les.c:63
PetscInt end[3]
Exclusive end per dimension (i, j, k).
PetscInt start[3]
Inclusive start per dimension (i, j, k).
Resolved iteration domain for one field on one block.
@ WALL
Definition variables.h:312
BoundaryFaceConfig boundary_faces[6]
Definition variables.h:1099
DMDALocalInfo info
Definition variables.h:1086
BCType mathematical_type
Definition variables.h:392
BCFace
Identifies the six logical faces of a structured computational block.
Definition variables.h:287
@ BC_FACE_NEG_X
Definition variables.h:288
@ BC_FACE_POS_Z
Definition variables.h:290
@ BC_FACE_POS_Y
Definition variables.h:289
@ BC_FACE_NEG_Z
Definition variables.h:290
@ BC_FACE_POS_X
Definition variables.h:288
@ BC_FACE_NEG_Y
Definition variables.h:289
Here is the call graph for this function:
Here is the caller graph for this function:

◆ LESPeriodicAxes()

static void LESPeriodicAxes ( const SimCtx *  simCtx,
PetscBool  periodic[3] 
)
static

Reports the block's per-axis periodicity in (xi, eta, zeta) order.

Reads the resolved global flags rather than the boundary face configuration. The two cannot disagree: the flags are derived from the boundary files, and the loader rejects a case whose opposite faces or whose blocks do not agree on periodicity. The flags are also what the DMDA itself was built from, and what the shared spatial target consults, so taking them here keeps one source of truth.

Definition at line 129 of file les.c.

130{
131 periodic[0] = (PetscBool)(simCtx->i_periodic != 0);
132 periodic[1] = (PetscBool)(simCtx->j_periodic != 0);
133 periodic[2] = (PetscBool)(simCtx->k_periodic != 0);
134}
PetscInt k_periodic
Definition variables.h:953
PetscInt i_periodic
Definition variables.h:953
PetscInt j_periodic
Definition variables.h:953
Here is the caller graph for this function:

◆ SymTensorSelfOuter()

SymTensor SymTensorSelfOuter ( Cmpnts  v)

Implementation of SymTensorSelfOuter().

Forms a symmetric tensor from a vector's outer product with itself.

Full API contract is documented with the header declaration in include/les.h.

See also
SymTensorSelfOuter()

Definition at line 144 of file les.c.

145{
146 SymTensor t;
147
148 t.xx = v.x * v.x;
149 t.xy = v.x * v.y;
150 t.xz = v.x * v.z;
151 t.yy = v.y * v.y;
152 t.yz = v.y * v.z;
153 t.zz = v.z * v.z;
154 return t;
155}
PetscReal yy
Definition variables.h:139
PetscScalar x
Definition variables.h:122
PetscScalar z
Definition variables.h:122
PetscReal xx
Definition variables.h:139
PetscScalar y
Definition variables.h:122
PetscReal yz
Definition variables.h:139
PetscReal zz
Definition variables.h:139
PetscReal xz
Definition variables.h:139
PetscReal xy
Definition variables.h:139
A symmetric second-order tensor stored by its six independent components.
Definition variables.h:138
Here is the caller graph for this function:

◆ SymTensorCombine()

SymTensor SymTensorCombine ( PetscReal  a,
SymTensor  x,
PetscReal  b,
SymTensor  y 
)

Implementation of SymTensorCombine().

Forms the linear combination a*x + b*y.

Full API contract is documented with the header declaration in include/les.h.

See also
SymTensorCombine()

Definition at line 165 of file les.c.

166{
167 SymTensor t;
168
169 t.xx = a * x.xx + b * y.xx;
170 t.xy = a * x.xy + b * y.xy;
171 t.xz = a * x.xz + b * y.xz;
172 t.yy = a * x.yy + b * y.yy;
173 t.yz = a * x.yz + b * y.yz;
174 t.zz = a * x.zz + b * y.zz;
175 return t;
176}
Here is the caller graph for this function:

◆ SymTensorTrace()

PetscReal SymTensorTrace ( SymTensor  t)

Implementation of SymTensorTrace().

Returns the trace t_kk.

Full API contract is documented with the header declaration in include/les.h.

See also
SymTensorTrace()

Definition at line 186 of file les.c.

187{
188 return t.xx + t.yy + t.zz;
189}
Here is the caller graph for this function:

◆ SymTensorDeviator()

SymTensor SymTensorDeviator ( SymTensor  t)

Implementation of SymTensorDeviator().

Removes the isotropic part, returning t_ij - (1/3) delta_ij t_kk.

Full API contract is documented with the header declaration in include/les.h.

See also
SymTensorDeviator()

Definition at line 199 of file les.c.

200{
201 const PetscReal isotropic = SymTensorTrace(t) / 3.0;
202
203 t.xx -= isotropic;
204 t.yy -= isotropic;
205 t.zz -= isotropic;
206 return t;
207}
PetscReal SymTensorTrace(SymTensor t)
Implementation of SymTensorTrace().
Definition les.c:186
Here is the call graph for this function:
Here is the caller graph for this function:

◆ SymTensorContract()

PetscReal SymTensorContract ( SymTensor  a,
SymTensor  b 
)

Implementation of SymTensorContract().

Contracts two symmetric tensors as a_ij b_ij.

Full API contract is documented with the header declaration in include/les.h.

See also
SymTensorContract()

Definition at line 217 of file les.c.

218{
219 // The three off-diagonal components each stand for two entries of the full tensor.
220 return a.xx * b.xx + a.yy * b.yy + a.zz * b.zz +
221 2.0 * (a.xy * b.xy + a.xz * b.xz + a.yz * b.yz);
222}
Here is the caller graph for this function:

◆ SymTensorTimesVector()

Cmpnts SymTensorTimesVector ( SymTensor  t,
Cmpnts  v 
)

Implementation of SymTensorTimesVector().

Applies a symmetric tensor to a vector, returning t_ij v_j.

Full API contract is documented with the header declaration in include/les.h.

See also
SymTensorTimesVector()

Definition at line 232 of file les.c.

233{
234 Cmpnts result;
235
236 result.x = t.xx * v.x + t.xy * v.y + t.xz * v.z;
237 result.y = t.xy * v.x + t.yy * v.y + t.yz * v.z;
238 result.z = t.xz * v.x + t.yz * v.y + t.zz * v.z;
239 return result;
240}
A 3D point or vector with PetscScalar components.
Definition variables.h:121
Here is the caller graph for this function:

◆ SymTensorNormSq()

PetscReal SymTensorNormSq ( SymTensor  t)

Implementation of SymTensorNormSq().

Returns the squared Frobenius norm t_ij t_ij.

Full API contract is documented with the header declaration in include/les.h.

See also
SymTensorNormSq()

Definition at line 250 of file les.c.

251{
252 return SymTensorContract(t, t);
253}
PetscReal SymTensorContract(SymTensor a, SymTensor b)
Implementation of SymTensorContract().
Definition les.c:217
Here is the call graph for this function:
Here is the caller graph for this function:

◆ StrainRateFromGradients()

PetscErrorCode StrainRateFromGradients ( Cmpnts  dudx,
Cmpnts  dvdx,
Cmpnts  dwdx,
SymTensor *  strain,
PetscReal *  magnitude 
)

Implementation of StrainRateFromGradients().

Builds the strain-rate tensor and its magnitude from a velocity gradient.

Full API contract is documented with the header declaration in include/les.h.

See also
StrainRateFromGradients()

Definition at line 267 of file les.c.

269{
270 SymTensor s;
271
272 PetscFunctionBeginUser;
273
274 s.xx = dudx.x;
275 s.xy = 0.5 * (dudx.y + dvdx.x);
276 s.xz = 0.5 * (dudx.z + dwdx.x);
277 s.yy = dvdx.y;
278 s.yz = 0.5 * (dvdx.z + dwdx.y);
279 s.zz = dwdx.z;
280
281 if (strain) *strain = s;
282 // |S| = sqrt(2 S_ij S_ij), with the off-diagonal doubling handled by the contraction.
283 if (magnitude) *magnitude = PetscSqrtReal(2.0 * SymTensorNormSq(s));
284
285 PetscFunctionReturn(0);
286}
PetscReal SymTensorNormSq(SymTensor t)
Implementation of SymTensorNormSq().
Definition les.c:250
Here is the call graph for this function:
Here is the caller graph for this function:

◆ ComputeCellFilterWidth()

PetscErrorCode ComputeCellFilterWidth ( LESFilterWidthModel  model,
PetscReal  aj,
Cmpnts  csi,
Cmpnts  eta,
Cmpnts  zet,
PetscReal *  delta 
)

Implementation of ComputeCellFilterWidth().

Computes one cell's grid filter width under the selected width model.

Full API contract is documented with the header declaration in include/les.h.

See also
ComputeCellFilterWidth()

Definition at line 296 of file les.c.

298{
299 double dx = 0.0, dy = 0.0, dz = 0.0;
300
301 PetscFunctionBeginUser;
302 PetscCheck(delta != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
303 "Filter width destination cannot be NULL.");
304 PetscCheck(aj > 0.0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE,
305 "Cell Jacobian must be positive to define a filter width; received %g.", (double)aj);
306
308 // Cell volume alone: exact for a cube, and increasingly optimistic as the
309 // cell is stretched, because it averages the three extents geometrically.
310 *delta = PetscPowReal(1.0 / aj, 1.0 / 3.0);
311 PetscFunctionReturn(0);
312 }
313
314 // The cell's own extents, not the Cartesian components of its diagonal: a filter
315 // width that changed when the same cell was rotated would hand a bend nine times the
316 // eddy viscosity of an identical cell in a straight section.
317 PetscCall(ComputeCellDirectionalExtents(aj, csi, eta, zet, &dx, &dy, &dz));
318
319 switch (model) {
321 // The largest unresolved scale the cell can carry; conservative on stretched grids.
322 *delta = PetscMax(dx, PetscMax(dy, dz));
323 break;
325 // Scotti, Meneveau & Lilly (1993): the cube root of the extents, multiplied by
326 // f(a1, a2), where a1 and a2 are the two shorter extents over the longest. f is 1
327 // for a cube and grows with the cell's distortion. It assumes the cut-off lies in
328 // the inertial range in every direction, which a very long cell violates.
329 const double longest = PetscMax(dx, PetscMax(dy, dz));
330 const double shortest = PetscMin(dx, PetscMin(dy, dz));
331 const double middle = dx + dy + dz - longest - shortest;
332 const double ln_a1 = log(shortest / longest), ln_a2 = log(middle / longest);
333 const double shape = cosh(sqrt((4.0 / 27.0) *
334 (ln_a1 * ln_a1 - ln_a1 * ln_a2 + ln_a2 * ln_a2)));
335 *delta = PetscPowReal(PetscMax(dx * dy * dz, LES_EPSILON), 1.0 / 3.0) * shape;
336 break;
337 }
339 default:
340 *delta = PetscPowReal(PetscMax(dx * dy * dz, LES_EPSILON), 1.0 / 3.0);
341 break;
342 }
343
344 PetscFunctionReturn(0);
345}
PetscErrorCode ComputeCellDirectionalExtents(PetscReal ajc, Cmpnts csi, Cmpnts eta, Cmpnts zet, double *l_xi, double *l_eta, double *l_zeta)
Computes a cell's extent along each of its own grid directions.
Definition Metric.c:328
static const double LES_EPSILON
Definition les.c:31
@ LES_FILTER_WIDTH_SCOTTI
Definition variables.h:583
@ LES_FILTER_WIDTH_GEOMETRIC_MEAN
Definition variables.h:581
@ LES_FILTER_WIDTH_CUBE_ROOT_VOLUME
Definition variables.h:580
@ LES_FILTER_WIDTH_MAX_EDGE
Definition variables.h:582
Here is the call graph for this function:
Here is the caller graph for this function:

◆ LeonardStress()

SymTensor LeonardStress ( Cmpnts  velocity_filtered,
SymTensor  velocity_product_filtered 
)

Implementation of LeonardStress().

Forms the Leonard stress L_ij = (u_i u_j)^ - u^_i u^_j.

Full API contract is documented with the header declaration in include/les.h.

See also
LeonardStress()

Definition at line 359 of file les.c.

360{
361 // L_ij = (u_i u_j)^ - u^_i u^_j : the stress carried between the two filter widths.
362 return SymTensorCombine(1.0, velocity_product_filtered,
363 -1.0, SymTensorSelfOuter(velocity_filtered));
364}
SymTensor SymTensorCombine(PetscReal a, SymTensor x, PetscReal b, SymTensor y)
Implementation of SymTensorCombine().
Definition les.c:165
SymTensor SymTensorSelfOuter(Cmpnts v)
Implementation of SymTensorSelfOuter().
Definition les.c:144
Here is the call graph for this function:
Here is the caller graph for this function:

◆ GermanoModelTensor()

SymTensor GermanoModelTensor ( PetscReal  delta,
PetscReal  alpha,
PetscReal  strain_magnitude_filtered,
SymTensor  strain_filtered,
SymTensor  strain_product_filtered 
)

Implementation of GermanoModelTensor().

Forms the deviatoric Germano model tensor M_ij.

Full API contract is documented with the header declaration in include/les.h.

See also
GermanoModelTensor()

Definition at line 374 of file les.c.

378{
379 // Term one: the model evaluated on the test-filtered strain. Both factors were
380 // filtered separately, then multiplied.
381 const SymTensor test_scale = SymTensorCombine(alpha * strain_magnitude_filtered,
382 strain_filtered, 0.0, strain_filtered);
383
384 // Term two: the grid-scale model formed pointwise and then test-filtered. This is
385 // the filter of a product, and it is not equal to the product of the filtered
386 // factors above. That inequality carries the whole information content of the
387 // dynamic procedure.
388 const SymTensor grid_scale = strain_product_filtered;
389
390 const SymTensor model = SymTensorCombine(-2.0 * delta * delta, test_scale,
391 2.0 * delta * delta, grid_scale);
392
393 // Trace-free, so the Leonard stress's trace cannot enter the contraction through
394 // whatever discrete divergence the velocity field carries.
395 return SymTensorDeviator(model);
396}
SymTensor SymTensorDeviator(SymTensor t)
Implementation of SymTensorDeviator().
Definition les.c:199
Here is the call graph for this function:
Here is the caller graph for this function:

◆ ResolveLESAveragingDirections()

PetscErrorCode ResolveLESAveragingDirections ( UserCtx *  user,
PetscBool  direction[3] 
)

Implementation of ResolveLESAveragingDirections().

Resolves which logical directions the dynamic coefficient is averaged over.

Full API contract is documented with the header declaration in include/les.h.

See also
ResolveLESAveragingDirections()

Definition at line 410 of file les.c.

411{
412 const LESConfig *config = &user->simCtx->les_config;
413 PetscInt selected = 0;
414 PetscBool periodic[3];
415
416 PetscFunctionBeginUser;
417 PetscCheck(direction != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
418 "Averaging direction destination cannot be NULL.");
419 LESPeriodicAxes(user->simCtx, periodic);
420
421 for (PetscInt axis = 0; axis < 3; axis++) direction[axis] = PETSC_FALSE;
422
423 switch (config->averaging_mode) {
425 // No averaging: the coefficient is whatever the single cell's contractions give.
426 PetscFunctionReturn(0);
427
429 for (PetscInt axis = 0; axis < 3; axis++) direction[axis] = PETSC_TRUE;
430 PetscFunctionReturn(0);
431
433 default:
434 for (PetscInt axis = 0; axis < 3; axis++) {
435 if (config->averaging_direction[axis]) {
436 direction[axis] = PETSC_TRUE;
437 selected++;
438 }
439 }
440 // Naming no direction asks for the periodic axes, which is where the flow is
441 // homogeneous in every case the solver can currently express.
442 if (selected == 0) {
443 for (PetscInt axis = 0; axis < 3; axis++) {
444 if (periodic[axis]) {
445 direction[axis] = PETSC_TRUE;
446 selected++;
447 }
448 }
449 }
450 PetscCheck(selected > 0, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE,
451 "Homogeneous LES averaging was requested, but this block declares no "
452 "periodic axis and the configuration names no direction. Name the "
453 "homogeneous directions explicitly or select local averaging.");
454 PetscFunctionReturn(0);
455 }
456}
static void LESPeriodicAxes(const SimCtx *simCtx, PetscBool periodic[3])
Reports the block's per-axis periodicity in (xi, eta, zeta) order.
Definition les.c:129
LESConfig les_config
Parameters of the LES closure selected by les.
Definition variables.h:987
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:1077
PetscBool averaging_direction[3]
Averaged-over logical directions (xi, eta, zeta).
Definition variables.h:638
@ LES_AVERAGING_LOCAL
Definition variables.h:605
@ LES_AVERAGING_GLOBAL
Definition variables.h:607
@ LES_AVERAGING_HOMOGENEOUS
Definition variables.h:606
LESAveragingMode averaging_mode
Averaging set for the Germano contractions.
Definition variables.h:637
Every user-selectable parameter of the LES closure.
Definition variables.h:629
Here is the call graph for this function:
Here is the caller graph for this function:

◆ ClipModelCoefficient()

PetscReal ClipModelCoefficient ( PetscReal  coefficient,
const LESConfig *  config,
PetscBool *  limited 
)

Implementation of ClipModelCoefficient().

Applies the configured admissible range to one model coefficient.

Full API contract is documented with the header declaration in include/les.h.

See also
ClipModelCoefficient()

Definition at line 466 of file les.c.

467{
468 PetscReal result = coefficient;
469
470 switch (config->clip_mode) {
471 case LES_CLIP_NONE:
472 // Signed coefficient retained: backscatter survives, and stability is left to
473 // the total-viscosity floor rather than to the coefficient's sign.
474 break;
476 if (result < 0.0) result = 0.0;
477 break;
478 case LES_CLIP_CLAMP:
479 default:
480 if (result < 0.0) result = 0.0;
481 if (result > config->max_cs * config->max_cs) result = config->max_cs * config->max_cs;
482 break;
483 }
484
485 if (limited) *limited = (PetscBool)(result != coefficient);
486 return result;
487}
@ LES_CLIP_CLIP_NEGATIVE
Definition variables.h:619
@ LES_CLIP_CLAMP
Definition variables.h:618
@ LES_CLIP_NONE
Definition variables.h:620
PetscReal max_cs
Ceiling on Cs under LES_CLIP_CLAMP.
Definition variables.h:640
LESClipMode clip_mode
Admissible range for the coefficient.
Definition variables.h:639
Here is the caller graph for this function:

◆ EddyViscosityFromCoefficient()

PetscReal EddyViscosityFromCoefficient ( PetscReal  coefficient,
PetscReal  delta,
PetscReal  strain_magnitude,
PetscReal  molecular_viscosity,
PetscReal  min_viscosity_ratio 
)

Implementation of EddyViscosityFromCoefficient().

Builds the eddy viscosity from a model coefficient.

Full API contract is documented with the header declaration in include/les.h.

See also
EddyViscosityFromCoefficient()

Definition at line 497 of file les.c.

501{
502 PetscReal nu_t = coefficient * delta * delta * strain_magnitude;
503
504 // A negative eddy viscosity is physical up to the point where it would drive the
505 // total viscosity non-positive and make the momentum operator ill posed.
506 const PetscReal floor = (min_viscosity_ratio - 1.0) * molecular_viscosity;
507
508 if (nu_t < floor) nu_t = floor;
509 return nu_t;
510}
double nu_t(double yplus)
Computes turbulent eddy viscosity ratio (ν_t / ν)
Here is the call graph for this function:
Here is the caller graph for this function:

◆ WALEEddyViscosity()

PetscReal WALEEddyViscosity ( Cmpnts  dudx,
Cmpnts  dvdx,
Cmpnts  dwdx,
PetscReal  delta,
PetscReal  coefficient 
)

Implementation of WALEEddyViscosity().

WALE eddy viscosity (Nicoud & Ducros 1999) from the resolved velocity gradient.

Full API contract is documented with the header declaration in include/les.h.

See also
WALEEddyViscosity()

Definition at line 520 of file les.c.

522{
523 const PetscReal g[3][3] = {{dudx.x, dudx.y, dudx.z},
524 {dvdx.x, dvdx.y, dvdx.z},
525 {dwdx.x, dwdx.y, dwdx.z}};
526 PetscReal g2[3][3], sd[3][3], trace = 0.0, ss = 0.0, sdsd = 0.0, denominator;
527
528 for (PetscInt a = 0; a < 3; ++a)
529 for (PetscInt b = 0; b < 3; ++b) {
530 g2[a][b] = 0.0;
531 for (PetscInt c = 0; c < 3; ++c) g2[a][b] += g[a][c] * g[c][b];
532 }
533 for (PetscInt a = 0; a < 3; ++a) trace += g2[a][a];
534 for (PetscInt a = 0; a < 3; ++a)
535 for (PetscInt b = 0; b < 3; ++b) {
536 const PetscReal s = 0.5 * (g[a][b] + g[b][a]);
537 // Traceless symmetric part of the squared gradient: it vanishes in pure shear,
538 // which is what lets the model switch itself off in a laminar shear layer.
539 sd[a][b] = 0.5 * (g2[a][b] + g2[b][a]) - (a == b ? trace / 3.0 : 0.0);
540 ss += s * s;
541 sdsd += sd[a][b] * sd[a][b];
542 }
543
544 denominator = PetscPowReal(ss, 2.5) + PetscPowReal(sdsd, 1.25);
545 if (denominator <= LES_EPSILON * LES_EPSILON) return 0.0;
546 return coefficient * coefficient * delta * delta * PetscPowReal(sdsd, 1.5) / denominator;
547}
Here is the caller graph for this function:

◆ VremanEddyViscosity()

PetscReal VremanEddyViscosity ( Cmpnts  dudx,
Cmpnts  dvdx,
Cmpnts  dwdx,
const Cmpnts  edges[3],
PetscReal  coefficient 
)

Implementation of VremanEddyViscosity().

Vreman eddy viscosity (Vreman 2004), weighting each grid direction by its own spacing.

Full API contract is documented with the header declaration in include/les.h.

See also
VremanEddyViscosity()

Definition at line 557 of file les.c.

559{
560 const Cmpnts gradient[3] = {dudx, dvdx, dwdx};
561 PetscReal beta[3][3] = {{0.0}}, alpha_sq = 0.0, b_beta;
562
563 // Vreman's beta_ij = sum_m Delta_m^2 alpha_mi alpha_mj on a Cartesian grid, where
564 // Delta_m alpha_mi is the change in u_i across the cell in direction m. On a
565 // curvilinear grid that change is edge_m . grad(u_i), so the same sum over the
566 // cell's own directions reproduces the Cartesian form on an aligned grid and does
567 // not depend on the grid's orientation elsewhere.
568 for (PetscInt m = 0; m < 3; ++m) {
569 PetscReal change[3];
570 for (PetscInt a = 0; a < 3; ++a)
571 change[a] = edges[m].x * gradient[a].x + edges[m].y * gradient[a].y + edges[m].z * gradient[a].z;
572 for (PetscInt a = 0; a < 3; ++a)
573 for (PetscInt b = 0; b < 3; ++b) beta[a][b] += change[a] * change[b];
574 }
575 for (PetscInt a = 0; a < 3; ++a)
576 alpha_sq += gradient[a].x * gradient[a].x + gradient[a].y * gradient[a].y + gradient[a].z * gradient[a].z;
577
578 b_beta = beta[0][0] * beta[1][1] - beta[0][1] * beta[0][1]
579 + beta[0][0] * beta[2][2] - beta[0][2] * beta[0][2]
580 + beta[1][1] * beta[2][2] - beta[1][2] * beta[1][2];
581
582 // B_beta is non-negative in exact arithmetic; rounding can leave it a hair below.
583 if (alpha_sq <= LES_EPSILON * LES_EPSILON || b_beta <= 0.0) return 0.0;
584 return coefficient * PetscSqrtReal(b_beta / alpha_sq);
585}
Here is the caller graph for this function:

◆ SubgridKineticEnergy()

PetscReal SubgridKineticEnergy ( PetscReal  yoshizawa_ci,
PetscReal  delta,
PetscReal  strain_magnitude 
)

Implementation of SubgridKineticEnergy().

Returns the modelled subgrid kinetic energy at a cell.

Full API contract is documented with the header declaration in include/les.h.

See also
SubgridKineticEnergy()

Definition at line 595 of file les.c.

596{
597 return 2.0 * yoshizawa_ci * delta * delta * strain_magnitude * strain_magnitude;
598}
Here is the caller graph for this function:

◆ LESStrainSampleIndex()

static PetscInt LESStrainSampleIndex ( PetscInt  index,
PetscInt  extent,
PetscBool  periodic 
)
static

Maps a cell index to the index whose strain rate represents it.

The strain rate must be known one cell beyond the computed range, because the test-filter stencil reaches there. At an interior index the answer is the index itself. At the two layout-boundary planes it is not, and for opposite reasons.

On a periodic axis, index 0 and index m-1 are duplicates of the last and first interior cells. A central difference evaluated at index 0 straddles the wrap and returns nonsense, so the strain is evaluated instead at ghost index -2, which addresses the same physical cell from the interior side. Index m+1 plays the same role at the other end. These are the ghost indices the periodic cell synchronization itself copies from, and the DMDA carries the three ghost layers they need whenever any axis is periodic.

On a non-periodic axis the two planes are the solver's boundary layer, where a central difference would reach outside the domain. The nearest interior cell's strain is used instead, which is a zeroth-order extrapolation into a layer that only ever contributes to a filter average.

Definition at line 624 of file les.c.

625{
626 if (index == 0) return periodic ? -2 : 1;
627 if (index == extent - 1) return periodic ? extent + 1 : extent - 2;
628 return index;
629}
Here is the caller graph for this function:

◆ ComputeStrainRateField()

static PetscErrorCode ComputeStrainRateField ( UserCtx *  user,
Cmpnts ***  ucat,
Vec  strain_diagonal,
Vec  strain_offdiagonal,
Vec  strain_magnitude 
)
static

Fills the strain-rate scratch fields across the computed range and its halo.

Definition at line 634 of file les.c.

637{
638 DM da = user->da, fda = user->fda;
639 DMDALocalInfo info;
641 PetscBool periodic[3];
642 Cmpnts ***diagonal = NULL, ***offdiagonal = NULL;
643 PetscReal ***magnitude = NULL;
644
645 PetscFunctionBeginUser;
646
647 PetscCall(DMDAGetLocalInfo(da, &info));
648 PetscCall(LESResolveDomain(user, &plan));
649 LESPeriodicAxes(user->simCtx, periodic);
650
651 PetscCall(DMDAVecGetArray(fda, strain_diagonal, &diagonal));
652 PetscCall(DMDAVecGetArray(fda, strain_offdiagonal, &offdiagonal));
653 PetscCall(DMDAVecGetArray(da, strain_magnitude, &magnitude));
654
655 // One cell beyond the computed range in every direction, because that is how far
656 // the 3x3x3 test-filter stencil reaches from the outermost computed cell.
657 for (PetscInt k = plan.start[2] - 1; k <= plan.end[2]; k++)
658 for (PetscInt j = plan.start[1] - 1; j <= plan.end[1]; j++)
659 for (PetscInt i = plan.start[0] - 1; i <= plan.end[0]; i++) {
660 const PetscInt si = LESStrainSampleIndex(i, info.mx, periodic[0]);
661 const PetscInt sj = LESStrainSampleIndex(j, info.my, periodic[1]);
662 const PetscInt sk = LESStrainSampleIndex(k, info.mz, periodic[2]);
663
664 Cmpnts dudx, dvdx, dwdx;
665 SymTensor strain;
666 PetscReal strain_abs;
667
668 PetscCall(ComputeVectorFieldDerivatives(user, si, sj, sk, ucat, &dudx, &dvdx, &dwdx));
669 PetscCall(StrainRateFromGradients(dudx, dvdx, dwdx, &strain, &strain_abs));
670
671 diagonal[k][j][i].x = strain.xx;
672 diagonal[k][j][i].y = strain.yy;
673 diagonal[k][j][i].z = strain.zz;
674 offdiagonal[k][j][i].x = strain.xy;
675 offdiagonal[k][j][i].y = strain.xz;
676 offdiagonal[k][j][i].z = strain.yz;
677 magnitude[k][j][i] = strain_abs;
678 }
679
680 PetscCall(DMDAVecRestoreArray(fda, strain_diagonal, &diagonal));
681 PetscCall(DMDAVecRestoreArray(fda, strain_offdiagonal, &offdiagonal));
682 PetscCall(DMDAVecRestoreArray(da, strain_magnitude, &magnitude));
683
684 PetscFunctionReturn(0);
685}
PetscErrorCode StrainRateFromGradients(Cmpnts dudx, Cmpnts dvdx, Cmpnts dwdx, SymTensor *strain, PetscReal *magnitude)
Implementation of StrainRateFromGradients().
Definition les.c:267
static PetscInt LESStrainSampleIndex(PetscInt index, PetscInt extent, PetscBool periodic)
Maps a cell index to the index whose strain rate represents it.
Definition les.c:624
PetscErrorCode ComputeVectorFieldDerivatives(UserCtx *user, PetscInt i, PetscInt j, PetscInt k, Cmpnts ***field_data, Cmpnts *dudx, Cmpnts *dvdx, Cmpnts *dwdx)
Computes the derivatives of a cell-centered vector field at a specific grid point.
Definition setup.c:4074
Here is the call graph for this function:
Here is the caller graph for this function:

◆ LESStrainAt()

static SymTensor LESStrainAt ( Cmpnts  diagonal,
Cmpnts  offdiagonal 
)
inlinestatic

Rebuilds a symmetric strain tensor from its two scratch storage vectors.

Definition at line 690 of file les.c.

691{
692 SymTensor s;
693
694 s.xx = diagonal.x;
695 s.yy = diagonal.y;
696 s.zz = diagonal.z;
697 s.xy = offdiagonal.x;
698 s.xz = offdiagonal.y;
699 s.yz = offdiagonal.z;
700 return s;
701}
Here is the caller graph for this function:

◆ ComputeSmagorinskyConstant()

PetscErrorCode ComputeSmagorinskyConstant ( UserCtx *  user)

Implementation of ComputeSmagorinskyConstant().

Computes the dynamic Smagorinsky coefficient field for one block.

Full API contract (arguments, ownership, side effects) is documented with the header declaration in include/les.h.

See also
ComputeSmagorinskyConstant()

Definition at line 715 of file les.c.

716{
717 SimCtx *simCtx = user->simCtx;
718 const LESConfig *config = &simCtx->les_config;
719 DM da = user->da, fda = user->fda;
721 PetscBool average_direction[3];
722 PetscReal alpha;
723
724 Vec lStrainDiag, lStrainOff, lStrainAbs, lLM, lMM, lCsq;
725 Vec lWallMask = NULL;
726
727 Cmpnts ***ucat = NULL, ***strain_diag = NULL, ***strain_off = NULL;
728 Cmpnts ***csi = NULL, ***eta = NULL, ***zet = NULL;
729 PetscReal ***strain_abs = NULL, ***nvert = NULL, ***aj = NULL;
730 PetscReal ***lm = NULL, ***mm = NULL, ***csq = NULL, ***cs_out = NULL;
731
732 LESDiagnosticsState diagnostics = {0.0, 0.0, 0.0, 0.0, 0.0, PETSC_TRUE};
733
734 PetscFunctionBeginUser;
736
737 PetscCheck(simCtx->les == DYNAMIC_SMAGORINSKY, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE,
738 "The dynamic procedure runs only for the dynamic Smagorinsky model. The "
739 "constant model prescribes its coefficient from configuration and holds "
740 "no coefficient field.");
741
742 // The dynamic procedure needs a developed field to sample. Starting from rest it
743 // has nothing to measure, so the first steps run with no subgrid dissipation.
744 if (simCtx->step < 2 && simCtx->StartStep == 0) {
745 PetscCall(VecSet(user->CS, 0.0));
746 PetscCall(FinalizeSmagorinskyConstantField(user));
747 user->les_diagnostics = (LESDiagnosticsState){0.0, 0.0, 0.0, 0.0, 0.0, PETSC_FALSE};
749 "Holding the dynamic coefficient at zero for the opening steps (step=%d).\n",
750 simCtx->step);
752 PetscFunctionReturn(0);
753 }
754
755 PetscCall(LESResolveDomain(user, &plan));
756 PetscCall(ResolveLESAveragingDirections(user, average_direction));
757
758 // alpha is the squared ratio of the two filter widths and sets the relative weight
759 // of the test-scale and grid-scale terms in the model tensor. The widths are the
760 // cube roots of the three directional widths, and the configured ratio applies to
761 // each direction the kernel filters. The box filters all three, so alpha is the
762 // ratio squared. Simpson filters only i and k and leaves j at the grid width, so
763 // its widths differ by ratio^(2/3) and alpha is ratio^(4/3); squaring the ratio
764 // there halved the dynamic coefficient on HOM02 (0.091 against the box's 0.194).
766 alpha = PetscPowReal(config->test_filter_width_ratio, 4.0 / 3.0);
767 } else {
768 alpha = config->test_filter_width_ratio * config->test_filter_width_ratio;
769 }
770
771 PetscCall(VecDuplicate(user->lUcat, &lStrainDiag));
772 PetscCall(VecDuplicate(user->lUcat, &lStrainOff));
773 PetscCall(VecDuplicate(user->lNvert, &lStrainAbs));
774 PetscCall(VecDuplicate(user->lNvert, &lLM));
775 PetscCall(VecDuplicate(user->lNvert, &lMM));
776 PetscCall(VecDuplicate(user->lNvert, &lCsq));
777 PetscCall(VecSet(lStrainDiag, 0.0));
778 PetscCall(VecSet(lStrainOff, 0.0));
779 PetscCall(VecSet(lStrainAbs, 0.0));
780 PetscCall(VecSet(lLM, 0.0));
781 PetscCall(VecSet(lMM, 0.0));
782 PetscCall(VecSet(lCsq, 0.0));
783 /* A wall model overwrites the first interior cell, so those cells carry an imposed
784 profile rather than a measurement and are kept out of the contraction sums. */
785 if (simCtx->wallfunction) {
786 PetscCall(VecDuplicate(user->lNvert, &lWallMask));
787 PetscCall(BuildWallModelExclusionMask(user, lWallMask));
788 }
789
790 PetscCall(DMDAVecGetArray(fda, user->lUcat, &ucat));
791
792 // --- 1. Strain rate everywhere the filter stencil will look -----------------
793 PetscCall(ComputeStrainRateField(user, ucat, lStrainDiag, lStrainOff, lStrainAbs));
794
795 PetscCall(DMDAVecGetArrayRead(fda, lStrainDiag, &strain_diag));
796 PetscCall(DMDAVecGetArrayRead(fda, lStrainOff, &strain_off));
797 PetscCall(DMDAVecGetArrayRead(da, lStrainAbs, &strain_abs));
798 PetscCall(DMDAVecGetArrayRead(fda, user->lCsi, &csi));
799 PetscCall(DMDAVecGetArrayRead(fda, user->lEta, &eta));
800 PetscCall(DMDAVecGetArrayRead(fda, user->lZet, &zet));
801 PetscCall(DMDAVecGetArrayRead(da, user->lNvert, &nvert));
802 PetscCall(DMDAVecGetArrayRead(da, user->lAj, &aj));
803 PetscCall(DMDAVecGetArray(da, lLM, &lm));
804 PetscCall(DMDAVecGetArray(da, lMM, &mm));
805
806 // --- 2. Germano contractions at every fluid cell ----------------------------
807 for (PetscInt k = plan.start[2]; k < plan.end[2]; k++)
808 for (PetscInt j = plan.start[1]; j < plan.end[1]; j++)
809 for (PetscInt i = plan.start[0]; i < plan.end[0]; i++) {
810 double velocity_x[3][3][3], velocity_y[3][3][3], velocity_z[3][3][3];
811 double strain_abs_stencil[3][3][3], weights[3][3][3];
812 SymTensor strain_stencil[3][3][3], product_stencil[3][3][3], velocity_product[3][3][3];
813 Cmpnts velocity_filtered;
814 SymTensor strain_filtered, product_filtered, velocity_product_filtered;
815 SymTensor leonard, model;
816 PetscReal strain_abs_filtered, delta, volume;
817
818 if (!SpatialTargetPlanMaskAllows(&plan, nvert[k][j][i])) {
819 lm[k][j][i] = 0.0;
820 mm[k][j][i] = 0.0;
821 continue;
822 }
823
824 // --- 2a. Gather the 3x3x3 stencil -------------------------------------
825 for (PetscInt r = -1; r <= 1; r++)
826 for (PetscInt q = -1; q <= 1; q++)
827 for (PetscInt p = -1; p <= 1; p++) {
828 const PetscInt R = r + 1, Q = q + 1, P = p + 1;
829 const PetscInt KK = k + r, JJ = j + q, II = i + p;
830 const SymTensor strain = LESStrainAt(strain_diag[KK][JJ][II], strain_off[KK][JJ][II]);
831 const PetscReal strain_abs_here = strain_abs[KK][JJ][II];
832
833 velocity_x[R][Q][P] = ucat[KK][JJ][II].x;
834 velocity_y[R][Q][P] = ucat[KK][JJ][II].y;
835 velocity_z[R][Q][P] = ucat[KK][JJ][II].z;
836
837 strain_stencil[R][Q][P] = strain;
838 strain_abs_stencil[R][Q][P] = strain_abs_here;
839
840 // The grid-scale model formed pointwise, before any test filtering. Test
841 // filtering this product is the term the model tensor cannot do without,
842 // and it is not the same as multiplying the two filtered factors.
843 product_stencil[R][Q][P] = SymTensorCombine(strain_abs_here, strain, 0.0, strain);
844
845 velocity_product[R][Q][P] = SymTensorSelfOuter(ucat[KK][JJ][II]);
846 weights[R][Q][P] = aj[KK][JJ][II];
847 }
848
849 // --- 2b. Test-filter every quantity the identity needs ----------------
850 velocity_filtered.x = ApplyLESTestFilter(config->test_filter_kernel, velocity_x, weights);
851 velocity_filtered.y = ApplyLESTestFilter(config->test_filter_kernel, velocity_y, weights);
852 velocity_filtered.z = ApplyLESTestFilter(config->test_filter_kernel, velocity_z, weights);
853 strain_abs_filtered = ApplyLESTestFilter(config->test_filter_kernel, strain_abs_stencil, weights);
854 PetscCall(ApplyLESTestFilterSymTensor(config->test_filter_kernel, strain_stencil, weights, &strain_filtered));
855 PetscCall(ApplyLESTestFilterSymTensor(config->test_filter_kernel, product_stencil, weights, &product_filtered));
856 PetscCall(ApplyLESTestFilterSymTensor(config->test_filter_kernel, velocity_product, weights, &velocity_product_filtered));
857
858 // --- 2c. Leonard stress and model tensor -------------------------------
859 leonard = LeonardStress(velocity_filtered, velocity_product_filtered);
860
861 PetscCall(ComputeCellFilterWidth(config->filter_width_model, aj[k][j][i],
862 csi[k][j][i], eta[k][j][i], zet[k][j][i], &delta));
863
864 model = GermanoModelTensor(delta, alpha, strain_abs_filtered, strain_filtered, product_filtered);
865
866 // --- 2d. Lilly's least-squares contraction -----------------------------
867 // Stored pre-multiplied by cell volume. The average that follows is over
868 // volume, not over cell count, so that a refined region does not dominate it
869 // merely by holding more cells; carrying the weight here keeps the shared
870 // averaging kernel free of any weighting policy of its own.
871 volume = 1.0 / aj[k][j][i];
872 lm[k][j][i] = volume * SymTensorContract(leonard, model);
873 mm[k][j][i] = volume * SymTensorNormSq(model);
874 }
875
876 PetscCall(DMDAVecRestoreArrayRead(fda, lStrainDiag, &strain_diag));
877 PetscCall(DMDAVecRestoreArrayRead(fda, lStrainOff, &strain_off));
878 PetscCall(DMDAVecRestoreArray(da, lLM, &lm));
879 PetscCall(DMDAVecRestoreArray(da, lMM, &mm));
880 PetscCall(DMDAVecRestoreArray(fda, user->lUcat, &ucat));
881
882 // --- 3. Average the two contractions, then divide ---------------------------
883 // Both sums are formed before the division. Averaging the pointwise quotients
884 // instead would not be the least-squares coefficient Lilly's closure defines.
885 PetscCall(PicurvSpatialRatioAverage(user, &plan, lLM, lMM, lWallMask, average_direction,
886 PetscObjectComm((PetscObject)da), lCsq, NULL));
887
888 // --- 4. Limit the coefficient and record what limiting cost -----------------
889 PetscCall(DMDAVecGetArrayRead(da, lLM, &lm));
890 PetscCall(DMDAVecGetArrayRead(da, lMM, &mm));
891 PetscCall(DMDAVecGetArrayRead(da, lCsq, &csq));
892 PetscCall(DMDAVecGetArray(da, user->CS, &cs_out));
893
894 for (PetscInt k = plan.start[2]; k < plan.end[2]; k++)
895 for (PetscInt j = plan.start[1]; j < plan.end[1]; j++)
896 for (PetscInt i = plan.start[0]; i < plan.end[0]; i++) {
897 PetscBool limited = PETSC_FALSE;
898 PetscReal weight, raw;
899
900 if (!SpatialTargetPlanMaskAllows(&plan, nvert[k][j][i])) {
901 cs_out[k][j][i] = 0.0;
902 continue;
903 }
904
905 weight = 1.0 / aj[k][j][i];
906 raw = csq[k][j][i];
907
908 cs_out[k][j][i] = ClipModelCoefficient(raw, config, &limited);
909
910 // The two contraction fields already carry the cell volume from the loop
911 // above, so they are summed as they stand rather than weighted twice.
912 diagnostics.contraction_lm += lm[k][j][i];
913 diagnostics.contraction_mm += mm[k][j][i];
914 diagnostics.fluid_volume += weight;
915 if (raw < 0.0) diagnostics.backscatter_volume += weight;
916 if (limited) diagnostics.limited_volume += weight;
917 }
918
919 PetscCall(DMDAVecRestoreArrayRead(da, lLM, &lm));
920 PetscCall(DMDAVecRestoreArrayRead(da, lMM, &mm));
921 PetscCall(DMDAVecRestoreArrayRead(da, lCsq, &csq));
922 PetscCall(DMDAVecRestoreArray(da, user->CS, &cs_out));
923 PetscCall(DMDAVecRestoreArrayRead(fda, user->lCsi, &csi));
924 PetscCall(DMDAVecRestoreArrayRead(fda, user->lEta, &eta));
925 PetscCall(DMDAVecRestoreArrayRead(fda, user->lZet, &zet));
926 PetscCall(DMDAVecRestoreArrayRead(da, user->lNvert, &nvert));
927 PetscCall(DMDAVecRestoreArrayRead(da, user->lAj, &aj));
928
929 PetscCall(VecDestroy(&lStrainDiag));
930 PetscCall(VecDestroy(&lStrainOff));
931 PetscCall(VecDestroy(&lStrainAbs));
932 PetscCall(VecDestroy(&lLM));
933 PetscCall(VecDestroy(&lMM));
934 PetscCall(VecDestroy(&lCsq));
935 if (lWallMask) PetscCall(VecDestroy(&lWallMask));
936
937 user->les_diagnostics = diagnostics;
938 PetscCall(FinalizeSmagorinskyConstantField(user));
939
941 PetscFunctionReturn(0);
942}
double ApplyLESTestFilter(LESTestFilterKernel kernel, double values[3][3][3], double weights[3][3][3])
Applies a numerical "test filter" to a 3x3x3 stencil of data points.
Definition Filter.c:123
PetscErrorCode ApplyLESTestFilterSymTensor(LESTestFilterKernel kernel, SymTensor values[3][3][3], double weights[3][3][3], SymTensor *filtered)
Applies the test filter to all six components of a symmetric tensor.
Definition Filter.c:147
SymTensor GermanoModelTensor(PetscReal delta, PetscReal alpha, PetscReal strain_magnitude_filtered, SymTensor strain_filtered, SymTensor strain_product_filtered)
Implementation of GermanoModelTensor().
Definition les.c:374
PetscReal ClipModelCoefficient(PetscReal coefficient, const LESConfig *config, PetscBool *limited)
Implementation of ClipModelCoefficient().
Definition les.c:466
PetscErrorCode ComputeCellFilterWidth(LESFilterWidthModel model, PetscReal aj, Cmpnts csi, Cmpnts eta, Cmpnts zet, PetscReal *delta)
Implementation of ComputeCellFilterWidth().
Definition les.c:296
PetscErrorCode ResolveLESAveragingDirections(UserCtx *user, PetscBool direction[3])
Implementation of ResolveLESAveragingDirections().
Definition les.c:410
SymTensor LeonardStress(Cmpnts velocity_filtered, SymTensor velocity_product_filtered)
Implementation of LeonardStress().
Definition les.c:359
static SymTensor LESStrainAt(Cmpnts diagonal, Cmpnts offdiagonal)
Rebuilds a symmetric strain tensor from its two scratch storage vectors.
Definition les.c:690
static PetscErrorCode FinalizeSmagorinskyConstantField(UserCtx *user)
Synchronizes the completed Smagorinsky coefficient field.
Definition les.c:40
static PetscErrorCode ComputeStrainRateField(UserCtx *user, Cmpnts ***ucat, Vec strain_diagonal, Vec strain_offdiagonal, Vec strain_magnitude)
Fills the strain-rate scratch fields across the computed range and its halo.
Definition les.c:634
static PetscErrorCode BuildWallModelExclusionMask(UserCtx *user, Vec inclusion)
Marks the near-wall cells a wall model overwrites, so the procedure skips them.
Definition les.c:87
#define GLOBAL
Scope for global logging across all processes.
Definition logging.h:46
#define LOG_ALLOW(scope, level, fmt,...)
Logging macro that checks both the log level and whether the calling function is in the allowed-funct...
Definition logging.h:200
#define PROFILE_FUNCTION_END
Marks the end of a profiled code block.
Definition logging.h:894
@ LOG_DEBUG
Detailed debugging information.
Definition logging.h:32
#define PROFILE_FUNCTION_BEGIN
Marks the beginning of a profiled code block (typically a function).
Definition logging.h:885
PetscBool SpatialTargetPlanMaskAllows(const SpatialTargetPlan *plan, PetscReal nvert_value)
Reports whether a point passes the plan's mask.
PetscErrorCode PicurvSpatialRatioAverage(UserCtx *user, const SpatialTargetPlan *plan, Vec numerator, Vec denominator, Vec inclusion, const PetscBool average_direction[3], MPI_Comm comm, Vec ratio, PetscReal *scalar)
Averages two fields over a target domain and divides the results.
@ DYNAMIC_SMAGORINSKY
Definition variables.h:551
LESFilterWidthModel filter_width_model
How the grid filter width Delta is derived per cell.
Definition variables.h:634
PetscReal contraction_mm
Volume-weighted sum of M_ij M_ij over fluid cells.
Definition variables.h:656
LESTestFilterKernel test_filter_kernel
Discrete test-filter stencil.
Definition variables.h:635
Vec lNvert
Definition variables.h:1113
@ LES_TEST_FILTER_SIMPSON_IK
Definition variables.h:593
LESDiagnosticsState les_diagnostics
Pre-clipping statistics from the last dynamic update.
Definition variables.h:1157
PetscInt StartStep
Definition variables.h:869
PetscReal limited_volume
Fluid volume whose coefficient the clip modified.
Definition variables.h:658
PetscReal fluid_volume
Total fluid volume sampled.
Definition variables.h:659
PetscReal contraction_lm
Volume-weighted sum of L_ij M_ij over fluid cells.
Definition variables.h:655
PetscInt wallfunction
Enable wall functions on WALL faces.
Definition variables.h:985
PetscReal test_filter_width_ratio
Test-to-grid ratio per filtered direction; alpha is its square for the box, ratio^(4/3) for Simpson.
Definition variables.h:636
PetscInt step
Definition variables.h:867
Vec lUcat
Definition variables.h:1113
PetscInt les
Active LES closure; an LESModelType value.
Definition variables.h:984
PetscReal backscatter_volume
Fluid volume whose raw coefficient was negative.
Definition variables.h:657
Pre-clipping volume statistics captured by one dynamic-coefficient update.
Definition variables.h:654
The master context for the entire simulation.
Definition variables.h:859
Here is the call graph for this function:
Here is the caller graph for this function:

◆ ComputeEddyViscosityLES()

PetscErrorCode ComputeEddyViscosityLES ( UserCtx *  user)

Implementation of ComputeEddyViscosityLES().

Computes the turbulent eddy viscosity for one block.

Full API contract (arguments, ownership, side effects) is documented with the header declaration in include/les.h.

See also
ComputeEddyViscosityLES()

Definition at line 952 of file les.c.

953{
954 SimCtx *simCtx = user->simCtx;
955 const LESConfig *config = &simCtx->les_config;
956 DM da = user->da, fda = user->fda;
958 const PetscReal molecular_viscosity = 1.0 / simCtx->ren;
959
960 // The constant model's coefficient is a number from the configuration, so it needs
961 // no field, no ghost exchange, and no periodic synchronization.
962 const PetscBool coefficient_is_field = (PetscBool)(simCtx->les == DYNAMIC_SMAGORINSKY);
963 const PetscReal prescribed_coefficient = config->constant_cs * config->constant_cs;
964
965 Cmpnts ***ucat = NULL, ***csi = NULL, ***eta = NULL, ***zet = NULL;
966 PetscReal ***coefficient = NULL, ***nu_t = NULL, ***nvert = NULL, ***aj = NULL;
967
968 PetscFunctionBeginUser;
970
971 PetscCall(LESResolveDomain(user, &plan));
972
973 PetscCall(DMDAVecGetArrayRead(fda, user->lUcat, &ucat));
974 PetscCall(DMDAVecGetArrayRead(fda, user->lCsi, &csi));
975 PetscCall(DMDAVecGetArrayRead(fda, user->lEta, &eta));
976 PetscCall(DMDAVecGetArrayRead(fda, user->lZet, &zet));
977 PetscCall(DMDAVecGetArray(da, user->Nu_t, &nu_t));
978 PetscCall(DMDAVecGetArrayRead(da, user->lNvert, &nvert));
979 PetscCall(DMDAVecGetArrayRead(da, user->lAj, &aj));
980 if (coefficient_is_field) PetscCall(DMDAVecGetArrayRead(da, user->lCs, &coefficient));
981
982 for (PetscInt k = plan.start[2]; k < plan.end[2]; k++)
983 for (PetscInt j = plan.start[1]; j < plan.end[1]; j++)
984 for (PetscInt i = plan.start[0]; i < plan.end[0]; i++) {
985 Cmpnts dudx, dvdx, dwdx;
986 PetscReal strain_magnitude, delta, model_coefficient;
987
988 if (!SpatialTargetPlanMaskAllows(&plan, nvert[k][j][i])) {
989 nu_t[k][j][i] = 0.0;
990 continue;
991 }
992
993 PetscCall(ComputeVectorFieldDerivatives(user, i, j, k, (Cmpnts ***)ucat, &dudx, &dvdx, &dwdx));
994
995 if (simCtx->les == VREMAN) {
996 // Resolved per direction from the cell's own edges; no scalar width enters.
997 Cmpnts edges[3];
998 PetscCall(ComputeCellEdgeVectors(aj[k][j][i], csi[k][j][i], eta[k][j][i], zet[k][j][i], edges));
999 nu_t[k][j][i] = VremanEddyViscosity(dudx, dvdx, dwdx, edges, config->vreman_coefficient);
1000 continue;
1001 }
1002
1003 PetscCall(ComputeCellFilterWidth(config->filter_width_model, aj[k][j][i],
1004 csi[k][j][i], eta[k][j][i], zet[k][j][i], &delta));
1005 if (simCtx->les == WALE) {
1006 nu_t[k][j][i] = WALEEddyViscosity(dudx, dvdx, dwdx, delta, config->wale_coefficient);
1007 continue;
1008 }
1009
1010 PetscCall(StrainRateFromGradients(dudx, dvdx, dwdx, NULL, &strain_magnitude));
1011 model_coefficient = coefficient_is_field ? coefficient[k][j][i] : prescribed_coefficient;
1012
1013 nu_t[k][j][i] = EddyViscosityFromCoefficient(model_coefficient, delta, strain_magnitude,
1014 molecular_viscosity, config->min_viscosity_ratio);
1015 }
1016
1017 if (coefficient_is_field) PetscCall(DMDAVecRestoreArrayRead(da, user->lCs, &coefficient));
1018 PetscCall(DMDAVecRestoreArrayRead(fda, user->lUcat, &ucat));
1019 PetscCall(DMDAVecRestoreArrayRead(fda, user->lCsi, &csi));
1020 PetscCall(DMDAVecRestoreArrayRead(fda, user->lEta, &eta));
1021 PetscCall(DMDAVecRestoreArrayRead(fda, user->lZet, &zet));
1022 PetscCall(DMDAVecRestoreArray(da, user->Nu_t, &nu_t));
1023 PetscCall(DMDAVecRestoreArrayRead(da, user->lNvert, &nvert));
1024 PetscCall(DMDAVecRestoreArrayRead(da, user->lAj, &aj));
1025
1026 {
1027 const FieldId periodic_fields[] = {FIELD_ID_NU_T};
1028
1029 PetscCall(SynchronizePeriodicCellFields(user, 1, periodic_fields));
1030 PetscCall(UpdateLocalGhosts(user, FIELD_ID_NU_T));
1031 }
1032
1034 PetscFunctionReturn(0);
1035}
PetscErrorCode ComputeCellEdgeVectors(PetscReal ajc, Cmpnts csi, Cmpnts eta, Cmpnts zet, Cmpnts edges[3])
Computes a cell's edge vectors along its three grid directions.
Definition Metric.c:360
@ FIELD_ID_NU_T
PetscReal EddyViscosityFromCoefficient(PetscReal coefficient, PetscReal delta, PetscReal strain_magnitude, PetscReal molecular_viscosity, PetscReal min_viscosity_ratio)
Implementation of EddyViscosityFromCoefficient().
Definition les.c:497
PetscReal WALEEddyViscosity(Cmpnts dudx, Cmpnts dvdx, Cmpnts dwdx, PetscReal delta, PetscReal coefficient)
Implementation of WALEEddyViscosity().
Definition les.c:520
PetscReal VremanEddyViscosity(Cmpnts dudx, Cmpnts dvdx, Cmpnts dwdx, const Cmpnts edges[3], PetscReal coefficient)
Implementation of VremanEddyViscosity().
Definition les.c:557
@ VREMAN
Definition variables.h:552
@ WALE
Definition variables.h:553
PetscReal ren
Definition variables.h:906
PetscReal wale_coefficient
Model constant C_w for WALE (Nicoud & Ducros 1999).
Definition variables.h:633
PetscReal min_viscosity_ratio
Enforce nu + nu_t >= ratio * nu.
Definition variables.h:641
PetscReal constant_cs
Fixed Cs for CONSTANT_SMAGORINSKY; unused by the dynamic model.
Definition variables.h:631
PetscReal vreman_coefficient
Model constant c for VREMAN (Vreman 2004: 2.5 Cs^2).
Definition variables.h:632
Here is the call graph for this function:
Here is the caller graph for this function:

◆ LogLESDiagnostics()

PetscErrorCode LogLESDiagnostics ( UserCtx *  user)

Implementation of LogLESDiagnostics().

Appends one row of LES coefficient statistics to the run's log directory.

Full API contract (arguments, ownership, side effects) is documented with the header declaration in include/les.h.

See also
LogLESDiagnostics()

Definition at line 1045 of file les.c.

1046{
1047 SimCtx *simCtx = user->simCtx;
1048 const LESConfig *config = &simCtx->les_config;
1049 DM da = user->da, fda = user->fda;
1050 SpatialTargetPlan plan;
1051 MPI_Comm comm;
1052
1053 const PetscBool dynamic = (PetscBool)(simCtx->les == DYNAMIC_SMAGORINSKY);
1054 // Vreman and WALE carry no Smagorinsky coefficient, so the coefficient columns are
1055 // written as nan rather than as a number that would read like a measured Cs.
1056 const PetscBool has_coefficient =
1057 (PetscBool)(simCtx->les == DYNAMIC_SMAGORINSKY || simCtx->les == CONSTANT_SMAGORINSKY);
1058
1059 // Index 0..4: fluid volume, coefficient first and second moments, nu_t volume sum,
1060 // subgrid-energy volume sum. Index 5..6: the pre-clip fractions. Reduced together
1061 // so the diagnostic costs one collective rather than seven.
1062 PetscReal local_sum[7] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
1063 PetscReal global_sum[7];
1064 PetscReal local_max[2] = {0.0, 0.0}, global_max[2];
1065 PetscReal local_min_coefficient = PETSC_MAX_REAL, global_min_coefficient;
1066 PetscReal contraction[2], global_contraction[2];
1067
1068 Cmpnts ***ucat = NULL, ***csi = NULL, ***eta = NULL, ***zet = NULL;
1069 PetscReal ***coefficient = NULL, ***nu_t = NULL, ***nvert = NULL, ***aj = NULL;
1070
1071 PetscFunctionBeginUser;
1072
1073 if (!config->diagnostics_enabled) PetscFunctionReturn(0);
1074 if (config->diagnostics_cadence <= 0) PetscFunctionReturn(0);
1075 if (simCtx->step % config->diagnostics_cadence != 0) PetscFunctionReturn(0);
1076
1077 PetscCall(LESResolveDomain(user, &plan));
1078 PetscCall(PetscObjectGetComm((PetscObject)da, &comm));
1079
1080 PetscCall(DMDAVecGetArrayRead(fda, user->lUcat, &ucat));
1081 PetscCall(DMDAVecGetArrayRead(fda, user->lCsi, &csi));
1082 PetscCall(DMDAVecGetArrayRead(fda, user->lEta, &eta));
1083 PetscCall(DMDAVecGetArrayRead(fda, user->lZet, &zet));
1084 PetscCall(DMDAVecGetArrayRead(da, user->lNu_t, &nu_t));
1085 PetscCall(DMDAVecGetArrayRead(da, user->lNvert, &nvert));
1086 PetscCall(DMDAVecGetArrayRead(da, user->lAj, &aj));
1087 if (dynamic) PetscCall(DMDAVecGetArrayRead(da, user->lCs, &coefficient));
1088
1089 for (PetscInt k = plan.start[2]; k < plan.end[2]; k++)
1090 for (PetscInt j = plan.start[1]; j < plan.end[1]; j++)
1091 for (PetscInt i = plan.start[0]; i < plan.end[0]; i++) {
1092 Cmpnts dudx, dvdx, dwdx;
1093 PetscReal strain_magnitude, delta, weight, value;
1094
1095 if (!SpatialTargetPlanMaskAllows(&plan, nvert[k][j][i])) continue;
1096
1097 weight = 1.0 / aj[k][j][i];
1098 value = dynamic ? coefficient[k][j][i]
1099 : config->constant_cs * config->constant_cs;
1100
1101 PetscCall(ComputeVectorFieldDerivatives(user, i, j, k, (Cmpnts ***)ucat, &dudx, &dvdx, &dwdx));
1102 PetscCall(StrainRateFromGradients(dudx, dvdx, dwdx, NULL, &strain_magnitude));
1103 PetscCall(ComputeCellFilterWidth(config->filter_width_model, aj[k][j][i],
1104 csi[k][j][i], eta[k][j][i], zet[k][j][i], &delta));
1105
1106 local_sum[0] += weight;
1107 local_sum[3] += weight * nu_t[k][j][i];
1108 local_sum[4] += weight * SubgridKineticEnergy(config->yoshizawa_ci, delta, strain_magnitude);
1109 local_max[1] = PetscMax(local_max[1], nu_t[k][j][i]);
1110 if (has_coefficient) {
1111 local_sum[1] += weight * value;
1112 local_sum[2] += weight * value * value;
1113 local_max[0] = PetscMax(local_max[0], value);
1114 local_min_coefficient = PetscMin(local_min_coefficient, value);
1115 }
1116 }
1117
1118 if (dynamic) PetscCall(DMDAVecRestoreArrayRead(da, user->lCs, &coefficient));
1119 PetscCall(DMDAVecRestoreArrayRead(fda, user->lUcat, &ucat));
1120 PetscCall(DMDAVecRestoreArrayRead(fda, user->lCsi, &csi));
1121 PetscCall(DMDAVecRestoreArrayRead(fda, user->lEta, &eta));
1122 PetscCall(DMDAVecRestoreArrayRead(fda, user->lZet, &zet));
1123 PetscCall(DMDAVecRestoreArrayRead(da, user->lNu_t, &nu_t));
1124 PetscCall(DMDAVecRestoreArrayRead(da, user->lNvert, &nvert));
1125 PetscCall(DMDAVecRestoreArrayRead(da, user->lAj, &aj));
1126
1127 local_sum[5] = user->les_diagnostics.valid ? user->les_diagnostics.backscatter_volume : 0.0;
1128 local_sum[6] = user->les_diagnostics.valid ? user->les_diagnostics.limited_volume : 0.0;
1129 contraction[0] = user->les_diagnostics.valid ? user->les_diagnostics.contraction_lm : 0.0;
1130 contraction[1] = user->les_diagnostics.valid ? user->les_diagnostics.contraction_mm : 0.0;
1131
1132 PetscCallMPI(MPI_Allreduce(local_sum, global_sum, 7, MPIU_REAL, MPI_SUM, comm));
1133 PetscCallMPI(MPI_Allreduce(local_max, global_max, 2, MPIU_REAL, MPI_MAX, comm));
1134 PetscCallMPI(MPI_Allreduce(&local_min_coefficient, &global_min_coefficient, 1, MPIU_REAL, MPI_MIN, comm));
1135 PetscCallMPI(MPI_Allreduce(contraction, global_contraction, 2, MPIU_REAL, MPI_SUM, comm));
1136
1137 if (simCtx->rank == 0) {
1138 const PetscReal volume = (global_sum[0] > 0.0) ? global_sum[0] : 1.0;
1139 const PetscReal mean = global_sum[1] / volume;
1140 const PetscReal mean_square = global_sum[2] / volume;
1141 const PetscReal variance = PetscMax(mean_square - mean * mean, 0.0);
1142 const PetscReal molecular = 1.0 / simCtx->ren;
1143 const PetscReal effective = (global_contraction[1] > LES_EPSILON)
1144 ? (global_contraction[0] / global_contraction[1]) : 0.0;
1145 FILE *file = NULL;
1146
1147 // Reported as Cs rather than as the stored coefficient, because that is the
1148 // number with a recognised value: near 0.16 for decaying isotropic turbulence.
1149 // A negative average means the box is backscattering on balance, and is written
1150 // as a negative Cs rather than hidden behind a square root.
1151 const PetscReal cs_effective = (effective >= 0.0) ? PetscSqrtReal(effective)
1152 : -PetscSqrtReal(-effective);
1153 const PetscReal cs_mean = (mean >= 0.0) ? PetscSqrtReal(mean) : -PetscSqrtReal(-mean);
1154
1155 PetscCall(PicurvOpenDiagnosticsCsv(simCtx, "les_coefficient.csv",
1156 "step,time,cs_effective,cs_mean,coefficient_mean,"
1157 "coefficient_rms,coefficient_min,coefficient_max,"
1158 "nu_t_mean,nu_t_max,nu_t_over_nu_mean,k_sgs_mean,"
1159 "backscatter_fraction,limited_fraction,physical_time", &file));
1160 PetscReal physical_time = 0.0;
1161 PetscCall(PicurvPhysicalTime(simCtx, simCtx->ti, &physical_time));
1162 fprintf(file,
1163 "%d,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e\n",
1164 (int)simCtx->step, (double)simCtx->ti,
1165 has_coefficient ? (double)cs_effective : (double)NAN,
1166 has_coefficient ? (double)cs_mean : (double)NAN,
1167 has_coefficient ? (double)mean : (double)NAN,
1168 has_coefficient ? (double)PetscSqrtReal(variance) : (double)NAN,
1169 has_coefficient ? (double)global_min_coefficient : (double)NAN,
1170 has_coefficient ? (double)global_max[0] : (double)NAN,
1171 (double)(global_sum[3] / volume), (double)global_max[1],
1172 (double)(global_sum[3] / volume / molecular),
1173 (double)(global_sum[4] / volume),
1174 (double)(global_sum[5] / volume), (double)(global_sum[6] / volume),
1175 (double)physical_time);
1176 PetscCheck(fclose(file) == 0, PETSC_COMM_SELF, PETSC_ERR_FILE_WRITE,
1177 "Unable to close the LES diagnostics file.");
1178
1180 " LES coefficient: Cs_effective=%.4f, nu_t/nu (mean)=%.3e, backscatter=%.1f%%\n",
1181 (double)cs_effective, (double)(global_sum[3] / volume / molecular),
1182 (double)(100.0 * global_sum[5] / volume));
1183 }
1184
1185 PetscFunctionReturn(0);
1186}
PetscErrorCode PicurvPhysicalTime(const SimCtx *simCtx, PetscReal solver_time, PetscReal *physical)
Convert a solver time to physical seconds, t * L_ref / U_ref.
Definition io.c:3177
PetscReal SubgridKineticEnergy(PetscReal yoshizawa_ci, PetscReal delta, PetscReal strain_magnitude)
Implementation of SubgridKineticEnergy().
Definition les.c:595
@ LOG_INFO
Informational messages about program execution.
Definition logging.h:31
PetscErrorCode PicurvOpenDiagnosticsCsv(const SimCtx *simCtx, const char *filename, const char *header, FILE **file)
Opens a per-run diagnostics CSV in the run's analysis directory for appending.
Definition logging.c:3503
@ CONSTANT_SMAGORINSKY
Definition variables.h:550
PetscReal yoshizawa_ci
Yoshizawa constant for the reported SGS kinetic energy.
Definition variables.h:642
PetscMPIInt rank
Definition variables.h:862
PetscBool valid
Set once a dynamic update has populated this state.
Definition variables.h:660
Vec lNu_t
Definition variables.h:1156
PetscInt diagnostics_cadence
Steps between diagnostic rows.
Definition variables.h:644
PetscBool diagnostics_enabled
Append per-step coefficient statistics to the run log directory.
Definition variables.h:643
PetscReal ti
Definition variables.h:868
Here is the call graph for this function:
Here is the caller graph for this function:

Variable Documentation

◆ LES_EPSILON

const double LES_EPSILON = 1.0e-12
static

Definition at line 31 of file les.c.