Skip to content

Commit f0e32be

Browse files
author
Gregoire Biot
committed
New tests
1 parent 9be39c3 commit f0e32be

File tree

3 files changed

+137
-21
lines changed

3 files changed

+137
-21
lines changed

openmc/tallies.py

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -555,30 +555,24 @@ def std_dev(self):
555555
return self._std_dev
556556

557557
@staticmethod
558-
def _VOV_from_sums(n, mean, sum1, sum2, sum3, sum4):
559-
nonzero = np.abs(mean) > 0
560-
vov = np.zeros_like(mean, dtype=float)
558+
def _VOV_from_sums(n, sum1, sum2, sum3, sum4):
559+
560+
vov = np.zeros_like(sum1, dtype=float)
561561

562562
numerator = sum4 - ( 4.0*sum3*sum1 ) / n + (6.0 * sum2 * (sum1)**2) / (n**2) - (3.0 * (sum1)**4 ) / (n**3)
563563
denominator = (sum2 - (1.0/n)*(sum1)**2) **2
564564

565-
# Apply the nonzero mask to numerator and denominator
566-
numerator = numerator[nonzero]
567-
denominator = denominator[nonzero]
568-
569-
# Ensure the shapes match
570-
numerator = np.reshape(numerator, vov[nonzero].shape)
571-
denominator = np.reshape(denominator, vov[nonzero].shape)
572-
573-
vov[nonzero] = numerator / denominator - 1.0 / n
565+
mask = denominator > 0.0
566+
567+
vov[mask] = numerator[mask] / denominator[mask] - 1.0 / n
574568

575569
return vov
576570

577571
@property
578572
def VOV(self):
579573
if not self._sp_filename:
580574
return None
581-
return Tally._VOV_from_sums(self.num_realizations, self.mean, self.sum, self.sum_sq, self.sum_rd, self.sum_th)
575+
return Tally._VOV_from_sums(self.num_realizations, self.sum, self.sum_sq, self.sum_rd, self.sum_th)
582576

583577
@property
584578
def normality_test(self):

tests/unit_tests/test_stats.py

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import openmc
66
import openmc.stats
77
from scipy.integrate import trapezoid
8-
8+
from scipy import stats as sps
99

1010
def assert_sample_mean(samples, expected_mean):
1111
# Calculate sample standard deviation
@@ -506,3 +506,123 @@ def test_combine_distributions():
506506
# uncertainty of the expected value
507507
samples = combined.sample(10_000)
508508
assert_sample_mean(samples, 0.25)
509+
510+
@pytest.mark.parametrize("x, skew_true, kurt_true", [
511+
([-1, 0, 1], 0.0, 1.5),
512+
([-1, 1], 0.0, 1.0),
513+
([0, 0, 1, 1], 0.0, 1.0),
514+
([0, 1, 2, 3], 0.0, 41/25),
515+
])
516+
517+
def test_b1_b2_analytical(x, skew_true, kurt_true):
518+
x = np.asarray(x, dtype=float)
519+
n = x.size
520+
mean = x.mean()
521+
s2 = (x**2).sum()
522+
s3 = (x**3).sum()
523+
s4 = (x**4).sum()
524+
525+
sqrt_b1, b2 = openmc.tally_stats._calc_b1_b2(n, np.array([mean]),
526+
np.array([s2]), np.array([s3]), np.array([s4]))
527+
skew = sqrt_b1.item()
528+
kurt = b2.item()
529+
530+
assert np.isclose(skew, skew_true, rtol=0, atol=1e-15)
531+
assert np.isclose(kurt, kurt_true, rtol=0, atol=1e-14)
532+
533+
@pytest.mark.parametrize("draw, skew_true, kurt_true", [
534+
(lambda rng, n: rng.normal(0, 1, n), 0.0, 3.0),
535+
(lambda rng, n: rng.random(n), 0.0, 1.8),
536+
(lambda rng, n: rng.exponential(1.0, n), 2.0, 9.0),
537+
(lambda rng, n: (rng.random(n) < 0.3).astype(float),
538+
(1-2*0.3)/np.sqrt(0.3*0.7),
539+
(1 - 3*0.3 + 3*0.3**2)/(0.3*0.7)),
540+
])
541+
542+
def test_b1_b2_scipy(draw, skew_true, kurt_true):
543+
rng = np.random.default_rng(12345)
544+
N = 200_000
545+
x = draw(rng, N)
546+
547+
n = x.size
548+
mean = x.mean()
549+
s2 = (x**2).sum()
550+
s3 = (x**3).sum()
551+
s4 = (x**4).sum()
552+
553+
sqrt_b1, b2 = openmc.tally_stats._calc_b1_b2(n, np.array([mean]),
554+
np.array([s2]), np.array([s3]), np.array([s4]))
555+
skew_mine = sqrt_b1.item()
556+
kurt_mine = b2.item()
557+
558+
# Scipy values
559+
skew_sp = sps.skew(x, bias=True)
560+
kurt_sp = sps.kurtosis(x, fisher=False, bias=True)
561+
562+
# Compare to SciPy
563+
assert np.isclose(skew_mine, skew_sp, rtol=0, atol=5e-3)
564+
assert np.isclose(kurt_mine, kurt_sp, rtol=0, atol=5e-3)
565+
566+
tol_skew = 0.02 if abs(skew_true) < 0.5 else 0.05
567+
tol_kurt = 0.03 if kurt_true < 4 else 0.1
568+
569+
assert abs(skew_mine - skew_true) < tol_skew
570+
assert abs(kurt_mine - kurt_true) < tol_kurt
571+
572+
def ztests_scipy_comparison():
573+
rng = np.random.default_rng(987)
574+
x_norm = rng.normal(size=50_000)
575+
x_exp = rng.exponential(size=50_000)
576+
577+
# Normal dataset
578+
n0 = x_norm.size
579+
mean0 = x_norm.mean()
580+
s20 = (x_norm**2).sum()
581+
s30 = (x_norm**3).sum()
582+
s40 = (x_norm**4).sum()
583+
Zb1_0, p_skew_0, _ = openmc.tally_stats.skewness_test(n0, mean0, s20, s30, s40)
584+
Zb2_0, p_kurt_0, _ = openmc.tally_stats.kurtosis_test(n0, mean0, s20, s30, s40)
585+
K2_0, p_omni_0 = openmc.tally_stats.k2_test(Zb1_0, Zb2_0)
586+
587+
# SciPy Z and p values
588+
z_skew_0, p_skew_sp0 = sps.skewtest(x_norm)
589+
z_kurt_0, p_kurt_sp0 = sps.kurtosistest(x_norm)
590+
k2_sp0, p_omni_sp0 = sps.normaltest(x_norm)
591+
592+
assert np.isclose(Zb1_0, z_skew_0, atol=0.15)
593+
assert np.isclose(Zb2_0, z_kurt_0, atol=0.15)
594+
assert np.isclose(K2_0, k2_sp0, atol=0.3)
595+
assert np.isclose(p_skew_0, p_skew_sp0, atol=5e-3)
596+
assert np.isclose(p_kurt_0, p_kurt_sp0, atol=5e-3)
597+
assert np.isclose(p_omni_0, p_omni_sp0, atol=5e-3)
598+
599+
# Exponential dataset
600+
n1 = x_exp.size
601+
mean1 = x_exp.mean()
602+
s21 = (x_exp**2).sum()
603+
s31 = (x_exp**3).sum()
604+
s41 = (x_exp**4).sum()
605+
Zb1_1, p_skew_1, _ = openmc.tally_stats.skewness_test(n1, mean1, s21, s31, s41)
606+
Zb2_1, p_kurt_1, _ = openmc.tally_stats.kurtosis_test(n1, mean1, s21, s31, s41)
607+
K2_1, p_omni_1 = openmc.tally_stats.k2_test(Zb1_1, Zb2_1)
608+
609+
# SciPy Z and p values
610+
z_skew_1, p_skew_sp1 = sps.skewtest(x_exp)
611+
z_kurt_1, p_kurt_sp1 = sps.kurtosistest(x_exp)
612+
k2_sp1, p_omni_sp1 = sps.normaltest(x_exp)
613+
614+
# Both pipelines should reject (p ≪ 1e-6)
615+
assert p_skew_1 < 1e-6 and p_skew_sp1 < 1e-6
616+
assert p_kurt_1 < 1e-6 and p_kurt_sp1 < 1e-6
617+
assert p_omni_1 < 1e-6 and p_omni_sp1 < 1e-6
618+
619+
# Right-skewed: large positive Zb1
620+
assert Zb1_1 > 30
621+
assert z_skew_1 > 30
622+
# Heavy-tailed: large positive Zb2
623+
assert Zb2_1 > 30
624+
assert z_kurt_1 > 30
625+
# Combined omnibus rejects
626+
assert K2_1 > 2000
627+
assert k2_sp1 >2000
628+

tests/unit_tests/test_tallies.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -151,14 +151,16 @@ def test_tally_normality_functions():
151151
])
152152

153153
def test_vov_deterministic(x, expected):
154-
x = np.arange(1, 15, dtype=float)
154+
print(x)
155+
x = np.asarray(x, dtype=float)
155156
N = x.size
156-
S1, S2, S3, S4 = [x.sum(),(x**2).sum(),(x**3).sum(), (x**4).sum()]
157-
158-
mean = np.array([x.mean()])
159-
vov = openmc.Tally._VOV_from_sums(N, mean,
160-
np.array([S1]), np.array([S2]),
161-
np.array([S3]), np.array([S4]))[0]
157+
S1 = x.sum()
158+
S2 = (x**2).sum()
159+
S3 = (x**3).sum()
160+
S4 = (x**4).sum()
161+
vov = openmc.Tally._VOV_from_sums(N, np.array([S1]), np.array([S2]),
162+
np.array([S3]), np.array([S4]))[0]
163+
print("VOV=", vov)
162164
assert np.isclose(vov, float(expected), rtol=0, atol=1e-15)
163165

164166
def test_tally_normality_stats(sphere_model, run_in_tmpdir):

0 commit comments

Comments
 (0)