PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
Loading...
Searching...
No Matches
test_post_eulerian_vtk_mpi.c
Go to the documentation of this file.
1/**
2 * @file test_post_eulerian_vtk_mpi.c
3 * @brief Focused multirank regression for instantaneous Eulerian VTK output.
4 */
5
6#include "test_support.h"
7
8#include "postprocessor.h"
9
10#include <stdint.h>
11#include <stdio.h>
12#include <stdlib.h>
13#include <string.h>
14
15/** @brief Compare one scalar from the raw VTK payload with its deterministic truth. */
16static PetscBool ScalarMatches(PetscScalar actual, PetscReal expected)
17{
18 return (PetscBool)(PetscAbsReal(PetscRealPart(actual) - expected) <= 1.0e-12);
19}
20
21/** @brief Read and exhaustively validate every appended value in a generated VTS file. */
22static PetscBool ValidateEulerianVTKFile(const char *path, PetscInt mx, PetscInt my, PetscInt mz)
23{
24 FILE *file = NULL;
25 char line[1024];
26 char footer[128];
27 char expected_extent[128];
28 PetscBool saw_pressure = PETSC_FALSE;
29 PetscBool saw_velocity = PETSC_FALSE;
30 PetscBool saw_qcrit = PETSC_FALSE;
31 PetscBool saw_extent = PETSC_FALSE;
32 uint32_t block_size = 0;
33 PetscInt npoints = mx * my * mz;
34 PetscScalar *coords = NULL;
35 PetscScalar *pressure = NULL;
36 PetscScalar *velocity = NULL;
37 PetscScalar *qcrit = NULL;
38 size_t footer_size = 0;
39 PetscBool valid = PETSC_FALSE;
40
41 file = fopen(path, "rb");
42 if (!file) return PETSC_FALSE;
43 snprintf(expected_extent, sizeof(expected_extent),
44 "WholeExtent=\"0 %d 0 %d 0 %d\"", (int)(mx - 1), (int)(my - 1), (int)(mz - 1));
45
46 while (fgets(line, sizeof(line), file)) {
47 if (strstr(line, "Name=\"P_nodal\"")) saw_pressure = PETSC_TRUE;
48 if (strstr(line, "Name=\"Ucat_nodal\"")) saw_velocity = PETSC_TRUE;
49 if (strstr(line, "Name=\"Qcrit\"")) saw_qcrit = PETSC_TRUE;
50 if (strstr(line, expected_extent)) saw_extent = PETSC_TRUE;
51 if (strstr(line, "<AppendedData encoding=\"raw\">")) break;
52 }
53 if (!saw_pressure || !saw_velocity || !saw_qcrit || !saw_extent || fgetc(file) != '_') goto cleanup;
54
55 /* Coordinates. */
56 if (fread(&block_size, sizeof(block_size), 1, file) != 1 ||
57 block_size != (uint32_t)(3 * npoints * (PetscInt)sizeof(PetscScalar))) goto cleanup;
58 coords = malloc((size_t)(3 * npoints) * sizeof(*coords));
59 if (!coords ||
60 fread(coords, sizeof(PetscScalar), (size_t)(3 * npoints), file) != (size_t)(3 * npoints)) goto cleanup;
61
62 /* P_nodal. */
63 if (fread(&block_size, sizeof(block_size), 1, file) != 1 ||
64 block_size != (uint32_t)(npoints * (PetscInt)sizeof(PetscScalar))) goto cleanup;
65 pressure = malloc((size_t)npoints * sizeof(*pressure));
66 if (!pressure ||
67 fread(pressure, sizeof(PetscScalar), (size_t)npoints, file) != (size_t)npoints) goto cleanup;
68
69 /* Ucat_nodal. */
70 if (fread(&block_size, sizeof(block_size), 1, file) != 1 ||
71 block_size != (uint32_t)(3 * npoints * (PetscInt)sizeof(PetscScalar))) goto cleanup;
72 velocity = malloc((size_t)(3 * npoints) * sizeof(*velocity));
73 if (!velocity ||
74 fread(velocity, sizeof(PetscScalar), (size_t)(3 * npoints), file) != (size_t)(3 * npoints)) goto cleanup;
75
76 /* Qcrit. */
77 if (fread(&block_size, sizeof(block_size), 1, file) != 1 ||
78 block_size != (uint32_t)(npoints * (PetscInt)sizeof(PetscScalar))) goto cleanup;
79 qcrit = malloc((size_t)npoints * sizeof(*qcrit));
80 if (!qcrit ||
81 fread(qcrit, sizeof(PetscScalar), (size_t)npoints, file) != (size_t)npoints) goto cleanup;
82
83 for (PetscInt k = 0; k < mz; ++k) {
84 for (PetscInt j = 0; j < my; ++j) {
85 for (PetscInt i = 0; i < mx; ++i) {
86 const PetscInt point = (k * my + j) * mx + i;
87 const PetscReal base = (PetscReal)(10000 * k + 100 * j + i);
88
89 if (!ScalarMatches(coords[3 * point + 0], (PetscReal)i / (PetscReal)mx) ||
90 !ScalarMatches(coords[3 * point + 1], (PetscReal)j / (PetscReal)my) ||
91 !ScalarMatches(coords[3 * point + 2], (PetscReal)k / (PetscReal)mz) ||
92 !ScalarMatches(pressure[point], base + 0.25) ||
93 !ScalarMatches(velocity[3 * point + 0], base + 1.0) ||
94 !ScalarMatches(velocity[3 * point + 1], base + 2.0) ||
95 !ScalarMatches(velocity[3 * point + 2], base + 3.0) ||
96 !ScalarMatches(qcrit[point], -base - 0.5)) goto cleanup;
97 }
98 }
99 }
100
101 footer_size = fread(footer, 1, sizeof(footer) - 1, file);
102 footer[footer_size] = '\0';
103 if (!strstr(footer, "</AppendedData>") || !strstr(footer, "</VTKFile>")) goto cleanup;
104
105 valid = PETSC_TRUE;
106
107cleanup:
108 fclose(file);
109 free(coords);
110 free(pressure);
111 free(velocity);
112 free(qcrit);
113 return valid;
114}
115
116/**
117 * @brief Ensures every Eulerian field and coordinate is correct after collective output.
118 */
120{
121 SimCtx *simCtx = NULL;
122 UserCtx *user = NULL;
124 char vtk_path[PETSC_MAX_PATH_LEN];
125 char vtk_tmp_path[PETSC_MAX_PATH_LEN];
126 PetscReal ***pressure = NULL;
127 PetscReal ***qcrit = NULL;
128 Cmpnts ***velocity = NULL;
129 PetscMPIInt rank = 0, size = 1;
130 PetscMPIInt local_owner = -1, probe_owner = -1;
131 PetscBool parsed = PETSC_FALSE;
132 PetscBool tmp_exists = PETSC_FALSE;
133 PetscBool prefix_set = PETSC_FALSE;
134
135 PetscFunctionBeginUser;
136 PetscCallMPI(MPI_Comm_rank(PETSC_COMM_WORLD, &rank));
137 PetscCallMPI(MPI_Comm_size(PETSC_COMM_WORLD, &size));
138
139 PetscCall(PetscMemzero(&pps, sizeof(pps)));
140 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 8, 6, 4));
141 PetscCall(PetscOptionsGetString(NULL, NULL, "-vtk_test_output_prefix",
142 pps.output_prefix, sizeof(pps.output_prefix), &prefix_set));
143 PetscCall(PicurvAssertBool(prefix_set,
144 "the focused Eulerian VTK test requires -vtk_test_output_prefix"));
145 PetscCall(PetscSNPrintf(vtk_path, sizeof(vtk_path), "%s_00003.vts", pps.output_prefix));
146 PetscCall(PetscSNPrintf(vtk_tmp_path, sizeof(vtk_tmp_path), "%s.tmp", vtk_path));
147
148 PetscCall(DMDAVecGetArray(user->da, user->P_nodal, &pressure));
149 PetscCall(DMDAVecGetArray(user->fda, user->Ucat_nodal, &velocity));
150 PetscCall(DMDAVecGetArray(user->da, user->Qcrit, &qcrit));
151 for (PetscInt k = user->info.zs; k < user->info.zs + user->info.zm; ++k) {
152 for (PetscInt j = user->info.ys; j < user->info.ys + user->info.ym; ++j) {
153 for (PetscInt i = user->info.xs; i < user->info.xs + user->info.xm; ++i) {
154 const PetscReal base = (PetscReal)(10000 * k + 100 * j + i);
155
156 pressure[k][j][i] = base + 0.25;
157 velocity[k][j][i] = (Cmpnts){base + 1.0, base + 2.0, base + 3.0};
158 qcrit[k][j][i] = -base - 0.5;
159 }
160 }
161 }
162 PetscCall(DMDAVecRestoreArray(user->da, user->P_nodal, &pressure));
163 PetscCall(DMDAVecRestoreArray(user->fda, user->Ucat_nodal, &velocity));
164 PetscCall(DMDAVecRestoreArray(user->da, user->Qcrit, &qcrit));
165
166 if (user->IM - 1 >= user->info.xs && user->IM - 1 < user->info.xs + user->info.xm &&
167 user->JM - 1 >= user->info.ys && user->JM - 1 < user->info.ys + user->info.ym &&
168 user->KM - 1 >= user->info.zs && user->KM - 1 < user->info.zs + user->info.zm) {
169 local_owner = rank;
170 }
171 PetscCallMPI(MPI_Allreduce(&local_owner, &probe_owner, 1, MPI_INT, MPI_MAX, PETSC_COMM_WORLD));
172 PetscCall(PicurvAssertBool((PetscBool)(probe_owner == ((size > 1) ? size - 1 : 0)),
173 "the final physical VTK point should have the expected owner"));
174
175 PetscCall(PetscStrncpy(pps.output_fields_instantaneous,
176 "P_nodal,Ucat_nodal,Qcrit",
177 sizeof(pps.output_fields_instantaneous)));
178
179 PetscCall(WriteEulerianFile(user, &pps, 3));
180 PetscCallMPI(MPI_Barrier(PETSC_COMM_WORLD));
181 PetscCall(PicurvAssertFileExists(vtk_path,
182 "multirank WriteEulerianFile should emit one combined .vts file"));
183 PetscCall(PetscTestFile(vtk_tmp_path, 'r', &tmp_exists));
184 PetscCall(PicurvAssertBool((PetscBool)!tmp_exists,
185 "successful multirank Eulerian VTK output should not leave a temporary file"));
186
187 if (rank == 0) {
188 parsed = ValidateEulerianVTKFile(vtk_path, user->IM, user->JM, user->KM);
189 }
190 PetscCallMPI(MPI_Bcast(&parsed, 1, MPIU_BOOL, 0, PETSC_COMM_WORLD));
191 PetscCall(PicurvAssertBool(parsed,
192 "every coordinate and Eulerian field value in the generated VTK should match"));
193
194 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
195 PetscFunctionReturn(0);
196}
197
198/** @brief Runs the focused serial/MPI Eulerian VTK regression. */
199int main(int argc, char **argv)
200{
201 PetscErrorCode ierr;
202 const PicurvTestCase cases[] = {
203 {"write-eulerian-file-collective-multi-rank", TestWriteEulerianFileCollectiveMultiRank},
204 };
205
206 ierr = PetscInitialize(&argc, &argv, NULL, "PICurv multirank Eulerian VTK tests");
207 if (ierr) return (int)ierr;
208
209 ierr = PicurvRunTests("unit-post-eulerian-vtk-mpi", cases, sizeof(cases) / sizeof(cases[0]));
210 if (ierr) {
211 PetscFinalize();
212 return (int)ierr;
213 }
214
215 ierr = PetscFinalize();
216 return (int)ierr;
217}
PetscErrorCode WriteEulerianFile(UserCtx *user, PostProcessParams *pps, PetscInt ti)
Orchestrates the writing of a combined, multi-field VTK file for a single time step.
static PetscBool ScalarMatches(PetscScalar actual, PetscReal expected)
Compare one scalar from the raw VTK payload with its deterministic truth.
int main(int argc, char **argv)
Runs the focused serial/MPI Eulerian VTK regression.
static PetscErrorCode TestWriteEulerianFileCollectiveMultiRank(void)
Ensures every Eulerian field and coordinate is correct after collective output.
static PetscBool ValidateEulerianVTKFile(const char *path, PetscInt mx, PetscInt my, PetscInt mz)
Read and exhaustively validate every appended value in a generated VTS file.
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 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 PicurvAssertFileExists(const char *path, const char *context)
Asserts that a filesystem path exists as a readable file.
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 P_nodal
Definition variables.h:991
char output_prefix[256]
Definition variables.h:609
PetscInt KM
Definition variables.h:910
Vec Ucat_nodal
Definition variables.h:992
Vec Qcrit
Definition variables.h:993
char output_fields_instantaneous[1024]
Definition variables.h:608
PetscInt JM
Definition variables.h:910
DMDALocalInfo info
Definition variables.h:908
PetscInt IM
Definition variables.h:910
A 3D point or vector with PetscScalar components.
Definition variables.h:102
Holds all configuration parameters for a post-processing run.
Definition variables.h:596
The master context for the entire simulation.
Definition variables.h:695
User-defined context containing data specific to a single computational grid level.
Definition variables.h:896