PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
Functions
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 "particle_statistics.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.
 
PetscErrorCode GlobalStatisticsPipeline (UserCtx *user, PostProcessParams *pps, PetscInt ti)
 Executes the global statistics pipeline, computing aggregate reductions over all particles.
 
PetscErrorCode ResolvePostProcessingSteps (PostProcessParams *pps, PetscInt **steps, PetscInt *count)
 Resolve the ordered steps one post-processing run processes.
 
PetscErrorCode FieldStatisticsPipeline (UserCtx *user, PostProcessParams *pps, PetscInt ti)
 Derives and writes field statistics for the windows a recipe requests.
 

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

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

Local to this translation unit.

Definition at line 21 of file postprocessor.c.

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

Full API contract (arguments, ownership, side effects) is documented with the header declaration in include/postprocessor.h.

See also
WriteEulerianFile()

Definition at line 202 of file postprocessor.c.

203{
204 PetscErrorCode ierr;
205 VTKMetaData meta;
206 char filename[MAX_FILENAME_LENGTH];
207
208 PetscFunctionBeginUser;
210
211 if (pps->output_fields_instantaneous[0] == '\0') {
212 LOG_ALLOW(GLOBAL, LOG_DEBUG, "No instantaneous fields requested for output at ti=%" PetscInt_FMT ". Skipping.\n", ti);
214 PetscFunctionReturn(0);
215 }
216
217 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Starting VTK File Writing for ti = %" PetscInt_FMT " ---\n", ti);
218
219 /* 1) Metadata init */
220 /* 2) Metadata and coordinates, through the shared assembly the statistics
221 * stage also uses, so both producers build a file the same way. */
222 ierr = BeginStructuredVTKOutput(user, &meta); CHKERRQ(ierr);
223 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",
224 meta.mx, meta.my, meta.mz);
225
226 /* 3) Field preparation is collective because each append gathers a
227 * distributed Vec. Rank zero alone owns the packed output buffers, but
228 * every rank must resolve the same field list and enter each append. */
229 {
230 char *fields_copy, *field_name;
231 ierr = PetscStrallocpy(pps->output_fields_instantaneous, &fields_copy); CHKERRQ(ierr);
232
233 field_name = strtok(fields_copy, ",");
234 while (field_name) {
235 TrimWhitespace(field_name);
236 if (!*field_name) { field_name = strtok(NULL, ","); continue; }
237
238 LOG_ALLOW(LOCAL, LOG_DEBUG, "Preparing field '%s' for output.\n", field_name);
239
240 Vec field_vec = NULL;
241 PetscInt num_components = 0;
242
243 if (!strcasecmp(field_name, "P_nodal")) {
244 field_vec = user->P_nodal; num_components = 1;
245 } else if (!strcasecmp(field_name, "Ucat_nodal")) {
246 field_vec = user->Ucat_nodal; num_components = 3;
247 } else if (!strcasecmp(field_name, "Qcrit_nodal")) {
248 field_vec = user->Qcrit_nodal; num_components = 1;
249 } else if (!strcasecmp(field_name, "Qcrit")) {
250 /* Qcrit is cell-centred; written as point data it would sit half a cell
251 from the node the file assigns it. The conductor refuses this too. */
252 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG,
253 "Field 'Qcrit' is cell-centred and cannot be written as point data. "
254 "Add a nodal_average task (input_field: Qcrit, output_field: Qcrit_nodal) and write 'Qcrit_nodal'.");
255 } else if (!strcasecmp(field_name, "Psi_nodal")){
256 if(user->simCtx->np==0){
257 LOG_ALLOW(LOCAL, LOG_WARNING, "Field 'Psi_nodal' requested but no particles are present. Skipping.\n");
258 field_name = strtok(NULL, ",");
259 continue;
260 }
261 field_vec = user->Psi_nodal; num_components = 1;
262 } else {
263 LOG_ALLOW(LOCAL, LOG_WARNING, "Field '%s' not recognized. Skipping.\n", field_name);
264 field_name = strtok(NULL, ",");
265 continue;
266 }
267
268 // --- Add field to metadata ---
269 ierr = AppendStructuredVTKField(user, &meta, field_name, field_vec, num_components); CHKERRQ(ierr);
270
271 /*
272 // *** DEBUG: Dump Ucat_nodal details and add scalar companions Ux, Uy, Uz for easier visualization ***
273
274 // If this is Ucat_nodal, dump a few tuples and add scalar companions
275 if (!strcasecmp(field_name, "Ucat_nodal")) {
276 const PetscInt npts = meta.npoints;
277 const PetscScalar *a = (const PetscScalar*)current_field->data;
278
279 LOG_ALLOW(GLOBAL, LOG_INFO, "DBG Ucat_nodal: ptr=%p npoints=%" PetscInt_FMT " num_components=%" PetscInt_FMT "\n",
280 (void*)a, npts, current_field->num_components);
281
282 if (a && current_field->num_components == 3 && npts > 0) {
283 const PetscInt nshow = (npts < 5) ? npts : 5;
284 LOG_ALLOW(GLOBAL, LOG_INFO, "DBG Ucat_nodal: showing first %d of %" PetscInt_FMT " tuples (AoS x,y,z):\n",
285 (int)nshow, npts);
286 for (PetscInt t = 0; t < nshow; ++t) {
287 LOG_ALLOW(GLOBAL, LOG_INFO, " Ucat_nodal[%3" PetscInt_FMT "] = (%g, %g, %g)\n",
288 t, (double)a[3*t+0], (double)a[3*t+1], (double)a[3*t+2]);
289 }
290 if (npts > 10) {
291 PetscInt mid = npts / 2;
292 LOG_ALLOW(GLOBAL, LOG_INFO, " Ucat_nodal[mid=%" PetscInt_FMT "] = (%g, %g, %g)\n",
293 mid, (double)a[3*mid+0], (double)a[3*mid+1], (double)a[3*mid+2]);
294 }
295
296 // Add scalar companions from the AoS we just created
297 PetscScalar *Ux=NULL,*Uy=NULL,*Uz=NULL;
298 ierr = PetscMalloc1(meta.npoints, &Ux); CHKERRQ(ierr);
299 ierr = PetscMalloc1(meta.npoints, &Uy); CHKERRQ(ierr);
300 ierr = PetscMalloc1(meta.npoints, &Uz); CHKERRQ(ierr);
301 for (PetscInt i = 0; i < meta.npoints; ++i) {
302 Ux[i] = a[3*i+0]; Uy[i] = a[3*i+1]; Uz[i] = a[3*i+2];
303 }
304
305 if (meta.num_point_data_fields + 3 <= MAX_POINT_DATA_FIELDS) {
306 VTKFieldInfo *fx = &meta.point_data_fields[++meta.num_point_data_fields];
307 strncpy(fx->name, "Ux_debug", MAX_VTK_FIELD_NAME_LENGTH-1);
308 fx->name[MAX_VTK_FIELD_NAME_LENGTH-1] = '\0';
309 fx->num_components = 1; fx->data = Ux;
310
311 VTKFieldInfo *fy = &meta.point_data_fields[++meta.num_point_data_fields];
312 strncpy(fy->name, "Uy_debug", MAX_VTK_FIELD_NAME_LENGTH-1);
313 fy->name[MAX_VTK_FIELD_NAME_LENGTH-1] = '\0';
314 fy->num_components = 1; fy->data = Uy;
315
316 VTKFieldInfo *fz = &meta.point_data_fields[++meta.num_point_data_fields];
317 strncpy(fz->name, "Uz_debug", MAX_VTK_FIELD_NAME_LENGTH-1);
318 fz->name[MAX_VTK_FIELD_NAME_LENGTH-1] = '\0';
319 fz->num_components = 1; fz->data = Uz;
320
321 LOG_ALLOW(GLOBAL, LOG_INFO, "DBG: Added scalar companions Ux_debug, Uy_debug, Uz_debug.\n");
322 } else {
323 LOG_ALLOW(GLOBAL, LOG_WARNING, "DBG: Not enough slots to add Ux/Uy/Uz debug fields.\n");
324 PetscFree(Ux); PetscFree(Uy); PetscFree(Uz);
325 }
326
327 // Mid-plane CSV + AoS vs NATURAL compare (component X)
328
329 // Gather NATURAL again (small cost, but isolated and clear)
330 PetscInt Ng = 0;
331 double *nat_d = NULL;
332 DM dmU = NULL;
333 DMDALocalInfo infU;
334 ierr = VecGetDM(field_vec, &dmU); CHKERRQ(ierr);
335 ierr = DMDAGetLocalInfo(dmU, &infU); CHKERRQ(ierr);
336
337 const PetscInt M=infU.mx, N=infU.my, P=infU.mz;
338 const PetscInt mx = meta.mx, my = meta.my, mz = meta.mz;
339 const PetscInt iInnerMid = mx/2; // interior index [0..mx-1]
340 const PetscInt iGlob = iInnerMid;
341
342 ierr = VecToArrayOnRank0(field_vec, &Ng, &nat_d); CHKERRQ(ierr);
343
344 if (nat_d) {
345 const PetscScalar *nar = (const PetscScalar*)nat_d;
346 const char *base = pps->output_prefix;
347 char fn[512], fnc[512];
348 snprintf(fn, sizeof(fn), "%s_%05" PetscInt_FMT "_iMid.csv", base, ti);
349 snprintf(fnc, sizeof(fnc), "%s_%05" PetscInt_FMT "_iMid_compare.csv", base, ti);
350
351 FILE *fp = fopen(fn, "w");
352 FILE *fpc = fopen(fnc, "w");
353 if (fp) fprintf(fp, "jInner,kInner,Ux,Uy,Uz\n");
354 if (fpc) fprintf(fpc, "jInner,kInner,Ux_AoS,Ux_NAT,abs_diff\n");
355
356 double maxAbsDiff = 0.0, sumAbs = 0.0;
357 PetscInt count = 0;
358
359 for (PetscInt kInner = 0; kInner < mz; ++kInner) {
360 const PetscInt k = kInner;
361 for (PetscInt jInner = 0; jInner < my; ++jInner) {
362 const PetscInt j = jInner;
363
364 // AoS tuple index
365 const PetscInt t = iInnerMid + mx * (jInner + my * kInner);
366 const PetscScalar ux = a[3*t+0], uy = a[3*t+1], uz = a[3*t+2];
367
368 if (fp) fprintf(fp, "%d,%d,%.15e,%.15e,%.15e\n",
369 (int)jInner,(int)kInner,(double)ux,(double)uy,(double)uz);
370
371 // NATURAL base for (iGlob,j,k)
372 const PetscInt baseNat = 3 * (((k)*N + j)*M + iGlob);
373 const PetscScalar uxN = nar[baseNat + 0];
374
375 const double diff = fabs((double)ux - (double)uxN);
376 if (diff > maxAbsDiff) maxAbsDiff = diff;
377 sumAbs += diff; ++count;
378
379 if (fpc) fprintf(fpc, "%d,%d,%.15e,%.15e,%.15e\n",
380 (int)jInner,(int)kInner,(double)ux,(double)uxN,diff);
381 } // for jInner
382 } // for kInner
383 if (fp) fclose(fp);
384 if (fpc) fclose(fpc);
385
386 if (count > 0) {
387 const double meanAbs = sumAbs / (double)count;
388 LOG_ALLOW(GLOBAL, LOG_INFO,
389 "PETSc-Vec vs AoS (i-mid, Ux): max|Δ|=%.6e, mean|Δ|=%.6e -> CSV: %s\n",
390 maxAbsDiff, meanAbs, fnc);
391 LOG_ALLOW(GLOBAL, LOG_INFO, "Wrote i-mid plane CSV: %s\n", fn);
392 } // if count>0
393
394 ierr = PetscFree(nat_d); CHKERRQ(ierr);
395
396
397 } // if nat_d
398
399 } // if a && num_components==3 && npts>0
400 } // if Ucat_nodal
401 // --- END DEBUG BLOCK (Ucat_nodal) ---
402 */
403
404 field_name = strtok(NULL, ",");
405 }
406
407 ierr = PetscFree(fields_copy); CHKERRQ(ierr);
408
409 // --- DEBUG: Add sanity fields i_idx, j_idx, k_idx and x_pos, y_pos, z_pos ---
410 // These are the logical indices and physical coordinates of each point in the subsampled grid.
411 // They can be used to verify the grid structure and orientation in visualization tools.
412 // They are added as scalar fields with names "i_idx", "j_idx", "k_idx" and "x_pos", "y_pos", "z_pos".
413 // Note: these are only added if there is room in the MAX_POINT_DATA_FIELDS limit.
414 // They are allocated and owned here, and will be freed below.
415 // They are in the same linearization order as meta.coords (AoS x,y,z by point).
416 /*
417 // Append sanity fields i/j/k indices and coordinates
418
419 // Build i/j/k and x/y/z (length = npoints); these match the same linearization as coords
420 const PetscInt n = meta.npoints;
421 PetscScalar *i_idx=NULL,*j_idx=NULL,*k_idx=NULL,*x_pos=NULL,*y_pos=NULL,*z_pos=NULL;
422
423 ierr = PetscMalloc1(n, &i_idx); CHKERRQ(ierr);
424 ierr = PetscMalloc1(n, &j_idx); CHKERRQ(ierr);
425 ierr = PetscMalloc1(n, &k_idx); CHKERRQ(ierr);
426 ierr = PetscMalloc1(n, &x_pos); CHKERRQ(ierr);
427 ierr = PetscMalloc1(n, &y_pos); CHKERRQ(ierr);
428 ierr = PetscMalloc1(n, &z_pos); CHKERRQ(ierr);
429
430 // coords is length 3*n, AoS: (x,y,z) by point
431 const PetscScalar *c = (const PetscScalar*)meta.coords;
432 for (PetscInt k = 0; k < meta.mz; ++k) {
433 for (PetscInt j = 0; j < meta.my; ++j) {
434 for (PetscInt i = 0; i < meta.mx; ++i) {
435 const PetscInt t = i + meta.mx * (j + meta.my * k);
436 i_idx[t] = (PetscScalar)i;
437 j_idx[t] = (PetscScalar)j;
438 k_idx[t] = (PetscScalar)k;
439 x_pos[t] = c[3*t+0];
440 y_pos[t] = c[3*t+1];
441 z_pos[t] = c[3*t+2];
442 }
443 }
444 }
445
446 const char *nf[6] = {"i_idx","j_idx","k_idx","x_pos","y_pos","z_pos"};
447 PetscScalar *arrs[6] = {i_idx,j_idx,k_idx,x_pos,y_pos,z_pos};
448 for (int s=0; s<6; ++s) {
449 if (meta.num_point_data_fields < MAX_POINT_DATA_FIELDS) {
450 VTKFieldInfo *f = &meta.point_data_fields[meta.num_point_data_fields++];
451 strncpy(f->name, nf[s], MAX_VTK_FIELD_NAME_LENGTH-1);
452 f->name[MAX_VTK_FIELD_NAME_LENGTH-1] = '\0';
453 f->num_components = 1;
454 f->data = arrs[s];
455 } else {
456 LOG_ALLOW(GLOBAL, LOG_WARNING, "Sanity field '%s' dropped: MAX_POINT_DATA_FIELDS reached.\n", nf[s]);
457 PetscFree(arrs[s]);
458 }
459 }
460 LOG_ALLOW(GLOBAL, LOG_INFO, "DBG: Added sanity fields i_idx/j_idx/k_idx and x_pos/y_pos/z_pos.\n");
461 */
462 // --- END DEBUG BLOCK (sanity fields) ---
463
464 if (user->simCtx->rank == 0) {
465 /* Field summary */
466 LOG_ALLOW(GLOBAL, LOG_INFO, "PointData fields to write: %d\n", (int)meta.num_point_data_fields);
467 for (PetscInt ii=0; ii<meta.num_point_data_fields; ++ii) {
468 LOG_ALLOW(GLOBAL, LOG_INFO, " # %2" PetscInt_FMT " Field Name = %s Components = %d\n",
469 ii, meta.point_data_fields[ii].name, (int)meta.point_data_fields[ii].num_components);
470 }
471 }
472 }
473
474 /* 4) Write the VTS */
475 ierr = PetscSNPrintf(filename, sizeof(filename), "%s_%05" PetscInt_FMT ".vts", pps->output_prefix, ti); CHKERRQ(ierr);
476 ierr = FinishStructuredVTKOutput(&meta, filename); CHKERRQ(ierr);
477
478 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Eulerian File Writing for ti = %" PetscInt_FMT " Complete ---\n", ti);
480 PetscFunctionReturn(0);
481}
Vec Qcrit_nodal
Q-criterion averaged to grid nodes; the field a .vts can place correctly.
Definition variables.h:1179
Vec P_nodal
Definition variables.h:1176
PetscInt num_components
Definition variables.h:807
PetscMPIInt rank
Definition variables.h:862
PetscInt num_point_data_fields
Definition variables.h:823
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:1077
char output_prefix[256]
Definition variables.h:769
Vec Ucat_nodal
Definition variables.h:1177
PetscInt np
Definition variables.h:990
#define MAX_FILENAME_LENGTH
Definition variables.h:746
char output_fields_instantaneous[1024]
Definition variables.h:768
char name[64]
Definition variables.h:806
VTKFieldInfo point_data_fields[20]
Definition variables.h:822
Vec Psi_nodal
Definition variables.h:1180
PetscInt mz
Definition variables.h:819
PetscInt my
Definition variables.h:819
PetscInt mx
Definition variables.h:819
PetscErrorCode FinishStructuredVTKOutput(VTKMetaData *meta, const char *filename)
Writes an assembled structured VTK file and releases its buffers.
Definition vtk_io.c:354
PetscErrorCode BeginStructuredVTKOutput(UserCtx *user, VTKMetaData *meta)
Begins one structured VTK file: clears the metadata and builds its coordinates.
Definition vtk_io.c:308
PetscErrorCode AppendStructuredVTKField(UserCtx *user, VTKMetaData *meta, const char *name, Vec field_vec, PetscInt components)
Adds one point-data field to a structured VTK file being assembled.
Definition vtk_io.c:326
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.
ppsThe PostProcessParams containing the pipeline string.
Returns
PetscErrorCode

Parses the processing pipeline string and executes the requested kernels.

Full API contract (arguments, ownership, side effects) is documented with the header declaration in include/postprocessor.h.

See also
EulerianDataProcessingPipeline()

Definition at line 107 of file postprocessor.c.

108{
109 PetscErrorCode ierr;
110 char *pipeline_copy, *step_token, *step_saveptr;
111
112 PetscFunctionBeginUser;
114 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Starting Data Transformation Pipeline ---\n");
115
116 // Do nothing if the pipeline string is empty
117 if (pps->process_pipeline[0] == '\0') {
118 LOG_ALLOW(GLOBAL, LOG_INFO, "Processing pipeline is empty. No transformations will be run.\n");
120 PetscFunctionReturn(0);
121 }
122
123 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Pipeline string: [%s]\n", pps->process_pipeline);
124
125 // Make a writable copy for strtok_r, as it modifies the string
126 ierr = PetscStrallocpy(pps->process_pipeline, &pipeline_copy); CHKERRQ(ierr);
127
128 // --- Outer Loop: Tokenize by Semicolon (;) to get each processing step ---
129 step_token = strtok_r(pipeline_copy, ";", &step_saveptr);
130 while (step_token) {
131 TrimWhitespace(step_token);
132 if (strlen(step_token) == 0) {
133 step_token = strtok_r(NULL, ";", &step_saveptr);
134 continue;
135 }
136
137 char *keyword = strtok(step_token, ":");
138 char *args_str = strtok(NULL, ""); // Get the rest of the string as arguments
139
140 if (!keyword) { // Should not happen with TrimWhitespace, but is a safe check
141 step_token = strtok_r(NULL, ";", &step_saveptr);
142 continue;
143 }
144
145 TrimWhitespace(keyword);
146 if (args_str) TrimWhitespace(args_str);
147
148 LOG_ALLOW(GLOBAL, LOG_INFO, "Executing Transformation: '%s' on args: '%s'\n", keyword, args_str ? args_str : "None");
149
150 // --- DISPATCHER: Route to the correct kernel based on the keyword ---
151 if (strcasecmp(keyword, "CellToNodeAverage") == 0) {
152 if (!args_str) SETERRQ(PETSC_COMM_SELF, 1, "CellToNodeAverage requires arguments in 'in_field>out_field' format.");
153 char *in_field = strtok(args_str, ">");
154 char *out_field = strtok(NULL, ">");
155 if (!in_field || !out_field) SETERRQ(PETSC_COMM_SELF, 1, "CellToNodeAverage requires 'in>out' syntax (e.g., P>P_nodal).");
156 if(strcmp(in_field,out_field)==0) SETERRQ(PETSC_COMM_SELF, 1, "CellToNodeAverage input and output fields must be different.");
157 if(user->simCtx->np == 0 && (strcmp(out_field,"Psi_nodal")==0 || strcmp(in_field,"Psi_nodal")==0)){
158 LOG(GLOBAL,LOG_WARNING,"CellToNodeAverage cannot process 'Psi_nodal' when no particles are present in the simulation.\n");
159 step_token = strtok_r(NULL, ";", &step_saveptr);
160 continue;
161 }
162 TrimWhitespace(in_field); TrimWhitespace(out_field);
163 ierr = ComputeNodalAverage(user, in_field, out_field); CHKERRQ(ierr);
164 }
165 else if (strcasecmp(keyword, "ComputeQCriterion") == 0) {
166 ierr = ComputeQCriterion(user); CHKERRQ(ierr);
167 }
168 else if (strcasecmp(keyword, "DimensionalizeAllLoadedFields") == 0) {
169 /* Retired stage. Loaded fields are now scaled as they are read, which is the
170 * only point that knows which fields a step actually loaded; scaling them here
171 * too would scale them twice. Recipes locked before the change still carry
172 * the token, so it is accepted and does nothing. */
173 }
174 else if (strcasecmp(keyword, "NormalizeRelativeField") == 0) {
175 if (!args_str) SETERRQ(PETSC_COMM_SELF, 1, "NormalizePressure requires the pressure field name (e.g., 'P') as an argument.");
176 ierr = NormalizeRelativeField(user, args_str); CHKERRQ(ierr);
177 }
178 // *** Add new kernels here in the future using 'else if' ***
179 // else if (strcasecmp(keyword, "ComputeVorticity") == 0) { ... }
180 else {
181 LOG_ALLOW(GLOBAL, LOG_WARNING, "Unknown transformation keyword '%s'. Skipping.\n", keyword);
182 }
183
184 step_token = strtok_r(NULL, ";", &step_saveptr);
185 }
186
187 ierr = PetscFree(pipeline_copy); CHKERRQ(ierr);
188 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Data Transformation Pipeline Complete ---\n");
190 PetscFunctionReturn(0);
191}
#define LOG(scope, level, fmt,...)
Logging macro for PETSc-based applications with scope control.
Definition logging.h:84
PetscErrorCode ComputeQCriterion(UserCtx *user)
Computes the Q-criterion diagnostic from the local velocity-gradient tensor.
PetscErrorCode NormalizeRelativeField(UserCtx *user, const char *relative_field_name)
Normalizes pressure using the value at the configured logical grid point.
PetscErrorCode ComputeNodalAverage(UserCtx *user, const char *in_field_name, const char *out_field_name)
Interpolates a cell-centered field to nodal locations using local stencil averaging.
char process_pipeline[1024]
Definition variables.h:767
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

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

Full API contract (arguments, ownership, side effects) is documented with the header declaration in include/postprocessor.h.

See also
ParticleDataProcessingPipeline()

Definition at line 634 of file postprocessor.c.

635{
636 PetscErrorCode ierr;
637 char *pipeline_copy, *step_token, *step_saveptr;
638
639 PetscFunctionBeginUser;
640
642
643 if (pps->particle_pipeline[0] == '\0') {
645 PetscFunctionReturn(0);
646 }
647
648 // --- Timestep Setup: Synchronize post_swarm size ---
649 PetscInt n_local_source;
650 ierr = DMSwarmGetLocalSize(user->swarm, &n_local_source); CHKERRQ(ierr);
651
652 // Derived entries use the same local index as their source particle.
653 ierr = DMSwarmSetLocalSizes(user->post_swarm, n_local_source, -1); CHKERRQ(ierr);
654
655 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Starting Particle Data Transformation Pipeline ---\n");
656 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Particle Pipeline string: [%s]\n", pps->particle_pipeline);
657
658 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Executing compute kernels...\n");
659 ierr = PetscStrallocpy(pps->particle_pipeline, &pipeline_copy); CHKERRQ(ierr);
660 step_token = strtok_r(pipeline_copy, ";", &step_saveptr);
661 while (step_token) {
662 TrimWhitespace(step_token);
663 if (strlen(step_token) == 0) { step_token = strtok_r(NULL, ";", &step_saveptr); continue; }
664
665 char *keyword = strtok(step_token, ":");
666 char *args_str = strtok(NULL, "");
667 TrimWhitespace(keyword);
668 if (args_str) TrimWhitespace(args_str);
669
670 LOG_ALLOW(GLOBAL, LOG_INFO, "Executing Particle Transformation: '%s' on args: '%s'\n", keyword, args_str ? args_str : "None");
671
672 if (strcasecmp(keyword, "ComputeSpecificKE") == 0) {
673 if (!args_str) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "ComputeSpecificKE requires 'input_field>output_field' arguments.");
674 char *velocity_field = strtok(args_str, ">");
675 char *ske_field = strtok(NULL, ">");
676 if (!velocity_field || !ske_field) {
677 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "ComputeSpecificKE requires 'input_field>output_field' arguments.");
678 }
679 TrimWhitespace(velocity_field); TrimWhitespace(ske_field);
680 if (strlen(velocity_field) == 0 || strlen(ske_field) == 0) {
681 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "ComputeSpecificKE does not allow empty input/output field names.");
682 }
683
684 ierr = ComputeSpecificKE(user, velocity_field, ske_field); CHKERRQ(ierr);
685 }
686 else {
687 LOG_ALLOW(GLOBAL, LOG_WARNING, "Unknown particle transformation keyword '%s'. Skipping.\n", keyword);
688 }
689
690 step_token = strtok_r(NULL, ";", &step_saveptr);
691 }
692 ierr = PetscFree(pipeline_copy); CHKERRQ(ierr);
693
694 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Particle Data Transformation Pipeline Complete ---\n");
695
697 PetscFunctionReturn(0);
698}
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.

Parameters
userPrimary UserCtx input for the operation.
ppsPost-processing configuration for the operation.
tiSource timestep written into the VTP filename and metadata.
Returns
PetscErrorCode 0 on success.

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

Full API contract (arguments, ownership, side effects) is documented with the header declaration in include/postprocessor.h.

See also
WriteParticleFile()

Definition at line 757 of file postprocessor.c.

758{
759 PetscErrorCode ierr;
760 VTKMetaData part_meta;
761 char filename[MAX_FILENAME_LENGTH];
762 PetscInt n_total_particles_before_subsample;
763
764 PetscFunctionBeginUser;
766
767 // These checks can be done on all ranks
768 if (!pps->outputParticles || pps->particle_fields[0] == '\0') {
770 PetscFunctionReturn(0);
771 }
772 PetscInt n_global;
773 ierr = DMSwarmGetSize(user->swarm, &n_global); CHKERRQ(ierr);
774 if (n_global == 0) {
775 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Swarm is empty for ti=%" PetscInt_FMT ". Skipping particle file write.\n", ti);
777 PetscFunctionReturn(0);
778 }
779
780 ierr = PetscMemzero(&part_meta, sizeof(VTKMetaData)); CHKERRQ(ierr);
781
782 // --- 1. PREPARE (Collective Call) ---
783 ierr = PrepareOutputParticleData(user, pps, &part_meta, &n_total_particles_before_subsample); CHKERRQ(ierr);
784
785 // --- 2. WRITE and CLEANUP (Rank 0 only) ---
786 if (user->simCtx->rank == 0) {
787 if (part_meta.npoints > 0) {
788 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Starting VTP Particle File Writing for ti = %" PetscInt_FMT " (writing %" PetscInt_FMT " of %" PetscInt_FMT " particles) ---\n",
789 ti, part_meta.npoints, n_total_particles_before_subsample);
790
791 /* Field summary */
792 LOG_ALLOW(GLOBAL, LOG_INFO, "Particle Data fields to write: %d\n", (int)part_meta.num_point_data_fields);
793 for (PetscInt ii=0; ii<part_meta.num_point_data_fields; ++ii) {
794 LOG_ALLOW(GLOBAL, LOG_INFO, " # %2" PetscInt_FMT " Field Name = %s Components = %d\n",
795 ii, part_meta.point_data_fields[ii].name, (int)part_meta.point_data_fields[ii].num_components);
796 }
797
798 ierr = PetscSNPrintf(filename, sizeof(filename), "%s_%05" PetscInt_FMT ".vtp", pps->particle_output_prefix, ti); CHKERRQ(ierr);
799 ierr = CreateVTKFileFromMetadata(filename, &part_meta, PETSC_COMM_WORLD); CHKERRQ(ierr);
800
801 } else {
802 LOG_ALLOW(GLOBAL, LOG_DEBUG, "No particles to write at ti=%" PetscInt_FMT " after subsampling. Skipping.\n", ti);
803 }
804
805 for (PetscInt field = 0; field < part_meta.num_point_data_fields; ++field) {
806 ierr = PetscFree(part_meta.point_data_fields[field].data); CHKERRQ(ierr);
807 }
808 ierr = PetscFree(part_meta.coords); CHKERRQ(ierr);
809 ierr = PetscFree(part_meta.connectivity); CHKERRQ(ierr);
810 ierr = PetscFree(part_meta.offsets); CHKERRQ(ierr);
811 }
812
813 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Particle File Writing for ti = %" PetscInt_FMT " Complete ---\n", ti);
815 PetscFunctionReturn(0);
816}
PetscInt CreateVTKFileFromMetadata(const char *filename, const VTKMetaData *meta, MPI_Comm comm)
Creates a VTK file from prepared metadata and field payloads.
Definition vtk_io.c:149
PetscInt npoints
Definition variables.h:820
char particle_output_prefix[256]
Definition variables.h:772
PetscInt * connectivity
Definition variables.h:824
PetscInt * offsets
Definition variables.h:825
PetscScalar * data
Definition variables.h:808
PetscScalar * coords
Definition variables.h:821
char particle_fields[1024]
Definition variables.h:771
PetscBool outputParticles
Definition variables.h:764
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:486
Here is the call graph for this function:
Here is the caller graph for this function:

◆ GlobalStatisticsPipeline()

PetscErrorCode GlobalStatisticsPipeline ( UserCtx *  user,
PostProcessParams *  pps,
PetscInt  ti 
)

Executes the global statistics pipeline, computing aggregate reductions over all particles.

Parses the semicolon-delimited pps->statistics_pipeline string and dispatches to the appropriate kernel (e.g. ComputeParticleMSD). Each kernel appends one row to its own CSV file and logs a summary via LOG_INFO. All MPI reductions happen inside each kernel. This pipeline is independent of the per-particle VTK pipeline; it produces no .vtp output.

Parameters
userThe UserCtx containing the primary DMSwarm (user->swarm).
ppsThe PostProcessParams containing statistics_pipeline and statistics_output_prefix.
tiCurrent time-step index (passed through to kernels for time computation).
Returns
PetscErrorCode

Executes the global statistics pipeline, computing aggregate reductions over all particles.

Local to this translation unit.

Definition at line 706 of file postprocessor.c.

707{
708 PetscErrorCode ierr;
709 char *pipeline_copy, *step_token, *step_saveptr;
710
711 PetscFunctionBeginUser;
713
714 if (pps->statistics_pipeline[0] == '\0') { PROFILE_FUNCTION_END; PetscFunctionReturn(0); }
715
716 PetscInt n_global;
717 ierr = DMSwarmGetSize(user->swarm, &n_global); CHKERRQ(ierr);
718 if (n_global == 0) { PROFILE_FUNCTION_END; PetscFunctionReturn(0); }
719
720 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Starting Global Statistics Pipeline ---\n");
721
722 ierr = PetscStrallocpy(pps->statistics_pipeline, &pipeline_copy); CHKERRQ(ierr);
723 step_token = strtok_r(pipeline_copy, ";", &step_saveptr);
724 while (step_token) {
725 TrimWhitespace(step_token);
726 if (strlen(step_token) == 0) {
727 step_token = strtok_r(NULL, ";", &step_saveptr); continue;
728 }
729 char *keyword = strtok(step_token, ":");
730 TrimWhitespace(keyword);
731
732 if (strcasecmp(keyword, "ComputeMSD") == 0) {
733 ierr = ComputeParticleMSD(user, pps->statistics_output_prefix, ti); CHKERRQ(ierr);
734 } else {
736 "Unknown statistics keyword '%s'. Skipping.\n", keyword);
737 }
738 /* Additional kernels should add else-if branches here when implemented. */
739
740 step_token = strtok_r(NULL, ";", &step_saveptr);
741 }
742 ierr = PetscFree(pipeline_copy); CHKERRQ(ierr);
743
744 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Global Statistics Pipeline Complete ---\n");
746 PetscFunctionReturn(0);
747}
PetscErrorCode ComputeParticleMSD(UserCtx *user, const char *stats_prefix, PetscInt ti)
Computes the mean-squared displacement (MSD) of a particle cloud.
char statistics_output_prefix[256]
basename for CSV output, e.g.
Definition variables.h:777
char statistics_pipeline[1024]
e.g.
Definition variables.h:776
Here is the call graph for this function:
Here is the caller graph for this function:

◆ ResolvePostProcessingSteps()

PetscErrorCode ResolvePostProcessingSteps ( PostProcessParams *  pps,
PetscInt **  steps,
PetscInt *  count 
)

Resolve the ordered steps one post-processing run processes.

A step list file, written by the conductor when a job starts, names exactly the steps whose output is missing or stale; without one the time controls define an evenly spaced window. Rank 0 reads the file and broadcasts it, so every rank iterates the same steps.

Parameters
[in]ppsPost-processing settings holding the time controls and list file.
[out]stepsNewly allocated array of steps; the caller frees it with PetscFree().
[out]countNumber of entries in steps.
Returns
PetscErrorCode 0 on success; an error when the list file cannot be read.

Resolve the ordered steps one post-processing run processes.

Local to this translation unit.

Definition at line 824 of file postprocessor.c.

825{
826 PetscMPIInt rank;
827
828 PetscFunctionBeginUser;
829 *steps = NULL;
830 *count = 0;
831 if (pps->step_list_file[0] == '\0') {
832 PetscInt n = (pps->endTime >= pps->startTime) ? (pps->endTime - pps->startTime) / pps->timeStep + 1 : 0;
833 PetscCall(PetscMalloc1(n > 0 ? n : 1, steps));
834 for (PetscInt k = 0; k < n; ++k) (*steps)[k] = pps->startTime + k * pps->timeStep;
835 *count = n;
836 PetscFunctionReturn(0);
837 }
838
839 PetscCallMPI(MPI_Comm_rank(PETSC_COMM_WORLD, &rank));
840 /* Rank 0 reads; a failure is broadcast as a negative count so every rank stops
841 together instead of the others waiting in the broadcast. */
842 PetscInt n = 0, capacity = 0;
843 PetscInt *list = NULL;
844 char bad_line[256] = "";
845 if (rank == 0) {
846 char line[256];
847 FILE *file = fopen(pps->step_list_file, "r");
848 if (!file) n = -1;
849 while (file && n >= 0 && fgets(line, sizeof(line), file)) {
850 char *end = NULL;
851 long value;
852 TrimWhitespace(line);
853 if (line[0] == '\0' || line[0] == '#') continue;
854 value = strtol(line, &end, 10);
855 if (end == line || *end != '\0') {
856 PetscCall(PetscStrncpy(bad_line, line, sizeof(bad_line)));
857 n = -2;
858 break;
859 }
860 if (n == capacity) {
861 PetscInt *grown = NULL;
862 capacity = capacity ? 2 * capacity : 64;
863 PetscCall(PetscMalloc1(capacity, &grown));
864 if (n) PetscCall(PetscArraycpy(grown, list, n));
865 PetscCall(PetscFree(list));
866 list = grown;
867 }
868 list[n++] = (PetscInt)value;
869 }
870 if (file) fclose(file);
871 }
872 PetscCallMPI(MPI_Bcast(&n, 1, MPIU_INT, 0, PETSC_COMM_WORLD));
873 if (n < 0) {
874 PetscCall(PetscFree(list));
875 PetscCheck(n != -1, PETSC_COMM_WORLD, PETSC_ERR_FILE_OPEN,
876 "Step list file '%s' is missing. It is written when picurv launches the post "
877 "stage; launch post-processing through picurv.", pps->step_list_file);
878 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_FILE_UNEXPECTED,
879 "Step list file '%s' has a non-integer line '%s'.", pps->step_list_file, bad_line);
880 }
881 if (rank != 0) PetscCall(PetscMalloc1(n > 0 ? n : 1, &list));
882 if (n > 0) PetscCallMPI(MPI_Bcast(list, (PetscMPIInt)n, MPIU_INT, 0, PETSC_COMM_WORLD));
883 *steps = list;
884 *count = n;
885 PetscFunctionReturn(0);
886}
char step_list_file[PETSC_MAX_PATH_LEN]
File listing the exact steps to process, one per line; empty uses the time controls.
Definition variables.h:763
PetscInt timeStep
Definition variables.h:761
PetscInt startTime
Definition variables.h:759
Head of a generic C-style linked list.
Definition variables.h:469
Here is the call graph for this function:
Here is the caller graph for this function:

◆ FieldStatisticsPipeline()

PetscErrorCode FieldStatisticsPipeline ( UserCtx *  user,
PostProcessParams *  pps,
PetscInt  ti 
)

Derives and writes field statistics for the windows a recipe requests.

Runs once per processed step, beside the particle statistics pipeline, because a window's accumulated state is a property of the bundle at that step. A recipe spanning several steps therefore produces a convergence history rather than the same picture repeated; pinning field_statistics_source_step collapses it back to a single bundle.

Each window is written to its own file. The VTK point-data cap is per file, and one window carrying every output already fills most of it.

Does nothing when no window is requested. A window the run does not configure is fatal; a window that has not yet accumulated a sample is skipped with a note, since a recipe covering a whole run legitimately reaches bundles from before it began.

Parameters
[in]userFinest-level block context holding accumulators and staging fields.
[in]ppsPost-processing recipe naming the windows, outputs, and formats.
[in]tiStep being processed; names the output and selects the bundle.
Returns
Zero on success, or a PETSc error.

Derives and writes field statistics for the windows a recipe requests.

Full API contract (arguments, ownership, side effects) is documented with the header declaration in include/postprocessor.h.

See also
FieldStatisticsPipeline()

Definition at line 492 of file postprocessor.c.

493{
494 PetscErrorCode ierr;
495 SimCtx *simCtx = NULL;
496 char *windows_copy = NULL;
497 char *window_name = NULL;
498 PetscInt source_step = 0;
499 PetscBool want_vtk = PETSC_FALSE, want_csv = PETSC_FALSE;
500
501 PetscFunctionBeginUser;
503 if (pps->field_statistics_windows[0] == '\0') { PROFILE_FUNCTION_END; PetscFunctionReturn(0); }
504 simCtx = user->simCtx;
505
506 PetscCheck(FieldStatisticsIsActive(simCtx), PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONGSTATE,
507 "Field-statistics post-processing was requested, but the run's control configures "
508 "no statistics window.");
509
510 {
511 char formats[MAX_FIELD_LIST_LENGTH];
512 char *token = NULL;
513
514 ierr = PetscStrncpy(formats, pps->field_statistics_formats, sizeof(formats)); CHKERRQ(ierr);
515 token = strtok(formats, ",");
516 while (token) {
517 TrimWhitespace(token);
518 if (!strcasecmp(token, "vtk")) want_vtk = PETSC_TRUE;
519 else if (!strcasecmp(token, "csv")) want_csv = PETSC_TRUE;
521 "Unknown field-statistics format '%s'. Known formats are vtk and csv.\n", token);
522 token = strtok(NULL, ",");
523 }
524 }
525 if (!want_vtk && !want_csv) { PROFILE_FUNCTION_END; PetscFunctionReturn(0); }
526
527 /* An explicit source step pins every processed step to one bundle; otherwise each
528 * step derives from its own, which is what turns a multi-step recipe into a
529 * convergence history rather than the same picture repeated. */
530 source_step = (pps->field_statistics_source_step >= 0) ? pps->field_statistics_source_step : ti;
531
532 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Starting Field Statistics Pipeline (step %" PetscInt_FMT ") ---\n",
533 source_step);
534 simCtx->fieldStatisticsContinue = PETSC_TRUE;
535 ierr = RestoreFieldStatisticsState(simCtx, source_step); CHKERRQ(ierr);
536
537 ierr = PetscStrallocpy(pps->field_statistics_windows, &windows_copy); CHKERRQ(ierr);
538 window_name = strtok(windows_copy, ",");
539 while (window_name) {
540 PetscInt window_index = -1;
541 const PicurvWindow *window = NULL;
542
543 TrimWhitespace(window_name);
544 if (!*window_name) { window_name = strtok(NULL, ","); continue; }
545
546 for (PetscInt w = 0; w < simCtx->fieldStatisticsWindowCount; ++w) {
547 PetscBool matches = PETSC_FALSE;
548
549 ierr = PetscStrcmp(simCtx->fieldStatisticsWindows[w].definition.name,
550 window_name, &matches); CHKERRQ(ierr);
551 if (matches) { window_index = w; break; }
552 }
553 if (window_index < 0) {
554 ierr = PetscFree(windows_copy); CHKERRQ(ierr);
555 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG,
556 "Field-statistics post-processing requested window '%s', which this run does "
557 "not configure.", window_name);
558 }
559 window = &simCtx->fieldStatisticsWindows[window_index];
560
561 /* A window that has not started by this step is not an error: a recipe
562 * spanning a whole run legitimately reaches bundles from before it began. */
563 if (window->sample_count <= 0) {
565 "Statistics window '%s' had accumulated no sample by step %" PetscInt_FMT
566 "; nothing to derive yet.\n", window->definition.name, source_step);
567 window_name = strtok(NULL, ",");
568 continue;
569 }
570
571 if (want_vtk) {
572 VTKMetaData meta;
573 PetscInt derived_count = 0;
574 char filename[PETSC_MAX_PATH_LEN];
575
576 ierr = PicurvWindowDerivedCount(&window->definition,
577 &user->fieldStatisticsStorage[window_index],
578 pps->field_statistics_outputs, &derived_count); CHKERRQ(ierr);
579 /* An output kind resolves against what the window accumulated, so asking
580 * for stresses from a means-only window yields nothing. Report it the way
581 * an unrecognized Eulerian output field is reported, rather than writing a
582 * quietly short file. */
583 if (derived_count == 0) {
585 "Outputs '%s' produce no field for window '%s'; it accumulates none of "
586 "the state they need. Skipping.\n",
588 }
589 /* One file per window. The point-data cap is per file, and a single
590 * window with every output already fills most of it. */
591 ierr = BeginStructuredVTKOutput(user, &meta); CHKERRQ(ierr);
592 for (PetscInt index = 0; index < derived_count; ++index) {
593 char name[MAX_VTK_FIELD_NAME_LENGTH];
594 Vec nodal = NULL;
595 PetscInt components = 0;
596
597 ierr = ComputeWindowStatisticNodal(user, window_index,
598 pps->field_statistics_outputs, index,
599 name, sizeof(name), &nodal, &components); CHKERRQ(ierr);
600 /* The staging vector is reused for the next field, which is safe
601 * because appending copies the values out. */
602 ierr = AppendStructuredVTKField(user, &meta, name, nodal, components); CHKERRQ(ierr);
603 }
604 ierr = PetscSNPrintf(filename, sizeof(filename), "%s_statistics_%s_%05" PetscInt_FMT ".vts",
605 pps->output_prefix, window->definition.name, ti); CHKERRQ(ierr);
606 LOG_ALLOW(GLOBAL, LOG_INFO, "Wrote %d derived field(s) for window '%s' to %s\n",
607 (int)meta.num_point_data_fields, window->definition.name, filename);
608 ierr = FinishStructuredVTKOutput(&meta, filename); CHKERRQ(ierr);
609 }
610
611 if (want_csv) {
612 ierr = ComputeWindowStatisticsSummary(user, window_index,
614 ti); CHKERRQ(ierr);
615 }
616
617 window_name = strtok(NULL, ",");
618 }
619 ierr = PetscFree(windows_copy); CHKERRQ(ierr);
620
621 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Field Statistics Pipeline Complete ---\n");
623 PetscFunctionReturn(0);
624}
PetscErrorCode RestoreFieldStatisticsState(SimCtx *simCtx, PetscInt ti)
Restores field-statistics window state and accumulators from a checkpoint.
Definition io.c:1686
PetscErrorCode ComputeWindowStatisticsSummary(UserCtx *user, PetscInt window_index, const char *output_prefix, PetscInt ti)
Appends one convergence row for an accumulated window to its CSV history.
PetscErrorCode ComputeWindowStatisticNodal(UserCtx *user, PetscInt window_index, const char *outputs, PetscInt output_index, char *out_name, size_t name_size, Vec *out_vec, PetscInt *out_components)
Derives one accumulated statistic and converts it to nodal values.
PetscErrorCode PicurvWindowDerivedCount(const PicurvWindowDefinition *definition, const PicurvWindowStorage *storage, const char *outputs, PetscInt *count)
Reports how many derived fields a requested output set produces.
PetscInt sample_count
PicurvWindowDefinition definition
PetscBool FieldStatisticsIsActive(const struct SimCtx *simCtx)
Reports whether this run has live field-statistics state.
Runtime state of one window.
PetscInt fieldStatisticsWindowCount
Definition variables.h:932
char field_statistics_output_prefix[PETSC_MAX_PATH_LEN]
Analysis prefix for CSV summaries; VTK continues to use output_prefix.
Definition variables.h:787
#define MAX_FIELD_LIST_LENGTH
Definition variables.h:745
char field_statistics_formats[1024]
Comma-separated formats: vtk for derived fields, csv for the convergence history.
Definition variables.h:785
struct PicurvWindow * fieldStatisticsWindows
Definition variables.h:933
struct PicurvWindowStorage * fieldStatisticsStorage
Definition variables.h:1136
PetscInt field_statistics_source_step
Committed step supplying the state; negative means the step being processed.
Definition variables.h:791
char field_statistics_windows[1024]
Comma-separated window names to derive; empty disables the pipeline.
Definition variables.h:781
char field_statistics_outputs[1024]
Comma-separated outputs: mean, reynolds_stress, rms, tke, flux.
Definition variables.h:783
#define MAX_VTK_FIELD_NAME_LENGTH
Maximum length for VTK field names.
Definition variables.h:749
PetscBool fieldStatisticsContinue
Definition variables.h:938
The master context for the entire simulation.
Definition variables.h:859
Here is the call graph for this function:
Here is the caller graph for this function: