PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
Loading...
Searching...
No Matches
statistics_accumulator.c
Go to the documentation of this file.
1/**
2 * @file statistics_accumulator.c
3 * @brief Per-window PETSc accumulator storage and pointwise application.
4 *
5 * Full API contract is documented with the declarations in
6 * `include/statistics_accumulator.h`.
7 */
8
10#include "statistics_moments.h"
11#include "statistics_target.h"
12#include "field_catalog.h"
13#include "io.h"
14#include "logging.h"
15
16/** @brief Longest output list a recipe may request. */
17#define STATISTICS_DERIVED_OUTPUT_LENGTH 256
18
19/**
20 * @brief Upper-triangular row-major component pairs for a three-vector self-product.
21 *
22 * This pair table is the single definition of the symmetric component order. The
23 * diagonal positions and the two-axis component labels are both derived from it
24 * rather than restated, because accumulation reads the table while derivation reads
25 * what follows from it: a hand-written copy that drifted would mislabel every
26 * Reynolds stress and take the root of the wrong component, with nothing failing.
27 */
28static const PetscInt kProductFirst[6] = {0, 0, 0, 1, 1, 2};
29static const PetscInt kProductSecond[6] = {0, 1, 2, 1, 2, 2};
30
31/** @brief Axis labels indexing the pair table above. */
32static const char *const kAxisName[3] = {"x", "y", "z"};
33
34/**
35 * @brief Internal helper: the product index carrying one component's own variance.
36 * @details Local to this translation unit. Found by searching the pair table for the
37 * entry pairing a component with itself, so the diagonal cannot be stated
38 * separately from the order it belongs to.
39 */
40static PetscInt ProductDiagonalIndex(PetscInt component)
41{
42 for (PetscInt c = 0; c < 6; ++c) {
43 if (kProductFirst[c] == component && kProductSecond[c] == component) return c;
44 }
45 return -1;
46}
47
48/**
49 * @brief Internal helper: writes the two-axis label of one product component.
50 * @details Local to this translation unit. Built from the pair table, so "xy" and the
51 * slot it names can never refer to different pairs.
52 */
53static PetscErrorCode ProductComponentLabel(PetscInt index, char *out, size_t size)
54{
55 PetscFunctionBeginUser;
56 PetscCheck(index >= 0 && index < 6, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE,
57 "Product component %" PetscInt_FMT " is outside the symmetric set.", index);
58 PetscCall(PetscSNPrintf(out, size, "%s%s", kAxisName[kProductFirst[index]],
59 kAxisName[kProductSecond[index]]));
60 PetscFunctionReturn(0);
61}
62
63/**
64 * @brief Implementation of \ref PicurvProductComponentCount().
65 * @see PicurvProductComponentCount()
66 */
67PetscErrorCode PicurvProductComponentCount(PetscInt dof, PetscInt *count)
68{
69 PetscFunctionBeginUser;
70 PetscCheck(count != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Count output is required.");
71 PetscCheck(dof == 1 || dof == 3, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE,
72 "Self-products are supported for scalar and three-vector fields, got dof %" PetscInt_FMT ".", dof);
73 *count = (dof == 1) ? 1 : 6;
74 PetscFunctionReturn(0);
75}
76
77/**
78 * @brief Implementation of \ref PicurvCovarianceComponentCount().
79 * @see PicurvCovarianceComponentCount()
80 */
81PetscErrorCode PicurvCovarianceComponentCount(PetscInt dof_a, PetscInt dof_b, PetscInt *count)
82{
83 PetscFunctionBeginUser;
84 PetscCheck(count != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Count output is required.");
85 /* Phase 2 accepts scalar-scalar and vector-scalar pairs; vector-vector cross
86 * products are an explicit non-goal. */
87 PetscCheck((dof_a == 1 && dof_b == 1) || (dof_a == 3 && dof_b == 1) || (dof_a == 1 && dof_b == 3),
88 PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE,
89 "Covariance is supported for scalar-scalar and vector-scalar pairs, got dof %" PetscInt_FMT
90 " and %" PetscInt_FMT ".", dof_a, dof_b);
91 *count = (dof_a == 3 || dof_b == 3) ? 3 : 1;
92 PetscFunctionReturn(0);
93}
94
95/**
96 * @brief Implementation of \ref PicurvStatisticsComponentDM().
97 * @see PicurvStatisticsComponentDM()
98 */
99PetscErrorCode PicurvStatisticsComponentDM(UserCtx *user, PetscInt components, DM *dm)
100{
101 PetscFunctionBeginUser;
102 PetscCheck(user != NULL && dm != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
103 "Context and DM output are required.");
104 switch (components) {
105 case 1: *dm = user->da; break;
106 case 3: *dm = user->fda; break;
107 case 6: *dm = user->fda6; break;
108 default:
109 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE,
110 "No block DM carries %" PetscInt_FMT " accumulator components.", components);
111 }
112 PetscCheck(*dm != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE,
113 "The block DM for %" PetscInt_FMT " accumulator components was never created.",
114 components);
115 PetscFunctionReturn(0);
116}
117
118/**
119 * @brief Implementation of \ref PicurvWindowStorageCreate().
120 * @see PicurvWindowStorageCreate()
121 */
122PetscErrorCode PicurvWindowStorageCreate(UserCtx *user, const PicurvWindowDefinition *definition,
123 PicurvWindowStorage *storage)
124{
125 PetscFunctionBeginUser;
126 PetscCheck(user != NULL && definition != NULL && storage != NULL,
127 PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Context, definition, and storage are required.");
128 PetscCall(PetscMemzero(storage, sizeof(*storage)));
129 storage->field_count = definition->field_count;
130 storage->covariance_count = definition->covariance_count;
131
132 /* Occupancy is per point and scalar regardless of what is accumulated. */
133 PetscCall(DMCreateGlobalVector(user->da, &storage->count));
134 PetscCall(VecSet(storage->count, 0.0));
135 PetscCall(DMCreateGlobalVector(user->da, &storage->weight));
136 PetscCall(VecSet(storage->weight, 0.0));
137 PetscCall(DMCreateGlobalVector(user->da, &storage->weight_sq));
138 PetscCall(VecSet(storage->weight_sq, 0.0));
139
140 if (definition->field_count > 0) {
141 PetscCall(PetscCalloc1((size_t)definition->field_count, &storage->mean));
142 PetscCall(PetscCalloc1((size_t)definition->field_count, &storage->m2));
143 }
144 for (PetscInt field_index = 0; field_index < definition->field_count; ++field_index) {
145 FieldView view;
146 PetscInt components = 0;
147 DM product_dm = NULL;
148
149 PetscCall(FieldGetView(user, (FieldId)definition->fields[field_index].field_id, &view));
150 /* The mean matches the source field's own layout, so it comes from that
151 * field's DM and inherits its decomposition. */
152 PetscCall(DMCreateGlobalVector(view.dm, &storage->mean[field_index]));
153 PetscCall(VecSet(storage->mean[field_index], 0.0));
154 if (!definition->fields[field_index].want_second) continue;
155 PetscCall(PicurvProductComponentCount(view.descriptor->dof, &components));
156 PetscCall(PicurvStatisticsComponentDM(user, components, &product_dm));
157 PetscCall(DMCreateGlobalVector(product_dm, &storage->m2[field_index]));
158 PetscCall(VecSet(storage->m2[field_index], 0.0));
159 }
160
161 if (definition->covariance_count > 0) {
162 PetscCall(PetscCalloc1((size_t)definition->covariance_count, &storage->cm));
163 }
164 for (PetscInt pair_index = 0; pair_index < definition->covariance_count; ++pair_index) {
165 FieldView view_a, view_b;
166 PetscInt components = 0;
167 DM pair_dm = NULL;
168
169 PetscCall(FieldGetView(user, (FieldId)definition->covariances[pair_index].first, &view_a));
170 PetscCall(FieldGetView(user, (FieldId)definition->covariances[pair_index].second, &view_b));
171 PetscCall(PicurvCovarianceComponentCount(view_a.descriptor->dof, view_b.descriptor->dof, &components));
172 PetscCall(PicurvStatisticsComponentDM(user, components, &pair_dm));
173 PetscCall(DMCreateGlobalVector(pair_dm, &storage->cm[pair_index]));
174 PetscCall(VecSet(storage->cm[pair_index], 0.0));
175 }
176
177 /* Report what this window costs and what it covers, once per block at setup.
178 * The point count is a function of layout, dimensions, and periodicity alone, so
179 * it tells an operator what "per point" will mean before any sample is taken. */
180 {
182 PetscInt payloads = 0;
183 PetscInt points = 0;
184
185 PetscCall(PicurvWindowStoragePayloadCount(storage, &payloads));
186 PetscCall(SpatialTargetPlanCreate(user, (FieldId)definition->fields[0].field_id,
188 PetscCall(SpatialTargetPlanGlobalPointCount(&plan, PETSC_COMM_WORLD, &points));
190 "Statistics window '%s' block %d: %d accumulator vector(s) over %d point(s).\n",
191 definition->name, (int)user->_this, (int)payloads, (int)points);
192 }
193 PetscFunctionReturn(0);
194}
195
196/**
197 * @brief Implementation of \ref PicurvWindowStorageDestroy().
198 * @see PicurvWindowStorageDestroy()
199 */
201{
202 PetscFunctionBeginUser;
203 if (storage == NULL) PetscFunctionReturn(0);
204 if (storage->count) PetscCall(VecDestroy(&storage->count));
205 if (storage->weight) PetscCall(VecDestroy(&storage->weight));
206 if (storage->weight_sq) PetscCall(VecDestroy(&storage->weight_sq));
207 for (PetscInt field_index = 0; storage->mean && field_index < storage->field_count; ++field_index) {
208 if (storage->mean[field_index]) PetscCall(VecDestroy(&storage->mean[field_index]));
209 }
210 for (PetscInt field_index = 0; storage->m2 && field_index < storage->field_count; ++field_index) {
211 if (storage->m2[field_index]) PetscCall(VecDestroy(&storage->m2[field_index]));
212 }
213 for (PetscInt pair_index = 0; storage->cm && pair_index < storage->covariance_count; ++pair_index) {
214 if (storage->cm[pair_index]) PetscCall(VecDestroy(&storage->cm[pair_index]));
215 }
216 if (storage->mean) PetscCall(PetscFree(storage->mean));
217 if (storage->m2) PetscCall(PetscFree(storage->m2));
218 if (storage->cm) PetscCall(PetscFree(storage->cm));
219 PetscCall(PetscMemzero(storage, sizeof(*storage)));
220 PetscFunctionReturn(0);
221}
222
223/**
224 * @brief Internal helper: locates the storage slot holding one field's running mean.
225 * @details Local to this translation unit. A covariance member must also appear in
226 * the window's field list, because the co-moment update needs that field's
227 * running mean; this is where that requirement is enforced.
228 */
229static PetscErrorCode FindFieldSlot(const PicurvWindowDefinition *definition, PetscInt field_id,
230 PetscInt *slot)
231{
232 PetscFunctionBeginUser;
233 for (PetscInt field_index = 0; field_index < definition->field_count; ++field_index) {
234 if (definition->fields[field_index].field_id == field_id) {
235 *slot = field_index;
236 PetscFunctionReturn(0);
237 }
238 }
239 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE,
240 "Window '%s' requests a covariance over field '%s', which is not in its field list.",
241 definition->name, FieldCanonicalName((FieldId)field_id));
242}
243
244/**
245 * @brief Implementation of \ref PicurvWindowStoragePayloadCount().
246 * @see PicurvWindowStoragePayloadCount()
247 */
248PetscErrorCode PicurvWindowStoragePayloadCount(const PicurvWindowStorage *storage, PetscInt *count)
249{
250 PetscFunctionBeginUser;
251 PetscCheck(storage != NULL && count != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
252 "Storage and count output are required.");
253 *count = 3 + storage->field_count + storage->covariance_count;
254 /* A field without a requested second moment holds no product vector, so the
255 * enumeration skips it rather than emitting an empty payload. */
256 for (PetscInt field_index = 0; storage->m2 && field_index < storage->field_count; ++field_index) {
257 if (storage->m2[field_index]) *count += 1;
258 }
259 PetscFunctionReturn(0);
260}
261
262/**
263 * @brief Implementation of \ref PicurvWindowStoragePayload().
264 * @see PicurvWindowStoragePayload()
265 */
266PetscErrorCode PicurvWindowStoragePayload(UserCtx *user, const PicurvWindowDefinition *definition,
267 const PicurvWindowStorage *storage, PetscInt index,
269{
270 PetscInt total = 0;
271 PetscInt cursor = index;
272
273 PetscFunctionBeginUser;
274 PetscCheck(user != NULL && definition != NULL && storage != NULL && payload != NULL,
275 PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
276 "Context, definition, storage, and payload output are required.");
277 PetscCall(PicurvWindowStoragePayloadCount(storage, &total));
278 PetscCheck(index >= 0 && index < total, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE,
279 "Payload index %" PetscInt_FMT " is outside [0, %" PetscInt_FMT ").", index, total);
280 PetscCall(PetscMemzero(payload, sizeof(*payload)));
281
282 /* Occupancy first, then means, then products, then co-moments. The order is the
283 * persistence contract: the manifest inventory, the writer, and the reader all
284 * walk it identically. */
285 if (cursor < 3) {
286 static const char *const occupancy_name[3] = {"count", "weight", "weight_sq"};
287 Vec occupancy[3];
288
289 occupancy[0] = storage->count;
290 occupancy[1] = storage->weight;
291 occupancy[2] = storage->weight_sq;
292 PetscCall(PetscStrncpy(payload->name, occupancy_name[cursor], sizeof(payload->name)));
293 payload->vec = occupancy[cursor];
294 payload->components = 1;
295 payload->role = "occupancy";
297 PetscFunctionReturn(0);
298 }
299 cursor -= 3;
300
301 if (cursor < storage->field_count) {
302 const FieldDescriptor *descriptor = NULL;
303
304 PetscCall(FieldGetDescriptor((FieldId)definition->fields[cursor].field_id, &descriptor));
305 PetscCall(PetscSNPrintf(payload->name, sizeof(payload->name), "%s_mean",
306 descriptor->canonical_name));
307 payload->vec = storage->mean[cursor];
308 payload->components = descriptor->dof;
309 payload->role = "mean";
310 payload->layout = FieldLayoutName(descriptor->layout);
311 PetscFunctionReturn(0);
312 }
313 cursor -= storage->field_count;
314
315 {
316 PetscInt product_count = 0;
317 PetscInt seen = 0;
318
319 for (PetscInt field_index = 0; storage->m2 && field_index < storage->field_count; ++field_index) {
320 if (storage->m2[field_index]) ++product_count;
321 }
322 if (cursor < product_count) {
323 for (PetscInt field_index = 0; field_index < storage->field_count; ++field_index) {
324 const FieldDescriptor *descriptor = NULL;
325
326 if (!storage->m2[field_index]) continue;
327 if (seen++ != cursor) continue;
328 PetscCall(FieldGetDescriptor((FieldId)definition->fields[field_index].field_id, &descriptor));
329 PetscCall(PetscSNPrintf(payload->name, sizeof(payload->name), "%s_m2",
330 descriptor->canonical_name));
331 payload->vec = storage->m2[field_index];
332 PetscCall(PicurvProductComponentCount(descriptor->dof, &payload->components));
333 payload->role = "second_moment";
334 payload->layout = FieldLayoutName(descriptor->layout);
335 PetscFunctionReturn(0);
336 }
337 }
338 cursor -= product_count;
339 }
340
341 {
342 const FieldDescriptor *first = NULL;
343 const FieldDescriptor *second = NULL;
344
345 PetscCheck(cursor >= 0 && cursor < storage->covariance_count, PETSC_COMM_SELF, PETSC_ERR_PLIB,
346 "Payload index %" PetscInt_FMT " fell through the storage enumeration.", index);
347 PetscCall(FieldGetDescriptor((FieldId)definition->covariances[cursor].first, &first));
348 PetscCall(FieldGetDescriptor((FieldId)definition->covariances[cursor].second, &second));
349 PetscCall(PetscSNPrintf(payload->name, sizeof(payload->name), "%s_%s_cm",
350 first->canonical_name, second->canonical_name));
351 payload->vec = storage->cm[cursor];
352 PetscCall(PicurvCovarianceComponentCount(first->dof, second->dof, &payload->components));
353 payload->role = "co_moment";
354 payload->layout = FieldLayoutName(first->layout);
355 }
356 PetscFunctionReturn(0);
357}
358
359/** @brief Output kinds a postprocessing recipe may request, in enumeration order. */
368
369/** @brief Recipe spellings of the output kinds. */
370static const char *const kDerivedKindName[DERIVED_KIND_COUNT] = {
371 "mean", "reynolds_stress", "rms", "tke", "flux"
372};
373
374/**
375 * @brief Internal helper: reports which output kinds a recipe requested.
376 * @details Local to this translation unit.
377 */
378static PetscErrorCode ParseDerivedKinds(const char *outputs, PetscBool wanted[DERIVED_KIND_COUNT])
379{
381 char *cursor = buffer;
382
383 PetscFunctionBeginUser;
384 for (PetscInt k = 0; k < DERIVED_KIND_COUNT; ++k) wanted[k] = PETSC_FALSE;
385 PetscCheck(outputs != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Output list is required.");
386 PetscCall(PetscStrncpy(buffer, outputs, sizeof(buffer)));
387
388 while (cursor && *cursor) {
389 char *comma = strchr(cursor, ',');
390 PetscBool matched = PETSC_FALSE;
391
392 if (comma) *comma = '\0';
393 TrimWhitespace(cursor);
394 if (cursor[0] != '\0') {
395 for (PetscInt k = 0; k < DERIVED_KIND_COUNT; ++k) {
396 if (strcmp(cursor, kDerivedKindName[k])) continue;
397 wanted[k] = PETSC_TRUE;
398 matched = PETSC_TRUE;
399 break;
400 }
401 PetscCheck(matched, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG,
402 "Unknown field-statistics output '%s'. Available outputs are "
403 "mean, reynolds_stress, rms, tke, and flux.", cursor);
404 }
405 cursor = comma ? comma + 1 : NULL;
406 }
407 PetscFunctionReturn(0);
408}
409
410/**
411 * @brief Internal helper: counts the derived fields each kind contributes.
412 * @details Local to this translation unit. A kind contributes nothing when the state
413 * it needs was never accumulated, so a recipe may name every output without
414 * having to know which window carries which moment.
415 */
416static PetscErrorCode DerivedKindExtent(const PicurvWindowDefinition *definition,
417 const PicurvWindowStorage *storage,
418 DerivedKind kind, PetscInt *count)
419{
420 PetscFunctionBeginUser;
421 *count = 0;
422 switch (kind) {
423 case DERIVED_MEAN:
424 *count = storage->field_count;
425 break;
427 case DERIVED_RMS:
428 for (PetscInt field_index = 0; storage->m2 && field_index < storage->field_count; ++field_index) {
429 const FieldDescriptor *descriptor = NULL;
430 PetscInt components = 0;
431
432 if (!storage->m2[field_index]) continue;
433 PetscCall(FieldGetDescriptor((FieldId)definition->fields[field_index].field_id, &descriptor));
434 PetscCall(PicurvProductComponentCount(descriptor->dof, &components));
435 /* A stress tensor emits every component; an RMS emits only the
436 * diagonal, because an off-diagonal has no square root to take. */
437 *count += (kind == DERIVED_REYNOLDS_STRESS) ? components : descriptor->dof;
438 }
439 break;
440 case DERIVED_TKE:
441 /* Turbulent kinetic energy is the trace of a three-vector's stress tensor,
442 * so it exists only for a vector field carrying a second moment. */
443 for (PetscInt field_index = 0; storage->m2 && field_index < storage->field_count; ++field_index) {
444 const FieldDescriptor *descriptor = NULL;
445
446 if (!storage->m2[field_index]) continue;
447 PetscCall(FieldGetDescriptor((FieldId)definition->fields[field_index].field_id, &descriptor));
448 if (descriptor->dof == 3) *count += 1;
449 }
450 break;
451 default:
452 *count = storage->covariance_count;
453 break;
454 }
455 PetscFunctionReturn(0);
456}
457
458/**
459 * @brief Implementation of \ref PicurvWindowDerivedCount().
460 * @see PicurvWindowDerivedCount()
461 */
462PetscErrorCode PicurvWindowDerivedCount(const PicurvWindowDefinition *definition,
463 const PicurvWindowStorage *storage,
464 const char *outputs, PetscInt *count)
465{
466 PetscBool wanted[DERIVED_KIND_COUNT];
467
468 PetscFunctionBeginUser;
469 PetscCheck(definition != NULL && storage != NULL && count != NULL,
470 PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Definition, storage, and count are required.");
471 PetscCall(ParseDerivedKinds(outputs, wanted));
472 *count = 0;
473 for (PetscInt k = 0; k < DERIVED_KIND_COUNT; ++k) {
474 PetscInt extent = 0;
475
476 if (!wanted[k]) continue;
477 PetscCall(DerivedKindExtent(definition, storage, (DerivedKind)k, &extent));
478 *count += extent;
479 }
480 PetscFunctionReturn(0);
481}
482
483/**
484 * @brief Internal helper: takes a square root of a variance that may be barely negative.
485 * @details Local to this translation unit. Centered accumulation can leave a variance
486 * a few ulps below zero when the signal is nearly constant. Clamping is
487 * confined to this one place, applies only under a root, and never touches
488 * stored state; a genuinely negative variance is a defect and is reported.
489 */
490static PetscErrorCode SafeStandardDeviation(PetscReal variance, const char *label, PetscReal *result)
491{
492 PetscFunctionBeginUser;
493 if (variance >= 0.0) {
494 *result = PetscSqrtReal(variance);
495 PetscFunctionReturn(0);
496 }
497 PetscCheck(variance >= -PICURV_STATISTICS_VARIANCE_FLOOR, PETSC_COMM_SELF, PETSC_ERR_FP,
498 "Derived variance for '%s' is %g, which is too negative to be floating-point "
499 "cancellation; the accumulated state is inconsistent.", label, (double)variance);
500 *result = 0.0;
501 PetscFunctionReturn(0);
502}
503
504/**
505 * @brief Internal helper: resolves which kind and member one derived index selects.
506 * @details Local to this translation unit. Walks the same order `DerivedKindExtent`
507 * counts, so the enumeration and the count cannot disagree.
508 */
509static PetscErrorCode ResolveDerivedIndex(const PicurvWindowDefinition *definition,
510 const PicurvWindowStorage *storage,
511 const PetscBool wanted[DERIVED_KIND_COUNT],
512 PetscInt index, DerivedKind *kind, PetscInt *offset)
513{
514 PetscFunctionBeginUser;
515 for (PetscInt k = 0; k < DERIVED_KIND_COUNT; ++k) {
516 PetscInt extent = 0;
517
518 if (!wanted[k]) continue;
519 PetscCall(DerivedKindExtent(definition, storage, (DerivedKind)k, &extent));
520 if (index < extent) {
521 *kind = (DerivedKind)k;
522 *offset = index;
523 PetscFunctionReturn(0);
524 }
525 index -= extent;
526 }
527 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE,
528 "Derived index is past the end of the requested output set.");
529}
530
531/**
532 * @brief Implementation of \ref PicurvWindowDerive().
533 * @see PicurvWindowDerive()
534 */
535PetscErrorCode PicurvWindowDerive(UserCtx *user, const PicurvWindowDefinition *definition,
536 const PicurvWindowStorage *storage, const char *outputs,
537 PetscInt index, Vec scalar_target, Vec vector_target,
538 PicurvDerivedField *field)
539{
540 PetscBool wanted[DERIVED_KIND_COUNT];
543 PetscReal ***weight_arr = NULL, ***weight_sq_arr = NULL, ***count_arr = NULL;
544 PetscScalar ****source = NULL, ****target = NULL;
545 const FieldDescriptor *descriptor = NULL;
546 DM source_dm = NULL;
547 Vec source_vec = NULL;
548 PetscInt offset = 0, slot = 0, member = 0;
549 /* The source's component count is not the output's: a scalar RMS reads a
550 * six-component tensor, so each needs its own DM. */
551 PetscInt source_components = 0;
552
553 PetscFunctionBeginUser;
554 PetscCheck(user != NULL && definition != NULL && storage != NULL && field != NULL,
555 PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
556 "Context, definition, storage, and output are required.");
557 PetscCheck(index >= 0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE,
558 "Derived index %" PetscInt_FMT " is negative.", index);
559 PetscCall(PetscMemzero(field, sizeof(*field)));
560 /* The output list is parsed once and the extents walked once. Resolving the
561 * index is what bounds-checks it, so counting first would repeat both. */
562 PetscCall(ParseDerivedKinds(outputs, wanted));
563 PetscCall(ResolveDerivedIndex(definition, storage, wanted, index, &kind, &offset));
564
565 /* Locate the accumulator slot and component this index selects. */
566 switch (kind) {
567 case DERIVED_MEAN:
568 slot = offset;
569 PetscCall(FieldGetDescriptor((FieldId)definition->fields[slot].field_id, &descriptor));
570 source_vec = storage->mean[slot];
571 field->components = descriptor->dof;
572 source_components = descriptor->dof;
573 PetscCall(PetscSNPrintf(field->name, sizeof(field->name), "%s_%s_mean",
574 definition->name, descriptor->canonical_name));
575 break;
576 case DERIVED_TKE:
577 for (slot = 0; slot < storage->field_count; ++slot) {
578 if (!storage->m2[slot]) continue;
579 PetscCall(FieldGetDescriptor((FieldId)definition->fields[slot].field_id, &descriptor));
580 if (descriptor->dof != 3) continue;
581 if (offset-- == 0) break;
582 }
583 source_vec = storage->m2[slot];
584 field->components = 1;
585 PetscCall(PicurvProductComponentCount(descriptor->dof, &source_components));
586 PetscCall(PetscSNPrintf(field->name, sizeof(field->name), "%s_%s_tke",
587 definition->name, descriptor->canonical_name));
588 break;
589 case DERIVED_FLUX:
590 slot = offset;
591 {
592 const FieldDescriptor *first = NULL;
593 const FieldDescriptor *second = NULL;
594
595 PetscCall(FieldGetDescriptor((FieldId)definition->covariances[slot].first, &first));
596 PetscCall(FieldGetDescriptor((FieldId)definition->covariances[slot].second, &second));
597 PetscCall(PicurvCovarianceComponentCount(first->dof, second->dof, &field->components));
598 source_components = field->components;
599 source_vec = storage->cm[slot];
600 PetscCall(PetscSNPrintf(field->name, sizeof(field->name), "%s_%s_%s_flux",
601 definition->name, first->canonical_name, second->canonical_name));
602 }
603 break;
604 default:
605 /* Stress and RMS both walk the fields carrying a product; stress emits every
606 * symmetric component, RMS only the diagonal. */
607 for (slot = 0; slot < storage->field_count; ++slot) {
608 PetscInt extent = 0;
609
610 if (!storage->m2[slot]) continue;
611 PetscCall(FieldGetDescriptor((FieldId)definition->fields[slot].field_id, &descriptor));
612 PetscCall(PicurvProductComponentCount(descriptor->dof, &extent));
613 if (kind == DERIVED_RMS) extent = descriptor->dof;
614 if (offset < extent) { member = offset; break; }
615 offset -= extent;
616 }
617 source_vec = storage->m2[slot];
618 field->components = 1;
619 PetscCall(PicurvProductComponentCount(descriptor->dof, &source_components));
620 if (kind == DERIVED_RMS) {
621 PetscCall(PetscSNPrintf(field->name, sizeof(field->name), "%s_%s_rms%s",
622 definition->name, descriptor->canonical_name,
623 descriptor->dof == 1 ? "" : kAxisName[member]));
624 } else if (descriptor->dof == 1) {
625 PetscCall(PetscSNPrintf(field->name, sizeof(field->name), "%s_%s_variance",
626 definition->name, descriptor->canonical_name));
627 } else {
628 char label[8];
629
630 PetscCall(ProductComponentLabel(member, label, sizeof(label)));
631 PetscCall(PetscSNPrintf(field->name, sizeof(field->name), "%s_%s_R_%s",
632 definition->name, descriptor->canonical_name, label));
633 }
634 break;
635 }
636
637 PetscCall(SpatialTargetPlanCreate(user, (FieldId)definition->fields[0].field_id,
639 PetscCall(PicurvStatisticsComponentDM(user, source_components, &source_dm));
640 {
641 Vec destination = (field->components == 1) ? scalar_target : vector_target;
642 DM destination_dm = NULL;
643
644 PetscCheck(destination != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
645 "Derived field '%s' needs a %d-component destination.",
646 field->name, (int)field->components);
647 PetscCall(PicurvStatisticsComponentDM(user, field->components, &destination_dm));
648 PetscCall(VecZeroEntries(destination));
649
650 PetscCall(DMDAVecGetArrayRead(user->da, storage->weight, &weight_arr));
651 PetscCall(DMDAVecGetArrayRead(user->da, storage->weight_sq, &weight_sq_arr));
652 PetscCall(DMDAVecGetArrayRead(user->da, storage->count, &count_arr));
653 PetscCall(DMDAVecGetArrayDOFRead(source_dm, source_vec, &source));
654 PetscCall(DMDAVecGetArrayDOF(destination_dm, destination, &target));
655
656 for (PetscInt k = plan.start[2]; k < plan.end[2]; ++k) {
657 for (PetscInt j = plan.start[1]; j < plan.end[1]; ++j) {
658 for (PetscInt i = plan.start[0]; i < plan.end[0]; ++i) {
660
661 /* A point the window never sampled has no average at all, so it
662 * is left at zero rather than divided by a zero weight. */
663 if (weight_arr[k][j][i] <= 0.0) continue;
664 pair.count = count_arr[k][j][i];
665 pair.weight = weight_arr[k][j][i];
666 pair.weight_sq = weight_sq_arr[k][j][i];
667 pair.mean_x = 0.0;
668 pair.mean_y = 0.0;
669
670 if (kind == DERIVED_MEAN) {
671 for (PetscInt c = 0; c < field->components; ++c) {
672 target[k][j][i][c] = source[k][j][i][c];
673 }
674 } else if (kind == DERIVED_TKE) {
675 /* The trace of the stress tensor, halved: components 0, 3
676 * and 5 are xx, yy and zz in the stored symmetric order. */
677 PetscReal trace = 0.0;
678
679 for (PetscInt c = 0; c < 3; ++c) {
680 pair.cm = source[k][j][i][ProductDiagonalIndex(c)];
681 trace += PicurvCoMomentStateCovariance(&pair);
682 }
683 target[k][j][i][0] = 0.5 * trace;
684 } else if (kind == DERIVED_RMS) {
685 PetscReal deviation = 0.0;
686
687 pair.cm = source[k][j][i][(descriptor->dof == 1)
688 ? 0 : ProductDiagonalIndex(member)];
690 field->name, &deviation));
691 target[k][j][i][0] = deviation;
692 } else if (kind == DERIVED_FLUX) {
693 for (PetscInt c = 0; c < field->components; ++c) {
694 pair.cm = source[k][j][i][c];
695 target[k][j][i][c] = PicurvCoMomentStateCovariance(&pair);
696 }
697 } else {
698 pair.cm = source[k][j][i][member];
699 target[k][j][i][0] = PicurvCoMomentStateCovariance(&pair);
700 }
701 }
702 }
703 }
704
705 PetscCall(DMDAVecRestoreArrayDOF(destination_dm, destination, &target));
706 PetscCall(DMDAVecRestoreArrayDOFRead(source_dm, source_vec, &source));
707 PetscCall(DMDAVecRestoreArrayRead(user->da, storage->count, &count_arr));
708 PetscCall(DMDAVecRestoreArrayRead(user->da, storage->weight_sq, &weight_sq_arr));
709 PetscCall(DMDAVecRestoreArrayRead(user->da, storage->weight, &weight_arr));
710 }
711 PetscFunctionReturn(0);
712}
713
714/**
715 * @brief Implementation of \ref PicurvWindowSpatialMean().
716 * @see PicurvWindowSpatialMean()
717 */
718PetscErrorCode PicurvWindowSpatialMean(UserCtx *user, const PicurvWindowDefinition *definition,
719 const PicurvWindowStorage *storage, Vec field,
720 PetscReal *mean)
721{
723 const PetscReal ***values = NULL;
724 PetscReal ***weight_arr = NULL;
725 PetscReal local_sum = 0.0;
726 PetscReal local_count = 0.0;
727 PetscReal totals[2] = {0.0, 0.0};
728 PetscReal reduced[2] = {0.0, 0.0};
729
730 PetscFunctionBeginUser;
731 PetscCheck(user != NULL && definition != NULL && storage != NULL && field != NULL && mean != NULL,
732 PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
733 "Context, definition, storage, field, and output are required.");
734 *mean = 0.0;
735 if (definition->field_count == 0) PetscFunctionReturn(0);
736
737 PetscCall(SpatialTargetPlanCreate(user, (FieldId)definition->fields[0].field_id,
739 PetscCall(DMDAVecGetArrayRead(user->da, field, &values));
740 PetscCall(DMDAVecGetArray(user->da, storage->weight, &weight_arr));
741 for (PetscInt k = plan.start[2]; k < plan.end[2]; ++k) {
742 for (PetscInt j = plan.start[1]; j < plan.end[1]; ++j) {
743 for (PetscInt i = plan.start[0]; i < plan.end[0]; ++i) {
744 /* A point with no accumulated weight holds a zero that means "never
745 * measured", so it is excluded from both the sum and the count. */
746 if (weight_arr[k][j][i] <= 0.0) continue;
747 local_sum += values[k][j][i];
748 local_count += 1.0;
749 }
750 }
751 }
752 PetscCall(DMDAVecRestoreArray(user->da, storage->weight, &weight_arr));
753 PetscCall(DMDAVecRestoreArrayRead(user->da, field, &values));
754
755 totals[0] = local_sum;
756 totals[1] = local_count;
757 PetscCallMPI(MPI_Allreduce(totals, reduced, 2, MPIU_REAL, MPIU_SUM, PETSC_COMM_WORLD));
758 if (reduced[1] > 0.0) *mean = reduced[0] / reduced[1];
759 PetscFunctionReturn(0);
760}
761
762/**
763 * @brief Implementation of \ref PicurvWindowValidFractionRange().
764 * @see PicurvWindowValidFractionRange()
765 */
766PetscErrorCode PicurvWindowValidFractionRange(UserCtx *user, const PicurvWindowDefinition *definition,
767 const PicurvWindowStorage *storage, PetscInt sample_count,
768 PetscReal *minimum, PetscReal *maximum)
769{
771 PetscReal ***nvert = NULL;
772 PetscReal ***count_arr = NULL;
773 PetscReal local_min = PETSC_MAX_REAL;
774 PetscReal local_max = 0.0;
775 PetscReal reduced_min = 0.0, reduced_max = 0.0;
776
777 PetscFunctionBeginUser;
778 PetscCheck(user != NULL && definition != NULL && storage != NULL &&
779 minimum != NULL && maximum != NULL,
780 PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Context, definition, storage, and outputs are required.");
781 *minimum = 1.0;
782 *maximum = 0.0;
783 if (definition->field_count == 0 || sample_count <= 0) PetscFunctionReturn(0);
784
785 PetscCall(SpatialTargetPlanCreate(user, (FieldId)definition->fields[0].field_id,
787 PetscCall(DMDAVecGetArrayRead(user->da, user->Nvert, &nvert));
788 PetscCall(DMDAVecGetArrayRead(user->da, storage->count, &count_arr));
789 for (PetscInt k = plan.start[2]; k < plan.end[2]; ++k) {
790 for (PetscInt j = plan.start[1]; j < plan.end[1]; ++j) {
791 for (PetscInt i = plan.start[0]; i < plan.end[0]; ++i) {
792 /* The mask is evaluated at the current state, but a point excluded
793 * now may have contributed earlier, so its stored count is what
794 * decides its fraction rather than its present eligibility. */
795 const PetscReal fraction = count_arr[k][j][i] / (PetscReal)sample_count;
796
797 local_min = PetscMin(local_min, fraction);
798 local_max = PetscMax(local_max, fraction);
799 }
800 }
801 }
802 PetscCall(DMDAVecRestoreArrayRead(user->da, storage->count, &count_arr));
803 PetscCall(DMDAVecRestoreArrayRead(user->da, user->Nvert, &nvert));
804
805 /* A rank owning no targeted point must not drag the minimum down, so its
806 * sentinel is neutral under the reduction rather than zero. */
807 PetscCallMPI(MPI_Allreduce(&local_min, &reduced_min, 1, MPIU_REAL, MPIU_MIN, PETSC_COMM_WORLD));
808 PetscCallMPI(MPI_Allreduce(&local_max, &reduced_max, 1, MPIU_REAL, MPIU_MAX, PETSC_COMM_WORLD));
809 *minimum = (reduced_min == PETSC_MAX_REAL) ? 1.0 : reduced_min;
810 *maximum = reduced_max;
811 PetscFunctionReturn(0);
812}
813
814/**
815 * @brief Implementation of \ref PicurvWindowAccumulate().
816 * @see PicurvWindowAccumulate()
817 */
818PetscErrorCode PicurvWindowAccumulate(UserCtx *user, const PicurvWindowDefinition *definition,
819 PicurvWindowStorage *storage, PetscReal weight)
820{
822 PetscReal ***nvert = NULL;
823 PetscReal ***count_arr = NULL, ***weight_arr = NULL, ***weight_sq_arr = NULL;
824
825 PetscFunctionBeginUser;
826 PetscCheck(user != NULL && definition != NULL && storage != NULL,
827 PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Context, definition, and storage are required.");
828 PetscCheck(weight > 0.0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE,
829 "Accepted states carry a positive weight, got %g.", (double)weight);
830 if (definition->field_count == 0) PetscFunctionReturn(0);
831
832 /* Every requested field shares the pointwise cell-centered domain in Phase 2,
833 * so the plan is resolved once rather than per field. */
834 PetscCall(SpatialTargetPlanCreate(user, (FieldId)definition->fields[0].field_id,
836
837 PetscCall(DMDAVecGetArrayRead(user->da, user->Nvert, &nvert));
838 PetscCall(DMDAVecGetArray(user->da, storage->count, &count_arr));
839 PetscCall(DMDAVecGetArray(user->da, storage->weight, &weight_arr));
840 PetscCall(DMDAVecGetArray(user->da, storage->weight_sq, &weight_sq_arr));
841
842 /* Pass one advances per-point occupancy, so every field pass afterwards can
843 * recover the pre-state weight uniformly as (stored weight - this weight).
844 * Occupancy is per point and per accepted state, never per field. */
845 for (PetscInt k = plan.start[2]; k < plan.end[2]; ++k) {
846 for (PetscInt j = plan.start[1]; j < plan.end[1]; ++j) {
847 for (PetscInt i = plan.start[0]; i < plan.end[0]; ++i) {
848 if (!SpatialTargetPlanMaskAllows(&plan, nvert[k][j][i])) continue;
849 count_arr[k][j][i] += 1.0;
850 weight_arr[k][j][i] += weight;
851 weight_sq_arr[k][j][i] += weight * weight;
852 }
853 }
854 }
855
856 /* Pass two updates every cross-field co-moment. It runs before any mean is
857 * written back, because a co-moment needs the pre-update mean of *both*
858 * members, and pass three overwrites them field by field. */
859 for (PetscInt pair = 0; pair < definition->covariance_count; ++pair) {
860 FieldView view_a, view_b;
861 PetscScalar ****src_a = NULL, ****mean_a = NULL;
862 PetscScalar ****src_b = NULL, ****mean_b = NULL;
863 PetscScalar ****co_moment = NULL;
864 DM pair_dm = NULL;
865 PetscInt slot_a = 0, slot_b = 0, dof_a = 0, dof_b = 0, components = 0;
866
867 PetscCall(FindFieldSlot(definition, definition->covariances[pair].first, &slot_a));
868 PetscCall(FindFieldSlot(definition, definition->covariances[pair].second, &slot_b));
869 PetscCall(FieldGetView(user, (FieldId)definition->covariances[pair].first, &view_a));
870 PetscCall(FieldGetView(user, (FieldId)definition->covariances[pair].second, &view_b));
871 dof_a = view_a.descriptor->dof;
872 dof_b = view_b.descriptor->dof;
873 PetscCall(PicurvCovarianceComponentCount(dof_a, dof_b, &components));
874 PetscCall(PicurvStatisticsComponentDM(user, components, &pair_dm));
875
876 /* A component-indexed view serves every degree of freedom, so the loop
877 * below needs no scalar-versus-vector branching. */
878 PetscCall(DMDAVecGetArrayDOFRead(view_a.dm, view_a.global_vec, &src_a));
879 PetscCall(DMDAVecGetArrayDOFRead(view_a.dm, storage->mean[slot_a], &mean_a));
880 PetscCall(DMDAVecGetArrayDOFRead(view_b.dm, view_b.global_vec, &src_b));
881 PetscCall(DMDAVecGetArrayDOFRead(view_b.dm, storage->mean[slot_b], &mean_b));
882 PetscCall(DMDAVecGetArrayDOF(pair_dm, storage->cm[pair], &co_moment));
883
884 for (PetscInt k = plan.start[2]; k < plan.end[2]; ++k) {
885 for (PetscInt j = plan.start[1]; j < plan.end[1]; ++j) {
886 for (PetscInt i = plan.start[0]; i < plan.end[0]; ++i) {
887 if (!SpatialTargetPlanMaskAllows(&plan, nvert[k][j][i])) continue;
888 for (PetscInt c = 0; c < components; ++c) {
889 /* A scalar member contributes its single value against every
890 * component of a vector member, which is what makes a
891 * vector-scalar covariance a three-component object. */
892 const PetscInt component_a = (dof_a == 3) ? c : 0;
893 const PetscInt component_b = (dof_b == 3) ? c : 0;
895
896 state.count = count_arr[k][j][i] - 1.0;
897 state.weight = weight_arr[k][j][i] - weight;
898 state.weight_sq = weight_sq_arr[k][j][i] - weight * weight;
899 state.mean_x = mean_a[k][j][i][component_a];
900 state.mean_y = mean_b[k][j][i][component_b];
901 state.cm = co_moment[k][j][i][c];
902 PetscCall(PicurvCoMomentStateUpdate(&state,
903 src_a[k][j][i][component_a],
904 src_b[k][j][i][component_b], weight));
905 co_moment[k][j][i][c] = state.cm;
906 }
907 }
908 }
909 }
910
911 PetscCall(DMDAVecRestoreArrayDOF(pair_dm, storage->cm[pair], &co_moment));
912 PetscCall(DMDAVecRestoreArrayDOFRead(view_b.dm, storage->mean[slot_b], &mean_b));
913 PetscCall(DMDAVecRestoreArrayDOFRead(view_b.dm, view_b.global_vec, &src_b));
914 PetscCall(DMDAVecRestoreArrayDOFRead(view_a.dm, storage->mean[slot_a], &mean_a));
915 PetscCall(DMDAVecRestoreArrayDOFRead(view_a.dm, view_a.global_vec, &src_a));
916 }
917
918 /* Pass three updates each requested field's mean and, when asked, its centered
919 * self-product. A three-vector's self-product is the six symmetric co-moments
920 * between component pairs, not three per-component variances, so the co-moment
921 * kernel drives every product component including the diagonal. */
922 for (PetscInt field_index = 0; field_index < definition->field_count; ++field_index) {
923 FieldView view;
924 PetscScalar ****src = NULL, ****mean = NULL, ****product = NULL;
925 DM product_dm = NULL;
926 PetscInt dof = 0, components = 0;
927 const PetscBool second = definition->fields[field_index].want_second;
928
929 PetscCall(FieldGetView(user, (FieldId)definition->fields[field_index].field_id, &view));
930 dof = view.descriptor->dof;
931 PetscCall(DMDAVecGetArrayDOFRead(view.dm, view.global_vec, &src));
932 PetscCall(DMDAVecGetArrayDOF(view.dm, storage->mean[field_index], &mean));
933 if (second) {
934 PetscCall(PicurvProductComponentCount(dof, &components));
935 PetscCall(PicurvStatisticsComponentDM(user, components, &product_dm));
936 PetscCall(DMDAVecGetArrayDOF(product_dm, storage->m2[field_index], &product));
937 }
938
939 for (PetscInt k = plan.start[2]; k < plan.end[2]; ++k) {
940 for (PetscInt j = plan.start[1]; j < plan.end[1]; ++j) {
941 for (PetscInt i = plan.start[0]; i < plan.end[0]; ++i) {
942 PetscReal prior_weight = 0.0;
943 PetscReal prior_weight_sq = 0.0;
944 PetscReal prior_count = 0.0;
945
946 if (!SpatialTargetPlanMaskAllows(&plan, nvert[k][j][i])) continue;
947
948 prior_weight = weight_arr[k][j][i] - weight;
949 prior_weight_sq = weight_sq_arr[k][j][i] - weight * weight;
950 prior_count = count_arr[k][j][i] - 1.0;
951
952 /* Products are updated before the means are written back,
953 * because the co-moment kernel needs the pre-update means. */
954 for (PetscInt c = 0; c < components; ++c) {
955 const PetscInt a = (dof == 1) ? 0 : kProductFirst[c];
956 const PetscInt b = (dof == 1) ? 0 : kProductSecond[c];
958
959 state.count = prior_count;
960 state.weight = prior_weight;
961 state.weight_sq = prior_weight_sq;
962 state.mean_x = mean[k][j][i][a];
963 state.mean_y = mean[k][j][i][b];
964 state.cm = product[k][j][i][c];
965 PetscCall(PicurvCoMomentStateUpdate(&state, src[k][j][i][a],
966 src[k][j][i][b], weight));
967 product[k][j][i][c] = state.cm;
968 }
969
970 for (PetscInt c = 0; c < dof; ++c) {
971 PicurvMomentState moment;
972
973 moment.count = prior_count;
974 moment.weight = prior_weight;
975 moment.weight_sq = prior_weight_sq;
976 moment.mean = mean[k][j][i][c];
977 moment.m2 = 0.0;
978 PetscCall(PicurvMomentStateUpdate(&moment, src[k][j][i][c], weight));
979 mean[k][j][i][c] = moment.mean;
980 }
981 }
982 }
983 }
984
985 if (second) PetscCall(DMDAVecRestoreArrayDOF(product_dm, storage->m2[field_index], &product));
986 PetscCall(DMDAVecRestoreArrayDOF(view.dm, storage->mean[field_index], &mean));
987 PetscCall(DMDAVecRestoreArrayDOFRead(view.dm, view.global_vec, &src));
988 }
989
990 PetscCall(DMDAVecRestoreArray(user->da, storage->weight_sq, &weight_sq_arr));
991 PetscCall(DMDAVecRestoreArray(user->da, storage->weight, &weight_arr));
992 PetscCall(DMDAVecRestoreArray(user->da, storage->count, &count_arr));
993 PetscCall(DMDAVecRestoreArrayRead(user->da, user->Nvert, &nvert));
994 PetscFunctionReturn(0);
995}
Authoritative identities and storage metadata for persistent Eulerian fields.
FieldLayout layout
const FieldDescriptor * descriptor
const char * FieldCanonicalName(FieldId field_id)
Return the canonical printable name for an ID.
PetscErrorCode FieldGetView(UserCtx *user, FieldId field_id, FieldView *view)
Resolve the existing DM and global/local vectors for one field.
@ 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.
FieldId
Compile-time identity for a catalogued Eulerian field.
Immutable metadata for one field identity.
Non-owning runtime objects resolved for one field and UserCtx.
Public interface for data input/output routines.
void TrimWhitespace(char *str)
Removes leading and trailing ASCII whitespace from a mutable string.
Definition io.c:399
Logging utilities and macros for PETSc-based applications.
#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
@ LOG_DEBUG
Detailed debugging information.
Definition logging.h:32
static const PetscInt kProductFirst[6]
Upper-triangular row-major component pairs for a three-vector self-product.
PetscErrorCode PicurvProductComponentCount(PetscInt dof, PetscInt *count)
Implementation of PicurvProductComponentCount().
static const char *const kDerivedKindName[DERIVED_KIND_COUNT]
Recipe spellings of the output kinds.
static const char *const kAxisName[3]
Axis labels indexing the pair table above.
#define STATISTICS_DERIVED_OUTPUT_LENGTH
Longest output list a recipe may request.
static PetscErrorCode ParseDerivedKinds(const char *outputs, PetscBool wanted[DERIVED_KIND_COUNT])
Internal helper: reports which output kinds a recipe requested.
static PetscInt ProductDiagonalIndex(PetscInt component)
Internal helper: the product index carrying one component's own variance.
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().
static PetscErrorCode FindFieldSlot(const PicurvWindowDefinition *definition, PetscInt field_id, PetscInt *slot)
Internal helper: locates the storage slot holding one field's running mean.
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.
DerivedKind
Output kinds a postprocessing recipe may request, in enumeration order.
@ DERIVED_REYNOLDS_STRESS
@ DERIVED_KIND_COUNT
PetscErrorCode PicurvWindowDerivedCount(const PicurvWindowDefinition *definition, const PicurvWindowStorage *storage, const char *outputs, PetscInt *count)
Implementation of PicurvWindowDerivedCount().
static PetscErrorCode ProductComponentLabel(PetscInt index, char *out, size_t size)
Internal helper: writes the two-axis label of one product component.
PetscErrorCode PicurvWindowStorageCreate(UserCtx *user, const PicurvWindowDefinition *definition, PicurvWindowStorage *storage)
Implementation of PicurvWindowStorageCreate().
PetscErrorCode PicurvCovarianceComponentCount(PetscInt dof_a, PetscInt dof_b, PetscInt *count)
Implementation of PicurvCovarianceComponentCount().
PetscErrorCode PicurvWindowStoragePayload(UserCtx *user, const PicurvWindowDefinition *definition, const PicurvWindowStorage *storage, PetscInt index, PicurvStatisticsPayload *payload)
Implementation of PicurvWindowStoragePayload().
static const PetscInt kProductSecond[6]
PetscErrorCode PicurvWindowValidFractionRange(UserCtx *user, const PicurvWindowDefinition *definition, const PicurvWindowStorage *storage, PetscInt sample_count, PetscReal *minimum, PetscReal *maximum)
Implementation of PicurvWindowValidFractionRange().
static PetscErrorCode SafeStandardDeviation(PetscReal variance, const char *label, PetscReal *result)
Internal helper: takes a square root of a variance that may be barely negative.
PetscErrorCode PicurvStatisticsComponentDM(UserCtx *user, PetscInt components, DM *dm)
Implementation of PicurvStatisticsComponentDM().
PetscErrorCode PicurvWindowSpatialMean(UserCtx *user, const PicurvWindowDefinition *definition, const PicurvWindowStorage *storage, Vec field, PetscReal *mean)
Implementation of PicurvWindowSpatialMean().
static PetscErrorCode DerivedKindExtent(const PicurvWindowDefinition *definition, const PicurvWindowStorage *storage, DerivedKind kind, PetscInt *count)
Internal helper: counts the derived fields each kind contributes.
PetscErrorCode PicurvWindowAccumulate(UserCtx *user, const PicurvWindowDefinition *definition, PicurvWindowStorage *storage, PetscReal weight)
Implementation of PicurvWindowAccumulate().
PetscErrorCode PicurvWindowStorageDestroy(PicurvWindowStorage *storage)
Implementation of PicurvWindowStorageDestroy().
PetscErrorCode PicurvWindowStoragePayloadCount(const PicurvWindowStorage *storage, PetscInt *count)
Implementation of PicurvWindowStoragePayloadCount().
Per-window PETSc accumulator storage and pointwise application.
Vec weight
Per-point valid weight.
PetscInt components
Degrees of freedom the vector carries.
PetscInt components
One or three.
Vec weight_sq
Per-point squared-weight sum.
PetscInt field_count
Fields accumulated.
PetscInt covariance_count
Covariance pairs accumulated.
char name[96]
Output field name, window qualified.
#define PICURV_STATISTICS_VARIANCE_FLOOR
Tolerance within which a negative variance is treated as floating-point noise.
Vec * mean
One per field, matching that field's layout.
Vec vec
Borrowed accumulator vector; never owned by the caller.
Vec * m2
One per field; NULL when no second moment was requested.
const char * role
Inventory role: occupancy, mean, second_moment, co_moment.
Vec count
Per-point accepted sample count.
char name[96]
File basename, no extension.
const char * layout
Catalog layout name for the inventory entry.
Vec * cm
One per covariance pair.
One derived output field, resolved by enumeration index.
One checkpointable accumulator vector, resolved by enumeration index.
Independent accumulator state for one window on one block.
Weighted centered-moment kernels for the field-statistics pipeline.
PetscReal weight_sq
Sum of squared weights W2.
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 mean_y
Weighted mean of the second member.
PetscReal weight
Total weight W.
PetscReal count
Number of accepted samples.
PetscReal cm
Centered co-moment sum C.
PetscReal m2
Centered second-moment sum M2.
PetscReal PicurvCoMomentStateCovariance(const PicurvCoMomentState *state)
Returns the weighted covariance C/W, or zero when no weight accumulated.
PetscReal mean
Weighted mean mu.
PetscReal mean_x
Weighted mean of the first member.
PetscErrorCode PicurvMomentStateUpdate(PicurvMomentState *state, PetscReal value, PetscReal weight)
Applies one weighted sample to a scalar moment accumulator.
PetscReal weight
Total weight W.
PetscReal count
Number of accepted samples.
Weighted centered co-moment state for one ordered pair of quantities.
Weighted centered state for one scalar quantity at one point.
Spatial target resolution for the field-statistics pipeline.
PetscBool SpatialTargetPlanMaskAllows(const SpatialTargetPlan *plan, PetscReal nvert_value)
Reports whether a point passes the plan's 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).
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.
The scientifically immutable definition of one window.
PetscInt _this
Definition variables.h:914
Vec Nvert
Definition variables.h:929
User-defined context containing data specific to a single computational grid level.
Definition variables.h:896