PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
postprocessing_kernels.c
Go to the documentation of this file.
3#include "statistics_window.h"
5
6// =========== Dimensionalization Kernels ========================
7#undef __FUNCT__
8#define __FUNCT__ "DimensionalizeField"
9/**
10 * @brief Implementation of \ref DimensionalizeField().
11 * @details Full API contract (arguments, ownership, side effects) is documented with
12 * the header declaration in `include/postprocessing_kernels.h`.
13 * @see DimensionalizeField()
14 */
15PetscErrorCode DimensionalizeField(UserCtx *user, const char *field_name)
16{
17 PetscErrorCode ierr;
18 SimCtx *simCtx = NULL;
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 FieldId field_id = FIELD_ID_INVALID;
26 PetscBool found = PETSC_FALSE;
27
28 PetscFunctionBeginUser;
30 if (!user) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "UserCtx is NULL.");
31 if (!field_name) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "field_name is NULL.");
32 simCtx = user->simCtx;
33
34 // --- 1. Resolve the field in either catalog; its entry records its dimension ---
35 ierr = FieldTryIdFromName(field_name, &field_id, &found); CHKERRQ(ierr);
36 if (found && field_id == FIELD_ID_COORDINATES) {
37 ierr = DMGetCoordinates(user->da, &target_vec); CHKERRQ(ierr);
38 } else if (found) {
39 FieldView view;
40
41 ierr = FieldGetView(user, field_id, &view); CHKERRQ(ierr);
42 target_vec = view.global_vec;
43 } else {
44 const ParticleFieldDescriptor *descriptor = NULL;
45
46 ierr = ParticleFieldTryIdFromName(field_name, &particle_id, &found); CHKERRQ(ierr);
47 PetscCheck(found, PETSC_COMM_SELF, PETSC_ERR_ARG_UNKNOWN_TYPE,
48 "DimensionalizeField: '%s' is in neither field catalog.", field_name);
49 ierr = ParticleFieldGetDescriptor(particle_id, &descriptor); CHKERRQ(ierr);
50 is_swarm_field = PETSC_TRUE;
51 swarm_field_name = descriptor->canonical_name;
52 }
53 ierr = PicurvFieldReferenceScale(simCtx, field_name, &scale_factor,
54 field_type, sizeof(field_type)); CHKERRQ(ierr);
55
56 // --- 2. Check for trivial scaling ---
57 if (PetscAbsReal(scale_factor - 1.0) < PETSC_MACHINE_EPSILON) {
58 LOG(GLOBAL, LOG_DEBUG, "DimensionalizeField: Scaling factor for '%s' is 1.0. Skipping operation.\n", field_name);
60 PetscFunctionReturn(0);
61 }
62
63 // --- 3. Perform the in-place scaling operation ---
64 LOG(GLOBAL, LOG_INFO, "Scaling '%s' field (%s) by factor %.4e.\n", field_name, field_type, scale_factor);
65
66 if (is_swarm_field) {
67 // Special handling for DMSwarm fields
68 ierr = DMSwarmCreateGlobalVectorFromField(user->swarm, swarm_field_name, &target_vec); CHKERRQ(ierr);
69 ierr = VecScale(target_vec, scale_factor); CHKERRQ(ierr);
70 ierr = DMSwarmDestroyGlobalVectorFromField(user->swarm, swarm_field_name, &target_vec); CHKERRQ(ierr);
71 } else {
72 // Standard handling for PETSc Vecs
73 if (target_vec) {
74 ierr = VecScale(target_vec, scale_factor); CHKERRQ(ierr);
75 } else {
76 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONGSTATE, "Target vector for field '%s' was not found or is NULL.", field_name);
77 }
78 }
79
80 // --- 4. Post-scaling updates for special cases ---
81 if (strcasecmp(field_name, "Coordinates") == 0) {
82 ierr = UpdateLocalGhosts(user, FIELD_ID_COORDINATES); CHKERRQ(ierr);
83 }
84
86 PetscFunctionReturn(0);
87}
88
89//============ Post-Processing Kernels ===========================
90
91#undef __FUNCT__
92#define __FUNCT__ "ComputeNodalAverage"
93/**
94 * @brief Implementation of \ref ComputeNodalAverage().
95 * @details Full API contract (arguments, ownership, side effects) is documented with
96 * the header declaration in `include/postprocessing_kernels.h`.
97 * @see ComputeNodalAverage()
98 */
99PetscErrorCode ComputeNodalAverage(UserCtx* user, const char* in_field_name, const char* out_field_name)
100{
101 PetscErrorCode ierr;
102 FieldId in_field_id;
103 Vec in_vec_local = NULL, out_vec_global = NULL;
104 DM dm_in = NULL, dm_out = NULL;
105 PetscInt dof = 0;
106
107 PetscFunctionBeginUser;
109 LOG_ALLOW(GLOBAL, LOG_INFO, "-> KERNEL: Running ComputeNodalAverage on '%s' -> '%s'.\n", in_field_name, out_field_name);
110
111 // --- 1. Map string names to PETSc objects ---
112 if (strcasecmp(in_field_name, "P") == 0) { in_vec_local = user->lP; dm_in = user->da; dof = 1; }
113 else if (strcasecmp(in_field_name, "Ucat") == 0) { in_vec_local = user->lUcat; dm_in = user->fda; dof = 3; }
114 else if (strcasecmp(in_field_name, "Psi") == 0) { in_vec_local = user->lPsi; dm_in = user->da; dof = 1; }
115 else if (strcasecmp(in_field_name, "Qcrit") == 0) { in_vec_local = user->lQcrit; dm_in = user->da; dof = 1; }
116 /* The staging pair carries derived statistics, which are config-counted and so
117 * cannot be named by a compile-time member of their own. */
118 else if (strcasecmp(in_field_name, "PostScalar") == 0) { in_vec_local = user->lPostScalar; dm_in = user->da; dof = 1; }
119 else if (strcasecmp(in_field_name, "PostVector") == 0) { in_vec_local = user->lPostVector; dm_in = user->fda; dof = 3; }
120 // ... (add other fields as needed) ...
121 else SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG, "Unknown input field name for nodal averaging: %s", in_field_name);
122
123 if (strcasecmp(out_field_name, "P_nodal") == 0) { out_vec_global = user->P_nodal; dm_out = user->da; }
124 else if (strcasecmp(out_field_name, "Ucat_nodal") == 0) { out_vec_global = user->Ucat_nodal; dm_out = user->fda; }
125 else if (strcasecmp(out_field_name, "Psi_nodal") == 0) { out_vec_global = user->Psi_nodal; dm_out = user->da; }
126 else if (strcasecmp(out_field_name, "Qcrit_nodal") == 0) { out_vec_global = user->Qcrit_nodal; dm_out = user->da; }
127 else if (strcasecmp(out_field_name, "PostScalarNodal") == 0) { out_vec_global = user->PostScalarNodal; dm_out = user->da; }
128 else if (strcasecmp(out_field_name, "PostVectorNodal") == 0) { out_vec_global = user->PostVectorNodal; dm_out = user->fda; }
129 // ... (add other fields as needed) ...
130 else SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG, "Unknown output field name for nodal averaging: %s", out_field_name);
131
132 // --- 2. Ensure Input Data Ghosts are Up-to-Date ---
133 ierr = FieldIdFromName(in_field_name, &in_field_id); CHKERRQ(ierr);
134 ierr = UpdateLocalGhosts(user, in_field_id); CHKERRQ(ierr);
135 /* The boundary node average reads the dummy cells, so show them before averaging. */
137 ierr = LOG_FIELD_ANATOMY(user, in_field_id, "PreNodalAverage"); CHKERRQ(ierr);
138 }
139
140 // --- 3. Get DMDA info and array pointers ---
141 DMDALocalInfo info;
142 ierr = DMDAGetLocalInfo(dm_out, &info); CHKERRQ(ierr);
143 /* Every owned output point is valid except the global high layout plane.
144 * A rank interface is not a boundary: its +1 source value is in the halo. */
145 const PetscInt i_end = PetscMin(info.xs + info.xm, info.mx - 1);
146 const PetscInt j_end = PetscMin(info.ys + info.ym, info.my - 1);
147 const PetscInt k_end = PetscMin(info.zs + info.zm, info.mz - 1);
148
149 if (dof == 1) { // --- Scalar Field Averaging ---
150 const PetscReal ***l_in_arr;
151 PetscReal ***g_out_arr;
152 ierr = DMDAVecGetArrayRead(dm_in,in_vec_local, (void*)&l_in_arr); CHKERRQ(ierr);
153 ierr = DMDAVecGetArray(dm_out,out_vec_global, (void*)&g_out_arr); CHKERRQ(ierr);
154
155 // Loop over the output NODE locations. The loop bounds match the required
156 // size of the final subsampled grid.
157 for (PetscInt k = info.zs; k < k_end; k++) {
158 for (PetscInt j = info.ys; j < j_end; j++) {
159 for (PetscInt i = info.xs; i < i_end; i++) {
160 g_out_arr[k][j][i] = 0.125 * (l_in_arr[k][j][i] + l_in_arr[k][j][i+1] +
161 l_in_arr[k][j+1][i] + l_in_arr[k][j+1][i+1] +
162 l_in_arr[k+1][j][i] + l_in_arr[k+1][j][i+1] +
163 l_in_arr[k+1][j+1][i] + l_in_arr[k+1][j+1][i+1]);
164 }
165 }
166 }
167 ierr = DMDAVecRestoreArrayRead(dm_in,in_vec_local, (void*)&l_in_arr); CHKERRQ(ierr);
168 ierr = DMDAVecRestoreArray(dm_out,out_vec_global, (void*)&g_out_arr); CHKERRQ(ierr);
169
170 } else if (dof == 3) { // --- Vector Field Averaging ---
171 const Cmpnts ***l_in_arr;
172 Cmpnts ***g_out_arr;
173 ierr = DMDAVecGetArrayRead(dm_in,in_vec_local, (void*)&l_in_arr); CHKERRQ(ierr);
174 ierr = DMDAVecGetArray(dm_out,out_vec_global, (void*)&g_out_arr); CHKERRQ(ierr);
175
176 for (PetscInt k = info.zs; k < k_end; k++) {
177 for (PetscInt j = info.ys; j < j_end; j++) {
178 for (PetscInt i = info.xs; i < i_end; i++) {
179 g_out_arr[k][j][i].x = 0.125 * (l_in_arr[k][j][i].x + l_in_arr[k][j][i+1].x +
180 l_in_arr[k][j+1][i].x + l_in_arr[k][j+1][i+1].x +
181 l_in_arr[k+1][j][i].x + l_in_arr[k+1][j][i+1].x +
182 l_in_arr[k+1][j+1][i].x + l_in_arr[k+1][j+1][i+1].x);
183
184 g_out_arr[k][j][i].y = 0.125 * (l_in_arr[k][j][i].y + l_in_arr[k][j][i+1].y +
185 l_in_arr[k][j+1][i].y + l_in_arr[k][j+1][i+1].y +
186 l_in_arr[k+1][j][i].y + l_in_arr[k+1][j][i+1].y +
187 l_in_arr[k+1][j+1][i].y + l_in_arr[k+1][j+1][i+1].y);
188
189 g_out_arr[k][j][i].z = 0.125 * (l_in_arr[k][j][i].z + l_in_arr[k][j][i+1].z +
190 l_in_arr[k][j+1][i].z + l_in_arr[k][j+1][i+1].z +
191 l_in_arr[k+1][j][i].z + l_in_arr[k+1][j][i+1].z +
192 l_in_arr[k+1][j+1][i].z + l_in_arr[k+1][j+1][i+1].z);
193 }
194 }
195 }
196 ierr = DMDAVecRestoreArrayRead(dm_in,in_vec_local, (void*)&l_in_arr); CHKERRQ(ierr);
197 ierr = DMDAVecRestoreArray(dm_out,out_vec_global, (void*)&g_out_arr); CHKERRQ(ierr);
198 }
200 PetscFunctionReturn(0);
201}
202
203
204#undef __FUNCT__
205#define __FUNCT__ "ComputeWindowStatisticNodal"
206/**
207 * @brief Implementation of \ref ComputeWindowStatisticNodal().
208 * @details Full API contract (arguments, ownership, side effects) is documented with
209 * the header declaration in `include/postprocessing_kernels.h`.
210 * @see ComputeWindowStatisticNodal()
211 */
212PetscErrorCode ComputeWindowStatisticNodal(UserCtx *user, PetscInt window_index,
213 const char *outputs, PetscInt output_index,
214 char *out_name, size_t name_size,
215 Vec *out_vec, PetscInt *out_components)
216{
217 PetscErrorCode ierr;
218 SimCtx *simCtx = NULL;
219 PicurvDerivedField derived;
220 const PicurvWindowDefinition *definition = NULL;
221
222 PetscFunctionBeginUser;
224 PetscCheck(user != NULL && out_name != NULL && out_vec != NULL && out_components != NULL,
225 PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Context and outputs are required.");
226 simCtx = user->simCtx;
227 PetscCheck(FieldStatisticsIsActive(simCtx) && user->fieldStatisticsStorage != NULL,
228 PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE,
229 "No accumulated window state exists to derive.");
230 definition = &simCtx->fieldStatisticsWindows[window_index].definition;
231
232 /* Derive into the cell-centred staging pair, then reach the nodal path by
233 * catalogued name. A per-window accumulator has no compile-time offset, so
234 * staging is what lets the shared ghost and nodal kernels address it at all. */
235 ierr = PicurvWindowDerive(user, definition, &user->fieldStatisticsStorage[window_index],
236 outputs, output_index, user->PostScalar, user->PostVector,
237 &derived); CHKERRQ(ierr);
238 /* The derivation writes the physical interior only, so the layout boundary would
239 * otherwise reach the nodal average as structural zeros. On periodic axes the
240 * shared cell synchronizer fills the dummy planes from the wrapped physical
241 * planes; the ghost update then carries the written values outward. */
242 if (derived.components == 1) {
243 const FieldId staged[] = {FIELD_ID_POST_SCALAR};
244 ierr = SynchronizePeriodicCellFields(user, 1, staged); CHKERRQ(ierr);
245 ierr = UpdateLocalGhosts(user, FIELD_ID_POST_SCALAR); CHKERRQ(ierr);
246 ierr = ComputeNodalAverage(user, "PostScalar", "PostScalarNodal"); CHKERRQ(ierr);
247 *out_vec = user->PostScalarNodal;
248 } else {
249 const FieldId staged[] = {FIELD_ID_POST_VECTOR};
250 ierr = SynchronizePeriodicCellFields(user, 1, staged); CHKERRQ(ierr);
251 ierr = UpdateLocalGhosts(user, FIELD_ID_POST_VECTOR); CHKERRQ(ierr);
252 ierr = ComputeNodalAverage(user, "PostVector", "PostVectorNodal"); CHKERRQ(ierr);
253 *out_vec = user->PostVectorNodal;
254 }
255 *out_components = derived.components;
256 ierr = PetscStrncpy(out_name, derived.name, name_size); CHKERRQ(ierr);
257
258 LOG_ALLOW(GLOBAL, LOG_DEBUG, "-> KERNEL: Derived '%s' (%d component(s)).\n",
259 out_name, (int)derived.components);
261 PetscFunctionReturn(0);
262}
263
264#undef __FUNCT__
265#define __FUNCT__ "ComputeWindowStatisticsSummary"
266/**
267 * @brief Implementation of \ref ComputeWindowStatisticsSummary().
268 * @details Full API contract (arguments, ownership, side effects) is documented with
269 * the header declaration in `include/postprocessing_kernels.h`.
270 * @see ComputeWindowStatisticsSummary()
271 */
272PetscErrorCode ComputeWindowStatisticsSummary(UserCtx *user, PetscInt window_index,
273 const char *output_prefix, PetscInt ti)
274{
275 PetscErrorCode ierr;
276 SimCtx *simCtx = NULL;
277 const PicurvWindow *window = NULL;
278 const PicurvWindowStorage *storage = NULL;
279 PetscReal lowest = 1.0, highest = 0.0;
280 PetscReal mean_tke = 0.0;
281 PetscBool has_tke = PETSC_FALSE;
282 PetscInt derived_count = 0;
283 char path[PETSC_MAX_PATH_LEN];
284
285 PetscFunctionBeginUser;
287 PetscCheck(user != NULL && output_prefix != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
288 "Context and output prefix are required.");
289 simCtx = user->simCtx;
290 PetscCheck(FieldStatisticsIsActive(simCtx) && user->fieldStatisticsStorage != NULL,
291 PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE,
292 "No accumulated window state exists to summarize.");
293 window = &simCtx->fieldStatisticsWindows[window_index];
294 storage = &user->fieldStatisticsStorage[window_index];
295
296 ierr = PicurvWindowValidFractionRange(user, &window->definition, storage,
297 window->sample_count, &lowest, &highest); CHKERRQ(ierr);
298
299 /* A single domain number is what makes the row plottable against step. It is
300 * produced by the same derivation the field output uses, so the scalar and the
301 * field can never disagree about what the window holds. */
302 ierr = PicurvWindowDerivedCount(&window->definition, storage, "tke", &derived_count); CHKERRQ(ierr);
303 if (derived_count > 0) {
304 PicurvDerivedField derived;
305
306 ierr = PicurvWindowDerive(user, &window->definition, storage, "tke", 0,
307 user->PostScalar, user->PostVector, &derived); CHKERRQ(ierr);
308 /* Averaged over the fluid cells the window actually sampled. A whole-vector
309 * mean would divide by boundary and dummy entries the derivation never
310 * writes, scaling the answer down by the fraction it never covered. */
311 ierr = PicurvWindowSpatialMean(user, &window->definition, storage,
312 user->PostScalar, &mean_tke); CHKERRQ(ierr);
313 has_tke = PETSC_TRUE;
314 }
315
316 /* The window's clock is solver time. A dimensionalized summary reports its derived
317 * statistics physically, so the times beside them are reported in seconds too; a
318 * time-weighted total weight is a time, a sample-weighted one is a count. */
319 PetscReal time_scale = 1.0;
320 if (simCtx->pps && simCtx->pps->dimensionalize) {
321 const FieldDimension time_dimension = FIELD_DIM_TIME;
322
323 ierr = FieldDimensionReferenceScale(&simCtx->scaling, time_dimension, &time_scale); CHKERRQ(ierr);
324 }
325 const PetscReal weight_scale =
326 (window->definition.weighting == PICURV_WEIGHTING_PHYSICAL_TIME) ? time_scale : 1.0;
327
328 if (simCtx->rank == 0) {
329 FILE *csv = NULL;
330 PetscBool exists = PETSC_FALSE;
331
332 ierr = PetscSNPrintf(path, sizeof(path), "%s_statistics_%s.csv",
333 output_prefix, window->definition.name); CHKERRQ(ierr);
334 ierr = PetscTestFile(path, 'r', &exists); CHKERRQ(ierr);
335 csv = fopen(path, exists ? "a" : "w");
336 PetscCheck(csv != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN,
337 "Unable to open statistics summary '%s'.", path);
338 if (!exists) {
339 fprintf(csv, "step,state,samples,total_weight,represented_time,"
340 "valid_fraction_min,valid_fraction_max,mean_tke\n");
341 }
342 fprintf(csv, "%" PetscInt_FMT ",%s,%d,%.10e,%.10e,%.6f,%.6f,",
343 ti, PicurvWindowStateName(window->state), window->sample_count,
344 (double)(window->total_weight * weight_scale),
345 (double)(window->represented_time * time_scale),
346 (double)lowest, (double)highest);
347 if (has_tke) fprintf(csv, "%.10e\n", (double)mean_tke);
348 else fprintf(csv, "\n");
349 PetscCheck(fclose(csv) == 0, PETSC_COMM_SELF, PETSC_ERR_FILE_WRITE,
350 "Unable to close statistics summary '%s'.", path);
351 }
352 LOG_ALLOW(GLOBAL, LOG_DEBUG, "-> KERNEL: Summarized window '%s' at step %" PetscInt_FMT ".\n",
353 window->definition.name, ti);
355 PetscFunctionReturn(0);
356}
357
358#undef __FUNCT__
359#define __FUNCT__ "ComputeQCriterion"
360/**
361 * @brief Implementation of \ref ComputeQCriterion().
362 * @details Full API contract (arguments, ownership, side effects) is documented with
363 * the header declaration in `include/postprocessing_kernels.h`.
364 * @see ComputeQCriterion()
365 */
366PetscErrorCode ComputeQCriterion(UserCtx* user)
367{
368 PetscErrorCode ierr;
369 DMDALocalInfo info;
370 const Cmpnts ***lucat, ***lcsi, ***leta, ***lzet;
371 const PetscReal***laj, ***lnvert;
372 PetscReal ***gq;
373
374 PetscFunctionBeginUser;
376 LOG_ALLOW(GLOBAL, LOG_INFO, "-> KERNEL: Running ComputeQCriterion.\n");
377
378 // --- 1. Ensure all required ghost values are up-to-date ---
379 ierr = UpdateLocalGhosts(user, FIELD_ID_UCAT); CHKERRQ(ierr);
380 ierr = UpdateLocalGhosts(user, FIELD_ID_CSI); CHKERRQ(ierr);
381 ierr = UpdateLocalGhosts(user, FIELD_ID_ETA); CHKERRQ(ierr);
382 ierr = UpdateLocalGhosts(user, FIELD_ID_ZET); CHKERRQ(ierr);
383 ierr = UpdateLocalGhosts(user, FIELD_ID_AJ); CHKERRQ(ierr);
384 ierr = UpdateLocalGhosts(user, FIELD_ID_NVERT); CHKERRQ(ierr);
385
386 // --- 2. Get DMDA info and array pointers ---
387 ierr = DMDAGetLocalInfo(user->da, &info); CHKERRQ(ierr);
388
389 ierr = DMDAVecGetArrayRead(user->fda, user->lUcat, (void*)&lucat); CHKERRQ(ierr);
390 ierr = DMDAVecGetArrayRead(user->fda, user->lCsi, (void*)&lcsi); CHKERRQ(ierr);
391 ierr = DMDAVecGetArrayRead(user->fda, user->lEta, (void*)&leta); CHKERRQ(ierr);
392 ierr = DMDAVecGetArrayRead(user->fda, user->lZet, (void*)&lzet); CHKERRQ(ierr);
393 ierr = DMDAVecGetArrayRead(user->da, user->lAj, (void*)&laj); CHKERRQ(ierr);
394 ierr = DMDAVecGetArrayRead(user->da, user->lNvert, (void*)&lnvert); CHKERRQ(ierr);
395 ierr = DMDAVecGetArray(user->da, user->Qcrit, (void*)&gq); CHKERRQ(ierr);
396
397 // --- 3. Define Loop Bounds for INTERIOR Cells ---
398 PetscInt i_start = (info.xs == 0) ? 1 : info.xs;
399 PetscInt i_end = (info.xs + info.xm == info.mx) ? info.mx - 1 : info.xs + info.xm;
400 PetscInt j_start = (info.ys == 0) ? 1 : info.ys;
401 PetscInt j_end = (info.ys + info.ym == info.my) ? info.my - 1 : info.ys + info.ym;
402 PetscInt k_start = (info.zs == 0) ? 1 : info.zs;
403 PetscInt k_end = (info.zs + info.zm == info.mz) ? info.mz - 1 : info.zs + info.zm;
404
405 // --- 4. Main Computation Loop ---
406 for (PetscInt k = k_start; k < k_end; k++) {
407 for (PetscInt j = j_start; j < j_end; j++) {
408 for (PetscInt i = i_start; i < i_end; i++) {
409
410 // Calculate velocity derivatives in computational space (central differences)
411 PetscReal uc = 0.5 * (lucat[k][j][i+1].x - lucat[k][j][i-1].x);
412 PetscReal vc = 0.5 * (lucat[k][j][i+1].y - lucat[k][j][i-1].y);
413 PetscReal wc = 0.5 * (lucat[k][j][i+1].z - lucat[k][j][i-1].z);
414
415 PetscReal ue = 0.5 * (lucat[k][j+1][i].x - lucat[k][j-1][i].x);
416 PetscReal ve = 0.5 * (lucat[k][j+1][i].y - lucat[k][j-1][i].y);
417 PetscReal we = 0.5 * (lucat[k][j+1][i].z - lucat[k][j-1][i].z);
418
419 PetscReal uz = 0.5 * (lucat[k+1][j][i].x - lucat[k-1][j][i].x);
420 PetscReal vz = 0.5 * (lucat[k+1][j][i].y - lucat[k-1][j][i].y);
421 PetscReal wz = 0.5 * (lucat[k+1][j][i].z - lucat[k-1][j][i].z);
422
423 // Average metrics to the cell center
424 PetscReal csi1 = 0.5 * (lcsi[k][j][i].x + lcsi[k][j][i-1].x) * laj[k][j][i];
425 PetscReal csi2 = 0.5 * (lcsi[k][j][i].y + lcsi[k][j][i-1].y) * laj[k][j][i];
426 PetscReal csi3 = 0.5 * (lcsi[k][j][i].z + lcsi[k][j][i-1].z) * laj[k][j][i];
427
428 PetscReal eta1 = 0.5 * (leta[k][j][i].x + leta[k][j-1][i].x) * laj[k][j][i];
429 PetscReal eta2 = 0.5 * (leta[k][j][i].y + leta[k][j-1][i].y) * laj[k][j][i];
430 PetscReal eta3 = 0.5 * (leta[k][j][i].z + leta[k][j-1][i].z) * laj[k][j][i];
431
432 PetscReal zet1 = 0.5 * (lzet[k][j][i].x + lzet[k-1][j][i].x) * laj[k][j][i];
433 PetscReal zet2 = 0.5 * (lzet[k][j][i].y + lzet[k-1][j][i].y) * laj[k][j][i];
434 PetscReal zet3 = 0.5 * (lzet[k][j][i].z + lzet[k-1][j][i].z) * laj[k][j][i];
435
436 // Calculate velocity gradient tensor components d_ij = du_i/dx_j
437 PetscReal d11 = uc * csi1 + ue * eta1 + uz * zet1;
438 PetscReal d12 = uc * csi2 + ue * eta2 + uz * zet2;
439 PetscReal d13 = uc * csi3 + ue * eta3 + uz * zet3;
440
441 PetscReal d21 = vc * csi1 + ve * eta1 + vz * zet1;
442 PetscReal d22 = vc * csi2 + ve * eta2 + vz * zet2;
443 PetscReal d23 = vc * csi3 + ve * eta3 + vz * zet3;
444
445 PetscReal d31 = wc * csi1 + we * eta1 + wz * zet1;
446 PetscReal d32 = wc * csi2 + we * eta2 + wz * zet2;
447 PetscReal d33 = wc * csi3 + we * eta3 + wz * zet3;
448
449 // Strain-Rate Tensor S_ij = 0.5 * (d_ij + d_ji)
450 PetscReal s11 = d11;
451 PetscReal s12 = 0.5 * (d12 + d21);
452 PetscReal s13 = 0.5 * (d13 + d31);
453 PetscReal s22 = d22;
454 PetscReal s23 = 0.5 * (d23 + d32);
455 PetscReal s33 = d33;
456
457 // Vorticity Tensor Omega_ij = 0.5 * (d_ij - d_ji)
458 PetscReal w12 = 0.5 * (d12 - d21);
459 PetscReal w13 = 0.5 * (d13 - d31);
460 PetscReal w23 = 0.5 * (d23 - d32);
461
462 // Squared norms of the tensors
463 PetscReal s_norm_sq = s11*s11 + s22*s22 + s33*s33 + 2.0*(s12*s12 + s13*s13 + s23*s23);
464 PetscReal w_norm_sq = 2.0 * (w12*w12 + w13*w13 + w23*w23);
465
466 gq[k][j][i] = 0.5 * (w_norm_sq - s_norm_sq);
467
468 if (lnvert[k][j][i] > 0.1) {
469 gq[k][j][i] = 0.0;
470 }
471 }
472 }
473 }
474
475 // --- 5. Restore arrays ---
476 ierr = DMDAVecRestoreArrayRead(user->fda, user->lUcat, (void*)&lucat); CHKERRQ(ierr);
477 ierr = DMDAVecRestoreArrayRead(user->fda, user->lCsi, (void*)&lcsi); CHKERRQ(ierr);
478 ierr = DMDAVecRestoreArrayRead(user->fda, user->lEta, (void*)&leta); CHKERRQ(ierr);
479 ierr = DMDAVecRestoreArrayRead(user->fda, user->lZet, (void*)&lzet); CHKERRQ(ierr);
480 ierr = DMDAVecRestoreArrayRead(user->da, user->lAj, (void*)&laj); CHKERRQ(ierr);
481 ierr = DMDAVecRestoreArrayRead(user->da, user->lNvert, (void*)&lnvert); CHKERRQ(ierr);
482 ierr = DMDAVecRestoreArray(user->da, user->Qcrit, (void*)&gq); CHKERRQ(ierr);
483
484 /* With dimensionalize on, Ucat already carries U_ref but the metrics are still
485 * nondimensional, so the gradients above are U_ref/L_ref short of physical by one
486 * factor of L_ref each: Q came out U_ref^2 Q* instead of (U_ref/L_ref)^2 Q*. */
487 if (user->simCtx->pps && user->simCtx->pps->dimensionalize) {
488 PetscReal length_scale = 1.0;
489 if (!PicurvFieldReferenceScale(user->simCtx, "Coordinates", &length_scale, NULL, 0) &&
490 length_scale > 0.0) {
491 ierr = VecScale(user->Qcrit, 1.0 / (length_scale * length_scale)); CHKERRQ(ierr);
492 }
493 }
494
495 /* The loop above skips the layout boundary, so fill it before anything reads
496 * Qcrit with a stencil. Q is not a moment: it does not vanish at a wall, so only
497 * the periodic case is defined, and the cell synchronizer writes only that case. */
498 {
499 const FieldId qcrit[] = {FIELD_ID_QCRIT};
500 ierr = SynchronizePeriodicCellFields(user, 1, qcrit); CHKERRQ(ierr);
501 }
502
504 PetscFunctionReturn(0);
505}
506
507#undef __FUNCT__
508#define __FUNCT__ "NormalizeRelativeField"
509/**
510 * @brief Implementation of \ref NormalizeRelativeField().
511 * @details Full API contract (arguments, ownership, side effects) is documented with
512 * the header declaration in `include/postprocessing_kernels.h`.
513 * @see NormalizeRelativeField()
514 */
515PetscErrorCode NormalizeRelativeField(UserCtx* user, const char* relative_field_name)
516{
517 PetscErrorCode ierr;
518 Vec P_vec = NULL;
519 DMDALocalInfo info;
520 PetscInt ip=1, jp=1, kp=1; // Default reference point
521 PetscReal p_ref = 0.0;
522 PetscReal p_ref_local = 0.0;
523 PetscInt found_local = 0, found_global = 0;
524 PostProcessParams *pps = user->simCtx->pps;
525
526 // Fetch the logical reference point from pps.
527 ip = pps->reference[0];
528 jp = pps->reference[1];
529 kp = pps->reference[2];
530
531 PetscFunctionBeginUser;
533 LOG_ALLOW(GLOBAL, LOG_INFO, "-> KERNEL: Running NormalizeRelativeField on '%s'.\n", relative_field_name);
534
535 // --- 1. Map string argument to the PETSc Vec ---
536 if (strcasecmp(relative_field_name, "P") == 0) {
537 P_vec = user->P;
538 } else {
539 SETERRQ(PETSC_COMM_SELF, 1, "NormalizeRelativeField only supports the primary 'P' field , not '%s' currently.", relative_field_name);
540 }
541
542 // --- 2. Read the logical reference point from whichever rank owns it ---
543 ierr = DMDAGetLocalInfo(user->da, &info); CHKERRQ(ierr);
544 PetscCheck(ip >= 0 && ip < info.mx && jp >= 0 && jp < info.my &&
545 kp >= 0 && kp < info.mz,
546 PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE,
547 "Reference point (%" PetscInt_FMT ", %" PetscInt_FMT ", %" PetscInt_FMT
548 ") lies outside the %" PetscInt_FMT "x%" PetscInt_FMT "x%" PetscInt_FMT
549 " pressure layout.", ip, jp, kp, info.mx, info.my, info.mz);
550 if (ip >= info.xs && ip < info.xs + info.xm &&
551 jp >= info.ys && jp < info.ys + info.ym &&
552 kp >= info.zs && kp < info.zs + info.zm) {
553 const PetscReal ***pressure = NULL;
554
555 ierr = DMDAVecGetArrayRead(user->da, P_vec, &pressure); CHKERRQ(ierr);
556 p_ref_local = pressure[kp][jp][ip];
557 ierr = DMDAVecRestoreArrayRead(user->da, P_vec, &pressure); CHKERRQ(ierr);
558 found_local = 1;
559 }
560 ierr = MPI_Allreduce(&p_ref_local, &p_ref, 1, MPIU_REAL, MPI_SUM,
561 PETSC_COMM_WORLD); CHKERRQ(ierr);
562 ierr = MPI_Allreduce(&found_local, &found_global, 1, MPIU_INT, MPI_SUM,
563 PETSC_COMM_WORLD); CHKERRQ(ierr);
564 PetscCheck(found_global == 1, PETSC_COMM_WORLD, PETSC_ERR_PLIB,
565 "Reference pressure point must have exactly one owner; found %" PetscInt_FMT ".",
566 found_global);
568 "%s reference point (%" PetscInt_FMT ", %" PetscInt_FMT ", %" PetscInt_FMT
569 ") has value %g.\n", relative_field_name, ip, jp, kp, (double)p_ref);
570
571 // --- 3. Perform the normalization (in-place shift) on the full distributed vector ---
572 ierr = VecShift(P_vec, -p_ref); CHKERRQ(ierr);
573 LOG_ALLOW(GLOBAL, LOG_DEBUG, "%s field normalized by subtracting %g.\n", relative_field_name, p_ref);
574
576 PetscFunctionReturn(0);
577}
578
579// ===========================================================================
580// Particle Post-Processing Kernels
581// ===========================================================================
582#undef __FUNCT__
583#define __FUNCT__ "ComputeSpecificKE"
584/**
585 * @brief Internal helper implementation: `ComputeSpecificKE()`.
586 * @details Local to this translation unit.
587 */
588PetscErrorCode ComputeSpecificKE(UserCtx* user, const char* velocity_field, const char* ske_field)
589{
590 PetscErrorCode ierr;
591 PetscInt n_local;
592 const PetscScalar (*vel_arr)[3]; // Access velocity as array of 3-component vectors
593 PetscScalar *ske_arr;
594
595 PetscFunctionBeginUser;
597 LOG_ALLOW(GLOBAL, LOG_INFO, "-> KERNEL: Running ComputeSpecificKE ('%s' -> '%s').\n", velocity_field, ske_field);
598
599 // Get local data arrays from the DMSwarm
600 ierr = DMSwarmGetLocalSize(user->swarm, &n_local); CHKERRQ(ierr);
601 if (n_local == 0) { PROFILE_FUNCTION_END; PetscFunctionReturn(0); }
602
603 // Get read-only access to velocity and write access to the output field
604 ierr = DMSwarmGetField(user->swarm, velocity_field, NULL, NULL, (void**)&vel_arr); CHKERRQ(ierr);
605 ierr = DMSwarmGetField(user->post_swarm, ske_field, NULL, NULL, (void**)&ske_arr); CHKERRQ(ierr);
606
607 // Main computation loop
608 for (PetscInt p = 0; p < n_local; p++) {
609 const PetscScalar u = vel_arr[p][0];
610 const PetscScalar v = vel_arr[p][1];
611 const PetscScalar w = vel_arr[p][2];
612 const PetscScalar vel_sq = u*u + v*v + w*w;
613 ske_arr[p] = 0.5 * vel_sq;
614 }
615
616 // Restore arrays
617 ierr = DMSwarmRestoreField(user->swarm, velocity_field, NULL, NULL, (void**)&vel_arr); CHKERRQ(ierr);
618 ierr = DMSwarmRestoreField(user->post_swarm, ske_field, NULL, NULL, (void**)&ske_arr); CHKERRQ(ierr);
619
621 PetscFunctionReturn(0);
622}
623
624#undef __FUNCT__
625#define __FUNCT__ "ComputeDisplacement"
626/**
627 * @brief Internal helper implementation: `ComputeDisplacement()`.
628 * @details Local to this translation unit.
629 */
630PetscErrorCode ComputeDisplacement(UserCtx *user, const char *disp_field)
631{
632 PetscErrorCode ierr;
633 PetscInt n_local;
634 const PetscReal (*pos_arr)[3];
635 PetscScalar *disp_out;
636 SimCtx *simCtx = user->simCtx;
637
638 PetscFunctionBeginUser;
640 LOG_ALLOW(GLOBAL, LOG_INFO, "-> KERNEL: Running ComputeDisplacement (-> '%s').\n", disp_field);
641
642 ierr = DMSwarmGetLocalSize(user->swarm, &n_local); CHKERRQ(ierr);
643 if (n_local == 0) { PROFILE_FUNCTION_END; PetscFunctionReturn(0); }
644
645 const PetscReal x0 = simCtx->psrc_x;
646 const PetscReal y0 = simCtx->psrc_y;
647 const PetscReal z0 = simCtx->psrc_z;
648
649 ierr = DMSwarmGetField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&pos_arr); CHKERRQ(ierr);
650 ierr = DMSwarmGetField(user->post_swarm, disp_field, NULL, NULL, (void**)&disp_out); CHKERRQ(ierr);
651
652 for (PetscInt p = 0; p < n_local; p++) {
653 const PetscReal dx = pos_arr[p][0] - x0;
654 const PetscReal dy = pos_arr[p][1] - y0;
655 const PetscReal dz = pos_arr[p][2] - z0;
656 disp_out[p] = PetscSqrtReal(dx*dx + dy*dy + dz*dz);
657 }
658
659 ierr = DMSwarmRestoreField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void**)&pos_arr); CHKERRQ(ierr);
660 ierr = DMSwarmRestoreField(user->post_swarm, disp_field, NULL, NULL, (void**)&disp_out); CHKERRQ(ierr);
661
663 PetscFunctionReturn(0);
664}
PetscErrorCode SynchronizePeriodicCellFields(UserCtx *user, PetscInt num_fields, const FieldId field_ids[])
Synchronizes periodic endpoint cells for a list of cell-centered fields.
PetscErrorCode FieldIdFromName(const char *field_name, FieldId *field_id)
Resolve a user-facing field name once into its typed identity.
PetscErrorCode FieldDimensionReferenceScale(const ScalingCtx *scaling, FieldDimension dimension, PetscReal *scale)
Return the factor that turns a solver value of one dimension into physical units.
PetscErrorCode FieldGetView(UserCtx *user, FieldId field_id, FieldView *view)
Resolve the existing DM and global/local vectors for one field.
PetscErrorCode FieldTryIdFromName(const char *field_name, FieldId *field_id, PetscBool *found)
Look a name up without treating an unknown name as an error.
#define FIELD_DIM_TIME
FieldId
Compile-time identity for a catalogued Eulerian field.
@ FIELD_ID_CSI
@ FIELD_ID_NVERT
@ FIELD_ID_UCAT
@ FIELD_ID_COORDINATES
@ FIELD_ID_AJ
@ FIELD_ID_POST_VECTOR
@ FIELD_ID_ETA
@ FIELD_ID_POST_SCALAR
@ FIELD_ID_QCRIT
@ FIELD_ID_INVALID
@ FIELD_ID_ZET
Physical dimension as exponents of the reference length, velocity, and density.
Non-owning runtime objects resolved for one field and UserCtx.
PetscErrorCode PicurvFieldReferenceScale(SimCtx *simCtx, const char *field_name, PetscReal *scale, char *description, size_t description_length)
Physical scale one field is multiplied by to leave non-dimensional form.
Definition io.c:3197
PetscBool is_function_allowed(const char *functionName)
Checks if a given function is in the allow-list.
Definition logging.c:186
#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
#define LOG(scope, level, fmt,...)
Logging macro for PETSc-based applications with scope control.
Definition logging.h:84
LogLevel get_log_level()
Retrieves the current logging level from the environment variable LOG_LEVEL.
Definition logging.c:87
PetscErrorCode LOG_FIELD_ANATOMY(UserCtx *user, FieldId field_id, const char *stage_name)
Logs the anatomy of a specified field at key boundary locations, respecting the solver's specific gri...
Definition logging.c:2861
@ LOG_INFO
Informational messages about program execution.
Definition logging.h:31
@ LOG_DEBUG
Detailed debugging information.
Definition logging.h:32
@ LOG_VERBOSE
Extremely detailed logs, typically for development use only.
Definition logging.h:34
#define PROFILE_FUNCTION_BEGIN
Marks the beginning of a profiled code block (typically a function).
Definition logging.h:885
Typed identities and metadata for persistent solver-particle fields.
const char * ParticleFieldName(ParticleFieldId field_id)
Return the canonical PETSc DMSwarm name for an ID.
ParticleFieldId
Compile-time identity for a persistent solver-particle field.
@ PARTICLE_FIELD_ID_POSITION
@ PARTICLE_FIELD_ID_INVALID
PetscErrorCode ParticleFieldGetDescriptor(ParticleFieldId field_id, const ParticleFieldDescriptor **descriptor)
Return immutable metadata for a valid particle field ID.
PetscErrorCode ParticleFieldTryIdFromName(const char *field_name, ParticleFieldId *field_id, PetscBool *found)
Look a name up without treating an unknown name as an error.
Immutable metadata for one persistent particle field.
PetscErrorCode ComputeQCriterion(UserCtx *user)
Implementation of ComputeQCriterion().
PetscErrorCode ComputeSpecificKE(UserCtx *user, const char *velocity_field, const char *ske_field)
Internal helper implementation: ComputeSpecificKE().
PetscErrorCode ComputeDisplacement(UserCtx *user, const char *disp_field)
Internal helper implementation: ComputeDisplacement().
PetscErrorCode NormalizeRelativeField(UserCtx *user, const char *relative_field_name)
Implementation of NormalizeRelativeField().
PetscErrorCode ComputeWindowStatisticsSummary(UserCtx *user, PetscInt window_index, const char *output_prefix, PetscInt ti)
Implementation of ComputeWindowStatisticsSummary().
PetscErrorCode DimensionalizeField(UserCtx *user, const char *field_name)
Implementation of DimensionalizeField().
PetscErrorCode ComputeNodalAverage(UserCtx *user, const char *in_field_name, const char *out_field_name)
Implementation of ComputeNodalAverage().
#define __FUNCT__
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)
Implementation of ComputeWindowStatisticNodal().
PetscErrorCode UpdateLocalGhosts(UserCtx *user, FieldId field_id)
Updates the local vector (including ghost points) from its corresponding global vector.
Definition setup.c:2489
Per-window PETSc accumulator storage and pointwise application.
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.
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.
One derived output field, resolved by enumeration index.
Independent accumulator state for one window on one block.
Window lifecycle, scheduling, and weighting for the field-statistics pipeline.
PetscInt sample_count
PicurvWindowState state
PetscReal total_weight
const char * PicurvWindowStateName(PicurvWindowState state)
Returns a stable human-readable name for a window state.
PicurvWindowDefinition definition
PetscBool FieldStatisticsIsActive(const struct SimCtx *simCtx)
Reports whether this run has live field-statistics state.
@ PICURV_WEIGHTING_PHYSICAL_TIME
Weight is the represented interval.
PetscReal represented_time
Physical time the window covers.
Runtime state of one window.
The scientifically immutable definition of one window.
Vec Qcrit_nodal
Q-criterion averaged to grid nodes; the field a .vts can place correctly.
Definition variables.h:1179
Vec lPostScalar
Definition variables.h:1133
Vec P_nodal
Definition variables.h:1176
PetscMPIInt rank
Definition variables.h:862
Vec lNvert
Definition variables.h:1113
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:1077
PetscBool dimensionalize
Whether derived output leaves non-dimensional form, from global_operations.dimensionalize.
Definition variables.h:789
PetscReal psrc_x
Definition variables.h:945
DM post_swarm
Definition variables.h:1175
PetscInt reference[3]
Definition variables.h:798
Vec Ucat_nodal
Definition variables.h:1177
Vec PostScalarNodal
Definition variables.h:1133
Vec PostScalar
Definition variables.h:1133
Vec Qcrit
Definition variables.h:1178
PetscScalar x
Definition variables.h:122
PetscReal psrc_z
Point source location for PARTICLE_INIT_POINT_SOURCE.
Definition variables.h:945
struct PicurvWindow * fieldStatisticsWindows
Definition variables.h:933
PetscScalar z
Definition variables.h:122
ScalingCtx scaling
Definition variables.h:946
Vec PostVectorNodal
Definition variables.h:1134
PetscReal psrc_y
Definition variables.h:945
struct PicurvWindowStorage * fieldStatisticsStorage
Definition variables.h:1136
Vec Psi_nodal
Definition variables.h:1180
Vec lPostVector
Definition variables.h:1134
Vec lUcat
Definition variables.h:1113
PostProcessParams * pps
Definition variables.h:1058
PetscScalar y
Definition variables.h:122
Vec PostVector
Definition variables.h:1134
Vec lQcrit
Cell-centred Q-criterion and its ghosted copy for nodal averaging.
Definition variables.h:1178
A 3D point or vector with PetscScalar components.
Definition variables.h:121
Holds all configuration parameters for a post-processing run.
Definition variables.h:754
The master context for the entire simulation.
Definition variables.h:859
User-defined context containing data specific to a single computational grid level.
Definition variables.h:1074