PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
test_postprocessing.c
Go to the documentation of this file.
1/**
2 * @file test_postprocessing.c
3 * @brief C unit tests for post-processing kernel functions.
4 */
5
6#include "test_support.h"
7
9/**
10 * @brief Tests specific turbulent kinetic energy computation on particle data.
11 */
12
13static PetscErrorCode TestComputeSpecificKE(void)
14{
15 SimCtx *simCtx = NULL;
16 UserCtx *user = NULL;
17 PetscReal (*vel_arr)[3] = NULL;
18 PetscScalar *ske_arr = NULL;
19
20 PetscFunctionBeginUser;
21 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
22 PetscCall(PicurvCreateSwarmPair(user, 2, "ske"));
23
24 PetscCall(DMSwarmGetField(user->swarm, "velocity", NULL, NULL, (void *)&vel_arr));
25 vel_arr[0][0] = 1.0;
26 vel_arr[0][1] = 2.0;
27 vel_arr[0][2] = 2.0;
28 vel_arr[1][0] = 0.0;
29 vel_arr[1][1] = 3.0;
30 vel_arr[1][2] = 4.0;
31 PetscCall(DMSwarmRestoreField(user->swarm, "velocity", NULL, NULL, (void *)&vel_arr));
32
33 PetscCall(ComputeSpecificKE(user, "velocity", "ske"));
34 PetscCall(DMSwarmGetField(user->post_swarm, "ske", NULL, NULL, (void *)&ske_arr));
35 PetscCall(PicurvAssertRealNear(4.5, ske_arr[0], 1.0e-12, "ComputeSpecificKE first particle"));
36 PetscCall(PicurvAssertRealNear(12.5, ske_arr[1], 1.0e-12, "ComputeSpecificKE second particle"));
37 PetscCall(DMSwarmRestoreField(user->post_swarm, "ske", NULL, NULL, (void *)&ske_arr));
38
39 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
40 PetscFunctionReturn(0);
41}
42/**
43 * @brief Tests particle displacement computation against reference positions.
44 */
45
46static PetscErrorCode TestComputeDisplacement(void)
47{
48 SimCtx *simCtx = NULL;
49 UserCtx *user = NULL;
50 PetscReal (*pos_arr)[3] = NULL;
51 PetscScalar *disp_arr = NULL;
52
53 PetscFunctionBeginUser;
54 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
55 PetscCall(PicurvCreateSwarmPair(user, 2, "disp"));
56
57 simCtx->psrc_x = 1.0;
58 simCtx->psrc_y = 2.0;
59 simCtx->psrc_z = 3.0;
60
61 PetscCall(DMSwarmGetField(user->swarm, "position", NULL, NULL, (void *)&pos_arr));
62 pos_arr[0][0] = 1.0; pos_arr[0][1] = 2.0; pos_arr[0][2] = 3.0;
63 pos_arr[1][0] = 4.0; pos_arr[1][1] = 6.0; pos_arr[1][2] = 3.0;
64 PetscCall(DMSwarmRestoreField(user->swarm, "position", NULL, NULL, (void *)&pos_arr));
65
66 PetscCall(ComputeDisplacement(user, "disp"));
67 PetscCall(DMSwarmGetField(user->post_swarm, "disp", NULL, NULL, (void *)&disp_arr));
68 PetscCall(PicurvAssertRealNear(0.0, PetscRealPart(disp_arr[0]), 1.0e-12, "ComputeDisplacement first particle"));
69 PetscCall(PicurvAssertRealNear(5.0, PetscRealPart(disp_arr[1]), 1.0e-12, "ComputeDisplacement second particle"));
70 PetscCall(DMSwarmRestoreField(user->post_swarm, "disp", NULL, NULL, (void *)&disp_arr));
71
72 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
73 PetscFunctionReturn(0);
74}
75/**
76 * @brief Tests nodal averaging of scalar cell-centered data.
77 */
78
79static PetscErrorCode TestComputeNodalAverageScalar(void)
80{
81 SimCtx *simCtx = NULL;
82 UserCtx *user = NULL;
83 const PetscScalar ***p_nodal_arr = NULL;
84
85 PetscFunctionBeginUser;
86 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
87 PetscCall(VecSet(user->P, 7.0));
88 PetscCall(VecSet(user->P_nodal, -1.0));
89
90 PetscCall(ComputeNodalAverage(user, "P", "P_nodal"));
91
92 PetscCall(DMDAVecGetArrayRead(user->da, user->P_nodal, (void *)&p_nodal_arr));
93 PetscCall(PicurvAssertRealNear(7.0, PetscRealPart(p_nodal_arr[0][0][0]), 1.0e-12, "ComputeNodalAverage interior node"));
94 PetscCall(PicurvAssertRealNear(-1.0, PetscRealPart(p_nodal_arr[user->KM][user->JM][user->IM]), 1.0e-12,
95 "ComputeNodalAverage untouched non-physical boundary node"));
96 PetscCall(DMDAVecRestoreArrayRead(user->da, user->P_nodal, (void *)&p_nodal_arr));
97
98 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
99 PetscFunctionReturn(0);
100}
101/**
102 * @brief Tests that the cell-centred Q-criterion can be averaged to nodes.
103 *
104 * Qcrit is computed at cell centres and a .vts carries point data only, so writing it
105 * directly placed every value half a cell from its node. It now goes through the same
106 * nodal average as P, which needs it catalogued for the ghost refresh.
107 */
108static PetscErrorCode TestComputeNodalAverageQcrit(void)
109{
110 SimCtx *simCtx = NULL;
111 UserCtx *user = NULL;
112 const PetscScalar ***q_nodal_arr = NULL;
113
114 PetscFunctionBeginUser;
115 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
116 PetscCall(VecSet(user->Qcrit, 3.0));
117 PetscCall(VecSet(user->Qcrit_nodal, -1.0));
118
119 PetscCall(ComputeNodalAverage(user, "Qcrit", "Qcrit_nodal"));
120
121 PetscCall(DMDAVecGetArrayRead(user->da, user->Qcrit_nodal, (void *)&q_nodal_arr));
122 PetscCall(PicurvAssertRealNear(3.0, PetscRealPart(q_nodal_arr[0][0][0]), 1.0e-12,
123 "ComputeNodalAverage should average Qcrit onto nodes"));
124 PetscCall(DMDAVecRestoreArrayRead(user->da, user->Qcrit_nodal, (void *)&q_nodal_arr));
125
126 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
127 PetscFunctionReturn(0);
128}
129/**
130 * @brief Tests normalization of one field relative to a reference field.
131 */
132
133static PetscErrorCode TestNormalizeRelativeField(void)
134{
135 SimCtx *simCtx = NULL;
136 UserCtx *user = NULL;
137 PetscInt ref_idx = 0;
138 PetscScalar ref_value = 0.0;
139 PetscReal vmin = 0.0, vmax = 0.0;
140
141 PetscFunctionBeginUser;
142 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
143 PetscCall(PetscCalloc1(1, &simCtx->pps));
144 simCtx->pps->reference[0] = 1;
145 simCtx->pps->reference[1] = 1;
146 simCtx->pps->reference[2] = 1;
147
148 PetscCall(VecSet(user->P, 10.0));
149 ref_idx = simCtx->pps->reference[2] * (user->info.mx * user->info.my) +
150 simCtx->pps->reference[1] * user->info.mx +
151 simCtx->pps->reference[0];
152 PetscCall(VecSetValue(user->P, ref_idx, 4.0, INSERT_VALUES));
153 PetscCall(VecAssemblyBegin(user->P));
154 PetscCall(VecAssemblyEnd(user->P));
155
156 PetscCall(NormalizeRelativeField(user, "P"));
157
158 PetscCall(VecGetValues(user->P, 1, &ref_idx, &ref_value));
159 PetscCall(PicurvAssertRealNear(0.0, PetscRealPart(ref_value), 1.0e-12, "NormalizeRelativeField reference value"));
160 PetscCall(VecMin(user->P, NULL, &vmin));
161 PetscCall(VecMax(user->P, NULL, &vmax));
162 PetscCall(PicurvAssertRealNear(0.0, vmin, 1.0e-12, "NormalizeRelativeField minimum"));
163 PetscCall(PicurvAssertRealNear(6.0, vmax, 1.0e-12, "NormalizeRelativeField maximum"));
164
165 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
166 PetscFunctionReturn(0);
167}
168/**
169 * @brief Tests dimensionalization of pressure data from nondimensional values.
170 */
171
172static PetscErrorCode TestDimensionalizePressureField(void)
173{
174 SimCtx *simCtx = NULL;
175 UserCtx *user = NULL;
176
177 PetscFunctionBeginUser;
178 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
179 simCtx->scaling.L_ref = 1.0;
180 simCtx->scaling.U_ref = 1.0;
181 simCtx->scaling.rho_ref = 3.0;
182 simCtx->scaling.P_ref = 3.0;
183
184 PetscCall(VecSet(user->P, 2.0));
185 PetscCall(DimensionalizeField(user, "P"));
186 PetscCall(PicurvAssertVecConstant(user->P, 6.0, 1.0e-12, "DimensionalizeField should scale pressure by rho U^2"));
187
188 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
189 PetscFunctionReturn(0);
190}
191/**
192 * @brief Tests that each field is scaled by the dimension its catalog entry records.
193 * @details At L=2, U=3, rho=5: velocity scales by U, a face flux by U L^2, pressure and
194 * the pressure correction by rho U^2, and the dimensionless solid marker not at
195 * all. A name in neither catalog is refused instead of being left unscaled.
196 */
198{
199 SimCtx *simCtx = NULL;
200 UserCtx *user = NULL;
201 PetscErrorCode refused = 0;
202
203 PetscFunctionBeginUser;
204 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
205 simCtx->scaling.L_ref = 2.0;
206 simCtx->scaling.U_ref = 3.0;
207 simCtx->scaling.rho_ref = 5.0;
208 simCtx->scaling.P_ref = 45.0;
209 PetscCall(VecSet(user->Ucat, 1.0));
210 PetscCall(VecSet(user->Ucont, 1.0));
211 PetscCall(VecSet(user->Phi, 1.0));
212 PetscCall(VecSet(user->Nvert, 1.0));
213
214 PetscCall(DimensionalizeField(user, "Ucat"));
215 PetscCall(DimensionalizeField(user, "Ucont"));
216 PetscCall(DimensionalizeField(user, "Phi"));
217 PetscCall(DimensionalizeField(user, "Nvert"));
218 PetscCall(PicurvAssertVecConstant(user->Ucat, 3.0, 1.0e-12, "velocity scales by U"));
219 PetscCall(PicurvAssertVecConstant(user->Ucont, 12.0, 1.0e-12, "a face flux scales by U L^2"));
220 PetscCall(PicurvAssertVecConstant(user->Phi, 45.0, 1.0e-12, "the pressure correction is a pressure"));
221 PetscCall(PicurvAssertVecConstant(user->Nvert, 1.0, 1.0e-12, "the solid marker is dimensionless"));
222
223 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
224 refused = DimensionalizeField(user, "NotAField");
225 PetscCall(PetscPopErrorHandler());
226 PetscCall(PicurvAssertIntEqual(PETSC_ERR_ARG_UNKNOWN_TYPE, refused,
227 "an uncatalogued name is refused, not silently left unscaled"));
228
229 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
230 PetscFunctionReturn(0);
231}
232
233/**
234 * @brief Tests that a dimensionalized Q-criterion carries (U_ref/L_ref)^2, not U_ref^2.
235 * @details Ucat is already dimensional when Q is computed, but the metrics are not, so
236 * the result must be divided by L_ref^2.
237 */
239{
240 SimCtx *simCtx = NULL;
241 UserCtx *user = NULL;
242 Cmpnts ***ucat = NULL;
243 Vec nondimensional = NULL;
244 PostProcessParams *pps = NULL;
245 PetscReal difference = 0.0, reference = 0.0;
246
247 PetscFunctionBeginUser;
248 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 6, 6, 6));
249 PetscCall(PicurvPopulateIdentityMetrics(user));
250 PetscCall(DMDAVecGetArray(user->fda, user->Ucat, &ucat));
251 for (PetscInt k = user->info.zs; k < user->info.zs + user->info.zm; k++)
252 for (PetscInt j = user->info.ys; j < user->info.ys + user->info.ym; j++)
253 for (PetscInt i = user->info.xs; i < user->info.xs + user->info.xm; i++)
254 ucat[k][j][i] = (Cmpnts){-(PetscReal)j, (PetscReal)i, 0.0}; /* solid-body rotation */
255 PetscCall(DMDAVecRestoreArray(user->fda, user->Ucat, &ucat));
256
257 PetscCall(ComputeQCriterion(user));
258 PetscCall(VecDuplicate(user->Qcrit, &nondimensional));
259 PetscCall(VecCopy(user->Qcrit, nondimensional));
260 PetscCall(VecNorm(nondimensional, NORM_INFINITY, &reference));
261 PetscCall(PicurvAssertBool((PetscBool)(reference > 0.1), "a rotating field should have a positive Q"));
262
263 PetscCall(PetscNew(&pps));
264 pps->dimensionalize = PETSC_TRUE;
265 simCtx->pps = pps;
266 simCtx->scaling.L_ref = 2.0;
267 PetscCall(ComputeQCriterion(user));
268 PetscCall(VecAXPY(nondimensional, -4.0, user->Qcrit));
269 PetscCall(VecNorm(nondimensional, NORM_INFINITY, &difference));
270 PetscCall(PicurvAssertRealNear(0.0, difference, 1.0e-12 * reference,
271 "dimensionalized Q must be the nondimensional Q over L_ref^2"));
272
273 simCtx->pps = NULL;
274 PetscCall(PetscFree(pps));
275 PetscCall(VecDestroy(&nondimensional));
276 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
277 PetscFunctionReturn(0);
278}
279
280/**
281 * @brief Tests Q-criterion computation for a quiescent velocity field.
282 */
283
284static PetscErrorCode TestComputeQCriterionZeroFlow(void)
285{
286 SimCtx *simCtx = NULL;
287 UserCtx *user = NULL;
288
289 PetscFunctionBeginUser;
290 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
291
292 PetscCall(VecSet(user->Aj, 1.0));
293 PetscCall(DMGlobalToLocalBegin(user->da, user->Aj, INSERT_VALUES, user->lAj));
294 PetscCall(DMGlobalToLocalEnd(user->da, user->Aj, INSERT_VALUES, user->lAj));
295
296 PetscCall(VecSet(user->Ucat, 0.0));
297 PetscCall(DMGlobalToLocalBegin(user->fda, user->Ucat, INSERT_VALUES, user->lUcat));
298 PetscCall(DMGlobalToLocalEnd(user->fda, user->Ucat, INSERT_VALUES, user->lUcat));
299 PetscCall(VecSet(user->Nvert, 0.0));
300 PetscCall(DMGlobalToLocalBegin(user->da, user->Nvert, INSERT_VALUES, user->lNvert));
301 PetscCall(DMGlobalToLocalEnd(user->da, user->Nvert, INSERT_VALUES, user->lNvert));
302
303 PetscCall(ComputeQCriterion(user));
304 PetscCall(PicurvAssertVecConstant(user->Qcrit, 0.0, 1.0e-12, "ComputeQCriterion should be zero for uniform flow"));
305
306 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
307 PetscFunctionReturn(0);
308}
309/**
310 * @brief Runs the unit-post PETSc test binary.
311 */
312
313int main(int argc, char **argv)
314{
315 PetscErrorCode ierr;
316 const PicurvTestCase cases[] = {
317 {"compute-specific-ke", TestComputeSpecificKE},
318 {"compute-displacement", TestComputeDisplacement},
319 {"compute-nodal-average-scalar", TestComputeNodalAverageScalar},
320 {"compute-nodal-average-qcrit", TestComputeNodalAverageQcrit},
321 {"normalize-relative-field", TestNormalizeRelativeField},
322 {"dimensionalize-pressure-field", TestDimensionalizePressureField},
323 {"compute-qcriterion-zero-flow", TestComputeQCriterionZeroFlow},
324 {"dimensionalize-field-uses-catalog-dimensions", TestDimensionalizeFieldUsesCatalogDimensions},
325 {"compute-qcriterion-dimensionalized-scales-by-length-squared", TestComputeQCriterionDimensionalizedScalesByLengthSquared},
326 };
327
328 ierr = PetscInitialize(&argc, &argv, NULL, "PICurv post-processing tests");
329 if (ierr) {
330 return (int)ierr;
331 }
332
333 ierr = PicurvRunTests("unit-post", cases, sizeof(cases) / sizeof(cases[0]));
334 if (ierr) {
335 PetscFinalize();
336 return (int)ierr;
337 }
338
339 ierr = PetscFinalize();
340 return (int)ierr;
341}
PetscErrorCode ComputeQCriterion(UserCtx *user)
Computes the Q-criterion diagnostic from the local velocity-gradient tensor.
PetscErrorCode ComputeSpecificKE(UserCtx *user, const char *velocity_field, const char *ske_field)
Computes the specific kinetic energy (KE per unit mass) for each particle.
PetscErrorCode ComputeDisplacement(UserCtx *user, const char *disp_field)
Computes the displacement magnitude |r_i - r_0| for each particle (per-particle VTK kernel).
PetscErrorCode NormalizeRelativeField(UserCtx *user, const char *relative_field_name)
Normalizes pressure using the value at the configured logical grid point.
PetscErrorCode DimensionalizeField(UserCtx *user, const char *field_name)
Scales a specified field from non-dimensional to dimensional units in-place.
PetscErrorCode ComputeNodalAverage(UserCtx *user, const char *in_field_name, const char *out_field_name)
Interpolates a cell-centered field to nodal locations using local stencil averaging.
static PetscErrorCode TestComputeNodalAverageQcrit(void)
Tests that the cell-centred Q-criterion can be averaged to nodes.
int main(int argc, char **argv)
Runs the unit-post PETSc test binary.
static PetscErrorCode TestComputeQCriterionZeroFlow(void)
Tests Q-criterion computation for a quiescent velocity field.
static PetscErrorCode TestComputeSpecificKE(void)
Tests specific turbulent kinetic energy computation on particle data.
static PetscErrorCode TestComputeDisplacement(void)
Tests particle displacement computation against reference positions.
static PetscErrorCode TestDimensionalizeFieldUsesCatalogDimensions(void)
Tests that each field is scaled by the dimension its catalog entry records.
static PetscErrorCode TestComputeQCriterionDimensionalizedScalesByLengthSquared(void)
Tests that a dimensionalized Q-criterion carries (U_ref/L_ref)^2, not U_ref^2.
static PetscErrorCode TestDimensionalizePressureField(void)
Tests dimensionalization of pressure data from nondimensional values.
static PetscErrorCode TestComputeNodalAverageScalar(void)
Tests nodal averaging of scalar cell-centered data.
static PetscErrorCode TestNormalizeRelativeField(void)
Tests normalization of one field relative to a reference field.
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 PicurvCreateSwarmPair(UserCtx *user, PetscInt nlocal, const char *post_field_name)
Creates matched solver and post-processing swarms for tests.
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 PicurvAssertVecConstant(Vec vec, PetscScalar expected, PetscReal tol, const char *context)
Asserts that a PETSc vector is spatially constant within tolerance.
PetscErrorCode PicurvAssertIntEqual(PetscInt expected, PetscInt actual, const char *context)
Asserts that two integer values are equal.
PetscErrorCode PicurvPopulateIdentityMetrics(UserCtx *user)
Populates identity metric vectors on the minimal grid fixture.
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.
Vec Qcrit_nodal
Q-criterion averaged to grid nodes; the field a .vts can place correctly.
Definition variables.h:1179
Vec P_nodal
Definition variables.h:1176
Vec lNvert
Definition variables.h:1113
PetscBool dimensionalize
Whether derived output leaves non-dimensional form, from global_operations.dimensionalize.
Definition variables.h:789
PetscInt KM
Definition variables.h:1088
PetscReal L_ref
Definition variables.h:841
PetscReal psrc_x
Definition variables.h:945
DM post_swarm
Definition variables.h:1175
PetscInt reference[3]
Definition variables.h:798
Vec Ucont
Definition variables.h:1113
Vec Qcrit
Definition variables.h:1178
PetscReal psrc_z
Point source location for PARTICLE_INIT_POINT_SOURCE.
Definition variables.h:945
ScalingCtx scaling
Definition variables.h:946
PetscInt JM
Definition variables.h:1088
PetscReal psrc_y
Definition variables.h:945
DMDALocalInfo info
Definition variables.h:1086
Vec lUcat
Definition variables.h:1113
PostProcessParams * pps
Definition variables.h:1058
PetscInt IM
Definition variables.h:1088
Vec Nvert
Definition variables.h:1113
PetscReal P_ref
Definition variables.h:844
PetscReal rho_ref
Definition variables.h:843
PetscReal U_ref
Definition variables.h:842
A 3D point or vector with PetscScalar components.
Definition variables.h:121
Holds all configuration parameters for a post-processing run.
Definition variables.h:754
The master context for the entire simulation.
Definition variables.h:859
User-defined context containing data specific to a single computational grid level.
Definition variables.h:1074