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

Go to the source code of this file.

Functions

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.
 
PetscErrorCode ExtendToLayoutBoundary (UserCtx *user, Vec global, PetscInt components)
 Populates the layout boundary of a field that was written on the interior only.
 
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 DimensionalizeField (UserCtx *user, const char *field_name)
 Scales a specified field from non-dimensional to dimensional units in-place.
 
PetscErrorCode DimensionalizeAllLoadedFields (UserCtx *user)
 Orchestrates the dimensionalization of all relevant fields loaded from a file.
 
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 ComputeDisplacement (UserCtx *user, const char *disp_field)
 Computes the displacement magnitude |r_i - r_0| for each particle (per-particle VTK kernel).
 
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 ComputeWindowStatisticsSummary (UserCtx *user, PetscInt window_index, const char *output_prefix, PetscInt ti)
 Appends one convergence row for an accumulated window to its CSV history.
 

Function Documentation

◆ ComputeNodalAverage()

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.

The kernel reads the input field by name, computes nodal values, and stores the output in the named destination field. Both fields must already exist in the current UserCtx.

Parameters
[in,out]userBlock-level context that owns the source and destination vectors.
[in]in_field_nameName of the input field to sample.
[in]out_field_nameName of the output field to populate.
Returns
PetscErrorCode 0 on success.

Interpolates a cell-centered field to nodal locations using local stencil averaging.

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

See also
ComputeNodalAverage()

Definition at line 236 of file postprocessing_kernels.c.

237{
238 PetscErrorCode ierr;
239 FieldId in_field_id;
240 Vec in_vec_local = NULL, out_vec_global = NULL;
241 DM dm_in = NULL, dm_out = NULL;
242 PetscInt dof = 0;
243
244 PetscFunctionBeginUser;
246 LOG_ALLOW(GLOBAL, LOG_INFO, "-> KERNEL: Running ComputeNodalAverage on '%s' -> '%s'.\n", in_field_name, out_field_name);
247
248 // --- 1. Map string names to PETSc objects ---
249 if (strcasecmp(in_field_name, "P") == 0) { in_vec_local = user->lP; dm_in = user->da; dof = 1; }
250 else if (strcasecmp(in_field_name, "Ucat") == 0) { in_vec_local = user->lUcat; dm_in = user->fda; dof = 3; }
251 else if (strcasecmp(in_field_name, "Psi") == 0) { in_vec_local = user->lPsi; dm_in = user->da; dof = 1; }
252 /* The staging pair carries derived statistics, which are config-counted and so
253 * cannot be named by a compile-time member of their own. */
254 else if (strcasecmp(in_field_name, "PostScalar") == 0) { in_vec_local = user->lPostScalar; dm_in = user->da; dof = 1; }
255 else if (strcasecmp(in_field_name, "PostVector") == 0) { in_vec_local = user->lPostVector; dm_in = user->fda; dof = 3; }
256 // ... (add other fields as needed) ...
257 else SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG, "Unknown input field name for nodal averaging: %s", in_field_name);
258
259 if (strcasecmp(out_field_name, "P_nodal") == 0) { out_vec_global = user->P_nodal; dm_out = user->da; }
260 else if (strcasecmp(out_field_name, "Ucat_nodal") == 0) { out_vec_global = user->Ucat_nodal; dm_out = user->fda; }
261 else if (strcasecmp(out_field_name, "Psi_nodal") == 0) { out_vec_global = user->Psi_nodal; dm_out = user->da; }
262 else if (strcasecmp(out_field_name, "PostScalarNodal") == 0) { out_vec_global = user->PostScalarNodal; dm_out = user->da; }
263 else if (strcasecmp(out_field_name, "PostVectorNodal") == 0) { out_vec_global = user->PostVectorNodal; dm_out = user->fda; }
264 // ... (add other fields as needed) ...
265 else SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG, "Unknown output field name for nodal averaging: %s", out_field_name);
266
267 // --- 2. Ensure Input Data Ghosts are Up-to-Date ---
268 ierr = FieldIdFromName(in_field_name, &in_field_id); CHKERRQ(ierr);
269 ierr = UpdateLocalGhosts(user, in_field_id); CHKERRQ(ierr);
270
271 // --- 3. Get DMDA info and array pointers ---
272 DMDALocalInfo info;
273 ierr = DMDAGetLocalInfo(dm_out, &info); CHKERRQ(ierr);
274 /* Every owned output point is valid except the global high layout plane.
275 * A rank interface is not a boundary: its +1 source value is in the halo. */
276 const PetscInt i_end = PetscMin(info.xs + info.xm, info.mx - 1);
277 const PetscInt j_end = PetscMin(info.ys + info.ym, info.my - 1);
278 const PetscInt k_end = PetscMin(info.zs + info.zm, info.mz - 1);
279
280 if (dof == 1) { // --- Scalar Field Averaging ---
281 const PetscReal ***l_in_arr;
282 PetscReal ***g_out_arr;
283 ierr = DMDAVecGetArrayRead(dm_in,in_vec_local, (void*)&l_in_arr); CHKERRQ(ierr);
284 ierr = DMDAVecGetArray(dm_out,out_vec_global, (void*)&g_out_arr); CHKERRQ(ierr);
285
286 // Loop over the output NODE locations. The loop bounds match the required
287 // size of the final subsampled grid.
288 for (PetscInt k = info.zs; k < k_end; k++) {
289 for (PetscInt j = info.ys; j < j_end; j++) {
290 for (PetscInt i = info.xs; i < i_end; i++) {
291 g_out_arr[k][j][i] = 0.125 * (l_in_arr[k][j][i] + l_in_arr[k][j][i+1] +
292 l_in_arr[k][j+1][i] + l_in_arr[k][j+1][i+1] +
293 l_in_arr[k+1][j][i] + l_in_arr[k+1][j][i+1] +
294 l_in_arr[k+1][j+1][i] + l_in_arr[k+1][j+1][i+1]);
295 }
296 }
297 }
298 ierr = DMDAVecRestoreArrayRead(dm_in,in_vec_local, (void*)&l_in_arr); CHKERRQ(ierr);
299 ierr = DMDAVecRestoreArray(dm_out,out_vec_global, (void*)&g_out_arr); CHKERRQ(ierr);
300
301 } else if (dof == 3) { // --- Vector Field Averaging ---
302 const Cmpnts ***l_in_arr;
303 Cmpnts ***g_out_arr;
304 ierr = DMDAVecGetArrayRead(dm_in,in_vec_local, (void*)&l_in_arr); CHKERRQ(ierr);
305 ierr = DMDAVecGetArray(dm_out,out_vec_global, (void*)&g_out_arr); CHKERRQ(ierr);
306
307 for (PetscInt k = info.zs; k < k_end; k++) {
308 for (PetscInt j = info.ys; j < j_end; j++) {
309 for (PetscInt i = info.xs; i < i_end; i++) {
310 g_out_arr[k][j][i].x = 0.125 * (l_in_arr[k][j][i].x + l_in_arr[k][j][i+1].x +
311 l_in_arr[k][j+1][i].x + l_in_arr[k][j+1][i+1].x +
312 l_in_arr[k+1][j][i].x + l_in_arr[k+1][j][i+1].x +
313 l_in_arr[k+1][j+1][i].x + l_in_arr[k+1][j+1][i+1].x);
314
315 g_out_arr[k][j][i].y = 0.125 * (l_in_arr[k][j][i].y + l_in_arr[k][j][i+1].y +
316 l_in_arr[k][j+1][i].y + l_in_arr[k][j+1][i+1].y +
317 l_in_arr[k+1][j][i].y + l_in_arr[k+1][j][i+1].y +
318 l_in_arr[k+1][j+1][i].y + l_in_arr[k+1][j+1][i+1].y);
319
320 g_out_arr[k][j][i].z = 0.125 * (l_in_arr[k][j][i].z + l_in_arr[k][j][i+1].z +
321 l_in_arr[k][j+1][i].z + l_in_arr[k][j+1][i+1].z +
322 l_in_arr[k+1][j][i].z + l_in_arr[k+1][j][i+1].z +
323 l_in_arr[k+1][j+1][i].z + l_in_arr[k+1][j+1][i+1].z);
324 }
325 }
326 }
327 ierr = DMDAVecRestoreArrayRead(dm_in,in_vec_local, (void*)&l_in_arr); CHKERRQ(ierr);
328 ierr = DMDAVecRestoreArray(dm_out,out_vec_global, (void*)&g_out_arr); CHKERRQ(ierr);
329 }
331 PetscFunctionReturn(0);
332}
PetscErrorCode FieldIdFromName(const char *field_name, FieldId *field_id)
Resolve a user-facing field name once into its typed identity.
FieldId
Compile-time identity for a catalogued Eulerian field.
#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:859
@ LOG_INFO
Informational messages about program execution.
Definition logging.h:31
#define PROFILE_FUNCTION_BEGIN
Marks the beginning of a profiled code block (typically a function).
Definition logging.h:850
PetscErrorCode UpdateLocalGhosts(UserCtx *user, FieldId field_id)
Updates the local vector (including ghost points) from its corresponding global vector.
Definition setup.c:1838
Vec lPostScalar
Definition variables.h:959
Vec P_nodal
Definition variables.h:1001
Vec Ucat_nodal
Definition variables.h:1002
Vec lPsi
Definition variables.h:997
Vec PostScalarNodal
Definition variables.h:959
PetscScalar x
Definition variables.h:103
PetscScalar z
Definition variables.h:103
Vec PostVectorNodal
Definition variables.h:960
Vec Psi_nodal
Definition variables.h:1004
Vec lPostVector
Definition variables.h:960
Vec lUcat
Definition variables.h:939
PetscScalar y
Definition variables.h:103
A 3D point or vector with PetscScalar components.
Definition variables.h:102
Here is the call graph for this function:
Here is the caller graph for this function:

◆ ExtendToLayoutBoundary()

PetscErrorCode ExtendToLayoutBoundary ( UserCtx user,
Vec  global,
PetscInt  components 
)

Populates the layout boundary of a field that was written on the interior only.

This is not a boundary condition. It enforces no physics and satisfies no equation. It replicates the layout convention that boundary-condition application already gives solver state fields, so that stencil kernels — ComputeNodalAverage() above all — read defined values instead of the structural zeros an interior-only producer leaves behind.

Fields written by PicurvWindowDerive() and ComputeQCriterion() cover only the physical interior, so their layout boundary holds zeros that mean "never written" rather than "zero". A node on the domain edge averages four such entries with four real cells and comes out at roughly half its true value. Solver state fields never need this: Ucat, P, Nu_t, and CS all carry boundary values written when their boundary conditions were applied.

Only periodic directions are handled. There the correct value exists and is exact — the low dummy plane repeats the last physical plane and the high dummy plane repeats the first. On a non-periodic face the correct value depends on both the quantity and the boundary type, and no single convention serves a staging buffer that carries stresses, pressure, and eddy viscosity in turn; nothing is written there rather than something invented. The design for the non-periodic case is recorded in Field Statistics Planned Extensions.

Operates on the global vector, so it must run before the caller's UpdateLocalGhosts(), which then carries the written values into every local ghost region. Rank interfaces are that scatter's responsibility and are never touched here.

Multi-block interface boundaries are out of scope.

Parameters
[in]userBlock context supplying the DMDA layout and periodicity.
[in,out]globalGlobal vector whose layout boundary is populated.
[in]componentsDegrees of freedom carried: 1 or 3.
Returns
Zero on success, or PETSC_ERR_ARG_NULL for a null argument, or PETSC_ERR_ARG_OUTOFRANGE for an unsupported component count.

Populates the layout boundary of a field that was written on the interior only.

Full API contract is documented with the header declaration in include/postprocessing_kernels.h.

See also
ExtendToLayoutBoundary()

Definition at line 142 of file postprocessing_kernels.c.

143{
144 SimCtx *simCtx = NULL;
145 DM dm = NULL;
146 Vec local = NULL;
147 PetscInt periodic[3];
148
149 PetscFunctionBeginUser;
151 PetscCheck(user != NULL && global != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
152 "Context and field are required.");
153 PetscCheck(components == 1 || components == 3, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE,
154 "ExtendToLayoutBoundary handles 1- or 3-component fields; got %d.", (int)components);
155 simCtx = user->simCtx;
156 dm = (components == 1) ? user->da : user->fda;
157
158 periodic[0] = simCtx->i_periodic;
159 periodic[1] = simCtx->j_periodic;
160 periodic[2] = simCtx->k_periodic;
161 if (!periodic[0] && !periodic[1] && !periodic[2]) {
162 /* Nothing is defined on a non-periodic layout boundary, so nothing is written.
163 * The boundary node keeps the value the producing kernel left there, and the
164 * spatial reductions that matter scientifically never read it. */
166 PetscFunctionReturn(0);
167 }
168
169 PetscCall(DMGetLocalVector(dm, &local));
170 /* One pass per periodic direction, each preceded by its own scatter. The order
171 * matters: after the i pass the i layout boundary carries data, so the j pass
172 * reading across it fills edges correctly, and the k pass then fills corners. */
173 for (PetscInt dir = 0; dir < 3; ++dir) {
174 DMDALocalInfo info;
175 const PetscReal ****source = NULL;
176 PetscReal ****target = NULL;
177 PetscInt extent = 0;
178
179 if (!periodic[dir]) continue;
180
181 PetscCall(DMGlobalToLocalBegin(dm, global, INSERT_VALUES, local));
182 PetscCall(DMGlobalToLocalEnd(dm, global, INSERT_VALUES, local));
183 PetscCall(DMDAGetLocalInfo(dm, &info));
184 extent = (dir == 0) ? info.mx : ((dir == 1) ? info.my : info.mz);
185
186 PetscCall(DMDAVecGetArrayDOFRead(dm, local, &source));
187 PetscCall(DMDAVecGetArrayDOF(dm, global, &target));
188 for (PetscInt k = info.zs; k < info.zs + info.zm; ++k) {
189 for (PetscInt j = info.ys; j < info.ys + info.ym; ++j) {
190 for (PetscInt i = info.xs; i < info.xs + info.xm; ++i) {
191 const PetscInt index = (dir == 0) ? i : ((dir == 1) ? j : k);
192 PetscInt ss = i, sj = j, sk = k;
193
194 /* The layout wraps one plane inside the DMDA extent: the low dummy
195 * plane repeats the last physical plane, and the high dummy plane
196 * repeats the first. PETSc's periodic ghosts supply both sources
197 * even when they live on another rank. */
198 if (index == 0) {
199 if (dir == 0) ss = extent - 2;
200 else if (dir == 1) sj = extent - 2;
201 else sk = extent - 2;
202 } else if (index == extent - 1) {
203 if (dir == 0) ss = extent + 1;
204 else if (dir == 1) sj = extent + 1;
205 else sk = extent + 1;
206 } else {
207 continue;
208 }
209 for (PetscInt c = 0; c < components; ++c) {
210 target[k][j][i][c] = source[sk][sj][ss][c];
211 }
212 }
213 }
214 }
215 PetscCall(DMDAVecRestoreArrayDOF(dm, global, &target));
216 PetscCall(DMDAVecRestoreArrayDOFRead(dm, local, &source));
217 }
218 PetscCall(DMRestoreLocalVector(dm, &local));
219
221 "-> KERNEL: Extended %d-component field across the periodic layout boundary "
222 "(i=%d, j=%d, k=%d).\n", (int)components,
223 (int)periodic[0], (int)periodic[1], (int)periodic[2]);
225 PetscFunctionReturn(0);
226}
@ LOG_DEBUG
Detailed debugging information.
Definition logging.h:32
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:909
PetscInt k_periodic
Definition variables.h:791
PetscInt i_periodic
Definition variables.h:791
PetscInt j_periodic
Definition variables.h:791
The master context for the entire simulation.
Definition variables.h:695
Here is the caller graph for this function:

◆ ComputeQCriterion()

PetscErrorCode ComputeQCriterion ( UserCtx user)

Computes the Q-criterion diagnostic from the local velocity-gradient tensor.

This kernel evaluates rotational versus strain-rate dominance and writes the result into the configured Q-criterion output vector for visualization and flow feature identification.

Parameters
[in,out]userBlock-level context containing velocity fields and target output storage.
Returns
PetscErrorCode 0 on success.

Computes the Q-criterion diagnostic from the local velocity-gradient tensor.

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

See also
ComputeQCriterion()

Definition at line 481 of file postprocessing_kernels.c.

482{
483 PetscErrorCode ierr;
484 DMDALocalInfo info;
485 const Cmpnts ***lucat, ***lcsi, ***leta, ***lzet;
486 const PetscReal***laj, ***lnvert;
487 PetscReal ***gq;
488
489 PetscFunctionBeginUser;
491 LOG_ALLOW(GLOBAL, LOG_INFO, "-> KERNEL: Running ComputeQCriterion.\n");
492
493 // --- 1. Ensure all required ghost values are up-to-date ---
494 ierr = UpdateLocalGhosts(user, FIELD_ID_UCAT); CHKERRQ(ierr);
495 ierr = UpdateLocalGhosts(user, FIELD_ID_CSI); CHKERRQ(ierr);
496 ierr = UpdateLocalGhosts(user, FIELD_ID_ETA); CHKERRQ(ierr);
497 ierr = UpdateLocalGhosts(user, FIELD_ID_ZET); CHKERRQ(ierr);
498 ierr = UpdateLocalGhosts(user, FIELD_ID_AJ); CHKERRQ(ierr);
499 ierr = UpdateLocalGhosts(user, FIELD_ID_NVERT); CHKERRQ(ierr);
500
501 // --- 2. Get DMDA info and array pointers ---
502 ierr = DMDAGetLocalInfo(user->da, &info); CHKERRQ(ierr);
503
504 ierr = DMDAVecGetArrayRead(user->fda, user->lUcat, (void*)&lucat); CHKERRQ(ierr);
505 ierr = DMDAVecGetArrayRead(user->fda, user->lCsi, (void*)&lcsi); CHKERRQ(ierr);
506 ierr = DMDAVecGetArrayRead(user->fda, user->lEta, (void*)&leta); CHKERRQ(ierr);
507 ierr = DMDAVecGetArrayRead(user->fda, user->lZet, (void*)&lzet); CHKERRQ(ierr);
508 ierr = DMDAVecGetArrayRead(user->da, user->lAj, (void*)&laj); CHKERRQ(ierr);
509 ierr = DMDAVecGetArrayRead(user->da, user->lNvert, (void*)&lnvert); CHKERRQ(ierr);
510 ierr = DMDAVecGetArray(user->da, user->Qcrit, (void*)&gq); CHKERRQ(ierr);
511
512 // --- 3. Define Loop Bounds for INTERIOR Cells ---
513 PetscInt i_start = (info.xs == 0) ? 1 : info.xs;
514 PetscInt i_end = (info.xs + info.xm == info.mx) ? info.mx - 1 : info.xs + info.xm;
515 PetscInt j_start = (info.ys == 0) ? 1 : info.ys;
516 PetscInt j_end = (info.ys + info.ym == info.my) ? info.my - 1 : info.ys + info.ym;
517 PetscInt k_start = (info.zs == 0) ? 1 : info.zs;
518 PetscInt k_end = (info.zs + info.zm == info.mz) ? info.mz - 1 : info.zs + info.zm;
519
520 // --- 4. Main Computation Loop ---
521 for (PetscInt k = k_start; k < k_end; k++) {
522 for (PetscInt j = j_start; j < j_end; j++) {
523 for (PetscInt i = i_start; i < i_end; i++) {
524
525 // Calculate velocity derivatives in computational space (central differences)
526 PetscReal uc = 0.5 * (lucat[k][j][i+1].x - lucat[k][j][i-1].x);
527 PetscReal vc = 0.5 * (lucat[k][j][i+1].y - lucat[k][j][i-1].y);
528 PetscReal wc = 0.5 * (lucat[k][j][i+1].z - lucat[k][j][i-1].z);
529
530 PetscReal ue = 0.5 * (lucat[k][j+1][i].x - lucat[k][j-1][i].x);
531 PetscReal ve = 0.5 * (lucat[k][j+1][i].y - lucat[k][j-1][i].y);
532 PetscReal we = 0.5 * (lucat[k][j+1][i].z - lucat[k][j-1][i].z);
533
534 PetscReal uz = 0.5 * (lucat[k+1][j][i].x - lucat[k-1][j][i].x);
535 PetscReal vz = 0.5 * (lucat[k+1][j][i].y - lucat[k-1][j][i].y);
536 PetscReal wz = 0.5 * (lucat[k+1][j][i].z - lucat[k-1][j][i].z);
537
538 // Average metrics to the cell center
539 PetscReal csi1 = 0.5 * (lcsi[k][j][i].x + lcsi[k][j][i-1].x) * laj[k][j][i];
540 PetscReal csi2 = 0.5 * (lcsi[k][j][i].y + lcsi[k][j][i-1].y) * laj[k][j][i];
541 PetscReal csi3 = 0.5 * (lcsi[k][j][i].z + lcsi[k][j][i-1].z) * laj[k][j][i];
542
543 PetscReal eta1 = 0.5 * (leta[k][j][i].x + leta[k][j-1][i].x) * laj[k][j][i];
544 PetscReal eta2 = 0.5 * (leta[k][j][i].y + leta[k][j-1][i].y) * laj[k][j][i];
545 PetscReal eta3 = 0.5 * (leta[k][j][i].z + leta[k][j-1][i].z) * laj[k][j][i];
546
547 PetscReal zet1 = 0.5 * (lzet[k][j][i].x + lzet[k-1][j][i].x) * laj[k][j][i];
548 PetscReal zet2 = 0.5 * (lzet[k][j][i].y + lzet[k-1][j][i].y) * laj[k][j][i];
549 PetscReal zet3 = 0.5 * (lzet[k][j][i].z + lzet[k-1][j][i].z) * laj[k][j][i];
550
551 // Calculate velocity gradient tensor components d_ij = du_i/dx_j
552 PetscReal d11 = uc * csi1 + ue * eta1 + uz * zet1;
553 PetscReal d12 = uc * csi2 + ue * eta2 + uz * zet2;
554 PetscReal d13 = uc * csi3 + ue * eta3 + uz * zet3;
555
556 PetscReal d21 = vc * csi1 + ve * eta1 + vz * zet1;
557 PetscReal d22 = vc * csi2 + ve * eta2 + vz * zet2;
558 PetscReal d23 = vc * csi3 + ve * eta3 + vz * zet3;
559
560 PetscReal d31 = wc * csi1 + we * eta1 + wz * zet1;
561 PetscReal d32 = wc * csi2 + we * eta2 + wz * zet2;
562 PetscReal d33 = wc * csi3 + we * eta3 + wz * zet3;
563
564 // Strain-Rate Tensor S_ij = 0.5 * (d_ij + d_ji)
565 PetscReal s11 = d11;
566 PetscReal s12 = 0.5 * (d12 + d21);
567 PetscReal s13 = 0.5 * (d13 + d31);
568 PetscReal s22 = d22;
569 PetscReal s23 = 0.5 * (d23 + d32);
570 PetscReal s33 = d33;
571
572 // Vorticity Tensor Omega_ij = 0.5 * (d_ij - d_ji)
573 PetscReal w12 = 0.5 * (d12 - d21);
574 PetscReal w13 = 0.5 * (d13 - d31);
575 PetscReal w23 = 0.5 * (d23 - d32);
576
577 // Squared norms of the tensors
578 PetscReal s_norm_sq = s11*s11 + s22*s22 + s33*s33 + 2.0*(s12*s12 + s13*s13 + s23*s23);
579 PetscReal w_norm_sq = 2.0 * (w12*w12 + w13*w13 + w23*w23);
580
581 gq[k][j][i] = 0.5 * (w_norm_sq - s_norm_sq);
582
583 if (lnvert[k][j][i] > 0.1) {
584 gq[k][j][i] = 0.0;
585 }
586 }
587 }
588 }
589
590 // --- 5. Restore arrays ---
591 ierr = DMDAVecRestoreArrayRead(user->fda, user->lUcat, (void*)&lucat); CHKERRQ(ierr);
592 ierr = DMDAVecRestoreArrayRead(user->fda, user->lCsi, (void*)&lcsi); CHKERRQ(ierr);
593 ierr = DMDAVecRestoreArrayRead(user->fda, user->lEta, (void*)&leta); CHKERRQ(ierr);
594 ierr = DMDAVecRestoreArrayRead(user->fda, user->lZet, (void*)&lzet); CHKERRQ(ierr);
595 ierr = DMDAVecRestoreArrayRead(user->da, user->lAj, (void*)&laj); CHKERRQ(ierr);
596 ierr = DMDAVecRestoreArrayRead(user->da, user->lNvert, (void*)&lnvert); CHKERRQ(ierr);
597 ierr = DMDAVecRestoreArray(user->da, user->Qcrit, (void*)&gq); CHKERRQ(ierr);
598
599 /* The loop above skips the layout boundary, so extend it before anything reads
600 * Qcrit with a stencil. Q is not a moment: it does not vanish at a wall, so only
601 * the periodic case is defined and only that case is written. */
602 ierr = ExtendToLayoutBoundary(user, user->Qcrit, 1); CHKERRQ(ierr);
603
605 PetscFunctionReturn(0);
606}
@ FIELD_ID_CSI
@ FIELD_ID_NVERT
@ FIELD_ID_UCAT
@ FIELD_ID_AJ
@ FIELD_ID_ETA
@ FIELD_ID_ZET
PetscErrorCode ExtendToLayoutBoundary(UserCtx *user, Vec global, PetscInt components)
Implementation of ExtendToLayoutBoundary().
Vec lNvert
Definition variables.h:939
Vec lZet
Definition variables.h:974
Vec Qcrit
Definition variables.h:1003
Vec lCsi
Definition variables.h:974
Vec lAj
Definition variables.h:974
Vec lEta
Definition variables.h:974
Here is the call graph for this function:
Here is the caller graph for this function:

◆ NormalizeRelativeField()

PetscErrorCode NormalizeRelativeField ( UserCtx user,
const char *  relative_field_name 
)

Normalizes pressure using the value at the configured logical grid point.

The owning rank reads the reference value, shares it collectively, and every rank subtracts it in-place from its distributed portion of the field.

Parameters
[in,out]userBlock-level context containing pressure and reference configuration.
[in]relative_field_nameName of the field to normalize.
Returns
PetscErrorCode 0 on success.

Normalizes pressure using the value at the configured logical grid point.

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

See also
NormalizeRelativeField()

Definition at line 616 of file postprocessing_kernels.c.

617{
618 PetscErrorCode ierr;
619 Vec P_vec = NULL;
620 DMDALocalInfo info;
621 PetscInt ip=1, jp=1, kp=1; // Default reference point
622 PetscReal p_ref = 0.0;
623 PetscReal p_ref_local = 0.0;
624 PetscInt found_local = 0, found_global = 0;
625 PostProcessParams *pps = user->simCtx->pps;
626
627 // Fetch the logical reference point from pps.
628 ip = pps->reference[0];
629 jp = pps->reference[1];
630 kp = pps->reference[2];
631
632 PetscFunctionBeginUser;
634 LOG_ALLOW(GLOBAL, LOG_INFO, "-> KERNEL: Running NormalizeRelativeField on '%s'.\n", relative_field_name);
635
636 // --- 1. Map string argument to the PETSc Vec ---
637 if (strcasecmp(relative_field_name, "P") == 0) {
638 P_vec = user->P;
639 } else {
640 SETERRQ(PETSC_COMM_SELF, 1, "NormalizeRelativeField only supports the primary 'P' field , not '%s' currently.", relative_field_name);
641 }
642
643 // --- 2. Read the logical reference point from whichever rank owns it ---
644 ierr = DMDAGetLocalInfo(user->da, &info); CHKERRQ(ierr);
645 PetscCheck(ip >= 0 && ip < info.mx && jp >= 0 && jp < info.my &&
646 kp >= 0 && kp < info.mz,
647 PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE,
648 "Reference point (%" PetscInt_FMT ", %" PetscInt_FMT ", %" PetscInt_FMT
649 ") lies outside the %" PetscInt_FMT "x%" PetscInt_FMT "x%" PetscInt_FMT
650 " pressure layout.", ip, jp, kp, info.mx, info.my, info.mz);
651 if (ip >= info.xs && ip < info.xs + info.xm &&
652 jp >= info.ys && jp < info.ys + info.ym &&
653 kp >= info.zs && kp < info.zs + info.zm) {
654 const PetscReal ***pressure = NULL;
655
656 ierr = DMDAVecGetArrayRead(user->da, P_vec, &pressure); CHKERRQ(ierr);
657 p_ref_local = pressure[kp][jp][ip];
658 ierr = DMDAVecRestoreArrayRead(user->da, P_vec, &pressure); CHKERRQ(ierr);
659 found_local = 1;
660 }
661 ierr = MPI_Allreduce(&p_ref_local, &p_ref, 1, MPIU_REAL, MPI_SUM,
662 PETSC_COMM_WORLD); CHKERRQ(ierr);
663 ierr = MPI_Allreduce(&found_local, &found_global, 1, MPIU_INT, MPI_SUM,
664 PETSC_COMM_WORLD); CHKERRQ(ierr);
665 PetscCheck(found_global == 1, PETSC_COMM_WORLD, PETSC_ERR_PLIB,
666 "Reference pressure point must have exactly one owner; found %" PetscInt_FMT ".",
667 found_global);
669 "%s reference point (%" PetscInt_FMT ", %" PetscInt_FMT ", %" PetscInt_FMT
670 ") has value %g.\n", relative_field_name, ip, jp, kp, (double)p_ref);
671
672 // --- 3. Perform the normalization (in-place shift) on the full distributed vector ---
673 ierr = VecShift(P_vec, -p_ref); CHKERRQ(ierr);
674 LOG_ALLOW(GLOBAL, LOG_DEBUG, "%s field normalized by subtracting %g.\n", relative_field_name, p_ref);
675
677 PetscFunctionReturn(0);
678}
PetscInt reference[3]
Definition variables.h:634
PostProcessParams * pps
Definition variables.h:890
Holds all configuration parameters for a post-processing run.
Definition variables.h:596
Here is the caller graph for this function:

◆ DimensionalizeField()

PetscErrorCode DimensionalizeField ( UserCtx user,
const char *  field_name 
)

Scales a specified field from non-dimensional to dimensional units in-place.

This function acts as a dispatcher. It takes the string name of a field, identifies the corresponding PETSc Vec object and the correct physical scaling factor (e.g., U_ref for velocity, P_ref for pressure), and then performs an in-place VecScale operation. It correctly handles the different physical dimensions of Cartesian velocity vs. contravariant volume flux.

Parameters
[in,out]userThe UserCtx containing the PETSc Vecs to be modified.
[in]field_nameThe case-insensitive string name of the field to dimensionalize (e.g., "Ucat", "P", "Ucont", "Coordinates", "ParticlePosition", "ParticleVelocity").
Returns
PetscErrorCode

Scales a specified field from non-dimensional to dimensional units in-place.

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

See also
DimensionalizeField()

Definition at line 15 of file postprocessing_kernels.c.

16{
17 PetscErrorCode ierr;
18 SimCtx *simCtx = user->simCtx;
19 Vec target_vec = NULL;
20 PetscReal scale_factor = 1.0;
21 char field_type[64] = "Unknown";
22 PetscBool is_swarm_field = PETSC_FALSE; // Flag for special swarm handling
23 const char *swarm_field_name = NULL; // Name of the field within the swarm
24
25 PetscFunctionBeginUser;
27 if (!user) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "UserCtx is NULL.");
28 if (!field_name) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "field_name is NULL.");
29
30 // --- 1. Identify the target Vec and the correct scaling factor ---
31 if (strcasecmp(field_name, "Ucat") == 0) {
32 target_vec = user->Ucat;
33 scale_factor = simCtx->scaling.U_ref;
34 strcpy(field_type, "Cartesian Velocity (L/T)");
35 } else if (strcasecmp(field_name, "Ucont") == 0) {
36 target_vec = user->Ucont;
37 scale_factor = simCtx->scaling.U_ref * simCtx->scaling.L_ref * simCtx->scaling.L_ref;
38 strcpy(field_type, "Contravariant Volume Flux (L^3/T)");
39 } else if (strcasecmp(field_name, "P") == 0) {
40 target_vec = user->P;
41 scale_factor = simCtx->scaling.P_ref;
42 strcpy(field_type, "Pressure (M L^-1 T^-2)");
43 } else if (strcasecmp(field_name, "Coordinates") == 0) {
44 ierr = DMGetCoordinates(user->da, &target_vec); CHKERRQ(ierr);
45 scale_factor = simCtx->scaling.L_ref;
46 strcpy(field_type, "Grid Coordinates (L)");
47 } else if (strcasecmp(field_name, "ParticlePosition") == 0) {
48 is_swarm_field = PETSC_TRUE;
49 swarm_field_name = "position";
50 scale_factor = simCtx->scaling.L_ref;
51 strcpy(field_type, "Particle Position (L)");
52 } else if (strcasecmp(field_name, "ParticleVelocity") == 0) {
53 is_swarm_field = PETSC_TRUE;
54 swarm_field_name = "velocity";
55 scale_factor = simCtx->scaling.U_ref;
56 strcpy(field_type, "Particle Velocity (L/T)");
57 } else {
58 LOG(GLOBAL, LOG_WARNING, "DimensionalizeField: Unknown or unhandled field_name '%s'. Field will not be scaled.\n", field_name);
60 PetscFunctionReturn(0);
61 }
62
63 // --- 2. Check for trivial scaling ---
64 if (PetscAbsReal(scale_factor - 1.0) < PETSC_MACHINE_EPSILON) {
65 LOG(GLOBAL, LOG_DEBUG, "DimensionalizeField: Scaling factor for '%s' is 1.0. Skipping operation.\n", field_name);
67 PetscFunctionReturn(0);
68 }
69
70 // --- 3. Perform the in-place scaling operation ---
71 LOG(GLOBAL, LOG_INFO, "Scaling '%s' field (%s) by factor %.4e.\n", field_name, field_type, scale_factor);
72
73 if (is_swarm_field) {
74 // Special handling for DMSwarm fields
75 ierr = DMSwarmCreateGlobalVectorFromField(user->swarm, swarm_field_name, &target_vec); CHKERRQ(ierr);
76 ierr = VecScale(target_vec, scale_factor); CHKERRQ(ierr);
77 ierr = DMSwarmDestroyGlobalVectorFromField(user->swarm, swarm_field_name, &target_vec); CHKERRQ(ierr);
78 } else {
79 // Standard handling for PETSc Vecs
80 if (target_vec) {
81 ierr = VecScale(target_vec, scale_factor); CHKERRQ(ierr);
82 } else {
83 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONGSTATE, "Target vector for field '%s' was not found or is NULL.", field_name);
84 }
85 }
86
87 // --- 4. Post-scaling updates for special cases ---
88 if (strcasecmp(field_name, "Coordinates") == 0) {
89 ierr = UpdateLocalGhosts(user, FIELD_ID_COORDINATES); CHKERRQ(ierr);
90 }
91
93 PetscFunctionReturn(0);
94}
@ FIELD_ID_COORDINATES
#define LOG(scope, level, fmt,...)
Logging macro for PETSc-based applications with scope control.
Definition logging.h:84
@ LOG_WARNING
Non-critical issues that warrant attention.
Definition logging.h:30
PetscReal L_ref
Definition variables.h:677
Vec Ucont
Definition variables.h:939
Vec Ucat
Definition variables.h:939
ScalingCtx scaling
Definition variables.h:785
PetscReal P_ref
Definition variables.h:680
PetscReal U_ref
Definition variables.h:678
Here is the call graph for this function:
Here is the caller graph for this function:

◆ DimensionalizeAllLoadedFields()

PetscErrorCode DimensionalizeAllLoadedFields ( UserCtx user)

Orchestrates the dimensionalization of all relevant fields loaded from a file.

This function is intended to be called in the post-processor immediately after all solver output has been read into memory. It calls DimensionalizeField() for each of the core physical quantities to convert the entire loaded state from non-dimensional to dimensional units, preparing it for analysis and visualization.

Parameters
[in,out]userThe UserCtx containing all the fields to be dimensionalized.
Returns
PetscErrorCode

Orchestrates the dimensionalization of all relevant fields loaded from a file.

Local to this translation unit.

Definition at line 102 of file postprocessing_kernels.c.

103{
104 PetscErrorCode ierr;
105 SimCtx *simCtx = user->simCtx;
106
107 PetscFunctionBeginUser;
109
110 LOG(GLOBAL, LOG_INFO, "--- Converting all loaded fields to dimensional units ---\n");
111
112 // Scale the grid itself first
113 ierr = DimensionalizeField(user, "Coordinates"); CHKERRQ(ierr);
114
115 // Scale primary fluid fields
116 ierr = DimensionalizeField(user, "Ucat"); CHKERRQ(ierr);
117 ierr = DimensionalizeField(user, "Ucont"); CHKERRQ(ierr);
118 ierr = DimensionalizeField(user, "P"); CHKERRQ(ierr);
119
120 // If particles are present, scale their fields
121 if (simCtx->np > 0 && user->swarm) {
122 ierr = DimensionalizeField(user, "ParticlePosition"); CHKERRQ(ierr);
123 ierr = DimensionalizeField(user, "ParticleVelocity"); CHKERRQ(ierr);
124 }
125
126 LOG(GLOBAL, LOG_INFO, "--- Field dimensionalization complete ---\n");
127
129 PetscFunctionReturn(0);
130}
PetscErrorCode DimensionalizeField(UserCtx *user, const char *field_name)
Implementation of DimensionalizeField().
PetscInt np
Definition variables.h:827
Here is the call graph for this function:
Here is the caller graph for this function:

◆ ComputeSpecificKE()

PetscErrorCode ComputeSpecificKE ( UserCtx user,
const char *  velocity_field,
const char *  ske_field 
)

Computes the specific kinetic energy (KE per unit mass) for each particle.

This kernel calculates SKE = 0.5 * |velocity|^2. It requires that the velocity field exists and will populate the specific kinetic energy field. The output field must be registered before this kernel is called.

Parameters
userThe UserCtx containing the DMSwarm.
velocity_fieldThe name of the input vector field for particle velocity.
ske_fieldThe name of the output scalar field to store specific KE.
Returns
PetscErrorCode

Computes the specific kinetic energy (KE per unit mass) for each particle.

Local to this translation unit.

Definition at line 689 of file postprocessing_kernels.c.

690{
691 PetscErrorCode ierr;
692 PetscInt n_local;
693 const PetscScalar (*vel_arr)[3]; // Access velocity as array of 3-component vectors
694 PetscScalar *ske_arr;
695
696 PetscFunctionBeginUser;
698 LOG_ALLOW(GLOBAL, LOG_INFO, "-> KERNEL: Running ComputeSpecificKE ('%s' -> '%s').\n", velocity_field, ske_field);
699
700 // Get local data arrays from the DMSwarm
701 ierr = DMSwarmGetLocalSize(user->swarm, &n_local); CHKERRQ(ierr);
702 if (n_local == 0) { PROFILE_FUNCTION_END; PetscFunctionReturn(0); }
703
704 // Get read-only access to velocity and write access to the output field
705 ierr = DMSwarmGetField(user->swarm, velocity_field, NULL, NULL, (void**)&vel_arr); CHKERRQ(ierr);
706 ierr = DMSwarmGetField(user->post_swarm, ske_field, NULL, NULL, (void**)&ske_arr); CHKERRQ(ierr);
707
708 // Main computation loop
709 for (PetscInt p = 0; p < n_local; p++) {
710 const PetscScalar u = vel_arr[p][0];
711 const PetscScalar v = vel_arr[p][1];
712 const PetscScalar w = vel_arr[p][2];
713 const PetscScalar vel_sq = u*u + v*v + w*w;
714 ske_arr[p] = 0.5 * vel_sq;
715 }
716
717 // Restore arrays
718 ierr = DMSwarmRestoreField(user->swarm, velocity_field, NULL, NULL, (void**)&vel_arr); CHKERRQ(ierr);
719 ierr = DMSwarmRestoreField(user->post_swarm, ske_field, NULL, NULL, (void**)&ske_arr); CHKERRQ(ierr);
720
722 PetscFunctionReturn(0);
723}
DM post_swarm
Definition variables.h:1000
Here is the caller graph for this function:

◆ ComputeDisplacement()

PetscErrorCode ComputeDisplacement ( UserCtx user,
const char *  disp_field 
)

Computes the displacement magnitude |r_i - r_0| for each particle (per-particle VTK kernel).

Reference point r_0 = (simCtx->psrc_x, psrc_y, psrc_z). Writes the scalar displacement to post_swarm[disp_field]. This is a visualisation kernel only — use ComputeParticleMSD from particle_statistics.h for quantitative global statistics.

Parameters
userThe UserCtx containing the DMSwarms.
disp_fieldName of the output scalar field in post_swarm.
Returns
PetscErrorCode

Computes the displacement magnitude |r_i - r_0| for each particle (per-particle VTK kernel).

Local to this translation unit.

Definition at line 731 of file postprocessing_kernels.c.

732{
733 PetscErrorCode ierr;
734 PetscInt n_local;
735 const PetscReal (*pos_arr)[3];
736 PetscScalar *disp_out;
737 SimCtx *simCtx = user->simCtx;
738
739 PetscFunctionBeginUser;
741 LOG_ALLOW(GLOBAL, LOG_INFO, "-> KERNEL: Running ComputeDisplacement (-> '%s').\n", disp_field);
742
743 ierr = DMSwarmGetLocalSize(user->swarm, &n_local); CHKERRQ(ierr);
744 if (n_local == 0) { PROFILE_FUNCTION_END; PetscFunctionReturn(0); }
745
746 const PetscReal x0 = simCtx->psrc_x;
747 const PetscReal y0 = simCtx->psrc_y;
748 const PetscReal z0 = simCtx->psrc_z;
749
750 ierr = DMSwarmGetField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&pos_arr); CHKERRQ(ierr);
751 ierr = DMSwarmGetField(user->post_swarm, disp_field, NULL, NULL, (void**)&disp_out); CHKERRQ(ierr);
752
753 for (PetscInt p = 0; p < n_local; p++) {
754 const PetscReal dx = pos_arr[p][0] - x0;
755 const PetscReal dy = pos_arr[p][1] - y0;
756 const PetscReal dz = pos_arr[p][2] - z0;
757 disp_out[p] = PetscSqrtReal(dx*dx + dy*dy + dz*dz);
758 }
759
760 ierr = DMSwarmRestoreField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&pos_arr); CHKERRQ(ierr);
761 ierr = DMSwarmRestoreField(user->post_swarm, disp_field, NULL, NULL, (void**)&disp_out); CHKERRQ(ierr);
762
764 PetscFunctionReturn(0);
765}
const char * ParticleFieldName(ParticleFieldId field_id)
Return the canonical PETSc DMSwarm name for an ID.
@ PARTICLE_FIELD_ID_POSITION
PetscReal psrc_x
Definition variables.h:784
PetscReal psrc_z
Point source location for PARTICLE_INIT_POINT_SOURCE.
Definition variables.h:784
PetscReal psrc_y
Definition variables.h:784
Here is the call graph for this function:
Here is the caller graph for this function:

◆ ComputeWindowStatisticNodal()

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.

The counterpart of ComputeNodalAverage() for accumulated window state: it takes an index into a window's requested output set, normalizes the centered state behind it, and leaves the result in the shared post-processing staging field ready for output. The staging vectors are reused between calls, so a caller must consume the result before requesting the next one.

Derived statistics are left non-dimensional even when global_operations.dimensionalize is set. A Reynolds stress scales as velocity squared and a co-moment as a product of two different scales, none of which the existing per-field scaling table expresses; silently applying a velocity scale would be wrong rather than merely incomplete.

Parameters
[in]userBlock context holding the accumulators and staging fields.
[in]window_indexWindow whose state is derived.
[in]outputsComma-separated output kinds the recipe requested.
[in]output_indexIndex into that output set.
[out]out_nameName of the derived field, window qualified.
[in]name_sizeCapacity of out_name.
[out]out_vecNodal vector holding the result; borrowed, not owned.
[out]out_componentsComponent count of the result.
Returns
Zero on success, or a PETSc error.

Derives one accumulated statistic and converts it to nodal values.

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

See also
ComputeWindowStatisticNodal()

Definition at line 343 of file postprocessing_kernels.c.

347{
348 PetscErrorCode ierr;
349 SimCtx *simCtx = NULL;
350 PicurvDerivedField derived;
351 const PicurvWindowDefinition *definition = NULL;
352
353 PetscFunctionBeginUser;
355 PetscCheck(user != NULL && out_name != NULL && out_vec != NULL && out_components != NULL,
356 PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Context and outputs are required.");
357 simCtx = user->simCtx;
358 PetscCheck(FieldStatisticsIsActive(simCtx) && user->fieldStatisticsStorage != NULL,
359 PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE,
360 "No accumulated window state exists to derive.");
361 definition = &simCtx->fieldStatisticsWindows[window_index].definition;
362
363 /* Derive into the cell-centred staging pair, then reach the nodal path by
364 * catalogued name. A per-window accumulator has no compile-time offset, so
365 * staging is what lets the shared ghost and nodal kernels address it at all. */
366 ierr = PicurvWindowDerive(user, definition, &user->fieldStatisticsStorage[window_index],
367 outputs, output_index, user->PostScalar, user->PostVector,
368 &derived); CHKERRQ(ierr);
369 /* The derivation writes the physical interior only, so the layout boundary would
370 * otherwise reach the nodal average as structural zeros. Extend it before the
371 * ghost update, which then carries the written values outward. */
372 if (derived.components == 1) {
373 ierr = ExtendToLayoutBoundary(user, user->PostScalar, 1); CHKERRQ(ierr);
374 ierr = UpdateLocalGhosts(user, FIELD_ID_POST_SCALAR); CHKERRQ(ierr);
375 ierr = ComputeNodalAverage(user, "PostScalar", "PostScalarNodal"); CHKERRQ(ierr);
376 *out_vec = user->PostScalarNodal;
377 } else {
378 ierr = ExtendToLayoutBoundary(user, user->PostVector, 3); CHKERRQ(ierr);
379 ierr = UpdateLocalGhosts(user, FIELD_ID_POST_VECTOR); CHKERRQ(ierr);
380 ierr = ComputeNodalAverage(user, "PostVector", "PostVectorNodal"); CHKERRQ(ierr);
381 *out_vec = user->PostVectorNodal;
382 }
383 *out_components = derived.components;
384 ierr = PetscStrncpy(out_name, derived.name, name_size); CHKERRQ(ierr);
385
386 LOG_ALLOW(GLOBAL, LOG_DEBUG, "-> KERNEL: Derived '%s' (%d component(s)).\n",
387 out_name, (int)derived.components);
389 PetscFunctionReturn(0);
390}
@ FIELD_ID_POST_VECTOR
@ FIELD_ID_POST_SCALAR
PetscErrorCode ComputeNodalAverage(UserCtx *user, const char *in_field_name, const char *out_field_name)
Implementation of ComputeNodalAverage().
PetscInt components
One or three.
char name[96]
Output field name, window qualified.
PetscErrorCode PicurvWindowDerive(UserCtx *user, const PicurvWindowDefinition *definition, const PicurvWindowStorage *storage, const char *outputs, PetscInt index, Vec scalar_target, Vec vector_target, PicurvDerivedField *field)
Derives one output field from centered accumulator state.
One derived output field, resolved by enumeration index.
PicurvWindowDefinition definition
PetscBool FieldStatisticsIsActive(const struct SimCtx *simCtx)
Reports whether this run has live field-statistics state.
The scientifically immutable definition of one window.
Vec PostScalar
Definition variables.h:959
struct PicurvWindow * fieldStatisticsWindows
Definition variables.h:771
struct PicurvWindowStorage * fieldStatisticsStorage
Definition variables.h:962
Vec PostVector
Definition variables.h:960
Here is the call graph for this function:
Here is the caller graph for this function:

◆ ComputeWindowStatisticsSummary()

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.

The counterpart of ComputeParticleMSD() for Eulerian window state: it reduces the window to the few numbers that answer whether it has run long enough — sample count, total weight, represented time, the per-point valid-fraction range, and the mean turbulent kinetic energy — and appends them as one row per processed step. No single field snapshot can answer that question, which is why the history exists beside the field output rather than instead of it.

The mean is taken over the fluid cells the window actually sampled. Cells outside the target domain, and cells a moving mask excluded, hold zeros that mean "never measured"; averaging over them would scale the result down by the fraction of the grid the window never covered.

Parameters
[in]userBlock context holding the accumulators and staging fields.
[in]window_indexWindow to summarize.
[in]output_prefixOutput path prefix; the window name and .csv are appended.
[in]tiStep being processed, recorded as the row's key.
Returns
Zero on success, or a PETSc error.

Appends one convergence row for an accumulated window to its CSV history.

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

See also
ComputeWindowStatisticsSummary()

Definition at line 400 of file postprocessing_kernels.c.

402{
403 PetscErrorCode ierr;
404 SimCtx *simCtx = NULL;
405 const PicurvWindow *window = NULL;
406 const PicurvWindowStorage *storage = NULL;
407 PetscReal lowest = 1.0, highest = 0.0;
408 PetscReal mean_tke = 0.0;
409 PetscBool has_tke = PETSC_FALSE;
410 PetscInt derived_count = 0;
411 char path[PETSC_MAX_PATH_LEN];
412
413 PetscFunctionBeginUser;
415 PetscCheck(user != NULL && output_prefix != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
416 "Context and output prefix are required.");
417 simCtx = user->simCtx;
418 PetscCheck(FieldStatisticsIsActive(simCtx) && user->fieldStatisticsStorage != NULL,
419 PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE,
420 "No accumulated window state exists to summarize.");
421 window = &simCtx->fieldStatisticsWindows[window_index];
422 storage = &user->fieldStatisticsStorage[window_index];
423
424 ierr = PicurvWindowValidFractionRange(user, &window->definition, storage,
425 window->sample_count, &lowest, &highest); CHKERRQ(ierr);
426
427 /* A single domain number is what makes the row plottable against step. It is
428 * produced by the same derivation the field output uses, so the scalar and the
429 * field can never disagree about what the window holds. */
430 ierr = PicurvWindowDerivedCount(&window->definition, storage, "tke", &derived_count); CHKERRQ(ierr);
431 if (derived_count > 0) {
432 PicurvDerivedField derived;
433
434 ierr = PicurvWindowDerive(user, &window->definition, storage, "tke", 0,
435 user->PostScalar, user->PostVector, &derived); CHKERRQ(ierr);
436 /* Averaged over the fluid cells the window actually sampled. A whole-vector
437 * mean would divide by boundary and dummy entries the derivation never
438 * writes, scaling the answer down by the fraction it never covered. */
439 ierr = PicurvWindowSpatialMean(user, &window->definition, storage,
440 user->PostScalar, &mean_tke); CHKERRQ(ierr);
441 has_tke = PETSC_TRUE;
442 }
443
444 if (simCtx->rank == 0) {
445 FILE *csv = NULL;
446 PetscBool exists = PETSC_FALSE;
447
448 ierr = PetscSNPrintf(path, sizeof(path), "%s_statistics_%s.csv",
449 output_prefix, window->definition.name); CHKERRQ(ierr);
450 ierr = PetscTestFile(path, 'r', &exists); CHKERRQ(ierr);
451 csv = fopen(path, exists ? "a" : "w");
452 PetscCheck(csv != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN,
453 "Unable to open statistics summary '%s'.", path);
454 if (!exists) {
455 fprintf(csv, "step,state,samples,total_weight,represented_time,"
456 "valid_fraction_min,valid_fraction_max,mean_tke\n");
457 }
458 fprintf(csv, "%" PetscInt_FMT ",%s,%d,%.10e,%.10e,%.6f,%.6f,",
459 ti, PicurvWindowStateName(window->state), window->sample_count,
460 (double)window->total_weight, (double)window->represented_time,
461 (double)lowest, (double)highest);
462 if (has_tke) fprintf(csv, "%.10e\n", (double)mean_tke);
463 else fprintf(csv, "\n");
464 PetscCheck(fclose(csv) == 0, PETSC_COMM_SELF, PETSC_ERR_FILE_WRITE,
465 "Unable to close statistics summary '%s'.", path);
466 }
467 LOG_ALLOW(GLOBAL, LOG_DEBUG, "-> KERNEL: Summarized window '%s' at step %" PetscInt_FMT ".\n",
468 window->definition.name, ti);
470 PetscFunctionReturn(0);
471}
PetscErrorCode PicurvWindowDerivedCount(const PicurvWindowDefinition *definition, const PicurvWindowStorage *storage, const char *outputs, PetscInt *count)
Reports how many derived fields a requested output set produces.
PetscErrorCode PicurvWindowValidFractionRange(UserCtx *user, const PicurvWindowDefinition *definition, const PicurvWindowStorage *storage, PetscInt sample_count, PetscReal *minimum, PetscReal *maximum)
Reports the range of per-point valid fraction across a window's domain.
PetscErrorCode PicurvWindowSpatialMean(UserCtx *user, const PicurvWindowDefinition *definition, const PicurvWindowStorage *storage, Vec field, PetscReal *mean)
Reports the spatial mean of a derived field over the points a window sampled.
Independent accumulator state for one window on one block.
PetscInt sample_count
PicurvWindowState state
PetscReal total_weight
const char * PicurvWindowStateName(PicurvWindowState state)
Returns a stable human-readable name for a window state.
PetscReal represented_time
Physical time the window covers.
Runtime state of one window.
PetscMPIInt rank
Definition variables.h:698
Here is the call graph for this function:
Here is the caller graph for this function: