PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
test_statistics_moments.c
Go to the documentation of this file.
1/**
2 * @file test_statistics_moments.c
3 * @brief C unit tests for the weighted centered-moment kernels.
4 *
5 * Covers the moment acceptance items in @ref p58_validation_sec — constant fields
6 * yielding exactly zero covariance, known two- and three-sample moments across all
7 * six symmetric velocity components, high-mean/low-fluctuation precision, and
8 * merge-equals-sequential.
9 */
10
11#include "test_support.h"
12
13#include "statistics_moments.h"
14
15/** @brief A constant signal must produce exactly zero variance, not merely small. */
16static PetscErrorCode TestConstantFieldHasExactlyZeroVariance(void)
17{
18 PicurvMomentState scalar;
20
21 PetscFunctionBeginUser;
24
25 /* Unequal weights so this also covers the physical-time weighting path. */
26 for (PetscInt i = 0; i < 8; ++i) {
27 PetscCall(PicurvMomentStateUpdate(&scalar, 7.25, 1.0 + 0.5 * (PetscReal)i));
28 PetscCall(PicurvCoMomentStateUpdate(&pair, 7.25, -3.5, 1.0 + 0.5 * (PetscReal)i));
29 }
30
31 PetscCall(PicurvAssertBool((PetscBool)(scalar.m2 == 0.0),
32 "constant signal must give bitwise-zero centered second moment"));
33 PetscCall(PicurvAssertBool((PetscBool)(pair.cm == 0.0),
34 "constant signal pair must give bitwise-zero co-moment"));
35 PetscCall(PicurvAssertRealNear(7.25, scalar.mean, 1.0e-14, "constant signal mean"));
36 PetscCall(PicurvAssertBool((PetscBool)(PicurvMomentStateVariance(&scalar) == 0.0),
37 "constant signal variance must be exactly zero"));
38 PetscFunctionReturn(0);
39}
40
41/** @brief Known scalar moments for equal and unequal weights. */
42static PetscErrorCode TestKnownScalarMoments(void)
43{
45 PicurvMomentState weighted;
46 const PetscReal samples[3] = {1.0, 2.0, 6.0};
47
48 PetscFunctionBeginUser;
50 for (PetscInt i = 0; i < 3; ++i) PetscCall(PicurvMomentStateUpdate(&equal, samples[i], 1.0));
51 /* mean 3, M2 = 4 + 1 + 9 = 14, variance 14/3 */
52 PetscCall(PicurvAssertRealNear(3.0, equal.mean, 1.0e-14, "three-sample mean"));
53 PetscCall(PicurvAssertRealNear(14.0, equal.m2, 1.0e-13, "three-sample centered second moment"));
54 PetscCall(PicurvAssertRealNear(14.0 / 3.0, PicurvMomentStateVariance(&equal), 1.0e-14,
55 "three-sample variance"));
56 PetscCall(PicurvAssertRealNear(3.0, PicurvMomentStateEffectiveCount(&equal), 1.0e-14,
57 "equal weights make effective count equal sample count"));
58
59 /* Unequal weights: x=1 (w=1), x=3 (w=3) -> W=4, mean 2.5, M2 = 3, variance 0.75 */
60 PicurvMomentStateReset(&weighted);
61 PetscCall(PicurvMomentStateUpdate(&weighted, 1.0, 1.0));
62 PetscCall(PicurvMomentStateUpdate(&weighted, 3.0, 3.0));
63 PetscCall(PicurvAssertRealNear(4.0, weighted.weight, 1.0e-14, "unequal-weight total weight"));
64 PetscCall(PicurvAssertRealNear(2.5, weighted.mean, 1.0e-14, "unequal-weight mean"));
65 PetscCall(PicurvAssertRealNear(3.0, weighted.m2, 1.0e-13, "unequal-weight centered second moment"));
66 PetscCall(PicurvAssertRealNear(0.75, PicurvMomentStateVariance(&weighted), 1.0e-14,
67 "unequal-weight variance"));
68 /* Kish effective count: W^2/W2 = 16/10 */
69 PetscCall(PicurvAssertRealNear(1.6, PicurvMomentStateEffectiveCount(&weighted), 1.0e-14,
70 "unequal weights reduce effective count"));
71 PetscFunctionReturn(0);
72}
73
74/** @brief Known two-sample covariance through the co-moment update. */
75static PetscErrorCode TestKnownTwoSampleCovariance(void)
76{
78
79 PetscFunctionBeginUser;
81 PetscCall(PicurvCoMomentStateUpdate(&pair, 1.0, 2.0, 1.0));
82 PetscCall(PicurvCoMomentStateUpdate(&pair, 3.0, 6.0, 1.0));
83 /* means (2,4); deviations (-1,-2) and (1,2); C = 2 + 2 = 4; covariance 2 */
84 PetscCall(PicurvAssertRealNear(2.0, pair.mean_x, 1.0e-14, "two-sample co-moment mean x"));
85 PetscCall(PicurvAssertRealNear(4.0, pair.mean_y, 1.0e-14, "two-sample co-moment mean y"));
86 PetscCall(PicurvAssertRealNear(4.0, pair.cm, 1.0e-13, "two-sample centered co-moment"));
87 PetscCall(PicurvAssertRealNear(2.0, PicurvCoMomentStateCovariance(&pair), 1.0e-14,
88 "two-sample covariance"));
89 PetscFunctionReturn(0);
90}
91
92/**
93 * @brief All six symmetric components of a three-sample vector self-product.
94 *
95 * Component order is the fixed upper-triangular row-major order required by the
96 * accumulator contract: (xx, xy, xz, yy, yz, zz).
97 */
98static PetscErrorCode TestSixSymmetricVelocityComponents(void)
99{
100 const PetscReal series[3][3] = {{1.0, 2.0, 3.0}, {3.0, 6.0, 5.0}, {5.0, 4.0, 7.0}};
101 const PetscInt first[6] = {0, 0, 0, 1, 1, 2};
102 const PetscInt second[6] = {0, 1, 2, 1, 2, 2};
103 const PetscReal expected_cm[6] = {8.0, 4.0, 8.0, 8.0, 4.0, 8.0};
104 const char *labels[6] = {"xx", "xy", "xz", "yy", "yz", "zz"};
105 PicurvCoMomentState products[6];
106 PetscReal tke = 0.0;
107 char context[128];
108
109 PetscFunctionBeginUser;
110 for (PetscInt c = 0; c < 6; ++c) PicurvCoMomentStateReset(&products[c]);
111
112 for (PetscInt s = 0; s < 3; ++s) {
113 for (PetscInt c = 0; c < 6; ++c) {
114 PetscCall(PicurvCoMomentStateUpdate(&products[c],
115 series[s][first[c]], series[s][second[c]], 1.0));
116 }
117 }
118
119 /* means (3,4,5); C_xx=C_xz=C_yy=C_zz=8, C_xy=C_yz=4 */
120 for (PetscInt c = 0; c < 6; ++c) {
121 PetscCall(PetscSNPrintf(context, sizeof(context), "centered co-moment component %s", labels[c]));
122 PetscCall(PicurvAssertRealNear(expected_cm[c], products[c].cm, 1.0e-13, context));
123 }
124
125 /* TKE = 0.5 * (R_xx + R_yy + R_zz) with R_ii = C_ii / W = 8/3 each */
126 tke = 0.5 * (PicurvCoMomentStateCovariance(&products[0]) +
127 PicurvCoMomentStateCovariance(&products[3]) +
128 PicurvCoMomentStateCovariance(&products[5]));
129 PetscCall(PicurvAssertRealNear(4.0, tke, 1.0e-13, "turbulent kinetic energy from the trace"));
130 PetscFunctionReturn(0);
131}
132
133/** @brief A co-moment of a signal with itself must reproduce the scalar second moment bitwise. */
134static PetscErrorCode TestCoMomentOfSelfMatchesSecondMoment(void)
135{
136 PicurvMomentState scalar;
138 const PetscReal samples[5] = {2.5, -1.75, 9.0, 0.25, 4.5};
139 const PetscReal weights[5] = {1.0, 0.5, 2.25, 3.0, 0.125};
140
141 PetscFunctionBeginUser;
142 PicurvMomentStateReset(&scalar);
144 for (PetscInt i = 0; i < 5; ++i) {
145 PetscCall(PicurvMomentStateUpdate(&scalar, samples[i], weights[i]));
146 PetscCall(PicurvCoMomentStateUpdate(&self, samples[i], samples[i], weights[i]));
147 }
148 PetscCall(PicurvAssertBool((PetscBool)(self.cm == scalar.m2),
149 "co-moment of a signal with itself must equal its centered second moment"));
150 PetscCall(PicurvAssertBool((PetscBool)(self.mean_x == scalar.mean && self.mean_y == scalar.mean),
151 "co-moment self-pair means must equal the scalar mean"));
152 PetscFunctionReturn(0);
153}
154
155/**
156 * @brief High mean with small fluctuation, where a naive sum-of-squares cancels catastrophically.
157 *
158 * Samples 1e8-1 and 1e8+1 have variance exactly 1. Accumulating raw second sums
159 * would compute a difference of two values near 2e16, whose spacing exceeds the
160 * answer, so the centered update is what makes this recoverable.
161 */
162static PetscErrorCode TestHighMeanLowFluctuationPrecision(void)
163{
164 PicurvMomentState state;
166
167 PetscFunctionBeginUser;
170 PetscCall(PicurvMomentStateUpdate(&state, 1.0e8 - 1.0, 1.0));
171 PetscCall(PicurvMomentStateUpdate(&state, 1.0e8 + 1.0, 1.0));
172 PetscCall(PicurvCoMomentStateUpdate(&pair, 1.0e8 - 1.0, 1.0e8 + 1.0, 1.0));
173 PetscCall(PicurvCoMomentStateUpdate(&pair, 1.0e8 + 1.0, 1.0e8 - 1.0, 1.0));
174
175 PetscCall(PicurvAssertRealNear(1.0e8, state.mean, 1.0e-6, "high-mean signal mean"));
176 PetscCall(PicurvAssertRealNear(1.0, PicurvMomentStateVariance(&state), 1.0e-9,
177 "high-mean low-fluctuation variance must not cancel"));
178 /* Anti-correlated pair of the same fluctuation: covariance is exactly -1. */
179 PetscCall(PicurvAssertRealNear(-1.0, PicurvCoMomentStateCovariance(&pair), 1.0e-9,
180 "high-mean anti-correlated covariance must not cancel"));
181 PetscFunctionReturn(0);
182}
183
184/** @brief Merging two partitions must reproduce a single sequential accumulation. */
185static PetscErrorCode TestMergeEqualsSequential(void)
186{
187 PicurvMomentState sequential, part_a, part_b, merged;
188 PicurvCoMomentState co_sequential, co_a, co_b, co_merged;
189 PicurvMomentState empty;
190 const PetscReal samples[8] = {1.5, -2.0, 7.25, 0.5, 3.0, -4.5, 6.0, 2.25};
191 const PetscReal weights[8] = {1.0, 2.0, 0.5, 1.25, 3.0, 0.75, 1.0, 2.5};
192
193 PetscFunctionBeginUser;
194 PicurvMomentStateReset(&sequential);
195 PicurvMomentStateReset(&part_a);
196 PicurvMomentStateReset(&part_b);
198 PicurvCoMomentStateReset(&co_sequential);
201
202 for (PetscInt i = 0; i < 8; ++i) {
203 PetscCall(PicurvMomentStateUpdate(&sequential, samples[i], weights[i]));
204 PetscCall(PicurvCoMomentStateUpdate(&co_sequential, samples[i], 2.0 * samples[i] + 1.0, weights[i]));
205 if (i < 3) {
206 PetscCall(PicurvMomentStateUpdate(&part_a, samples[i], weights[i]));
207 PetscCall(PicurvCoMomentStateUpdate(&co_a, samples[i], 2.0 * samples[i] + 1.0, weights[i]));
208 } else {
209 PetscCall(PicurvMomentStateUpdate(&part_b, samples[i], weights[i]));
210 PetscCall(PicurvCoMomentStateUpdate(&co_b, samples[i], 2.0 * samples[i] + 1.0, weights[i]));
211 }
212 }
213
214 PetscCall(PicurvMomentStateMerge(&merged, &part_a, &part_b));
215 PetscCall(PicurvAssertRealNear(sequential.weight, merged.weight, 1.0e-14, "merged total weight"));
216 PetscCall(PicurvAssertRealNear(sequential.count, merged.count, 1.0e-14, "merged sample count"));
217 PetscCall(PicurvAssertRealNear(sequential.mean, merged.mean, 1.0e-13, "merged mean"));
218 PetscCall(PicurvAssertRealNear(sequential.m2, merged.m2, 1.0e-11, "merged centered second moment"));
219
220 PetscCall(PicurvCoMomentStateMerge(&co_merged, &co_a, &co_b));
221 PetscCall(PicurvAssertRealNear(co_sequential.mean_x, co_merged.mean_x, 1.0e-13, "merged co-moment mean x"));
222 PetscCall(PicurvAssertRealNear(co_sequential.mean_y, co_merged.mean_y, 1.0e-13, "merged co-moment mean y"));
223 PetscCall(PicurvAssertRealNear(co_sequential.cm, co_merged.cm, 1.0e-11, "merged centered co-moment"));
224
225 /* Merging an unsampled partition must be a no-op in both directions. */
226 PetscCall(PicurvMomentStateMerge(&merged, &sequential, &empty));
227 PetscCall(PicurvAssertBool((PetscBool)(merged.m2 == sequential.m2 && merged.mean == sequential.mean),
228 "merging an empty partition on the right must not change the state"));
229 PetscCall(PicurvMomentStateMerge(&merged, &empty, &sequential));
230 PetscCall(PicurvAssertBool((PetscBool)(merged.m2 == sequential.m2 && merged.mean == sequential.mean),
231 "merging an empty partition on the left must not change the state"));
232 PetscFunctionReturn(0);
233}
234
235/** @brief Non-positive weights are rejected rather than silently corrupting the accumulator. */
236static PetscErrorCode TestNonPositiveWeightRejected(void)
237{
238 PicurvMomentState state;
240 PetscErrorCode zero_ierr = 0, negative_ierr = 0, co_zero_ierr = 0;
241
242 PetscFunctionBeginUser;
245
246 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
247 zero_ierr = PicurvMomentStateUpdate(&state, 1.0, 0.0);
248 PetscCall(PetscPopErrorHandler());
249 PetscCall(PicurvAssertIntEqual(PETSC_ERR_ARG_OUTOFRANGE, zero_ierr,
250 "zero sample weight should be rejected"));
251
252 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
253 negative_ierr = PicurvMomentStateUpdate(&state, 1.0, -2.0);
254 PetscCall(PetscPopErrorHandler());
255 PetscCall(PicurvAssertIntEqual(PETSC_ERR_ARG_OUTOFRANGE, negative_ierr,
256 "negative sample weight should be rejected"));
257
258 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
259 co_zero_ierr = PicurvCoMomentStateUpdate(&pair, 1.0, 2.0, 0.0);
260 PetscCall(PetscPopErrorHandler());
261 PetscCall(PicurvAssertIntEqual(PETSC_ERR_ARG_OUTOFRANGE, co_zero_ierr,
262 "zero co-moment weight should be rejected"));
263
264 PetscCall(PicurvAssertBool((PetscBool)(state.count == 0.0 && state.weight == 0.0),
265 "a rejected sample must leave the accumulator untouched"));
266 PetscFunctionReturn(0);
267}
268
269/**
270 * @brief Entry point for the centered-moment kernel suite.
271 */
272int main(int argc, char **argv)
273{
274 PetscErrorCode ierr;
275 const PicurvTestCase cases[] = {
276 {"constant-field-zero-variance", TestConstantFieldHasExactlyZeroVariance},
277 {"known-scalar-moments", TestKnownScalarMoments},
278 {"known-two-sample-covariance", TestKnownTwoSampleCovariance},
279 {"six-symmetric-velocity-components", TestSixSymmetricVelocityComponents},
280 {"co-moment-of-self-matches-second-moment", TestCoMomentOfSelfMatchesSecondMoment},
281 {"high-mean-low-fluctuation-precision", TestHighMeanLowFluctuationPrecision},
282 {"merge-equals-sequential", TestMergeEqualsSequential},
283 {"non-positive-weight-rejected", TestNonPositiveWeightRejected},
284 };
285
286 ierr = PetscInitialize(&argc, &argv, NULL, "PICurv centered-moment kernel tests");
287 if (ierr) {
288 return (int)ierr;
289 }
290
291 ierr = PicurvRunTests("unit-statistics", cases, sizeof(cases) / sizeof(cases[0]));
292 if (ierr) {
293 PetscFinalize();
294 return (int)ierr;
295 }
296
297 ierr = PetscFinalize();
298 return (int)ierr;
299}
Weighted centered-moment kernels for the field-statistics pipeline.
PetscErrorCode PicurvMomentStateMerge(PicurvMomentState *result, const PicurvMomentState *a, const PicurvMomentState *b)
Merges two independently accumulated scalar states.
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 cm
Centered co-moment sum C.
PetscErrorCode PicurvCoMomentStateMerge(PicurvCoMomentState *result, const PicurvCoMomentState *a, const PicurvCoMomentState *b)
Merges two independently accumulated co-moment states.
PetscReal PicurvMomentStateEffectiveCount(const PicurvMomentState *state)
Returns Kish effective sample size W^2/W2, or zero when no weight accumulated.
void PicurvCoMomentStateReset(PicurvCoMomentState *state)
Resets a co-moment accumulator to the empty state.
PetscReal PicurvMomentStateVariance(const PicurvMomentState *state)
Returns the weighted variance M2/W, or zero when no weight accumulated.
PetscReal m2
Centered second-moment sum M2.
PetscReal PicurvCoMomentStateCovariance(const PicurvCoMomentState *state)
Returns the weighted covariance C/W, or zero when no weight accumulated.
void PicurvMomentStateReset(PicurvMomentState *state)
Resets a scalar moment accumulator to the empty state.
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 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.
static PetscErrorCode TestConstantFieldHasExactlyZeroVariance(void)
A constant signal must produce exactly zero variance, not merely small.
static PetscErrorCode TestCoMomentOfSelfMatchesSecondMoment(void)
A co-moment of a signal with itself must reproduce the scalar second moment bitwise.
static PetscErrorCode TestKnownScalarMoments(void)
Known scalar moments for equal and unequal weights.
int main(int argc, char **argv)
Entry point for the centered-moment kernel suite.
static PetscErrorCode TestKnownTwoSampleCovariance(void)
Known two-sample covariance through the co-moment update.
static PetscErrorCode TestNonPositiveWeightRejected(void)
Non-positive weights are rejected rather than silently corrupting the accumulator.
static PetscErrorCode TestSixSymmetricVelocityComponents(void)
All six symmetric components of a three-sample vector self-product.
static PetscErrorCode TestMergeEqualsSequential(void)
Merging two partitions must reproduce a single sequential accumulation.
static PetscErrorCode TestHighMeanLowFluctuationPrecision(void)
High mean with small fluctuation, where a naive sum-of-squares cancels catastrophically.
PetscErrorCode PicurvAssertRealNear(PetscReal expected, PetscReal actual, PetscReal tol, const char *context)
Asserts that two real values agree within tolerance.
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.
PetscErrorCode PicurvAssertIntEqual(PetscInt expected, PetscInt actual, const char *context)
Asserts that two integer values are equal.
PetscErrorCode PicurvAssertBool(PetscBool value, const char *context)
Asserts that one boolean condition is true.
Shared declarations for the PICurv C test fixture and assertion layer.
Named test case descriptor consumed by PicurvRunTests.