Skip to content

Commit 2d77544

Browse files
Grego01-bioteepetersonpaulromano
authored
Adding variance of variance and normality tests for tally statistics (#3454)
Co-authored-by: Ethan Peterson <eepeterson3@gmail.com> Co-authored-by: Paul Romano <paul.k.romano@gmail.com>
1 parent e5348d3 commit 2d77544

File tree

12 files changed

+915
-35
lines changed

12 files changed

+915
-35
lines changed

docs/source/io_formats/statepoint.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ The current version of the statepoint file format is 18.1.
149149
tallies will have a value of 0 unless otherwise instructed.
150150
- **multiply_density** (*int*) -- Flag indicating whether reaction
151151
rates should be multiplied by atom density (1) or not (0).
152+
- **higher_moments** (*int*) -- Flag indicating whether
153+
higher-order tally moments are enabled (1) or not (0).
152154

153155
:Datasets: - **n_realizations** (*int*) -- Number of realizations.
154156
- **n_filters** (*int*) -- Number of filters used.

docs/source/methods/tallies.rst

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,101 @@ of this is that the longer you run a simulation, the better you know your
387387
results. Therefore, by running a simulation long enough, it is possible to
388388
reduce the stochastic uncertainty to arbitrarily low levels.
389389

390+
Skewness
391+
++++++++
392+
393+
The `skewness`_ of a population quantifies the asymmetry of the probability
394+
distribution around its mean. Positive and negative skewness indicate a
395+
longer/heavier right and left tail respectively. Let :math:`x_1,\ldots,x_n` be
396+
the per-realization values for a bin, with sample mean :math:`\bar{x}` and
397+
sample central moments:
398+
399+
.. math::
400+
401+
m_k \;=\; \frac{1}{n}\sum_{i=1}^{n}\bigl(x_i-\bar{x}\bigr)^k.
402+
403+
OpenMC reports the *adjusted Fisher-Pearson skewness* (defined for :math:`n \ge
404+
3`), which is commonly used in many statistical packages:
405+
406+
.. math::
407+
408+
G_1 \;=\; \frac{\sqrt{n \cdot (n-1)}}{\,n-2\,}\cdot\frac{m_3}{m_2^{3/2}}.
409+
410+
where :math:`m_2` and :math:`m_3` correspond to the biased sample second and
411+
third central moment respectively.
412+
413+
Kurtosis
414+
++++++++
415+
416+
The `kurtosis`_ of a population quantifies tail weight (also called tailedness)
417+
of the probability distribution relative to a normal distribution. Positive
418+
excess kurtosis indicates *heavier tails* whereas negative excess kurtosis
419+
indicates *lighter tails*. Kurtosis is especially useful for identifying bins
420+
where occasional extreme scores dominate uncertainty. OpenMC reports the
421+
*adjusted excess kurtosis* (defined for :math:`n \ge 4`):
422+
423+
.. math::
424+
425+
G_2 \;=\; \frac{(n-1)}{(n-2)(n-3)}
426+
\left[(n+1)\,\frac{m_4}{m_2^{2}} \;-\; 3(n-1)\right].
427+
428+
where :math:`m_2` and :math:`m_4` correspond to the biased sample second and
429+
fourth central moment respectively. For a perfectly normal distribution, the
430+
excess kurtosis is :math:`0`.
431+
432+
Variance of Variance
433+
++++++++++++++++++++
434+
435+
The variance of the variance (also known as the coefficient of variation
436+
squared) measures *stability of the sample variance* :math:`s^2` and, by
437+
extension, the reliability of reported relative errors. High VOV means that
438+
error bars themselves are noisy—often due to heavy tails, skewness, or too few
439+
realizations.
440+
441+
.. math::
442+
443+
VOV = \frac{s^2(s_{\bar{X}}^2)}{s_{\bar{X}}^4 } = \frac{m_4}{m_2^2} - \frac{1}{n}
444+
445+
where :math:`s_{\bar{X}}^2` is the estimated variance of the mean and
446+
:math:`s^2(s_{\bar{X}}^2)` is the estimated variance in :math:`s_{\bar{X}}^2`.
447+
The MCNP manual suggests a hard threshold such that :math:`VOV < 0.1` to improve
448+
the probability of forming a reliable confidence interval. However, OpenMC does
449+
not enforce an universal cut-off because the suitability of any single threshold
450+
depends strongly on problem specifics (estimator choice, variance-reduction
451+
settings, tally binning, or even effective sample size).
452+
453+
454+
Normality Tests (D'Agostino-Pearson)
455+
++++++++++++++++++++++++++++++++++++
456+
457+
These normality test verify the hypothesis that fluctuations are *approximately
458+
normal*, a working assumption behind many Monte Carlo diagnostics and
459+
`confidence-interval heuristics`_. Tests are provided for: (i) skewness-only,
460+
(ii) kurtosis-only, and (iii) the *omnibus* combination. OpenMC uses the
461+
finite-sample-adjusted skewness :math:`G_1` and excess kurtosis :math:`G_2`
462+
above to construct standardized normal scores :math:`Z_1` (from :math:`G_1`) and
463+
:math:`Z_2` (from :math:`G_2`) via the D'Agostino-Pearson transformations. The
464+
omnibus statistic is
465+
466+
.. math::
467+
468+
K^2 \;=\; Z_1^{\,2} \;+\; Z_2^{\,2}
469+
\;\sim\; \chi^2_{(2)} \quad \text{under } H_0:\ \text{normality}.
470+
471+
OpenMC reports :math:`Z_1`, :math:`Z_2`, :math:`K^2`, and their p-values when
472+
prerequisites are met (skewness for :math:`n\ge 3`, kurtosis and omnibus for
473+
:math:`n\ge 4`). Given a user-chosen significance level :math:`\alpha` (default
474+
is :math:`0.05`), reject :math:`H_0` if :math:`\text{p-value}<\alpha`; otherwise
475+
fail to reject. OpenMC leaves the interpretation to the user, who should
476+
consider VOV together with skewness, kurtosis, and normality tests results when
477+
judging whether reported confidence intervals are credible for their application
478+
[#norm-tests]_.
479+
480+
.. [#norm-tests]
481+
Higher-moments accumulation must be enabled with ``higher_moments = True``
482+
for running these diagnostics including the skewness, kurtosis, and normality
483+
tests.
484+
390485
Figure of Merit
391486
+++++++++++++++
392487

@@ -405,14 +500,16 @@ defined as
405500
.. math::
406501
:label: relative_error
407502
408-
r = \frac{s_\bar{X}}{\bar{x}}.
503+
r = \frac{s_{\bar{X}}}{\bar{x}}.
409504
410505
Based on this definition, one can see that a higher FOM is desirable. The FOM is
411506
useful as a comparative tool. For example, if a variance reduction technique is
412507
being applied to a simulation, the FOM with variance reduction can be compared
413508
to the FOM without variance reduction to ascertain whether the reduction in
414509
variance outweighs the potential increase in execution time (e.g., due to
415-
particle splitting).
510+
particle splitting). It is important to note that MCNP reports the FOM using CPU
511+
time (wall-clock time multiplied by the number of threads/cores), whereas OpenMC
512+
reports the FOM using only the wall-clock time :math:`t`.
416513

417514
Confidence Intervals
418515
++++++++++++++++++++
@@ -521,6 +618,8 @@ improve the estimate of the percentile.
521618

522619
.. rubric:: References
523620

621+
.. _confidence-interval heuristics: https://doi.org/10.1080/00031305.1990.10475751
622+
524623
.. _following approximation: https://doi.org/10.1080/03610918708812641
525624

526625
.. _Bessel's correction: https://en.wikipedia.org/wiki/Bessel's_correction
@@ -541,6 +640,10 @@ improve the estimate of the percentile.
541640

542641
.. _converges in distribution: https://en.wikipedia.org/wiki/Convergence_of_random_variables#Convergence_in_distribution
543642

643+
.. _skewness: https://en.wikipedia.org/wiki/Skewness
644+
645+
.. _kurtosis: https://en.wikipedia.org/wiki/Kurtosis
646+
544647
.. _confidence intervals: https://en.wikipedia.org/wiki/Confidence_interval
545648

546649
.. _Student's t-distribution: https://en.wikipedia.org/wiki/Student%27s_t-distribution

include/openmc/constants.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ enum class MgxsType {
291291
// ============================================================================
292292
// TALLY-RELATED CONSTANTS
293293

294-
enum class TallyResult { VALUE, SUM, SUM_SQ, SIZE };
294+
enum class TallyResult { VALUE, SUM, SUM_SQ, SUM_THIRD, SUM_FOURTH };
295295

296296
enum class TallyType { VOLUME, MESH_SURFACE, SURFACE, PULSE_HEIGHT };
297297

include/openmc/hdf5_interface.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ void read_llong(hid_t obj_id, const char* name, long long* buffer, bool indep);
100100
void read_string(
101101
hid_t obj_id, const char* name, size_t slen, char* buffer, bool indep);
102102

103-
void read_tally_results(
104-
hid_t group_id, hsize_t n_filter, hsize_t n_score, double* results);
103+
void read_tally_results(hid_t group_id, hsize_t n_filter, hsize_t n_score,
104+
hsize_t n_results, double* results);
105105
void write_attr_double(hid_t obj_id, int ndim, const hsize_t* dims,
106106
const char* name, const double* buffer);
107107
void write_attr_int(hid_t obj_id, int ndim, const hsize_t* dims,
@@ -114,9 +114,9 @@ void write_int(hid_t group_id, int ndim, const hsize_t* dims, const char* name,
114114
void write_llong(hid_t group_id, int ndim, const hsize_t* dims,
115115
const char* name, const long long* buffer, bool indep);
116116
void write_string(hid_t group_id, int ndim, const hsize_t* dims, size_t slen,
117-
const char* name, char const* buffer, bool indep);
118-
void write_tally_results(
119-
hid_t group_id, hsize_t n_filter, hsize_t n_score, const double* results);
117+
const char* name, const char* buffer, bool indep);
118+
void write_tally_results(hid_t group_id, hsize_t n_filter, hsize_t n_score,
119+
hsize_t n_results, const double* results);
120120
} // extern "C"
121121

122122
//==============================================================================

include/openmc/tallies/tally.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ class Tally {
106106

107107
bool writable() const { return writable_; }
108108

109+
bool higher_moments() const { return higher_moments_; }
110+
109111
//----------------------------------------------------------------------------
110112
// Other methods.
111113

@@ -190,6 +192,9 @@ class Tally {
190192
//! Whether to multiply by atom density for reaction rates
191193
bool multiply_density_ {true};
192194

195+
//! Whether to accumulate higher moments (third and fourth)
196+
bool higher_moments_ {false};
197+
193198
int64_t index_;
194199
};
195200

openmc/statepoint.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,10 @@ def tallies(self):
434434
if "multiply_density" in group.attrs:
435435
tally.multiply_density = group.attrs["multiply_density"].item() > 0
436436

437+
# Check if tally has higher_moments attribute
438+
if 'higher_moments' in group.attrs:
439+
tally.higher_moments = bool(group.attrs['higher_moments'][()])
440+
437441
# Read the number of realizations
438442
n_realizations = group['n_realizations'][()]
439443

0 commit comments

Comments
 (0)