PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
Loading...
Searching...
No Matches
test_post_particle_vtk_mpi.c
Go to the documentation of this file.
1/**
2 * @file test_post_particle_vtk_mpi.c
3 * @brief Focused multirank regression for particle 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 <string.h>
13
14enum {
17};
18
19/** @brief Compare one scalar from the raw VTK payload with its deterministic truth. */
20static PetscBool ScalarMatches(PetscScalar actual, PetscReal expected)
21{
22 return (PetscBool)(PetscAbsReal(PetscRealPart(actual) - expected) <= 1.0e-12);
23}
24
25/** @brief Read one length-prefixed raw appended-data block. */
26static PetscBool ReadBlock(FILE *file, void *data, size_t count, size_t element_size)
27{
28 uint32_t block_size = 0;
29 const size_t expected_size = count * element_size;
30
31 if (expected_size > UINT32_MAX) return PETSC_FALSE;
32 if (fread(&block_size, sizeof(block_size), 1, file) != 1 || block_size != (uint32_t)expected_size) {
33 return PETSC_FALSE;
34 }
35 return (PetscBool)(fread(data, element_size, count, file) == count);
36}
37
38/** @brief Read and exhaustively validate every appended value in a generated VTP file. */
39static PetscBool ValidateParticleVTKFile(const char *path)
40{
41 FILE *file = NULL;
42 char line[1024];
43 char footer[128];
44 PetscScalar coords[3 * TEST_OUTPUT_PARTICLES];
45 PetscScalar velocity[3 * TEST_OUTPUT_PARTICLES];
46 PetscScalar cell_id[3 * TEST_OUTPUT_PARTICLES];
47 PetscScalar migration_status[TEST_OUTPUT_PARTICLES];
48 PetscScalar pid[TEST_OUTPUT_PARTICLES];
49 PetscScalar ske[TEST_OUTPUT_PARTICLES];
50 PetscInt connectivity[TEST_OUTPUT_PARTICLES];
51 PetscInt offsets[TEST_OUTPUT_PARTICLES];
52 PetscBool saw_piece = PETSC_FALSE;
53 PetscBool saw_velocity = PETSC_FALSE;
54 PetscBool saw_cell_id = PETSC_FALSE;
55 PetscBool saw_migration_status = PETSC_FALSE;
56 PetscBool saw_pid = PETSC_FALSE;
57 PetscBool saw_ske = PETSC_FALSE;
58 PetscBool valid = PETSC_FALSE;
59 size_t footer_size = 0;
60
61 file = fopen(path, "rb");
62 if (!file) return PETSC_FALSE;
63
64 while (fgets(line, sizeof(line), file)) {
65 if (strstr(line, "NumberOfPoints=\"4\"") && strstr(line, "NumberOfVerts=\"4\"")) saw_piece = PETSC_TRUE;
66 if (strstr(line, "Name=\"velocity\"") && strstr(line, "NumberOfComponents=\"3\"")) saw_velocity = PETSC_TRUE;
67 if (strstr(line, "Name=\"CellID\"") && strstr(line, "NumberOfComponents=\"3\"")) saw_cell_id = PETSC_TRUE;
68 if (strstr(line, "Name=\"Migration Status\"") && strstr(line, "NumberOfComponents=\"1\"")) saw_migration_status = PETSC_TRUE;
69 if (strstr(line, "Name=\"pid\"") && strstr(line, "NumberOfComponents=\"1\"")) saw_pid = PETSC_TRUE;
70 if (strstr(line, "Name=\"ske\"") && strstr(line, "NumberOfComponents=\"1\"")) saw_ske = PETSC_TRUE;
71 if (strstr(line, "<AppendedData encoding=\"raw\">")) break;
72 }
73 if (!saw_piece || !saw_velocity || !saw_cell_id || !saw_migration_status || !saw_pid || !saw_ske ||
74 fgetc(file) != '_') goto cleanup;
75
76 if (!ReadBlock(file, coords, 3 * TEST_OUTPUT_PARTICLES, sizeof(PetscScalar)) ||
77 !ReadBlock(file, velocity, 3 * TEST_OUTPUT_PARTICLES, sizeof(PetscScalar)) ||
78 !ReadBlock(file, cell_id, 3 * TEST_OUTPUT_PARTICLES, sizeof(PetscScalar)) ||
79 !ReadBlock(file, migration_status, TEST_OUTPUT_PARTICLES, sizeof(PetscScalar)) ||
80 !ReadBlock(file, pid, TEST_OUTPUT_PARTICLES, sizeof(PetscScalar)) ||
81 !ReadBlock(file, ske, TEST_OUTPUT_PARTICLES, sizeof(PetscScalar)) ||
82 !ReadBlock(file, connectivity, TEST_OUTPUT_PARTICLES, sizeof(PetscInt)) ||
83 !ReadBlock(file, offsets, TEST_OUTPUT_PARTICLES, sizeof(PetscInt))) goto cleanup;
84
85 for (PetscInt p = 0; p < TEST_OUTPUT_PARTICLES; ++p) {
86 const PetscInt source = 2 * p;
87
88 if (!ScalarMatches(coords[3 * p + 0], (PetscReal)source + 0.1) ||
89 !ScalarMatches(coords[3 * p + 1], (PetscReal)source + 0.2) ||
90 !ScalarMatches(coords[3 * p + 2], (PetscReal)source + 0.3) ||
91 !ScalarMatches(velocity[3 * p + 0], (PetscReal)source + 10.0) ||
92 !ScalarMatches(velocity[3 * p + 1], (PetscReal)source + 20.0) ||
93 !ScalarMatches(velocity[3 * p + 2], (PetscReal)source + 30.0) ||
94 !ScalarMatches(cell_id[3 * p + 0], (PetscReal)source + 100.0) ||
95 !ScalarMatches(cell_id[3 * p + 1], (PetscReal)source + 200.0) ||
96 !ScalarMatches(cell_id[3 * p + 2], (PetscReal)source + 300.0) ||
97 !ScalarMatches(migration_status[p], (PetscReal)source + 400.0) ||
98 !ScalarMatches(pid[p], (PetscReal)source + 100000.0) ||
99 !ScalarMatches(ske[p], (PetscReal)source + 0.5) ||
100 connectivity[p] != p || offsets[p] != p + 1) goto cleanup;
101 }
102
103 footer_size = fread(footer, 1, sizeof(footer) - 1, file);
104 footer[footer_size] = '\0';
105 if (!strstr(footer, "</AppendedData>") || !strstr(footer, "</VTKFile>")) goto cleanup;
106
107 valid = PETSC_TRUE;
108
109cleanup:
110 fclose(file);
111 return valid;
112}
113
114/** @brief Fill each local swarm entry from its rank-ordered global particle index. */
115static PetscErrorCode FillParticleFields(UserCtx *user, PetscInt nlocal, PetscInt global_offset)
116{
117 PetscReal (*real3)[3] = NULL;
118 PetscInt (*int3)[3] = NULL;
119 PetscInt *int1 = NULL;
120 PetscInt64 *int64 = NULL;
121 PetscReal *real1 = NULL;
122
123 PetscFunctionBeginUser;
124 PetscCall(DMSwarmGetField(user->swarm, "position", NULL, NULL, (void *)&real3));
125 for (PetscInt p = 0; p < nlocal; ++p) {
126 const PetscReal source = (PetscReal)(global_offset + p);
127 real3[p][0] = source + 0.1;
128 real3[p][1] = source + 0.2;
129 real3[p][2] = source + 0.3;
130 }
131 PetscCall(DMSwarmRestoreField(user->swarm, "position", NULL, NULL, (void *)&real3));
132
133 PetscCall(DMSwarmGetField(user->swarm, "velocity", NULL, NULL, (void *)&real3));
134 for (PetscInt p = 0; p < nlocal; ++p) {
135 const PetscReal source = (PetscReal)(global_offset + p);
136 real3[p][0] = source + 10.0;
137 real3[p][1] = source + 20.0;
138 real3[p][2] = source + 30.0;
139 }
140 PetscCall(DMSwarmRestoreField(user->swarm, "velocity", NULL, NULL, (void *)&real3));
141
142 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_CellID", NULL, NULL, (void *)&int3));
143 for (PetscInt p = 0; p < nlocal; ++p) {
144 const PetscInt source = global_offset + p;
145 int3[p][0] = source + 100;
146 int3[p][1] = source + 200;
147 int3[p][2] = source + 300;
148 }
149 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_CellID", NULL, NULL, (void *)&int3));
150
151 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_location_status", NULL, NULL, (void *)&int1));
152 for (PetscInt p = 0; p < nlocal; ++p) int1[p] = global_offset + p + 400;
153 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_location_status", NULL, NULL, (void *)&int1));
154
155 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_pid", NULL, NULL, (void *)&int64));
156 for (PetscInt p = 0; p < nlocal; ++p) int64[p] = (PetscInt64)(global_offset + p + 100000);
157 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_pid", NULL, NULL, (void *)&int64));
158
159 PetscCall(DMSwarmGetField(user->post_swarm, "ske", NULL, NULL, (void *)&real1));
160 for (PetscInt p = 0; p < nlocal; ++p) real1[p] = (PetscReal)(global_offset + p) + 0.5;
161 PetscCall(DMSwarmRestoreField(user->post_swarm, "ske", NULL, NULL, (void *)&real1));
162 PetscFunctionReturn(0);
163}
164
165/** @brief Ensures particle fields are collectively gathered and written without corruption. */
167{
168 SimCtx *simCtx = NULL;
169 UserCtx *user = NULL;
171 char vtk_path[PETSC_MAX_PATH_LEN];
172 char vtk_tmp_path[PETSC_MAX_PATH_LEN];
173 PetscMPIInt rank = 0, size = 1;
174 PetscInt nlocal = 0, global_offset = 0;
175 PetscMPIInt local_owner = -1, final_owner = -1;
176 PetscBool parsed = PETSC_FALSE;
177 PetscBool tmp_exists = PETSC_FALSE;
178 PetscBool prefix_set = PETSC_FALSE;
179
180 PetscFunctionBeginUser;
181 PetscCallMPI(MPI_Comm_rank(PETSC_COMM_WORLD, &rank));
182 PetscCallMPI(MPI_Comm_size(PETSC_COMM_WORLD, &size));
183 nlocal = TEST_PARTICLES / size + (rank < TEST_PARTICLES % size ? 1 : 0);
184 PetscCallMPI(MPI_Exscan(&nlocal, &global_offset, 1, MPIU_INT, MPI_SUM, PETSC_COMM_WORLD));
185 if (rank == 0) global_offset = 0;
186
187 PetscCall(PetscMemzero(&pps, sizeof(pps)));
188 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
189 PetscCall(PicurvCreateSwarmPair(user, nlocal, "ske"));
190 PetscCall(FillParticleFields(user, nlocal, global_offset));
191 PetscCall(PetscOptionsGetString(NULL, NULL, "-vtk_test_output_prefix",
192 pps.particle_output_prefix, sizeof(pps.particle_output_prefix), &prefix_set));
193 PetscCall(PicurvAssertBool(prefix_set,
194 "the focused particle VTK test requires -vtk_test_output_prefix"));
195 PetscCall(PetscSNPrintf(vtk_path, sizeof(vtk_path), "%s_00004.vtp", pps.particle_output_prefix));
196 PetscCall(PetscSNPrintf(vtk_tmp_path, sizeof(vtk_tmp_path), "%s.tmp", vtk_path));
197
198 if (global_offset <= TEST_PARTICLES - 1 && TEST_PARTICLES - 1 < global_offset + nlocal) local_owner = rank;
199 PetscCallMPI(MPI_Allreduce(&local_owner, &final_owner, 1, MPI_INT, MPI_MAX, PETSC_COMM_WORLD));
200 PetscCall(PicurvAssertBool((PetscBool)(final_owner == ((size < TEST_PARTICLES) ? size - 1 : TEST_PARTICLES - 1)),
201 "the final exported particle should have the expected owner"));
202
203 pps.outputParticles = PETSC_TRUE;
204 pps.particle_output_freq = 2;
205 PetscCall(PetscStrncpy(pps.particle_fields,
206 "position,velocity,CellID,Migration Status,pid,ske",
207 sizeof(pps.particle_fields)));
208
209 PetscCall(WriteParticleFile(user, &pps, 4));
210 PetscCallMPI(MPI_Barrier(PETSC_COMM_WORLD));
211 PetscCall(PicurvAssertFileExists(vtk_path,
212 "multirank WriteParticleFile should emit one combined .vtp file"));
213 PetscCall(PetscTestFile(vtk_tmp_path, 'r', &tmp_exists));
214 PetscCall(PicurvAssertBool((PetscBool)!tmp_exists,
215 "successful multirank particle VTK output should not leave a temporary file"));
216
217 if (rank == 0) parsed = ValidateParticleVTKFile(vtk_path);
218 PetscCallMPI(MPI_Bcast(&parsed, 1, MPIU_BOOL, 0, PETSC_COMM_WORLD));
219 PetscCall(PicurvAssertBool(parsed,
220 "every coordinate, particle field, and vertex index in the generated VTK should match"));
221
222 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
223 PetscFunctionReturn(0);
224}
225
226/** @brief Runs the focused serial/MPI particle VTK regression. */
227int main(int argc, char **argv)
228{
229 PetscErrorCode ierr;
230 const PicurvTestCase cases[] = {
231 {"write-particle-file-collective-multi-rank", TestWriteParticleFileCollectiveMultiRank},
232 };
233
234 ierr = PetscInitialize(&argc, &argv, NULL, "PICurv multirank particle VTK tests");
235 if (ierr) return (int)ierr;
236
237 ierr = PicurvRunTests("unit-post-particle-vtk-mpi", cases, sizeof(cases) / sizeof(cases[0]));
238 if (ierr) {
239 PetscFinalize();
240 return (int)ierr;
241 }
242
243 ierr = PetscFinalize();
244 return (int)ierr;
245}
PetscErrorCode WriteParticleFile(UserCtx *user, PostProcessParams *pps, PetscInt ti)
Writes particle data to a VTP file using the Prepare-Write-Cleanup pattern.
static PetscErrorCode TestWriteParticleFileCollectiveMultiRank(void)
Ensures particle fields are collectively gathered and written without corruption.
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 particle VTK regression.
static PetscBool ReadBlock(FILE *file, void *data, size_t count, size_t element_size)
Read one length-prefixed raw appended-data block.
@ TEST_PARTICLES
@ TEST_OUTPUT_PARTICLES
static PetscBool ValidateParticleVTKFile(const char *path)
Read and exhaustively validate every appended value in a generated VTP file.
static PetscErrorCode FillParticleFields(UserCtx *user, PetscInt nlocal, PetscInt global_offset)
Fill each local swarm entry from its rank-ordered global particle index.
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 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.
char particle_output_prefix[256]
Definition variables.h:612
DM post_swarm
Definition variables.h:990
PetscInt particle_output_freq
Definition variables.h:613
char particle_fields[1024]
Definition variables.h:611
PetscBool outputParticles
Definition variables.h:604
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