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};
36 PetscFunctionBeginUser;
39 PetscCall(
PicurvAssertRealNear(1.0, outer.
xx, 1.0e-14,
"self outer product should square the x component"));
42 PetscCall(
PicurvAssertRealNear(9.0, outer.
zz, 1.0e-14,
"self outer product should square the z component"));
49 "the deviator should be trace free"));
51 "the deviator should leave off-diagonal components alone"));
55 "contraction against a diagonal tensor should scale its trace"));
58 "the squared norm must count off-diagonal components twice"));
64 PetscFunctionReturn(0);
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};
77 PetscFunctionBeginUser;
81 "pure shear should give S_xy = half the velocity gradient"));
84 "an incompressible gradient should give a trace-free strain"));
87 "the strain magnitude must count both off-diagonal entries"));
89 PetscFunctionReturn(0);
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;
103 PetscFunctionBeginUser;
111 "the cube-root model should depend only on cell volume"));
113 "the geometric mean of the extents should recover the same volume"));
115 "the max-edge model should report the long direction"));
117 "a stretched cell should make the max-edge width the larger of the two"));
119 PetscFunctionReturn(0);
128 csi->
x = b.
y*c.
z - b.
z*c.
y; csi->
y = b.
z*c.
x - b.
x*c.
z; csi->
z = b.
x*c.
y - b.
y*c.
x;
129 eta->
x = c.
y*a.
z - c.
z*a.
y; eta->
y = c.
z*a.
x - c.
x*a.
z; eta->
z = c.
x*a.
y - c.
y*a.
x;
130 zet->
x = a.
y*b.
z - a.
z*b.
y; zet->
y = a.
z*b.
x - a.
x*b.
z; zet->
z = a.
x*b.
y - a.
y*b.
x;
131 *aj = 1.0 / (a.
x*csi->
x + a.
y*csi->
y + a.
z*csi->
z);
146 const PetscReal c45 = PetscCosReal(PETSC_PI / 4.0), s45 = PetscSinReal(PETSC_PI / 4.0);
147 const PetscReal c30 = PetscCosReal(PETSC_PI / 6.0), s30 = PetscSinReal(PETSC_PI / 6.0);
148 const Cmpnts aligned[3] = {{0.25, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 8.0}};
153 PetscReal aj, reference[3], turned[3];
155 PetscFunctionBeginUser;
156 for (PetscInt e = 0; e < 3; ++e) {
158 const Cmpnts v = aligned[e];
159 const Cmpnts z = {c45*v.
x - s45*v.
y, s45*v.
x + c45*v.
y, v.
z};
160 rotated[e] = (
Cmpnts){z.
x, c30*z.
y - s30*z.
z, s30*z.
y + c30*z.
z};
164 for (PetscInt m = 0; m < 3; ++m) PetscCall(
ComputeCellFilterWidth(models[m], aj, csi, eta, zet, &reference[m]));
166 for (PetscInt m = 0; m < 3; ++m) PetscCall(
ComputeCellFilterWidth(models[m], aj, csi, eta, zet, &turned[m]));
168 PetscCall(
PicurvAssertRealNear(reference[0], turned[0], 1.0e-12,
"cube-root width is orientation independent"));
169 PetscCall(
PicurvAssertRealNear(reference[1], turned[1], 1.0e-12,
"geometric-mean width is orientation independent"));
170 PetscCall(
PicurvAssertRealNear(reference[2], turned[2], 1.0e-12,
"max-edge width is orientation independent"));
171 PetscCall(
PicurvAssertRealNear(8.0, turned[2], 1.0e-12,
"max-edge width is the cell's longest extent"));
173 "geometric-mean width of a 0.25 x 1 x 8 cell is the cube root of its volume"));
174 PetscFunctionReturn(0);
188 const Cmpnts cube[3] = {{1.5, 0.0, 0.0}, {0.0, 1.5, 0.0}, {0.0, 0.0, 1.5}};
189 const Cmpnts brick[3] = {{1.0, 0.0, 0.0}, {0.0, 2.0, 0.0}, {0.0, 0.0, 8.0}};
190 const PetscReal c45 = PetscCosReal(PETSC_PI / 4.0), s45 = PetscSinReal(PETSC_PI / 4.0);
191 Cmpnts turned[3], csi, eta, zet;
192 PetscReal aj, delta, delta_turned, cube_root;
194 PetscFunctionBeginUser;
198 PetscCall(
PicurvAssertRealNear(cube_root, delta, 1.0e-12,
"the Scotti correction is 1 for a cube"));
204 "Scotti width of a 1 x 2 x 8 cell matches the closed form"));
206 for (PetscInt e = 0; e < 3; ++e) {
207 const Cmpnts v = brick[e];
208 turned[e] = (
Cmpnts){c45 * v.
x - s45 * v.
z, v.
y, s45 * v.
x + c45 * v.
z};
212 PetscCall(
PicurvAssertRealNear(delta, delta_turned, 1.0e-12,
"the Scotti width is orientation independent"));
213 PetscFunctionReturn(0);
219 const PetscReal cz = PetscCosReal(40.0 * PETSC_PI / 180.0), sz = PetscSinReal(40.0 * PETSC_PI / 180.0);
220 const PetscReal cx = PetscCosReal(25.0 * PETSC_PI / 180.0), sx = PetscSinReal(25.0 * PETSC_PI / 180.0);
221 const Cmpnts z = {cz * v.
x - sz * v.
y, sz * v.
x + cz * v.
y, v.
z};
222 return (
Cmpnts){z.
x, cx * z.
y - sx * z.
z, sx * z.
y + cx * z.
z};
228 Cmpnts column_rotated[3], cols[3];
232 for (PetscInt b = 0; b < 3; ++b) {
233 const Cmpnts column = {b == 0 ? column_rotated[0].
x : (b == 1 ? column_rotated[0].
y : column_rotated[0].
z),
234 b == 0 ? column_rotated[1].x : (b == 1 ? column_rotated[1].
y : column_rotated[1].
z),
235 b == 0 ? column_rotated[2].x : (b == 1 ? column_rotated[2].
y : column_rotated[2].
z)};
238 for (PetscInt a = 0; a < 3; ++a)
239 out[a] = (
Cmpnts){a == 0 ? cols[0].
x : (a == 1 ? cols[0].
y : cols[0].
z),
240 a == 0 ? cols[1].x : (a == 1 ? cols[1].
y : cols[1].
z),
241 a == 0 ? cols[2].x : (a == 1 ? cols[2].
y : cols[2].
z)};
254 const Cmpnts g[3] = {{0.3, 1.2, -0.4}, {0.7, -0.5, 0.9}, {-0.2, 0.6, 0.2}};
255 const Cmpnts shear[3] = {{0.0, 2.5, 0.0}, {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}};
256 const Cmpnts aligned_edges[3] = {{0.1, 0.0, 0.0}, {0.0, 0.2, 0.0}, {0.0, 0.0, 0.8}};
257 Cmpnts rotated_edges[3], rotated_g[3], csi, eta, zet, recovered[3];
260 PetscFunctionBeginUser;
262 1.0e-15,
"WALE matches the independent evaluation"));
265 1.0e-15,
"Vreman on aligned edges matches the Cartesian formula"));
267 1.0e-15,
"WALE vanishes in pure shear"));
269 1.0e-15,
"Vreman vanishes in pure shear"));
275 1.0e-14,
"Vreman is unchanged when the flow and the cell rotate together"));
278 1.0e-15,
"WALE is unchanged when the flow rotates"));
283 for (PetscInt m = 0; m < 3; ++m) {
284 PetscCall(
PicurvAssertRealNear(rotated_edges[m].x, recovered[m].x, 1.0e-14,
"edge x recovered from metrics"));
285 PetscCall(
PicurvAssertRealNear(rotated_edges[m].y, recovered[m].y, 1.0e-14,
"edge y recovered from metrics"));
286 PetscCall(
PicurvAssertRealNear(rotated_edges[m].z, recovered[m].z, 1.0e-14,
"edge z recovered from metrics"));
288 PetscFunctionReturn(0);
294 const Cmpnts velocity = {1.5, -0.5, 2.0};
298 PetscFunctionBeginUser;
303 "a uniform field carries no stress between the two filter widths"));
304 PetscFunctionReturn(0);
318 const PetscReal delta = 0.5;
319 const PetscReal alpha = 4.0;
320 Cmpnts dudx = {0.0, 2.0, 0.0}, dvdx = {0.0, 0.0, 0.0}, dwdx = {0.0, 0.0, 0.0};
321 SymTensor strain, product, model, expected;
324 PetscFunctionBeginUser;
331 strain, 0.0, strain));
334 "constant strain should give the closed-form model tensor"));
336 "the model tensor must be trace free"));
337 PetscFunctionReturn(0);
352 const PetscReal delta = 1.0;
353 const PetscReal alpha = 4.0;
354 double magnitude_stencil[3][3][3], weights[3][3][3];
355 SymTensor strain_stencil[3][3][3], product_stencil[3][3][3];
356 SymTensor strain_filtered, product_filtered, model, collapsed;
357 PetscReal magnitude_filtered, separation;
359 PetscFunctionBeginUser;
361 for (PetscInt k = 0; k < 3; ++k)
362 for (PetscInt j = 0; j < 3; ++j)
363 for (PetscInt i = 0; i < 3; ++i) {
366 const PetscReal shear = 1.0 + 0.5 * (PetscReal)(i + j + k);
367 Cmpnts dudx = {0.0, 2.0 * shear, 0.0};
368 Cmpnts dvdx = {0.0, 0.0, 0.0};
369 Cmpnts dwdx = {0.0, 0.0, 0.0};
374 strain_stencil[k][j][i] = strain;
375 magnitude_stencil[k][j][i] = magnitude;
376 product_stencil[k][j][i] =
SymTensorCombine(magnitude, strain, 0.0, strain);
377 weights[k][j][i] = 1.0;
385 separation = PetscAbsReal(product_filtered.
xy - magnitude_filtered * strain_filtered.
xy);
387 "a varying strain field must separate the filtered product from "
388 "the product of the filtered factors"));
390 model =
GermanoModelTensor(delta, alpha, magnitude_filtered, strain_filtered, product_filtered);
392 strain_filtered, 0.0, strain_filtered));
395 "the model tensor must be built from the filtered product, not from "
396 "the product of separately filtered quantities"));
397 PetscFunctionReturn(0);
404 PetscBool limited = PETSC_FALSE;
406 PetscFunctionBeginUser;
412 "clamping should discard a negative coefficient"));
413 PetscCall(
PicurvAssertBool(limited,
"clamping a negative coefficient counts as limiting"));
415 "clamping should cap the coefficient at max_cs squared"));
417 "clamping should pass an admissible coefficient through"));
418 PetscCall(
PicurvAssertBool((PetscBool)(!limited),
"an admissible coefficient is not limited"));
422 "clip_negative should discard a negative coefficient"));
424 "clip_negative should impose no ceiling"));
428 "no limiting should preserve the sign that carries backscatter"));
429 PetscCall(
PicurvAssertBool((PetscBool)(!limited),
"an unlimited coefficient is not modified"));
430 PetscFunctionReturn(0);
436 const PetscReal molecular = 0.01;
438 PetscFunctionBeginUser;
442 1.0e-13,
"eddy viscosity should be the coefficient times Delta^2 |S|"));
448 1.0e-14,
"the floor should stop nu_t at minus the molecular value"));
451 1.0e-14,
"a ratio of one half should keep half the molecular viscosity"));
454 1.0e-14,
"a mild backscatter should survive the floor untouched"));
455 PetscFunctionReturn(0);
461 PetscFunctionBeginUser;
464 "the Yoshizawa relation should scale with Delta^2 |S|^2"));
465 PetscFunctionReturn(0);
480 PetscFunctionBeginUser;
481 PetscCall(VecSet(local, value));
483 PetscFunctionReturn(0);
492 PetscReal ***array = NULL;
493 PetscInt xs, xe, ys, ye, zs, ze;
495 PetscFunctionBeginUser;
496 PetscCall(DMDAGetLocalInfo(user->
da, &info));
497 xs = (info.xs == 0) ? 1 : info.xs;
498 ys = (info.ys == 0) ? 1 : info.ys;
499 zs = (info.zs == 0) ? 1 : info.zs;
500 xe = (info.xs + info.xm == info.mx) ? info.mx - 1 : info.xs + info.xm;
501 ye = (info.ys + info.ym == info.my) ? info.my - 1 : info.ys + info.ym;
502 ze = (info.zs + info.zm == info.mz) ? info.mz - 1 : info.zs + info.zm;
504 PetscCall(DMDAVecGetArray(user->
da, local, &array));
505 for (PetscInt k = zs; k < ze; ++k)
506 for (PetscInt j = ys; j < ye; ++j)
507 for (PetscInt i = xs; i < xe; ++i) array[k][j][i] = value;
508 PetscCall(DMDAVecRestoreArray(user->
da, local, &array));
509 PetscFunctionReturn(0);
516 PetscReal ***array = NULL;
518 PetscFunctionBeginUser;
519 PetscCall(DMDAVecGetArrayRead(user->
da, local, &array));
520 *value = array[k][j][i];
521 PetscCall(DMDAVecRestoreArrayRead(user->
da, local, &array));
522 PetscFunctionReturn(0);
538 const PetscBool selected[3] = {xi, eta, zeta};
540 for (PetscInt axis = 0; axis < 3; ++axis) {
541 if (!selected[axis])
continue;
552 Vec numerator, denominator, ratio;
554 PetscReal ***num = NULL;
555 PetscReal value = 0.0;
556 const PetscBool none[3] = {PETSC_FALSE, PETSC_FALSE, PETSC_FALSE};
558 PetscFunctionBeginUser;
560 PetscCall(VecSet(user->
lNvert, 0.0));
561 PetscCall(VecSet(user->
lAj, 1.0));
563 PetscCall(VecDuplicate(user->
lNvert, &numerator));
564 PetscCall(VecDuplicate(user->
lNvert, &denominator));
565 PetscCall(VecDuplicate(user->
lNvert, &ratio));
572 PetscCall(DMDAVecGetArray(user->
da, numerator, &num));
573 for (PetscInt k = 1; k < 6; ++k)
574 for (PetscInt j = 1; j < 6; ++j)
575 for (PetscInt i = 1; i < 6; ++i) num[k][j][i] = (PetscReal)(i + j + k);
576 PetscCall(DMDAVecRestoreArray(user->
da, numerator, &num));
581 PETSC_COMM_WORLD, ratio, NULL));
585 "with no averaging direction the quotient should stay pointwise"));
587 PetscCall(VecDestroy(&numerator));
588 PetscCall(VecDestroy(&denominator));
589 PetscCall(VecDestroy(&ratio));
591 PetscFunctionReturn(0);
607 Vec numerator, denominator, ratio;
609 PetscReal ***num = NULL, ***den = NULL;
610 PetscReal value = 0.0;
611 const PetscBool all[3] = {PETSC_TRUE, PETSC_TRUE, PETSC_TRUE};
613 PetscFunctionBeginUser;
615 PetscCall(VecSet(user->
lNvert, 0.0));
616 PetscCall(VecSet(user->
lAj, 1.0));
618 PetscCall(VecDuplicate(user->
lNvert, &numerator));
619 PetscCall(VecDuplicate(user->
lNvert, &denominator));
620 PetscCall(VecDuplicate(user->
lNvert, &ratio));
625 PetscCall(DMDAVecGetArray(user->
da, numerator, &num));
626 PetscCall(DMDAVecGetArray(user->
da, denominator, &den));
627 for (PetscInt k = 1; k < 6; ++k)
628 for (PetscInt j = 1; j < 6; ++j)
629 for (PetscInt i = 1; i < 6; ++i) {
630 const PetscBool low = (PetscBool)(i < 4);
632 num[k][j][i] = low ? 1.0 : 6.0;
633 den[k][j][i] = low ? 1.0 : 2.0;
635 PetscCall(DMDAVecRestoreArray(user->
da, numerator, &num));
636 PetscCall(DMDAVecRestoreArray(user->
da, denominator, &den));
641 PETSC_COMM_WORLD, ratio, NULL));
646 "the global average should divide summed numerators by summed "
647 "denominators, not average the pointwise quotients"));
650 "a global average should be the same number in every cell"));
652 PetscCall(VecDestroy(&numerator));
653 PetscCall(VecDestroy(&denominator));
654 PetscCall(VecDestroy(&ratio));
656 PetscFunctionReturn(0);
666 Vec numerator, denominator, ratio;
668 PetscReal ***num = NULL;
669 PetscReal at_low = 0.0, at_high = 0.0;
671 const PetscBool ik[3] = {PETSC_TRUE, PETSC_FALSE, PETSC_TRUE};
673 PetscFunctionBeginUser;
675 PetscCall(VecSet(user->
lNvert, 0.0));
676 PetscCall(VecSet(user->
lAj, 1.0));
678 PetscCall(VecDuplicate(user->
lNvert, &numerator));
679 PetscCall(VecDuplicate(user->
lNvert, &denominator));
680 PetscCall(VecDuplicate(user->
lNvert, &ratio));
687 PetscCall(DMDAVecGetArray(user->
da, numerator, &num));
688 for (PetscInt k = 1; k < 6; ++k)
689 for (PetscInt j = 1; j < 6; ++j)
690 for (PetscInt i = 1; i < 6; ++i) num[k][j][i] = (PetscReal)j;
691 PetscCall(DMDAVecRestoreArray(user->
da, numerator, &num));
696 PETSC_COMM_WORLD, ratio, NULL));
701 "averaging over xi and zeta should preserve the eta profile"));
703 "each retained eta plane should keep its own average"));
705 PetscCall(VecDestroy(&numerator));
706 PetscCall(VecDestroy(&denominator));
707 PetscCall(VecDestroy(&ratio));
709 PetscFunctionReturn(0);
723 Vec numerator, denominator, ratio;
725 PetscReal ***num = NULL;
727 PetscReal clean = 0.0, poisoned = 0.0;
728 const PetscBool all[3] = {PETSC_TRUE, PETSC_TRUE, PETSC_TRUE};
730 PetscFunctionBeginUser;
732 PETSC_TRUE, PETSC_TRUE, PETSC_TRUE));
734 PetscCall(DMDAGetLocalInfo(user->
da, &info));
735 PetscCall(VecSet(user->
lNvert, 0.0));
736 PetscCall(VecSet(user->
lAj, 1.0));
738 PetscCall(VecDuplicate(user->
lNvert, &numerator));
739 PetscCall(VecDuplicate(user->
lNvert, &denominator));
740 PetscCall(VecDuplicate(user->
lNvert, &ratio));
750 PETSC_COMM_WORLD, ratio, NULL));
754 PetscCall(DMDAVecGetArray(user->
da, numerator, &num));
755 for (PetscInt k = 0; k < info.mz; ++k)
756 for (PetscInt j = 0; j < info.my; ++j)
757 for (PetscInt i = 0; i < info.mx; ++i) {
758 const PetscBool duplicate = (PetscBool)(i == 0 || j == 0 || k == 0 ||
759 i == info.mx - 1 || j == info.my - 1 || k == info.mz - 1);
761 if (duplicate) num[k][j][i] = 1.0e6;
763 PetscCall(DMDAVecRestoreArray(user->
da, numerator, &num));
768 PETSC_COMM_WORLD, ratio, NULL));
772 "the interior average should be the interior value"));
774 "the duplicate planes must not contribute to a spatial average"));
776 PetscCall(VecDestroy(&numerator));
777 PetscCall(VecDestroy(&denominator));
778 PetscCall(VecDestroy(&ratio));
780 PetscFunctionReturn(0);
788 PetscBool direction[3];
790 PetscFunctionBeginUser;
795 PETSC_TRUE, PETSC_FALSE, PETSC_TRUE));
799 PetscCall(
PicurvAssertBool(direction[0],
"a periodic xi axis should be averaged over"));
801 "a wall-normal axis should keep its own coefficient profile"));
802 PetscCall(
PicurvAssertBool(direction[2],
"a periodic zeta axis should be averaged over"));
809 PetscCall(
PicurvAssertBool(direction[0],
"an explicit direction should be honoured"));
811 "an explicit selection should replace the periodic default"));
814 PetscFunctionReturn(0);
822 PetscBool direction[3];
824 PetscFunctionBeginUser;
830 PetscCall(
PicurvAssertBool((PetscBool)(!direction[0] && !direction[1] && !direction[2]),
831 "local averaging should select no direction at all"));
835 PetscCall(
PicurvAssertBool((PetscBool)(direction[0] && direction[1] && direction[2]),
836 "global averaging should select every direction"));
839 PetscFunctionReturn(0);
854 PetscReal ***
nu_t = NULL;
855 const PetscReal constant_cs = 0.2;
857 const PetscReal expected = constant_cs * constant_cs * PetscSqrtReal(2.0);
859 PetscFunctionBeginUser;
864 PetscCall(DMCreateGlobalVector(user->
da, &user->
Nu_t));
865 PetscCall(DMCreateLocalVector(user->
da, &user->
lNu_t));
866 PetscCall(VecSet(user->
Aj, 1.0));
867 PetscCall(VecSet(user->
Nu_t, 0.0));
869 PetscCall(DMDAVecGetArray(user->
fda, user->
Ucat, &ucat));
870 for (PetscInt k = user->
info.zs; k < user->
info.zs + user->
info.zm; ++k)
871 for (PetscInt j = user->
info.ys; j < user->
info.ys + user->
info.ym; ++j)
872 for (PetscInt i = user->
info.xs; i < user->
info.xs + user->
info.xm; ++i) {
873 ucat[k][j][i].
x = (PetscReal)i;
874 ucat[k][j][i].
y = 0.0;
875 ucat[k][j][i].
z = 0.0;
877 PetscCall(DMDAVecRestoreArray(user->
fda, user->
Ucat, &ucat));
878 PetscCall(DMGlobalToLocalBegin(user->
fda, user->
Ucat, INSERT_VALUES, user->
lUcat));
879 PetscCall(DMGlobalToLocalEnd(user->
fda, user->
Ucat, INSERT_VALUES, user->
lUcat));
880 PetscCall(DMGlobalToLocalBegin(user->
da, user->
Aj, INSERT_VALUES, user->
lAj));
881 PetscCall(DMGlobalToLocalEnd(user->
da, user->
Aj, INSERT_VALUES, user->
lAj));
884 "the constant model should allocate no coefficient field"));
887 PetscCall(DMDAVecGetArrayRead(user->
da, user->
Nu_t, &
nu_t));
889 "the constant model should read its coefficient from configuration"));
890 PetscCall(DMDAVecRestoreArrayRead(user->
da, user->
Nu_t, &
nu_t));
893 PetscFunctionReturn(0);
901 PetscErrorCode call_ierr;
903 PetscFunctionBeginUser;
909 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
911 PetscCall(PetscPopErrorHandler());
914 "the dynamic procedure should reject a model that has no "
915 "coefficient field to fill"));
918 PetscFunctionReturn(0);
934 PetscReal ***coefficient = NULL;
936 PetscFunctionBeginUser;
942 PetscCall(DMCreateGlobalVector(user->
da, &user->
CS));
943 PetscCall(DMCreateLocalVector(user->
da, &user->
lCs));
944 PetscCall(VecSet(user->
Aj, 1.0));
945 PetscCall(VecSet(user->
lAj, 1.0));
946 PetscCall(VecSet(user->
lNvert, 0.0));
948 PetscCall(DMDAVecGetArray(user->
fda, user->
Ucat, &ucat));
949 for (PetscInt k = user->
info.zs; k < user->
info.zs + user->
info.zm; ++k)
950 for (PetscInt j = user->
info.ys; j < user->
info.ys + user->
info.ym; ++j)
951 for (PetscInt i = user->
info.xs; i < user->
info.xs + user->
info.xm; ++i) {
952 ucat[k][j][i].
x = 1.25;
953 ucat[k][j][i].
y = -0.5;
954 ucat[k][j][i].
z = 3.0;
956 PetscCall(DMDAVecRestoreArray(user->
fda, user->
Ucat, &ucat));
957 PetscCall(DMGlobalToLocalBegin(user->
fda, user->
Ucat, INSERT_VALUES, user->
lUcat));
958 PetscCall(DMGlobalToLocalEnd(user->
fda, user->
Ucat, INSERT_VALUES, user->
lUcat));
962 PetscCall(DMDAVecGetArrayRead(user->
da, user->
CS, &coefficient));
963 for (PetscInt k = 1; k < 6; ++k)
964 for (PetscInt j = 1; j < 6; ++j)
965 for (PetscInt i = 1; i < 6; ++i) {
967 "a uniform field should give the dynamic procedure "
970 PetscCall(DMDAVecRestoreArrayRead(user->
da, user->
CS, &coefficient));
973 PetscFunctionReturn(0);
990 PetscReal ***coefficient = NULL;
991 PetscReal reference = 0.0;
993 const PetscReal wavenumber = 2.0 * PETSC_PI / 5.0;
995 PetscFunctionBeginUser;
997 PETSC_TRUE, PETSC_TRUE, PETSC_TRUE));
1004 PetscCall(DMCreateGlobalVector(user->
da, &user->
CS));
1005 PetscCall(DMCreateLocalVector(user->
da, &user->
lCs));
1006 PetscCall(VecSet(user->
Aj, 1.0));
1007 PetscCall(VecSet(user->
lAj, 1.0));
1008 PetscCall(VecSet(user->
lNvert, 0.0));
1012 PetscCall(DMDAVecGetArray(user->
fda, user->
Ucat, &ucat));
1013 for (PetscInt k = user->
info.zs; k < user->
info.zs + user->
info.zm; ++k)
1014 for (PetscInt j = user->
info.ys; j < user->
info.ys + user->
info.ym; ++j)
1015 for (PetscInt i = user->
info.xs; i < user->
info.xs + user->
info.xm; ++i) {
1016 ucat[k][j][i].
x = PetscSinReal(wavenumber * (PetscReal)j);
1017 ucat[k][j][i].
y = PetscSinReal(wavenumber * (PetscReal)k);
1018 ucat[k][j][i].
z = PetscSinReal(wavenumber * (PetscReal)i);
1020 PetscCall(DMDAVecRestoreArray(user->
fda, user->
Ucat, &ucat));
1026 PetscCall(DMDAVecGetArrayRead(user->
da, user->
CS, &coefficient));
1027 reference = coefficient[1][1][1];
1028 PetscCall(
PicurvAssertBool((PetscBool)(PetscIsNormalReal(reference) || reference == 0.0),
1029 "the dynamic coefficient must be finite"));
1031 reference <= simCtx->les_config.max_cs *
1033 "clamping should keep the coefficient inside its ceiling"));
1034 for (PetscInt k = 1; k < 6; ++k)
1035 for (PetscInt j = 1; j < 6; ++j)
1036 for (PetscInt i = 1; i < 6; ++i) {
1038 "global averaging should give the block one coefficient"));
1040 PetscCall(DMDAVecRestoreArrayRead(user->
da, user->
CS, &coefficient));
1043 PetscFunctionReturn(0);
1057 PetscReal ***cs = NULL;
1058 const PetscReal gamma = 0.1;
1060 PetscFunctionBeginUser;
1070 PetscCall(DMCreateGlobalVector(user->
da, &user->
CS));
1071 PetscCall(DMCreateLocalVector(user->
da, &user->
lCs));
1072 PetscCall(VecSet(user->
Aj, 1.0));
1073 PetscCall(VecSet(user->
lAj, 1.0));
1074 PetscCall(VecSet(user->
lNvert, 0.0));
1079 PetscCall(DMDAVecGetArray(user->
fda, user->
Ucat, &ucat));
1080 for (PetscInt k = user->
info.zs; k < user->
info.zs + user->
info.zm; ++k)
1081 for (PetscInt j = user->
info.ys; j < user->
info.ys + user->
info.ym; ++j)
1082 for (PetscInt i = user->
info.xs; i < user->
info.xs + user->
info.xm; ++i) {
1083 ucat[k][j][i].
x = -gamma * (PetscReal)i;
1084 ucat[k][j][i].
y = gamma * (PetscReal)j;
1085 ucat[k][j][i].
z = 0.0;
1087 PetscCall(DMDAVecRestoreArray(user->
fda, user->
Ucat, &ucat));
1088 PetscCall(DMGlobalToLocalBegin(user->
fda, user->
Ucat, INSERT_VALUES, user->
lUcat));
1089 PetscCall(DMGlobalToLocalEnd(user->
fda, user->
Ucat, INSERT_VALUES, user->
lUcat));
1093 PetscCall(DMDAVecGetArrayRead(user->
da, user->
CS, &cs));
1094 *coefficient = cs[4][4][4];
1095 PetscCall(DMDAVecRestoreArrayRead(user->
da, user->
CS, &cs));
1097 PetscFunctionReturn(0);
1110 PetscReal coefficient_2 = 0.0, coefficient_4 = 0.0;
1111 const PetscReal alpha_2 = PetscPowReal(2.0, 4.0 / 3.0);
1112 const PetscReal alpha_4 = PetscPowReal(4.0, 4.0 / 3.0);
1114 PetscFunctionBeginUser;
1117 PetscCall(
PicurvAssertBool((PetscBool)(coefficient_2 > 0.0 && coefficient_4 > 0.0),
1118 "a linear strain should give a positive Simpson coefficient"));
1119 PetscCall(
PicurvAssertRealNear((alpha_4 - 1.0) / (alpha_2 - 1.0), coefficient_2 / coefficient_4,
1121 "the Simpson coefficient should scale as 1/(ratio^(4/3) - 1)"));
1122 PetscFunctionReturn(0);
1130 PetscErrorCode ierr;
1157 ierr = PetscInitialize(&argc, &argv, NULL,
"PICurv LES closure tests");
1162 ierr =
PicurvRunTests(
"unit-les", cases,
sizeof(cases) /
sizeof(cases[0]));
1168 ierr = PetscFinalize();
PetscErrorCode SynchronizePeriodicCellFields(UserCtx *user, PetscInt num_fields, const FieldId field_ids[])
Synchronizes periodic endpoint cells for a list of cell-centered fields.
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.
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.
PetscErrorCode ComputeCellEdgeVectors(PetscReal ajc, Cmpnts csi, Cmpnts eta, Cmpnts zet, Cmpnts edges[3])
Computes a cell's edge vectors along its three grid directions.
FieldId
Compile-time identity for a catalogued Eulerian field.
PetscReal SymTensorNormSq(SymTensor t)
Returns the squared Frobenius norm t_ij t_ij.
PetscErrorCode ComputeEddyViscosityLES(UserCtx *user)
Computes the turbulent eddy viscosity for one block.
SymTensor SymTensorCombine(PetscReal a, SymTensor x, PetscReal b, SymTensor y)
Forms the linear combination a*x + b*y.
SymTensor SymTensorSelfOuter(Cmpnts v)
Forms a symmetric tensor from a vector's outer product with itself.
SymTensor SymTensorDeviator(SymTensor t)
Removes the isotropic part, returning t_ij - (1/3) delta_ij t_kk.
PetscReal SubgridKineticEnergy(PetscReal yoshizawa_ci, PetscReal delta, PetscReal strain_magnitude)
Returns the modelled subgrid kinetic energy at a cell.
PetscReal SymTensorTrace(SymTensor t)
Returns the trace t_kk.
PetscReal EddyViscosityFromCoefficient(PetscReal coefficient, PetscReal delta, PetscReal strain_magnitude, PetscReal molecular_viscosity, PetscReal min_viscosity_ratio)
Builds the eddy viscosity from a model coefficient.
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.
PetscReal ClipModelCoefficient(PetscReal coefficient, const LESConfig *config, PetscBool *limited)
Applies the configured admissible range to one model coefficient.
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.
PetscErrorCode StrainRateFromGradients(Cmpnts dudx, Cmpnts dvdx, Cmpnts dwdx, SymTensor *strain, PetscReal *magnitude)
Builds the strain-rate tensor and its magnitude from a velocity gradient.
PetscErrorCode ResolveLESAveragingDirections(UserCtx *user, PetscBool direction[3])
Resolves which logical directions the dynamic coefficient is averaged over.
SymTensor LeonardStress(Cmpnts velocity_filtered, SymTensor velocity_product_filtered)
Forms the Leonard stress L_ij = (u_i u_j)^ - u^_i u^_j.
PetscReal SymTensorContract(SymTensor a, SymTensor b)
Contracts two symmetric tensors as a_ij b_ij.
PetscErrorCode ComputeSmagorinskyConstant(UserCtx *user)
Computes the dynamic Smagorinsky coefficient field for one block.
PetscReal WALEEddyViscosity(Cmpnts dudx, Cmpnts dvdx, Cmpnts dwdx, PetscReal delta, PetscReal coefficient)
WALE eddy viscosity (Nicoud & Ducros 1999) from the resolved velocity gradient.
PetscReal VremanEddyViscosity(Cmpnts dudx, Cmpnts dvdx, Cmpnts dwdx, const Cmpnts edges[3], PetscReal coefficient)
Vreman eddy viscosity (Vreman 2004), weighting each grid direction by its own spacing.
PetscErrorCode UpdateLocalGhosts(UserCtx *user, FieldId field_id)
Updates the local vector (including ghost points) from its corresponding global vector.
PetscErrorCode LESConfigSetDefaults(LESConfig *config)
Installs the shipped defaults for every LES closure parameter.
Spatial target resolution for the field-statistics pipeline.
@ 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 SetInteriorScalar(UserCtx *user, Vec local, PetscReal value)
Writes a scalar into every cell the closure treats as owned and interior.
static PetscErrorCode SimpsonCoefficientOnLinearField(PetscReal width_ratio, PetscReal *coefficient)
Runs the dynamic procedure with the Simpson kernel on a linear field and returns the raw coefficient ...
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 TestHomogeneousAveragingDerivesPeriodicAxes(void)
Tests that homogeneous averaging falls back to the block's periodic axes.
static PetscErrorCode TestGermanoModelTensorOnConstantStrain(void)
Tests the model tensor where the two filter terms provably coincide.
static void DeclarePeriodicFaces(UserCtx *user, PetscBool xi, PetscBool eta, PetscBool zeta)
Declares periodic boundary pairs on the axes a test wants homogeneous.
int main(int argc, char **argv)
Entry point for the LES closure suite.
static PetscErrorCode TestEddyViscosityFloorBoundsTotalViscosity(void)
Tests eddy-viscosity assembly and the total-viscosity floor.
static Cmpnts RotateForInvarianceTest(Cmpnts v)
Rotates a vector by Rz(40 deg) then Rx(25 deg).
static PetscErrorCode TestDynamicProcedureVanishesOnUniformFlow(void)
Tests that the dynamic procedure returns a zero coefficient on uniform flow.
static PetscErrorCode TestScottiFilterWidthMatchesClosedForm(void)
Tests the Scotti aspect-ratio correction against its closed form.
static PetscErrorCode TestLeonardStressVanishesOnUniformFlow(void)
Tests that the Leonard stress vanishes on a uniform velocity field.
static PetscErrorCode FillLocalScalar(UserCtx *user, Vec local, PetscReal value)
Fills a ghosted local scalar field over the whole local array.
static PetscErrorCode TestSymTensorAlgebra(void)
Tests the symmetric-tensor primitives against hand-computed values.
static PetscErrorCode TestConstantModelNeedsNoCoefficientField(void)
Tests that the constant model builds its viscosity without a coefficient field.
static PetscErrorCode TestAverageRatioIgnoresPeriodicDuplicatePlanes(void)
Tests that a spatial average ignores the periodic duplicate planes.
static PetscErrorCode TestStrainRateFromGradients(void)
Tests strain-rate assembly and its magnitude for a known velocity gradient.
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 PetscErrorCode TestDynamicProcedureSimpsonRatioCountsFilteredDirections(void)
Tests that the Simpson kernel's width ratio applies to the two directions it filters.
static PetscErrorCode TestAveragingModesSelectTheirOwnDirections(void)
Tests that local and global averaging ignore the configured direction list.
static PetscErrorCode TestSubgridKineticEnergy(void)
Tests the Yoshizawa subgrid kinetic energy against its closed form.
static void RotateGradientForInvarianceTest(const Cmpnts rows[3], Cmpnts out[3])
Rotates a velocity gradient g_ij = du_i/dx_j as R g R^T, rows passed as dudx, dvdx,...
static PetscErrorCode TestFilterWidthModelsSeparateOnStretchedCell(void)
Tests that the three filter-width models separate on an anisotropic cell.
static PetscErrorCode TestWALEAndVremanKernels(void)
Tests the WALE and Vreman kernels against independent evaluations.
static PetscErrorCode TestDynamicProcedureRejectsConstantModel(void)
Tests that the dynamic procedure refuses to run for the constant model.
static PetscErrorCode TestGermanoModelTensorUsesFilteredProduct(void)
Tests that the model tensor uses the filtered product, not the filtered factors.
static void ParallelepipedMetrics(Cmpnts a, Cmpnts b, Cmpnts c, Cmpnts *csi, Cmpnts *eta, Cmpnts *zet, PetscReal *aj)
Face-area vectors and inverse volume of the parallelepiped spanned by a, b, c.
static PetscErrorCode TestFilterWidthIsIndependentOfCellOrientation(void)
Tests that a filter width is a property of the cell, not of its orientation.
static PetscErrorCode TestAverageRatioLocalIsPointwise(void)
Tests that an empty direction set reproduces the pointwise local model.
static PetscErrorCode TestDynamicProcedureGlobalAverageIsUniform(void)
Tests the dynamic procedure end to end on a periodic sheared field.
static PetscErrorCode TestClipModelCoefficientModes(void)
Tests each limiting mode, including the sign that carries backscatter.
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 PicurvAssertRealNear(PetscReal expected, PetscReal actual, PetscReal tol, const char *context)
Asserts that two real values agree within tolerance.
PetscErrorCode PicurvDestroyMinimalContexts(SimCtx **simCtx_ptr, UserCtx **user_ptr)
Destroys minimal SimCtx/UserCtx fixtures and all owned PETSc objects.
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.
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.
PetscErrorCode PicurvAssertBool(PetscBool value, const char *context)
Asserts that one boolean condition is true.
Shared declarations for the PICurv C test fixture and assertion layer.
Named test case descriptor consumed by PicurvRunTests.
BoundaryFaceConfig boundary_faces[6]
LESTestFilterKernel test_filter_kernel
Discrete test-filter stencil.
LESConfig les_config
Parameters of the LES closure selected by les.
LESTestFilterKernel
Selects the discrete test-filter kernel used by the dynamic procedure.
@ LES_TEST_FILTER_SIMPSON_IK
@ LES_TEST_FILTER_VOLUME_WEIGHTED_BOX
PetscReal max_cs
Ceiling on Cs under LES_CLIP_CLAMP.
LESClipMode clip_mode
Admissible range for the coefficient.
PetscBool averaging_direction[3]
Averaged-over logical directions (xi, eta, zeta).
@ LES_AVERAGING_HOMOGENEOUS
PetscReal constant_cs
Fixed Cs for CONSTANT_SMAGORINSKY; unused by the dynamic model.
PetscReal test_filter_width_ratio
Test-to-grid ratio per filtered direction; alpha is its square for the box, ratio^(4/3) for Simpson.
LESAveragingMode averaging_mode
Averaging set for the Germano contractions.
PetscInt les
Active LES closure; an LESModelType value.
LESFilterWidthModel
Selects how a cell's grid filter width is derived from its metrics.
@ LES_FILTER_WIDTH_SCOTTI
@ LES_FILTER_WIDTH_GEOMETRIC_MEAN
@ LES_FILTER_WIDTH_CUBE_ROOT_VOLUME
@ LES_FILTER_WIDTH_MAX_EDGE
BCFace
Identifies the six logical faces of a structured computational block.
A 3D point or vector with PetscScalar components.
Every user-selectable parameter of the LES closure.
The master context for the entire simulation.
A symmetric second-order tensor stored by its six independent components.
User-defined context containing data specific to a single computational grid level.
double nu_t(double yplus)
Computes turbulent eddy viscosity ratio (ν_t / ν)