Skip to content

Commit 055ea15

Browse files
authored
Clip mixture distributions based on mean times integral (#3603)
1 parent b94b496 commit 055ea15

File tree

1 file changed

+64
-12
lines changed

1 file changed

+64
-12
lines changed

openmc/stats/univariate.py

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,20 @@ def integral(self):
295295
"""
296296
return np.sum(self.p)
297297

298+
def mean(self) -> float:
299+
"""Return mean of the discrete distribution
300+
301+
The mean is the weighted average of the discrete values.
302+
303+
.. versionadded:: 0.15.3
304+
305+
Returns
306+
-------
307+
float
308+
Mean of discrete distribution
309+
"""
310+
return np.sum(self.x * self.p) / np.sum(self.p)
311+
298312
def clip(self, tolerance: float = 1e-6, inplace: bool = False) -> Discrete:
299313
r"""Remove low-importance points from discrete distribution.
300314
@@ -413,6 +427,18 @@ def sample(self, n_samples=1, seed=None):
413427
rng = np.random.RandomState(seed)
414428
return rng.uniform(self.a, self.b, n_samples)
415429

430+
def mean(self) -> float:
431+
"""Return mean of the uniform distribution
432+
433+
.. versionadded:: 0.15.3
434+
435+
Returns
436+
-------
437+
float
438+
Mean of uniform distribution
439+
"""
440+
return 0.5 * (self.a + self.b)
441+
416442
def to_xml_element(self, element_name: str):
417443
"""Return XML representation of the uniform distribution
418444
@@ -1123,7 +1149,7 @@ def from_xml_element(cls, elem: ET.Element):
11231149
11241150
"""
11251151
interpolation = get_text(elem, 'interpolation')
1126-
params = get_elem_list(elem, "parameters", float)
1152+
params = get_elem_list(elem, "parameters", float)
11271153
m = (len(params) + 1)//2 # +1 for when len(params) is odd
11281154
x = params[:m]
11291155
p = params[m:]
@@ -1347,6 +1373,30 @@ def integral(self):
13471373
for p, dist in zip(self.probability, self.distribution)
13481374
])
13491375

1376+
def mean(self) -> float:
1377+
"""Return mean of the mixture distribution
1378+
1379+
The mean is the weighted average of the means of the component
1380+
distributions, weighted by probability * integral.
1381+
1382+
.. versionadded:: 0.15.3
1383+
1384+
Returns
1385+
-------
1386+
float
1387+
Mean of the mixture distribution
1388+
"""
1389+
# Weight each component by its probability and integral
1390+
weights = [p*dist.integral() for p, dist in
1391+
zip(self.probability, self.distribution)]
1392+
total_weight = sum(weights)
1393+
1394+
if total_weight == 0:
1395+
return 0.0
1396+
1397+
return sum([w*dist.mean() for w, dist in
1398+
zip(weights, self.distribution)]) / total_weight
1399+
13501400
def clip(self, tolerance: float = 1e-6, inplace: bool = False) -> Mixture:
13511401
r"""Remove low-importance points / distributions
13521402
@@ -1369,14 +1419,14 @@ def clip(self, tolerance: float = 1e-6, inplace: bool = False) -> Mixture:
13691419
Distribution with low-importance points / distributions removed
13701420
13711421
"""
1372-
# Determine integral of original distribution to compare later
1373-
original_integral = self.integral()
1422+
# Calculate mean * integral for original distribution to compare later.
1423+
original_mean_integral = self.mean() * self.integral()
13741424

13751425
# Determine indices for any distributions that contribute non-negligibly
1376-
# to overall intensity
1377-
intensities = [prob*dist.integral() for prob, dist in
1378-
zip(self.probability, self.distribution)]
1379-
indices = _intensity_clip(intensities, tolerance=tolerance)
1426+
# to overall mean * integral
1427+
mean_integrals = [prob*dist.mean()*dist.integral() for prob, dist in
1428+
zip(self.probability, self.distribution)]
1429+
indices = _intensity_clip(mean_integrals, tolerance=tolerance)
13801430

13811431
# Clip mixture of distributions
13821432
probability = self.probability[indices]
@@ -1397,12 +1447,14 @@ def clip(self, tolerance: float = 1e-6, inplace: bool = False) -> Mixture:
13971447
# Create new distribution
13981448
new_dist = type(self)(probability, distribution)
13991449

1400-
# Show warning if integral of new distribution is not within
1401-
# tolerance of original
1402-
diff = (original_integral - new_dist.integral())/original_integral
1450+
# Show warning if mean * integral of new distribution is not within
1451+
# tolerance of original. For energy distributions, mean * integral
1452+
# represents total energy.
1453+
new_mean_integral = new_dist.mean() * new_dist.integral()
1454+
diff = (original_mean_integral - new_mean_integral)/original_mean_integral
14031455
if diff > tolerance:
1404-
warn("Clipping mixture distribution resulted in an integral that is "
1405-
f"lower by a fraction of {diff} when tolerance={tolerance}.")
1456+
warn("Clipping mixture distribution resulted in a mean*integral "
1457+
f"that is lower by a fraction of {diff} when tolerance={tolerance}.")
14061458

14071459
return new_dist
14081460

0 commit comments

Comments
 (0)