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

C unit tests for the LES subgrid-scale closure. More...

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

Go to the source code of this file.

Functions

static PetscErrorCode TestSymTensorAlgebra (void)
 Tests the symmetric-tensor primitives against hand-computed values.
 
static PetscErrorCode TestStrainRateFromGradients (void)
 Tests strain-rate assembly and its magnitude for a known velocity gradient.
 
static PetscErrorCode TestFilterWidthModelsSeparateOnStretchedCell (void)
 Tests that the three filter-width models separate on an anisotropic cell.
 
static PetscErrorCode TestLeonardStressVanishesOnUniformFlow (void)
 Tests that the Leonard stress vanishes on a uniform velocity field.
 
static PetscErrorCode TestGermanoModelTensorOnConstantStrain (void)
 Tests the model tensor where the two filter terms provably coincide.
 
static PetscErrorCode TestGermanoModelTensorUsesFilteredProduct (void)
 Tests that the model tensor uses the filtered product, not the filtered factors.
 
static PetscErrorCode TestClipModelCoefficientModes (void)
 Tests each limiting mode, including the sign that carries backscatter.
 
static PetscErrorCode TestEddyViscosityFloorBoundsTotalViscosity (void)
 Tests eddy-viscosity assembly and the total-viscosity floor.
 
static PetscErrorCode TestSubgridKineticEnergy (void)
 Tests the Yoshizawa subgrid kinetic energy against its closed form.
 
static PetscErrorCode FillLocalScalar (UserCtx *user, Vec local, PetscReal value)
 Fills a ghosted local scalar field over the whole local array.
 
static PetscErrorCode SetInteriorScalar (UserCtx *user, Vec local, PetscReal value)
 Writes a scalar into every cell the closure treats as owned and interior.
 
static PetscErrorCode ReadLocalScalar (UserCtx *user, Vec local, PetscInt i, PetscInt j, PetscInt k, PetscReal *value)
 Reads one cell of a ghosted local scalar field.
 
static void DeclarePeriodicFaces (UserCtx *user, PetscBool xi, PetscBool eta, PetscBool zeta)
 Declares periodic boundary pairs on the axes a test wants homogeneous.
 
static PetscErrorCode TestAverageRatioLocalIsPointwise (void)
 Tests that an empty direction set reproduces the pointwise local model.
 
static PetscErrorCode TestAverageRatioDividesSummedFields (void)
 Tests that averaging divides summed numerators by summed denominators.
 
static PetscErrorCode TestAverageRatioRetainsUnaveragedDirection (void)
 Tests that averaging over two directions leaves a profile along the third.
 
static PetscErrorCode TestAverageRatioIgnoresPeriodicDuplicatePlanes (void)
 Tests that a spatial average ignores the periodic duplicate planes.
 
static PetscErrorCode TestHomogeneousAveragingDerivesPeriodicAxes (void)
 Tests that homogeneous averaging falls back to the block's periodic axes.
 
static PetscErrorCode TestAveragingModesSelectTheirOwnDirections (void)
 Tests that local and global averaging ignore the configured direction list.
 
static PetscErrorCode TestConstantModelNeedsNoCoefficientField (void)
 Tests that the constant model builds its viscosity without a coefficient field.
 
static PetscErrorCode TestDynamicProcedureRejectsConstantModel (void)
 Tests that the dynamic procedure refuses to run for the constant model.
 
static PetscErrorCode TestDynamicProcedureVanishesOnUniformFlow (void)
 Tests that the dynamic procedure returns a zero coefficient on uniform flow.
 
static PetscErrorCode TestDynamicProcedureGlobalAverageIsUniform (void)
 Tests the dynamic procedure end to end on a periodic sheared field.
 
int main (int argc, char **argv)
 Entry point for the LES closure suite.
 

Detailed Description

C unit tests for the LES subgrid-scale closure.

The suite is layered the way the module is. Pure kernels are checked against hand computed values with no PETSc objects at all; the Germano pieces are checked on fields whose answers are known in closed form; and the driver routines are checked on a small DMDA fixture.

Two of the cases exist because of specific defects and are worth naming. The "filtered-product" case pins the distinction between the filter of a product and the product of filtered factors, which the model tensor once collapsed. The "duplicate-plane" case pins the requirement that a spatial average count each physical cell once on a periodic block, where the two layout boundary planes are copies of interior cells.

Definition in file test_les.c.

Function Documentation

◆ TestSymTensorAlgebra()

static PetscErrorCode TestSymTensorAlgebra ( void  )
static

Tests the symmetric-tensor primitives against hand-computed values.

Definition at line 29 of file test_les.c.

30{
31 const Cmpnts v = {1.0, 2.0, 3.0};
32 const SymTensor a = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0};
33 const SymTensor b = {2.0, 0.0, 0.0, 2.0, 0.0, 2.0};
34 SymTensor outer, deviator, combined;
35
36 PetscFunctionBeginUser;
37
38 outer = SymTensorSelfOuter(v);
39 PetscCall(PicurvAssertRealNear(1.0, outer.xx, 1.0e-14, "self outer product should square the x component"));
40 PetscCall(PicurvAssertRealNear(2.0, outer.xy, 1.0e-14, "self outer product should cross x and y"));
41 PetscCall(PicurvAssertRealNear(6.0, outer.yz, 1.0e-14, "self outer product should cross y and z"));
42 PetscCall(PicurvAssertRealNear(9.0, outer.zz, 1.0e-14, "self outer product should square the z component"));
43
44 /* trace(a) = 1 + 4 + 6 = 11 */
45 PetscCall(PicurvAssertRealNear(11.0, SymTensorTrace(a), 1.0e-14, "trace should sum the diagonal"));
46
47 deviator = SymTensorDeviator(a);
48 PetscCall(PicurvAssertRealNear(0.0, SymTensorTrace(deviator), 1.0e-13,
49 "the deviator should be trace free"));
50 PetscCall(PicurvAssertRealNear(a.xy, deviator.xy, 1.0e-14,
51 "the deviator should leave off-diagonal components alone"));
52
53 /* a:b doubles the off-diagonal terms; b is diagonal, so a:b = 2*(1 + 4 + 6) = 22. */
54 PetscCall(PicurvAssertRealNear(22.0, SymTensorContract(a, b), 1.0e-13,
55 "contraction against a diagonal tensor should scale its trace"));
56 /* a:a = 1 + 16 + 36 + 2*(4 + 9 + 25) = 53 + 76 = 129 */
57 PetscCall(PicurvAssertRealNear(129.0, SymTensorNormSq(a), 1.0e-13,
58 "the squared norm must count off-diagonal components twice"));
59
60 combined = SymTensorCombine(2.0, a, -1.0, b);
61 PetscCall(PicurvAssertRealNear(0.0, combined.xx, 1.0e-14, "combine should scale and subtract"));
62 PetscCall(PicurvAssertRealNear(4.0, combined.xy, 1.0e-14, "combine should scale off-diagonal terms"));
63
64 PetscFunctionReturn(0);
65}
PetscReal SymTensorNormSq(SymTensor t)
Returns the squared Frobenius norm t_ij t_ij.
Definition les.c:250
SymTensor SymTensorCombine(PetscReal a, SymTensor x, PetscReal b, SymTensor y)
Forms the linear combination a*x + b*y.
Definition les.c:165
SymTensor SymTensorSelfOuter(Cmpnts v)
Forms a symmetric tensor from a vector's outer product with itself.
Definition les.c:144
SymTensor SymTensorDeviator(SymTensor t)
Removes the isotropic part, returning t_ij - (1/3) delta_ij t_kk.
Definition les.c:199
PetscReal SymTensorTrace(SymTensor t)
Returns the trace t_kk.
Definition les.c:186
PetscReal SymTensorContract(SymTensor a, SymTensor b)
Contracts two symmetric tensors as a_ij b_ij.
Definition les.c:217
PetscErrorCode PicurvAssertRealNear(PetscReal expected, PetscReal actual, PetscReal tol, const char *context)
Asserts that two real values agree within tolerance.
PetscReal xx
Definition variables.h:138
PetscReal yz
Definition variables.h:138
PetscReal zz
Definition variables.h:138
PetscReal xy
Definition variables.h:138
A 3D point or vector with PetscScalar components.
Definition variables.h:120
A symmetric second-order tensor stored by its six independent components.
Definition variables.h:137
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestStrainRateFromGradients()

static PetscErrorCode TestStrainRateFromGradients ( void  )
static

Tests strain-rate assembly and its magnitude for a known velocity gradient.

Definition at line 68 of file test_les.c.

69{
70 /* du/dy = 2 and every other derivative zero: pure shear. */
71 const Cmpnts dudx = {0.0, 2.0, 0.0};
72 const Cmpnts dvdx = {0.0, 0.0, 0.0};
73 const Cmpnts dwdx = {0.0, 0.0, 0.0};
74 SymTensor strain;
75 PetscReal magnitude;
76
77 PetscFunctionBeginUser;
78 PetscCall(StrainRateFromGradients(dudx, dvdx, dwdx, &strain, &magnitude));
79
80 PetscCall(PicurvAssertRealNear(1.0, strain.xy, 1.0e-14,
81 "pure shear should give S_xy = half the velocity gradient"));
82 PetscCall(PicurvAssertRealNear(0.0, strain.xx, 1.0e-14, "pure shear should have no normal strain"));
83 PetscCall(PicurvAssertRealNear(0.0, SymTensorTrace(strain), 1.0e-14,
84 "an incompressible gradient should give a trace-free strain"));
85 /* |S| = sqrt(2 S_ij S_ij) = sqrt(2 * 2 * 1) = 2 */
86 PetscCall(PicurvAssertRealNear(2.0, magnitude, 1.0e-13,
87 "the strain magnitude must count both off-diagonal entries"));
88
89 PetscFunctionReturn(0);
90}
PetscErrorCode StrainRateFromGradients(Cmpnts dudx, Cmpnts dvdx, Cmpnts dwdx, SymTensor *strain, PetscReal *magnitude)
Builds the strain-rate tensor and its magnitude from a velocity gradient.
Definition les.c:267
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestFilterWidthModelsSeparateOnStretchedCell()

static PetscErrorCode TestFilterWidthModelsSeparateOnStretchedCell ( void  )
static

Tests that the three filter-width models separate on an anisotropic cell.

Definition at line 93 of file test_les.c.

94{
95 /* Cartesian-aligned metrics for a cell of size 4 x 1 x 1: the face area vectors
96 are the products of the two spanning edges, and the Jacobian is 1/volume. */
97 const Cmpnts csi = {1.0, 0.0, 0.0};
98 const Cmpnts eta = {0.0, 4.0, 0.0};
99 const Cmpnts zet = {0.0, 0.0, 4.0};
100 const PetscReal aj = 0.25;
101 PetscReal cube_root = 0.0, geometric = 0.0, max_edge = 0.0;
102
103 PetscFunctionBeginUser;
104
105 PetscCall(ComputeCellFilterWidth(LES_FILTER_WIDTH_CUBE_ROOT_VOLUME, aj, csi, eta, zet, &cube_root));
106 PetscCall(ComputeCellFilterWidth(LES_FILTER_WIDTH_GEOMETRIC_MEAN, aj, csi, eta, zet, &geometric));
107 PetscCall(ComputeCellFilterWidth(LES_FILTER_WIDTH_MAX_EDGE, aj, csi, eta, zet, &max_edge));
108
109 /* Volume is 4, so the cube-root model gives 4^(1/3). */
110 PetscCall(PicurvAssertRealNear(PetscPowReal(4.0, 1.0 / 3.0), cube_root, 1.0e-12,
111 "the cube-root model should depend only on cell volume"));
112 PetscCall(PicurvAssertRealNear(PetscPowReal(4.0, 1.0 / 3.0), geometric, 1.0e-12,
113 "the geometric mean of the extents should recover the same volume"));
114 PetscCall(PicurvAssertRealNear(4.0, max_edge, 1.0e-12,
115 "the max-edge model should report the long direction"));
116 PetscCall(PicurvAssertBool((PetscBool)(max_edge > cube_root),
117 "a stretched cell should make the max-edge width the larger of the two"));
118
119 PetscFunctionReturn(0);
120}
PetscErrorCode ComputeCellFilterWidth(LESFilterWidthModel model, PetscReal aj, Cmpnts csi, Cmpnts eta, Cmpnts zet, PetscReal *delta)
Computes one cell's grid filter width under the selected width model.
Definition les.c:296
PetscErrorCode PicurvAssertBool(PetscBool value, const char *context)
Asserts that one boolean condition is true.
@ LES_FILTER_WIDTH_GEOMETRIC_MEAN
Definition variables.h:584
@ LES_FILTER_WIDTH_CUBE_ROOT_VOLUME
Definition variables.h:583
@ LES_FILTER_WIDTH_MAX_EDGE
Definition variables.h:585
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestLeonardStressVanishesOnUniformFlow()

static PetscErrorCode TestLeonardStressVanishesOnUniformFlow ( void  )
static

Tests that the Leonard stress vanishes on a uniform velocity field.

Definition at line 123 of file test_les.c.

124{
125 const Cmpnts velocity = {1.5, -0.5, 2.0};
126 const SymTensor product = SymTensorSelfOuter(velocity);
127 SymTensor leonard;
128
129 PetscFunctionBeginUser;
130 /* Filtering a constant returns the constant, so both filtered inputs are exact. */
131 leonard = LeonardStress(velocity, product);
132
133 PetscCall(PicurvAssertRealNear(0.0, SymTensorNormSq(leonard), 1.0e-24,
134 "a uniform field carries no stress between the two filter widths"));
135 PetscFunctionReturn(0);
136}
SymTensor LeonardStress(Cmpnts velocity_filtered, SymTensor velocity_product_filtered)
Forms the Leonard stress L_ij = (u_i u_j)^ - u^_i u^_j.
Definition les.c:342
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestGermanoModelTensorOnConstantStrain()

static PetscErrorCode TestGermanoModelTensorOnConstantStrain ( void  )
static

Tests the model tensor where the two filter terms provably coincide.

On a constant strain field the test filter is the identity, so the filter of the product equals the product of the filtered factors and the model tensor collapses to a closed form, -2 Delta^2 (alpha - 1) |S| S_ij, made trace free. This is the one configuration in which the corrected tensor and the collapsed expression that preceded it agree, which is what makes it a clean analytic anchor.

Definition at line 147 of file test_les.c.

148{
149 const PetscReal delta = 0.5;
150 const PetscReal alpha = 4.0;
151 Cmpnts dudx = {0.0, 2.0, 0.0}, dvdx = {0.0, 0.0, 0.0}, dwdx = {0.0, 0.0, 0.0};
152 SymTensor strain, product, model, expected;
153 PetscReal magnitude;
154
155 PetscFunctionBeginUser;
156 PetscCall(StrainRateFromGradients(dudx, dvdx, dwdx, &strain, &magnitude));
157
158 product = SymTensorCombine(magnitude, strain, 0.0, strain);
159 model = GermanoModelTensor(delta, alpha, magnitude, strain, product);
160
161 expected = SymTensorDeviator(SymTensorCombine(-2.0 * delta * delta * (alpha - 1.0) * magnitude,
162 strain, 0.0, strain));
163
164 PetscCall(PicurvAssertRealNear(expected.xy, model.xy, 1.0e-13,
165 "constant strain should give the closed-form model tensor"));
166 PetscCall(PicurvAssertRealNear(0.0, SymTensorTrace(model), 1.0e-13,
167 "the model tensor must be trace free"));
168 PetscFunctionReturn(0);
169}
SymTensor GermanoModelTensor(PetscReal delta, PetscReal alpha, PetscReal strain_magnitude_filtered, SymTensor strain_filtered, SymTensor strain_product_filtered)
Forms the deviatoric Germano model tensor M_ij.
Definition les.c:357
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestGermanoModelTensorUsesFilteredProduct()

static PetscErrorCode TestGermanoModelTensorUsesFilteredProduct ( void  )
static

Tests that the model tensor uses the filtered product, not the filtered factors.

Builds a stencil whose strain magnitude varies across it, so the test filter of |S| S_ij genuinely differs from |S|^ S^_ij. The model tensor must be built from the former. Collapsing it onto the latter yields -2 Delta^2 (alpha - 1) |S|^ S^_ij, which is well scaled, responds to the flow, and is not the Germano-Lilly tensor; this case fails if the two are ever conflated again.

Definition at line 180 of file test_les.c.

181{
183 const PetscReal delta = 1.0;
184 const PetscReal alpha = 4.0;
185 double magnitude_stencil[3][3][3], weights[3][3][3];
186 SymTensor strain_stencil[3][3][3], product_stencil[3][3][3];
187 SymTensor strain_filtered, product_filtered, model, collapsed;
188 PetscReal magnitude_filtered, separation;
189
190 PetscFunctionBeginUser;
191
192 for (PetscInt k = 0; k < 3; ++k)
193 for (PetscInt j = 0; j < 3; ++j)
194 for (PetscInt i = 0; i < 3; ++i) {
195 /* A strain that grows across the stencil: filtering no longer commutes with
196 forming the product, which is exactly the regime the identity relies on. */
197 const PetscReal shear = 1.0 + 0.5 * (PetscReal)(i + j + k);
198 Cmpnts dudx = {0.0, 2.0 * shear, 0.0};
199 Cmpnts dvdx = {0.0, 0.0, 0.0};
200 Cmpnts dwdx = {0.0, 0.0, 0.0};
201 SymTensor strain;
202 PetscReal magnitude;
203
204 PetscCall(StrainRateFromGradients(dudx, dvdx, dwdx, &strain, &magnitude));
205 strain_stencil[k][j][i] = strain;
206 magnitude_stencil[k][j][i] = magnitude;
207 product_stencil[k][j][i] = SymTensorCombine(magnitude, strain, 0.0, strain);
208 weights[k][j][i] = 1.0;
209 }
210
211 magnitude_filtered = ApplyLESTestFilter(kernel, magnitude_stencil, weights);
212 PetscCall(ApplyLESTestFilterSymTensor(kernel, strain_stencil, weights, &strain_filtered));
213 PetscCall(ApplyLESTestFilterSymTensor(kernel, product_stencil, weights, &product_filtered));
214
215 /* The filter of the product must not equal the product of the filtered factors. */
216 separation = PetscAbsReal(product_filtered.xy - magnitude_filtered * strain_filtered.xy);
217 PetscCall(PicurvAssertBool((PetscBool)(separation > 1.0e-6),
218 "a varying strain field must separate the filtered product from "
219 "the product of the filtered factors"));
220
221 model = GermanoModelTensor(delta, alpha, magnitude_filtered, strain_filtered, product_filtered);
222 collapsed = SymTensorDeviator(SymTensorCombine(-2.0 * delta * delta * (alpha - 1.0) * magnitude_filtered,
223 strain_filtered, 0.0, strain_filtered));
224
225 PetscCall(PicurvAssertBool((PetscBool)(PetscAbsReal(model.xy - collapsed.xy) > 1.0e-6),
226 "the model tensor must be built from the filtered product, not from "
227 "the product of separately filtered quantities"));
228 PetscFunctionReturn(0);
229}
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
LESTestFilterKernel
Selects the discrete test-filter kernel used by the dynamic procedure.
Definition variables.h:593
@ LES_TEST_FILTER_VOLUME_WEIGHTED_BOX
Definition variables.h:594
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestClipModelCoefficientModes()

static PetscErrorCode TestClipModelCoefficientModes ( void  )
static

Tests each limiting mode, including the sign that carries backscatter.

Definition at line 232 of file test_les.c.

233{
234 LESConfig config;
235 PetscBool limited = PETSC_FALSE;
236
237 PetscFunctionBeginUser;
238 PetscCall(LESConfigSetDefaults(&config));
239 config.max_cs = 0.2; /* ceiling on Cs, so the coefficient ceiling is 0.04 */
240
241 config.clip_mode = LES_CLIP_CLAMP;
242 PetscCall(PicurvAssertRealNear(0.0, ClipModelCoefficient(-0.01, &config, &limited), 1.0e-14,
243 "clamping should discard a negative coefficient"));
244 PetscCall(PicurvAssertBool(limited, "clamping a negative coefficient counts as limiting"));
245 PetscCall(PicurvAssertRealNear(0.04, ClipModelCoefficient(1.0, &config, &limited), 1.0e-14,
246 "clamping should cap the coefficient at max_cs squared"));
247 PetscCall(PicurvAssertRealNear(0.01, ClipModelCoefficient(0.01, &config, &limited), 1.0e-14,
248 "clamping should pass an admissible coefficient through"));
249 PetscCall(PicurvAssertBool((PetscBool)(!limited), "an admissible coefficient is not limited"));
250
252 PetscCall(PicurvAssertRealNear(0.0, ClipModelCoefficient(-0.01, &config, &limited), 1.0e-14,
253 "clip_negative should discard a negative coefficient"));
254 PetscCall(PicurvAssertRealNear(1.0, ClipModelCoefficient(1.0, &config, &limited), 1.0e-14,
255 "clip_negative should impose no ceiling"));
256
257 config.clip_mode = LES_CLIP_NONE;
258 PetscCall(PicurvAssertRealNear(-0.01, ClipModelCoefficient(-0.01, &config, &limited), 1.0e-14,
259 "no limiting should preserve the sign that carries backscatter"));
260 PetscCall(PicurvAssertBool((PetscBool)(!limited), "an unlimited coefficient is not modified"));
261 PetscFunctionReturn(0);
262}
PetscReal ClipModelCoefficient(PetscReal coefficient, const LESConfig *config, PetscBool *limited)
Applies the configured admissible range to one model coefficient.
Definition les.c:449
PetscErrorCode LESConfigSetDefaults(LESConfig *config)
Installs the shipped defaults for every LES closure parameter.
Definition setup.c:180
@ LES_CLIP_CLIP_NEGATIVE
Definition variables.h:621
@ LES_CLIP_CLAMP
Definition variables.h:620
@ LES_CLIP_NONE
Definition variables.h:622
PetscReal max_cs
Ceiling on Cs under LES_CLIP_CLAMP.
Definition variables.h:641
LESClipMode clip_mode
Admissible range for the coefficient.
Definition variables.h:640
Every user-selectable parameter of the LES closure.
Definition variables.h:632
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestEddyViscosityFloorBoundsTotalViscosity()

static PetscErrorCode TestEddyViscosityFloorBoundsTotalViscosity ( void  )
static

Tests eddy-viscosity assembly and the total-viscosity floor.

Definition at line 265 of file test_les.c.

266{
267 const PetscReal molecular = 0.01;
268
269 PetscFunctionBeginUser;
270
271 /* C = 0.04, Delta = 2, |S| = 3 -> nu_t = 0.04 * 4 * 3 = 0.48 */
272 PetscCall(PicurvAssertRealNear(0.48, EddyViscosityFromCoefficient(0.04, 2.0, 3.0, molecular, 0.0),
273 1.0e-13, "eddy viscosity should be the coefficient times Delta^2 |S|"));
274
275 /* A backscattering coefficient may drive nu_t negative, but only down to the point
276 where the total viscosity would vanish. */
277 PetscCall(PicurvAssertRealNear(-molecular,
278 EddyViscosityFromCoefficient(-100.0, 2.0, 3.0, molecular, 0.0),
279 1.0e-14, "the floor should stop nu_t at minus the molecular value"));
280 PetscCall(PicurvAssertRealNear(-0.5 * molecular,
281 EddyViscosityFromCoefficient(-100.0, 2.0, 3.0, molecular, 0.5),
282 1.0e-14, "a ratio of one half should keep half the molecular viscosity"));
283 PetscCall(PicurvAssertRealNear(-0.006,
284 EddyViscosityFromCoefficient(-0.0005, 2.0, 3.0, molecular, 0.0),
285 1.0e-14, "a mild backscatter should survive the floor untouched"));
286 PetscFunctionReturn(0);
287}
PetscReal EddyViscosityFromCoefficient(PetscReal coefficient, PetscReal delta, PetscReal strain_magnitude, PetscReal molecular_viscosity, PetscReal min_viscosity_ratio)
Builds the eddy viscosity from a model coefficient.
Definition les.c:480
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestSubgridKineticEnergy()

static PetscErrorCode TestSubgridKineticEnergy ( void  )
static

Tests the Yoshizawa subgrid kinetic energy against its closed form.

Definition at line 290 of file test_les.c.

291{
292 PetscFunctionBeginUser;
293 /* 2 * 0.09 * 4 * 9 = 6.48 */
294 PetscCall(PicurvAssertRealNear(6.48, SubgridKineticEnergy(0.09, 2.0, 3.0), 1.0e-13,
295 "the Yoshizawa relation should scale with Delta^2 |S|^2"));
296 PetscFunctionReturn(0);
297}
PetscReal SubgridKineticEnergy(PetscReal yoshizawa_ci, PetscReal delta, PetscReal strain_magnitude)
Returns the modelled subgrid kinetic energy at a cell.
Definition les.c:503
Here is the call graph for this function:
Here is the caller graph for this function:

◆ FillLocalScalar()

static PetscErrorCode FillLocalScalar ( UserCtx user,
Vec  local,
PetscReal  value 
)
static

Fills a ghosted local scalar field over the whole local array.

Writes the halo as well as the owned cells, so a test can poison the layout boundary planes and observe whether an averaging routine counts them.

Definition at line 309 of file test_les.c.

310{
311 PetscFunctionBeginUser;
312 PetscCall(VecSet(local, value));
313 (void)user;
314 PetscFunctionReturn(0);
315}
Here is the caller graph for this function:

◆ SetInteriorScalar()

static PetscErrorCode SetInteriorScalar ( UserCtx user,
Vec  local,
PetscReal  value 
)
static

Writes a scalar into every cell the closure treats as owned and interior.

Definition at line 320 of file test_les.c.

321{
322 DMDALocalInfo info;
323 PetscReal ***array = NULL;
324 PetscInt xs, xe, ys, ye, zs, ze;
325
326 PetscFunctionBeginUser;
327 PetscCall(DMDAGetLocalInfo(user->da, &info));
328 xs = (info.xs == 0) ? 1 : info.xs;
329 ys = (info.ys == 0) ? 1 : info.ys;
330 zs = (info.zs == 0) ? 1 : info.zs;
331 xe = (info.xs + info.xm == info.mx) ? info.mx - 1 : info.xs + info.xm;
332 ye = (info.ys + info.ym == info.my) ? info.my - 1 : info.ys + info.ym;
333 ze = (info.zs + info.zm == info.mz) ? info.mz - 1 : info.zs + info.zm;
334
335 PetscCall(DMDAVecGetArray(user->da, local, &array));
336 for (PetscInt k = zs; k < ze; ++k)
337 for (PetscInt j = ys; j < ye; ++j)
338 for (PetscInt i = xs; i < xe; ++i) array[k][j][i] = value;
339 PetscCall(DMDAVecRestoreArray(user->da, local, &array));
340 PetscFunctionReturn(0);
341}
Here is the caller graph for this function:

◆ ReadLocalScalar()

static PetscErrorCode ReadLocalScalar ( UserCtx user,
Vec  local,
PetscInt  i,
PetscInt  j,
PetscInt  k,
PetscReal *  value 
)
static

Reads one cell of a ghosted local scalar field.

Definition at line 344 of file test_les.c.

346{
347 PetscReal ***array = NULL;
348
349 PetscFunctionBeginUser;
350 PetscCall(DMDAVecGetArrayRead(user->da, local, &array));
351 *value = array[k][j][i];
352 PetscCall(DMDAVecRestoreArrayRead(user->da, local, &array));
353 PetscFunctionReturn(0);
354}
Here is the caller graph for this function:

◆ DeclarePeriodicFaces()

static void DeclarePeriodicFaces ( UserCtx user,
PetscBool  xi,
PetscBool  eta,
PetscBool  zeta 
)
static

Declares periodic boundary pairs on the axes a test wants homogeneous.

The shared fixture builds a periodic DMDA from the SimCtx flags but leaves the boundary face configuration untouched. The periodic field synchronization reads the faces rather than the flags, so a test that synchronizes has to declare them. The closure's own periodicity questions are answered from the flags, so tests that only ask those do not call this.

Definition at line 365 of file test_les.c.

366{
367 const BCFace negative[3] = {BC_FACE_NEG_X, BC_FACE_NEG_Y, BC_FACE_NEG_Z};
368 const BCFace positive[3] = {BC_FACE_POS_X, BC_FACE_POS_Y, BC_FACE_POS_Z};
369 const PetscBool selected[3] = {xi, eta, zeta};
370
371 for (PetscInt axis = 0; axis < 3; ++axis) {
372 if (!selected[axis]) continue;
373 user->boundary_faces[negative[axis]].mathematical_type = PERIODIC;
374 user->boundary_faces[positive[axis]].mathematical_type = PERIODIC;
375 }
376}
@ PERIODIC
Definition variables.h:322
BoundaryFaceConfig boundary_faces[6]
Definition variables.h:1096
BCType mathematical_type
Definition variables.h:398
BCFace
Identifies the six logical faces of a structured computational block.
Definition variables.h:291
@ BC_FACE_NEG_X
Definition variables.h:292
@ BC_FACE_POS_Z
Definition variables.h:294
@ BC_FACE_POS_Y
Definition variables.h:293
@ BC_FACE_NEG_Z
Definition variables.h:294
@ BC_FACE_POS_X
Definition variables.h:292
@ BC_FACE_NEG_Y
Definition variables.h:293
Here is the caller graph for this function:

◆ TestAverageRatioLocalIsPointwise()

static PetscErrorCode TestAverageRatioLocalIsPointwise ( void  )
static

Tests that an empty direction set reproduces the pointwise local model.

Definition at line 379 of file test_les.c.

380{
381 SimCtx *simCtx = NULL;
382 UserCtx *user = NULL;
383 Vec numerator, denominator, ratio;
385 PetscReal ***num = NULL;
386 PetscReal value = 0.0;
387 const PetscBool none[3] = {PETSC_FALSE, PETSC_FALSE, PETSC_FALSE};
388
389 PetscFunctionBeginUser;
390 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 6, 6, 6));
391 PetscCall(VecSet(user->lNvert, 0.0));
392 PetscCall(VecSet(user->lAj, 1.0));
393
394 PetscCall(VecDuplicate(user->lNvert, &numerator));
395 PetscCall(VecDuplicate(user->lNvert, &denominator));
396 PetscCall(VecDuplicate(user->lNvert, &ratio));
397 PetscCall(FillLocalScalar(user, denominator, 4.0));
398 PetscCall(FillLocalScalar(user, ratio, 0.0));
399 PetscCall(FillLocalScalar(user, numerator, 0.0));
400
401 /* A numerator that varies cell by cell, so a pointwise result cannot be confused
402 with an averaged one. */
403 PetscCall(DMDAVecGetArray(user->da, numerator, &num));
404 for (PetscInt k = 1; k < 6; ++k)
405 for (PetscInt j = 1; j < 6; ++j)
406 for (PetscInt i = 1; i < 6; ++i) num[k][j][i] = (PetscReal)(i + j + k);
407 PetscCall(DMDAVecRestoreArray(user->da, numerator, &num));
408
409 PetscCall(SpatialTargetPlanCreate(user, FIELD_ID_CS,
411 PetscCall(PicurvSpatialRatioAverage(user, &plan, numerator, denominator, NULL, none,
412 PETSC_COMM_WORLD, ratio, NULL));
413
414 PetscCall(ReadLocalScalar(user, ratio, 2, 3, 4, &value));
415 PetscCall(PicurvAssertRealNear((2.0 + 3.0 + 4.0) / 4.0, value, 1.0e-13,
416 "with no averaging direction the quotient should stay pointwise"));
417
418 PetscCall(VecDestroy(&numerator));
419 PetscCall(VecDestroy(&denominator));
420 PetscCall(VecDestroy(&ratio));
421 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
422 PetscFunctionReturn(0);
423}
@ FIELD_ID_CS
@ 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.
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.
Resolved iteration domain for one field on one block.
static PetscErrorCode FillLocalScalar(UserCtx *user, Vec local, PetscReal value)
Fills a ghosted local scalar field over the whole local array.
Definition test_les.c:309
static PetscErrorCode ReadLocalScalar(UserCtx *user, Vec local, PetscInt i, PetscInt j, PetscInt k, PetscReal *value)
Reads one cell of a ghosted local scalar field.
Definition test_les.c:344
PetscErrorCode PicurvCreateMinimalContexts(SimCtx **simCtx_out, UserCtx **user_out, PetscInt mx, PetscInt my, PetscInt mz)
Builds minimal SimCtx and UserCtx fixtures for C unit tests.
PetscErrorCode PicurvDestroyMinimalContexts(SimCtx **simCtx_ptr, UserCtx **user_ptr)
Destroys minimal SimCtx/UserCtx fixtures and all owned PETSc objects.
Vec lNvert
Definition variables.h:1111
The master context for the entire simulation.
Definition variables.h:858
User-defined context containing data specific to a single computational grid level.
Definition variables.h:1071
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestAverageRatioDividesSummedFields()

static PetscErrorCode TestAverageRatioDividesSummedFields ( void  )
static

Tests that averaging divides summed numerators by summed denominators.

The distinction matters: the mean of the quotients differs from the quotient of the means, and Lilly's least-squares closure calls for the latter. Separating them needs a denominator that varies too. Three cells carry 1/1 and two carry 6/2, so every pointwise quotient is 1 or 3 and their mean is 9/5, while the ratio of the sums is 15/7. Only the second answer can come from averaging the two fields first.

Definition at line 434 of file test_les.c.

435{
436 SimCtx *simCtx = NULL;
437 UserCtx *user = NULL;
438 Vec numerator, denominator, ratio;
440 PetscReal ***num = NULL, ***den = NULL;
441 PetscReal value = 0.0;
442 const PetscBool all[3] = {PETSC_TRUE, PETSC_TRUE, PETSC_TRUE};
443
444 PetscFunctionBeginUser;
445 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 6, 6, 6));
446 PetscCall(VecSet(user->lNvert, 0.0));
447 PetscCall(VecSet(user->lAj, 1.0));
448
449 PetscCall(VecDuplicate(user->lNvert, &numerator));
450 PetscCall(VecDuplicate(user->lNvert, &denominator));
451 PetscCall(VecDuplicate(user->lNvert, &ratio));
452 PetscCall(FillLocalScalar(user, numerator, 0.0));
453 PetscCall(FillLocalScalar(user, denominator, 0.0));
454 PetscCall(FillLocalScalar(user, ratio, 0.0));
455
456 PetscCall(DMDAVecGetArray(user->da, numerator, &num));
457 PetscCall(DMDAVecGetArray(user->da, denominator, &den));
458 for (PetscInt k = 1; k < 6; ++k)
459 for (PetscInt j = 1; j < 6; ++j)
460 for (PetscInt i = 1; i < 6; ++i) {
461 const PetscBool low = (PetscBool)(i < 4);
462
463 num[k][j][i] = low ? 1.0 : 6.0;
464 den[k][j][i] = low ? 1.0 : 2.0;
465 }
466 PetscCall(DMDAVecRestoreArray(user->da, numerator, &num));
467 PetscCall(DMDAVecRestoreArray(user->da, denominator, &den));
468
469 PetscCall(SpatialTargetPlanCreate(user, FIELD_ID_CS,
471 PetscCall(PicurvSpatialRatioAverage(user, &plan, numerator, denominator, NULL, all,
472 PETSC_COMM_WORLD, ratio, NULL));
473
474 /* 15/7, the ratio of the sums. Averaging the pointwise quotients would give 9/5. */
475 PetscCall(ReadLocalScalar(user, ratio, 2, 3, 4, &value));
476 PetscCall(PicurvAssertRealNear(15.0 / 7.0, value, 1.0e-13,
477 "the global average should divide summed numerators by summed "
478 "denominators, not average the pointwise quotients"));
479 PetscCall(ReadLocalScalar(user, ratio, 4, 2, 1, &value));
480 PetscCall(PicurvAssertRealNear(15.0 / 7.0, value, 1.0e-13,
481 "a global average should be the same number in every cell"));
482
483 PetscCall(VecDestroy(&numerator));
484 PetscCall(VecDestroy(&denominator));
485 PetscCall(VecDestroy(&ratio));
486 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
487 PetscFunctionReturn(0);
488}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestAverageRatioRetainsUnaveragedDirection()

static PetscErrorCode TestAverageRatioRetainsUnaveragedDirection ( void  )
static

Tests that averaging over two directions leaves a profile along the third.

Definition at line 493 of file test_les.c.

494{
495 SimCtx *simCtx = NULL;
496 UserCtx *user = NULL;
497 Vec numerator, denominator, ratio;
499 PetscReal ***num = NULL;
500 PetscReal at_low = 0.0, at_high = 0.0;
501 /* Average over xi and zeta, retain eta: the channel-flow arrangement. */
502 const PetscBool ik[3] = {PETSC_TRUE, PETSC_FALSE, PETSC_TRUE};
503
504 PetscFunctionBeginUser;
505 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 6, 6, 6));
506 PetscCall(VecSet(user->lNvert, 0.0));
507 PetscCall(VecSet(user->lAj, 1.0));
508
509 PetscCall(VecDuplicate(user->lNvert, &numerator));
510 PetscCall(VecDuplicate(user->lNvert, &denominator));
511 PetscCall(VecDuplicate(user->lNvert, &ratio));
512 PetscCall(FillLocalScalar(user, numerator, 0.0));
513 PetscCall(FillLocalScalar(user, denominator, 0.0));
514 PetscCall(FillLocalScalar(user, ratio, 0.0));
515 PetscCall(SetInteriorScalar(user, denominator, 1.0));
516
517 /* Depends on eta alone, so averaging over xi and zeta must leave it untouched. */
518 PetscCall(DMDAVecGetArray(user->da, numerator, &num));
519 for (PetscInt k = 1; k < 6; ++k)
520 for (PetscInt j = 1; j < 6; ++j)
521 for (PetscInt i = 1; i < 6; ++i) num[k][j][i] = (PetscReal)j;
522 PetscCall(DMDAVecRestoreArray(user->da, numerator, &num));
523
524 PetscCall(SpatialTargetPlanCreate(user, FIELD_ID_CS,
526 PetscCall(PicurvSpatialRatioAverage(user, &plan, numerator, denominator, NULL, ik,
527 PETSC_COMM_WORLD, ratio, NULL));
528
529 PetscCall(ReadLocalScalar(user, ratio, 2, 1, 3, &at_low));
530 PetscCall(ReadLocalScalar(user, ratio, 4, 5, 2, &at_high));
531 PetscCall(PicurvAssertRealNear(1.0, at_low, 1.0e-13,
532 "averaging over xi and zeta should preserve the eta profile"));
533 PetscCall(PicurvAssertRealNear(5.0, at_high, 1.0e-13,
534 "each retained eta plane should keep its own average"));
535
536 PetscCall(VecDestroy(&numerator));
537 PetscCall(VecDestroy(&denominator));
538 PetscCall(VecDestroy(&ratio));
539 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
540 PetscFunctionReturn(0);
541}
static PetscErrorCode SetInteriorScalar(UserCtx *user, Vec local, PetscReal value)
Writes a scalar into every cell the closure treats as owned and interior.
Definition test_les.c:320
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestAverageRatioIgnoresPeriodicDuplicatePlanes()

static PetscErrorCode TestAverageRatioIgnoresPeriodicDuplicatePlanes ( void  )
static

Tests that a spatial average ignores the periodic duplicate planes.

On a periodic block, index 0 and index m-1 hold copies of interior cells. Counting them would weight those cells twice. The test poisons both planes and requires the average not to move.

Definition at line 550 of file test_les.c.

551{
552 SimCtx *simCtx = NULL;
553 UserCtx *user = NULL;
554 Vec numerator, denominator, ratio;
556 PetscReal ***num = NULL;
557 DMDALocalInfo info;
558 PetscReal clean = 0.0, poisoned = 0.0;
559 const PetscBool all[3] = {PETSC_TRUE, PETSC_TRUE, PETSC_TRUE};
560
561 PetscFunctionBeginUser;
562 PetscCall(PicurvCreateMinimalContextsWithPeriodicity(&simCtx, &user, 6, 6, 6,
563 PETSC_TRUE, PETSC_TRUE, PETSC_TRUE));
564 DeclarePeriodicFaces(user, PETSC_TRUE, PETSC_TRUE, PETSC_TRUE);
565 PetscCall(DMDAGetLocalInfo(user->da, &info));
566 PetscCall(VecSet(user->lNvert, 0.0));
567 PetscCall(VecSet(user->lAj, 1.0));
568
569 PetscCall(VecDuplicate(user->lNvert, &numerator));
570 PetscCall(VecDuplicate(user->lNvert, &denominator));
571 PetscCall(VecDuplicate(user->lNvert, &ratio));
572 PetscCall(FillLocalScalar(user, numerator, 0.0));
573 PetscCall(FillLocalScalar(user, denominator, 0.0));
574 PetscCall(FillLocalScalar(user, ratio, 0.0));
575 PetscCall(SetInteriorScalar(user, numerator, 2.0));
576 PetscCall(SetInteriorScalar(user, denominator, 1.0));
577
578 PetscCall(SpatialTargetPlanCreate(user, FIELD_ID_CS,
580 PetscCall(PicurvSpatialRatioAverage(user, &plan, numerator, denominator, NULL, all,
581 PETSC_COMM_WORLD, ratio, NULL));
582 PetscCall(ReadLocalScalar(user, ratio, 2, 2, 2, &clean));
583
584 /* Poison both layout boundary planes in every direction. */
585 PetscCall(DMDAVecGetArray(user->da, numerator, &num));
586 for (PetscInt k = 0; k < info.mz; ++k)
587 for (PetscInt j = 0; j < info.my; ++j)
588 for (PetscInt i = 0; i < info.mx; ++i) {
589 const PetscBool duplicate = (PetscBool)(i == 0 || j == 0 || k == 0 ||
590 i == info.mx - 1 || j == info.my - 1 || k == info.mz - 1);
591
592 if (duplicate) num[k][j][i] = 1.0e6;
593 }
594 PetscCall(DMDAVecRestoreArray(user->da, numerator, &num));
595
596 PetscCall(SpatialTargetPlanCreate(user, FIELD_ID_CS,
598 PetscCall(PicurvSpatialRatioAverage(user, &plan, numerator, denominator, NULL, all,
599 PETSC_COMM_WORLD, ratio, NULL));
600 PetscCall(ReadLocalScalar(user, ratio, 2, 2, 2, &poisoned));
601
602 PetscCall(PicurvAssertRealNear(2.0, clean, 1.0e-13,
603 "the interior average should be the interior value"));
604 PetscCall(PicurvAssertRealNear(clean, poisoned, 1.0e-13,
605 "the duplicate planes must not contribute to a spatial average"));
606
607 PetscCall(VecDestroy(&numerator));
608 PetscCall(VecDestroy(&denominator));
609 PetscCall(VecDestroy(&ratio));
610 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
611 PetscFunctionReturn(0);
612}
static void DeclarePeriodicFaces(UserCtx *user, PetscBool xi, PetscBool eta, PetscBool zeta)
Declares periodic boundary pairs on the axes a test wants homogeneous.
Definition test_les.c:365
PetscErrorCode PicurvCreateMinimalContextsWithPeriodicity(SimCtx **simCtx_out, UserCtx **user_out, PetscInt mx, PetscInt my, PetscInt mz, PetscBool x_periodic, PetscBool y_periodic, PetscBool z_periodic)
Builds minimal SimCtx and UserCtx fixtures for C unit tests with configurable periodicity.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestHomogeneousAveragingDerivesPeriodicAxes()

static PetscErrorCode TestHomogeneousAveragingDerivesPeriodicAxes ( void  )
static

Tests that homogeneous averaging falls back to the block's periodic axes.

Definition at line 615 of file test_les.c.

616{
617 SimCtx *simCtx = NULL;
618 UserCtx *user = NULL;
619 PetscBool direction[3];
620
621 PetscFunctionBeginUser;
622 /* Periodic in xi and zeta only: the channel arrangement. The fixture sets the
623 resolved periodicity flags, which are what the closure reads; the boundary faces
624 are left alone here so the test cannot pass by consulting them instead. */
625 PetscCall(PicurvCreateMinimalContextsWithPeriodicity(&simCtx, &user, 6, 6, 6,
626 PETSC_TRUE, PETSC_FALSE, PETSC_TRUE));
628
629 PetscCall(ResolveLESAveragingDirections(user, direction));
630 PetscCall(PicurvAssertBool(direction[0], "a periodic xi axis should be averaged over"));
631 PetscCall(PicurvAssertBool((PetscBool)(!direction[1]),
632 "a wall-normal axis should keep its own coefficient profile"));
633 PetscCall(PicurvAssertBool(direction[2], "a periodic zeta axis should be averaged over"));
634
635 /* An explicit selection overrides the periodic default. */
636 simCtx->les_config.averaging_direction[0] = PETSC_TRUE;
637 simCtx->les_config.averaging_direction[1] = PETSC_FALSE;
638 simCtx->les_config.averaging_direction[2] = PETSC_FALSE;
639 PetscCall(ResolveLESAveragingDirections(user, direction));
640 PetscCall(PicurvAssertBool(direction[0], "an explicit direction should be honoured"));
641 PetscCall(PicurvAssertBool((PetscBool)(!direction[2]),
642 "an explicit selection should replace the periodic default"));
643
644 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
645 PetscFunctionReturn(0);
646}
PetscErrorCode ResolveLESAveragingDirections(UserCtx *user, PetscBool direction[3])
Resolves which logical directions the dynamic coefficient is averaged over.
Definition les.c:393
LESConfig les_config
Parameters of the LES closure selected by les.
Definition variables.h:989
PetscBool averaging_direction[3]
Averaged-over logical directions (xi, eta, zeta).
Definition variables.h:639
@ LES_AVERAGING_HOMOGENEOUS
Definition variables.h:608
LESAveragingMode averaging_mode
Averaging set for the Germano contractions.
Definition variables.h:638
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestAveragingModesSelectTheirOwnDirections()

static PetscErrorCode TestAveragingModesSelectTheirOwnDirections ( void  )
static

Tests that local and global averaging ignore the configured direction list.

Definition at line 649 of file test_les.c.

650{
651 SimCtx *simCtx = NULL;
652 UserCtx *user = NULL;
653 PetscBool direction[3];
654
655 PetscFunctionBeginUser;
656 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 6, 6, 6));
657 simCtx->les_config.averaging_direction[0] = PETSC_TRUE;
658
660 PetscCall(ResolveLESAveragingDirections(user, direction));
661 PetscCall(PicurvAssertBool((PetscBool)(!direction[0] && !direction[1] && !direction[2]),
662 "local averaging should select no direction at all"));
663
665 PetscCall(ResolveLESAveragingDirections(user, direction));
666 PetscCall(PicurvAssertBool((PetscBool)(direction[0] && direction[1] && direction[2]),
667 "global averaging should select every direction"));
668
669 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
670 PetscFunctionReturn(0);
671}
@ LES_AVERAGING_LOCAL
Definition variables.h:607
@ LES_AVERAGING_GLOBAL
Definition variables.h:609
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestConstantModelNeedsNoCoefficientField()

static PetscErrorCode TestConstantModelNeedsNoCoefficientField ( void  )
static

Tests that the constant model builds its viscosity without a coefficient field.

UserCtx::CS and UserCtx::lCs are deliberately left unallocated. If the constant path ever reaches for them again this case crashes rather than silently reintroducing a field of one repeated number.

Definition at line 680 of file test_les.c.

681{
682 SimCtx *simCtx = NULL;
683 UserCtx *user = NULL;
684 Cmpnts ***ucat = NULL;
685 PetscReal ***nu_t = NULL;
686 const PetscReal constant_cs = 0.2;
687 /* u = x gives |S| = sqrt(2); with Aj = 1 the filter width is 1. */
688 const PetscReal expected = constant_cs * constant_cs * PetscSqrtReal(2.0);
689
690 PetscFunctionBeginUser;
691 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 5, 5, 5));
692 simCtx->les = CONSTANT_SMAGORINSKY;
693 simCtx->les_config.constant_cs = constant_cs;
694
695 PetscCall(DMCreateGlobalVector(user->da, &user->Nu_t));
696 PetscCall(DMCreateLocalVector(user->da, &user->lNu_t));
697 PetscCall(VecSet(user->Aj, 1.0));
698 PetscCall(VecSet(user->Nu_t, 0.0));
699
700 PetscCall(DMDAVecGetArray(user->fda, user->Ucat, &ucat));
701 for (PetscInt k = user->info.zs; k < user->info.zs + user->info.zm; ++k)
702 for (PetscInt j = user->info.ys; j < user->info.ys + user->info.ym; ++j)
703 for (PetscInt i = user->info.xs; i < user->info.xs + user->info.xm; ++i) {
704 ucat[k][j][i].x = (PetscReal)i;
705 ucat[k][j][i].y = 0.0;
706 ucat[k][j][i].z = 0.0;
707 }
708 PetscCall(DMDAVecRestoreArray(user->fda, user->Ucat, &ucat));
709 PetscCall(DMGlobalToLocalBegin(user->fda, user->Ucat, INSERT_VALUES, user->lUcat));
710 PetscCall(DMGlobalToLocalEnd(user->fda, user->Ucat, INSERT_VALUES, user->lUcat));
711 PetscCall(DMGlobalToLocalBegin(user->da, user->Aj, INSERT_VALUES, user->lAj));
712 PetscCall(DMGlobalToLocalEnd(user->da, user->Aj, INSERT_VALUES, user->lAj));
713
714 PetscCall(PicurvAssertBool((PetscBool)(user->lCs == NULL),
715 "the constant model should allocate no coefficient field"));
716 PetscCall(ComputeEddyViscosityLES(user));
717
718 PetscCall(DMDAVecGetArrayRead(user->da, user->Nu_t, &nu_t));
719 PetscCall(PicurvAssertRealNear(expected, nu_t[2][2][2], 1.0e-9,
720 "the constant model should read its coefficient from configuration"));
721 PetscCall(DMDAVecRestoreArrayRead(user->da, user->Nu_t, &nu_t));
722
723 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
724 PetscFunctionReturn(0);
725}
PetscErrorCode ComputeEddyViscosityLES(UserCtx *user)
Computes the turbulent eddy viscosity for one block.
Definition les.c:851
@ CONSTANT_SMAGORINSKY
Definition variables.h:556
PetscScalar x
Definition variables.h:121
Vec lNu_t
Definition variables.h:1154
PetscScalar z
Definition variables.h:121
PetscReal constant_cs
Fixed Cs for CONSTANT_SMAGORINSKY; unused by the dynamic model.
Definition variables.h:634
DMDALocalInfo info
Definition variables.h:1083
Vec lUcat
Definition variables.h:1111
PetscScalar y
Definition variables.h:121
PetscInt les
Active LES closure; an LESModelType value.
Definition variables.h:985
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:

◆ TestDynamicProcedureRejectsConstantModel()

static PetscErrorCode TestDynamicProcedureRejectsConstantModel ( void  )
static

Tests that the dynamic procedure refuses to run for the constant model.

Definition at line 728 of file test_les.c.

729{
730 SimCtx *simCtx = NULL;
731 UserCtx *user = NULL;
732 PetscErrorCode call_ierr;
733
734 PetscFunctionBeginUser;
735 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 5, 5, 5));
736 simCtx->les = CONSTANT_SMAGORINSKY;
737 simCtx->step = 5;
738 simCtx->StartStep = 0;
739
740 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
741 call_ierr = ComputeSmagorinskyConstant(user);
742 PetscCall(PetscPopErrorHandler());
743
744 PetscCall(PicurvAssertBool((PetscBool)(call_ierr != 0),
745 "the dynamic procedure should reject a model that has no "
746 "coefficient field to fill"));
747
748 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
749 PetscFunctionReturn(0);
750}
PetscErrorCode ComputeSmagorinskyConstant(UserCtx *user)
Computes the dynamic Smagorinsky coefficient field for one block.
Definition les.c:623
PetscInt StartStep
Definition variables.h:868
PetscInt step
Definition variables.h:866
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestDynamicProcedureVanishesOnUniformFlow()

static PetscErrorCode TestDynamicProcedureVanishesOnUniformFlow ( void  )
static

Tests that the dynamic procedure returns a zero coefficient on uniform flow.

Exercises the whole path: the strain precompute and its halo, the stencil gather, every test filter, both contractions, the averaging, and the limiting. A uniform field has no strain and no stress between the two filter widths, so the Germano identity has nothing to fit and the coefficient must come out exactly zero.

Definition at line 760 of file test_les.c.

761{
762 SimCtx *simCtx = NULL;
763 UserCtx *user = NULL;
764 Cmpnts ***ucat = NULL;
765 PetscReal ***coefficient = NULL;
766
767 PetscFunctionBeginUser;
768 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 6, 6, 6));
769 simCtx->les = DYNAMIC_SMAGORINSKY;
770 simCtx->step = 5;
771 simCtx->StartStep = 0;
772
773 PetscCall(DMCreateGlobalVector(user->da, &user->CS));
774 PetscCall(DMCreateLocalVector(user->da, &user->lCs));
775 PetscCall(VecSet(user->Aj, 1.0));
776 PetscCall(VecSet(user->lAj, 1.0));
777 PetscCall(VecSet(user->lNvert, 0.0));
778
779 PetscCall(DMDAVecGetArray(user->fda, user->Ucat, &ucat));
780 for (PetscInt k = user->info.zs; k < user->info.zs + user->info.zm; ++k)
781 for (PetscInt j = user->info.ys; j < user->info.ys + user->info.ym; ++j)
782 for (PetscInt i = user->info.xs; i < user->info.xs + user->info.xm; ++i) {
783 ucat[k][j][i].x = 1.25;
784 ucat[k][j][i].y = -0.5;
785 ucat[k][j][i].z = 3.0;
786 }
787 PetscCall(DMDAVecRestoreArray(user->fda, user->Ucat, &ucat));
788 PetscCall(DMGlobalToLocalBegin(user->fda, user->Ucat, INSERT_VALUES, user->lUcat));
789 PetscCall(DMGlobalToLocalEnd(user->fda, user->Ucat, INSERT_VALUES, user->lUcat));
790
791 PetscCall(ComputeSmagorinskyConstant(user));
792
793 PetscCall(DMDAVecGetArrayRead(user->da, user->CS, &coefficient));
794 for (PetscInt k = 1; k < 6; ++k)
795 for (PetscInt j = 1; j < 6; ++j)
796 for (PetscInt i = 1; i < 6; ++i) {
797 PetscCall(PicurvAssertRealNear(0.0, coefficient[k][j][i], 1.0e-20,
798 "a uniform field should give the dynamic procedure "
799 "nothing to fit"));
800 }
801 PetscCall(DMDAVecRestoreArrayRead(user->da, user->CS, &coefficient));
802
803 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
804 PetscFunctionReturn(0);
805}
@ DYNAMIC_SMAGORINSKY
Definition variables.h:557
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestDynamicProcedureGlobalAverageIsUniform()

static PetscErrorCode TestDynamicProcedureGlobalAverageIsUniform ( void  )
static

Tests the dynamic procedure end to end on a periodic sheared field.

Runs the full procedure on a block that is periodic in all three directions, where the strain precompute has to reach across the wrap correctly, and asks for global averaging. Two things must hold: every cell receives the same finite coefficient, because a global average is one number for the block; and the coefficient stays inside the configured ceiling.

Definition at line 816 of file test_les.c.

817{
818 SimCtx *simCtx = NULL;
819 UserCtx *user = NULL;
820 Cmpnts ***ucat = NULL;
821 PetscReal ***coefficient = NULL;
822 PetscReal reference = 0.0;
823 const FieldId cell_fields[] = {FIELD_ID_UCAT};
824 const PetscReal wavenumber = 2.0 * PETSC_PI / 5.0; /* five distinct cells per direction */
825
826 PetscFunctionBeginUser;
827 PetscCall(PicurvCreateMinimalContextsWithPeriodicity(&simCtx, &user, 6, 6, 6,
828 PETSC_TRUE, PETSC_TRUE, PETSC_TRUE));
829 DeclarePeriodicFaces(user, PETSC_TRUE, PETSC_TRUE, PETSC_TRUE);
830 simCtx->les = DYNAMIC_SMAGORINSKY;
831 simCtx->step = 5;
832 simCtx->StartStep = 0;
834
835 PetscCall(DMCreateGlobalVector(user->da, &user->CS));
836 PetscCall(DMCreateLocalVector(user->da, &user->lCs));
837 PetscCall(VecSet(user->Aj, 1.0));
838 PetscCall(VecSet(user->lAj, 1.0));
839 PetscCall(VecSet(user->lNvert, 0.0));
840
841 /* A divergence-free periodic shear: enough structure that both contractions are
842 nonzero, and smooth enough that the coefficient stays in range. */
843 PetscCall(DMDAVecGetArray(user->fda, user->Ucat, &ucat));
844 for (PetscInt k = user->info.zs; k < user->info.zs + user->info.zm; ++k)
845 for (PetscInt j = user->info.ys; j < user->info.ys + user->info.ym; ++j)
846 for (PetscInt i = user->info.xs; i < user->info.xs + user->info.xm; ++i) {
847 ucat[k][j][i].x = PetscSinReal(wavenumber * (PetscReal)j);
848 ucat[k][j][i].y = PetscSinReal(wavenumber * (PetscReal)k);
849 ucat[k][j][i].z = PetscSinReal(wavenumber * (PetscReal)i);
850 }
851 PetscCall(DMDAVecRestoreArray(user->fda, user->Ucat, &ucat));
852 PetscCall(SynchronizePeriodicCellFields(user, 1, cell_fields));
853 PetscCall(UpdateLocalGhosts(user, FIELD_ID_UCAT));
854
855 PetscCall(ComputeSmagorinskyConstant(user));
856
857 PetscCall(DMDAVecGetArrayRead(user->da, user->CS, &coefficient));
858 reference = coefficient[1][1][1];
859 PetscCall(PicurvAssertBool((PetscBool)(PetscIsNormalReal(reference) || reference == 0.0),
860 "the dynamic coefficient must be finite"));
861 PetscCall(PicurvAssertBool((PetscBool)(reference >= 0.0 &&
862 reference <= simCtx->les_config.max_cs *
863 simCtx->les_config.max_cs),
864 "clamping should keep the coefficient inside its ceiling"));
865 for (PetscInt k = 1; k < 6; ++k)
866 for (PetscInt j = 1; j < 6; ++j)
867 for (PetscInt i = 1; i < 6; ++i) {
868 PetscCall(PicurvAssertRealNear(reference, coefficient[k][j][i], 1.0e-13,
869 "global averaging should give the block one coefficient"));
870 }
871 PetscCall(DMDAVecRestoreArrayRead(user->da, user->CS, &coefficient));
872
873 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
874 PetscFunctionReturn(0);
875}
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_UCAT
PetscErrorCode UpdateLocalGhosts(UserCtx *user, FieldId field_id)
Updates the local vector (including ghost points) from its corresponding global vector.
Definition setup.c:2451
Here is the call graph for this function:
Here is the caller graph for this function:

◆ main()

int main ( int  argc,
char **  argv 
)

Entry point for the LES closure suite.

Definition at line 880 of file test_les.c.

881{
882 PetscErrorCode ierr;
883 const PicurvTestCase cases[] = {
884 {"sym-tensor-algebra", TestSymTensorAlgebra},
885 {"strain-rate-from-gradients", TestStrainRateFromGradients},
886 {"filter-width-models-separate-on-stretched-cell", TestFilterWidthModelsSeparateOnStretchedCell},
887 {"leonard-stress-vanishes-on-uniform-flow", TestLeonardStressVanishesOnUniformFlow},
888 {"germano-model-tensor-on-constant-strain", TestGermanoModelTensorOnConstantStrain},
889 {"germano-model-tensor-uses-filtered-product", TestGermanoModelTensorUsesFilteredProduct},
890 {"clip-model-coefficient-modes", TestClipModelCoefficientModes},
891 {"eddy-viscosity-floor-bounds-total-viscosity", TestEddyViscosityFloorBoundsTotalViscosity},
892 {"subgrid-kinetic-energy", TestSubgridKineticEnergy},
893 {"average-ratio-local-is-pointwise", TestAverageRatioLocalIsPointwise},
894 {"average-ratio-divides-summed-fields", TestAverageRatioDividesSummedFields},
895 {"average-ratio-retains-unaveraged-direction", TestAverageRatioRetainsUnaveragedDirection},
896 {"average-ratio-ignores-periodic-duplicate-planes", TestAverageRatioIgnoresPeriodicDuplicatePlanes},
897 {"homogeneous-averaging-derives-periodic-axes", TestHomogeneousAveragingDerivesPeriodicAxes},
898 {"averaging-modes-select-their-own-directions", TestAveragingModesSelectTheirOwnDirections},
899 {"constant-model-needs-no-coefficient-field", TestConstantModelNeedsNoCoefficientField},
900 {"dynamic-procedure-rejects-constant-model", TestDynamicProcedureRejectsConstantModel},
901 {"dynamic-procedure-vanishes-on-uniform-flow", TestDynamicProcedureVanishesOnUniformFlow},
902 {"dynamic-procedure-global-average-is-uniform", TestDynamicProcedureGlobalAverageIsUniform},
903 };
904
905 ierr = PetscInitialize(&argc, &argv, NULL, "PICurv LES closure tests");
906 if (ierr) {
907 return (int)ierr;
908 }
909
910 ierr = PicurvRunTests("unit-les", cases, sizeof(cases) / sizeof(cases[0]));
911 if (ierr) {
912 PetscFinalize();
913 return (int)ierr;
914 }
915
916 ierr = PetscFinalize();
917 return (int)ierr;
918}
static PetscErrorCode TestAverageRatioDividesSummedFields(void)
Tests that averaging divides summed numerators by summed denominators.
Definition test_les.c:434
static PetscErrorCode TestAverageRatioRetainsUnaveragedDirection(void)
Tests that averaging over two directions leaves a profile along the third.
Definition test_les.c:493
static PetscErrorCode TestHomogeneousAveragingDerivesPeriodicAxes(void)
Tests that homogeneous averaging falls back to the block's periodic axes.
Definition test_les.c:615
static PetscErrorCode TestGermanoModelTensorOnConstantStrain(void)
Tests the model tensor where the two filter terms provably coincide.
Definition test_les.c:147
static PetscErrorCode TestEddyViscosityFloorBoundsTotalViscosity(void)
Tests eddy-viscosity assembly and the total-viscosity floor.
Definition test_les.c:265
static PetscErrorCode TestDynamicProcedureVanishesOnUniformFlow(void)
Tests that the dynamic procedure returns a zero coefficient on uniform flow.
Definition test_les.c:760
static PetscErrorCode TestLeonardStressVanishesOnUniformFlow(void)
Tests that the Leonard stress vanishes on a uniform velocity field.
Definition test_les.c:123
static PetscErrorCode TestSymTensorAlgebra(void)
Tests the symmetric-tensor primitives against hand-computed values.
Definition test_les.c:29
static PetscErrorCode TestConstantModelNeedsNoCoefficientField(void)
Tests that the constant model builds its viscosity without a coefficient field.
Definition test_les.c:680
static PetscErrorCode TestAverageRatioIgnoresPeriodicDuplicatePlanes(void)
Tests that a spatial average ignores the periodic duplicate planes.
Definition test_les.c:550
static PetscErrorCode TestStrainRateFromGradients(void)
Tests strain-rate assembly and its magnitude for a known velocity gradient.
Definition test_les.c:68
static PetscErrorCode TestAveragingModesSelectTheirOwnDirections(void)
Tests that local and global averaging ignore the configured direction list.
Definition test_les.c:649
static PetscErrorCode TestSubgridKineticEnergy(void)
Tests the Yoshizawa subgrid kinetic energy against its closed form.
Definition test_les.c:290
static PetscErrorCode TestFilterWidthModelsSeparateOnStretchedCell(void)
Tests that the three filter-width models separate on an anisotropic cell.
Definition test_les.c:93
static PetscErrorCode TestDynamicProcedureRejectsConstantModel(void)
Tests that the dynamic procedure refuses to run for the constant model.
Definition test_les.c:728
static PetscErrorCode TestGermanoModelTensorUsesFilteredProduct(void)
Tests that the model tensor uses the filtered product, not the filtered factors.
Definition test_les.c:180
static PetscErrorCode TestAverageRatioLocalIsPointwise(void)
Tests that an empty direction set reproduces the pointwise local model.
Definition test_les.c:379
static PetscErrorCode TestDynamicProcedureGlobalAverageIsUniform(void)
Tests the dynamic procedure end to end on a periodic sheared field.
Definition test_les.c:816
static PetscErrorCode TestClipModelCoefficientModes(void)
Tests each limiting mode, including the sign that carries backscatter.
Definition test_les.c:232
PetscErrorCode PicurvRunTests(const char *suite_name, const PicurvTestCase *cases, size_t case_count)
Runs a named C test suite and prints pass/fail progress markers.
Named test case descriptor consumed by PicurvRunTests.
Here is the call graph for this function: