PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
statistics_target.c
Go to the documentation of this file.
1/**
2 * @file statistics_target.c
3 * @brief Spatial target resolution for the field-statistics pipeline.
4 *
5 * Full API contract is documented with the declarations in
6 * `include/statistics_target.h`.
7 */
8
9#include "statistics_target.h"
10#include "logging.h"
11
12/**
13 * @brief Implementation of \ref PicurvLayoutDimensionIsNodeLike().
14 * @see PicurvLayoutDimensionIsNodeLike()
15 */
16PetscErrorCode PicurvLayoutDimensionIsNodeLike(FieldLayout layout, PetscInt dim, PetscBool *node_like)
17{
18 PetscFunctionBeginUser;
19 PetscCheck(node_like != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
20 "Classification output is required.");
21 PetscCheck(dim >= 0 && dim < 3, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE,
22 "Dimension must be 0, 1, or 2; got %" PetscInt_FMT ".", dim);
23
24 switch (layout) {
26 *node_like = PETSC_TRUE;
27 break;
29 *node_like = PETSC_FALSE;
30 break;
32 *node_like = (PetscBool)(dim == 0);
33 break;
35 *node_like = (PetscBool)(dim == 1);
36 break;
38 *node_like = (PetscBool)(dim == 2);
39 break;
41 /* x, y, and z live on I-, J-, and K-faces respectively, so no single
42 * classification describes the packed vector. */
43 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE,
44 "Component-staggered layout has no single per-dimension classification.");
45 default:
46 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE,
47 "Unknown field layout %d.", (int)layout);
48 }
49 PetscFunctionReturn(0);
50}
51
52/**
53 * @brief Internal helper: resolves the layout-valid index span for one dimension.
54 * @details Local to this translation unit.
55 *
56 * The DMDA carries one extra high-side slot beyond the physical grid, and the
57 * solver's shifted convention places boundary/dummy values at index zero. Under
58 * periodicity the repair algorithms in `Boundaries.c` write index `0` and index
59 * `size-1` from the opposite side, so both are dependent duplicates and the
60 * independent span starts at one in every layout.
61 */
62static void ResolveLayoutSpan(PetscBool node_like, PetscBool periodic, PetscInt size,
63 PetscInt *lo, PetscInt *hi_exclusive)
64{
65 /* Node-like and non-periodic is the only case whose first physical entry
66 * sits at index zero; everywhere else index zero is a boundary, dummy, or
67 * periodic duplicate. */
68 *lo = (node_like && !periodic) ? 0 : 1;
69 /* The final slot is the DMDA's extra non-physical entry under every layout. */
70 *hi_exclusive = size - 1;
71}
72
73#undef __FUNCT__
74#define __FUNCT__ "SpatialTargetPlanCreate"
75/**
76 * @brief Implementation of \ref SpatialTargetPlanCreate().
77 * @see SpatialTargetPlanCreate()
78 */
79PetscErrorCode SpatialTargetPlanCreate(UserCtx *user, FieldId field_id,
81{
82 const FieldDescriptor *descriptor = NULL;
83 const DMDALocalInfo *info = NULL;
84 SimCtx *simCtx = NULL;
85 PetscInt owned_start[3];
86 PetscInt owned_end[3];
87 PetscInt global_size[3];
88 PetscBool periodic[3];
89
90 PetscFunctionBeginUser;
92 PetscCheck(user != NULL && plan != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
93 "Block context and plan output are required.");
94 PetscCheck(mask == PICURV_STATISTICS_MASK_FLUID, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE,
95 "Only the fluid mask is implemented.");
96 simCtx = user->simCtx;
97 PetscCheck(simCtx != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE,
98 "Block context must carry a simulation context.");
99
100 PetscCall(FieldGetDescriptor(field_id, &descriptor));
101 PetscCheck(descriptor->layout != FIELD_LAYOUT_COMPONENT_STAGGERED,
102 PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE,
103 "Field '%s' is component-staggered; its components live on different face "
104 "families and cannot share one pointwise target domain.",
105 descriptor->canonical_name);
106
107 info = &user->info;
108 owned_start[0] = info->xs; owned_end[0] = info->xs + info->xm; global_size[0] = info->mx;
109 owned_start[1] = info->ys; owned_end[1] = info->ys + info->ym; global_size[1] = info->my;
110 owned_start[2] = info->zs; owned_end[2] = info->zs + info->zm; global_size[2] = info->mz;
111 /* The simCtx flags are authoritative here because they are what selects
112 * DM_BOUNDARY_PERIODIC when the DMDA is built, and therefore what decides
113 * whether the end planes are wrapped duplicates. */
114 periodic[0] = (PetscBool)(simCtx->i_periodic != 0);
115 periodic[1] = (PetscBool)(simCtx->j_periodic != 0);
116 periodic[2] = (PetscBool)(simCtx->k_periodic != 0);
117
119 plan->mask = mask;
120 plan->descriptor = descriptor;
121
122 for (PetscInt dim = 0; dim < 3; ++dim) {
123 PetscBool node_like = PETSC_FALSE;
124 PetscInt layout_lo = 0;
125 PetscInt layout_hi = 0;
126
127 PetscCall(PicurvLayoutDimensionIsNodeLike(descriptor->layout, dim, &node_like));
128 ResolveLayoutSpan(node_like, periodic[dim], global_size[dim], &layout_lo, &layout_hi);
129
130 /* Intersect the layout-valid span with this rank's owned range: the
131 * first exclusion removes solver-layout indices, the second removes
132 * PETSc halo storage. */
133 plan->start[dim] = PetscMax(owned_start[dim], layout_lo);
134 plan->end[dim] = PetscMin(owned_end[dim], layout_hi);
135 if (plan->end[dim] < plan->start[dim]) plan->end[dim] = plan->start[dim];
136 }
138 PetscFunctionReturn(0);
139}
140
141/**
142 * @brief Implementation of \ref SpatialTargetPlanLocalPointCount().
143 * @see SpatialTargetPlanLocalPointCount()
144 */
145PetscErrorCode SpatialTargetPlanLocalPointCount(const SpatialTargetPlan *plan, PetscInt *count)
146{
147 PetscInt total = 1;
148
149 PetscFunctionBeginUser;
150 PetscCheck(plan != NULL && count != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
151 "Plan and count output are required.");
152 for (PetscInt dim = 0; dim < 3; ++dim) {
153 const PetscInt extent = plan->end[dim] - plan->start[dim];
154 if (extent <= 0) { *count = 0; PetscFunctionReturn(0); }
155 total *= extent;
156 }
157 *count = total;
158 PetscFunctionReturn(0);
159}
160
161#undef __FUNCT__
162#define __FUNCT__ "SpatialTargetPlanGlobalPointCount"
163/**
164 * @brief Implementation of \ref SpatialTargetPlanGlobalPointCount().
165 * @see SpatialTargetPlanGlobalPointCount()
166 */
167PetscErrorCode SpatialTargetPlanGlobalPointCount(const SpatialTargetPlan *plan, MPI_Comm comm, PetscInt *count)
168{
169 PetscInt local = 0;
170
171 PetscFunctionBeginUser;
173 PetscCheck(plan != NULL && count != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
174 "Plan and count output are required.");
175 PetscCall(SpatialTargetPlanLocalPointCount(plan, &local));
176 PetscCallMPI(MPI_Allreduce(&local, count, 1, MPIU_INT, MPI_SUM, comm));
178 PetscFunctionReturn(0);
179}
180
181/**
182 * @brief Implementation of \ref SpatialTargetPlanMaskAllows().
183 * @see SpatialTargetPlanMaskAllows()
184 */
185PetscBool SpatialTargetPlanMaskAllows(const SpatialTargetPlan *plan, PetscReal nvert_value)
186{
187 if (plan == NULL) return PETSC_FALSE;
188 return (PetscBool)(nvert_value < PICURV_STATISTICS_FLUID_THRESHOLD);
189}
190
191/**
192 * @brief Flattens the retained global indices of one point into a reduction slot.
193 *
194 * A direction being averaged over collapses to a single entry, so the buffer is
195 * indexed only by the directions left out.
196 */
197static PetscInt SpatialAverageSlot(const PetscBool average_direction[3],
198 const PetscInt retained_extent[3],
199 PetscInt i, PetscInt j, PetscInt k)
200{
201 return (average_direction[0] ? 0 : i) +
202 retained_extent[0] * ((average_direction[1] ? 0 : j) +
203 retained_extent[1] * (average_direction[2] ? 0 : k));
204}
205
206/**
207 * @brief Implementation of \ref PicurvSpatialRatioAverage().
208 * @see PicurvSpatialRatioAverage()
209 */
210PetscErrorCode PicurvSpatialRatioAverage(UserCtx *user, const SpatialTargetPlan *plan,
211 Vec numerator, Vec denominator, Vec inclusion,
212 const PetscBool average_direction[3], MPI_Comm comm,
213 Vec ratio, PetscReal *scalar)
214{
215 DM da = NULL;
216 DMDALocalInfo info;
217 PetscInt retained_extent[3];
218 PetscInt buffer_size = 1;
219 PetscInt averaged_count = 0;
220 PetscReal ***num = NULL, ***den = NULL, ***inc = NULL, ***out = NULL, ***nvert = NULL;
221 PetscReal *num_sum = NULL, *den_sum = NULL;
222
223 PetscFunctionBeginUser;
225 PetscCheck(user != NULL && plan != NULL && numerator != NULL && average_direction != NULL,
226 PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
227 "Context, plan, numerator, and direction selector are required.");
228
229 da = user->da;
230 PetscCall(DMDAGetLocalInfo(da, &info));
231
232 retained_extent[0] = average_direction[0] ? 1 : info.mx;
233 retained_extent[1] = average_direction[1] ? 1 : info.my;
234 retained_extent[2] = average_direction[2] ? 1 : info.mz;
235 for (PetscInt axis = 0; axis < 3; axis++) {
236 if (average_direction[axis]) averaged_count++;
237 buffer_size *= retained_extent[axis];
238 }
239 PetscCheck(scalar == NULL || averaged_count == 3, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE,
240 "A single averaged value exists only when every direction is averaged over; "
241 "%" PetscInt_FMT " of 3 were selected.", averaged_count);
242
243 PetscCall(DMDAVecGetArrayRead(da, numerator, &num));
244 if (denominator) PetscCall(DMDAVecGetArrayRead(da, denominator, &den));
245 if (inclusion) PetscCall(DMDAVecGetArrayRead(da, inclusion, &inc));
246 if (ratio) PetscCall(DMDAVecGetArray(da, ratio, &out));
247 PetscCall(DMDAVecGetArrayRead(da, user->lNvert, &nvert));
248
249 if (averaged_count == 0) {
250 /* Pointwise: the empty averaging set, which is a legitimate request rather
251 * than a degenerate one. It is what a local formulation asks for. The masks
252 * mean the same thing here as they do for an averaged set - a point they
253 * exclude contributes nothing and receives nothing - so that a caller can
254 * switch between the two without its exclusions quietly changing meaning. */
255 for (PetscInt k = plan->start[2]; k < plan->end[2]; k++)
256 for (PetscInt j = plan->start[1]; j < plan->end[1]; j++)
257 for (PetscInt i = plan->start[0]; i < plan->end[0]; i++) {
258 const PetscReal divisor = denominator ? den[k][j][i] : 1.0;
259
260 if (!SpatialTargetPlanMaskAllows(plan, nvert[k][j][i]) ||
261 (inc && inc[k][j][i] <= 0.0)) {
262 out[k][j][i] = 0.0;
263 continue;
264 }
265 out[k][j][i] = (PetscAbsReal(divisor) > 0.0) ? (num[k][j][i] / divisor) : 0.0;
266 }
267 } else {
268 PetscCheck(buffer_size <= PICURV_SPATIAL_AVERAGE_MAX_BUFFER, PETSC_COMM_SELF,
269 PETSC_ERR_ARG_OUTOFRANGE,
270 "Averaging over %" PetscInt_FMT " direction(s) would need a reduction buffer "
271 "of %" PetscInt_FMT " entries, above the %d entry limit. Average over more "
272 "directions.", averaged_count, buffer_size, PICURV_SPATIAL_AVERAGE_MAX_BUFFER);
273
274 PetscCall(PetscCalloc2(buffer_size, &num_sum, buffer_size, &den_sum));
275
276 for (PetscInt k = plan->start[2]; k < plan->end[2]; k++)
277 for (PetscInt j = plan->start[1]; j < plan->end[1]; j++)
278 for (PetscInt i = plan->start[0]; i < plan->end[0]; i++) {
279 PetscInt slot;
280
281 if (!SpatialTargetPlanMaskAllows(plan, nvert[k][j][i])) continue;
282 /* A point the caller marks unmeasured holds a zero that means absence, not
283 * a measurement; counting it would scale the answer toward zero. */
284 if (inc && inc[k][j][i] <= 0.0) continue;
285
286 slot = SpatialAverageSlot(average_direction, retained_extent, i, j, k);
287 num_sum[slot] += num[k][j][i];
288 den_sum[slot] += denominator ? den[k][j][i] : 1.0;
289 }
290
291 PetscCallMPI(MPI_Allreduce(MPI_IN_PLACE, num_sum, (PetscMPIInt)buffer_size,
292 MPIU_REAL, MPI_SUM, comm));
293 PetscCallMPI(MPI_Allreduce(MPI_IN_PLACE, den_sum, (PetscMPIInt)buffer_size,
294 MPIU_REAL, MPI_SUM, comm));
295
296 if (out) {
297 for (PetscInt k = plan->start[2]; k < plan->end[2]; k++)
298 for (PetscInt j = plan->start[1]; j < plan->end[1]; j++)
299 for (PetscInt i = plan->start[0]; i < plan->end[0]; i++) {
300 const PetscInt slot = SpatialAverageSlot(average_direction, retained_extent, i, j, k);
301
302 out[k][j][i] = (PetscAbsReal(den_sum[slot]) > 0.0)
303 ? (num_sum[slot] / den_sum[slot]) : 0.0;
304 }
305 }
306 if (scalar) {
307 *scalar = (PetscAbsReal(den_sum[0]) > 0.0) ? (num_sum[0] / den_sum[0]) : 0.0;
308 }
309
310 PetscCall(PetscFree2(num_sum, den_sum));
311 }
312
313 PetscCall(DMDAVecRestoreArrayRead(da, numerator, &num));
314 if (denominator) PetscCall(DMDAVecRestoreArrayRead(da, denominator, &den));
315 if (inclusion) PetscCall(DMDAVecRestoreArrayRead(da, inclusion, &inc));
316 if (ratio) PetscCall(DMDAVecRestoreArray(da, ratio, &out));
317 PetscCall(DMDAVecRestoreArrayRead(da, user->lNvert, &nvert));
318
320 PetscFunctionReturn(0);
321}
FieldLayout layout
FieldLayout
Logical storage topology of a field.
@ FIELD_LAYOUT_K_FACE
@ FIELD_LAYOUT_I_FACE
@ FIELD_LAYOUT_CELL_CENTERED
@ FIELD_LAYOUT_COMPONENT_STAGGERED
@ FIELD_LAYOUT_NODE_CENTERED
@ FIELD_LAYOUT_J_FACE
const char * canonical_name
PetscErrorCode FieldGetDescriptor(FieldId field_id, const FieldDescriptor **descriptor)
Return immutable metadata for a valid field identifier.
FieldId
Compile-time identity for a catalogued Eulerian field.
Immutable metadata for one field identity.
Logging utilities and macros for PETSc-based applications.
#define PROFILE_FUNCTION_END
Marks the end of a profiled code block.
Definition logging.h:894
#define PROFILE_FUNCTION_BEGIN
Marks the beginning of a profiled code block (typically a function).
Definition logging.h:885
PetscBool SpatialTargetPlanMaskAllows(const SpatialTargetPlan *plan, PetscReal nvert_value)
Implementation of SpatialTargetPlanMaskAllows().
static void ResolveLayoutSpan(PetscBool node_like, PetscBool periodic, PetscInt size, PetscInt *lo, PetscInt *hi_exclusive)
Internal helper: resolves the layout-valid index span for one dimension.
PetscErrorCode SpatialTargetPlanLocalPointCount(const SpatialTargetPlan *plan, PetscInt *count)
Implementation of SpatialTargetPlanLocalPointCount().
PetscErrorCode SpatialTargetPlanGlobalPointCount(const SpatialTargetPlan *plan, MPI_Comm comm, PetscInt *count)
Implementation of SpatialTargetPlanGlobalPointCount().
PetscErrorCode SpatialTargetPlanCreate(UserCtx *user, FieldId field_id, PicurvStatisticsMask mask, SpatialTargetPlan *plan)
Implementation of SpatialTargetPlanCreate().
PetscErrorCode PicurvSpatialRatioAverage(UserCtx *user, const SpatialTargetPlan *plan, Vec numerator, Vec denominator, Vec inclusion, const PetscBool average_direction[3], MPI_Comm comm, Vec ratio, PetscReal *scalar)
Implementation of PicurvSpatialRatioAverage().
PetscErrorCode PicurvLayoutDimensionIsNodeLike(FieldLayout layout, PetscInt dim, PetscBool *node_like)
Implementation of PicurvLayoutDimensionIsNodeLike().
static PetscInt SpatialAverageSlot(const PetscBool average_direction[3], const PetscInt retained_extent[3], PetscInt i, PetscInt j, PetscInt k)
Flattens the retained global indices of one point into a reduction slot.
Spatial target resolution for the field-statistics pipeline.
#define PICURV_STATISTICS_FLUID_THRESHOLD
Threshold below which a cell counts as fluid for the default mask.
PicurvTargetKind kind
Spatial mapping; always pointwise.
@ PICURV_TARGET_POINTWISE
PicurvStatisticsMask
Point-eligibility mask.
@ PICURV_STATISTICS_MASK_FLUID
PetscInt end[3]
Exclusive end per dimension (i, j, k).
PetscInt start[3]
Inclusive start per dimension (i, j, k).
const FieldDescriptor * descriptor
Catalog metadata for the targeted field.
PicurvStatisticsMask mask
Point-eligibility mask.
#define PICURV_SPATIAL_AVERAGE_MAX_BUFFER
Largest reduction buffer a directional average will allocate, in entries.
Resolved iteration domain for one field on one block.
Vec lNvert
Definition variables.h:1113
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:1076
PetscInt k_periodic
Definition variables.h:961
PetscInt i_periodic
Definition variables.h:961
DMDALocalInfo info
Definition variables.h:1085
PetscInt j_periodic
Definition variables.h:961
The master context for the entire simulation.
Definition variables.h:866
User-defined context containing data specific to a single computational grid level.
Definition variables.h:1073