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"
2
3//================================================================================
4// STATIC (PRIVATE) HELPER FUNCTION PROTOTYPES
5//================================================================================
6
7static PetscErrorCode WriteVTKAppendedBlock(FILE *fp, const void *data, PetscInt num_elements, size_t element_size);
8static PetscErrorCode WriteVTSXMLHeader(FILE *fp, const VTKMetaData *meta, PetscInt *boffset);
9static PetscErrorCode WriteVTPXMLHeader(FILE *fp, const VTKMetaData *meta, PetscInt *boffset);
10static PetscErrorCode WriteVTKFileHeader(FILE *fp, const VTKMetaData *meta, PetscInt *boffset);
11static PetscErrorCode WriteVTKFileFooter(FILE *fp, const VTKMetaData *meta);
12
13//================================================================================
14// IMPLEMENTATION OF PRIVATE HELPERS
15//================================================================================
16
17static PetscErrorCode WriteVTKAppendedBlock(FILE *fp, const void *data, PetscInt num_elements, size_t element_size) {
18 uint32_t block_size = num_elements * (uint32_t)element_size;
19 if (fwrite(&block_size, sizeof(uint32_t), 1, fp) != 1) return PETSC_ERR_FILE_WRITE;
20 if (fwrite(data, element_size, num_elements, fp) != (size_t)num_elements) return PETSC_ERR_FILE_WRITE;
21 return 0;
22}
23
24static PetscErrorCode WriteVTSXMLHeader(FILE *fp, const VTKMetaData *meta, PetscInt *boffset)
25{
26 const char *byte_order = "LittleEndian";
27 const char *precision = "Float64";
28
29 const PetscInt header = (PetscInt)sizeof(uint32_t);
30
31 fprintf(fp, "<?xml version=\"1.0\"?>\n");
32 fprintf(fp, "<VTKFile type=\"StructuredGrid\" version=\"1.0\" byte_order=\"%s\">\n", byte_order);
33 fprintf(fp, " <StructuredGrid WholeExtent=\"0 %" PetscInt_FMT " 0 %" PetscInt_FMT " 0 %" PetscInt_FMT "\">\n", meta->mx - 1, meta->my - 1, meta->mz - 1);
34 fprintf(fp, " <Piece Extent=\"0 %" PetscInt_FMT " 0 %" PetscInt_FMT " 0 %" PetscInt_FMT "\">\n", meta->mx - 1, meta->my - 1, meta->mz - 1);
35
36 fprintf(fp, " <Points>\n");
37 fprintf(fp, " <DataArray type=\"%s\" Name=\"Position\" NumberOfComponents=\"3\" format=\"appended\" offset=\"%" PetscInt_FMT "\" />\n",
38 precision, *boffset);
39 *boffset += header + 3 * meta->npoints * (PetscInt)sizeof(PetscScalar);
40 fprintf(fp, " </Points>\n");
41
42 if (meta->num_point_data_fields > 0) {
43 fprintf(fp, " <PointData>\n");
44 for (PetscInt i = 0; i < meta->num_point_data_fields; i++) {
45 const VTKFieldInfo* field = &meta->point_data_fields[i];
46 fprintf(fp, " <DataArray type=\"%s\" Name=\"%s\" NumberOfComponents=\"%" PetscInt_FMT "\" format=\"appended\" offset=\"%" PetscInt_FMT "\" />\n",
47 precision, field->name, field->num_components, *boffset);
48 *boffset += header + field->num_components * meta->npoints * (PetscInt)sizeof(PetscScalar);
49 }
50 fprintf(fp, " </PointData>\n");
51 }
52
53 fprintf(fp, " </Piece>\n");
54 fprintf(fp, " </StructuredGrid>\n");
55 fprintf(fp, " <AppendedData encoding=\"raw\">\n_");
56
57 return 0;
58}
59
60static PetscErrorCode WriteVTPXMLHeader(FILE *fp, const VTKMetaData *meta, PetscInt *boffset)
61{
62 const char *byte_order = "LittleEndian";
63 const char *precision = "Float64";
64 const char *int_type_str = (sizeof(PetscInt) == 8) ? "Int64" : "Int32";
65 const PetscInt header = (PetscInt)sizeof(uint32_t);
66
67 fprintf(fp, "<?xml version=\"1.0\"?>\n");
68 fprintf(fp, "<VTKFile type=\"PolyData\" version=\"1.0\" byte_order=\"%s\">\n", byte_order);
69 fprintf(fp, " <PolyData>\n");
70 fprintf(fp, " <Piece NumberOfPoints=\"%" PetscInt_FMT "\" NumberOfVerts=\"%" PetscInt_FMT "\" NumberOfLines=\"0\" NumberOfStrips=\"0\" NumberOfPolys=\"0\">\n",
71 meta->npoints, meta->npoints);
72
73 fprintf(fp, " <Points>\n");
74 fprintf(fp, " <DataArray type=\"%s\" Name=\"Position\" NumberOfComponents=\"3\" format=\"appended\" offset=\"%" PetscInt_FMT "\" />\n",
75 precision, *boffset);
76 *boffset += header + 3 * meta->npoints * (PetscInt)sizeof(PetscScalar);
77 fprintf(fp, " </Points>\n");
78
79 if (meta->num_point_data_fields > 0) {
80 fprintf(fp, " <PointData>\n");
81 for (PetscInt i = 0; i < meta->num_point_data_fields; i++) {
82 const VTKFieldInfo* field = &meta->point_data_fields[i];
83 fprintf(fp, " <DataArray type=\"%s\" Name=\"%s\" NumberOfComponents=\"%" PetscInt_FMT "\" format=\"appended\" offset=\"%" PetscInt_FMT "\" />\n",
84 precision, field->name, field->num_components, *boffset);
85 *boffset += header + field->num_components * meta->npoints * (PetscInt)sizeof(PetscScalar);
86 }
87 fprintf(fp, " </PointData>\n");
88 }
89
90 fprintf(fp, " <Verts>\n");
91 fprintf(fp, " <DataArray type=\"%s\" Name=\"connectivity\" format=\"appended\" offset=\"%" PetscInt_FMT "\" />\n",
92 int_type_str, *boffset);
93 *boffset += (uint32_t)sizeof(uint32_t) + meta->npoints * (PetscInt)sizeof(PetscInt);
94 fprintf(fp, " <DataArray type=\"%s\" Name=\"offsets\" format=\"appended\" offset=\"%" PetscInt_FMT "\" />\n",
95 int_type_str, *boffset);
96 *boffset += (uint32_t)sizeof(uint32_t) + meta->npoints * (PetscInt)sizeof(PetscInt);
97 fprintf(fp, " </Verts>\n");
98
99 fprintf(fp, " </Piece>\n");
100 fprintf(fp, " </PolyData>\n");
101 fprintf(fp, " <AppendedData encoding=\"raw\">\n_");
102
103 return 0;
104}
105
106static PetscErrorCode WriteVTKFileHeader(FILE *fp, const VTKMetaData *meta, PetscInt *boffset)
107{
108 if (meta->fileType == VTK_STRUCTURED) {
109 return WriteVTSXMLHeader(fp, meta, boffset);
110 } else if (meta->fileType == VTK_POLYDATA) {
111 return WriteVTPXMLHeader(fp, meta, boffset);
112 }
113 return PETSC_ERR_ARG_WRONG;
114}
115
116static PetscErrorCode WriteVTKFileFooter(FILE *fp, const VTKMetaData *meta)
117{
118 fprintf(fp, "\n </AppendedData>\n");
119 fprintf(fp, "</VTKFile>\n");
120 return 0;
121}
122
123
124//================================================================================
125// PUBLIC VTK WRITER FUNCTION
126//================================================================================
127
128PetscErrorCode CreateVTKFileFromMetadata(const char *filename, const VTKMetaData *meta, MPI_Comm comm)
129{
130 PetscMPIInt rank;
131 MPI_Comm_rank(comm, &rank);
132 PetscErrorCode ierr = 0;
133
134 if (!rank) {
135 LOG_ALLOW(GLOBAL, LOG_INFO, "Rank 0 writing combined VTK file '%s'.\n", filename);
136 FILE *fp = fopen(filename, "wb");
137 if (!fp) {
138 LOG_ALLOW(GLOBAL, LOG_ERROR, "fopen failed for %s.\n", filename);
139 return PETSC_ERR_FILE_OPEN;
140 }
141
142 PetscInt boffset = 0;
143
144 ierr = WriteVTKFileHeader(fp, meta, &boffset);
145 if(ierr) { fclose(fp); return ierr; }
146
147 if (meta->coords) {
148 ierr = WriteVTKAppendedBlock(fp, meta->coords, 3 * meta->npoints, sizeof(PetscScalar));
149 if(ierr) { fclose(fp); return ierr; }
150 }
151 /*
152 // ======== DEBUG: Dump first few values of Ucat_nodal if present ========
153 if (!rank) {
154 for (PetscInt i = 0; i < meta->num_point_data_fields; i++) {
155 const VTKFieldInfo* f = &meta->point_data_fields[i];
156 if (strcasecmp(f->name, "Ucat_nodal") == 0) {
157 const PetscScalar *a = f->data;
158 const PetscInt npts = meta->npoints;
159 const PetscInt nshow = (npts < 5) ? npts : 5;
160
161 LOG_ALLOW(GLOBAL, LOG_INFO,
162 "DBG (writer) Ucat_nodal: first %d of %" PetscInt_FMT " tuples:\n",
163 (int)nshow, npts);
164 for (PetscInt t = 0; t < nshow; ++t) {
165 LOG_ALLOW(GLOBAL, LOG_INFO,
166 " Ucat_nodal[%3" PetscInt_FMT "] = (%g, %g, %g)\n",
167 t, (double)a[3*t+0], (double)a[3*t+1], (double)a[3*t+2]);
168 }
169 }
170 }
171 }
172 */
173 // ======== END DEBUG ========
174
175
176 for (PetscInt i = 0; i < meta->num_point_data_fields; i++) {
177 const VTKFieldInfo* field = &meta->point_data_fields[i];
178 if (field->data) {
179 ierr = WriteVTKAppendedBlock(fp, field->data, field->num_components * meta->npoints, sizeof(PetscScalar));
180 if(ierr) { fclose(fp); return ierr; }
181 }
182 }
183 if (meta->fileType == VTK_POLYDATA) {
184 if (meta->connectivity) {
185 ierr = WriteVTKAppendedBlock(fp, meta->connectivity, meta->npoints, sizeof(PetscInt));
186 if(ierr) { fclose(fp); return ierr; }
187 }
188 if (meta->offsets) {
189 ierr = WriteVTKAppendedBlock(fp, meta->offsets, meta->npoints, sizeof(PetscInt));
190 if(ierr) { fclose(fp); return ierr; }
191 }
192 }
193 ierr = WriteVTKFileFooter(fp, meta);
194 if(ierr) { fclose(fp); return ierr; }
195
196 fclose(fp);
197 LOG_ALLOW(GLOBAL, LOG_INFO, "Rank 0 finished writing VTK file '%s'.\n", filename);
198 }
199 return 0;
200}
201
202//================================================================================
203// PUBLIC COORDINATE PREPARATION FUNCTION
204//================================================================================
205
206/**
207 * @brief Creates a C array of coordinates corresponding to a subsampled (legacy-style) grid.
208 *
209 * This function gathers the full, distributed grid coordinates onto rank 0. On rank 0,
210 * it then allocates a new, smaller C array and copies only the coordinates for the
211 * nodes within the range [0..IM-2, 0..JM-2, 0..KM-2]. This produces a contiguous
212 * array of points for a grid of size (IM-1)x(JM-1)x(KM-1), matching the legacy output.
213 * The output arrays are only allocated and valid on rank 0.
214 *
215 * @param[in] user The UserCtx containing the grid information (DM, IM/JM/KM).
216 * @param[out] out_coords On rank 0, a pointer to the newly allocated C array for coordinate data. NULL on other ranks.
217 * @param[out] out_nx The number of points in the x-dimension for the new grid (IM-1).
218 * @param[out] out_ny The number of points in the y-dimension for the new grid (JM-1).
219 * @param[out] out_nz The number of points in the z-dimension for the new grid (KM-1).
220 * @param[out] out_npoints The total number of points in the new grid.
221 * @return PetscErrorCode
222 */
223PetscErrorCode PrepareOutputCoordinates(UserCtx* user, PetscScalar** out_coords, PetscInt* out_nx, PetscInt* out_ny, PetscInt* out_nz, PetscInt* out_npoints)
224{
225 PetscErrorCode ierr;
226 Vec coords_global;
227 PetscInt IM, JM, KM;
228 PetscInt N_full_coords;
229 PetscScalar *full_coords_arr = NULL;
230
231 PetscFunctionBeginUser;
232 ierr = DMDAGetInfo(user->da, NULL, &IM, &JM, &KM, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); CHKERRQ(ierr);
233
234 // Set the dimensions of the output grid
235 *out_nx = IM - 1;
236 *out_ny = JM - 1;
237 *out_nz = KM - 1;
238 *out_npoints = (*out_nx) * (*out_ny) * (*out_nz);
239
240 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);
241
242 // --- Step 1: Gather the full coordinate vector to rank 0 ---
243 ierr = DMGetCoordinates(user->da, &coords_global); CHKERRQ(ierr);
244 // Reuse of your existing, proven utility function's logic
245 ierr = VecToArrayOnRank0(coords_global, &N_full_coords, &full_coords_arr); CHKERRQ(ierr);
246
247 // --- Step 2: On rank 0, subsample the gathered array ---
248 if (user->simCtx->rank == 0) {
249 // We expect N_full_coords to be IM * JM * KM * 3
250 if (N_full_coords != IM * JM * KM * 3) {
251 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);
252 }
253
254 // Allocate the smaller output C array
255 ierr = PetscMalloc1(3 * (*out_npoints), out_coords); CHKERRQ(ierr);
256
257 // Loop over the smaller grid dimensions and copy data
258 PetscInt p_out = 0; // Index for the small output array
259 for (PetscInt k = 0; k < *out_nz; k++) {
260 for (PetscInt j = 0; j < *out_ny; j++) {
261 for (PetscInt i = 0; i < *out_nx; i++) {
262 // Calculate the index in the full, 1D source array
263 PetscInt p_in = 3 * (k * (JM * IM) + j * IM + i);
264
265 (*out_coords)[p_out++] = full_coords_arr[p_in + 0]; // x
266 (*out_coords)[p_out++] = full_coords_arr[p_in + 1]; // y
267 (*out_coords)[p_out++] = full_coords_arr[p_in + 2]; // z
268 }
269 }
270 }
271 // Free the temporary full array that was allocated by VecToArrayOnRank0
272 ierr = PetscFree(full_coords_arr); CHKERRQ(ierr);
273 } else {
274 // On other ranks, the output pointer is NULL
275 *out_coords = NULL;
276 }
277
278 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Subsampled coordinates prepared on rank 0 with total %" PetscInt_FMT " points.\n", *out_npoints);
279
280 PetscFunctionReturn(0);
281}
282
283//================================================================================
284// PUBLIC FIELD PREPARATION FUNCTION
285//================================================================================
286
287PetscErrorCode PrepareOutputEulerianFieldData(UserCtx *user, Vec field_vec, PetscInt num_components, PetscScalar** out_data)
288{
289 PetscErrorCode ierr;
290 PetscInt IM, JM, KM, nx, ny, nz, npoints;
291 PetscInt N_full_field = 0;
292 PetscScalar *full_field_arr = NULL;
293
294 PetscFunctionBeginUser;
295 ierr = DMDAGetInfo(user->da, NULL, &IM, &JM, &KM, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); CHKERRQ(ierr);
296 nx = IM - 1; ny = JM - 1; nz = KM - 1;
297 npoints = nx * ny * nz;
298
300 "Preparing subsampled field data (dof=%" PetscInt_FMT ") for a %" PetscInt_FMT "x%" PetscInt_FMT "x%" PetscInt_FMT " grid.\n",
301 num_components, nx, ny, nz);
302
303 // --- Step 1: Gather the full field vector to rank 0 ---
304 ierr = VecToArrayOnRank0(field_vec, &N_full_field, &full_field_arr); CHKERRQ(ierr);
305
306 // --- Step 2: On rank 0, subsample the gathered array ---
307 if (user->simCtx->rank == 0) {
308 // Sanity check the size of the gathered array
309 if (N_full_field != IM * JM * KM * num_components) {
310 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_UNEXPECTED,
311 "Gathered field array has wrong size. Expected %" PetscInt_FMT ", got %" PetscInt_FMT,
312 IM * JM * KM * num_components, N_full_field);
313 }
314
315 // Allocate the smaller output C array (AoS layout expected by your writer)
316 ierr = PetscMalloc1(num_components * npoints, out_data); CHKERRQ(ierr);
317
318 /*
319 // ======== Layout diagnostics (rank 0 only, read-only) ========
320 if (num_components > 1 && full_field_arr) {
321 const PetscInt n_full = IM * JM * KM;
322
323 // Choose a safe interior probe point and its +i neighbor
324 const PetscInt ic = (IM > 4) ? IM/2 : 1;
325 const PetscInt jc = (JM > 4) ? JM/2 : 1;
326 const PetscInt kc = (KM > 4) ? KM/2 : 1;
327 const PetscInt ic1 = (ic + 1 < IM-1) ? (ic + 1) : ic; // stay interior
328
329 const PetscInt t_full_c = (kc * JM + jc) * IM + ic;
330 const PetscInt t_full_c1 = (kc * JM + jc) * IM + ic1;
331
332 // If source were AoS: full[num_components * t + comp]
333 PetscScalar aos_c0 = full_field_arr[num_components * t_full_c + 0];
334 PetscScalar aos_c1 = full_field_arr[num_components * t_full_c1 + 0];
335 PetscScalar aos_c0_y = (num_components > 1) ? full_field_arr[num_components * t_full_c + 1] : 0.0;
336 PetscScalar aos_c0_z = (num_components > 2) ? full_field_arr[num_components * t_full_c + 2] : 0.0;
337
338 // If source were SoA: full[comp * n_full + t]
339 PetscScalar soa_c0 = full_field_arr[0 * n_full + t_full_c];
340 PetscScalar soa_c1 = full_field_arr[0 * n_full + t_full_c1];
341 PetscScalar soa_c0_y = (num_components > 1) ? full_field_arr[1 * n_full + t_full_c] : 0.0;
342 PetscScalar soa_c0_z = (num_components > 2) ? full_field_arr[2 * n_full + t_full_c] : 0.0;
343
344 double dAOS = fabs((double)(aos_c1 - aos_c0)); // spatial delta along +i
345 double dSOA = fabs((double)(soa_c1 - soa_c0));
346
347 const char *verdict = (dSOA < dAOS) ? "LIKELY SoA (component-major)" : "LIKELY AoS (interleaved)";
348
349 LOG_ALLOW(GLOBAL, LOG_INFO,
350 "Layout probe @ (i=%" PetscInt_FMT ", j=%" PetscInt_FMT ", k=%" PetscInt_FMT ") vs +i neighbor:\n"
351 " AoS-candidate: Ux(center)=%.6e Ux(+i)=%.6e |Δ|=%.6e ; Uy(center)=%.6e Uz(center)=%.6e\n"
352 " SoA-candidate: Ux(center)=%.6e Ux(+i)=%.6e |Δ|=%.6e ; Uy(center)=%.6e Uz(center)=%.6e\n"
353 " Verdict: %s\n",
354 ic, jc, kc,
355 (double)aos_c0, (double)aos_c1, dAOS, (double)aos_c0_y, (double)aos_c0_z,
356 (double)soa_c0, (double)soa_c1, dSOA, (double)soa_c0_y, (double)soa_c0_z,
357 verdict
358 );
359 } else if (num_components == 1) {
360 LOG_ALLOW(GLOBAL, LOG_INFO, "Layout probe: scalar field (dof=1) — no component interleave to detect.\n");
361 }
362 // ======== END diagnostics ========
363 */
364
365 // --- PACK: assumes source is already AoS ---
366 {
367 PetscInt p_out = 0;
368 for (PetscInt k = 0; k < nz; k++) {
369 for (PetscInt j = 0; j < ny; j++) {
370 for (PetscInt i = 0; i < nx; i++) {
371 const PetscInt p_in_start = num_components * (k * (JM * IM) + j * IM + i);
372 for (PetscInt c = 0; c < num_components; c++) {
373 (*out_data)[p_out++] = full_field_arr[p_in_start + c];
374 }
375 }
376 }
377 }
378 }
379
380 // Free temporary gathered array
381 ierr = PetscFree(full_field_arr); CHKERRQ(ierr);
382 } else {
383 // Other ranks: no allocation
384 *out_data = NULL;
385 }
386
387 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Subsampled field data prepared on rank 0 with total %" PetscInt_FMT " points.\n", npoints);
388
389 PetscFunctionReturn(0);
390}
391
392/**
393 * @brief Gathers, merges, subsamples, and prepares particle data for VTK output.
394 *
395 * This function orchestrates the preparation of particle data for writing. It is a
396 * COLLECTIVE operation that all MPI ranks must enter, though the primary work of
397 * data aggregation and preparation is performed on rank 0.
398 *
399 * The function follows a "merge-and-prepare" strategy:
400 * 1. It treats `user->swarm` as the read-only source of primary data (e.g., positions, velocity)
401 * that was loaded from disk.
402 * 2. It treats `user->pp_swarm` as the source of derived data (e.g., specific_ke) that
403 * was computed by the post-processing pipeline.
404 * 3. **On Rank 0 only**:
405 * a. It gathers the full particle coordinates from the source swarm. This determines
406 * the total number of particles before any subsampling.
407 * b. It parses the `pps->particle_fields` list. For each requested field, it
408 * determines whether to source it from `user->swarm` or `user->pp_swarm`.
409 * c. It gathers the full data for each requested field from its appropriate source into a generic buffer.
410 * d. It performs strided subsampling. During this step, any integer fields (like pid, CellID)
411 * are CAST to PetscScalar, so that all output arrays are of the same floating-point type.
412 * e. It populates the VTKMetaData struct with the final, subsampled, scalar-only data arrays,
413 * making it ready for the file writer.
414 *
415 * @param[in] user The UserCtx, containing both user->swarm and user->pp_swarm.
416 * @param[in] pps The PostProcessParams struct for configuration (field list, frequency).
417 * @param[out] meta A pointer to the VTKMetaData struct to be populated (on rank 0).
418 * @param[out] p_n_total A pointer to store the total number of particles before subsampling (on rank 0).
419 * @return PetscErrorCode
420 */
421PetscErrorCode PrepareOutputParticleData(UserCtx* user, PostProcessParams* pps, VTKMetaData* meta, PetscInt* p_n_total)
422{
423 PetscErrorCode ierr;
424 PetscInt n_total_particles, n_components;
425
426 PetscFunctionBeginUser;
427
428 // Initialize output parameters on all ranks
429 *p_n_total = 0;
430
431 // --- The entire preparation process is a series of collective gathers followed by rank-0 processing ---
432
433 // --- Step 1: Gather Full Coordinates from SOURCE Swarm (Collective) ---
434 // This establishes the ground truth for particle positions and total count.
435 PetscScalar *full_coords_arr = NULL;
436 // NOTE: This assumes SwarmFieldToArrayOnRank0 exists and works for PetscScalar fields.
437 // If not, it may need to be generalized like the logic below.
438 ierr = SwarmFieldToArrayOnRank0(user->swarm, "position", &n_total_particles, &n_components, &full_coords_arr); CHKERRQ(ierr);
439
440 // --- Step 2: Prepare and Subsample Data (Rank 0 Only) ---
441 if (user->simCtx->rank == 0) {
442 *p_n_total = n_total_particles; // Report original count back to the caller.
443
444 if (n_total_particles == 0) {
445 if (full_coords_arr) {
446 ierr = PetscFree(full_coords_arr); CHKERRQ(ierr); // Cleanup the gathered coords array
447 }
448 PetscFunctionReturn(0); // Nothing to prepare.
449 }
450 if (n_components != 3) {
451 SETERRQ(PETSC_COMM_SELF, 1, "Coordinate field position must have 3 components, but has %" PetscInt_FMT, n_components);
452 }
453
454 // --- Subsampling Calculation ---
455 PetscInt stride = pps->particle_output_freq > 0 ? pps->particle_output_freq : 1;
456 meta->npoints = (n_total_particles > 0) ? (n_total_particles - 1) / stride + 1 : 0;
457
458 LOG_ALLOW(LOCAL, LOG_DEBUG, "Subsampling %" PetscInt_FMT " total particles with stride %" PetscInt_FMT " -> %" PetscInt_FMT " output particles.\n",
459 n_total_particles, stride, meta->npoints);
460
461 // --- Prepare Final Coordinates Array ---
462 ierr = PetscMalloc1(3 * meta->npoints, &meta->coords); CHKERRQ(ierr);
463 for (PetscInt i = 0; i < meta->npoints; i++) {
464 PetscInt source_idx = i * stride;
465 for (int d = 0; d < 3; d++) meta->coords[3 * i + d] = full_coords_arr[3 * source_idx + d];
466 }
467 ierr = PetscFree(full_coords_arr); // Free the temporary full array immediately after use.
468
469
470 // --- Prepare Final Field Arrays (Gather -> Subsample -> Store) ---
471 char *fields_copy, *field_name;
472 ierr = PetscStrallocpy(pps->particle_fields, &fields_copy); CHKERRQ(ierr);
473 field_name = strtok(fields_copy, ",");
474 while (field_name && meta->num_point_data_fields < MAX_POINT_DATA_FIELDS) {
475 TrimWhitespace(field_name);
476 if (!*field_name || strcasecmp(field_name, "position") == 0) {
477 field_name = strtok(NULL, ","); continue;
478 }
479
480 // A. Determine which swarm is the source for this field.
481 DM swarm_to_use = user->swarm; // Default to the main solver swarm.
482 const char* internal_name = field_name; // Default internal name to user-facing name.
483
484 PetscErrorCode check_ierr;
485 ierr = PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL); CHKERRQ(ierr);
486 check_ierr = DMSwarmGetField(user->post_swarm, field_name, NULL, NULL, NULL);
487 ierr = PetscPopErrorHandler(); CHKERRQ(ierr);
488 if (!check_ierr) {
489 swarm_to_use = user->post_swarm;
490 ierr = DMSwarmRestoreField(user->post_swarm, field_name, NULL, NULL, NULL); CHKERRQ(ierr);
491 LOG_ALLOW(LOCAL, LOG_DEBUG, "Field '%s' will be sourced from the post-processing swarm.\n", field_name);
492 } else {
493 LOG_ALLOW(LOCAL, LOG_DEBUG, "Field '%s' will be sourced from the main solver swarm.\n", field_name);
494 if (strcasecmp(field_name, "pid") == 0) internal_name = "DMSwarm_pid";
495 else if (strcasecmp(field_name, "CellID") == 0) internal_name = "DMSwarm_CellID";
496 else if (strcasecmp(field_name, "Migration Status") == 0) internal_name = "DMSwarm_location_status";
497 }
498
499 // B. Gather the full data for this field from its determined source.
500 // NOTE: This assumes a generic gather function exists.
501 // A simplified placeholder is used here; you may need to implement this.
502 void* full_field_arr_void = NULL;
503 PetscInt field_total_particles, field_num_components;
504
505 ierr = SwarmFieldToArrayOnRank0(swarm_to_use, internal_name, &field_total_particles, &field_num_components, &full_field_arr_void); CHKERRQ(ierr);
506
507 if (field_total_particles != n_total_particles) {
508 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);
509 ierr = PetscFree(full_field_arr_void); CHKERRQ(ierr);
510 field_name = strtok(NULL, ","); continue;
511 }
512
513 // C. Allocate final array (ALWAYS as PetscScalar) and copy/cast subsampled data.
514 VTKFieldInfo* current_field = &meta->point_data_fields[meta->num_point_data_fields];
515 strncpy(current_field->name, field_name, MAX_VTK_FIELD_NAME_LENGTH - 1);
516 current_field->name[MAX_VTK_FIELD_NAME_LENGTH - 1] = '\0';
517 current_field->num_components = field_num_components;
518 ierr = PetscMalloc1(current_field->num_components * meta->npoints, (PetscScalar**)&current_field->data); CHKERRQ(ierr);
519
520 PetscScalar* final_data_arr = (PetscScalar*)current_field->data;
521
522 // D. Perform the subsampling and potential casting
523 if (strcasecmp(internal_name, "DMSwarm_pid") == 0) {
524 PetscInt64* source_arr = (PetscInt64*)full_field_arr_void;
525 for (PetscInt i = 0; i < meta->npoints; i++) {
526 final_data_arr[i] = (PetscScalar)source_arr[i * stride];
527 }
528 } else if (strcasecmp(internal_name, "DMSwarm_CellID") == 0 || strcasecmp(internal_name, "DMSwarm_location_status") == 0) {
529 PetscInt* source_arr = (PetscInt*)full_field_arr_void;
530 for (PetscInt i = 0; i < meta->npoints; i++) {
531 PetscInt source_idx = i * stride;
532 for (PetscInt c = 0; c < current_field->num_components; c++) {
533 final_data_arr[current_field->num_components * i + c] = (PetscScalar)source_arr[current_field->num_components * source_idx + c];
534 }
535 }
536 } else { // Default case: The field is already PetscScalar
537 PetscScalar* source_arr = (PetscScalar*)full_field_arr_void;
538 for (PetscInt i = 0; i < meta->npoints; i++) {
539 PetscInt source_idx = i * stride;
540 for (PetscInt c = 0; c < current_field->num_components; c++) {
541 final_data_arr[current_field->num_components * i + c] = source_arr[current_field->num_components * source_idx + c];
542 }
543 }
544 }
545
546 ierr = PetscFree(full_field_arr_void); // Free the temporary full array.
547 meta->num_point_data_fields++;
548 field_name = strtok(NULL, ",");
549 }
550 ierr = PetscFree(fields_copy); CHKERRQ(ierr);
551
552 // --- Finalize VTK MetaData for PolyData ---
553 if (meta->npoints > 0) {
554 meta->fileType = VTK_POLYDATA;
555 ierr = PetscMalloc1(meta->npoints, &meta->connectivity); CHKERRQ(ierr);
556 ierr = PetscMalloc1(meta->npoints, &meta->offsets); CHKERRQ(ierr);
557 for (PetscInt i = 0; i < meta->npoints; i++) {
558 meta->connectivity[i] = i;
559 meta->offsets[i] = i + 1;
560 }
561 }
562 } // End of rank 0 block
563
564 ierr = MPI_Barrier(PETSC_COMM_WORLD); CHKERRQ(ierr);
565
566 PetscFunctionReturn(0);
567}
PetscErrorCode SwarmFieldToArrayOnRank0(DM swarm, const char *field_name, PetscInt *n_total_particles, PetscInt *n_components, void **gathered_array)
Gathers any DMSwarm field from all ranks to a single, contiguous array on rank 0.
Definition io.c:1714
void TrimWhitespace(char *str)
Helper function to trim leading/trailing whitespace from a string.
Definition io.c:1910
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:1619
#define LOCAL
Logging scope definitions for controlling message output.
Definition logging.h:44
#define GLOBAL
Scope for global logging across all processes.
Definition logging.h:45
#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:207
@ LOG_ERROR
Critical errors that may halt the program.
Definition logging.h:29
@ LOG_INFO
Informational messages about program execution.
Definition logging.h:32
@ LOG_WARNING
Non-critical issues that warrant attention.
Definition logging.h:30
@ LOG_DEBUG
Detailed debugging information.
Definition logging.h:33
#define MAX_POINT_DATA_FIELDS
Defines the maximum number of data fields for VTK point data.
Definition variables.h:437
PetscInt npoints
Definition variables.h:484
PetscInt num_components
Definition variables.h:471
PetscMPIInt rank
Definition variables.h:516
PetscInt num_point_data_fields
Definition variables.h:487
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:633
DM post_swarm
Definition variables.h:701
PetscInt * connectivity
Definition variables.h:488
PetscInt * offsets
Definition variables.h:489
VTKFileType fileType
Definition variables.h:482
PetscScalar * data
Definition variables.h:472
char name[64]
Definition variables.h:470
PetscScalar * coords
Definition variables.h:485
PetscInt particle_output_freq
Definition variables.h:458
char particle_fields[1024]
Definition variables.h:456
VTKFieldInfo point_data_fields[20]
Definition variables.h:486
PetscInt mz
Definition variables.h:483
PetscInt my
Definition variables.h:483
PetscInt mx
Definition variables.h:483
#define MAX_VTK_FIELD_NAME_LENGTH
Maximum length for VTK field names.
Definition variables.h:438
@ VTK_POLYDATA
Definition variables.h:478
@ VTK_STRUCTURED
Definition variables.h:477
Holds all configuration parameters for a post-processing run.
Definition variables.h:443
User-defined context containing data specific to a single computational grid level.
Definition variables.h:630
Stores all necessary information for a single data array in a VTK file.
Definition variables.h:469
PetscErrorCode CreateVTKFileFromMetadata(const char *filename, const VTKMetaData *meta, MPI_Comm comm)
Creates and writes a VTK file (either .vts or .vtp) from a populated metadata struct.
Definition vtk_io.c:128
PetscErrorCode PrepareOutputEulerianFieldData(UserCtx *user, Vec field_vec, PetscInt num_components, PetscScalar **out_data)
Creates a C array of field data corresponding to a subsampled (legacy-style) grid.
Definition vtk_io.c:287
static PetscErrorCode WriteVTKFileFooter(FILE *fp, const VTKMetaData *meta)
Definition vtk_io.c:116
PetscErrorCode PrepareOutputCoordinates(UserCtx *user, PetscScalar **out_coords, PetscInt *out_nx, PetscInt *out_ny, PetscInt *out_nz, PetscInt *out_npoints)
Creates a C array of coordinates corresponding to a subsampled (legacy-style) grid.
Definition vtk_io.c:223
static PetscErrorCode WriteVTKAppendedBlock(FILE *fp, const void *data, PetscInt num_elements, size_t element_size)
Definition vtk_io.c:17
static PetscErrorCode WriteVTSXMLHeader(FILE *fp, const VTKMetaData *meta, PetscInt *boffset)
Definition vtk_io.c:24
static PetscErrorCode WriteVTKFileHeader(FILE *fp, const VTKMetaData *meta, PetscInt *boffset)
Definition vtk_io.c:106
static PetscErrorCode WriteVTPXMLHeader(FILE *fp, const VTKMetaData *meta, PetscInt *boffset)
Definition vtk_io.c:60
PetscErrorCode PrepareOutputParticleData(UserCtx *user, PostProcessParams *pps, VTKMetaData *meta, PetscInt *p_n_total)
Gathers, merges, subsamples, and prepares particle data for VTK output.
Definition vtk_io.c:421