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