PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
Macros | Enumerations | Functions | Variables
statistics_accumulator.c File Reference

Per-window PETSc accumulator storage and pointwise application. More...

#include "statistics_accumulator.h"
#include "statistics_moments.h"
#include "statistics_target.h"
#include "field_catalog.h"
#include "io.h"
#include "logging.h"
#include <stddef.h>
Include dependency graph for statistics_accumulator.c:

Go to the source code of this file.

Macros

#define STATISTICS_DERIVED_OUTPUT_LENGTH   256
 Longest output list a recipe may request.
 
#define __FUNCT__   "PicurvWindowStorageCreate"
 
#define __FUNCT__   "PicurvWindowDerive"
 
#define __FUNCT__   "PicurvWindowSpatialMean"
 
#define __FUNCT__   "PicurvWindowValidFractionRange"
 
#define __FUNCT__   "PicurvWindowAccumulate"
 

Enumerations

enum  DerivedKind {
  DERIVED_MEAN = 0 , DERIVED_REYNOLDS_STRESS , DERIVED_RMS , DERIVED_TKE ,
  DERIVED_FLUX , DERIVED_KIND_COUNT
}
 Output kinds a postprocessing recipe may request, in enumeration order. More...
 

Functions

static PetscInt ProductDiagonalIndex (PetscInt component)
 Internal helper: the product index carrying one component's own variance.
 
static PetscErrorCode ProductComponentLabel (PetscInt index, char *out, size_t size)
 Internal helper: writes the two-axis label of one product component.
 
PetscErrorCode PicurvProductComponentCount (PetscInt dof, PetscInt *count)
 Implementation of PicurvProductComponentCount().
 
PetscErrorCode PicurvCovarianceComponentCount (PetscInt dof_a, PetscInt dof_b, PetscInt *count)
 Implementation of PicurvCovarianceComponentCount().
 
PetscErrorCode PicurvStatisticsComponentDM (UserCtx *user, PetscInt components, DM *dm)
 Implementation of PicurvStatisticsComponentDM().
 
PetscErrorCode PicurvWindowStorageCreate (UserCtx *user, const PicurvWindowDefinition *definition, PicurvWindowStorage *storage)
 Implementation of PicurvWindowStorageCreate().
 
PetscErrorCode PicurvWindowStorageDestroy (PicurvWindowStorage *storage)
 Implementation of PicurvWindowStorageDestroy().
 
static PetscErrorCode FindFieldSlot (const PicurvWindowDefinition *definition, PetscInt field_id, PetscInt *slot)
 Internal helper: locates the storage slot holding one field's running mean.
 
PetscErrorCode PicurvWindowStoragePayloadCount (const PicurvWindowStorage *storage, PetscInt *count)
 Implementation of PicurvWindowStoragePayloadCount().
 
PetscErrorCode PicurvWindowStoragePayload (UserCtx *user, const PicurvWindowDefinition *definition, const PicurvWindowStorage *storage, PetscInt index, PicurvStatisticsPayload *payload)
 Implementation of PicurvWindowStoragePayload().
 
static PetscErrorCode ParseDerivedKinds (const char *outputs, PetscBool wanted[DERIVED_KIND_COUNT])
 Internal helper: reports which output kinds a recipe requested.
 
static PetscErrorCode DerivedKindExtent (const PicurvWindowDefinition *definition, const PicurvWindowStorage *storage, DerivedKind kind, PetscInt *count)
 Internal helper: counts the derived fields each kind contributes.
 
PetscErrorCode PicurvWindowDerivedCount (const PicurvWindowDefinition *definition, const PicurvWindowStorage *storage, const char *outputs, PetscInt *count)
 Implementation of PicurvWindowDerivedCount().
 
static PetscErrorCode SafeStandardDeviation (PetscReal variance, const char *label, PetscReal *result)
 Internal helper: takes a square root of a variance that may be barely negative.
 
static PetscErrorCode ResolveDerivedIndex (const PicurvWindowDefinition *definition, const PicurvWindowStorage *storage, const PetscBool wanted[DERIVED_KIND_COUNT], PetscInt index, DerivedKind *kind, PetscInt *offset)
 Internal helper: resolves which kind and member one derived index selects.
 
PetscErrorCode PicurvWindowDerive (UserCtx *user, const PicurvWindowDefinition *definition, const PicurvWindowStorage *storage, const char *outputs, PetscInt index, Vec scalar_target, Vec vector_target, PicurvDerivedField *field)
 Implementation of PicurvWindowDerive().
 
PetscErrorCode PicurvWindowSpatialMean (UserCtx *user, const PicurvWindowDefinition *definition, const PicurvWindowStorage *storage, Vec field, PetscReal *mean)
 Implementation of PicurvWindowSpatialMean().
 
PetscErrorCode PicurvWindowValidFractionRange (UserCtx *user, const PicurvWindowDefinition *definition, const PicurvWindowStorage *storage, PetscInt sample_count, PetscReal *minimum, PetscReal *maximum)
 Implementation of PicurvWindowValidFractionRange().
 
PetscErrorCode PicurvWindowAccumulate (UserCtx *user, const PicurvWindowDefinition *definition, PicurvWindowStorage *storage, PetscReal weight)
 Implementation of PicurvWindowAccumulate().
 

Variables

static const PetscInt kProductFirst [6] = {0, 0, 0, 1, 1, 2}
 Upper-triangular row-major component pairs for a three-vector self-product.
 
static const PetscInt kProductSecond [6] = {0, 1, 2, 1, 2, 2}
 
static const char *const kAxisName [3] = {"x", "y", "z"}
 Axis labels indexing the pair table above.
 
static const char *const kDerivedKindName [DERIVED_KIND_COUNT]
 Recipe spellings of the output kinds.
 
static const PetscInt kDerivedKindScaleExponent [DERIVED_KIND_COUNT]
 Power of the source field's reference scale each derived kind carries.
 

Detailed Description

Per-window PETSc accumulator storage and pointwise application.

Full API contract is documented with the declarations in include/statistics_accumulator.h.

Definition in file statistics_accumulator.c.

Macro Definition Documentation

◆ STATISTICS_DERIVED_OUTPUT_LENGTH

#define STATISTICS_DERIVED_OUTPUT_LENGTH   256

Longest output list a recipe may request.

Definition at line 19 of file statistics_accumulator.c.

◆ __FUNCT__ [1/5]

#define __FUNCT__   "PicurvWindowStorageCreate"

Definition at line 135 of file statistics_accumulator.c.

◆ __FUNCT__ [2/5]

#define __FUNCT__   "PicurvWindowDerive"

Definition at line 135 of file statistics_accumulator.c.

◆ __FUNCT__ [3/5]

#define __FUNCT__   "PicurvWindowSpatialMean"

Definition at line 135 of file statistics_accumulator.c.

◆ __FUNCT__ [4/5]

#define __FUNCT__   "PicurvWindowValidFractionRange"

Definition at line 135 of file statistics_accumulator.c.

◆ __FUNCT__ [5/5]

#define __FUNCT__   "PicurvWindowAccumulate"

Definition at line 135 of file statistics_accumulator.c.

Enumeration Type Documentation

◆ DerivedKind

Output kinds a postprocessing recipe may request, in enumeration order.

Enumerator
DERIVED_MEAN 
DERIVED_REYNOLDS_STRESS 
DERIVED_RMS 
DERIVED_TKE 
DERIVED_FLUX 
DERIVED_KIND_COUNT 

Definition at line 380 of file statistics_accumulator.c.

380 {
381 DERIVED_MEAN = 0,
DerivedKind
Output kinds a postprocessing recipe may request, in enumeration order.
@ DERIVED_REYNOLDS_STRESS
@ DERIVED_KIND_COUNT

Function Documentation

◆ ProductDiagonalIndex()

static PetscInt ProductDiagonalIndex ( PetscInt  component)
static

Internal helper: the product index carrying one component's own variance.

Local to this translation unit. Found by searching the pair table for the entry pairing a component with itself, so the diagonal cannot be stated separately from the order it belongs to.

Definition at line 56 of file statistics_accumulator.c.

57{
58 for (PetscInt c = 0; c < 6; ++c) {
59 if (kProductFirst[c] == component && kProductSecond[c] == component) return c;
60 }
61 return -1;
62}
static const PetscInt kProductFirst[6]
Upper-triangular row-major component pairs for a three-vector self-product.
static const PetscInt kProductSecond[6]
Here is the caller graph for this function:

◆ ProductComponentLabel()

static PetscErrorCode ProductComponentLabel ( PetscInt  index,
char *  out,
size_t  size 
)
static

Internal helper: writes the two-axis label of one product component.

Local to this translation unit. Built from the pair table, so "xy" and the slot it names can never refer to different pairs.

Definition at line 69 of file statistics_accumulator.c.

70{
71 PetscFunctionBeginUser;
72 PetscCheck(index >= 0 && index < 6, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE,
73 "Product component %" PetscInt_FMT " is outside the symmetric set.", index);
74 PetscCall(PetscSNPrintf(out, size, "%s%s", kAxisName[kProductFirst[index]],
75 kAxisName[kProductSecond[index]]));
76 PetscFunctionReturn(0);
77}
static const char *const kAxisName[3]
Axis labels indexing the pair table above.
Here is the caller graph for this function:

◆ PicurvProductComponentCount()

PetscErrorCode PicurvProductComponentCount ( PetscInt  dof,
PetscInt *  count 
)

Implementation of PicurvProductComponentCount().

Reports how many symmetric product components a field's second moment needs.

See also
PicurvProductComponentCount()

Definition at line 83 of file statistics_accumulator.c.

84{
85 PetscFunctionBeginUser;
86 PetscCheck(count != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Count output is required.");
87 PetscCheck(dof == 1 || dof == 3, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE,
88 "Self-products are supported for scalar and three-vector fields, got dof %" PetscInt_FMT ".", dof);
89 *count = (dof == 1) ? 1 : 6;
90 PetscFunctionReturn(0);
91}
Here is the caller graph for this function:

◆ PicurvCovarianceComponentCount()

PetscErrorCode PicurvCovarianceComponentCount ( PetscInt  dof_a,
PetscInt  dof_b,
PetscInt *  count 
)

Implementation of PicurvCovarianceComponentCount().

Reports how many components a covariance between two fields needs.

See also
PicurvCovarianceComponentCount()

Definition at line 97 of file statistics_accumulator.c.

98{
99 PetscFunctionBeginUser;
100 PetscCheck(count != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Count output is required.");
101 /* Scalar-scalar and vector-scalar pairs only; a vector-vector cross product
102 * would need a nine-component carrier that nothing allocates. */
103 PetscCheck((dof_a == 1 && dof_b == 1) || (dof_a == 3 && dof_b == 1) || (dof_a == 1 && dof_b == 3),
104 PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE,
105 "Covariance is supported for scalar-scalar and vector-scalar pairs, got dof %" PetscInt_FMT
106 " and %" PetscInt_FMT ".", dof_a, dof_b);
107 *count = (dof_a == 3 || dof_b == 3) ? 3 : 1;
108 PetscFunctionReturn(0);
109}
Here is the caller graph for this function:

◆ PicurvStatisticsComponentDM()

PetscErrorCode PicurvStatisticsComponentDM ( UserCtx *  user,
PetscInt  components,
DM *  dm 
)

Implementation of PicurvStatisticsComponentDM().

Resolves the DM carrying a given number of accumulator components.

See also
PicurvStatisticsComponentDM()

Definition at line 115 of file statistics_accumulator.c.

116{
117 PetscFunctionBeginUser;
118 PetscCheck(user != NULL && dm != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
119 "Context and DM output are required.");
120 switch (components) {
121 case 1: *dm = user->da; break;
122 case 3: *dm = user->fda; break;
123 case 6: *dm = user->fda6; break;
124 default:
125 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE,
126 "No block DM carries %" PetscInt_FMT " accumulator components.", components);
127 }
128 PetscCheck(*dm != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE,
129 "The block DM for %" PetscInt_FMT " accumulator components was never created.",
130 components);
131 PetscFunctionReturn(0);
132}
Here is the caller graph for this function:

◆ PicurvWindowStorageCreate()

PetscErrorCode PicurvWindowStorageCreate ( UserCtx *  user,
const PicurvWindowDefinition *  definition,
PicurvWindowStorage *  storage 
)

Implementation of PicurvWindowStorageCreate().

Allocates the accumulator state one window owns on one block.

See also
PicurvWindowStorageCreate()

Definition at line 140 of file statistics_accumulator.c.

142{
143 PetscFunctionBeginUser;
145 PetscCheck(user != NULL && definition != NULL && storage != NULL,
146 PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Context, definition, and storage are required.");
147 PetscCall(PetscMemzero(storage, sizeof(*storage)));
148 storage->field_count = definition->field_count;
149 storage->covariance_count = definition->covariance_count;
150
151 /* Occupancy is per point and scalar regardless of what is accumulated. */
152 PetscCall(DMCreateGlobalVector(user->da, &storage->count));
153 PetscCall(VecSet(storage->count, 0.0));
154 PetscCall(DMCreateGlobalVector(user->da, &storage->weight));
155 PetscCall(VecSet(storage->weight, 0.0));
156 PetscCall(DMCreateGlobalVector(user->da, &storage->weight_sq));
157 PetscCall(VecSet(storage->weight_sq, 0.0));
158
159 if (definition->field_count > 0) {
160 PetscCall(PetscCalloc1((size_t)definition->field_count, &storage->mean));
161 PetscCall(PetscCalloc1((size_t)definition->field_count, &storage->m2));
162 }
163 for (PetscInt field_index = 0; field_index < definition->field_count; ++field_index) {
164 FieldView view;
165 PetscInt components = 0;
166 DM product_dm = NULL;
167
168 PetscCall(FieldGetView(user, (FieldId)definition->fields[field_index].field_id, &view));
169 /* The mean matches the source field's own layout, so it comes from that
170 * field's DM and inherits its decomposition. */
171 PetscCall(DMCreateGlobalVector(view.dm, &storage->mean[field_index]));
172 PetscCall(VecSet(storage->mean[field_index], 0.0));
173 if (!definition->fields[field_index].want_second) continue;
174 PetscCall(PicurvProductComponentCount(view.descriptor->dof, &components));
175 PetscCall(PicurvStatisticsComponentDM(user, components, &product_dm));
176 PetscCall(DMCreateGlobalVector(product_dm, &storage->m2[field_index]));
177 PetscCall(VecSet(storage->m2[field_index], 0.0));
178 }
179
180 if (definition->covariance_count > 0) {
181 PetscCall(PetscCalloc1((size_t)definition->covariance_count, &storage->cm));
182 }
183 for (PetscInt pair_index = 0; pair_index < definition->covariance_count; ++pair_index) {
184 FieldView view_a, view_b;
185 PetscInt components = 0;
186 DM pair_dm = NULL;
187
188 PetscCall(FieldGetView(user, (FieldId)definition->covariances[pair_index].first, &view_a));
189 PetscCall(FieldGetView(user, (FieldId)definition->covariances[pair_index].second, &view_b));
190 PetscCall(PicurvCovarianceComponentCount(view_a.descriptor->dof, view_b.descriptor->dof, &components));
191 PetscCall(PicurvStatisticsComponentDM(user, components, &pair_dm));
192 PetscCall(DMCreateGlobalVector(pair_dm, &storage->cm[pair_index]));
193 PetscCall(VecSet(storage->cm[pair_index], 0.0));
194 }
195
196 /* Report what this window costs and what it covers, once per block at setup.
197 * The point count is a function of layout, dimensions, and periodicity alone, so
198 * it tells an operator what "per point" will mean before any sample is taken. */
199 {
201 PetscInt payloads = 0;
202 PetscInt points = 0;
203
204 PetscCall(PicurvWindowStoragePayloadCount(storage, &payloads));
205 PetscCall(SpatialTargetPlanCreate(user, (FieldId)definition->fields[0].field_id,
207 PetscCall(SpatialTargetPlanGlobalPointCount(&plan, PETSC_COMM_WORLD, &points));
209 "Statistics window '%s' block %d: %d accumulator vector(s) over %d point(s).\n",
210 definition->name, (int)user->_this, (int)payloads, (int)points);
211 }
213 PetscFunctionReturn(0);
214}
const FieldDescriptor * descriptor
PetscErrorCode FieldGetView(UserCtx *user, FieldId field_id, FieldView *view)
Resolve the existing DM and global/local vectors for one field.
FieldId
Compile-time identity for a catalogued Eulerian field.
Non-owning runtime objects resolved for one field and UserCtx.
#define LOCAL
Logging scope definitions for controlling message output.
Definition logging.h:45
#define LOG_ALLOW(scope, level, fmt,...)
Logging macro that checks both the log level and whether the calling function is in the allowed-funct...
Definition logging.h:200
#define PROFILE_FUNCTION_END
Marks the end of a profiled code block.
Definition logging.h:894
@ LOG_DEBUG
Detailed debugging information.
Definition logging.h:32
#define PROFILE_FUNCTION_BEGIN
Marks the beginning of a profiled code block (typically a function).
Definition logging.h:885
PetscErrorCode PicurvProductComponentCount(PetscInt dof, PetscInt *count)
Implementation of PicurvProductComponentCount().
PetscErrorCode PicurvCovarianceComponentCount(PetscInt dof_a, PetscInt dof_b, PetscInt *count)
Implementation of PicurvCovarianceComponentCount().
PetscErrorCode PicurvStatisticsComponentDM(UserCtx *user, PetscInt components, DM *dm)
Implementation of PicurvStatisticsComponentDM().
PetscErrorCode PicurvWindowStoragePayloadCount(const PicurvWindowStorage *storage, PetscInt *count)
Implementation of PicurvWindowStoragePayloadCount().
Vec weight
Per-point valid weight.
Vec weight_sq
Per-point squared-weight sum.
PetscInt field_count
Fields accumulated.
PetscInt covariance_count
Covariance pairs accumulated.
Vec * mean
One per field, matching that field's layout.
Vec * m2
One per field; NULL when no second moment was requested.
Vec count
Per-point accepted sample count.
Vec * cm
One per covariance pair.
@ PICURV_STATISTICS_MASK_FLUID
PetscErrorCode SpatialTargetPlanGlobalPointCount(const SpatialTargetPlan *plan, MPI_Comm comm, PetscInt *count)
Counts the points contributed across a communicator.
PetscErrorCode SpatialTargetPlanCreate(UserCtx *user, FieldId field_id, PicurvStatisticsMask mask, SpatialTargetPlan *plan)
Resolves the iteration domain for one field on one block.
Resolved iteration domain for one field on one block.
PetscInt first
First member; must also appear in the field list.
PicurvWindowFieldRequest fields[16]
PetscBool want_second
Also keep the centered second moment.
PetscInt second
Second member; must also appear in the field list.
PicurvWindowCovarianceRequest covariances[16]
PetscInt field_id
Catalogued Eulerian field identity.
PetscInt _this
Definition variables.h:1091
Here is the call graph for this function:
Here is the caller graph for this function:

◆ PicurvWindowStorageDestroy()

PetscErrorCode PicurvWindowStorageDestroy ( PicurvWindowStorage *  storage)

Implementation of PicurvWindowStorageDestroy().

Releases accumulator state previously created for one window.

See also
PicurvWindowStorageDestroy()

Definition at line 220 of file statistics_accumulator.c.

221{
222 PetscFunctionBeginUser;
223 if (storage == NULL) PetscFunctionReturn(0);
224 if (storage->count) PetscCall(VecDestroy(&storage->count));
225 if (storage->weight) PetscCall(VecDestroy(&storage->weight));
226 if (storage->weight_sq) PetscCall(VecDestroy(&storage->weight_sq));
227 for (PetscInt field_index = 0; storage->mean && field_index < storage->field_count; ++field_index) {
228 if (storage->mean[field_index]) PetscCall(VecDestroy(&storage->mean[field_index]));
229 }
230 for (PetscInt field_index = 0; storage->m2 && field_index < storage->field_count; ++field_index) {
231 if (storage->m2[field_index]) PetscCall(VecDestroy(&storage->m2[field_index]));
232 }
233 for (PetscInt pair_index = 0; storage->cm && pair_index < storage->covariance_count; ++pair_index) {
234 if (storage->cm[pair_index]) PetscCall(VecDestroy(&storage->cm[pair_index]));
235 }
236 if (storage->mean) PetscCall(PetscFree(storage->mean));
237 if (storage->m2) PetscCall(PetscFree(storage->m2));
238 if (storage->cm) PetscCall(PetscFree(storage->cm));
239 PetscCall(PetscMemzero(storage, sizeof(*storage)));
240 PetscFunctionReturn(0);
241}
Here is the caller graph for this function:

◆ FindFieldSlot()

static PetscErrorCode FindFieldSlot ( const PicurvWindowDefinition *  definition,
PetscInt  field_id,
PetscInt *  slot 
)
static

Internal helper: locates the storage slot holding one field's running mean.

Local to this translation unit. A covariance member must also appear in the window's field list, because the co-moment update needs that field's running mean; this is where that requirement is enforced.

Definition at line 249 of file statistics_accumulator.c.

251{
252 PetscFunctionBeginUser;
253 for (PetscInt field_index = 0; field_index < definition->field_count; ++field_index) {
254 if (definition->fields[field_index].field_id == field_id) {
255 *slot = field_index;
256 PetscFunctionReturn(0);
257 }
258 }
259 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE,
260 "Window '%s' requests a covariance over field '%s', which is not in its field list.",
261 definition->name, FieldCanonicalName((FieldId)field_id));
262}
const char * FieldCanonicalName(FieldId field_id)
Return the canonical printable name for an ID.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ PicurvWindowStoragePayloadCount()

PetscErrorCode PicurvWindowStoragePayloadCount ( const PicurvWindowStorage *  storage,
PetscInt *  count 
)

Implementation of PicurvWindowStoragePayloadCount().

Reports how many checkpointable vectors one window's storage holds.

See also
PicurvWindowStoragePayloadCount()

Definition at line 268 of file statistics_accumulator.c.

269{
270 PetscFunctionBeginUser;
271 PetscCheck(storage != NULL && count != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
272 "Storage and count output are required.");
273 *count = 3 + storage->field_count + storage->covariance_count;
274 /* A field without a requested second moment holds no product vector, so the
275 * enumeration skips it rather than emitting an empty payload. */
276 for (PetscInt field_index = 0; storage->m2 && field_index < storage->field_count; ++field_index) {
277 if (storage->m2[field_index]) *count += 1;
278 }
279 PetscFunctionReturn(0);
280}
Here is the caller graph for this function:

◆ PicurvWindowStoragePayload()

PetscErrorCode PicurvWindowStoragePayload ( UserCtx *  user,
const PicurvWindowDefinition *  definition,
const PicurvWindowStorage *  storage,
PetscInt  index,
PicurvStatisticsPayload *  payload 
)

Implementation of PicurvWindowStoragePayload().

Resolves one enumerated payload of a window's storage.

See also
PicurvWindowStoragePayload()

Definition at line 286 of file statistics_accumulator.c.

289{
290 PetscInt total = 0;
291 PetscInt cursor = index;
292
293 PetscFunctionBeginUser;
294 PetscCheck(user != NULL && definition != NULL && storage != NULL && payload != NULL,
295 PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
296 "Context, definition, storage, and payload output are required.");
297 PetscCall(PicurvWindowStoragePayloadCount(storage, &total));
298 PetscCheck(index >= 0 && index < total, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE,
299 "Payload index %" PetscInt_FMT " is outside [0, %" PetscInt_FMT ").", index, total);
300 PetscCall(PetscMemzero(payload, sizeof(*payload)));
301
302 /* Occupancy first, then means, then products, then co-moments. The order is the
303 * persistence contract: the manifest inventory, the writer, and the reader all
304 * walk it identically. */
305 if (cursor < 3) {
306 static const char *const occupancy_name[3] = {"count", "weight", "weight_sq"};
307 Vec occupancy[3];
308
309 occupancy[0] = storage->count;
310 occupancy[1] = storage->weight;
311 occupancy[2] = storage->weight_sq;
312 PetscCall(PetscStrncpy(payload->name, occupancy_name[cursor], sizeof(payload->name)));
313 payload->vec = occupancy[cursor];
314 payload->components = 1;
315 payload->role = "occupancy";
317 PetscFunctionReturn(0);
318 }
319 cursor -= 3;
320
321 if (cursor < storage->field_count) {
322 const FieldDescriptor *descriptor = NULL;
323
324 PetscCall(FieldGetDescriptor((FieldId)definition->fields[cursor].field_id, &descriptor));
325 PetscCall(PetscSNPrintf(payload->name, sizeof(payload->name), "%s_mean",
326 descriptor->canonical_name));
327 payload->vec = storage->mean[cursor];
328 payload->components = descriptor->dof;
329 payload->role = "mean";
330 payload->layout = FieldLayoutName(descriptor->layout);
331 PetscFunctionReturn(0);
332 }
333 cursor -= storage->field_count;
334
335 {
336 PetscInt product_count = 0;
337 PetscInt seen = 0;
338
339 for (PetscInt field_index = 0; storage->m2 && field_index < storage->field_count; ++field_index) {
340 if (storage->m2[field_index]) ++product_count;
341 }
342 if (cursor < product_count) {
343 for (PetscInt field_index = 0; field_index < storage->field_count; ++field_index) {
344 const FieldDescriptor *descriptor = NULL;
345
346 if (!storage->m2[field_index]) continue;
347 if (seen++ != cursor) continue;
348 PetscCall(FieldGetDescriptor((FieldId)definition->fields[field_index].field_id, &descriptor));
349 PetscCall(PetscSNPrintf(payload->name, sizeof(payload->name), "%s_m2",
350 descriptor->canonical_name));
351 payload->vec = storage->m2[field_index];
352 PetscCall(PicurvProductComponentCount(descriptor->dof, &payload->components));
353 payload->role = "second_moment";
354 payload->layout = FieldLayoutName(descriptor->layout);
355 PetscFunctionReturn(0);
356 }
357 }
358 cursor -= product_count;
359 }
360
361 {
362 const FieldDescriptor *first = NULL;
363 const FieldDescriptor *second = NULL;
364
365 PetscCheck(cursor >= 0 && cursor < storage->covariance_count, PETSC_COMM_SELF, PETSC_ERR_PLIB,
366 "Payload index %" PetscInt_FMT " fell through the storage enumeration.", index);
367 PetscCall(FieldGetDescriptor((FieldId)definition->covariances[cursor].first, &first));
368 PetscCall(FieldGetDescriptor((FieldId)definition->covariances[cursor].second, &second));
369 PetscCall(PetscSNPrintf(payload->name, sizeof(payload->name), "%s_%s_cm",
370 first->canonical_name, second->canonical_name));
371 payload->vec = storage->cm[cursor];
372 PetscCall(PicurvCovarianceComponentCount(first->dof, second->dof, &payload->components));
373 payload->role = "co_moment";
374 payload->layout = FieldLayoutName(first->layout);
375 }
376 PetscFunctionReturn(0);
377}
FieldLayout layout
@ FIELD_LAYOUT_CELL_CENTERED
const char * canonical_name
const char * FieldLayoutName(FieldLayout layout)
Return a stable printable label for a field layout.
PetscErrorCode FieldGetDescriptor(FieldId field_id, const FieldDescriptor **descriptor)
Return immutable metadata for a valid field identifier.
Immutable metadata for one field identity.
PetscInt components
Degrees of freedom the vector carries.
Vec vec
Borrowed accumulator vector; never owned by the caller.
const char * role
Inventory role: occupancy, mean, second_moment, co_moment.
char name[96]
File basename, no extension.
const char * layout
Catalog layout name for the inventory entry.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ ParseDerivedKinds()

static PetscErrorCode ParseDerivedKinds ( const char *  outputs,
PetscBool  wanted[DERIVED_KIND_COUNT] 
)
static

Internal helper: reports which output kinds a recipe requested.

Local to this translation unit.

Definition at line 417 of file statistics_accumulator.c.

418{
420 char *cursor = buffer;
421
422 PetscFunctionBeginUser;
423 for (PetscInt k = 0; k < DERIVED_KIND_COUNT; ++k) wanted[k] = PETSC_FALSE;
424 PetscCheck(outputs != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Output list is required.");
425 PetscCall(PetscStrncpy(buffer, outputs, sizeof(buffer)));
426
427 while (cursor && *cursor) {
428 char *comma = strchr(cursor, ',');
429 PetscBool matched = PETSC_FALSE;
430
431 if (comma) *comma = '\0';
432 TrimWhitespace(cursor);
433 if (cursor[0] != '\0') {
434 for (PetscInt k = 0; k < DERIVED_KIND_COUNT; ++k) {
435 if (strcmp(cursor, kDerivedKindName[k])) continue;
436 wanted[k] = PETSC_TRUE;
437 matched = PETSC_TRUE;
438 break;
439 }
440 PetscCheck(matched, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG,
441 "Unknown field-statistics output '%s'. Available outputs are "
442 "mean, reynolds_stress, rms, tke, and flux.", cursor);
443 }
444 cursor = comma ? comma + 1 : NULL;
445 }
446 PetscFunctionReturn(0);
447}
void TrimWhitespace(char *str)
Removes leading and trailing ASCII whitespace from a mutable string.
Definition io.c:393
static const char *const kDerivedKindName[DERIVED_KIND_COUNT]
Recipe spellings of the output kinds.
#define STATISTICS_DERIVED_OUTPUT_LENGTH
Longest output list a recipe may request.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ DerivedKindExtent()

static PetscErrorCode DerivedKindExtent ( const PicurvWindowDefinition *  definition,
const PicurvWindowStorage *  storage,
DerivedKind  kind,
PetscInt *  count 
)
static

Internal helper: counts the derived fields each kind contributes.

Local to this translation unit. A kind contributes nothing when the state it needs was never accumulated, so a recipe may name every output without having to know which window carries which moment.

Definition at line 455 of file statistics_accumulator.c.

458{
459 PetscFunctionBeginUser;
460 *count = 0;
461 switch (kind) {
462 case DERIVED_MEAN:
463 *count = storage->field_count;
464 break;
466 case DERIVED_RMS:
467 for (PetscInt field_index = 0; storage->m2 && field_index < storage->field_count; ++field_index) {
468 const FieldDescriptor *descriptor = NULL;
469 PetscInt components = 0;
470
471 if (!storage->m2[field_index]) continue;
472 PetscCall(FieldGetDescriptor((FieldId)definition->fields[field_index].field_id, &descriptor));
473 PetscCall(PicurvProductComponentCount(descriptor->dof, &components));
474 /* A stress tensor emits every component; an RMS emits only the
475 * diagonal, because an off-diagonal has no square root to take. */
476 *count += (kind == DERIVED_REYNOLDS_STRESS) ? components : descriptor->dof;
477 }
478 break;
479 case DERIVED_TKE:
480 /* Turbulent kinetic energy is the trace of a three-vector's stress tensor,
481 * so it exists only for a vector field carrying a second moment. */
482 for (PetscInt field_index = 0; storage->m2 && field_index < storage->field_count; ++field_index) {
483 const FieldDescriptor *descriptor = NULL;
484
485 if (!storage->m2[field_index]) continue;
486 PetscCall(FieldGetDescriptor((FieldId)definition->fields[field_index].field_id, &descriptor));
487 if (descriptor->dof == 3) *count += 1;
488 }
489 break;
490 default:
491 *count = storage->covariance_count;
492 break;
493 }
494 PetscFunctionReturn(0);
495}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ PicurvWindowDerivedCount()

PetscErrorCode PicurvWindowDerivedCount ( const PicurvWindowDefinition *  definition,
const PicurvWindowStorage *  storage,
const char *  outputs,
PetscInt *  count 
)

Implementation of PicurvWindowDerivedCount().

Reports how many derived fields a requested output set produces.

See also
PicurvWindowDerivedCount()

Definition at line 501 of file statistics_accumulator.c.

504{
505 PetscBool wanted[DERIVED_KIND_COUNT];
506
507 PetscFunctionBeginUser;
508 PetscCheck(definition != NULL && storage != NULL && count != NULL,
509 PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Definition, storage, and count are required.");
510 PetscCall(ParseDerivedKinds(outputs, wanted));
511 *count = 0;
512 for (PetscInt k = 0; k < DERIVED_KIND_COUNT; ++k) {
513 PetscInt extent = 0;
514
515 if (!wanted[k]) continue;
516 PetscCall(DerivedKindExtent(definition, storage, (DerivedKind)k, &extent));
517 *count += extent;
518 }
519 PetscFunctionReturn(0);
520}
static PetscErrorCode ParseDerivedKinds(const char *outputs, PetscBool wanted[DERIVED_KIND_COUNT])
Internal helper: reports which output kinds a recipe requested.
static PetscErrorCode DerivedKindExtent(const PicurvWindowDefinition *definition, const PicurvWindowStorage *storage, DerivedKind kind, PetscInt *count)
Internal helper: counts the derived fields each kind contributes.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ SafeStandardDeviation()

static PetscErrorCode SafeStandardDeviation ( PetscReal  variance,
const char *  label,
PetscReal *  result 
)
static

Internal helper: takes a square root of a variance that may be barely negative.

Local to this translation unit. Centered accumulation can leave a variance a few ulps below zero when the signal is nearly constant. Clamping is confined to this one place, applies only under a root, and never touches stored state; a genuinely negative variance is a defect and is reported.

Definition at line 529 of file statistics_accumulator.c.

530{
531 PetscFunctionBeginUser;
532 if (variance >= 0.0) {
533 *result = PetscSqrtReal(variance);
534 PetscFunctionReturn(0);
535 }
536 PetscCheck(variance >= -PICURV_STATISTICS_VARIANCE_FLOOR, PETSC_COMM_SELF, PETSC_ERR_FP,
537 "Derived variance for '%s' is %g, which is too negative to be floating-point "
538 "cancellation; the accumulated state is inconsistent.", label, (double)variance);
539 *result = 0.0;
540 PetscFunctionReturn(0);
541}
#define PICURV_STATISTICS_VARIANCE_FLOOR
Tolerance within which a negative variance is treated as floating-point noise.
Here is the caller graph for this function:

◆ ResolveDerivedIndex()

static PetscErrorCode ResolveDerivedIndex ( const PicurvWindowDefinition *  definition,
const PicurvWindowStorage *  storage,
const PetscBool  wanted[DERIVED_KIND_COUNT],
PetscInt  index,
DerivedKind *  kind,
PetscInt *  offset 
)
static

Internal helper: resolves which kind and member one derived index selects.

Local to this translation unit. Walks the same order DerivedKindExtent counts, so the enumeration and the count cannot disagree.

Definition at line 548 of file statistics_accumulator.c.

552{
553 PetscFunctionBeginUser;
554 for (PetscInt k = 0; k < DERIVED_KIND_COUNT; ++k) {
555 PetscInt extent = 0;
556
557 if (!wanted[k]) continue;
558 PetscCall(DerivedKindExtent(definition, storage, (DerivedKind)k, &extent));
559 if (index < extent) {
560 *kind = (DerivedKind)k;
561 *offset = index;
562 PetscFunctionReturn(0);
563 }
564 index -= extent;
565 }
566 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE,
567 "Derived index is past the end of the requested output set.");
568}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ PicurvWindowDerive()

PetscErrorCode PicurvWindowDerive ( UserCtx *  user,
const PicurvWindowDefinition *  definition,
const PicurvWindowStorage *  storage,
const char *  outputs,
PetscInt  index,
Vec  scalar_target,
Vec  vector_target,
PicurvDerivedField *  field 
)

Implementation of PicurvWindowDerive().

Derives one output field from centered accumulator state.

See also
PicurvWindowDerive()

Definition at line 576 of file statistics_accumulator.c.

580{
581 PetscBool wanted[DERIVED_KIND_COUNT];
584 PetscReal ***weight_arr = NULL, ***weight_sq_arr = NULL, ***count_arr = NULL;
585 PetscScalar ****source = NULL, ****target = NULL;
586 const FieldDescriptor *descriptor = NULL;
587 DM source_dm = NULL;
588 Vec source_vec = NULL;
589 PetscInt offset = 0, slot = 0, member = 0;
590 /* Resolved where the branch knows which fields it read; 1.0 leaves the result in
591 * solver units, which is also what an undeclared scale falls back to. */
592 PetscReal dimensional_factor = 1.0;
593 /* The source's component count is not the output's: a scalar RMS reads a
594 * six-component tensor, so each needs its own DM. */
595 PetscInt source_components = 0;
596
597 PetscFunctionBeginUser;
599 PetscCheck(user != NULL && definition != NULL && storage != NULL && field != NULL,
600 PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
601 "Context, definition, storage, and output are required.");
602 PetscCheck(index >= 0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE,
603 "Derived index %" PetscInt_FMT " is negative.", index);
604 PetscCall(PetscMemzero(field, sizeof(*field)));
605 /* The output list is parsed once and the extents walked once. Resolving the
606 * index is what bounds-checks it, so counting first would repeat both. */
607 PetscCall(ParseDerivedKinds(outputs, wanted));
608 PetscCall(ResolveDerivedIndex(definition, storage, wanted, index, &kind, &offset));
609
610 /* Locate the accumulator slot and component this index selects. */
611 switch (kind) {
612 case DERIVED_MEAN:
613 slot = offset;
614 PetscCall(FieldGetDescriptor((FieldId)definition->fields[slot].field_id, &descriptor));
615 source_vec = storage->mean[slot];
616 field->components = descriptor->dof;
617 source_components = descriptor->dof;
618 PetscCall(PetscSNPrintf(field->name, sizeof(field->name), "%s_%s_mean",
619 definition->name, descriptor->canonical_name));
620 break;
621 case DERIVED_TKE:
622 for (slot = 0; slot < storage->field_count; ++slot) {
623 if (!storage->m2[slot]) continue;
624 PetscCall(FieldGetDescriptor((FieldId)definition->fields[slot].field_id, &descriptor));
625 if (descriptor->dof != 3) continue;
626 if (offset-- == 0) break;
627 }
628 source_vec = storage->m2[slot];
629 field->components = 1;
630 PetscCall(PicurvProductComponentCount(descriptor->dof, &source_components));
631 PetscCall(PetscSNPrintf(field->name, sizeof(field->name), "%s_%s_tke",
632 definition->name, descriptor->canonical_name));
633 break;
634 case DERIVED_FLUX:
635 slot = offset;
636 {
637 const FieldDescriptor *first = NULL;
638 const FieldDescriptor *second = NULL;
639
640 PetscCall(FieldGetDescriptor((FieldId)definition->covariances[slot].first, &first));
641 PetscCall(FieldGetDescriptor((FieldId)definition->covariances[slot].second, &second));
642 PetscCall(PicurvCovarianceComponentCount(first->dof, second->dof, &field->components));
643 source_components = field->components;
644 source_vec = storage->cm[slot];
645 if (user->simCtx->pps && user->simCtx->pps->dimensionalize) {
646 PetscReal first_scale = 1.0, second_scale = 1.0;
647
648 /* A co-moment carries the product of its two fields' dimensions. */
649 PetscCall(PicurvFieldReferenceScale(user->simCtx, first->canonical_name,
650 &first_scale, NULL, 0));
651 PetscCall(PicurvFieldReferenceScale(user->simCtx, second->canonical_name,
652 &second_scale, NULL, 0));
653 dimensional_factor = first_scale * second_scale;
654 }
655 PetscCall(PetscSNPrintf(field->name, sizeof(field->name), "%s_%s_%s_flux",
656 definition->name, first->canonical_name, second->canonical_name));
657 }
658 break;
659 default:
660 /* Stress and RMS both walk the fields carrying a product; stress emits every
661 * symmetric component, RMS only the diagonal. */
662 for (slot = 0; slot < storage->field_count; ++slot) {
663 PetscInt extent = 0;
664
665 if (!storage->m2[slot]) continue;
666 PetscCall(FieldGetDescriptor((FieldId)definition->fields[slot].field_id, &descriptor));
667 PetscCall(PicurvProductComponentCount(descriptor->dof, &extent));
668 if (kind == DERIVED_RMS) extent = descriptor->dof;
669 if (offset < extent) { member = offset; break; }
670 offset -= extent;
671 }
672 source_vec = storage->m2[slot];
673 field->components = 1;
674 PetscCall(PicurvProductComponentCount(descriptor->dof, &source_components));
675 if (kind == DERIVED_RMS) {
676 PetscCall(PetscSNPrintf(field->name, sizeof(field->name), "%s_%s_rms%s",
677 definition->name, descriptor->canonical_name,
678 descriptor->dof == 1 ? "" : kAxisName[member]));
679 } else if (descriptor->dof == 1) {
680 PetscCall(PetscSNPrintf(field->name, sizeof(field->name), "%s_%s_variance",
681 definition->name, descriptor->canonical_name));
682 } else {
683 char label[8];
684
685 PetscCall(ProductComponentLabel(member, label, sizeof(label)));
686 PetscCall(PetscSNPrintf(field->name, sizeof(field->name), "%s_%s_R_%s",
687 definition->name, descriptor->canonical_name, label));
688 }
689 break;
690 }
691
692 PetscCall(SpatialTargetPlanCreate(user, (FieldId)definition->fields[0].field_id,
694 PetscCall(PicurvStatisticsComponentDM(user, source_components, &source_dm));
695 {
696 Vec destination = (field->components == 1) ? scalar_target : vector_target;
697 DM destination_dm = NULL;
698
699 PetscCheck(destination != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
700 "Derived field '%s' needs a %d-component destination.",
701 field->name, (int)field->components);
702 PetscCall(PicurvStatisticsComponentDM(user, field->components, &destination_dm));
703 PetscCall(VecZeroEntries(destination));
704
705 PetscCall(DMDAVecGetArrayRead(user->da, storage->weight, &weight_arr));
706 PetscCall(DMDAVecGetArrayRead(user->da, storage->weight_sq, &weight_sq_arr));
707 PetscCall(DMDAVecGetArrayRead(user->da, storage->count, &count_arr));
708 PetscCall(DMDAVecGetArrayDOFRead(source_dm, source_vec, &source));
709 PetscCall(DMDAVecGetArrayDOF(destination_dm, destination, &target));
710
711 for (PetscInt k = plan.start[2]; k < plan.end[2]; ++k) {
712 for (PetscInt j = plan.start[1]; j < plan.end[1]; ++j) {
713 for (PetscInt i = plan.start[0]; i < plan.end[0]; ++i) {
715
716 /* A point the window never sampled has no average at all, so it
717 * is left at zero rather than divided by a zero weight. */
718 if (weight_arr[k][j][i] <= 0.0) continue;
719 pair.count = count_arr[k][j][i];
720 pair.weight = weight_arr[k][j][i];
721 pair.weight_sq = weight_sq_arr[k][j][i];
722 pair.mean_x = 0.0;
723 pair.mean_y = 0.0;
724
725 if (kind == DERIVED_MEAN) {
726 for (PetscInt c = 0; c < field->components; ++c) {
727 target[k][j][i][c] = source[k][j][i][c];
728 }
729 } else if (kind == DERIVED_TKE) {
730 /* The trace of the stress tensor, halved: components 0, 3
731 * and 5 are xx, yy and zz in the stored symmetric order. */
732 PetscReal trace = 0.0;
733
734 for (PetscInt c = 0; c < 3; ++c) {
735 pair.cm = source[k][j][i][ProductDiagonalIndex(c)];
736 trace += PicurvCoMomentStateCovariance(&pair);
737 }
738 target[k][j][i][0] = 0.5 * trace;
739 } else if (kind == DERIVED_RMS) {
740 PetscReal deviation = 0.0;
741
742 pair.cm = source[k][j][i][(descriptor->dof == 1)
743 ? 0 : ProductDiagonalIndex(member)];
745 field->name, &deviation));
746 target[k][j][i][0] = deviation;
747 } else if (kind == DERIVED_FLUX) {
748 for (PetscInt c = 0; c < field->components; ++c) {
749 pair.cm = source[k][j][i][c];
750 target[k][j][i][c] = PicurvCoMomentStateCovariance(&pair);
751 }
752 } else {
753 pair.cm = source[k][j][i][member];
754 target[k][j][i][0] = PicurvCoMomentStateCovariance(&pair);
755 }
756 }
757 }
758 }
759
760 PetscCall(DMDAVecRestoreArrayDOF(destination_dm, destination, &target));
761 PetscCall(DMDAVecRestoreArrayDOFRead(source_dm, source_vec, &source));
762 PetscCall(DMDAVecRestoreArrayRead(user->da, storage->count, &count_arr));
763 PetscCall(DMDAVecRestoreArrayRead(user->da, storage->weight_sq, &weight_sq_arr));
764 PetscCall(DMDAVecRestoreArrayRead(user->da, storage->weight, &weight_arr));
765
766 /* Applied once, here, so the VTK field and the convergence-history CSV - which
767 * both read this staging vector - cannot end up in different unit systems. */
768 if (user->simCtx->pps && user->simCtx->pps->dimensionalize) {
769 if (kind != DERIVED_FLUX && descriptor) {
770 PetscReal base = 1.0;
771
772 PetscCall(PicurvFieldReferenceScale(user->simCtx, descriptor->canonical_name,
773 &base, NULL, 0));
774 dimensional_factor = PetscPowRealInt(base, kDerivedKindScaleExponent[kind]);
775 }
776 if (PetscAbsReal(dimensional_factor - 1.0) > PETSC_MACHINE_EPSILON) {
777 PetscCall(VecScale(destination, dimensional_factor));
778 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Scaled derived '%s' by %.4e.\n",
779 kDerivedKindName[kind], (double)dimensional_factor);
780 }
781 }
782 }
784 PetscFunctionReturn(0);
785}
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
#define GLOBAL
Scope for global logging across all processes.
Definition logging.h:46
static PetscInt ProductDiagonalIndex(PetscInt component)
Internal helper: the product index carrying one component's own variance.
static PetscErrorCode ResolveDerivedIndex(const PicurvWindowDefinition *definition, const PicurvWindowStorage *storage, const PetscBool wanted[DERIVED_KIND_COUNT], PetscInt index, DerivedKind *kind, PetscInt *offset)
Internal helper: resolves which kind and member one derived index selects.
static PetscErrorCode ProductComponentLabel(PetscInt index, char *out, size_t size)
Internal helper: writes the two-axis label of one product component.
static const PetscInt kDerivedKindScaleExponent[DERIVED_KIND_COUNT]
Power of the source field's reference scale each derived kind carries.
static PetscErrorCode SafeStandardDeviation(PetscReal variance, const char *label, PetscReal *result)
Internal helper: takes a square root of a variance that may be barely negative.
PetscInt components
One or three.
char name[96]
Output field name, window qualified.
PetscReal weight_sq
Sum of squared weights W2.
PetscReal mean_y
Weighted mean of the second member.
PetscReal count
Number of accepted samples.
PetscReal cm
Centered co-moment sum C.
PetscReal PicurvCoMomentStateCovariance(const PicurvCoMomentState *state)
Returns the weighted covariance C/W, or zero when no weight accumulated.
PetscReal mean_x
Weighted mean of the first member.
PetscReal weight
Total weight W.
Weighted centered co-moment state for one ordered pair of quantities.
PetscInt end[3]
Exclusive end per dimension (i, j, k).
PetscInt start[3]
Inclusive start per dimension (i, j, k).
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:1076
PetscBool dimensionalize
Whether derived output leaves non-dimensional form, from global_operations.dimensionalize.
Definition variables.h:796
PostProcessParams * pps
Definition variables.h:1057
Here is the call graph for this function:
Here is the caller graph for this function:

◆ PicurvWindowSpatialMean()

PetscErrorCode PicurvWindowSpatialMean ( UserCtx *  user,
const PicurvWindowDefinition *  definition,
const PicurvWindowStorage *  storage,
Vec  field,
PetscReal *  mean 
)

Implementation of PicurvWindowSpatialMean().

Reports the spatial mean of a derived field over the points a window sampled.

See also
PicurvWindowSpatialMean()

Definition at line 793 of file statistics_accumulator.c.

796{
798 /* Every direction collapses: a window mean is one number for the domain. */
799 const PetscBool whole_domain[3] = {PETSC_TRUE, PETSC_TRUE, PETSC_TRUE};
800
801 PetscFunctionBeginUser;
803 PetscCheck(user != NULL && definition != NULL && storage != NULL && field != NULL && mean != NULL,
804 PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
805 "Context, definition, storage, field, and output are required.");
806 *mean = 0.0;
807 if (definition->field_count == 0) { PROFILE_FUNCTION_END; PetscFunctionReturn(0); }
808
809 PetscCall(SpatialTargetPlanCreate(user, (FieldId)definition->fields[0].field_id,
811 /* No denominator means each admitted point counts once, so the result is the mean
812 * of the field over those points rather than a volume-weighted average. The
813 * accumulated weight is passed as the inclusion mask: a point the window never
814 * sampled holds a zero that means "never measured", and averaging it in would
815 * scale the answer down by the fraction of the domain the window never covered. */
816 PetscCall(PicurvSpatialRatioAverage(user, &plan, field, NULL, storage->weight,
817 whole_domain, PETSC_COMM_WORLD, NULL, mean));
819 PetscFunctionReturn(0);
820}
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)
Averages two fields over a target domain and divides the results.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ PicurvWindowValidFractionRange()

PetscErrorCode PicurvWindowValidFractionRange ( UserCtx *  user,
const PicurvWindowDefinition *  definition,
const PicurvWindowStorage *  storage,
PetscInt  sample_count,
PetscReal *  minimum,
PetscReal *  maximum 
)

Implementation of PicurvWindowValidFractionRange().

Reports the range of per-point valid fraction across a window's domain.

See also
PicurvWindowValidFractionRange()

Definition at line 828 of file statistics_accumulator.c.

831{
833 PetscReal ***nvert = NULL;
834 PetscReal ***count_arr = NULL;
835 PetscReal local_min = PETSC_MAX_REAL;
836 PetscReal local_max = 0.0;
837 PetscReal reduced_min = 0.0, reduced_max = 0.0;
838
839 PetscFunctionBeginUser;
841 PetscCheck(user != NULL && definition != NULL && storage != NULL &&
842 minimum != NULL && maximum != NULL,
843 PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Context, definition, storage, and outputs are required.");
844 *minimum = 1.0;
845 *maximum = 0.0;
846 if (definition->field_count == 0 || sample_count <= 0) { PROFILE_FUNCTION_END; PetscFunctionReturn(0); }
847
848 PetscCall(SpatialTargetPlanCreate(user, (FieldId)definition->fields[0].field_id,
850 PetscCall(DMDAVecGetArrayRead(user->da, user->Nvert, &nvert));
851 PetscCall(DMDAVecGetArrayRead(user->da, storage->count, &count_arr));
852 for (PetscInt k = plan.start[2]; k < plan.end[2]; ++k) {
853 for (PetscInt j = plan.start[1]; j < plan.end[1]; ++j) {
854 for (PetscInt i = plan.start[0]; i < plan.end[0]; ++i) {
855 /* The mask is evaluated at the current state, but a point excluded
856 * now may have contributed earlier, so its stored count is what
857 * decides its fraction rather than its present eligibility. */
858 const PetscReal fraction = count_arr[k][j][i] / (PetscReal)sample_count;
859
860 local_min = PetscMin(local_min, fraction);
861 local_max = PetscMax(local_max, fraction);
862 }
863 }
864 }
865 PetscCall(DMDAVecRestoreArrayRead(user->da, storage->count, &count_arr));
866 PetscCall(DMDAVecRestoreArrayRead(user->da, user->Nvert, &nvert));
867
868 /* A rank owning no targeted point must not drag the minimum down, so its
869 * sentinel is neutral under the reduction rather than zero. */
870 PetscCallMPI(MPI_Allreduce(&local_min, &reduced_min, 1, MPIU_REAL, MPIU_MIN, PETSC_COMM_WORLD));
871 PetscCallMPI(MPI_Allreduce(&local_max, &reduced_max, 1, MPIU_REAL, MPIU_MAX, PETSC_COMM_WORLD));
872 *minimum = (reduced_min == PETSC_MAX_REAL) ? 1.0 : reduced_min;
873 *maximum = reduced_max;
875 PetscFunctionReturn(0);
876}
Vec Nvert
Definition variables.h:1113
Here is the call graph for this function:
Here is the caller graph for this function:

◆ PicurvWindowAccumulate()

PetscErrorCode PicurvWindowAccumulate ( UserCtx *  user,
const PicurvWindowDefinition *  definition,
PicurvWindowStorage *  storage,
PetscReal  weight 
)

Implementation of PicurvWindowAccumulate().

Applies one accepted completed state to a window's accumulators.

See also
PicurvWindowAccumulate()

Definition at line 884 of file statistics_accumulator.c.

886{
888 PetscReal ***nvert = NULL;
889 PetscReal ***count_arr = NULL, ***weight_arr = NULL, ***weight_sq_arr = NULL;
890
891 PetscFunctionBeginUser;
893 PetscCheck(user != NULL && definition != NULL && storage != NULL,
894 PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Context, definition, and storage are required.");
895 PetscCheck(weight > 0.0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE,
896 "Accepted states carry a positive weight, got %g.", (double)weight);
897 if (definition->field_count == 0) { PROFILE_FUNCTION_END; PetscFunctionReturn(0); }
898
899 /* Every requested field shares the pointwise cell-centered domain, so the plan
900 * is resolved once rather than per field. */
901 PetscCall(SpatialTargetPlanCreate(user, (FieldId)definition->fields[0].field_id,
903
904 PetscCall(DMDAVecGetArrayRead(user->da, user->Nvert, &nvert));
905 PetscCall(DMDAVecGetArray(user->da, storage->count, &count_arr));
906 PetscCall(DMDAVecGetArray(user->da, storage->weight, &weight_arr));
907 PetscCall(DMDAVecGetArray(user->da, storage->weight_sq, &weight_sq_arr));
908
909 /* Pass one advances per-point occupancy, so every field pass afterwards can
910 * recover the pre-state weight uniformly as (stored weight - this weight).
911 * Occupancy is per point and per accepted state, never per field. */
912 for (PetscInt k = plan.start[2]; k < plan.end[2]; ++k) {
913 for (PetscInt j = plan.start[1]; j < plan.end[1]; ++j) {
914 for (PetscInt i = plan.start[0]; i < plan.end[0]; ++i) {
915 if (!SpatialTargetPlanMaskAllows(&plan, nvert[k][j][i])) continue;
916 count_arr[k][j][i] += 1.0;
917 weight_arr[k][j][i] += weight;
918 weight_sq_arr[k][j][i] += weight * weight;
919 }
920 }
921 }
922
923 /* Pass two updates every cross-field co-moment. It runs before any mean is
924 * written back, because a co-moment needs the pre-update mean of *both*
925 * members, and pass three overwrites them field by field. */
926 for (PetscInt pair = 0; pair < definition->covariance_count; ++pair) {
927 FieldView view_a, view_b;
928 PetscScalar ****src_a = NULL, ****mean_a = NULL;
929 PetscScalar ****src_b = NULL, ****mean_b = NULL;
930 PetscScalar ****co_moment = NULL;
931 DM pair_dm = NULL;
932 PetscInt slot_a = 0, slot_b = 0, dof_a = 0, dof_b = 0, components = 0;
933
934 PetscCall(FindFieldSlot(definition, definition->covariances[pair].first, &slot_a));
935 PetscCall(FindFieldSlot(definition, definition->covariances[pair].second, &slot_b));
936 PetscCall(FieldGetView(user, (FieldId)definition->covariances[pair].first, &view_a));
937 PetscCall(FieldGetView(user, (FieldId)definition->covariances[pair].second, &view_b));
938 dof_a = view_a.descriptor->dof;
939 dof_b = view_b.descriptor->dof;
940 PetscCall(PicurvCovarianceComponentCount(dof_a, dof_b, &components));
941 PetscCall(PicurvStatisticsComponentDM(user, components, &pair_dm));
942
943 /* A component-indexed view serves every degree of freedom, so the loop
944 * below needs no scalar-versus-vector branching. */
945 PetscCall(DMDAVecGetArrayDOFRead(view_a.dm, view_a.global_vec, &src_a));
946 PetscCall(DMDAVecGetArrayDOFRead(view_a.dm, storage->mean[slot_a], &mean_a));
947 PetscCall(DMDAVecGetArrayDOFRead(view_b.dm, view_b.global_vec, &src_b));
948 PetscCall(DMDAVecGetArrayDOFRead(view_b.dm, storage->mean[slot_b], &mean_b));
949 PetscCall(DMDAVecGetArrayDOF(pair_dm, storage->cm[pair], &co_moment));
950
951 for (PetscInt k = plan.start[2]; k < plan.end[2]; ++k) {
952 for (PetscInt j = plan.start[1]; j < plan.end[1]; ++j) {
953 for (PetscInt i = plan.start[0]; i < plan.end[0]; ++i) {
954 if (!SpatialTargetPlanMaskAllows(&plan, nvert[k][j][i])) continue;
955 for (PetscInt c = 0; c < components; ++c) {
956 /* A scalar member contributes its single value against every
957 * component of a vector member, which is what makes a
958 * vector-scalar covariance a three-component object. */
959 const PetscInt component_a = (dof_a == 3) ? c : 0;
960 const PetscInt component_b = (dof_b == 3) ? c : 0;
962
963 state.count = count_arr[k][j][i] - 1.0;
964 state.weight = weight_arr[k][j][i] - weight;
965 state.weight_sq = weight_sq_arr[k][j][i] - weight * weight;
966 state.mean_x = mean_a[k][j][i][component_a];
967 state.mean_y = mean_b[k][j][i][component_b];
968 state.cm = co_moment[k][j][i][c];
969 PetscCall(PicurvCoMomentStateUpdate(&state,
970 src_a[k][j][i][component_a],
971 src_b[k][j][i][component_b], weight));
972 co_moment[k][j][i][c] = state.cm;
973 }
974 }
975 }
976 }
977
978 PetscCall(DMDAVecRestoreArrayDOF(pair_dm, storage->cm[pair], &co_moment));
979 PetscCall(DMDAVecRestoreArrayDOFRead(view_b.dm, storage->mean[slot_b], &mean_b));
980 PetscCall(DMDAVecRestoreArrayDOFRead(view_b.dm, view_b.global_vec, &src_b));
981 PetscCall(DMDAVecRestoreArrayDOFRead(view_a.dm, storage->mean[slot_a], &mean_a));
982 PetscCall(DMDAVecRestoreArrayDOFRead(view_a.dm, view_a.global_vec, &src_a));
983 }
984
985 /* Pass three updates each requested field's mean and, when asked, its centered
986 * self-product. A three-vector's self-product is the six symmetric co-moments
987 * between component pairs, not three per-component variances, so the co-moment
988 * kernel drives every product component including the diagonal. */
989 for (PetscInt field_index = 0; field_index < definition->field_count; ++field_index) {
990 FieldView view;
991 PetscScalar ****src = NULL, ****mean = NULL, ****product = NULL;
992 DM product_dm = NULL;
993 PetscInt dof = 0, components = 0;
994 const PetscBool second = definition->fields[field_index].want_second;
995
996 PetscCall(FieldGetView(user, (FieldId)definition->fields[field_index].field_id, &view));
997 dof = view.descriptor->dof;
998 PetscCall(DMDAVecGetArrayDOFRead(view.dm, view.global_vec, &src));
999 PetscCall(DMDAVecGetArrayDOF(view.dm, storage->mean[field_index], &mean));
1000 if (second) {
1001 PetscCall(PicurvProductComponentCount(dof, &components));
1002 PetscCall(PicurvStatisticsComponentDM(user, components, &product_dm));
1003 PetscCall(DMDAVecGetArrayDOF(product_dm, storage->m2[field_index], &product));
1004 }
1005
1006 for (PetscInt k = plan.start[2]; k < plan.end[2]; ++k) {
1007 for (PetscInt j = plan.start[1]; j < plan.end[1]; ++j) {
1008 for (PetscInt i = plan.start[0]; i < plan.end[0]; ++i) {
1009 PetscReal prior_weight = 0.0;
1010 PetscReal prior_weight_sq = 0.0;
1011 PetscReal prior_count = 0.0;
1012
1013 if (!SpatialTargetPlanMaskAllows(&plan, nvert[k][j][i])) continue;
1014
1015 prior_weight = weight_arr[k][j][i] - weight;
1016 prior_weight_sq = weight_sq_arr[k][j][i] - weight * weight;
1017 prior_count = count_arr[k][j][i] - 1.0;
1018
1019 /* Products are updated before the means are written back,
1020 * because the co-moment kernel needs the pre-update means. */
1021 for (PetscInt c = 0; c < components; ++c) {
1022 const PetscInt a = (dof == 1) ? 0 : kProductFirst[c];
1023 const PetscInt b = (dof == 1) ? 0 : kProductSecond[c];
1024 PicurvCoMomentState state;
1025
1026 state.count = prior_count;
1027 state.weight = prior_weight;
1028 state.weight_sq = prior_weight_sq;
1029 state.mean_x = mean[k][j][i][a];
1030 state.mean_y = mean[k][j][i][b];
1031 state.cm = product[k][j][i][c];
1032 PetscCall(PicurvCoMomentStateUpdate(&state, src[k][j][i][a],
1033 src[k][j][i][b], weight));
1034 product[k][j][i][c] = state.cm;
1035 }
1036
1037 for (PetscInt c = 0; c < dof; ++c) {
1038 PicurvMomentState moment;
1039
1040 moment.count = prior_count;
1041 moment.weight = prior_weight;
1042 moment.weight_sq = prior_weight_sq;
1043 moment.mean = mean[k][j][i][c];
1044 moment.m2 = 0.0;
1045 PetscCall(PicurvMomentStateUpdate(&moment, src[k][j][i][c], weight));
1046 mean[k][j][i][c] = moment.mean;
1047 }
1048 }
1049 }
1050 }
1051
1052 if (second) PetscCall(DMDAVecRestoreArrayDOF(product_dm, storage->m2[field_index], &product));
1053 PetscCall(DMDAVecRestoreArrayDOF(view.dm, storage->mean[field_index], &mean));
1054 PetscCall(DMDAVecRestoreArrayDOFRead(view.dm, view.global_vec, &src));
1055 }
1056
1057 PetscCall(DMDAVecRestoreArray(user->da, storage->weight_sq, &weight_sq_arr));
1058 PetscCall(DMDAVecRestoreArray(user->da, storage->weight, &weight_arr));
1059 PetscCall(DMDAVecRestoreArray(user->da, storage->count, &count_arr));
1060 PetscCall(DMDAVecRestoreArrayRead(user->da, user->Nvert, &nvert));
1062 PetscFunctionReturn(0);
1063}
static PetscErrorCode FindFieldSlot(const PicurvWindowDefinition *definition, PetscInt field_id, PetscInt *slot)
Internal helper: locates the storage slot holding one field's running mean.
PetscReal weight_sq
Sum of squared weights W2.
PetscErrorCode PicurvCoMomentStateUpdate(PicurvCoMomentState *state, PetscReal value_x, PetscReal value_y, PetscReal weight)
Applies one weighted paired sample to a co-moment accumulator.
PetscReal weight
Total weight W.
PetscReal m2
Centered second-moment sum M2.
PetscReal mean
Weighted mean mu.
PetscErrorCode PicurvMomentStateUpdate(PicurvMomentState *state, PetscReal value, PetscReal weight)
Applies one weighted sample to a scalar moment accumulator.
PetscReal count
Number of accepted samples.
Weighted centered state for one scalar quantity at one point.
PetscBool SpatialTargetPlanMaskAllows(const SpatialTargetPlan *plan, PetscReal nvert_value)
Reports whether a point passes the plan's mask.
Here is the call graph for this function:
Here is the caller graph for this function:

Variable Documentation

◆ kProductFirst

const PetscInt kProductFirst[6] = {0, 0, 0, 1, 1, 2}
static

Upper-triangular row-major component pairs for a three-vector self-product.

This pair table is the single definition of the symmetric component order. The diagonal positions and the two-axis component labels are both derived from it rather than restated, because accumulation reads the table while derivation reads what follows from it: a hand-written copy that drifted would mislabel every Reynolds stress and take the root of the wrong component, with nothing failing.

Definition at line 30 of file statistics_accumulator.c.

30{0, 0, 0, 1, 1, 2};

◆ kProductSecond

const PetscInt kProductSecond[6] = {0, 1, 2, 1, 2, 2}
static

Definition at line 31 of file statistics_accumulator.c.

31{0, 1, 2, 1, 2, 2};

◆ kAxisName

const char* const kAxisName[3] = {"x", "y", "z"}
static

Axis labels indexing the pair table above.

Definition at line 48 of file statistics_accumulator.c.

48{"x", "y", "z"};

◆ kDerivedKindName

const char* const kDerivedKindName[DERIVED_KIND_COUNT]
static
Initial value:
= {
"mean", "reynolds_stress", "rms", "tke", "flux"
}

Recipe spellings of the output kinds.

Definition at line 390 of file statistics_accumulator.c.

390 {
391 "mean", "reynolds_stress", "rms", "tke", "flux"
392};

◆ kDerivedKindScaleExponent

const PetscInt kDerivedKindScaleExponent[DERIVED_KIND_COUNT]
static
Initial value:
= {
1,
2,
1,
2,
0
}

Power of the source field's reference scale each derived kind carries.

A first moment and a standard deviation have the field's own units; a covariance, its trace, and a co-moment flux are quadratic in it. This is what the per-field scaling table alone cannot express, and why derived statistics were previously left non-dimensional rather than scaled by a velocity that would have been wrong for three of the five kinds.

Definition at line 403 of file statistics_accumulator.c.

403 {
404 1, /* mean - first moment, the field's own units */
405 2, /* reynolds_stress - covariance of one field with itself */
406 1, /* rms - standard deviation */
407 2, /* tke - half the trace of that covariance */
408 0 /* flux - a co-moment of two possibly different fields, so its
409 factor is their product and cannot be an exponent of
410 one scale; DERIVED_FLUX resolves it directly. */
411};