PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
Loading...
Searching...
No Matches
vtk_io.c
Go to the documentation of this file.
1#include "vtk_io.h"
3
4//================================================================================
5// STATIC (PRIVATE) HELPER FUNCTION PROTOTYPES
6//================================================================================
7
8static PetscErrorCode WriteVTKAppendedBlock(FILE *fp, const void *data, PetscInt num_elements, size_t element_size);
9static PetscErrorCode WriteVTSXMLHeader(FILE *fp, const VTKMetaData *meta, PetscInt *boffset);
10static PetscErrorCode WriteVTPXMLHeader(FILE *fp, const VTKMetaData *meta, PetscInt *boffset);
11static PetscErrorCode WriteVTKFileHeader(FILE *fp, const VTKMetaData *meta, PetscInt *boffset);
12static PetscErrorCode WriteVTKFileFooter(FILE *fp, const VTKMetaData *meta);
13
14//================================================================================
15// IMPLEMENTATION OF PRIVATE HELPERS
16//================================================================================
17
18/**
19 * @brief Write one binary data block in VTK appended-data format.
20 */
21static PetscErrorCode WriteVTKAppendedBlock(FILE *fp, const void *data, PetscInt num_elements, size_t element_size) {
22 uint32_t block_size = num_elements * (uint32_t)element_size;
23 if (fwrite(&block_size, sizeof(uint32_t), 1, fp) != 1) return PETSC_ERR_FILE_WRITE;
24 if (fwrite(data, element_size, num_elements, fp) != (size_t)num_elements) return PETSC_ERR_FILE_WRITE;
25 return 0;
26}
27
28/**
29 * @brief Write the XML header for a VTK structured-grid (`.vts`) file.
30 */
31static PetscErrorCode WriteVTSXMLHeader(FILE *fp, const VTKMetaData *meta, PetscInt *boffset)
32{
33 const char *byte_order = "LittleEndian";
34 const char *precision = "Float64";
35
36 const PetscInt header = (PetscInt)sizeof(uint32_t);
37
38 fprintf(fp, "<?xml version=\"1.0\"?>\n");
39 fprintf(fp, "<VTKFile type=\"StructuredGrid\" version=\"1.0\" byte_order=\"%s\">\n", byte_order);
40 fprintf(fp, " <StructuredGrid WholeExtent=\"0 %" PetscInt_FMT " 0 %" PetscInt_FMT " 0 %" PetscInt_FMT "\">\n", meta->mx - 1, meta->my - 1, meta->mz - 1);
41 fprintf(fp, " <Piece Extent=\"0 %" PetscInt_FMT " 0 %" PetscInt_FMT " 0 %" PetscInt_FMT "\">\n", meta->mx - 1, meta->my - 1, meta->mz - 1);
42
43 fprintf(fp, " <Points>\n");
44 fprintf(fp, " <DataArray type=\"%s\" Name=\"Position\" NumberOfComponents=\"3\" format=\"appended\" offset=\"%" PetscInt_FMT "\" />\n",
45 precision, *boffset);
46 *boffset += header + 3 * meta->npoints * (PetscInt)sizeof(PetscScalar);
47 fprintf(fp, " </Points>\n");
48
49 if (meta->num_point_data_fields > 0) {
50 fprintf(fp, " <PointData>\n");
51 for (PetscInt i = 0; i < meta->num_point_data_fields; i++) {
52 const VTKFieldInfo* field = &meta->point_data_fields[i];
53 fprintf(fp, " <DataArray type=\"%s\" Name=\"%s\" NumberOfComponents=\"%" PetscInt_FMT "\" format=\"appended\" offset=\"%" PetscInt_FMT "\" />\n",
54 precision, field->name, field->num_components, *boffset);
55 *boffset += header + field->num_components * meta->npoints * (PetscInt)sizeof(PetscScalar);
56 }
57 fprintf(fp, " </PointData>\n");
58 }
59
60 fprintf(fp, " </Piece>\n");
61 fprintf(fp, " </StructuredGrid>\n");
62 fprintf(fp, " <AppendedData encoding=\"raw\">\n_");
63
64 return 0;
65}
66
67/**
68 * @brief Write the XML header for a VTK polydata (`.vtp`) file.
69 */
70static PetscErrorCode WriteVTPXMLHeader(FILE *fp, const VTKMetaData *meta, PetscInt *boffset)
71{
72 const char *byte_order = "LittleEndian";
73 const char *precision = "Float64";
74 const char *int_type_str = (sizeof(PetscInt) == 8) ? "Int64" : "Int32";
75 const PetscInt header = (PetscInt)sizeof(uint32_t);
76
77 fprintf(fp, "<?xml version=\"1.0\"?>\n");
78 fprintf(fp, "<VTKFile type=\"PolyData\" version=\"1.0\" byte_order=\"%s\">\n", byte_order);
79 fprintf(fp, " <PolyData>\n");
80 fprintf(fp, " <Piece NumberOfPoints=\"%" PetscInt_FMT "\" NumberOfVerts=\"%" PetscInt_FMT "\" NumberOfLines=\"0\" NumberOfStrips=\"0\" NumberOfPolys=\"0\">\n",
81 meta->npoints, meta->npoints);
82
83 fprintf(fp, " <Points>\n");
84 fprintf(fp, " <DataArray type=\"%s\" Name=\"Position\" NumberOfComponents=\"3\" format=\"appended\" offset=\"%" PetscInt_FMT "\" />\n",
85 precision, *boffset);
86 *boffset += header + 3 * meta->npoints * (PetscInt)sizeof(PetscScalar);
87 fprintf(fp, " </Points>\n");
88
89 if (meta->num_point_data_fields > 0) {
90 fprintf(fp, " <PointData>\n");
91 for (PetscInt i = 0; i < meta->num_point_data_fields; i++) {
92 const VTKFieldInfo* field = &meta->point_data_fields[i];
93 fprintf(fp, " <DataArray type=\"%s\" Name=\"%s\" NumberOfComponents=\"%" PetscInt_FMT "\" format=\"appended\" offset=\"%" PetscInt_FMT "\" />\n",
94 precision, field->name, field->num_components, *boffset);
95 *boffset += header + field->num_components * meta->npoints * (PetscInt)sizeof(PetscScalar);
96 }
97 fprintf(fp, " </PointData>\n");
98 }
99
100 fprintf(fp, " <Verts>\n");
101 fprintf(fp, " <DataArray type=\"%s\" Name=\"connectivity\" format=\"appended\" offset=\"%" PetscInt_FMT "\" />\n",
102 int_type_str, *boffset);
103 *boffset += (uint32_t)sizeof(uint32_t) + meta->npoints * (PetscInt)sizeof(PetscInt);
104 fprintf(fp, " <DataArray type=\"%s\" Name=\"offsets\" format=\"appended\" offset=\"%" PetscInt_FMT "\" />\n",
105 int_type_str, *boffset);
106 *boffset += (uint32_t)sizeof(uint32_t) + meta->npoints * (PetscInt)sizeof(PetscInt);
107 fprintf(fp, " </Verts>\n");
108
109 fprintf(fp, " </Piece>\n");
110 fprintf(fp, " </PolyData>\n");
111 fprintf(fp, " <AppendedData encoding=\"raw\">\n_");
112
113 return 0;
114}
115
116/**
117 * @brief Open a VTK XML document and write its file-level header.
118 */
119static PetscErrorCode WriteVTKFileHeader(FILE *fp, const VTKMetaData *meta, PetscInt *boffset)
120{
121 if (meta->fileType == VTK_STRUCTURED) {
122 return WriteVTSXMLHeader(fp, meta, boffset);
123 } else if (meta->fileType == VTK_POLYDATA) {
124 return WriteVTPXMLHeader(fp, meta, boffset);
125 }
126 return PETSC_ERR_ARG_WRONG;
127}
128
129/**
130 * @brief Close a VTK XML document after all appended data have been written.
131 */
132static PetscErrorCode WriteVTKFileFooter(FILE *fp, const VTKMetaData *meta)
133{
134 (void)meta;
135 fprintf(fp, "\n </AppendedData>\n");
136 fprintf(fp, "</VTKFile>\n");
137 return 0;
138}
139
140
141
142/**
143 * @brief Implementation of \ref CreateVTKFileFromMetadata().
144 * @details Full API contract (arguments, ownership, side effects) is documented with
145 * the header declaration in `include/vtk_io.h`.
146 * @see CreateVTKFileFromMetadata()
147 */
148
149PetscErrorCode CreateVTKFileFromMetadata(const char *filename, const VTKMetaData *meta, MPI_Comm comm)
150{
151 PetscMPIInt rank;
152 MPI_Comm_rank(comm, &rank);
153 PetscErrorCode ierr = 0;
154
155 if (!rank) {
156 char tmp_filename[PETSC_MAX_PATH_LEN];
157 FILE *fp = NULL;
158
159 ierr = PetscSNPrintf(tmp_filename, sizeof(tmp_filename), "%s.tmp", filename);CHKERRQ(ierr);
160 LOG_ALLOW(GLOBAL, LOG_INFO, "Rank 0 writing combined VTK file '%s'.\n", filename);
161 fp = fopen(tmp_filename, "wb");
162 if (!fp) {
163 LOG_ALLOW(GLOBAL, LOG_ERROR, "fopen failed for %s.\n", tmp_filename);
164 return PETSC_ERR_FILE_OPEN;
165 }
166
167 PetscInt boffset = 0;
168
169 ierr = WriteVTKFileHeader(fp, meta, &boffset);
170 if (ierr) {
171 fclose(fp);
172 remove(tmp_filename);
173 return ierr;
174 }
175
176 if (meta->coords) {
177 ierr = WriteVTKAppendedBlock(fp, meta->coords, 3 * meta->npoints, sizeof(PetscScalar));
178 if (ierr) {
179 fclose(fp);
180 remove(tmp_filename);
181 return ierr;
182 }
183 }
184
185 for (PetscInt i = 0; i < meta->num_point_data_fields; i++) {
186 const VTKFieldInfo *field = &meta->point_data_fields[i];
187 if (field->data) {
188 ierr = WriteVTKAppendedBlock(fp, field->data, field->num_components * meta->npoints, sizeof(PetscScalar));
189 if (ierr) {
190 fclose(fp);
191 remove(tmp_filename);
192 return ierr;
193 }
194 }
195 }
196 if (meta->fileType == VTK_POLYDATA) {
197 if (meta->connectivity) {
198 ierr = WriteVTKAppendedBlock(fp, meta->connectivity, meta->npoints, sizeof(PetscInt));
199 if (ierr) {
200 fclose(fp);
201 remove(tmp_filename);
202 return ierr;
203 }
204 }
205 if (meta->offsets) {
206 ierr = WriteVTKAppendedBlock(fp, meta->offsets, meta->npoints, sizeof(PetscInt));
207 if (ierr) {
208 fclose(fp);
209 remove(tmp_filename);
210 return ierr;
211 }
212 }
213 }
214 ierr = WriteVTKFileFooter(fp, meta);
215 if (ierr) {
216 fclose(fp);
217 remove(tmp_filename);
218 return ierr;
219 }
220 if (fclose(fp) != 0) {
221 remove(tmp_filename);
222 return PETSC_ERR_FILE_WRITE;
223 }
224 if (rename(tmp_filename, filename) != 0) {
225 remove(tmp_filename);
226 return PETSC_ERR_FILE_WRITE;
227 }
228 LOG_ALLOW(GLOBAL, LOG_INFO, "Rank 0 finished writing VTK file '%s'.\n", filename);
229 }
230 return 0;
231}
232
233//================================================================================
234// PUBLIC COORDINATE PREPARATION FUNCTION
235//================================================================================
236
237/**
238 * @brief Internal helper implementation: `PrepareOutputCoordinates()`.
239 * @details Local to this translation unit.
240 */
241PetscErrorCode PrepareOutputCoordinates(UserCtx* user, PetscScalar** out_coords, PetscInt* out_nx, PetscInt* out_ny, PetscInt* out_nz, PetscInt* out_npoints)
242{
243 PetscErrorCode ierr;
244 Vec coords_global;
245 PetscInt IM, JM, KM;
246 PetscInt N_full_coords;
247 PetscScalar *full_coords_arr = NULL;
248
249 PetscFunctionBeginUser;
250 ierr = DMDAGetInfo(user->da, NULL, &IM, &JM, &KM, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); CHKERRQ(ierr);
251
252 // Set the dimensions of the output grid
253 *out_nx = IM - 1;
254 *out_ny = JM - 1;
255 *out_nz = KM - 1;
256 *out_npoints = (*out_nx) * (*out_ny) * (*out_nz);
257
258 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Preparing subsampled coordinates for a %" PetscInt_FMT "x%" PetscInt_FMT "x%" PetscInt_FMT " grid.\n", *out_nx, *out_ny, *out_nz);
259
260 // --- Step 1: Gather the full coordinate vector to rank 0 ---
261 ierr = DMGetCoordinates(user->da, &coords_global); CHKERRQ(ierr);
262 // Reuse of your existing, proven utility function's logic
263 ierr = VecToArrayOnRank0(coords_global, &N_full_coords, &full_coords_arr); CHKERRQ(ierr);
264
265 // --- Step 2: On rank 0, subsample the gathered array ---
266 if (user->simCtx->rank == 0) {
267 // We expect N_full_coords to be IM * JM * KM * 3
268 if (N_full_coords != IM * JM * KM * 3) {
269 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_UNEXPECTED, "Gathered coordinate array has wrong size. Expected %" PetscInt_FMT ", got %" PetscInt_FMT, IM * JM * KM * 3, N_full_coords);
270 }
271
272 // Allocate the smaller output C array
273 ierr = PetscMalloc1(3 * (*out_npoints), out_coords); CHKERRQ(ierr);
274
275 // Loop over the smaller grid dimensions and copy data
276 PetscInt p_out = 0; // Index for the small output array
277 for (PetscInt k = 0; k < *out_nz; k++) {
278 for (PetscInt j = 0; j < *out_ny; j++) {
279 for (PetscInt i = 0; i < *out_nx; i++) {
280 // Calculate the index in the full, 1D source array
281 PetscInt p_in = 3 * (k * (JM * IM) + j * IM + i);
282
283 (*out_coords)[p_out++] = full_coords_arr[p_in + 0]; // x
284 (*out_coords)[p_out++] = full_coords_arr[p_in + 1]; // y
285 (*out_coords)[p_out++] = full_coords_arr[p_in + 2]; // z
286 }
287 }
288 }
289 // Free the temporary full array that was allocated by VecToArrayOnRank0
290 ierr = PetscFree(full_coords_arr); CHKERRQ(ierr);
291 } else {
292 // On other ranks, the output pointer is NULL
293 *out_coords = NULL;
294 }
295
296 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Subsampled coordinates prepared on rank 0 with total %" PetscInt_FMT " points.\n", *out_npoints);
297
298 PetscFunctionReturn(0);
299}
300
301
302/**
303 * @brief Implementation of \ref BeginStructuredVTKOutput().
304 * @details Full API contract (arguments, ownership, side effects) is documented with
305 * the header declaration in `include/vtk_io.h`.
306 * @see BeginStructuredVTKOutput()
307 */
308PetscErrorCode BeginStructuredVTKOutput(UserCtx *user, VTKMetaData *meta)
309{
310 PetscFunctionBeginUser;
311 PetscCheck(user != NULL && meta != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
312 "Context and metadata are required.");
313 PetscCall(PetscMemzero(meta, sizeof(*meta)));
314 meta->fileType = VTK_STRUCTURED;
315 PetscCall(PrepareOutputCoordinates(user, &meta->coords, &meta->mx, &meta->my, &meta->mz,
316 &meta->npoints));
317 PetscFunctionReturn(0);
318}
319
320/**
321 * @brief Implementation of \ref AppendStructuredVTKField().
322 * @details Full API contract (arguments, ownership, side effects) is documented with
323 * the header declaration in `include/vtk_io.h`.
324 * @see AppendStructuredVTKField()
325 */
326PetscErrorCode AppendStructuredVTKField(UserCtx *user, VTKMetaData *meta, const char *name,
327 Vec field_vec, PetscInt components)
328{
329 VTKFieldInfo *entry = NULL;
330
331 PetscFunctionBeginUser;
332 PetscCheck(user != NULL && meta != NULL && name != NULL && field_vec != NULL,
333 PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Context, metadata, name, and vector are required.");
336 "Reached the %d point-data field limit; '%s' is not written.\n",
338 PetscFunctionReturn(0);
339 }
340 entry = &meta->point_data_fields[meta->num_point_data_fields];
341 PetscCall(PetscStrncpy(entry->name, name, MAX_VTK_FIELD_NAME_LENGTH));
342 entry->num_components = components;
343 PetscCall(PrepareOutputEulerianFieldData(user, field_vec, components, &entry->data));
344 meta->num_point_data_fields++;
345 PetscFunctionReturn(0);
346}
347
348/**
349 * @brief Implementation of \ref FinishStructuredVTKOutput().
350 * @details Full API contract (arguments, ownership, side effects) is documented with
351 * the header declaration in `include/vtk_io.h`.
352 * @see FinishStructuredVTKOutput()
353 */
354PetscErrorCode FinishStructuredVTKOutput(VTKMetaData *meta, const char *filename)
355{
356 PetscFunctionBeginUser;
357 PetscCheck(meta != NULL && filename != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
358 "Metadata and filename are required.");
359 PetscCall(CreateVTKFileFromMetadata(filename, meta, PETSC_COMM_WORLD));
360 for (PetscInt index = 0; index < meta->num_point_data_fields; ++index) {
361 if (meta->point_data_fields[index].data) {
362 PetscCall(PetscFree(meta->point_data_fields[index].data));
363 }
364 }
365 if (meta->coords) PetscCall(PetscFree(meta->coords));
366 meta->num_point_data_fields = 0;
367 PetscFunctionReturn(0);
368}
369
370/**
371 * @brief Implementation of \ref PrepareOutputEulerianFieldData().
372 * @details Full API contract (arguments, ownership, side effects) is documented with
373 * the header declaration in `include/vtk_io.h`.
374 * @see PrepareOutputEulerianFieldData()
375 */
376
377PetscErrorCode PrepareOutputEulerianFieldData(UserCtx *user, Vec field_vec, PetscInt num_components, PetscScalar** out_data)
378{
379 PetscErrorCode ierr;
380 PetscInt IM, JM, KM, nx, ny, nz, npoints;
381 PetscInt N_full_field = 0;
382 PetscScalar *full_field_arr = NULL;
383
384 PetscFunctionBeginUser;
385 ierr = DMDAGetInfo(user->da, NULL, &IM, &JM, &KM, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); CHKERRQ(ierr);
386 nx = IM - 1; ny = JM - 1; nz = KM - 1;
387 npoints = nx * ny * nz;
388
390 "Preparing subsampled field data (dof=%" PetscInt_FMT ") for a %" PetscInt_FMT "x%" PetscInt_FMT "x%" PetscInt_FMT " grid.\n",
391 num_components, nx, ny, nz);
392
393 // --- Step 1: Gather the full field vector to rank 0 ---
394 ierr = VecToArrayOnRank0(field_vec, &N_full_field, &full_field_arr); CHKERRQ(ierr);
395
396 // --- Step 2: On rank 0, subsample the gathered array ---
397 if (user->simCtx->rank == 0) {
398 // Sanity check the size of the gathered array
399 if (N_full_field != IM * JM * KM * num_components) {
400 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_UNEXPECTED,
401 "Gathered field array has wrong size. Expected %" PetscInt_FMT ", got %" PetscInt_FMT,
402 IM * JM * KM * num_components, N_full_field);
403 }
404
405 // Allocate the smaller output C array (AoS layout expected by your writer)
406 ierr = PetscMalloc1(num_components * npoints, out_data); CHKERRQ(ierr);
407
408 /*
409 // ======== Layout diagnostics (rank 0 only, read-only) ========
410 if (num_components > 1 && full_field_arr) {
411 const PetscInt n_full = IM * JM * KM;
412
413 // Choose a safe interior probe point and its +i neighbor
414 const PetscInt ic = (IM > 4) ? IM/2 : 1;
415 const PetscInt jc = (JM > 4) ? JM/2 : 1;
416 const PetscInt kc = (KM > 4) ? KM/2 : 1;
417 const PetscInt ic1 = (ic + 1 < IM-1) ? (ic + 1) : ic; // stay interior
418
419 const PetscInt t_full_c = (kc * JM + jc) * IM + ic;
420 const PetscInt t_full_c1 = (kc * JM + jc) * IM + ic1;
421
422 // If source were AoS: full[num_components * t + comp]
423 PetscScalar aos_c0 = full_field_arr[num_components * t_full_c + 0];
424 PetscScalar aos_c1 = full_field_arr[num_components * t_full_c1 + 0];
425 PetscScalar aos_c0_y = (num_components > 1) ? full_field_arr[num_components * t_full_c + 1] : 0.0;
426 PetscScalar aos_c0_z = (num_components > 2) ? full_field_arr[num_components * t_full_c + 2] : 0.0;
427
428 // If source were SoA: full[comp * n_full + t]
429 PetscScalar soa_c0 = full_field_arr[0 * n_full + t_full_c];
430 PetscScalar soa_c1 = full_field_arr[0 * n_full + t_full_c1];
431 PetscScalar soa_c0_y = (num_components > 1) ? full_field_arr[1 * n_full + t_full_c] : 0.0;
432 PetscScalar soa_c0_z = (num_components > 2) ? full_field_arr[2 * n_full + t_full_c] : 0.0;
433
434 double dAOS = fabs((double)(aos_c1 - aos_c0)); // spatial delta along +i
435 double dSOA = fabs((double)(soa_c1 - soa_c0));
436
437 const char *verdict = (dSOA < dAOS) ? "LIKELY SoA (component-major)" : "LIKELY AoS (interleaved)";
438
439 LOG_ALLOW(GLOBAL, LOG_INFO,
440 "Layout probe @ (i=%" PetscInt_FMT ", j=%" PetscInt_FMT ", k=%" PetscInt_FMT ") vs +i neighbor:\n"
441 " AoS-candidate: Ux(center)=%.6e Ux(+i)=%.6e |Δ|=%.6e ; Uy(center)=%.6e Uz(center)=%.6e\n"
442 " SoA-candidate: Ux(center)=%.6e Ux(+i)=%.6e |Δ|=%.6e ; Uy(center)=%.6e Uz(center)=%.6e\n"
443 " Verdict: %s\n",
444 ic, jc, kc,
445 (double)aos_c0, (double)aos_c1, dAOS, (double)aos_c0_y, (double)aos_c0_z,
446 (double)soa_c0, (double)soa_c1, dSOA, (double)soa_c0_y, (double)soa_c0_z,
447 verdict
448 );
449 } else if (num_components == 1) {
450 LOG_ALLOW(GLOBAL, LOG_INFO, "Layout probe: scalar field (dof=1) — no component interleave to detect.\n");
451 }
452 // ======== END diagnostics ========
453 */
454
455 // --- PACK: assumes source is already AoS ---
456 {
457 PetscInt p_out = 0;
458 for (PetscInt k = 0; k < nz; k++) {
459 for (PetscInt j = 0; j < ny; j++) {
460 for (PetscInt i = 0; i < nx; i++) {
461 const PetscInt p_in_start = num_components * (k * (JM * IM) + j * IM + i);
462 for (PetscInt c = 0; c < num_components; c++) {
463 (*out_data)[p_out++] = full_field_arr[p_in_start + c];
464 }
465 }
466 }
467 }
468 }
469
470 // Free temporary gathered array
471 ierr = PetscFree(full_field_arr); CHKERRQ(ierr);
472 } else {
473 // Other ranks: no allocation
474 *out_data = NULL;
475 }
476
477 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Subsampled field data prepared on rank 0 with total %" PetscInt_FMT " points.\n", npoints);
478
479 PetscFunctionReturn(0);
480}
481
482/**
483 * @brief Internal helper implementation: `PrepareOutputParticleData()`.
484 * @details Local to this translation unit.
485 */
486PetscErrorCode PrepareOutputParticleData(UserCtx* user, PostProcessParams* pps, VTKMetaData* meta, PetscInt* p_n_total)
487{
488 PetscErrorCode ierr;
489 PetscInt n_total_particles, n_components;
490 PetscInt stride = pps->particle_output_freq > 0 ? pps->particle_output_freq : 1;
491
492 PetscFunctionBeginUser;
493
494 // Initialize output parameters on all ranks
495 *p_n_total = 0;
496
497 // --- The entire preparation process is a series of collective gathers followed by rank-0 processing ---
498
499 // --- Step 1: Gather Full Coordinates from SOURCE Swarm (Collective) ---
500 // This establishes the ground truth for particle positions and total count.
501 PetscReal *full_coords_arr = NULL;
502 PetscDataType coordinate_type;
503 // NOTE: This assumes SwarmFieldToArrayOnRank0 exists and works for PetscScalar fields.
504 // If not, it may need to be generalized like the logic below.
506 &n_total_particles, &n_components, &coordinate_type,
507 (void**)&full_coords_arr); CHKERRQ(ierr);
508 PetscCheck(coordinate_type == PETSC_REAL,
509 PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG,
510 "Particle position field must use PETSC_REAL storage, not %s.",
511 PetscDataTypes[coordinate_type]);
512
513 *p_n_total = n_total_particles;
514 if (n_total_particles == 0) {
515 ierr = PetscFree(full_coords_arr); CHKERRQ(ierr);
516 PetscFunctionReturn(0);
517 }
518 PetscCheck(n_components == 3,
519 PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG,
520 "Coordinate field position must have 3 components, but has %" PetscInt_FMT,
521 n_components);
522
523 // --- Step 2: Prepare and Subsample Coordinates (Rank 0 Only) ---
524 if (user->simCtx->rank == 0) {
525 // --- Subsampling Calculation ---
526 meta->npoints = (n_total_particles > 0) ? (n_total_particles - 1) / stride + 1 : 0;
527
528 LOG_ALLOW(LOCAL, LOG_DEBUG, "Subsampling %" PetscInt_FMT " total particles with stride %" PetscInt_FMT " -> %" PetscInt_FMT " output particles.\n",
529 n_total_particles, stride, meta->npoints);
530
531 // --- Prepare Final Coordinates Array ---
532 ierr = PetscMalloc1(3 * meta->npoints, &meta->coords); CHKERRQ(ierr);
533 for (PetscInt i = 0; i < meta->npoints; i++) {
534 PetscInt source_idx = i * stride;
535 for (int d = 0; d < 3; d++) meta->coords[3 * i + d] = full_coords_arr[3 * source_idx + d];
536 }
537 }
538 ierr = PetscFree(full_coords_arr); CHKERRQ(ierr);
539
540 // --- Step 3: Gather Fields (Collective), then Subsample and Store (Rank 0 Only) ---
541 char *fields_copy, *field_name;
542 PetscInt num_fields = 0;
543 ierr = PetscStrallocpy(pps->particle_fields, &fields_copy); CHKERRQ(ierr);
544 field_name = strtok(fields_copy, ",");
545 while (field_name && num_fields < MAX_POINT_DATA_FIELDS) {
546 TrimWhitespace(field_name);
547 if (!*field_name || strcasecmp(field_name, "position") == 0) {
548 field_name = strtok(NULL, ","); continue;
549 }
550
551 // A. Determine which swarm is the source for this field on every rank.
552 DM swarm_to_use = user->swarm; // Default to the main solver swarm.
553 const char* internal_name = field_name; // Default internal name to user-facing name.
554
555 PetscErrorCode check_ierr;
556 ierr = PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL); CHKERRQ(ierr);
557 check_ierr = DMSwarmGetField(user->post_swarm, field_name, NULL, NULL, NULL);
558 ierr = PetscPopErrorHandler(); CHKERRQ(ierr);
559 if (!check_ierr) {
560 swarm_to_use = user->post_swarm;
561 ierr = DMSwarmRestoreField(user->post_swarm, field_name, NULL, NULL, NULL); CHKERRQ(ierr);
562 if (user->simCtx->rank == 0) {
563 LOG_ALLOW(LOCAL, LOG_DEBUG, "Field '%s' will be sourced from the post-processing swarm.\n", field_name);
564 }
565 } else {
566 if (user->simCtx->rank == 0) {
567 LOG_ALLOW(LOCAL, LOG_DEBUG, "Field '%s' will be sourced from the main solver swarm.\n", field_name);
568 }
569 if (strcasecmp(field_name, "pid") == 0) internal_name = "DMSwarm_pid";
570 else if (strcasecmp(field_name, "CellID") == 0) internal_name = "DMSwarm_CellID";
571 else if (strcasecmp(field_name, "Migration Status") == 0) internal_name = "DMSwarm_location_status";
572 }
573
574 // B. Gather the complete field on rank 0 before applying the output stride.
575 // SwarmFieldToArrayOnRank0 handles both scalar and vector field layouts.
576 void* full_field_arr_void = NULL;
577 PetscInt field_total_particles, field_num_components;
578 PetscDataType field_data_type;
579
580 ierr = SwarmFieldToArrayOnRank0(swarm_to_use, internal_name,
581 &field_total_particles, &field_num_components,
582 &field_data_type, &full_field_arr_void); CHKERRQ(ierr);
583
584 if (field_total_particles != n_total_particles) {
585 if (user->simCtx->rank == 0) {
586 LOG_ALLOW(LOCAL, LOG_WARNING, "Field '%s' has %" PetscInt_FMT " particles, but expected %" PetscInt_FMT ". Skipping.\n", field_name, field_total_particles, n_total_particles);
587 }
588 ierr = PetscFree(full_field_arr_void); CHKERRQ(ierr);
589 field_name = strtok(NULL, ","); continue;
590 }
591
592 if (user->simCtx->rank == 0) {
593 // C. Allocate final array (ALWAYS as PetscScalar) and copy/cast subsampled data.
594 VTKFieldInfo* current_field = &meta->point_data_fields[meta->num_point_data_fields];
595 strncpy(current_field->name, field_name, MAX_VTK_FIELD_NAME_LENGTH - 1);
596 current_field->name[MAX_VTK_FIELD_NAME_LENGTH - 1] = '\0';
597 current_field->num_components = field_num_components;
598 ierr = PetscMalloc1(current_field->num_components * meta->npoints, (PetscScalar**)&current_field->data); CHKERRQ(ierr);
599
600 PetscScalar* final_data_arr = (PetscScalar*)current_field->data;
601
602 // D. Perform the subsampling and potential casting
603 if (field_data_type == PETSC_INT64) {
604 PetscInt64* source_arr = (PetscInt64*)full_field_arr_void;
605 for (PetscInt i = 0; i < meta->npoints; i++) {
606 final_data_arr[i] = (PetscScalar)source_arr[i * stride];
607 }
608 } else if (field_data_type == PETSC_INT) {
609 PetscInt* source_arr = (PetscInt*)full_field_arr_void;
610 for (PetscInt i = 0; i < meta->npoints; i++) {
611 PetscInt source_idx = i * stride;
612 for (PetscInt c = 0; c < current_field->num_components; c++) {
613 final_data_arr[current_field->num_components * i + c] = (PetscScalar)source_arr[current_field->num_components * source_idx + c];
614 }
615 }
616 } else if (field_data_type == PETSC_REAL) {
617 PetscReal* source_arr = (PetscReal*)full_field_arr_void;
618 for (PetscInt i = 0; i < meta->npoints; i++) {
619 PetscInt source_idx = i * stride;
620 for (PetscInt c = 0; c < current_field->num_components; c++) {
621 final_data_arr[current_field->num_components * i + c] = source_arr[current_field->num_components * source_idx + c];
622 }
623 }
624#if defined(PETSC_USE_COMPLEX)
625 } else if (field_data_type == PETSC_SCALAR) {
626 PetscScalar* source_arr = (PetscScalar*)full_field_arr_void;
627 for (PetscInt i = 0; i < meta->npoints; i++) {
628 PetscInt source_idx = i * stride;
629 for (PetscInt c = 0; c < current_field->num_components; c++) {
630 final_data_arr[current_field->num_components * i + c] = source_arr[current_field->num_components * source_idx + c];
631 }
632 }
633#endif
634 } else SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP,
635 "VTK particle output does not support field '%s' with PETSc type %s.",
636 field_name, PetscDataTypes[field_data_type]);
637
638 meta->num_point_data_fields++;
639 }
640 ierr = PetscFree(full_field_arr_void); CHKERRQ(ierr);
641 num_fields++;
642 field_name = strtok(NULL, ",");
643 }
644 ierr = PetscFree(fields_copy); CHKERRQ(ierr);
645
646 // --- Step 4: Finalize VTK MetaData for PolyData (Rank 0 Only) ---
647 if (user->simCtx->rank == 0) {
648 if (meta->npoints > 0) {
649 meta->fileType = VTK_POLYDATA;
650 ierr = PetscMalloc1(meta->npoints, &meta->connectivity); CHKERRQ(ierr);
651 ierr = PetscMalloc1(meta->npoints, &meta->offsets); CHKERRQ(ierr);
652 for (PetscInt i = 0; i < meta->npoints; i++) {
653 meta->connectivity[i] = i;
654 meta->offsets[i] = i + 1;
655 }
656 }
657 }
658
659 ierr = MPI_Barrier(PETSC_COMM_WORLD); CHKERRQ(ierr);
660
661 PetscFunctionReturn(0);
662}
PetscErrorCode SwarmFieldToArrayOnRank0(DM swarm, const char *field_name, PetscInt *n_total_particles, PetscInt *n_components, PetscDataType *field_type_out, void **gathered_array)
Gathers any DMSwarm field from all ranks to a single, contiguous array on rank 0.
Definition io.c:2686
void TrimWhitespace(char *str)
Removes leading and trailing ASCII whitespace from a mutable string.
Definition io.c:399
PetscErrorCode VecToArrayOnRank0(Vec inVec, PetscInt *N, double **arrayOut)
Gathers the contents of a distributed PETSc Vec into a single array on rank 0.
Definition io.c:2650
#define LOCAL
Logging scope definitions for controlling message output.
Definition logging.h:45
#define GLOBAL
Scope for global logging across all processes.
Definition logging.h:46
#define LOG_ALLOW(scope, level, fmt,...)
Logging macro that checks both the log level and whether the calling function is in the allowed-funct...
Definition logging.h:200
@ LOG_ERROR
Critical errors that may halt the program.
Definition logging.h:29
@ LOG_INFO
Informational messages about program execution.
Definition logging.h:31
@ LOG_WARNING
Non-critical issues that warrant attention.
Definition logging.h:30
@ LOG_DEBUG
Detailed debugging information.
Definition logging.h:32
Typed identities and metadata for persistent solver-particle fields.
const char * ParticleFieldName(ParticleFieldId field_id)
Return the canonical PETSc DMSwarm name for an ID.
@ PARTICLE_FIELD_ID_POSITION
#define MAX_POINT_DATA_FIELDS
Defines the maximum number of data fields for VTK point data.
Definition variables.h:590
PetscInt npoints
Definition variables.h:656
PetscInt num_components
Definition variables.h:643
PetscMPIInt rank
Definition variables.h:698
PetscInt num_point_data_fields
Definition variables.h:659
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:909
DM post_swarm
Definition variables.h:1000
PetscInt * connectivity
Definition variables.h:660
PetscInt * offsets
Definition variables.h:661
VTKFileType fileType
Definition variables.h:654
PetscScalar * data
Definition variables.h:644
char name[64]
Definition variables.h:642
PetscScalar * coords
Definition variables.h:657
PetscInt particle_output_freq
Definition variables.h:613
char particle_fields[1024]
Definition variables.h:611
VTKFieldInfo point_data_fields[20]
Definition variables.h:658
PetscInt mz
Definition variables.h:655
PetscInt my
Definition variables.h:655
PetscInt mx
Definition variables.h:655
#define MAX_VTK_FIELD_NAME_LENGTH
Maximum length for VTK field names.
Definition variables.h:591
@ VTK_POLYDATA
Definition variables.h:650
@ VTK_STRUCTURED
Definition variables.h:649
Holds all configuration parameters for a post-processing run.
Definition variables.h:596
User-defined context containing data specific to a single computational grid level.
Definition variables.h:906
Stores all necessary information for a single data array in a VTK file.
Definition variables.h:641
PetscErrorCode CreateVTKFileFromMetadata(const char *filename, const VTKMetaData *meta, MPI_Comm comm)
Implementation of CreateVTKFileFromMetadata().
Definition vtk_io.c:149
PetscErrorCode PrepareOutputEulerianFieldData(UserCtx *user, Vec field_vec, PetscInt num_components, PetscScalar **out_data)
Implementation of PrepareOutputEulerianFieldData().
Definition vtk_io.c:377
PetscErrorCode FinishStructuredVTKOutput(VTKMetaData *meta, const char *filename)
Implementation of FinishStructuredVTKOutput().
Definition vtk_io.c:354
static PetscErrorCode WriteVTKFileFooter(FILE *fp, const VTKMetaData *meta)
Close a VTK XML document after all appended data have been written.
Definition vtk_io.c:132
PetscErrorCode BeginStructuredVTKOutput(UserCtx *user, VTKMetaData *meta)
Implementation of BeginStructuredVTKOutput().
Definition vtk_io.c:308
PetscErrorCode PrepareOutputCoordinates(UserCtx *user, PetscScalar **out_coords, PetscInt *out_nx, PetscInt *out_ny, PetscInt *out_nz, PetscInt *out_npoints)
Internal helper implementation: PrepareOutputCoordinates().
Definition vtk_io.c:241
static PetscErrorCode WriteVTKAppendedBlock(FILE *fp, const void *data, PetscInt num_elements, size_t element_size)
Write one binary data block in VTK appended-data format.
Definition vtk_io.c:21
static PetscErrorCode WriteVTSXMLHeader(FILE *fp, const VTKMetaData *meta, PetscInt *boffset)
Write the XML header for a VTK structured-grid (.vts) file.
Definition vtk_io.c:31
static PetscErrorCode WriteVTKFileHeader(FILE *fp, const VTKMetaData *meta, PetscInt *boffset)
Open a VTK XML document and write its file-level header.
Definition vtk_io.c:119
static PetscErrorCode WriteVTPXMLHeader(FILE *fp, const VTKMetaData *meta, PetscInt *boffset)
Write the XML header for a VTK polydata (.vtp) file.
Definition vtk_io.c:70
PetscErrorCode AppendStructuredVTKField(UserCtx *user, VTKMetaData *meta, const char *name, Vec field_vec, PetscInt components)
Implementation of AppendStructuredVTKField().
Definition vtk_io.c:326
PetscErrorCode PrepareOutputParticleData(UserCtx *user, PostProcessParams *pps, VTKMetaData *meta, PetscInt *p_n_total)
Internal helper implementation: PrepareOutputParticleData().
Definition vtk_io.c:486