PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
Loading...
Searching...
No Matches
Enumerations | Functions
test_post_particle_vtk_mpi.c File Reference

Focused multirank regression for particle VTK output. More...

#include "test_support.h"
#include "postprocessor.h"
#include <stdint.h>
#include <stdio.h>
#include <string.h>
Include dependency graph for test_post_particle_vtk_mpi.c:

Go to the source code of this file.

Enumerations

enum  { TEST_PARTICLES = 7 , TEST_OUTPUT_PARTICLES = 4 }
 

Functions

static PetscBool ScalarMatches (PetscScalar actual, PetscReal expected)
 Compare one scalar from the raw VTK payload with its deterministic truth.
 
static PetscBool ReadBlock (FILE *file, void *data, size_t count, size_t element_size)
 Read one length-prefixed raw appended-data block.
 
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.
 
static PetscErrorCode TestWriteParticleFileCollectiveMultiRank (void)
 Ensures particle fields are collectively gathered and written without corruption.
 
int main (int argc, char **argv)
 Runs the focused serial/MPI particle VTK regression.
 

Detailed Description

Focused multirank regression for particle VTK output.

Definition in file test_post_particle_vtk_mpi.c.

Enumeration Type Documentation

◆ anonymous enum

anonymous enum
Enumerator
TEST_PARTICLES 
TEST_OUTPUT_PARTICLES 

Definition at line 14 of file test_post_particle_vtk_mpi.c.

14 {
17};
@ TEST_PARTICLES
@ TEST_OUTPUT_PARTICLES

Function Documentation

◆ ScalarMatches()

static PetscBool ScalarMatches ( PetscScalar  actual,
PetscReal  expected 
)
static

Compare one scalar from the raw VTK payload with its deterministic truth.

Definition at line 20 of file test_post_particle_vtk_mpi.c.

21{
22 return (PetscBool)(PetscAbsReal(PetscRealPart(actual) - expected) <= 1.0e-12);
23}
Here is the caller graph for this function:

◆ ReadBlock()

static PetscBool ReadBlock ( FILE *  file,
void *  data,
size_t  count,
size_t  element_size 
)
static

Read one length-prefixed raw appended-data block.

Definition at line 26 of file test_post_particle_vtk_mpi.c.

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}
Here is the caller graph for this function:

◆ ValidateParticleVTKFile()

static PetscBool ValidateParticleVTKFile ( const char *  path)
static

Read and exhaustively validate every appended value in a generated VTP file.

Definition at line 39 of file test_post_particle_vtk_mpi.c.

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}
static PetscBool ScalarMatches(PetscScalar actual, PetscReal expected)
Compare one scalar from the raw VTK payload with its deterministic truth.
static PetscBool ReadBlock(FILE *file, void *data, size_t count, size_t element_size)
Read one length-prefixed raw appended-data block.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ FillParticleFields()

static PetscErrorCode FillParticleFields ( UserCtx user,
PetscInt  nlocal,
PetscInt  global_offset 
)
static

Fill each local swarm entry from its rank-ordered global particle index.

Definition at line 115 of file test_post_particle_vtk_mpi.c.

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}
DM post_swarm
Definition variables.h:990
Here is the caller graph for this function:

◆ TestWriteParticleFileCollectiveMultiRank()

static PetscErrorCode TestWriteParticleFileCollectiveMultiRank ( void  )
static

Ensures particle fields are collectively gathered and written without corruption.

Definition at line 166 of file test_post_particle_vtk_mpi.c.

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}
PetscErrorCode WriteParticleFile(UserCtx *user, PostProcessParams *pps, PetscInt ti)
Writes particle data to a VTP file using the Prepare-Write-Cleanup pattern.
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 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.
char particle_output_prefix[256]
Definition variables.h:612
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
Here is the call graph for this function:
Here is the caller graph for this function:

◆ main()

int main ( int  argc,
char **  argv 
)

Runs the focused serial/MPI particle VTK regression.

Definition at line 227 of file test_post_particle_vtk_mpi.c.

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}
static PetscErrorCode TestWriteParticleFileCollectiveMultiRank(void)
Ensures particle fields are collectively gathered and written without corruption.
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.
Named test case descriptor consumed by PicurvRunTests.
Here is the call graph for this function: