PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
Loading...
Searching...
No Matches
postprocessor.h File Reference
#include "io.h"
#include "variables.h"
#include "logging.h"
#include "ParticleSwarm.h"
#include "interpolation.h"
#include "grid.h"
#include "setup.h"
#include "Metric.h"
#include "postprocessing_kernels.h"
#include "vtk_io.h"
Include dependency graph for postprocessor.h:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Functions

PetscErrorCode SetupPostProcessSwarm (UserCtx *user, PostProcessParams *pps)
 Creates a new, dedicated DMSwarm for post-processing tasks.
 
PetscErrorCode WriteEulerianFile (UserCtx *user, PostProcessParams *pps, PetscInt ti)
 Orchestrates the writing of a combined, multi-field VTK file for a single time step.
 
PetscErrorCode EulerianDataProcessingPipeline (UserCtx *user, PostProcessParams *pps)
 Parses the processing pipeline string and executes the requested kernels.
 
PetscErrorCode ParticleDataProcessingPipeline (UserCtx *user, PostProcessParams *pps)
 Parses and executes the particle pipeline using a robust two-pass approach.
 
PetscErrorCode WriteParticleFile (UserCtx *user, PostProcessParams *pps, PetscInt ti)
 Writes particle data to a VTP file using the Prepare-Write-Cleanup pattern.
 

Function Documentation

◆ SetupPostProcessSwarm()

PetscErrorCode SetupPostProcessSwarm ( UserCtx user,
PostProcessParams pps 
)

Creates a new, dedicated DMSwarm for post-processing tasks.

This function is called once at startup. It creates an empty DMSwarm and associates it with the same grid DM as the primary swarm and registers all the required fields.

Parameters
userThe UserCtx where user->post_swarm will be created.
ppsThe PostProcessParams containing the particle_pipeline string for field registration.
Returns
PetscErrorCode

Definition at line 23 of file postprocessor.c.

24{
25 PetscErrorCode ierr;
26 PetscFunctionBeginUser;
28 char *pipeline_copy, *step_token, *step_saveptr;
29 PetscBool finalize_needed = PETSC_FALSE;
30
31 ierr = DMCreate(PETSC_COMM_WORLD, &user->post_swarm); CHKERRQ(ierr);
32 ierr = DMSetType(user->post_swarm, DMSWARM); CHKERRQ(ierr);
33 ierr = DMSetDimension(user->post_swarm, 3); CHKERRQ(ierr);
34 ierr = DMSwarmSetType(user->post_swarm, DMSWARM_BASIC); CHKERRQ(ierr);
35 // Associate it with the same grid as the solver's swarm
36 if (user->da) {
37 ierr = DMSwarmSetCellDM(user->post_swarm, user->da); CHKERRQ(ierr);
38 LOG_ALLOW(LOCAL,LOG_INFO,"Associated DMSwarm with Cell DM (user->da).\n");
39 } else {
40 // If user->da is essential for your simulation logic with particles, this should be a fatal error.
41 LOG_ALLOW(GLOBAL, LOG_WARNING, "user->da (Cell DM for Swarm) is NULL. Cell-based swarm operations might fail.\n");
42 // SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONGSTATE, "CreateParticleSwarm - user->da (Cell DM) is NULL but required.");
43 }
44
45 LOG_ALLOW(GLOBAL, LOG_INFO, "Created dedicated DMSwarm for post-processing.\n");
46
47 LOG_ALLOW(GLOBAL, LOG_DEBUG, " --- Setting up Post-Processing Pipeline fields-- \n");
48
49 ierr = PetscStrallocpy(pps->particle_pipeline, &pipeline_copy); CHKERRQ(ierr);
50 step_token = strtok_r(pipeline_copy, ";", &step_saveptr);
51 while (step_token) {
52 TrimWhitespace(step_token);
53 if (strlen(step_token) == 0) { step_token = strtok_r(NULL, ";", &step_saveptr); continue; }
54
55 char *keyword = strtok(step_token, ":");
56 char *args_str = strtok(NULL, "");
57 TrimWhitespace(keyword);
58 PetscInt output_field_dimensions = 1; // Default to scalar output fields
59
60 if (strcasecmp(keyword, "ComputeSpecificKE") == 0) {
61 if (!args_str) SETERRQ(PETSC_COMM_SELF, 1, "Error (ComputeSpecificKE): Missing arguments.");
62 char *inputs_str = strtok(args_str, ">");
63 char *output_field = strtok(NULL, ">");
64 output_field_dimensions = 1; // SKE is scalar
65 if (!output_field) SETERRQ(PETSC_COMM_SELF, 1, "Error (ComputeSpecificKE): Missing output field in 'in>out' syntax.");
66 TrimWhitespace(output_field);
67 // Register the output field
68 ierr = RegisterSwarmField(user->post_swarm, output_field, output_field_dimensions,PETSC_REAL); CHKERRQ(ierr);
69 LOG_ALLOW(GLOBAL, LOG_INFO, "Registered particle field '%s' (ComputeSpecificKE).\n", output_field);
70 finalize_needed = PETSC_TRUE;
71 } else {
72 LOG_ALLOW(GLOBAL, LOG_WARNING, "Warning: Unknown particle transformation keyword '%s'. Skipping.\n", keyword);
73 }
74
75 // Add other 'else if' blocks here for other kernels that create output fields
76
77 step_token = strtok_r(NULL, ";", &step_saveptr);
78 } // while step_token
79
80 ierr = PetscFree(pipeline_copy); CHKERRQ(ierr);
81
82 // --- FINALIZE STEP ---
83 if (finalize_needed) {
84 LOG_ALLOW(GLOBAL, LOG_INFO, "Finalizing new field registrations for particle pipeline.\n");
85 ierr = DMSwarmFinalizeFieldRegister(user->post_swarm); CHKERRQ(ierr);
86 }
87
88 LOG_ALLOW(GLOBAL, LOG_INFO, "Post-Processing DMSwarm setup complete.\n");
89
91 PetscFunctionReturn(0);
92}
PetscErrorCode RegisterSwarmField(DM swarm, const char *fieldName, PetscInt fieldDim, PetscDataType dtype)
Registers a swarm field without finalizing registration.
void TrimWhitespace(char *str)
Helper function to trim leading/trailing whitespace from a string.
Definition io.c:36
#define LOCAL
Logging scope definitions for controlling message output.
Definition logging.h:46
#define GLOBAL
Scope for global logging across all processes.
Definition logging.h:47
#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:201
#define PROFILE_FUNCTION_END
Marks the end of a profiled code block.
Definition logging.h:740
@ 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 PROFILE_FUNCTION_BEGIN
Marks the beginning of a profiled code block (typically a function).
Definition logging.h:731
DM post_swarm
Definition variables.h:733
char particle_pipeline[1024]
Definition variables.h:467
Here is the call graph for this function:
Here is the caller graph for this function:

◆ WriteEulerianFile()

PetscErrorCode WriteEulerianFile ( UserCtx user,
PostProcessParams pps,
PetscInt  ti 
)

Orchestrates the writing of a combined, multi-field VTK file for a single time step.

This function is the primary driver for generating output. It performs these steps:

  1. Prepares the subsampled coordinate array required for the legacy grid format.
  2. Parses the user-requested list of fields from the configuration.
  3. For each field, prepares a corresponding subsampled data array.
  4. Assembles all prepared arrays into a single VTKMetaData struct.
  5. Calls the low-level VTK writer to generate the final .vts file.
  6. Frees all temporary memory allocated during the preparation phase.
Parameters
userThe UserCtx for the finest grid level.
ppsThe post-processing configuration struct.
tiThe current time step index.
Returns
PetscErrorCode

Orchestrates the writing of a combined, multi-field VTK file for a single time step.

This function exports instantaneous Eulerian field data (such as pressure, velocity, Q-criterion, and particle concentration) to a VTK structured grid file for the specified time step. The output includes subsampled interior grid coordinates and point data fields as specified in the PostProcessParams configuration.

The function performs the following steps:

  1. Initializes VTK metadata structure
  2. Prepares output coordinates (subsampled interior grid)
  3. Gathers and prepares requested field data on rank 0
  4. Writes the VTK structured grid file with the naming convention: {prefix}_{ti:05d}.vts

Only fields listed in pps->output_fields_instantaneous are written. If the field list is empty, the function returns immediately without creating a file.

Note
Fields containing particle data (e.g., Psi_nodal) are automatically skipped when no particles are present in the simulation (simCtx->np == 0).
Parameters
userThe UserCtx containing simulation data and field vectors to be output.
ppsThe PostProcessParams struct containing output configuration (field list, prefix).
tiThe time index/step number used for file naming.
Returns
PetscErrorCode indicating success or failure.

Definition at line 217 of file postprocessor.c.

218{
219 PetscErrorCode ierr;
220 VTKMetaData meta;
221 char filename[MAX_FILENAME_LENGTH];
222
223 PetscFunctionBeginUser;
225
226 if (pps->output_fields_instantaneous[0] == '\0') {
227 LOG_ALLOW(GLOBAL, LOG_DEBUG, "No instantaneous fields requested for output at ti=%" PetscInt_FMT ". Skipping.\n", ti);
228 PetscFunctionReturn(0);
229 }
230
231 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Starting VTK File Writing for ti = %" PetscInt_FMT " ---\n", ti);
232
233 /* 1) Metadata init */
234 ierr = PetscMemzero(&meta, sizeof(VTKMetaData)); CHKERRQ(ierr);
236
237 /* 2) Coordinates (subsampled interior) */
238 ierr = PrepareOutputCoordinates(user, &meta.coords, &meta.mx, &meta.my, &meta.mz, &meta.npoints); CHKERRQ(ierr);
239 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Using coords linearization order: fast=i mid=j slow=k (sizes: %" PetscInt_FMT " x %" PetscInt_FMT " x %" PetscInt_FMT ")\n",
240 meta.mx, meta.my, meta.mz);
241
242 /* 3) Fields (rank 0) */
243 if (user->simCtx->rank == 0) {
244 char *fields_copy, *field_name;
245 ierr = PetscStrallocpy(pps->output_fields_instantaneous, &fields_copy); CHKERRQ(ierr);
246
247 field_name = strtok(fields_copy, ",");
248 while (field_name) {
249 TrimWhitespace(field_name);
250 if (!*field_name) { field_name = strtok(NULL, ","); continue; }
251
252 LOG_ALLOW(LOCAL, LOG_DEBUG, "Preparing field '%s' for output.\n", field_name);
253
254 Vec field_vec = NULL;
255 PetscInt num_components = 0;
256
257 if (!strcasecmp(field_name, "P_nodal")) {
258 field_vec = user->P_nodal; num_components = 1;
259 } else if (!strcasecmp(field_name, "Ucat_nodal")) {
260 field_vec = user->Ucat_nodal; num_components = 3;
261 } else if (!strcasecmp(field_name, "Qcrit")) {
262 field_vec = user->Qcrit; num_components = 1;
263 } else if (!strcasecmp(field_name, "Psi_nodal")){
264 if(user->simCtx->np==0){
265 LOG_ALLOW(LOCAL, LOG_WARNING, "Field 'Psi_nodal' requested but no particles are present. Skipping.\n");
266 field_name = strtok(NULL, ",");
267 continue;
268 }
269 field_vec = user->Psi_nodal; num_components = 1;
270 } else {
271 LOG_ALLOW(LOCAL, LOG_WARNING, "Field '%s' not recognized. Skipping.\n", field_name);
272 field_name = strtok(NULL, ",");
273 continue;
274 }
275
277 LOG_ALLOW(LOCAL, LOG_WARNING, "MAX_POINT_DATA_FIELDS reached. Cannot add '%s'.\n", field_name);
278 field_name = strtok(NULL, ",");
279 continue;
280 }
281
282 // --- Add field to metadata ---
283 VTKFieldInfo* current_field = &meta.point_data_fields[meta.num_point_data_fields];
284 strncpy(current_field->name, field_name, MAX_VTK_FIELD_NAME_LENGTH-1);
285 current_field->name[MAX_VTK_FIELD_NAME_LENGTH-1] = '\0';
286 current_field->num_components = num_components;
287
288 /* Build interior AoS from NATURAL gathered Vec */
289 ierr = PrepareOutputEulerianFieldData(user, field_vec, num_components, &current_field->data); CHKERRQ(ierr);
290
291 /*
292 // *** DEBUG: Dump Ucat_nodal details and add scalar companions Ux, Uy, Uz for easier visualization ***
293
294 // If this is Ucat_nodal, dump a few tuples and add scalar companions
295 if (!strcasecmp(field_name, "Ucat_nodal")) {
296 const PetscInt npts = meta.npoints;
297 const PetscScalar *a = (const PetscScalar*)current_field->data;
298
299 LOG_ALLOW(GLOBAL, LOG_INFO, "DBG Ucat_nodal: ptr=%p npoints=%" PetscInt_FMT " num_components=%" PetscInt_FMT "\n",
300 (void*)a, npts, current_field->num_components);
301
302 if (a && current_field->num_components == 3 && npts > 0) {
303 const PetscInt nshow = (npts < 5) ? npts : 5;
304 LOG_ALLOW(GLOBAL, LOG_INFO, "DBG Ucat_nodal: showing first %d of %" PetscInt_FMT " tuples (AoS x,y,z):\n",
305 (int)nshow, npts);
306 for (PetscInt t = 0; t < nshow; ++t) {
307 LOG_ALLOW(GLOBAL, LOG_INFO, " Ucat_nodal[%3" PetscInt_FMT "] = (%g, %g, %g)\n",
308 t, (double)a[3*t+0], (double)a[3*t+1], (double)a[3*t+2]);
309 }
310 if (npts > 10) {
311 PetscInt mid = npts / 2;
312 LOG_ALLOW(GLOBAL, LOG_INFO, " Ucat_nodal[mid=%" PetscInt_FMT "] = (%g, %g, %g)\n",
313 mid, (double)a[3*mid+0], (double)a[3*mid+1], (double)a[3*mid+2]);
314 }
315
316 // Add scalar companions from the AoS we just created
317 PetscScalar *Ux=NULL,*Uy=NULL,*Uz=NULL;
318 ierr = PetscMalloc1(meta.npoints, &Ux); CHKERRQ(ierr);
319 ierr = PetscMalloc1(meta.npoints, &Uy); CHKERRQ(ierr);
320 ierr = PetscMalloc1(meta.npoints, &Uz); CHKERRQ(ierr);
321 for (PetscInt i = 0; i < meta.npoints; ++i) {
322 Ux[i] = a[3*i+0]; Uy[i] = a[3*i+1]; Uz[i] = a[3*i+2];
323 }
324
325 if (meta.num_point_data_fields + 3 <= MAX_POINT_DATA_FIELDS) {
326 VTKFieldInfo *fx = &meta.point_data_fields[++meta.num_point_data_fields];
327 strncpy(fx->name, "Ux_debug", MAX_VTK_FIELD_NAME_LENGTH-1);
328 fx->name[MAX_VTK_FIELD_NAME_LENGTH-1] = '\0';
329 fx->num_components = 1; fx->data = Ux;
330
331 VTKFieldInfo *fy = &meta.point_data_fields[++meta.num_point_data_fields];
332 strncpy(fy->name, "Uy_debug", MAX_VTK_FIELD_NAME_LENGTH-1);
333 fy->name[MAX_VTK_FIELD_NAME_LENGTH-1] = '\0';
334 fy->num_components = 1; fy->data = Uy;
335
336 VTKFieldInfo *fz = &meta.point_data_fields[++meta.num_point_data_fields];
337 strncpy(fz->name, "Uz_debug", MAX_VTK_FIELD_NAME_LENGTH-1);
338 fz->name[MAX_VTK_FIELD_NAME_LENGTH-1] = '\0';
339 fz->num_components = 1; fz->data = Uz;
340
341 LOG_ALLOW(GLOBAL, LOG_INFO, "DBG: Added scalar companions Ux_debug, Uy_debug, Uz_debug.\n");
342 } else {
343 LOG_ALLOW(GLOBAL, LOG_WARNING, "DBG: Not enough slots to add Ux/Uy/Uz debug fields.\n");
344 PetscFree(Ux); PetscFree(Uy); PetscFree(Uz);
345 }
346
347 // Mid-plane CSV + AoS vs NATURAL compare (component X)
348
349 // Gather NATURAL again (small cost, but isolated and clear)
350 PetscInt Ng = 0;
351 double *nat_d = NULL;
352 DM dmU = NULL;
353 DMDALocalInfo infU;
354 ierr = VecGetDM(field_vec, &dmU); CHKERRQ(ierr);
355 ierr = DMDAGetLocalInfo(dmU, &infU); CHKERRQ(ierr);
356
357 const PetscInt M=infU.mx, N=infU.my, P=infU.mz;
358 const PetscInt mx = meta.mx, my = meta.my, mz = meta.mz;
359 const PetscInt iInnerMid = mx/2; // interior index [0..mx-1]
360 const PetscInt iGlob = iInnerMid;
361
362 ierr = VecToArrayOnRank0(field_vec, &Ng, &nat_d); CHKERRQ(ierr);
363
364 if (nat_d) {
365 const PetscScalar *nar = (const PetscScalar*)nat_d;
366 const char *base = pps->output_prefix;
367 char fn[512], fnc[512];
368 snprintf(fn, sizeof(fn), "%s_%05" PetscInt_FMT "_iMid.csv", base, ti);
369 snprintf(fnc, sizeof(fnc), "%s_%05" PetscInt_FMT "_iMid_compare.csv", base, ti);
370
371 FILE *fp = fopen(fn, "w");
372 FILE *fpc = fopen(fnc, "w");
373 if (fp) fprintf(fp, "jInner,kInner,Ux,Uy,Uz\n");
374 if (fpc) fprintf(fpc, "jInner,kInner,Ux_AoS,Ux_NAT,abs_diff\n");
375
376 double maxAbsDiff = 0.0, sumAbs = 0.0;
377 PetscInt count = 0;
378
379 for (PetscInt kInner = 0; kInner < mz; ++kInner) {
380 const PetscInt k = kInner;
381 for (PetscInt jInner = 0; jInner < my; ++jInner) {
382 const PetscInt j = jInner;
383
384 // AoS tuple index
385 const PetscInt t = iInnerMid + mx * (jInner + my * kInner);
386 const PetscScalar ux = a[3*t+0], uy = a[3*t+1], uz = a[3*t+2];
387
388 if (fp) fprintf(fp, "%d,%d,%.15e,%.15e,%.15e\n",
389 (int)jInner,(int)kInner,(double)ux,(double)uy,(double)uz);
390
391 // NATURAL base for (iGlob,j,k)
392 const PetscInt baseNat = 3 * (((k)*N + j)*M + iGlob);
393 const PetscScalar uxN = nar[baseNat + 0];
394
395 const double diff = fabs((double)ux - (double)uxN);
396 if (diff > maxAbsDiff) maxAbsDiff = diff;
397 sumAbs += diff; ++count;
398
399 if (fpc) fprintf(fpc, "%d,%d,%.15e,%.15e,%.15e\n",
400 (int)jInner,(int)kInner,(double)ux,(double)uxN,diff);
401 } // for jInner
402 } // for kInner
403 if (fp) fclose(fp);
404 if (fpc) fclose(fpc);
405
406 if (count > 0) {
407 const double meanAbs = sumAbs / (double)count;
408 LOG_ALLOW(GLOBAL, LOG_INFO,
409 "PETSc-Vec vs AoS (i-mid, Ux): max|Δ|=%.6e, mean|Δ|=%.6e -> CSV: %s\n",
410 maxAbsDiff, meanAbs, fnc);
411 LOG_ALLOW(GLOBAL, LOG_INFO, "Wrote i-mid plane CSV: %s\n", fn);
412 } // if count>0
413
414 ierr = PetscFree(nat_d); CHKERRQ(ierr);
415
416
417 } // if nat_d
418
419 } // if a && num_components==3 && npts>0
420 } // if Ucat_nodal
421 // --- END DEBUG BLOCK (Ucat_nodal) ---
422 */
423
424 meta.num_point_data_fields++; /* count the main field we just added */
425 field_name = strtok(NULL, ",");
426 }
427
428 ierr = PetscFree(fields_copy); CHKERRQ(ierr);
429
430 // --- DEBUG: Add sanity fields i_idx, j_idx, k_idx and x_pos, y_pos, z_pos ---
431 // These are the logical indices and physical coordinates of each point in the subsampled grid.
432 // They can be used to verify the grid structure and orientation in visualization tools.
433 // They are added as scalar fields with names "i_idx", "j_idx", "k_idx" and "x_pos", "y_pos", "z_pos".
434 // Note: these are only added if there is room in the MAX_POINT_DATA_FIELDS limit.
435 // They are allocated and owned here, and will be freed below.
436 // They are in the same linearization order as meta.coords (AoS x,y,z by point).
437 /*
438 // Append sanity fields i/j/k indices and coordinates
439
440 // Build i/j/k and x/y/z (length = npoints); these match the same linearization as coords
441 const PetscInt n = meta.npoints;
442 PetscScalar *i_idx=NULL,*j_idx=NULL,*k_idx=NULL,*x_pos=NULL,*y_pos=NULL,*z_pos=NULL;
443
444 ierr = PetscMalloc1(n, &i_idx); CHKERRQ(ierr);
445 ierr = PetscMalloc1(n, &j_idx); CHKERRQ(ierr);
446 ierr = PetscMalloc1(n, &k_idx); CHKERRQ(ierr);
447 ierr = PetscMalloc1(n, &x_pos); CHKERRQ(ierr);
448 ierr = PetscMalloc1(n, &y_pos); CHKERRQ(ierr);
449 ierr = PetscMalloc1(n, &z_pos); CHKERRQ(ierr);
450
451 // coords is length 3*n, AoS: (x,y,z) by point
452 const PetscScalar *c = (const PetscScalar*)meta.coords;
453 for (PetscInt k = 0; k < meta.mz; ++k) {
454 for (PetscInt j = 0; j < meta.my; ++j) {
455 for (PetscInt i = 0; i < meta.mx; ++i) {
456 const PetscInt t = i + meta.mx * (j + meta.my * k);
457 i_idx[t] = (PetscScalar)i;
458 j_idx[t] = (PetscScalar)j;
459 k_idx[t] = (PetscScalar)k;
460 x_pos[t] = c[3*t+0];
461 y_pos[t] = c[3*t+1];
462 z_pos[t] = c[3*t+2];
463 }
464 }
465 }
466
467 const char *nf[6] = {"i_idx","j_idx","k_idx","x_pos","y_pos","z_pos"};
468 PetscScalar *arrs[6] = {i_idx,j_idx,k_idx,x_pos,y_pos,z_pos};
469 for (int s=0; s<6; ++s) {
470 if (meta.num_point_data_fields < MAX_POINT_DATA_FIELDS) {
471 VTKFieldInfo *f = &meta.point_data_fields[meta.num_point_data_fields++];
472 strncpy(f->name, nf[s], MAX_VTK_FIELD_NAME_LENGTH-1);
473 f->name[MAX_VTK_FIELD_NAME_LENGTH-1] = '\0';
474 f->num_components = 1;
475 f->data = arrs[s];
476 } else {
477 LOG_ALLOW(GLOBAL, LOG_WARNING, "Sanity field '%s' dropped: MAX_POINT_DATA_FIELDS reached.\n", nf[s]);
478 PetscFree(arrs[s]);
479 }
480 }
481 LOG_ALLOW(GLOBAL, LOG_INFO, "DBG: Added sanity fields i_idx/j_idx/k_idx and x_pos/y_pos/z_pos.\n");
482 */
483 // --- END DEBUG BLOCK (sanity fields) ---
484
485 /* Field summary */
486 LOG_ALLOW(GLOBAL, LOG_INFO, "PointData fields to write: %d\n", (int)meta.num_point_data_fields);
487 for (PetscInt ii=0; ii<meta.num_point_data_fields; ++ii) {
488 LOG_ALLOW(GLOBAL, LOG_INFO, " # %2" PetscInt_FMT " Field Name = %s Components = %d\n",
489 ii, meta.point_data_fields[ii].name, (int)meta.point_data_fields[ii].num_components);
490 }
491 } // if rank 0
492
493 /* 4) Write the VTS */
494 snprintf(filename, sizeof(filename), "%s_%05" PetscInt_FMT ".vts", pps->output_prefix, ti);
495 ierr = CreateVTKFileFromMetadata(filename, &meta, PETSC_COMM_WORLD); CHKERRQ(ierr);
496
497 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Eulerian File Writing for ti = %" PetscInt_FMT " Complete ---\n", ti);
499 PetscFunctionReturn(0);
500}
PetscInt CreateVTKFileFromMetadata(const char *filename, const VTKMetaData *meta, MPI_Comm comm)
Definition vtk_io.c:128
Vec P_nodal
Definition variables.h:734
#define MAX_POINT_DATA_FIELDS
Defines the maximum number of data fields for VTK point data.
Definition variables.h:446
PetscInt npoints
Definition variables.h:499
PetscInt num_components
Definition variables.h:486
PetscMPIInt rank
Definition variables.h:541
PetscInt num_point_data_fields
Definition variables.h:502
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:664
char output_prefix[256]
Definition variables.h:466
Vec Ucat_nodal
Definition variables.h:735
PetscInt np
Definition variables.h:616
#define MAX_FILENAME_LENGTH
Definition variables.h:444
Vec Qcrit
Definition variables.h:736
VTKFileType fileType
Definition variables.h:497
char output_fields_instantaneous[1024]
Definition variables.h:464
PetscScalar * data
Definition variables.h:487
char name[64]
Definition variables.h:485
PetscScalar * coords
Definition variables.h:500
VTKFieldInfo point_data_fields[20]
Definition variables.h:501
Vec Psi_nodal
Definition variables.h:737
PetscInt mz
Definition variables.h:498
PetscInt my
Definition variables.h:498
PetscInt mx
Definition variables.h:498
#define MAX_VTK_FIELD_NAME_LENGTH
Maximum length for VTK field names.
Definition variables.h:447
@ VTK_STRUCTURED
Definition variables.h:492
Stores all necessary information for a single data array in a VTK file.
Definition variables.h:484
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
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
Here is the call graph for this function:
Here is the caller graph for this function:

◆ EulerianDataProcessingPipeline()

PetscErrorCode EulerianDataProcessingPipeline ( UserCtx user,
PostProcessParams pps 
)

Parses the processing pipeline string and executes the requested kernels.

Parameters
userThe UserCtx containing the data to be transformed.
configThe PostProcessConfig containing the pipeline string.
Returns
PetscErrorCode

Parses the processing pipeline string and executes the requested kernels.

This function uses a general-purpose parser to handle a syntax of the form: "Keyword1:in1>out1; Keyword2:in1,in2>out2; Keyword3:arg1;"

It tokenizes the pipeline string and dispatches to the appropriate kernel function from processing_kernels.c with the specified field name arguments.

Parameters
userThe UserCtx containing the data to be transformed.
ppsThe PostProcessParams struct containing the pipeline string.
Returns
PetscErrorCode

Definition at line 110 of file postprocessor.c.

111{
112 PetscErrorCode ierr;
113 char *pipeline_copy, *step_token, *step_saveptr;
114
115 PetscFunctionBeginUser;
117 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Starting Data Transformation Pipeline ---\n");
118
119 // Do nothing if the pipeline string is empty
120 if (pps->process_pipeline[0] == '\0') {
121 LOG_ALLOW(GLOBAL, LOG_INFO, "Processing pipeline is empty. No transformations will be run.\n");
122 PetscFunctionReturn(0);
123 }
124
125 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Pipeline string: [%s]\n", pps->process_pipeline);
126
127 // Make a writable copy for strtok_r, as it modifies the string
128 ierr = PetscStrallocpy(pps->process_pipeline, &pipeline_copy); CHKERRQ(ierr);
129
130 // --- Outer Loop: Tokenize by Semicolon (;) to get each processing step ---
131 step_token = strtok_r(pipeline_copy, ";", &step_saveptr);
132 while (step_token) {
133 TrimWhitespace(step_token);
134 if (strlen(step_token) == 0) {
135 step_token = strtok_r(NULL, ";", &step_saveptr);
136 continue;
137 }
138
139 char *keyword = strtok(step_token, ":");
140 char *args_str = strtok(NULL, ""); // Get the rest of the string as arguments
141
142 if (!keyword) { // Should not happen with TrimWhitespace, but is a safe check
143 step_token = strtok_r(NULL, ";", &step_saveptr);
144 continue;
145 }
146
147 TrimWhitespace(keyword);
148 if (args_str) TrimWhitespace(args_str);
149
150 LOG_ALLOW(GLOBAL, LOG_INFO, "Executing Transformation: '%s' on args: '%s'\n", keyword, args_str ? args_str : "None");
151
152 // --- DISPATCHER: Route to the correct kernel based on the keyword ---
153 if (strcasecmp(keyword, "CellToNodeAverage") == 0) {
154 if (!args_str) SETERRQ(PETSC_COMM_SELF, 1, "CellToNodeAverage requires arguments in 'in_field>out_field' format.");
155 char *in_field = strtok(args_str, ">");
156 char *out_field = strtok(NULL, ">");
157 if (!in_field || !out_field) SETERRQ(PETSC_COMM_SELF, 1, "CellToNodeAverage requires 'in>out' syntax (e.g., P>P_nodal).");
158 if(strcmp(in_field,out_field)==0) SETERRQ(PETSC_COMM_SELF, 1, "CellToNodeAverage input and output fields must be different.");
159 if(user->simCtx->np == 0 && (strcmp(out_field,"Psi_nodal")==0 || strcmp(in_field,"Psi_nodal")==0)){
160 LOG(GLOBAL,LOG_WARNING,"CellToNodeAverage cannot process 'Psi_nodal' when no particles are present in the simulation.\n");
161 step_token = strtok_r(NULL, ";", &step_saveptr);
162 continue;
163 }
164 TrimWhitespace(in_field); TrimWhitespace(out_field);
165 ierr = ComputeNodalAverage(user, in_field, out_field); CHKERRQ(ierr);
166 }
167 else if (strcasecmp(keyword, "ComputeQCriterion") == 0) {
168 ierr = ComputeQCriterion(user); CHKERRQ(ierr);
169 }
170 else if (strcasecmp(keyword, "NormalizeRelativeField") == 0) {
171 if (!args_str) SETERRQ(PETSC_COMM_SELF, 1, "NormalizePressure requires the pressure field name (e.g., 'P') as an argument.");
172 ierr = NormalizeRelativeField(user, args_str); CHKERRQ(ierr);
173 }
174 // *** Add new kernels here in the future using 'else if' ***
175 // else if (strcasecmp(keyword, "ComputeVorticity") == 0) { ... }
176 else {
177 LOG_ALLOW(GLOBAL, LOG_WARNING, "Unknown transformation keyword '%s'. Skipping.\n", keyword);
178 }
179
180 step_token = strtok_r(NULL, ";", &step_saveptr);
181 }
182
183 ierr = PetscFree(pipeline_copy); CHKERRQ(ierr);
184 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Data Transformation Pipeline Complete ---\n");
186 PetscFunctionReturn(0);
187}
#define LOG(scope, level, fmt,...)
Logging macro for PETSc-based applications with scope control.
Definition logging.h:85
PetscErrorCode ComputeQCriterion(UserCtx *user)
Computes the Q-Criterion, a scalar value identifying vortex cores.
PetscErrorCode NormalizeRelativeField(UserCtx *user, const char *relative_field_name)
Normalizes a relative field by subtracting a reference value.
PetscErrorCode ComputeNodalAverage(UserCtx *user, const char *in_field_name, const char *out_field_name)
Computes node-centered data by averaging 8 surrounding cell-centered values, exactly replicating the ...
char process_pipeline[1024]
Definition variables.h:463
Here is the call graph for this function:
Here is the caller graph for this function:

◆ ParticleDataProcessingPipeline()

PetscErrorCode ParticleDataProcessingPipeline ( UserCtx user,
PostProcessParams pps 
)

Parses and executes the particle pipeline using a robust two-pass approach.

This function ensures correctness and efficiency by separating field registration from kernel execution.

PASS 1 (Registration): The pipeline string is parsed to identify all new fields that will be created. These fields are registered with the DMSwarm.

Finalize: After Pass 1, DMSwarmFinalizeFieldRegister is called exactly once if any new fields were added, preparing the swarm's memory layout.

PASS 2 (Execution): The pipeline string is parsed again, and this time the actual compute kernels are executed, filling the now-valid fields.

Parameters
userThe UserCtx containing the DMSwarm.
ppsThe PostProcessParams struct containing the particle_pipeline string.
Returns
PetscErrorCode

Definition at line 524 of file postprocessor.c.

525{
526 PetscErrorCode ierr;
527 char *pipeline_copy, *step_token, *step_saveptr;
528
529 PetscFunctionBeginUser;
530
532
533 // --- Timestep Setup: Synchronize post_swarm size ---
534 PetscInt n_global_source;
535 ierr = DMSwarmGetSize(user->swarm, &n_global_source); CHKERRQ(ierr);
536
537 // Resize post_swarm to match source swarm
538 ierr = ResizeSwarmGlobally(user->post_swarm, n_global_source); CHKERRQ(ierr);
539
540 if (pps->particle_pipeline[0] == '\0') {
541 PetscFunctionReturn(0);
542 }
543
544 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Starting Particle Data Transformation Pipeline ---\n");
545 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Particle Pipeline string: [%s]\n", pps->particle_pipeline);
546
547 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Executing compute kernels...\n");
548 ierr = PetscStrallocpy(pps->particle_pipeline, &pipeline_copy); CHKERRQ(ierr);
549 step_token = strtok_r(pipeline_copy, ";", &step_saveptr);
550 while (step_token) {
551 TrimWhitespace(step_token);
552 if (strlen(step_token) == 0) { step_token = strtok_r(NULL, ";", &step_saveptr); continue; }
553
554 char *keyword = strtok(step_token, ":");
555 char *args_str = strtok(NULL, "");
556 TrimWhitespace(keyword);
557 if (args_str) TrimWhitespace(args_str);
558
559 LOG_ALLOW(GLOBAL, LOG_INFO, "Executing Particle Transformation: '%s' on args: '%s'\n", keyword, args_str ? args_str : "None");
560
561 if (strcasecmp(keyword, "ComputeSpecificKE") == 0) {
562 char *velocity_field = strtok(args_str, ">");
563 char *ske_field = strtok(NULL, ">");
564 TrimWhitespace(velocity_field); TrimWhitespace(ske_field);
565
566 ierr = ComputeSpecificKE(user, velocity_field, ske_field); CHKERRQ(ierr);
567 }
568 else {
569 LOG_ALLOW(GLOBAL, LOG_WARNING, "Unknown particle transformation keyword '%s'. Skipping.\n", keyword);
570 }
571
572 step_token = strtok_r(NULL, ";", &step_saveptr);
573 }
574 ierr = PetscFree(pipeline_copy); CHKERRQ(ierr);
575
576 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Particle Data Transformation Pipeline Complete ---\n");
577
579 PetscFunctionReturn(0);
580}
PetscErrorCode ResizeSwarmGlobally(DM swarm, PetscInt N_target)
PetscErrorCode ComputeSpecificKE(UserCtx *user, const char *velocity_field, const char *ske_field)
Computes the specific kinetic energy (KE per unit mass) for each particle.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ WriteParticleFile()

PetscErrorCode WriteParticleFile ( UserCtx user,
PostProcessParams pps,
PetscInt  ti 
)

Writes particle data to a VTP file using the Prepare-Write-Cleanup pattern.

Definition at line 587 of file postprocessor.c.

588{
589 PetscErrorCode ierr;
590 VTKMetaData part_meta;
591 char filename[MAX_FILENAME_LENGTH];
592 PetscInt n_total_particles_before_subsample;
593
594 PetscFunctionBeginUser;
596
597 // These checks can be done on all ranks
598 if (!pps->outputParticles || pps->particle_fields[0] == '\0') {
599 PetscFunctionReturn(0);
600 }
601 PetscInt n_global;
602 ierr = DMSwarmGetSize(user->swarm, &n_global); CHKERRQ(ierr);
603 if (n_global == 0) {
604 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Swarm is empty for ti=%" PetscInt_FMT ". Skipping particle file write.\n", ti);
605 PetscFunctionReturn(0);
606 }
607
608 ierr = PetscMemzero(&part_meta, sizeof(VTKMetaData)); CHKERRQ(ierr);
609
610 // --- 1. PREPARE (Collective Call) ---
611 ierr = PrepareOutputParticleData(user, pps, &part_meta, &n_total_particles_before_subsample); CHKERRQ(ierr);
612
613 // --- 2. WRITE and CLEANUP (Rank 0 only) ---
614 if (user->simCtx->rank == 0) {
615 if (part_meta.npoints > 0) {
616 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Starting VTP Particle File Writing for ti = %" PetscInt_FMT " (writing %" PetscInt_FMT " of %" PetscInt_FMT " particles) ---\n",
617 ti, part_meta.npoints, n_total_particles_before_subsample);
618
619 /* Field summary */
620 LOG_ALLOW(GLOBAL, LOG_INFO, "Particle Data fields to write: %d\n", (int)part_meta.num_point_data_fields);
621 for (PetscInt ii=0; ii<part_meta.num_point_data_fields; ++ii) {
622 LOG_ALLOW(GLOBAL, LOG_INFO, " # %2" PetscInt_FMT " Field Name = %s Components = %d\n",
623 ii, part_meta.point_data_fields[ii].name, (int)part_meta.point_data_fields[ii].num_components);
624 }
625
626 snprintf(filename, sizeof(filename), "%s_%05" PetscInt_FMT ".vtp", pps->particle_output_prefix, ti);
627 ierr = CreateVTKFileFromMetadata(filename, &part_meta, PETSC_COMM_WORLD); CHKERRQ(ierr);
628
629 } else {
630 LOG_ALLOW(GLOBAL, LOG_DEBUG, "No particles to write at ti=%" PetscInt_FMT " after subsampling. Skipping.\n", ti);
631 }
632
633 }
634
635 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Particle File Writing for ti = %" PetscInt_FMT " Complete ---\n", ti);
637 PetscFunctionReturn(0);
638}
char particle_output_prefix[256]
Definition variables.h:469
char particle_fields[1024]
Definition variables.h:468
PetscBool outputParticles
Definition variables.h:460
PetscErrorCode PrepareOutputParticleData(UserCtx *user, PostProcessParams *pps, VTKMetaData *meta, PetscInt *p_n_total)
Gathers, subsamples, and prepares all particle data for VTK output.
Definition vtk_io.c:421
Here is the call graph for this function:
Here is the caller graph for this function: