PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
Loading...
Searching...
No Matches
postprocessor.c
Go to the documentation of this file.
1/**
2 * @file postprocessor.c
3 * @brief Phase 2 implementation of the post-processing tool.
4 *
5 * This phase introduces a dedicated configuration system and performs a
6 * single-step data load to verify the I/O and data structures.
7 */
8
9#include "postprocessor.h" // Use our new header
10
11/**
12 * @brief Creates a new, dedicated DMSwarm for post-processing tasks.
13 *
14 * This function is called once at startup. It creates an empty DMSwarm and
15 * associates it with the same grid DM as the primary swarm and registers all the required fields.
16 * @param user The UserCtx where user->post_swarm will be created.
17 * @param pps The PostProcessParams containing the particle_pipeline string for field registration.
18 * @return PetscErrorCode
19 */
21{
22 PetscErrorCode ierr;
23 PetscFunctionBeginUser;
24 char *pipeline_copy, *step_token, *step_saveptr;
25 PetscBool finalize_needed = PETSC_FALSE;
26
27 ierr = DMCreate(PETSC_COMM_WORLD, &user->post_swarm); CHKERRQ(ierr);
28 ierr = DMSetType(user->post_swarm, DMSWARM); CHKERRQ(ierr);
29 ierr = DMSetDimension(user->post_swarm, 3); CHKERRQ(ierr);
30 ierr = DMSwarmSetType(user->post_swarm, DMSWARM_BASIC); CHKERRQ(ierr);
31 // Associate it with the same grid as the solver's swarm
32 if (user->da) {
33 ierr = DMSwarmSetCellDM(user->post_swarm, user->da); CHKERRQ(ierr);
34 LOG_ALLOW(LOCAL,LOG_INFO,"Associated DMSwarm with Cell DM (user->da).\n");
35 } else {
36 // If user->da is essential for your simulation logic with particles, this should be a fatal error.
37 LOG_ALLOW(GLOBAL, LOG_WARNING, "user->da (Cell DM for Swarm) is NULL. Cell-based swarm operations might fail.\n");
38 // SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONGSTATE, "CreateParticleSwarm - user->da (Cell DM) is NULL but required.");
39 }
40
41 LOG_ALLOW(GLOBAL, LOG_INFO, "Created dedicated DMSwarm for post-processing.\n");
42
43 LOG_ALLOW(GLOBAL, LOG_DEBUG, " --- Setting up Post-Processing Pipeline fields-- \n");
44
45 ierr = PetscStrallocpy(pps->particle_pipeline, &pipeline_copy); CHKERRQ(ierr);
46 step_token = strtok_r(pipeline_copy, ";", &step_saveptr);
47 while (step_token) {
48 TrimWhitespace(step_token);
49 if (strlen(step_token) == 0) { step_token = strtok_r(NULL, ";", &step_saveptr); continue; }
50
51 char *keyword = strtok(step_token, ":");
52 char *args_str = strtok(NULL, "");
53 TrimWhitespace(keyword);
54 PetscInt output_field_dimensions = 1; // Default to scalar output fields
55
56 if (strcasecmp(keyword, "ComputeSpecificKE") == 0) {
57 if (!args_str) SETERRQ(PETSC_COMM_SELF, 1, "Error (ComputeSpecificKE): Missing arguments.");
58 char *inputs_str = strtok(args_str, ">");
59 char *output_field = strtok(NULL, ">");
60 output_field_dimensions = 1; // SKE is scalar
61 if (!output_field) SETERRQ(PETSC_COMM_SELF, 1, "Error (ComputeSpecificKE): Missing output field in 'in>out' syntax.");
62 TrimWhitespace(output_field);
63 // Register the output field
64 ierr = RegisterSwarmField(user->post_swarm, output_field, output_field_dimensions,PETSC_REAL); CHKERRQ(ierr);
65 LOG_ALLOW(GLOBAL, LOG_INFO, "Registered particle field '%s' (ComputeSpecificKE).\n", output_field);
66 finalize_needed = PETSC_TRUE;
67 } else {
68 LOG_ALLOW(GLOBAL, LOG_WARNING, "Warning: Unknown particle transformation keyword '%s'. Skipping.\n", keyword);
69 }
70
71 // Add other 'else if' blocks here for other kernels that create output fields
72
73 step_token = strtok_r(NULL, ";", &step_saveptr);
74 } // while step_token
75
76 ierr = PetscFree(pipeline_copy); CHKERRQ(ierr);
77
78 // --- FINALIZE STEP ---
79 if (finalize_needed) {
80 LOG_ALLOW(GLOBAL, LOG_INFO, "Finalizing new field registrations for particle pipeline.\n");
81 ierr = DMSwarmFinalizeFieldRegister(user->post_swarm); CHKERRQ(ierr);
82 }
83
84 LOG_ALLOW(GLOBAL, LOG_INFO, "Post-Processing DMSwarm setup complete.\n");
85
86 PetscFunctionReturn(0);
87}
88
89/**
90 * @brief Parses the processing pipeline string from the config and executes the requested kernels in sequence.
91 *
92 * This function uses a general-purpose parser to handle a syntax of the form:
93 * "Keyword1:in1>out1; Keyword2:in1,in2>out2; Keyword3:arg1;"
94 *
95 * It tokenizes the pipeline string and dispatches to the appropriate kernel function
96 * from processing_kernels.c with the specified field name arguments.
97 *
98 * @param user The UserCtx containing the data to be transformed.
99 * @param pps The PostProcessParams struct containing the pipeline string.
100 * @return PetscErrorCode
101 */
103{
104 PetscErrorCode ierr;
105 char *pipeline_copy, *step_token, *step_saveptr;
106
107 PetscFunctionBeginUser;
108 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Starting Data Transformation Pipeline ---\n");
109
110 // Do nothing if the pipeline string is empty
111 if (pps->process_pipeline[0] == '\0') {
112 LOG_ALLOW(GLOBAL, LOG_INFO, "Processing pipeline is empty. No transformations will be run.\n");
113 PetscFunctionReturn(0);
114 }
115
116 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Pipeline string: [%s]\n", pps->process_pipeline);
117
118 // Make a writable copy for strtok_r, as it modifies the string
119 ierr = PetscStrallocpy(pps->process_pipeline, &pipeline_copy); CHKERRQ(ierr);
120
121 // --- Outer Loop: Tokenize by Semicolon (;) to get each processing step ---
122 step_token = strtok_r(pipeline_copy, ";", &step_saveptr);
123 while (step_token) {
124 TrimWhitespace(step_token);
125 if (strlen(step_token) == 0) {
126 step_token = strtok_r(NULL, ";", &step_saveptr);
127 continue;
128 }
129
130 char *keyword = strtok(step_token, ":");
131 char *args_str = strtok(NULL, ""); // Get the rest of the string as arguments
132
133 if (!keyword) { // Should not happen with TrimWhitespace, but is a safe check
134 step_token = strtok_r(NULL, ";", &step_saveptr);
135 continue;
136 }
137
138 TrimWhitespace(keyword);
139 if (args_str) TrimWhitespace(args_str);
140
141 LOG_ALLOW(GLOBAL, LOG_INFO, "Executing Transformation: '%s' on args: '%s'\n", keyword, args_str ? args_str : "None");
142
143 // --- DISPATCHER: Route to the correct kernel based on the keyword ---
144 if (strcasecmp(keyword, "CellToNodeAverage") == 0) {
145 if (!args_str) SETERRQ(PETSC_COMM_SELF, 1, "CellToNodeAverage requires arguments in 'in_field>out_field' format.");
146 char *in_field = strtok(args_str, ">");
147 char *out_field = strtok(NULL, ">");
148 if (!in_field || !out_field) SETERRQ(PETSC_COMM_SELF, 1, "CellToNodeAverage requires 'in>out' syntax (e.g., P>P_nodal).");
149 TrimWhitespace(in_field); TrimWhitespace(out_field);
150 ierr = ComputeNodalAverage(user, in_field, out_field); CHKERRQ(ierr);
151 }
152 else if (strcasecmp(keyword, "ComputeQCriterion") == 0) {
153 ierr = ComputeQCriterion(user); CHKERRQ(ierr);
154 }
155 else if (strcasecmp(keyword, "NormalizeRelativeField") == 0) {
156 if (!args_str) SETERRQ(PETSC_COMM_SELF, 1, "NormalizePressure requires the pressure field name (e.g., 'P') as an argument.");
157 ierr = NormalizeRelativeField(user, args_str); CHKERRQ(ierr);
158 }
159 // *** Add new kernels here in the future using 'else if' ***
160 // else if (strcasecmp(keyword, "ComputeVorticity") == 0) { ... }
161 else {
162 LOG_ALLOW(GLOBAL, LOG_WARNING, "Unknown transformation keyword '%s'. Skipping.\n", keyword);
163 }
164
165 step_token = strtok_r(NULL, ";", &step_saveptr);
166 }
167
168 ierr = PetscFree(pipeline_copy); CHKERRQ(ierr);
169 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Data Transformation Pipeline Complete ---\n");
170 PetscFunctionReturn(0);
171}
172
173PetscErrorCode WriteEulerianFile(UserCtx* user, PostProcessParams* pps, PetscInt ti)
174{
175 PetscErrorCode ierr;
176 VTKMetaData meta;
177 char filename[MAX_FILENAME_LENGTH];
178
179 PetscFunctionBeginUser;
180
181 if (pps->output_fields_instantaneous[0] == '\0') {
182 LOG_ALLOW(GLOBAL, LOG_DEBUG, "No instantaneous fields requested for output at ti=%" PetscInt_FMT ". Skipping.\n", ti);
183 PetscFunctionReturn(0);
184 }
185
186 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Starting VTK File Writing for ti = %" PetscInt_FMT " ---\n", ti);
187
188 /* 1) Metadata init */
189 ierr = PetscMemzero(&meta, sizeof(VTKMetaData)); CHKERRQ(ierr);
191
192 /* 2) Coordinates (subsampled interior) */
193 ierr = PrepareOutputCoordinates(user, &meta.coords, &meta.mx, &meta.my, &meta.mz, &meta.npoints); CHKERRQ(ierr);
194 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",
195 meta.mx, meta.my, meta.mz);
196
197 /* 3) Fields (rank 0) */
198 if (user->simCtx->rank == 0) {
199 char *fields_copy, *field_name;
200 ierr = PetscStrallocpy(pps->output_fields_instantaneous, &fields_copy); CHKERRQ(ierr);
201
202 field_name = strtok(fields_copy, ",");
203 while (field_name) {
204 TrimWhitespace(field_name);
205 if (!*field_name) { field_name = strtok(NULL, ","); continue; }
206
207 LOG_ALLOW(LOCAL, LOG_DEBUG, "Preparing field '%s' for output.\n", field_name);
208
209 Vec field_vec = NULL;
210 PetscInt num_components = 0;
211
212 if (!strcasecmp(field_name, "P_nodal")) {
213 field_vec = user->P_nodal; num_components = 1;
214 } else if (!strcasecmp(field_name, "Ucat_nodal")) {
215 field_vec = user->Ucat_nodal; num_components = 3;
216 } else if (!strcasecmp(field_name, "Qcrit")) {
217 field_vec = user->Qcrit; num_components = 1;
218 } else {
219 LOG_ALLOW(LOCAL, LOG_WARNING, "Field '%s' not recognized. Skipping.\n", field_name);
220 field_name = strtok(NULL, ",");
221 continue;
222 }
223
225 LOG_ALLOW(LOCAL, LOG_WARNING, "MAX_POINT_DATA_FIELDS reached. Cannot add '%s'.\n", field_name);
226 field_name = strtok(NULL, ",");
227 continue;
228 }
229
230 // --- Add field to metadata ---
231 VTKFieldInfo* current_field = &meta.point_data_fields[meta.num_point_data_fields];
232 strncpy(current_field->name, field_name, MAX_VTK_FIELD_NAME_LENGTH-1);
233 current_field->name[MAX_VTK_FIELD_NAME_LENGTH-1] = '\0';
234 current_field->num_components = num_components;
235
236 /* Build interior AoS from NATURAL gathered Vec */
237 ierr = PrepareOutputEulerianFieldData(user, field_vec, num_components, &current_field->data); CHKERRQ(ierr);
238
239 /*
240 // *** DEBUG: Dump Ucat_nodal details and add scalar companions Ux, Uy, Uz for easier visualization ***
241
242 // If this is Ucat_nodal, dump a few tuples and add scalar companions
243 if (!strcasecmp(field_name, "Ucat_nodal")) {
244 const PetscInt npts = meta.npoints;
245 const PetscScalar *a = (const PetscScalar*)current_field->data;
246
247 LOG_ALLOW(GLOBAL, LOG_INFO, "DBG Ucat_nodal: ptr=%p npoints=%" PetscInt_FMT " num_components=%" PetscInt_FMT "\n",
248 (void*)a, npts, current_field->num_components);
249
250 if (a && current_field->num_components == 3 && npts > 0) {
251 const PetscInt nshow = (npts < 5) ? npts : 5;
252 LOG_ALLOW(GLOBAL, LOG_INFO, "DBG Ucat_nodal: showing first %d of %" PetscInt_FMT " tuples (AoS x,y,z):\n",
253 (int)nshow, npts);
254 for (PetscInt t = 0; t < nshow; ++t) {
255 LOG_ALLOW(GLOBAL, LOG_INFO, " Ucat_nodal[%3" PetscInt_FMT "] = (%g, %g, %g)\n",
256 t, (double)a[3*t+0], (double)a[3*t+1], (double)a[3*t+2]);
257 }
258 if (npts > 10) {
259 PetscInt mid = npts / 2;
260 LOG_ALLOW(GLOBAL, LOG_INFO, " Ucat_nodal[mid=%" PetscInt_FMT "] = (%g, %g, %g)\n",
261 mid, (double)a[3*mid+0], (double)a[3*mid+1], (double)a[3*mid+2]);
262 }
263
264 // Add scalar companions from the AoS we just created
265 PetscScalar *Ux=NULL,*Uy=NULL,*Uz=NULL;
266 ierr = PetscMalloc1(meta.npoints, &Ux); CHKERRQ(ierr);
267 ierr = PetscMalloc1(meta.npoints, &Uy); CHKERRQ(ierr);
268 ierr = PetscMalloc1(meta.npoints, &Uz); CHKERRQ(ierr);
269 for (PetscInt i = 0; i < meta.npoints; ++i) {
270 Ux[i] = a[3*i+0]; Uy[i] = a[3*i+1]; Uz[i] = a[3*i+2];
271 }
272
273 if (meta.num_point_data_fields + 3 <= MAX_POINT_DATA_FIELDS) {
274 VTKFieldInfo *fx = &meta.point_data_fields[++meta.num_point_data_fields];
275 strncpy(fx->name, "Ux_debug", MAX_VTK_FIELD_NAME_LENGTH-1);
276 fx->name[MAX_VTK_FIELD_NAME_LENGTH-1] = '\0';
277 fx->num_components = 1; fx->data = Ux;
278
279 VTKFieldInfo *fy = &meta.point_data_fields[++meta.num_point_data_fields];
280 strncpy(fy->name, "Uy_debug", MAX_VTK_FIELD_NAME_LENGTH-1);
281 fy->name[MAX_VTK_FIELD_NAME_LENGTH-1] = '\0';
282 fy->num_components = 1; fy->data = Uy;
283
284 VTKFieldInfo *fz = &meta.point_data_fields[++meta.num_point_data_fields];
285 strncpy(fz->name, "Uz_debug", MAX_VTK_FIELD_NAME_LENGTH-1);
286 fz->name[MAX_VTK_FIELD_NAME_LENGTH-1] = '\0';
287 fz->num_components = 1; fz->data = Uz;
288
289 LOG_ALLOW(GLOBAL, LOG_INFO, "DBG: Added scalar companions Ux_debug, Uy_debug, Uz_debug.\n");
290 } else {
291 LOG_ALLOW(GLOBAL, LOG_WARNING, "DBG: Not enough slots to add Ux/Uy/Uz debug fields.\n");
292 PetscFree(Ux); PetscFree(Uy); PetscFree(Uz);
293 }
294
295 // Mid-plane CSV + AoS vs NATURAL compare (component X)
296
297 // Gather NATURAL again (small cost, but isolated and clear)
298 PetscInt Ng = 0;
299 double *nat_d = NULL;
300 DM dmU = NULL;
301 DMDALocalInfo infU;
302 ierr = VecGetDM(field_vec, &dmU); CHKERRQ(ierr);
303 ierr = DMDAGetLocalInfo(dmU, &infU); CHKERRQ(ierr);
304
305 const PetscInt M=infU.mx, N=infU.my, P=infU.mz;
306 const PetscInt mx = meta.mx, my = meta.my, mz = meta.mz;
307 const PetscInt iInnerMid = mx/2; // interior index [0..mx-1]
308 const PetscInt iGlob = iInnerMid;
309
310 ierr = VecToArrayOnRank0(field_vec, &Ng, &nat_d); CHKERRQ(ierr);
311
312 if (nat_d) {
313 const PetscScalar *nar = (const PetscScalar*)nat_d;
314 const char *base = pps->output_prefix;
315 char fn[512], fnc[512];
316 snprintf(fn, sizeof(fn), "%s_%05" PetscInt_FMT "_iMid.csv", base, ti);
317 snprintf(fnc, sizeof(fnc), "%s_%05" PetscInt_FMT "_iMid_compare.csv", base, ti);
318
319 FILE *fp = fopen(fn, "w");
320 FILE *fpc = fopen(fnc, "w");
321 if (fp) fprintf(fp, "jInner,kInner,Ux,Uy,Uz\n");
322 if (fpc) fprintf(fpc, "jInner,kInner,Ux_AoS,Ux_NAT,abs_diff\n");
323
324 double maxAbsDiff = 0.0, sumAbs = 0.0;
325 PetscInt count = 0;
326
327 for (PetscInt kInner = 0; kInner < mz; ++kInner) {
328 const PetscInt k = kInner;
329 for (PetscInt jInner = 0; jInner < my; ++jInner) {
330 const PetscInt j = jInner;
331
332 // AoS tuple index
333 const PetscInt t = iInnerMid + mx * (jInner + my * kInner);
334 const PetscScalar ux = a[3*t+0], uy = a[3*t+1], uz = a[3*t+2];
335
336 if (fp) fprintf(fp, "%d,%d,%.15e,%.15e,%.15e\n",
337 (int)jInner,(int)kInner,(double)ux,(double)uy,(double)uz);
338
339 // NATURAL base for (iGlob,j,k)
340 const PetscInt baseNat = 3 * (((k)*N + j)*M + iGlob);
341 const PetscScalar uxN = nar[baseNat + 0];
342
343 const double diff = fabs((double)ux - (double)uxN);
344 if (diff > maxAbsDiff) maxAbsDiff = diff;
345 sumAbs += diff; ++count;
346
347 if (fpc) fprintf(fpc, "%d,%d,%.15e,%.15e,%.15e\n",
348 (int)jInner,(int)kInner,(double)ux,(double)uxN,diff);
349 } // for jInner
350 } // for kInner
351 if (fp) fclose(fp);
352 if (fpc) fclose(fpc);
353
354 if (count > 0) {
355 const double meanAbs = sumAbs / (double)count;
356 LOG_ALLOW(GLOBAL, LOG_INFO,
357 "PETSc-Vec vs AoS (i-mid, Ux): max|Δ|=%.6e, mean|Δ|=%.6e -> CSV: %s\n",
358 maxAbsDiff, meanAbs, fnc);
359 LOG_ALLOW(GLOBAL, LOG_INFO, "Wrote i-mid plane CSV: %s\n", fn);
360 } // if count>0
361
362 ierr = PetscFree(nat_d); CHKERRQ(ierr);
363
364
365 } // if nat_d
366
367 } // if a && num_components==3 && npts>0
368 } // if Ucat_nodal
369 // --- END DEBUG BLOCK (Ucat_nodal) ---
370 */
371
372 meta.num_point_data_fields++; /* count the main field we just added */
373 field_name = strtok(NULL, ",");
374 }
375
376 ierr = PetscFree(fields_copy); CHKERRQ(ierr);
377
378 // --- DEBUG: Add sanity fields i_idx, j_idx, k_idx and x_pos, y_pos, z_pos ---
379 // These are the logical indices and physical coordinates of each point in the subsampled grid.
380 // They can be used to verify the grid structure and orientation in visualization tools.
381 // They are added as scalar fields with names "i_idx", "j_idx", "k_idx" and "x_pos", "y_pos", "z_pos".
382 // Note: these are only added if there is room in the MAX_POINT_DATA_FIELDS limit.
383 // They are allocated and owned here, and will be freed below.
384 // They are in the same linearization order as meta.coords (AoS x,y,z by point).
385 /*
386 // Append sanity fields i/j/k indices and coordinates
387
388 // Build i/j/k and x/y/z (length = npoints); these match the same linearization as coords
389 const PetscInt n = meta.npoints;
390 PetscScalar *i_idx=NULL,*j_idx=NULL,*k_idx=NULL,*x_pos=NULL,*y_pos=NULL,*z_pos=NULL;
391
392 ierr = PetscMalloc1(n, &i_idx); CHKERRQ(ierr);
393 ierr = PetscMalloc1(n, &j_idx); CHKERRQ(ierr);
394 ierr = PetscMalloc1(n, &k_idx); CHKERRQ(ierr);
395 ierr = PetscMalloc1(n, &x_pos); CHKERRQ(ierr);
396 ierr = PetscMalloc1(n, &y_pos); CHKERRQ(ierr);
397 ierr = PetscMalloc1(n, &z_pos); CHKERRQ(ierr);
398
399 // coords is length 3*n, AoS: (x,y,z) by point
400 const PetscScalar *c = (const PetscScalar*)meta.coords;
401 for (PetscInt k = 0; k < meta.mz; ++k) {
402 for (PetscInt j = 0; j < meta.my; ++j) {
403 for (PetscInt i = 0; i < meta.mx; ++i) {
404 const PetscInt t = i + meta.mx * (j + meta.my * k);
405 i_idx[t] = (PetscScalar)i;
406 j_idx[t] = (PetscScalar)j;
407 k_idx[t] = (PetscScalar)k;
408 x_pos[t] = c[3*t+0];
409 y_pos[t] = c[3*t+1];
410 z_pos[t] = c[3*t+2];
411 }
412 }
413 }
414
415 const char *nf[6] = {"i_idx","j_idx","k_idx","x_pos","y_pos","z_pos"};
416 PetscScalar *arrs[6] = {i_idx,j_idx,k_idx,x_pos,y_pos,z_pos};
417 for (int s=0; s<6; ++s) {
418 if (meta.num_point_data_fields < MAX_POINT_DATA_FIELDS) {
419 VTKFieldInfo *f = &meta.point_data_fields[meta.num_point_data_fields++];
420 strncpy(f->name, nf[s], MAX_VTK_FIELD_NAME_LENGTH-1);
421 f->name[MAX_VTK_FIELD_NAME_LENGTH-1] = '\0';
422 f->num_components = 1;
423 f->data = arrs[s];
424 } else {
425 LOG_ALLOW(GLOBAL, LOG_WARNING, "Sanity field '%s' dropped: MAX_POINT_DATA_FIELDS reached.\n", nf[s]);
426 PetscFree(arrs[s]);
427 }
428 }
429 LOG_ALLOW(GLOBAL, LOG_INFO, "DBG: Added sanity fields i_idx/j_idx/k_idx and x_pos/y_pos/z_pos.\n");
430 */
431 // --- END DEBUG BLOCK (sanity fields) ---
432
433 /* Field summary */
434 LOG_ALLOW(GLOBAL, LOG_INFO, "PointData fields to write: %d\n", (int)meta.num_point_data_fields);
435 for (PetscInt ii=0; ii<meta.num_point_data_fields; ++ii) {
436 LOG_ALLOW(GLOBAL, LOG_INFO, " # %2" PetscInt_FMT " Field Name = %s Components = %d\n",
437 ii, meta.point_data_fields[ii].name, (int)meta.point_data_fields[ii].num_components);
438 }
439 } // if rank 0
440
441 /* 4) Write the VTS */
442 snprintf(filename, sizeof(filename), "%s_%05" PetscInt_FMT ".vts", pps->output_prefix, ti);
443 ierr = CreateVTKFileFromMetadata(filename, &meta, PETSC_COMM_WORLD); CHKERRQ(ierr);
444
445 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Eulerian File Writing for ti = %" PetscInt_FMT " Complete ---\n", ti);
446 PetscFunctionReturn(0);
447}
448
449/**
450 * @brief Parses and executes the particle pipeline using a robust two-pass approach.
451 *
452 * This function ensures correctness and efficiency by separating field registration
453 * from kernel execution.
454 *
455 * PASS 1 (Registration): The pipeline string is parsed to identify all new fields
456 * that will be created. These fields are registered with the DMSwarm.
457 *
458 * Finalize: After Pass 1, DMSwarmFinalizeFieldRegister is called exactly once if
459 * any new fields were added, preparing the swarm's memory layout.
460 *
461 * PASS 2 (Execution): The pipeline string is parsed again, and this time the
462 * actual compute kernels are executed, filling the now-valid fields.
463 *
464 * @param user The UserCtx containing the DMSwarm.
465 * @param pps The PostProcessParams struct containing the particle_pipeline string.
466 * @return PetscErrorCode
467 */
469{
470 PetscErrorCode ierr;
471 char *pipeline_copy, *step_token, *step_saveptr;
472
473 PetscFunctionBeginUser;
474
475 // --- Timestep Setup: Synchronize post_swarm size ---
476 PetscInt n_global_source;
477 ierr = DMSwarmGetSize(user->swarm, &n_global_source); CHKERRQ(ierr);
478
479 // Resize post_swarm to match source swarm
480 ierr = ResizeSwarmGlobally(user->post_swarm, n_global_source); CHKERRQ(ierr);
481
482 if (pps->particle_pipeline[0] == '\0') {
483 PetscFunctionReturn(0);
484 }
485
486 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Starting Particle Data Transformation Pipeline ---\n");
487 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Particle Pipeline string: [%s]\n", pps->particle_pipeline);
488
489 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Executing compute kernels...\n");
490 ierr = PetscStrallocpy(pps->particle_pipeline, &pipeline_copy); CHKERRQ(ierr);
491 step_token = strtok_r(pipeline_copy, ";", &step_saveptr);
492 while (step_token) {
493 TrimWhitespace(step_token);
494 if (strlen(step_token) == 0) { step_token = strtok_r(NULL, ";", &step_saveptr); continue; }
495
496 char *keyword = strtok(step_token, ":");
497 char *args_str = strtok(NULL, "");
498 TrimWhitespace(keyword);
499 if (args_str) TrimWhitespace(args_str);
500
501 LOG_ALLOW(GLOBAL, LOG_INFO, "Executing Particle Transformation: '%s' on args: '%s'\n", keyword, args_str ? args_str : "None");
502
503 if (strcasecmp(keyword, "ComputeSpecificKE") == 0) {
504 char *velocity_field = strtok(args_str, ">");
505 char *ske_field = strtok(NULL, ">");
506 TrimWhitespace(velocity_field); TrimWhitespace(ske_field);
507
508 ierr = ComputeSpecificKE(user, velocity_field, ske_field); CHKERRQ(ierr);
509 }
510 else {
511 LOG_ALLOW(GLOBAL, LOG_WARNING, "Unknown particle transformation keyword '%s'. Skipping.\n", keyword);
512 }
513
514 step_token = strtok_r(NULL, ";", &step_saveptr);
515 }
516 ierr = PetscFree(pipeline_copy); CHKERRQ(ierr);
517
518 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Particle Data Transformation Pipeline Complete ---\n");
519 PetscFunctionReturn(0);
520}
521
522
523/**
524 * @brief Writes particle data to a VTP file using the Prepare-Write-Cleanup pattern.
525 */
526PetscErrorCode WriteParticleFile(UserCtx* user, PostProcessParams* pps, PetscInt ti)
527{
528 PetscErrorCode ierr;
529 VTKMetaData part_meta;
530 char filename[MAX_FILENAME_LENGTH];
531 PetscInt n_total_particles_before_subsample;
532
533 PetscFunctionBeginUser;
534
535 // These checks can be done on all ranks
536 if (!pps->outputParticles || pps->particle_fields[0] == '\0') {
537 PetscFunctionReturn(0);
538 }
539 PetscInt n_global;
540 ierr = DMSwarmGetSize(user->swarm, &n_global); CHKERRQ(ierr);
541 if (n_global == 0) {
542 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Swarm is empty for ti=%" PetscInt_FMT ". Skipping particle file write.\n", ti);
543 PetscFunctionReturn(0);
544 }
545
546 ierr = PetscMemzero(&part_meta, sizeof(VTKMetaData)); CHKERRQ(ierr);
547
548 // --- 1. PREPARE (Collective Call) ---
549 ierr = PrepareOutputParticleData(user, pps, &part_meta, &n_total_particles_before_subsample); CHKERRQ(ierr);
550
551 // --- 2. WRITE and CLEANUP (Rank 0 only) ---
552 if (user->simCtx->rank == 0) {
553 if (part_meta.npoints > 0) {
554 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Starting VTP Particle File Writing for ti = %" PetscInt_FMT " (writing %" PetscInt_FMT " of %" PetscInt_FMT " particles) ---\n",
555 ti, part_meta.npoints, n_total_particles_before_subsample);
556
557 /* Field summary */
558 LOG_ALLOW(GLOBAL, LOG_INFO, "Particle Data fields to write: %d\n", (int)part_meta.num_point_data_fields);
559 for (PetscInt ii=0; ii<part_meta.num_point_data_fields; ++ii) {
560 LOG_ALLOW(GLOBAL, LOG_INFO, " # %2" PetscInt_FMT " Field Name = %s Components = %d\n",
561 ii, part_meta.point_data_fields[ii].name, (int)part_meta.point_data_fields[ii].num_components);
562 }
563
564 snprintf(filename, sizeof(filename), "%s_%05" PetscInt_FMT ".vtp", pps->particle_output_prefix, ti);
565 ierr = CreateVTKFileFromMetadata(filename, &part_meta, PETSC_COMM_WORLD); CHKERRQ(ierr);
566
567 } else {
568 LOG_ALLOW(GLOBAL, LOG_DEBUG, "No particles to write at ti=%" PetscInt_FMT " after subsampling. Skipping.\n", ti);
569 }
570
571 }
572
573 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Particle File Writing for ti = %" PetscInt_FMT " Complete ---\n", ti);
574 PetscFunctionReturn(0);
575}
576
577#undef __FUNCT__
578#define __FUNCT__ "main"
579int main(int argc, char **argv)
580{
581 PetscErrorCode ierr;
582 SimCtx *simCtx = NULL;
583
584 // === I. INITIALIZE PETSC & MPI ===========================================
585 ierr = PetscInitialize(&argc, &argv, (char *)0, "Unified Post-Processing Tool"); CHKERRQ(ierr);
586
587 // === II. CONFIGURE SIMULATION & POST-PROCESSING CONTEXTS =================
588 ierr = CreateSimulationContext(argc, argv, &simCtx); CHKERRQ(ierr);
589 // === IIA. SET EXECUTION MODE (SOLVER vs POST-PROCESSOR) =====
591 // === III. SETUP GRID & DATA STRUCTURES ===================================
592 ierr = SetupGridAndSolvers(simCtx); CHKERRQ(ierr);
593 // === IV. SETUP DOMAIN DECOMPOSITION INFORMATION =========================
594 ierr = SetupDomainRankInfo(simCtx); CHKERRQ(ierr);
595 // === V. SETUP BOUNDARY CONDITIONS ====================================
596 ierr = SetupBoundaryConditions(simCtx); CHKERRQ(ierr);
597 // === VI. SETUP USER CONTEXT & DATA STRUCTURES ============================
598 // Get the finest-level user context, as this is where we'll load data
599 UserCtx *user = simCtx->usermg.mgctx[simCtx->usermg.mglevels-1].user;
600 PostProcessParams *pps = simCtx->pps;
601
602 // === VI. PARTICLE INITIALIZATION (if needed) ============================
603 if(pps->outputParticles) {
604 if(simCtx->np > 0){
605 ierr = InitializeParticleSwarm(simCtx); CHKERRQ(ierr);
606 // Create a post-processing specific DMSwarm
607 ierr = SetupPostProcessSwarm(user,pps); CHKERRQ(ierr);
608 }else{
609 SETERRQ(PETSC_COMM_SELF,1,"Particle output requested but np=0. Please set np>0 to enable particle output.");
610 }
611 }
612
613 LOG_ALLOW(GLOBAL, LOG_INFO, "=============================================================\n");
614
615
616 // === VII. MAIN POST-PROCESSING LOOP ======================================
617 for (PetscInt ti = pps->startTime; ti <= pps->endTime; ti += pps->timeStep) {
618 LOG_ALLOW(GLOBAL, LOG_INFO, "--- Processing Time Step %" PetscInt_FMT " ---\n", ti);
619
620 // 1. Load Data (UpdateLocalGhosts is called inside the kernels)
621 ierr = ReadSimulationFields(user, ti); CHKERRQ(ierr);
622
623 // 2. Transform Data
624 ierr = EulerianDataProcessingPipeline(user, pps); CHKERRQ(ierr);
625
626 // 3. Write Output
627 ierr = WriteEulerianFile(user, pps, ti); CHKERRQ(ierr);
628
629 if(pps->outputParticles) {
630 // 1. Resize swarm based on particle count in this timestep's file
631 ierr = PreCheckAndResizeSwarm(user, ti, pps->particleExt); CHKERRQ(ierr);
632
633 // 2. Load particle data into the correctly sized swarm
634 ierr = ReadAllSwarmFields(user, ti); CHKERRQ(ierr);
635
636 // 3. Transform particle data
637 ierr = ParticleDataProcessingPipeline(user, pps); CHKERRQ(ierr);
638
639 // 4. Write particle output
640 ierr = WriteParticleFile(user, pps, ti); CHKERRQ(ierr);
641 }
642 }
643
644 LOG_ALLOW(GLOBAL, LOG_INFO, "=============================================================\n");
645 LOG_ALLOW(GLOBAL, LOG_INFO, "Post-processing finished successfully.\n");
646
647
648 // === VIII. FINALIZE =========================================================
649 // ierr = FinalizeSimulation(simCtx); CHKERRQ(ierr);
650 ierr = PetscFinalize();
651 return ierr;
652}
PetscErrorCode ResizeSwarmGlobally(DM swarm, PetscInt N_target)
PetscErrorCode PreCheckAndResizeSwarm(UserCtx *user, PetscInt ti, const char *ext)
Checks particle count in the reference file and resizes the swarm if needed.
PetscErrorCode InitializeParticleSwarm(SimCtx *simCtx)
Perform particle swarm initialization, particle-grid interaction, and related operations.
PetscErrorCode RegisterSwarmField(DM swarm, const char *fieldName, PetscInt fieldDim, PetscDataType dtype)
Registers a swarm field without finalizing registration.
PetscErrorCode ReadSimulationFields(UserCtx *user, PetscInt ti)
Reads binary field data for velocity, pressure, and other required vectors.
Definition io.c:823
void TrimWhitespace(char *str)
Helper function to trim leading/trailing whitespace from a string.
Definition io.c:1910
PetscErrorCode ReadAllSwarmFields(UserCtx *user, PetscInt ti)
Reads multiple fields (positions, velocity, CellID, and weight) into a DMSwarm.
Definition io.c:1100
PetscInt CreateVTKFileFromMetadata(const char *filename, const VTKMetaData *meta, MPI_Comm comm)
Definition vtk_io.c:128
#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_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
PetscErrorCode ComputeQCriterion(UserCtx *user)
Computes the Q-Criterion, a scalar value identifying vortex cores.
PetscErrorCode ComputeSpecificKE(UserCtx *user, const char *velocity_field, const char *ske_field)
Computes the specific kinetic energy (KE per unit mass) for each particle.
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 ...
PetscErrorCode EulerianDataProcessingPipeline(UserCtx *user, PostProcessParams *pps)
Parses the processing pipeline string from the config and executes the requested kernels in sequence.
PetscErrorCode WriteEulerianFile(UserCtx *user, PostProcessParams *pps, PetscInt ti)
Orchestrates the writing of a combined, multi-field VTK file for a single time step.
int main(int argc, char **argv)
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 SetupPostProcessSwarm(UserCtx *user, PostProcessParams *pps)
Creates a new, dedicated DMSwarm for post-processing tasks.
PetscErrorCode SetupDomainRankInfo(SimCtx *simCtx)
Sets up the full rank communication infrastructure, including neighbor ranks and bounding box exchang...
Definition setup.c:1687
PetscErrorCode SetupGridAndSolvers(SimCtx *simCtx)
The main orchestrator for setting up all grid-related components.
Definition setup.c:577
PetscErrorCode CreateSimulationContext(int argc, char **argv, SimCtx **p_simCtx)
Allocates and populates the master SimulationContext object.
Definition setup.c:41
PetscErrorCode SetupBoundaryConditions(SimCtx *simCtx)
(Orchestrator) Sets up all boundary conditions for the simulation.
Definition setup.c:1035
Vec P_nodal
Definition variables.h:702
UserCtx * user
Definition variables.h:418
#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
char particle_output_prefix[256]
Definition variables.h:457
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
char output_prefix[256]
Definition variables.h:454
UserMG usermg
Definition variables.h:599
DM post_swarm
Definition variables.h:701
Vec Ucat_nodal
Definition variables.h:703
PetscInt timeStep
Definition variables.h:447
PetscInt np
Definition variables.h:585
#define MAX_FILENAME_LENGTH
Definition variables.h:435
Vec Qcrit
Definition variables.h:704
VTKFileType fileType
Definition variables.h:482
char output_fields_instantaneous[1024]
Definition variables.h:452
PetscScalar * data
Definition variables.h:472
char particle_pipeline[1024]
Definition variables.h:455
char name[64]
Definition variables.h:470
PetscScalar * coords
Definition variables.h:485
PetscInt mglevels
Definition variables.h:425
char process_pipeline[1024]
Definition variables.h:451
char particle_fields[1024]
Definition variables.h:456
PetscBool outputParticles
Definition variables.h:448
VTKFieldInfo point_data_fields[20]
Definition variables.h:486
PetscInt mz
Definition variables.h:483
PostProcessParams * pps
Definition variables.h:616
@ EXEC_MODE_POSTPROCESSOR
Definition variables.h:497
char particleExt[8]
Definition variables.h:462
MGCtx * mgctx
Definition variables.h:428
ExecutionMode exec_mode
Definition variables.h:532
PetscInt startTime
Definition variables.h:445
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_STRUCTURED
Definition variables.h:477
Holds all configuration parameters for a post-processing run.
Definition variables.h:443
The master context for the entire simulation.
Definition variables.h:513
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 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
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