15static PetscReal
ScalarValue(PetscInt i, PetscInt j, PetscInt k)
17 return (PetscReal)(i + 10 * j + 100 * k);
24 PetscReal global_error = 0.0;
26 PetscFunctionBeginUser;
27 PetscCallMPI(MPI_Allreduce(&local_error, &global_error, 1, MPIU_REAL, MPI_MAX,
29 PetscCall(
PicurvAssertBool((PetscBool)(global_error <= tolerance), context));
30 PetscFunctionReturn(0);
39 PetscReal ***pressure = NULL;
41 const PetscReal ***pressure_result = NULL;
42 const PetscReal ***pressure_nodal = NULL;
43 const PetscReal ***qcrit = NULL;
44 const Cmpnts ***velocity_nodal = NULL;
45 PetscReal pressure_error = 0.0;
46 PetscReal pressure_nodal_error = 0.0;
47 PetscReal velocity_nodal_error = 0.0;
48 PetscReal qcrit_error = 0.0;
49 PetscInt local_physical_nodes = 0;
50 PetscInt global_physical_nodes = 0;
51 PetscInt local_q_points = 0;
52 PetscInt global_q_points = 0;
54 PetscFunctionBeginUser;
56 PetscCall(PetscCalloc1(1, &simCtx->
pps));
66 PetscCall(DMDAVecGetArray(user->
da, user->
P, &pressure));
67 PetscCall(DMDAVecGetArray(user->
fda, user->
Ucat, &velocity));
68 for (PetscInt k = user->
info.zs; k < user->
info.zs + user->
info.zm; ++k) {
69 for (PetscInt j = user->
info.ys; j < user->
info.ys + user->
info.ym; ++j) {
70 for (PetscInt i = user->
info.xs; i < user->
info.xs + user->
info.xm; ++i) {
72 velocity[k][j][i] = (
Cmpnts){1.0, 2.0, 3.0};
76 PetscCall(DMDAVecRestoreArray(user->
fda, user->
Ucat, &velocity));
77 PetscCall(DMDAVecRestoreArray(user->
da, user->
P, &pressure));
86 PetscCall(PetscStrncpy(
88 "NormalizeRelativeField:P;"
89 "CellToNodeAverage:P>P_nodal;CellToNodeAverage:Ucat>Ucat_nodal;"
94 PetscCall(DMDAVecGetArrayRead(user->
da, user->
P, &pressure_result));
95 PetscCall(DMDAVecGetArrayRead(user->
da, user->
P_nodal, &pressure_nodal));
96 PetscCall(DMDAVecGetArrayRead(user->
fda, user->
Ucat_nodal, &velocity_nodal));
97 PetscCall(DMDAVecGetArrayRead(user->
da, user->
Qcrit, &qcrit));
98 for (PetscInt k = user->
info.zs; k < user->
info.zs + user->
info.zm; ++k) {
99 for (PetscInt j = user->
info.ys; j < user->
info.ys + user->
info.ym; ++j) {
100 for (PetscInt i = user->
info.xs; i < user->
info.xs + user->
info.xm; ++i) {
101 const PetscBool physical_node =
102 (PetscBool)(i < user->info.mx - 1 && j < user->info.my - 1 &&
103 k < user->info.mz - 1);
104 const PetscBool q_point =
105 (PetscBool)(i >= 1 && i < user->info.mx - 1 &&
106 j >= 1 && j < user->info.my - 1 &&
107 k >= 1 && k < user->info.mz - 1);
108 const PetscReal expected_pressure = 2.0 * (
ScalarValue(i, j, k) - 111.0);
110 pressure_error = PetscMax(
112 PetscAbsReal(pressure_result[k][j][i] - expected_pressure));
114 const PetscReal expected_nodal =
117 pressure_nodal_error = PetscMax(
118 pressure_nodal_error,
119 PetscAbsReal(pressure_nodal[k][j][i] - expected_nodal));
120 velocity_nodal_error = PetscMax(
121 velocity_nodal_error,
122 PetscAbsReal(velocity_nodal[k][j][i].x - 1.0));
123 velocity_nodal_error = PetscMax(
124 velocity_nodal_error,
125 PetscAbsReal(velocity_nodal[k][j][i].y - 2.0));
126 velocity_nodal_error = PetscMax(
127 velocity_nodal_error,
128 PetscAbsReal(velocity_nodal[k][j][i].z - 3.0));
129 ++local_physical_nodes;
131 pressure_nodal_error = PetscMax(
132 pressure_nodal_error,
133 PetscAbsReal(pressure_nodal[k][j][i] -
kSentinel));
134 velocity_nodal_error = PetscMax(
135 velocity_nodal_error,
136 PetscAbsReal(velocity_nodal[k][j][i].x -
kSentinel));
137 velocity_nodal_error = PetscMax(
138 velocity_nodal_error,
139 PetscAbsReal(velocity_nodal[k][j][i].y -
kSentinel));
140 velocity_nodal_error = PetscMax(
141 velocity_nodal_error,
142 PetscAbsReal(velocity_nodal[k][j][i].z -
kSentinel));
144 qcrit_error = PetscMax(
146 PetscAbsReal(qcrit[k][j][i] - (q_point ? 0.0 :
kSentinel)));
147 if (q_point) ++local_q_points;
151 PetscCall(DMDAVecRestoreArrayRead(user->
da, user->
Qcrit, &qcrit));
152 PetscCall(DMDAVecRestoreArrayRead(user->
fda, user->
Ucat_nodal, &velocity_nodal));
153 PetscCall(DMDAVecRestoreArrayRead(user->
da, user->
P_nodal, &pressure_nodal));
154 PetscCall(DMDAVecRestoreArrayRead(user->
da, user->
P, &pressure_result));
156 PetscCallMPI(MPI_Allreduce(&local_physical_nodes, &global_physical_nodes, 1,
157 MPIU_INT, MPI_SUM, PETSC_COMM_WORLD));
158 PetscCallMPI(MPI_Allreduce(&local_q_points, &global_q_points, 1,
159 MPIU_INT, MPI_SUM, PETSC_COMM_WORLD));
161 global_physical_nodes,
162 "the Eulerian pipeline must visit every physical node exactly once"));
165 "Q-criterion must visit every interior cell exactly once"));
167 "pressure normalization and dimensionalization are decomposition independent"));
169 "scalar nodal averaging covers rank interfaces and physical boundaries"));
171 "vector nodal averaging covers rank interfaces and physical boundaries"));
173 "Q-criterion covers the complete distributed interior"));
176 PetscFunctionReturn(0);
185 PetscMPIInt rank = 0, size = 1;
186 PetscInt source_local = 0;
187 PetscInt source_global = 0;
188 PetscInt post_local = 0;
189 PetscInt post_global = 0;
191 PetscScalar (*velocity)[3] = NULL;
192 const PetscScalar *ske = NULL;
193 PetscReal local_error = 0.0;
194 const PetscInt total_particles = 7;
196 PetscFunctionBeginUser;
197 PetscCallMPI(MPI_Comm_rank(PETSC_COMM_WORLD, &rank));
198 PetscCallMPI(MPI_Comm_size(PETSC_COMM_WORLD, &size));
199 source_local = total_particles / size + (rank < total_particles % size ? 1 : 0);
200 PetscCall(PetscMemzero(&pps,
sizeof(pps)));
209 PetscCallMPI(MPI_Exscan(&source_local, &offset, 1, MPIU_INT, MPI_SUM,
211 if (rank == 0) offset = 0;
213 PetscCall(DMSwarmGetField(user->
swarm,
"velocity", NULL, NULL, (
void **)&velocity));
214 for (PetscInt p = 0; p < source_local; ++p) {
215 const PetscReal tag = (PetscReal)(offset + p + 1);
217 velocity[p][0] = tag;
218 velocity[p][1] = 2.0;
219 velocity[p][2] = -1.0;
221 PetscCall(DMSwarmRestoreField(user->
swarm,
"velocity", NULL, NULL,
222 (
void **)&velocity));
225 PetscCall(DMSwarmGetLocalSize(user->
swarm, &source_local));
226 PetscCall(DMSwarmGetSize(user->
swarm, &source_global));
227 PetscCall(DMSwarmGetLocalSize(user->
post_swarm, &post_local));
228 PetscCall(DMSwarmGetSize(user->
post_swarm, &post_global));
230 "derived particle fields must share source local ownership"));
232 "derived and source swarms must have the same global size"));
234 PetscCall(DMSwarmGetField(user->
post_swarm,
"ske", NULL, NULL, (
void **)&ske));
235 for (PetscInt p = 0; p < source_local; ++p) {
236 const PetscReal tag = (PetscReal)(offset + p + 1);
237 const PetscReal expected = 0.5 * (tag * tag + 5.0);
239 local_error = PetscMax(local_error,
240 PetscAbsReal(PetscRealPart(ske[p]) - expected));
242 PetscCall(DMSwarmRestoreField(user->
post_swarm,
"ske", NULL, NULL, (
void **)&ske));
244 "specific kinetic energy must match every source particle"));
247 PetscFunctionReturn(0);
255 PetscMPIInt rank = 0, size = 1;
256 const PetscInt targets[] = {7, 3, 9, 0};
258 PetscFunctionBeginUser;
259 PetscCallMPI(MPI_Comm_rank(PETSC_COMM_WORLD, &rank));
260 PetscCallMPI(MPI_Comm_size(PETSC_COMM_WORLD, &size));
264 for (
size_t target_index = 0;
265 target_index <
sizeof(targets) /
sizeof(targets[0]); ++target_index) {
268 const PetscInt target = targets[target_index];
269 const PetscInt expected_local =
270 target / size + (rank < target % size ? 1 : 0);
273 PetscCall(DMSwarmGetLocalSize(user->
swarm, &local));
274 PetscCall(DMSwarmGetSize(user->
swarm, &global));
276 "global swarm resize must use balanced local sizes"));
278 "global swarm resize must conserve the requested total"));
282 PetscFunctionReturn(0);
293 PetscReal ***pressure = NULL;
294 const PetscReal ***nodal = NULL;
296 PetscInt components = 0;
297 PetscInt local_checked = 0;
298 PetscInt global_checked = 0;
299 PetscReal local_error = 0.0;
302 PetscFunctionBeginUser;
303 PetscCall(PetscMemzero(&definition,
sizeof(definition)));
304 PetscCall(PetscStrncpy(definition.
name,
"analytic",
sizeof(definition.
name)));
312 PetscCall(VecSet(user->
Nvert, 0.0));
320 PetscCall(DMDAVecGetArray(user->
da, user->
P, &pressure));
321 for (PetscInt k = user->
info.zs; k < user->
info.zs + user->
info.zm; ++k)
322 for (PetscInt j = user->
info.ys; j < user->
info.ys + user->
info.ym; ++j)
323 for (PetscInt i = user->
info.xs; i < user->
info.xs + user->
info.xm; ++i)
325 PetscCall(DMDAVecRestoreArray(user->
da, user->
P, &pressure));
328 &result, &components));
330 "the pressure mean must derive as a scalar field"));
332 "the pressure mean must use the scalar nodal staging vector"));
334 PetscCall(DMDAVecGetArrayRead(user->
da, result, &nodal));
335 for (PetscInt k = PetscMax(user->
info.zs, 1);
336 k < PetscMin(user->
info.zs + user->
info.zm, user->
info.mz - 2); ++k) {
337 for (PetscInt j = PetscMax(user->
info.ys, 1);
338 j < PetscMin(user->
info.ys + user->
info.ym, user->
info.my - 2); ++j) {
339 for (PetscInt i = PetscMax(user->
info.xs, 1);
340 i < PetscMin(user->
info.xs + user->
info.xm, user->
info.mx - 2); ++i) {
341 local_error = PetscMax(
343 PetscAbsReal(nodal[k][j][i] - (
ScalarValue(i, j, k) + 55.5)));
348 PetscCall(DMDAVecRestoreArrayRead(user->
da, result, &nodal));
349 PetscCallMPI(MPI_Allreduce(&local_checked, &global_checked, 1, MPIU_INT, MPI_SUM,
353 "the derived mean must cover every fully resolved nodal point"));
355 "field-statistics nodal derivation must cross rank interfaces"));
363 PetscFunctionReturn(0);
377 ierr = PetscInitialize(&argc, &argv, NULL,
378 "PICurv serial/MPI post-processing compute tests");
379 if (ierr)
return (
int)ierr;
381 sizeof(cases) /
sizeof(cases[0]));
386 ierr = PetscFinalize();
PetscErrorCode ResizeSwarmGlobally(DM swarm, PetscInt N_target)
Resizes a swarm collectively to a target global particle count.
PetscErrorCode DimensionalizeField(UserCtx *user, const char *field_name)
Scales a specified field from non-dimensional to dimensional units in-place.
PetscErrorCode ComputeWindowStatisticNodal(UserCtx *user, PetscInt window_index, const char *outputs, PetscInt output_index, char *out_name, size_t name_size, Vec *out_vec, PetscInt *out_components)
Derives one accumulated statistic and converts it to nodal values.
PetscErrorCode EulerianDataProcessingPipeline(UserCtx *user, PostProcessParams *pps)
Parses the processing pipeline string and executes the requested kernels.
PetscErrorCode ParticleDataProcessingPipeline(UserCtx *user, PostProcessParams *pps)
Parses and executes the particle pipeline using a robust two-pass approach.
PetscErrorCode SetupPostProcessSwarm(UserCtx *user, PostProcessParams *pps)
Creates a new, dedicated DMSwarm for post-processing tasks.
Per-window PETSc accumulator storage and pointwise application.
#define PICURV_STATISTICS_PAYLOAD_NAME_LENGTH
Maximum stored length of a payload name, including the terminator.
PetscErrorCode PicurvWindowStorageCreate(UserCtx *user, const PicurvWindowDefinition *definition, PicurvWindowStorage *storage)
Allocates the accumulator state one window owns on one block.
PetscErrorCode PicurvWindowAccumulate(UserCtx *user, const PicurvWindowDefinition *definition, PicurvWindowStorage *storage, PetscReal weight)
Applies one accepted completed state to a window's accumulators.
PetscErrorCode PicurvWindowStorageDestroy(PicurvWindowStorage *storage)
Releases accumulator state previously created for one window.
Independent accumulator state for one window on one block.
Window lifecycle, scheduling, and weighting for the field-statistics pipeline.
PicurvWindowFieldRequest fields[16]
PicurvCadenceKind cadence_kind
PetscInt step_cadence
Used when cadence_kind is step; must be positive.
PetscErrorCode PicurvWindowInit(PicurvWindow *window, const PicurvWindowDefinition *definition)
Validates a definition and initializes a window to the pending state.
PetscInt field_id
Catalogued Eulerian field identity.
@ PICURV_WEIGHTING_SAMPLE
Equal weight per accepted state.
@ PICURV_CADENCE_STEP
Every n completed steps from activation.
PicurvWeighting weighting
Runtime state of one window.
The scientifically immutable definition of one window.
static PetscErrorCode TestResizeSwarmGloballyBalanced(void)
Checks that global resizing produces one balanced global population.
static PetscErrorCode TestFieldStatisticsDerivedNodalMultiRank(void)
Exercises accumulator-to-derived-nodal field statistics across ownership boundaries.
static PetscErrorCode TestParticlePipelineMatchesSourceOwnership(void)
Verifies the production-created derived swarm mirrors source ownership.
static const PetscReal kSentinel
static PetscReal ScalarValue(PetscInt i, PetscInt j, PetscInt k)
Analytic scalar field used to expose misplaced or missing grid points.
int main(int argc, char **argv)
Runs the serial/MPI post-processing compute regression suite.
static PetscErrorCode AssertGlobalError(PetscReal local_error, PetscReal tolerance, const char *context)
Requires every rank's maximum error to be within tolerance.
static PetscErrorCode TestEulerianPipelineDecompositionIndependent(void)
Exercises every Eulerian transformation currently dispatched by the pipeline.
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.
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 PicurvAssertIntEqual(PetscInt expected, PetscInt actual, const char *context)
Asserts that two integer values are equal.
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.
PetscInt fieldStatisticsWindowCount
PetscBool fieldStatisticsEnabled
struct PicurvWindow * fieldStatisticsWindows
char particle_pipeline[1024]
char process_pipeline[1024]
struct PicurvWindowStorage * fieldStatisticsStorage
A 3D point or vector with PetscScalar components.
Holds all configuration parameters for a post-processing run.
The master context for the entire simulation.
User-defined context containing data specific to a single computational grid level.