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

C unit tests for per-window accumulator storage and pointwise application. More...

#include "test_support.h"
#include "statistics_accumulator.h"
#include "statistics_moments.h"
#include "field_catalog.h"
#include "statistics_window.h"
#include "statistics_target.h"
Include dependency graph for test_statistics_accumulator.c:

Go to the source code of this file.

Macros

#define ACC_N   6 /* fixture size; cell domain is 5x5x5 */
 
#define ACC_CELLS   125
 

Functions

static PicurvWindowDefinition AccDefinition (PetscBool want_second)
 Builds a definition requesting Ucat and P with second moments.
 
static PetscErrorCode SetUniform (UserCtx *user, PetscReal x, PetscReal y, PetscReal z, PetscReal p)
 Sets Ucat to a uniform vector and P to a uniform scalar.
 
static PetscErrorCode ReadScalarAt (UserCtx *user, Vec v, PetscInt i, PetscInt j, PetscInt k, PetscReal *out)
 Reads one interior point of a scalar accumulator vector.
 
static PetscErrorCode ReadComponentAt (UserCtx *user, Vec v, PetscInt components, PetscInt component, PetscInt i, PetscInt j, PetscInt k, PetscReal *out)
 Reads one component of a multi-component accumulator vector at an interior point.
 
static PetscErrorCode TestComponentCounts (void)
 Component counts follow the documented product shapes.
 
static PetscErrorCode TestStorageShape (void)
 Storage allocates one mean per field and the right number of product components.
 
static PetscErrorCode TestKnownAccumulationAcrossField (void)
 Three accepted states reproduce the analytically known moments at every point.
 
static PetscErrorCode TestValidFractionRange (void)
 The valid-fraction range reports mask coverage, including a never-valid point.
 
static PetscErrorCode TestValidFractionDetectsNeverValidPoint (void)
 A point blanked from the start is reported as never valid.
 
static PetscErrorCode TestCovarianceAccumulation (void)
 A vector-scalar covariance reproduces its analytically known components.
 
static PetscErrorCode TestScalarSelfCovarianceMatchesSecondMoment (void)
 A field's covariance with itself reduces exactly to its own second moment.
 
static PetscErrorCode TestCovarianceRequiresFieldMembership (void)
 A covariance member missing from the field list is rejected, not silently skipped.
 
static PetscErrorCode TestDerivedQuantities (void)
 Derived quantities reproduce the analytic values the accumulated state implies.
 
static PetscErrorCode TestSpatialMeanExcludesUnsampledPoints (void)
 The spatial mean divides by sampled points, not by the whole vector.
 
static PetscErrorCode TestPayloadEnumeration (void)
 Payload enumeration covers every vector exactly once with stable names.
 
static PetscErrorCode TestWeightedAccumulation (void)
 Unequal weights reproduce the weighted result the kernels define.
 
static PetscErrorCode TestMaskedPointsAreExcluded (void)
 Masked points accumulate nothing, and remain distinguishable from unsampled ones.
 
static PetscErrorCode TestConstantFieldZeroProduct (void)
 A constant field yields exactly zero product at every point.
 
static PetscErrorCode TestRunloopDriverAppliesScheduledStates (void)
 The runloop entry point applies exactly the states the schedule accepts.
 
int main (int argc, char **argv)
 Entry point for the accumulator suite.
 

Detailed Description

C unit tests for per-window accumulator storage and pointwise application.

The field series reuses the values verified analytically by the moment-kernel suite, so a failure here isolates the field-to-accumulator wiring rather than the numerics: three samples of (1,2,3), (3,6,5), (5,4,7) have means (3,4,5) and symmetric centered products (8,4,8,8,4,8) in (xx,xy,xz,yy,yz,zz) order.

Definition in file test_statistics_accumulator.c.

Macro Definition Documentation

◆ ACC_N

#define ACC_N   6 /* fixture size; cell domain is 5x5x5 */

Definition at line 19 of file test_statistics_accumulator.c.

◆ ACC_CELLS

#define ACC_CELLS   125

Definition at line 20 of file test_statistics_accumulator.c.

Function Documentation

◆ AccDefinition()

static PicurvWindowDefinition AccDefinition ( PetscBool  want_second)
static

Builds a definition requesting Ucat and P with second moments.

Definition at line 23 of file test_statistics_accumulator.c.

24{
26 memset(&d, 0, sizeof(d));
27 strncpy(d.name, "acc", PICURV_WINDOW_NAME_LENGTH - 1);
30 d.step_cadence = 1;
31 d.field_count = 2;
32 d.fields[0].field_id = FIELD_ID_UCAT; d.fields[0].want_second = want_second;
33 d.fields[1].field_id = FIELD_ID_P; d.fields[1].want_second = want_second;
34 return d;
35}
@ FIELD_ID_UCAT
@ FIELD_ID_P
PicurvWindowFieldRequest fields[16]
PicurvCadenceKind cadence_kind
PetscInt step_cadence
Used when cadence_kind is step; must be positive.
#define PICURV_WINDOW_NAME_LENGTH
Maximum stored length of a window name, including the terminator.
PetscBool want_second
Also keep the centered second moment.
PetscInt field_id
Catalogued Eulerian field identity.
@ PICURV_WEIGHTING_SAMPLE
Equal weight per accepted state.
@ PICURV_CADENCE_STEP
Every n completed steps from activation.
The scientifically immutable definition of one window.
Here is the caller graph for this function:

◆ SetUniform()

static PetscErrorCode SetUniform ( UserCtx *  user,
PetscReal  x,
PetscReal  y,
PetscReal  z,
PetscReal  p 
)
static

Sets Ucat to a uniform vector and P to a uniform scalar.

Definition at line 38 of file test_statistics_accumulator.c.

39{
40 Cmpnts ***ucat = NULL;
41 PetscReal ***pp = NULL;
42 const DMDALocalInfo info = user->info;
43
44 PetscFunctionBeginUser;
45 PetscCall(DMDAVecGetArray(user->fda, user->Ucat, &ucat));
46 PetscCall(DMDAVecGetArray(user->da, user->P, &pp));
47 for (PetscInt k = info.zs; k < info.zs + info.zm; ++k)
48 for (PetscInt j = info.ys; j < info.ys + info.ym; ++j)
49 for (PetscInt i = info.xs; i < info.xs + info.xm; ++i) {
50 ucat[k][j][i].x = x; ucat[k][j][i].y = y; ucat[k][j][i].z = z;
51 pp[k][j][i] = p;
52 }
53 PetscCall(DMDAVecRestoreArray(user->da, user->P, &pp));
54 PetscCall(DMDAVecRestoreArray(user->fda, user->Ucat, &ucat));
55 PetscFunctionReturn(0);
56}
PetscScalar x
Definition variables.h:122
PetscScalar z
Definition variables.h:122
DMDALocalInfo info
Definition variables.h:1085
PetscScalar y
Definition variables.h:122
A 3D point or vector with PetscScalar components.
Definition variables.h:121
Here is the caller graph for this function:

◆ ReadScalarAt()

static PetscErrorCode ReadScalarAt ( UserCtx *  user,
Vec  v,
PetscInt  i,
PetscInt  j,
PetscInt  k,
PetscReal *  out 
)
static

Reads one interior point of a scalar accumulator vector.

Definition at line 59 of file test_statistics_accumulator.c.

60{
61 PetscReal ***a = NULL;
62 PetscFunctionBeginUser;
63 PetscCall(DMDAVecGetArrayRead(user->da, v, &a));
64 *out = a[k][j][i];
65 PetscCall(DMDAVecRestoreArrayRead(user->da, v, &a));
66 PetscFunctionReturn(0);
67}
Here is the caller graph for this function:

◆ ReadComponentAt()

static PetscErrorCode ReadComponentAt ( UserCtx *  user,
Vec  v,
PetscInt  components,
PetscInt  component,
PetscInt  i,
PetscInt  j,
PetscInt  k,
PetscReal *  out 
)
static

Reads one component of a multi-component accumulator vector at an interior point.

Definition at line 70 of file test_statistics_accumulator.c.

72{
73 PetscScalar ****a = NULL;
74 DM dm = NULL;
75 PetscFunctionBeginUser;
76 PetscCall(PicurvStatisticsComponentDM(user, components, &dm));
77 PetscCall(DMDAVecGetArrayDOFRead(dm, v, &a));
78 *out = a[k][j][i][component];
79 PetscCall(DMDAVecRestoreArrayDOFRead(dm, v, &a));
80 PetscFunctionReturn(0);
81}
PetscErrorCode PicurvStatisticsComponentDM(UserCtx *user, PetscInt components, DM *dm)
Resolves the DM carrying a given number of accumulator components.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestComponentCounts()

static PetscErrorCode TestComponentCounts ( void  )
static

Component counts follow the documented product shapes.

Definition at line 84 of file test_statistics_accumulator.c.

85{
86 PetscInt n = 0;
87 PetscErrorCode bad = 0;
88
89 PetscFunctionBeginUser;
90 PetscCall(PicurvProductComponentCount(1, &n));
91 PetscCall(PicurvAssertIntEqual(1, n, "a scalar self-product has one component"));
92 PetscCall(PicurvProductComponentCount(3, &n));
93 PetscCall(PicurvAssertIntEqual(6, n, "a three-vector self-product has six symmetric components"));
94 PetscCall(PicurvCovarianceComponentCount(3, 1, &n));
95 PetscCall(PicurvAssertIntEqual(3, n, "a vector-scalar covariance has three components"));
96 PetscCall(PicurvCovarianceComponentCount(1, 1, &n));
97 PetscCall(PicurvAssertIntEqual(1, n, "a scalar-scalar covariance has one component"));
98
99 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
100 bad = PicurvCovarianceComponentCount(3, 3, &n);
101 PetscCall(PetscPopErrorHandler());
102 PetscCall(PicurvAssertIntEqual(PETSC_ERR_ARG_OUTOFRANGE, bad,
103 "vector-vector cross products are an explicit non-goal"));
104 PetscFunctionReturn(0);
105}
PetscErrorCode PicurvProductComponentCount(PetscInt dof, PetscInt *count)
Reports how many symmetric product components a field's second moment needs.
PetscErrorCode PicurvCovarianceComponentCount(PetscInt dof_a, PetscInt dof_b, PetscInt *count)
Reports how many components a covariance between two fields needs.
PetscErrorCode PicurvAssertIntEqual(PetscInt expected, PetscInt actual, const char *context)
Asserts that two integer values are equal.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestStorageShape()

static PetscErrorCode TestStorageShape ( void  )
static

Storage allocates one mean per field and the right number of product components.

Definition at line 108 of file test_statistics_accumulator.c.

109{
110 SimCtx *simCtx = NULL;
111 UserCtx *user = NULL;
112 PicurvWindowStorage storage;
114 PetscInt bs = 0;
115
116 PetscFunctionBeginUser;
117 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, ACC_N, ACC_N, ACC_N));
118 PetscCall(PicurvWindowStorageCreate(user, &d, &storage));
119
120 PetscCall(PicurvAssertIntEqual(2, storage.field_count, "two fields requested"));
121
122 /* Each product is one vector carrying all of its components, so the symmetric
123 * second-order tensor stays a single object rather than six scalars. */
124 PetscCall(VecGetBlockSize(storage.m2[0], &bs));
125 PetscCall(PicurvAssertIntEqual(6, bs, "the Ucat product is one six-component tensor"));
126 PetscCall(VecGetBlockSize(storage.m2[1], &bs));
127 PetscCall(PicurvAssertIntEqual(1, bs, "the P product is a single scalar"));
128
129 /* Means inherit their source field's layout. */
130 PetscCall(VecGetBlockSize(storage.mean[0], &bs));
131 PetscCall(PicurvAssertIntEqual(3, bs, "the Ucat mean is a three-vector"));
132 PetscCall(VecGetBlockSize(storage.mean[1], &bs));
133 PetscCall(PicurvAssertIntEqual(1, bs, "the P mean is a scalar"));
134
135 PetscCall(PicurvWindowStorageDestroy(&storage));
136 PetscCall(PicurvAssertIntEqual(0, storage.field_count, "destroy zeroes the storage"));
137 PetscCall(PicurvAssertBool((PetscBool)(storage.mean == NULL), "destroy releases the mean array"));
138 /* Destroying zeroed storage is safe. */
139 PetscCall(PicurvWindowStorageDestroy(&storage));
140
141 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
142 PetscFunctionReturn(0);
143}
PetscInt field_count
Fields accumulated.
Vec * mean
One per field, matching that field's layout.
PetscErrorCode PicurvWindowStorageCreate(UserCtx *user, const PicurvWindowDefinition *definition, PicurvWindowStorage *storage)
Allocates the accumulator state one window owns on one block.
Vec * m2
One per field; NULL when no second moment was requested.
PetscErrorCode PicurvWindowStorageDestroy(PicurvWindowStorage *storage)
Releases accumulator state previously created for one window.
Independent accumulator state for one window on one block.
static PicurvWindowDefinition AccDefinition(PetscBool want_second)
Builds a definition requesting Ucat and P with second moments.
PetscErrorCode PicurvCreateMinimalContexts(SimCtx **simCtx_out, UserCtx **user_out, PetscInt mx, PetscInt my, PetscInt mz)
Builds minimal SimCtx and UserCtx fixtures for C unit tests.
PetscErrorCode PicurvDestroyMinimalContexts(SimCtx **simCtx_ptr, UserCtx **user_ptr)
Destroys minimal SimCtx/UserCtx fixtures and all owned PETSc objects.
PetscErrorCode PicurvAssertBool(PetscBool value, const char *context)
Asserts that one boolean condition is true.
The master context for the entire simulation.
Definition variables.h:866
User-defined context containing data specific to a single computational grid level.
Definition variables.h:1073
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestKnownAccumulationAcrossField()

static PetscErrorCode TestKnownAccumulationAcrossField ( void  )
static

Three accepted states reproduce the analytically known moments at every point.

Definition at line 146 of file test_statistics_accumulator.c.

147{
148 SimCtx *simCtx = NULL;
149 UserCtx *user = NULL;
150 PicurvWindowStorage storage;
152 const PetscReal series[3][3] = {{1.0, 2.0, 3.0}, {3.0, 6.0, 5.0}, {5.0, 4.0, 7.0}};
153 const PetscReal scalars[3] = {1.0, 2.0, 6.0};
154 const PetscReal expected_product[6] = {8.0, 4.0, 8.0, 8.0, 4.0, 8.0};
155 const char *labels[6] = {"xx", "xy", "xz", "yy", "yz", "zz"};
156 Cmpnts ***mean_vec = NULL;
157 PetscReal value = 0.0;
158 char context[128];
159
160 PetscFunctionBeginUser;
161 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, ACC_N, ACC_N, ACC_N));
162 PetscCall(VecSet(user->Nvert, 0.0));
163 PetscCall(PicurvWindowStorageCreate(user, &d, &storage));
164
165 for (PetscInt s = 0; s < 3; ++s) {
166 PetscCall(SetUniform(user, series[s][0], series[s][1], series[s][2], scalars[s]));
167 PetscCall(PicurvWindowAccumulate(user, &d, &storage, 1.0));
168 }
169
170 /* Occupancy: every fluid point saw all three states. */
171 PetscCall(ReadScalarAt(user, storage.count, 2, 2, 2, &value));
172 PetscCall(PicurvAssertRealNear(3.0, value, 1.0e-12, "per-point sample count"));
173 PetscCall(ReadScalarAt(user, storage.weight, 2, 2, 2, &value));
174 PetscCall(PicurvAssertRealNear(3.0, value, 1.0e-12, "per-point total weight"));
175 PetscCall(ReadScalarAt(user, storage.weight_sq, 2, 2, 2, &value));
176 PetscCall(PicurvAssertRealNear(3.0, value, 1.0e-12, "per-point squared-weight sum"));
177
178 /* Vector mean. */
179 PetscCall(DMDAVecGetArrayRead(user->fda, storage.mean[0], &mean_vec));
180 PetscCall(PicurvAssertRealNear(3.0, mean_vec[2][2][2].x, 1.0e-12, "Ucat mean x"));
181 PetscCall(PicurvAssertRealNear(4.0, mean_vec[2][2][2].y, 1.0e-12, "Ucat mean y"));
182 PetscCall(PicurvAssertRealNear(5.0, mean_vec[2][2][2].z, 1.0e-12, "Ucat mean z"));
183 PetscCall(DMDAVecRestoreArrayRead(user->fda, storage.mean[0], &mean_vec));
184
185 /* All six symmetric centered products, in fixed catalog order. */
186 for (PetscInt c = 0; c < 6; ++c) {
187 PetscCall(ReadComponentAt(user, storage.m2[0], 6, c, 2, 2, 2, &value));
188 PetscCall(PetscSNPrintf(context, sizeof(context), "Ucat centered product %s", labels[c]));
189 PetscCall(PicurvAssertRealNear(expected_product[c], value, 1.0e-11, context));
190 }
191
192 /* Scalar mean and second moment. */
193 PetscCall(ReadScalarAt(user, storage.mean[1], 2, 2, 2, &value));
194 PetscCall(PicurvAssertRealNear(3.0, value, 1.0e-12, "P mean"));
195 PetscCall(ReadScalarAt(user, storage.m2[1], 2, 2, 2, &value));
196 PetscCall(PicurvAssertRealNear(14.0, value, 1.0e-11, "P centered second moment"));
197
198 PetscCall(PicurvWindowStorageDestroy(&storage));
199 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
200 PetscFunctionReturn(0);
201}
Vec weight
Per-point valid weight.
Vec weight_sq
Per-point squared-weight sum.
Vec count
Per-point accepted sample count.
PetscErrorCode PicurvWindowAccumulate(UserCtx *user, const PicurvWindowDefinition *definition, PicurvWindowStorage *storage, PetscReal weight)
Applies one accepted completed state to a window's accumulators.
static PetscErrorCode ReadComponentAt(UserCtx *user, Vec v, PetscInt components, PetscInt component, PetscInt i, PetscInt j, PetscInt k, PetscReal *out)
Reads one component of a multi-component accumulator vector at an interior point.
static PetscErrorCode SetUniform(UserCtx *user, PetscReal x, PetscReal y, PetscReal z, PetscReal p)
Sets Ucat to a uniform vector and P to a uniform scalar.
static PetscErrorCode ReadScalarAt(UserCtx *user, Vec v, PetscInt i, PetscInt j, PetscInt k, PetscReal *out)
Reads one interior point of a scalar accumulator vector.
PetscErrorCode PicurvAssertRealNear(PetscReal expected, PetscReal actual, PetscReal tol, const char *context)
Asserts that two real values agree within tolerance.
Vec Nvert
Definition variables.h:1113
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestValidFractionRange()

static PetscErrorCode TestValidFractionRange ( void  )
static

The valid-fraction range reports mask coverage, including a never-valid point.

This is the mask-health indicator the console snapshot prints, and it is the only signal that distinguishes a mean built from every state from one built from none. A blanked point keeps a zero mean that looks like a legitimate value, so the range has to surface it.

Definition at line 211 of file test_statistics_accumulator.c.

212{
213 SimCtx *simCtx = NULL;
214 UserCtx *user = NULL;
215 PicurvWindowStorage storage;
216 PicurvWindowDefinition d = AccDefinition(PETSC_FALSE);
217 PetscReal ***nvert = NULL;
218 PetscReal lowest = 0.0, highest = 0.0;
219
220 PetscFunctionBeginUser;
221 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, ACC_N, ACC_N, ACC_N));
222 PetscCall(VecSet(user->Nvert, 0.0));
223 PetscCall(PicurvWindowStorageCreate(user, &d, &storage));
224 PetscCall(SetUniform(user, 1.0, 2.0, 3.0, 7.0));
225
226 /* Before any sample the range is the documented degenerate, not a division. */
227 PetscCall(PicurvWindowValidFractionRange(user, &d, &storage, 0, &lowest, &highest));
228 PetscCall(PicurvAssertRealNear(1.0, lowest, 1.0e-12, "an unsampled window reports full coverage"));
229 PetscCall(PicurvAssertRealNear(0.0, highest, 1.0e-12, "an unsampled window reports no maximum"));
230
231 /* An unobstructed domain: every targeted point saw every state. */
232 PetscCall(PicurvWindowAccumulate(user, &d, &storage, 1.0));
233 PetscCall(PicurvWindowAccumulate(user, &d, &storage, 1.0));
234 PetscCall(PicurvWindowValidFractionRange(user, &d, &storage, 2, &lowest, &highest));
235 PetscCall(PicurvAssertRealNear(1.0, lowest, 1.0e-12, "a clear domain is fully covered"));
236 PetscCall(PicurvAssertRealNear(1.0, highest, 1.0e-12, "a clear domain has no over-counted point"));
237
238 /* Blank one interior cell and take a third state: that point now trails. */
239 PetscCall(DMDAVecGetArray(user->da, user->Nvert, &nvert));
240 nvert[3][3][3] = 1.0;
241 PetscCall(DMDAVecRestoreArray(user->da, user->Nvert, &nvert));
242 PetscCall(PicurvWindowAccumulate(user, &d, &storage, 1.0));
243 PetscCall(PicurvWindowValidFractionRange(user, &d, &storage, 3, &lowest, &highest));
244 PetscCall(PicurvAssertRealNear(2.0 / 3.0, lowest, 1.0e-12,
245 "a point blanked for one state trails the rest"));
246 PetscCall(PicurvAssertRealNear(1.0, highest, 1.0e-12, "unobstructed points stay fully covered"));
247
248 PetscCall(PicurvWindowStorageDestroy(&storage));
249 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
250 PetscFunctionReturn(0);
251}
PetscErrorCode PicurvWindowValidFractionRange(UserCtx *user, const PicurvWindowDefinition *definition, const PicurvWindowStorage *storage, PetscInt sample_count, PetscReal *minimum, PetscReal *maximum)
Reports the range of per-point valid fraction across a window's domain.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestValidFractionDetectsNeverValidPoint()

static PetscErrorCode TestValidFractionDetectsNeverValidPoint ( void  )
static

A point blanked from the start is reported as never valid.

Definition at line 256 of file test_statistics_accumulator.c.

257{
258 SimCtx *simCtx = NULL;
259 UserCtx *user = NULL;
260 PicurvWindowStorage storage;
261 PicurvWindowDefinition d = AccDefinition(PETSC_FALSE);
262 PetscReal ***nvert = NULL;
263 PetscReal lowest = 1.0, highest = 0.0;
264
265 PetscFunctionBeginUser;
266 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, ACC_N, ACC_N, ACC_N));
267 PetscCall(VecSet(user->Nvert, 0.0));
268 PetscCall(DMDAVecGetArray(user->da, user->Nvert, &nvert));
269 nvert[3][3][3] = 1.0;
270 PetscCall(DMDAVecRestoreArray(user->da, user->Nvert, &nvert));
271
272 PetscCall(PicurvWindowStorageCreate(user, &d, &storage));
273 PetscCall(SetUniform(user, 1.0, 2.0, 3.0, 7.0));
274 PetscCall(PicurvWindowAccumulate(user, &d, &storage, 1.0));
275 PetscCall(PicurvWindowAccumulate(user, &d, &storage, 1.0));
276
277 PetscCall(PicurvWindowValidFractionRange(user, &d, &storage, 2, &lowest, &highest));
278 PetscCall(PicurvAssertRealNear(0.0, lowest, 1.0e-12,
279 "a permanently blanked point reports zero coverage"));
280 PetscCall(PicurvAssertRealNear(1.0, highest, 1.0e-12, "the rest of the domain is unaffected"));
281
282 PetscCall(PicurvWindowStorageDestroy(&storage));
283 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
284 PetscFunctionReturn(0);
285}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestCovarianceAccumulation()

static PetscErrorCode TestCovarianceAccumulation ( void  )
static

A vector-scalar covariance reproduces its analytically known components.

Definition at line 288 of file test_statistics_accumulator.c.

289{
290 SimCtx *simCtx = NULL;
291 UserCtx *user = NULL;
292 PicurvWindowStorage storage;
294 const PetscReal series[3][3] = {{1.0, 2.0, 3.0}, {3.0, 6.0, 5.0}, {5.0, 4.0, 7.0}};
295 const PetscReal scalars[3] = {1.0, 2.0, 6.0};
296 /* Means are (3,4,5) and 3, so the centered sums are
297 * x: (-2)(-2)+(0)(-1)+(2)(3) = 10, y: (-2)(-2)+(2)(-1)+(0)(3) = 2,
298 * z: (-2)(-2)+(0)(-1)+(2)(3) = 10. */
299 const PetscReal expected[3] = {10.0, 2.0, 10.0};
300 const char *labels[3] = {"x", "y", "z"};
301 PetscReal value = 0.0;
302 char context[128];
303
304 PetscFunctionBeginUser;
305 d.covariance_count = 1;
308
309 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, ACC_N, ACC_N, ACC_N));
310 PetscCall(VecSet(user->Nvert, 0.0));
311 PetscCall(PicurvWindowStorageCreate(user, &d, &storage));
312 {
313 PetscInt bs = 0;
314 PetscCall(VecGetBlockSize(storage.cm[0], &bs));
315 PetscCall(PicurvAssertIntEqual(3, bs, "a vector-scalar pair is one three-component vector"));
316 }
317
318 for (PetscInt s = 0; s < 3; ++s) {
319 PetscCall(SetUniform(user, series[s][0], series[s][1], series[s][2], scalars[s]));
320 PetscCall(PicurvWindowAccumulate(user, &d, &storage, 1.0));
321 }
322
323 for (PetscInt c = 0; c < 3; ++c) {
324 PetscCall(ReadComponentAt(user, storage.cm[0], 3, c, 2, 2, 2, &value));
325 PetscCall(PetscSNPrintf(context, sizeof(context), "Ucat-P co-moment %s", labels[c]));
326 PetscCall(PicurvAssertRealNear(expected[c], value, 1.0e-11, context));
327 }
328
329 /* The co-moment pass must not disturb the moments computed alongside it. */
330 PetscCall(ReadScalarAt(user, storage.mean[1], 2, 2, 2, &value));
331 PetscCall(PicurvAssertRealNear(3.0, value, 1.0e-12, "P mean is unaffected by the covariance pass"));
332 PetscCall(ReadComponentAt(user, storage.m2[0], 6, 0, 2, 2, 2, &value));
333 PetscCall(PicurvAssertRealNear(8.0, value, 1.0e-11, "Ucat xx product is unaffected by the covariance pass"));
334
335 PetscCall(PicurvWindowStorageDestroy(&storage));
336 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
337 PetscFunctionReturn(0);
338}
Vec * cm
One per covariance pair.
PetscInt first
First member; must also appear in the field list.
PetscInt second
Second member; must also appear in the field list.
PicurvWindowCovarianceRequest covariances[16]
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestScalarSelfCovarianceMatchesSecondMoment()

static PetscErrorCode TestScalarSelfCovarianceMatchesSecondMoment ( void  )
static

A field's covariance with itself reduces exactly to its own second moment.

Definition at line 341 of file test_statistics_accumulator.c.

342{
343 SimCtx *simCtx = NULL;
344 UserCtx *user = NULL;
345 PicurvWindowStorage storage;
347 const PetscReal scalars[3] = {1.0, 2.0, 6.0};
348 PetscReal co_moment = 0.0;
349 PetscReal second_moment = 0.0;
350
351 PetscFunctionBeginUser;
352 /* Pairing P with itself makes the co-moment pass and the moment pass compute the
353 * same quantity by different routes, so any drift between them shows up here
354 * rather than only in a cross-field result no closed form covers. */
355 d.covariance_count = 1;
358
359 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, ACC_N, ACC_N, ACC_N));
360 PetscCall(VecSet(user->Nvert, 0.0));
361 PetscCall(PicurvWindowStorageCreate(user, &d, &storage));
362
363 for (PetscInt s = 0; s < 3; ++s) {
364 PetscCall(SetUniform(user, 0.0, 0.0, 0.0, scalars[s]));
365 PetscCall(PicurvWindowAccumulate(user, &d, &storage, 1.0));
366 }
367
368 PetscCall(ReadScalarAt(user, storage.cm[0], 2, 2, 2, &co_moment));
369 PetscCall(ReadScalarAt(user, storage.m2[1], 2, 2, 2, &second_moment));
370 PetscCall(PicurvAssertRealNear(14.0, co_moment, 1.0e-11, "P self-covariance equals its centered sum"));
371 PetscCall(PicurvAssertBool((PetscBool)(co_moment == second_moment),
372 "self-covariance and second moment agree bit for bit"));
373
374 PetscCall(PicurvWindowStorageDestroy(&storage));
375 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
376 PetscFunctionReturn(0);
377}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestCovarianceRequiresFieldMembership()

static PetscErrorCode TestCovarianceRequiresFieldMembership ( void  )
static

A covariance member missing from the field list is rejected, not silently skipped.

Definition at line 380 of file test_statistics_accumulator.c.

381{
382 SimCtx *simCtx = NULL;
383 UserCtx *user = NULL;
384 PicurvWindowStorage storage;
385 PicurvWindowDefinition d = AccDefinition(PETSC_FALSE);
386 PetscErrorCode bad = 0;
387
388 PetscFunctionBeginUser;
389 /* Nvert is catalogued but is not in the window's field list, so no running mean
390 * exists for it and the co-moment update has nothing to center against. */
391 d.covariance_count = 1;
394
395 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, ACC_N, ACC_N, ACC_N));
396 PetscCall(VecSet(user->Nvert, 0.0));
397 PetscCall(PicurvWindowStorageCreate(user, &d, &storage));
398 PetscCall(SetUniform(user, 1.0, 2.0, 3.0, 4.0));
399
400 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
401 bad = PicurvWindowAccumulate(user, &d, &storage, 1.0);
402 PetscCall(PetscPopErrorHandler());
403 PetscCall(PicurvAssertIntEqual(PETSC_ERR_ARG_WRONGSTATE, bad,
404 "a covariance member outside the field list is rejected"));
405
406 PetscCall(PicurvWindowStorageDestroy(&storage));
407 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
408 PetscFunctionReturn(0);
409}
@ FIELD_ID_NVERT
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestDerivedQuantities()

static PetscErrorCode TestDerivedQuantities ( void  )
static

Derived quantities reproduce the analytic values the accumulated state implies.

The series is the one the moment suite verifies by hand, so a failure here isolates the normalization and component selection rather than the accumulation: with three unit-weight samples, R_ij = C_ij / 3, and the diagonal of the Ucat tensor is 8/3 in each direction, giving TKE = 4 and an RMS of sqrt(8/3) per component.

Definition at line 419 of file test_statistics_accumulator.c.

420{
421 SimCtx *simCtx = NULL;
422 UserCtx *user = NULL;
423 PicurvWindowStorage storage;
425 const PetscReal series[3][3] = {{1.0, 2.0, 3.0}, {3.0, 6.0, 5.0}, {5.0, 4.0, 7.0}};
426 const PetscReal scalars[3] = {1.0, 2.0, 6.0};
427 Vec scalar_target = NULL, vector_target = NULL;
428 PetscInt count = 0;
429 PetscReal value = 0.0;
430
431 PetscFunctionBeginUser;
432 d.covariance_count = 1;
435
436 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, ACC_N, ACC_N, ACC_N));
437 PetscCall(VecSet(user->Nvert, 0.0));
438 PetscCall(PicurvWindowStorageCreate(user, &d, &storage));
439 for (PetscInt s = 0; s < 3; ++s) {
440 PetscCall(SetUniform(user, series[s][0], series[s][1], series[s][2], scalars[s]));
441 PetscCall(PicurvWindowAccumulate(user, &d, &storage, 1.0));
442 }
443 PetscCall(DMCreateGlobalVector(user->da, &scalar_target));
444 PetscCall(DMCreateGlobalVector(user->fda, &vector_target));
445
446 /* Requesting every output resolves each kind against what the window carries. */
447 PetscCall(PicurvWindowDerivedCount(&d, &storage, "mean,reynolds_stress,rms,tke,flux", &count));
448 /* 2 means + (6 Ucat + 1 P) stresses + (3 Ucat + 1 P) RMS + 1 TKE + 1 flux. */
449 PetscCall(PicurvAssertIntEqual(15, count, "every requested output is enumerated"));
450
451 for (PetscInt index = 0; index < count; ++index) {
452 PicurvDerivedField field;
453 PetscBool is_tke = PETSC_FALSE, is_rxx = PETSC_FALSE, is_rmsx = PETSC_FALSE;
454 PetscBool is_flux = PETSC_FALSE, is_pvar = PETSC_FALSE;
455
456 PetscCall(PicurvWindowDerive(user, &d, &storage, "mean,reynolds_stress,rms,tke,flux",
457 index, scalar_target, vector_target, &field));
458 PetscCall(PetscStrcmp(field.name, "acc_Ucat_tke", &is_tke));
459 PetscCall(PetscStrcmp(field.name, "acc_Ucat_R_xx", &is_rxx));
460 PetscCall(PetscStrcmp(field.name, "acc_Ucat_rmsx", &is_rmsx));
461 PetscCall(PetscStrcmp(field.name, "acc_Ucat_P_flux", &is_flux));
462 PetscCall(PetscStrcmp(field.name, "acc_P_variance", &is_pvar));
463
464 if (is_rxx) {
465 PetscCall(ReadScalarAt(user, scalar_target, 2, 2, 2, &value));
466 PetscCall(PicurvAssertRealNear(8.0 / 3.0, value, 1.0e-11, "R_xx is C_xx over the weight"));
467 } else if (is_rmsx) {
468 PetscCall(ReadScalarAt(user, scalar_target, 2, 2, 2, &value));
469 PetscCall(PicurvAssertRealNear(PetscSqrtReal(8.0 / 3.0), value, 1.0e-11,
470 "RMS is the root of the normal stress"));
471 } else if (is_tke) {
472 PetscCall(ReadScalarAt(user, scalar_target, 2, 2, 2, &value));
473 PetscCall(PicurvAssertRealNear(4.0, value, 1.0e-11, "TKE is half the trace"));
474 } else if (is_pvar) {
475 PetscCall(ReadScalarAt(user, scalar_target, 2, 2, 2, &value));
476 PetscCall(PicurvAssertRealNear(14.0 / 3.0, value, 1.0e-11, "a scalar product normalizes too"));
477 } else if (is_flux) {
478 Cmpnts ***flux = NULL;
479
480 PetscCall(DMDAVecGetArrayRead(user->fda, vector_target, &flux));
481 PetscCall(PicurvAssertRealNear(10.0 / 3.0, flux[2][2][2].x, 1.0e-11, "flux x"));
482 PetscCall(PicurvAssertRealNear(2.0 / 3.0, flux[2][2][2].y, 1.0e-11, "flux y"));
483 PetscCall(PicurvAssertRealNear(10.0 / 3.0, flux[2][2][2].z, 1.0e-11, "flux z"));
484 PetscCall(DMDAVecRestoreArrayRead(user->fda, vector_target, &flux));
485 }
486 }
487
488 /* A narrower request produces only what it asked for. */
489 PetscCall(PicurvWindowDerivedCount(&d, &storage, "tke", &count));
490 PetscCall(PicurvAssertIntEqual(1, count, "a single output kind enumerates once"));
491
492 /* An unsampled point is left at zero rather than divided by a zero weight. */
493 {
494 PicurvDerivedField field;
495 PetscReal ***nvert = NULL;
496
497 PetscCall(DMDAVecGetArray(user->da, user->Nvert, &nvert));
498 nvert[3][3][3] = 1.0;
499 PetscCall(DMDAVecRestoreArray(user->da, user->Nvert, &nvert));
500 PetscCall(PicurvWindowStorageDestroy(&storage));
501 PetscCall(PicurvWindowStorageCreate(user, &d, &storage));
502 PetscCall(SetUniform(user, 1.0, 2.0, 3.0, 4.0));
503 PetscCall(PicurvWindowAccumulate(user, &d, &storage, 1.0));
504 PetscCall(PicurvWindowDerive(user, &d, &storage, "tke", 0, scalar_target, vector_target, &field));
505 PetscCall(ReadScalarAt(user, scalar_target, 3, 3, 3, &value));
506 PetscCall(PicurvAssertRealNear(0.0, value, 1.0e-12,
507 "a never-sampled point derives to zero, not a division"));
508 }
509
510 /* Dimensionalization raises the field's own scale to the kind's own power: a mean
511 * is linear in it and a covariance is quadratic. One blanket velocity factor would
512 * be right for two of the five kinds and wrong for the other three, which is why
513 * derived statistics were left non-dimensional before this existed. */
514 {
515 PicurvDerivedField field;
516 PetscReal nondimensional_mean = 0.0, nondimensional_tke = 0.0;
517 PetscReal dimensional_mean = 0.0, dimensional_tke = 0.0;
518 const PetscReal velocity = 3.0;
519
520 PetscCall(PicurvWindowStorageDestroy(&storage));
521 PetscCall(PicurvWindowStorageCreate(user, &d, &storage));
522 PetscCall(SetUniform(user, 1.0, 2.0, 3.0, 4.0));
523 PetscCall(PicurvWindowAccumulate(user, &d, &storage, 1.0));
524 PetscCall(SetUniform(user, 3.0, 4.0, 5.0, 6.0));
525 PetscCall(PicurvWindowAccumulate(user, &d, &storage, 1.0));
526
527 /* The minimal fixture carries no post-processing settings; the harness teardown
528 * already frees them, so allocating here is symmetric. */
529 if (!user->simCtx->pps) PetscCall(PetscCalloc1(1, &user->simCtx->pps));
530 user->simCtx->pps->dimensionalize = PETSC_FALSE;
531 PetscCall(PicurvWindowDerive(user, &d, &storage, "mean", 0, scalar_target, vector_target, &field));
532 PetscCall(ReadComponentAt(user, vector_target, 3, 0, 2, 2, 2, &nondimensional_mean));
533 PetscCall(PicurvWindowDerive(user, &d, &storage, "tke", 0, scalar_target, vector_target, &field));
534 PetscCall(ReadScalarAt(user, scalar_target, 2, 2, 2, &nondimensional_tke));
535
536 user->simCtx->scaling.U_ref = velocity;
537 user->simCtx->pps->dimensionalize = PETSC_TRUE;
538 PetscCall(PicurvWindowDerive(user, &d, &storage, "mean", 0, scalar_target, vector_target, &field));
539 PetscCall(ReadComponentAt(user, vector_target, 3, 0, 2, 2, 2, &dimensional_mean));
540 PetscCall(PicurvWindowDerive(user, &d, &storage, "tke", 0, scalar_target, vector_target, &field));
541 PetscCall(ReadScalarAt(user, scalar_target, 2, 2, 2, &dimensional_tke));
542
543 PetscCall(PicurvAssertRealNear(velocity * nondimensional_mean, dimensional_mean, 1.0e-11,
544 "a mean carries the field's scale once"));
545 PetscCall(PicurvAssertRealNear(velocity * velocity * nondimensional_tke, dimensional_tke,
546 1.0e-11, "a turbulent kinetic energy carries it squared"));
547 user->simCtx->pps->dimensionalize = PETSC_FALSE;
548 user->simCtx->scaling.U_ref = 1.0;
549 }
550
551 /* An unknown output kind is refused rather than silently ignored. */
552 {
553 PetscErrorCode bad = 0;
554
555 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
556 bad = PicurvWindowDerivedCount(&d, &storage, "skewness", &count);
557 PetscCall(PetscPopErrorHandler());
558 PetscCall(PicurvAssertIntEqual(PETSC_ERR_ARG_WRONG, bad, "an unknown output kind is refused"));
559 }
560
561 PetscCall(VecDestroy(&vector_target));
562 PetscCall(VecDestroy(&scalar_target));
563 PetscCall(PicurvWindowStorageDestroy(&storage));
564 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
565 PetscFunctionReturn(0);
566}
char name[96]
Output field name, window qualified.
PetscErrorCode PicurvWindowDerive(UserCtx *user, const PicurvWindowDefinition *definition, const PicurvWindowStorage *storage, const char *outputs, PetscInt index, Vec scalar_target, Vec vector_target, PicurvDerivedField *field)
Derives one output field from centered accumulator state.
PetscErrorCode PicurvWindowDerivedCount(const PicurvWindowDefinition *definition, const PicurvWindowStorage *storage, const char *outputs, PetscInt *count)
Reports how many derived fields a requested output set produces.
One derived output field, resolved by enumeration index.
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
ScalingCtx scaling
Definition variables.h:954
PostProcessParams * pps
Definition variables.h:1057
PetscReal U_ref
Definition variables.h:849
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestSpatialMeanExcludesUnsampledPoints()

static PetscErrorCode TestSpatialMeanExcludesUnsampledPoints ( void  )
static

The spatial mean divides by sampled points, not by the whole vector.

A derived field is zero outside the target domain and wherever the mask never admitted a point. Those zeros are absences, and averaging over them silently scales the answer down by the fraction of the vector the window never covered — a wrong number that still looks plausible.

Definition at line 576 of file test_statistics_accumulator.c.

577{
578 SimCtx *simCtx = NULL;
579 UserCtx *user = NULL;
580 PicurvWindowStorage storage;
581 PicurvWindowDefinition d = AccDefinition(PETSC_FALSE);
583 PetscReal ***nvert = NULL;
584 PetscReal mean = 0.0;
585 PetscReal whole_vector_mean = 0.0;
586 PetscReal sum = 0.0;
587 PetscInt vector_size = 0;
588 PetscInt targeted = 0;
589
590 PetscFunctionBeginUser;
591 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, ACC_N, ACC_N, ACC_N));
592 PetscCall(VecSet(user->Nvert, 0.0));
593 PetscCall(PicurvWindowStorageCreate(user, &d, &storage));
594 PetscCall(SetUniform(user, 1.0, 2.0, 3.0, 4.0));
595 PetscCall(PicurvWindowAccumulate(user, &d, &storage, 1.0));
596
597 /* A field holding exactly one over every sampled point: its masked mean must be
598 * one, whatever fraction of the vector the target domain occupies. */
599 PetscCall(VecZeroEntries(user->PostScalar));
601 PetscCall(SpatialTargetPlanGlobalPointCount(&plan, PETSC_COMM_WORLD, &targeted));
602 {
603 PetscReal ***values = NULL;
604
605 PetscCall(DMDAVecGetArray(user->da, user->PostScalar, &values));
606 for (PetscInt k = plan.start[2]; k < plan.end[2]; ++k)
607 for (PetscInt j = plan.start[1]; j < plan.end[1]; ++j)
608 for (PetscInt i = plan.start[0]; i < plan.end[0]; ++i) values[k][j][i] = 1.0;
609 PetscCall(DMDAVecRestoreArray(user->da, user->PostScalar, &values));
610 }
611
612 PetscCall(PicurvWindowSpatialMean(user, &d, &storage, user->PostScalar, &mean));
613 PetscCall(PicurvAssertRealNear(1.0, mean, 1.0e-12,
614 "the mean over sampled points ignores untargeted entries"));
615
616 /* The whole-vector average is genuinely different, which is what makes the
617 * distinction worth asserting rather than assuming. */
618 PetscCall(VecSum(user->PostScalar, &sum));
619 PetscCall(VecGetSize(user->PostScalar, &vector_size));
620 whole_vector_mean = sum / (PetscReal)vector_size;
621 PetscCall(PicurvAssertBool((PetscBool)(vector_size > targeted),
622 "the vector is larger than the targeted domain"));
623 PetscCall(PicurvAssertBool((PetscBool)(whole_vector_mean < 0.9),
624 "a whole-vector average would understate the result"));
625
626 /* Blanking a point removes it from both the sum and the count, so a uniform
627 * field still averages to its own value rather than being diluted. */
628 PetscCall(DMDAVecGetArray(user->da, user->Nvert, &nvert));
629 nvert[3][3][3] = 1.0;
630 PetscCall(DMDAVecRestoreArray(user->da, user->Nvert, &nvert));
631 PetscCall(PicurvWindowStorageDestroy(&storage));
632 PetscCall(PicurvWindowStorageCreate(user, &d, &storage));
633 PetscCall(PicurvWindowAccumulate(user, &d, &storage, 1.0));
634 PetscCall(PicurvWindowSpatialMean(user, &d, &storage, user->PostScalar, &mean));
635 PetscCall(PicurvAssertRealNear(1.0, mean, 1.0e-12,
636 "a never-sampled point is excluded from the average"));
637
638 PetscCall(PicurvWindowStorageDestroy(&storage));
639 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
640 PetscFunctionReturn(0);
641}
PetscErrorCode PicurvWindowSpatialMean(UserCtx *user, const PicurvWindowDefinition *definition, const PicurvWindowStorage *storage, Vec field, PetscReal *mean)
Reports the spatial mean of a derived field over the points a window sampled.
@ 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.
Vec PostScalar
Definition variables.h:1133
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestPayloadEnumeration()

static PetscErrorCode TestPayloadEnumeration ( void  )
static

Payload enumeration covers every vector exactly once with stable names.

Definition at line 644 of file test_statistics_accumulator.c.

645{
646 SimCtx *simCtx = NULL;
647 UserCtx *user = NULL;
648 PicurvWindowStorage storage;
650 /* 3 occupancy + 2 means + 2 products + 1 co-moment. Each product and co-moment
651 * is one payload carrying all of its components. */
652 const char *expected[8] = {
653 "count", "weight", "weight_sq",
654 "Ucat_mean", "P_mean",
655 "Ucat_m2", "P_m2",
656 "Ucat_P_cm"
657 };
658 const PetscInt expected_components[8] = {1, 1, 1, 3, 1, 6, 1, 3};
659 PetscInt count = 0;
660 PetscErrorCode bad = 0;
661
662 PetscFunctionBeginUser;
663 d.covariance_count = 1;
666
667 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, ACC_N, ACC_N, ACC_N));
668 PetscCall(PicurvWindowStorageCreate(user, &d, &storage));
669 PetscCall(PicurvWindowStoragePayloadCount(&storage, &count));
670 PetscCall(PicurvAssertIntEqual(8, count, "every accumulator vector is enumerated once"));
671
672 for (PetscInt index = 0; index < count; ++index) {
674 PetscBool matches = PETSC_FALSE;
675 PetscInt block_size = 0;
676 char context[160];
677
678 PetscCall(PicurvWindowStoragePayload(user, &d, &storage, index, &payload));
679 PetscCall(PetscStrcmp(payload.name, expected[index], &matches));
680 PetscCall(PetscSNPrintf(context, sizeof(context), "payload %d is named '%s', got '%s'",
681 (int)index, expected[index], payload.name));
682 PetscCall(PicurvAssertBool(matches, context));
683 PetscCall(PicurvAssertBool((PetscBool)(payload.vec != NULL), "an enumerated payload has a vector"));
684 PetscCall(PicurvAssertBool((PetscBool)(payload.role != NULL), "an enumerated payload has a role"));
685
686 /* The declared component count must match the vector actually enumerated,
687 * because the manifest inventory records it and the reader trusts it. */
688 PetscCall(VecGetBlockSize(payload.vec, &block_size));
689 PetscCall(PetscSNPrintf(context, sizeof(context), "payload '%s' declares %d components",
690 payload.name, (int)expected_components[index]));
691 PetscCall(PicurvAssertIntEqual(expected_components[index], payload.components, context));
692 PetscCall(PicurvAssertIntEqual(payload.components, block_size,
693 "the declared component count matches the vector"));
694 }
695
696 /* A field without a requested second moment contributes no product payload. */
697 {
698 PicurvWindowDefinition partial = AccDefinition(PETSC_FALSE);
700
701 partial.fields[0].want_second = PETSC_TRUE; /* Ucat keeps its product, P does not. */
702 PetscCall(PicurvWindowStorageCreate(user, &partial, &lean));
703 PetscCall(PicurvWindowStoragePayloadCount(&lean, &count));
704 PetscCall(PicurvAssertIntEqual(6, count, "a field without a second moment adds no payload"));
705 PetscCall(PicurvWindowStorageDestroy(&lean));
706 }
707
708 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
709 bad = PicurvWindowStoragePayload(user, &d, &storage, count, NULL);
710 PetscCall(PetscPopErrorHandler());
711 PetscCall(PicurvAssertBool((PetscBool)(bad != 0), "an out-of-range payload index is rejected"));
712
713 PetscCall(PicurvWindowStorageDestroy(&storage));
714 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
715 PetscFunctionReturn(0);
716}
PetscInt components
Degrees of freedom the vector carries.
Vec vec
Borrowed accumulator vector; never owned by the caller.
PetscErrorCode PicurvWindowStoragePayload(UserCtx *user, const PicurvWindowDefinition *definition, const PicurvWindowStorage *storage, PetscInt index, PicurvStatisticsPayload *payload)
Resolves one enumerated payload of a window's storage.
const char * role
Inventory role: occupancy, mean, second_moment, co_moment.
char name[96]
File basename, no extension.
PetscErrorCode PicurvWindowStoragePayloadCount(const PicurvWindowStorage *storage, PetscInt *count)
Reports how many checkpointable vectors one window's storage holds.
One checkpointable accumulator vector, resolved by enumeration index.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestWeightedAccumulation()

static PetscErrorCode TestWeightedAccumulation ( void  )
static

Unequal weights reproduce the weighted result the kernels define.

Definition at line 719 of file test_statistics_accumulator.c.

720{
721 SimCtx *simCtx = NULL;
722 UserCtx *user = NULL;
723 PicurvWindowStorage storage;
725 PetscReal value = 0.0;
726
727 PetscFunctionBeginUser;
728 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, ACC_N, ACC_N, ACC_N));
729 PetscCall(VecSet(user->Nvert, 0.0));
730 PetscCall(PicurvWindowStorageCreate(user, &d, &storage));
731
732 /* P = 1 with weight 1, then P = 3 with weight 3: mean 2.5, M2 = 3. */
733 PetscCall(SetUniform(user, 0.0, 0.0, 0.0, 1.0));
734 PetscCall(PicurvWindowAccumulate(user, &d, &storage, 1.0));
735 PetscCall(SetUniform(user, 0.0, 0.0, 0.0, 3.0));
736 PetscCall(PicurvWindowAccumulate(user, &d, &storage, 3.0));
737
738 PetscCall(ReadScalarAt(user, storage.weight, 2, 2, 2, &value));
739 PetscCall(PicurvAssertRealNear(4.0, value, 1.0e-12, "weighted total weight"));
740 PetscCall(ReadScalarAt(user, storage.weight_sq, 2, 2, 2, &value));
741 PetscCall(PicurvAssertRealNear(10.0, value, 1.0e-12, "weighted squared-weight sum"));
742 PetscCall(ReadScalarAt(user, storage.mean[1], 2, 2, 2, &value));
743 PetscCall(PicurvAssertRealNear(2.5, value, 1.0e-12, "weighted mean"));
744 PetscCall(ReadScalarAt(user, storage.m2[1], 2, 2, 2, &value));
745 PetscCall(PicurvAssertRealNear(3.0, value, 1.0e-11, "weighted centered second moment"));
746
747 PetscCall(PicurvWindowStorageDestroy(&storage));
748 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
749 PetscFunctionReturn(0);
750}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestMaskedPointsAreExcluded()

static PetscErrorCode TestMaskedPointsAreExcluded ( void  )
static

Masked points accumulate nothing, and remain distinguishable from unsampled ones.

Definition at line 753 of file test_statistics_accumulator.c.

754{
755 SimCtx *simCtx = NULL;
756 UserCtx *user = NULL;
757 PicurvWindowStorage storage;
758 PicurvWindowDefinition d = AccDefinition(PETSC_FALSE);
759 PetscReal ***nvert = NULL;
760 PetscReal value = 0.0;
761
762 PetscFunctionBeginUser;
763 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, ACC_N, ACC_N, ACC_N));
764 PetscCall(VecSet(user->Nvert, 0.0));
765 /* Blank a single interior cell. */
766 PetscCall(DMDAVecGetArray(user->da, user->Nvert, &nvert));
767 nvert[3][3][3] = 1.0;
768 PetscCall(DMDAVecRestoreArray(user->da, user->Nvert, &nvert));
769
770 PetscCall(PicurvWindowStorageCreate(user, &d, &storage));
771 PetscCall(SetUniform(user, 1.0, 2.0, 3.0, 7.0));
772 PetscCall(PicurvWindowAccumulate(user, &d, &storage, 1.0));
773 PetscCall(PicurvWindowAccumulate(user, &d, &storage, 1.0));
774
775 PetscCall(ReadScalarAt(user, storage.count, 2, 2, 2, &value));
776 PetscCall(PicurvAssertRealNear(2.0, value, 1.0e-12, "a fluid point sees every state"));
777 PetscCall(ReadScalarAt(user, storage.count, 3, 3, 3, &value));
778 PetscCall(PicurvAssertRealNear(0.0, value, 1.0e-12, "a blanked point accumulates no sample"));
779 PetscCall(ReadScalarAt(user, storage.weight, 3, 3, 3, &value));
780 PetscCall(PicurvAssertRealNear(0.0, value, 1.0e-12, "a blanked point accumulates no weight"));
781 PetscCall(ReadScalarAt(user, storage.mean[1], 3, 3, 3, &value));
782 PetscCall(PicurvAssertRealNear(0.0, value, 1.0e-12, "a blanked point keeps an untouched mean"));
783
784 PetscCall(PicurvWindowStorageDestroy(&storage));
785 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
786 PetscFunctionReturn(0);
787}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestConstantFieldZeroProduct()

static PetscErrorCode TestConstantFieldZeroProduct ( void  )
static

A constant field yields exactly zero product at every point.

Definition at line 790 of file test_statistics_accumulator.c.

791{
792 SimCtx *simCtx = NULL;
793 UserCtx *user = NULL;
794 PicurvWindowStorage storage;
796 PetscReal value = 0.0;
797
798 PetscFunctionBeginUser;
799 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, ACC_N, ACC_N, ACC_N));
800 PetscCall(VecSet(user->Nvert, 0.0));
801 PetscCall(PicurvWindowStorageCreate(user, &d, &storage));
802
803 for (PetscInt s = 0; s < 5; ++s) {
804 PetscCall(SetUniform(user, 2.5, -1.5, 4.0, 9.0));
805 PetscCall(PicurvWindowAccumulate(user, &d, &storage, 1.0 + 0.5 * (PetscReal)s));
806 }
807 for (PetscInt c = 0; c < 6; ++c) {
808 PetscCall(ReadComponentAt(user, storage.m2[0], 6, c, 2, 2, 2, &value));
809 PetscCall(PicurvAssertBool((PetscBool)(value == 0.0),
810 "a constant field gives bitwise-zero products"));
811 }
812 PetscCall(ReadScalarAt(user, storage.mean[1], 2, 2, 2, &value));
813 PetscCall(PicurvAssertRealNear(9.0, value, 1.0e-12, "a constant field keeps its value as the mean"));
814
815 PetscCall(PicurvWindowStorageDestroy(&storage));
816 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
817 PetscFunctionReturn(0);
818}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ TestRunloopDriverAppliesScheduledStates()

static PetscErrorCode TestRunloopDriverAppliesScheduledStates ( void  )
static

The runloop entry point applies exactly the states the schedule accepts.

Drives FieldStatisticsUpdateWindows directly, so the scheduling decision and the field accumulation are exercised through the same path the solver uses rather than being tested only in isolation.

Definition at line 827 of file test_statistics_accumulator.c.

828{
829 SimCtx *simCtx = NULL;
830 UserCtx *user = NULL;
831 PicurvWindow window;
833 PicurvWindowStorage storage;
834 PetscReal value = 0.0;
835 const PetscReal dt = 0.5;
836
837 PetscFunctionBeginUser;
838 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, ACC_N, ACC_N, ACC_N));
839 PetscCall(VecSet(user->Nvert, 0.0));
840
841 /* Stride two, physical-time weighting, starting at the origin. */
843 d.step_cadence = 2;
844 PetscCall(PicurvWindowInit(&window, &d));
845 PetscCall(PicurvWindowStorageCreate(user, &d, &storage));
846
847 simCtx->fieldStatisticsEnabled = PETSC_TRUE;
848 simCtx->fieldStatisticsWindowCount = 1;
849 simCtx->fieldStatisticsWindows = &window;
850 user->fieldStatisticsStorage = &storage;
851
852 /* Steps 0,2,4 are due. Step 0 anchors; steps 2 and 4 each represent 1.0 of time. */
853 for (PetscInt step = 0; step <= 4; ++step) {
854 PetscCall(SetUniform(user, 1.0, 2.0, 3.0, (PetscReal)(step + 1)));
855 PetscCall(FieldStatisticsUpdateWindows(simCtx, step, (PetscReal)step * dt));
856 }
857
858 PetscCall(PicurvAssertIntEqual(2, window.sample_count, "stride two over five steps accepts two samples"));
859 PetscCall(PicurvAssertRealNear(2.0, window.total_weight, 1.0e-12, "accepted weights span the elapsed time"));
860
861 /* Only the due states reached the field accumulators. */
862 PetscCall(ReadScalarAt(user, storage.count, 2, 2, 2, &value));
863 PetscCall(PicurvAssertRealNear(2.0, value, 1.0e-12, "per-point count matches the accepted samples"));
864 PetscCall(ReadScalarAt(user, storage.weight, 2, 2, 2, &value));
865 PetscCall(PicurvAssertRealNear(2.0, value, 1.0e-12, "per-point weight matches the accepted weights"));
866
867 /* P carried 3 at step 2 and 5 at step 4, each with weight 1: mean 4, M2 = 2. */
868 PetscCall(ReadScalarAt(user, storage.mean[1], 2, 2, 2, &value));
869 PetscCall(PicurvAssertRealNear(4.0, value, 1.0e-12, "only the scheduled samples contribute to the mean"));
870 PetscCall(ReadScalarAt(user, storage.m2[1], 2, 2, 2, &value));
871 PetscCall(PicurvAssertRealNear(2.0, value, 1.0e-11, "second moment reflects only the scheduled samples"));
872
873 /* A disabled subsystem is inert on the same path. */
874 simCtx->fieldStatisticsEnabled = PETSC_FALSE;
875 PetscCall(FieldStatisticsUpdateWindows(simCtx, 6, 3.0));
876 PetscCall(PicurvAssertIntEqual(2, window.sample_count, "a disabled subsystem accepts nothing"));
877
878 simCtx->fieldStatisticsWindows = NULL;
879 simCtx->fieldStatisticsWindowCount = 0;
880 user->fieldStatisticsStorage = NULL;
881 PetscCall(PicurvWindowStorageDestroy(&storage));
882 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
883 PetscFunctionReturn(0);
884}
PetscInt sample_count
PetscErrorCode FieldStatisticsUpdateWindows(struct SimCtx *simCtx, PetscInt step, PetscReal time)
Offers one completed state to every configured window.
PetscReal total_weight
PetscErrorCode PicurvWindowInit(PicurvWindow *window, const PicurvWindowDefinition *definition)
Validates a definition and initializes a window to the pending state.
@ PICURV_WEIGHTING_PHYSICAL_TIME
Weight is the represented interval.
Runtime state of one window.
PetscInt fieldStatisticsWindowCount
Definition variables.h:939
PetscBool fieldStatisticsEnabled
Definition variables.h:938
struct PicurvWindow * fieldStatisticsWindows
Definition variables.h:940
struct PicurvWindowStorage * fieldStatisticsStorage
Definition variables.h:1136
Here is the call graph for this function:
Here is the caller graph for this function:

◆ main()

int main ( int  argc,
char **  argv 
)

Entry point for the accumulator suite.

Definition at line 889 of file test_statistics_accumulator.c.

890{
891 PetscErrorCode ierr;
892 const PicurvTestCase cases[] = {
893 {"component-counts", TestComponentCounts},
894 {"storage-shape", TestStorageShape},
895 {"known-accumulation-across-field", TestKnownAccumulationAcrossField},
896 {"weighted-accumulation", TestWeightedAccumulation},
897 {"masked-points-excluded", TestMaskedPointsAreExcluded},
898 {"constant-field-zero-product", TestConstantFieldZeroProduct},
899 {"valid-fraction-range", TestValidFractionRange},
900 {"valid-fraction-detects-never-valid-point", TestValidFractionDetectsNeverValidPoint},
901 {"covariance-accumulation", TestCovarianceAccumulation},
902 {"scalar-self-covariance-matches-second-moment", TestScalarSelfCovarianceMatchesSecondMoment},
903 {"covariance-requires-field-membership", TestCovarianceRequiresFieldMembership},
904 {"payload-enumeration", TestPayloadEnumeration},
905 {"derived-quantities", TestDerivedQuantities},
906 {"spatial-mean-excludes-unsampled-points", TestSpatialMeanExcludesUnsampledPoints},
907 {"runloop-driver-applies-scheduled-states", TestRunloopDriverAppliesScheduledStates},
908 };
909
910 ierr = PetscInitialize(&argc, &argv, NULL, "PICurv statistics accumulator tests");
911 if (ierr) return (int)ierr;
912 ierr = PicurvRunTests("unit-statistics-accumulator", cases, sizeof(cases) / sizeof(cases[0]));
913 if (ierr) { PetscFinalize(); return (int)ierr; }
914 ierr = PetscFinalize();
915 return (int)ierr;
916}
static PetscErrorCode TestCovarianceAccumulation(void)
A vector-scalar covariance reproduces its analytically known components.
static PetscErrorCode TestWeightedAccumulation(void)
Unequal weights reproduce the weighted result the kernels define.
static PetscErrorCode TestStorageShape(void)
Storage allocates one mean per field and the right number of product components.
static PetscErrorCode TestValidFractionRange(void)
The valid-fraction range reports mask coverage, including a never-valid point.
static PetscErrorCode TestValidFractionDetectsNeverValidPoint(void)
A point blanked from the start is reported as never valid.
static PetscErrorCode TestRunloopDriverAppliesScheduledStates(void)
The runloop entry point applies exactly the states the schedule accepts.
static PetscErrorCode TestDerivedQuantities(void)
Derived quantities reproduce the analytic values the accumulated state implies.
static PetscErrorCode TestMaskedPointsAreExcluded(void)
Masked points accumulate nothing, and remain distinguishable from unsampled ones.
static PetscErrorCode TestKnownAccumulationAcrossField(void)
Three accepted states reproduce the analytically known moments at every point.
static PetscErrorCode TestScalarSelfCovarianceMatchesSecondMoment(void)
A field's covariance with itself reduces exactly to its own second moment.
static PetscErrorCode TestConstantFieldZeroProduct(void)
A constant field yields exactly zero product at every point.
static PetscErrorCode TestSpatialMeanExcludesUnsampledPoints(void)
The spatial mean divides by sampled points, not by the whole vector.
static PetscErrorCode TestPayloadEnumeration(void)
Payload enumeration covers every vector exactly once with stable names.
static PetscErrorCode TestCovarianceRequiresFieldMembership(void)
A covariance member missing from the field list is rejected, not silently skipped.
static PetscErrorCode TestComponentCounts(void)
Component counts follow the documented product shapes.
PetscErrorCode PicurvRunTests(const char *suite_name, const PicurvTestCase *cases, size_t case_count)
Runs a named C test suite and prints pass/fail progress markers.
Named test case descriptor consumed by PicurvRunTests.
Here is the call graph for this function: