Skip to content

Commit 3e6f151

Browse files
committed
simplified math
1 parent ee3f7bc commit 3e6f151

File tree

4 files changed

+84
-63
lines changed

4 files changed

+84
-63
lines changed

include/openmc/math_functions.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,12 @@ std::complex<double> w_derivative(std::complex<double> z, int order);
206206
//! \return (exp(x)-1)/x without loss of precision near 0
207207
double exprel(double x);
208208

209+
//! Evaluate relative logarithm function
210+
//!
211+
//! \param x Real argument
212+
//! \return log(1+x)/x without loss of precision near 0
213+
double log1prel(double x);
214+
209215
//! Evaluate principal branch of lambert_w function
210216
//!
211217
//! \param x Real argument

openmc/stats/univariate.py

Lines changed: 62 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ def exprel2(x):
3030
return hyp1f1(1, 3, x)
3131

3232

33+
def log1prel(x):
34+
"""Evaluate log(1+x)/x without loss of precision near 0"""
35+
return np.where(np.abs(x) < 1e-16, 1.0, np.log1p(x) / x)
36+
37+
3338
class Univariate(EqualityMixin, ABC):
3439
"""Probability distribution of a single random variable.
3540
@@ -977,55 +982,75 @@ def cdf(self):
977982
c[1:] = p[:x.size-1] * np.diff(x)
978983
elif self.interpolation == 'linear-linear':
979984
c[1:] = 0.5 * (p[:-1] + p[1:]) * np.diff(x)
980-
elif self.interpolation == 'linear-log':
985+
elif self.interpolation == "linear-log":
981986
m = np.diff(p) / np.diff(np.log(x))
982987
c[1:] = p[:-1] * np.diff(x) + m * (
983988
x[1:] * (np.diff(np.log(x)) - 1.0) + x[:-1]
984989
)
985-
elif self.interpolation == 'log-linear':
990+
elif self.interpolation == "log-linear":
986991
m = np.diff(np.log(p)) / np.diff(x)
987992
c[1:] = p[:-1] * np.diff(x) * exprel(m * np.diff(x))
988-
elif self.interpolation == 'log-log':
989-
m = np.diff(np.log(p)) / np.diff(np.log(x))
990-
c[1:] = p[:-1] * x[:-1] * np.diff(np.log(x)) * exprel((m + 1) * np.diff(np.log(x)))
993+
elif self.interpolation == "log-log":
994+
m = np.diff(np.log(x * p)) / np.diff(np.log(x))
995+
c[1:] = (x * p)[:-1] * np.diff(np.log(x)) * exprel(m * np.diff(np.log(x)))
991996
else:
992997
raise NotImplementedError(
993-
f'Cannot generate CDFs for tabular '
994-
f'distributions using {self.interpolation} interpolation'
998+
f"Cannot generate CDFs for tabular "
999+
f"distributions using {self.interpolation} interpolation"
9951000
)
9961001

9971002
return np.cumsum(c)
9981003

9991004
def mean(self):
10001005
"""Compute the mean of the tabular distribution"""
1001-
1006+
10021007
# use normalized probabilities when computing mean
10031008
p = self.p / self.cdf().max()
10041009
x = self.x
10051010
x_min = x[:-1]
10061011
x_max = x[1:]
1007-
p_min = p[:x.size-1]
1012+
p_min = p[: x.size - 1]
10081013
p_max = p[1:]
10091014

1010-
if self.interpolation == 'linear-linear':
1011-
m = np.diff(p)/np.diff(x)
1012-
mean = ((1./3.) * m * np.diff(x**3) + 0.5 * (p_min - m * x_min) * np.diff(x**2)).sum()
1013-
elif self.interpolation == 'linear-log':
1014-
m = np.diff(p)/np.diff(np.log(x))
1015-
mean = ((1.0/4.0) * m * x_min**2 * ((x_max/x_min)**2 * (2 * np.diff(np.log(x))-1) + 1) +
1016-
+ 0.5 * p_min * np.diff(x**2)).sum()
1017-
elif self.interpolation == 'log-linear':
1018-
m = np.diff(np.log(p))/np.diff(x)
1019-
mean = (p_min*(np.diff(x)**2*((0.5*exprel2(m*np.diff(x))*(m*np.diff(x)-1)+1))+np.diff(x)*x_min*exprel(m*np.diff(x)))).sum()
1020-
elif self.interpolation == 'log-log':
1021-
m = np.diff(np.log(p))/np.diff(np.log(x))
1022-
mean = (p_min*x_min**2*np.diff(np.log(x))*exprel((m+2)*np.diff(np.log(x)))).sum()
1015+
if self.interpolation == "linear-linear":
1016+
m = np.diff(p) / np.diff(x)
1017+
mean = (
1018+
(1.0 / 3.0) * m * np.diff(x**3)
1019+
+ 0.5 * (p_min - m * x_min) * np.diff(x**2)
1020+
).sum()
1021+
elif self.interpolation == "linear-log":
1022+
m = np.diff(p) / np.diff(np.log(x))
1023+
mean = (
1024+
(1.0 / 4.0)
1025+
* m
1026+
* x_min**2
1027+
* ((x_max / x_min) ** 2 * (2 * np.diff(np.log(x)) - 1) + 1)
1028+
+ +0.5 * p_min * np.diff(x**2)
1029+
).sum()
1030+
elif self.interpolation == "log-linear":
1031+
m = np.diff(np.log(p)) / np.diff(x)
1032+
mean = (
1033+
p_min
1034+
* (
1035+
np.diff(x) ** 2
1036+
* ((0.5 * exprel2(m * np.diff(x)) * (m * np.diff(x) - 1) + 1))
1037+
+ np.diff(x) * x_min * exprel(m * np.diff(x))
1038+
)
1039+
).sum()
1040+
elif self.interpolation == "log-log":
1041+
m = np.diff(np.log(p)) / np.diff(np.log(x))
1042+
mean = (
1043+
p_min
1044+
* x_min**2
1045+
* np.diff(np.log(x))
1046+
* exprel((m + 2) * np.diff(np.log(x)))
1047+
).sum()
10231048
elif self.interpolation == "histogram":
10241049
mean = (0.5 * (x_min + x_max) * np.diff(x) * p_min).sum()
10251050
else:
10261051
raise NotImplementedError(
1027-
f'Cannot compute mean for tabular '
1028-
f'distributions using {self.interpolation} interpolation'
1052+
f"Cannot compute mean for tabular "
1053+
f"distributions using {self.interpolation} interpolation"
10291054
)
10301055
return mean
10311056

@@ -1085,7 +1110,7 @@ def sample(self, n_samples: int = 1, seed: int | None = None):
10851110
quad[quad < 0.0] = 0.0
10861111
m[non_zero] = x_i[non_zero] + (np.sqrt(quad) - p_i[non_zero]) / m[non_zero]
10871112
samples_out = m
1088-
elif self.interpolation == 'linear-log':
1113+
elif self.interpolation == "linear-log":
10891114
# get variable and probability values for the
10901115
# next entry
10911116
x_i1 = self.x[cdf_idx + 1]
@@ -1110,44 +1135,30 @@ def sample(self, n_samples: int = 1, seed: int | None = None):
11101135
/ np.real(lambertw((((xi - c_i) / (m * x_i) + a)) * np.exp(a), -1.0))
11111136
)[negative]
11121137
samples_out = m
1113-
elif self.interpolation == 'log-linear':
1138+
elif self.interpolation == "log-linear":
11141139
# get variable and probability values for the
11151140
# next entry
11161141
x_i1 = self.x[cdf_idx + 1]
11171142
p_i1 = p[cdf_idx + 1]
11181143
# compute slope between entries
11191144
m = np.log(p_i1 / p_i) / (x_i1 - x_i)
1120-
# set values for zero slope
1121-
zero = m == 0.0
1122-
m[zero] = x_i[zero] + (xi[zero] - c_i[zero]) / p_i[zero]
1123-
# set values for non-zero slope
1124-
non_zero = ~zero
1125-
m[non_zero] = (
1126-
x_i[non_zero] + ((1 / m) * np.log1p(m * (xi - c_i) / p_i))[non_zero]
1127-
)
1128-
samples_out = m
1129-
elif self.interpolation == 'log-log':
1145+
f = (xi - c_i) / p_i
1146+
1147+
samples_out = x_i + f * log1prel(m * f)
1148+
elif self.interpolation == "log-log":
11301149
# get variable and probability values for the
11311150
# next entry
11321151
x_i1 = self.x[cdf_idx + 1]
11331152
p_i1 = p[cdf_idx + 1]
11341153
# compute slope between entries
11351154
m = np.log((x_i1 * p_i1) / (x_i * p_i)) / np.log(x_i1 / x_i)
1136-
# set values for zero slope
1137-
zero = m == 0.0
1138-
m[zero] = x_i[zero] * np.exp(
1139-
(xi[zero] - c_i[zero]) / (x_i[zero] * p_i[zero])
1140-
)
1141-
# set values for non-zero slope
1142-
non_zero = ~zero
1143-
m[non_zero] = (x_i * np.power(1.0 + m * (xi - c_i) / (x_i * p_i), 1.0 / m))[non_zero]
1144-
1145-
samples_out = m
1155+
f = (xi - c_i) / (x_i * p_i)
11461156

1157+
samples_out = x_i * np.exp(f * log1prel(m * f))
11471158
else:
11481159
raise NotImplementedError(
1149-
f'Cannot sample tabular distributions '
1150-
f'for {self.inteprolation} interpolation '
1160+
f"Cannot sample tabular distributions "
1161+
f"for {self.inteprolation} interpolation "
11511162
)
11521163

11531164
assert all(samples_out < self.x[-1])
@@ -1212,16 +1223,16 @@ def integral(self):
12121223
return np.sum(np.diff(self.x) * self.p[:self.x.size-1])
12131224
elif self.interpolation == 'linear-linear':
12141225
return trapezoid(self.p, self.x)
1215-
elif self.interpolation == 'linear-log':
1226+
elif self.interpolation == "linear-log":
12161227
m = np.diff(self.p) / np.diff(np.log(self.x))
12171228
return np.sum(
12181229
self.p[:-1] * np.diff(self.x)
12191230
+ m * (self.x[1:] * (np.diff(np.log(self.x)) - 1.0) + self.x[:-1])
12201231
)
1221-
elif self.interpolation == 'log-linear':
1232+
elif self.interpolation == "log-linear":
12221233
m = np.diff(np.log(self.p)) / np.diff(self.x)
12231234
return np.sum(self.p[:-1] * np.diff(self.x) * exprel(m * np.diff(self.x)))
1224-
elif self.interpolation == 'log-log':
1235+
elif self.interpolation == "log-log":
12251236
m = np.diff(np.log(self.p)) / np.diff(np.log(self.x))
12261237
return np.sum(
12271238
self.p[:-1]

src/distribution.cpp

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -310,8 +310,9 @@ void Tabular::init(
310310
} else if (interp_ == Interpolation::log_log) {
311311
double m = std::log((x_[i] * p_[i]) / (x_[i - 1] * p_[i - 1])) /
312312
std::log(x_[i] / x_[i - 1]);
313-
c_[i] = p_[i - 1] * std::log(x_[i] / x_[i - 1]) *
314-
exprel(m * std::log(x_[i] / x_[i - 1]));
313+
c_[i] = c_[i - 1] + x_[i - 1] * p_[i - 1] *
314+
std::log(x_[i] / x_[i - 1]) *
315+
exprel(m * std::log(x_[i] / x_[i - 1]));
315316
} else {
316317
UNREACHABLE();
317318
}
@@ -389,22 +390,16 @@ double Tabular::sample(uint64_t* seed) const
389390
double p_i1 = p_[i + 1];
390391

391392
double m = std::log(p_i1 / p_i) / (x_i1 - x_i);
392-
if (m == 0.0) {
393-
return x_i + (c - c_i) / p_i;
394-
} else {
395-
return x_i + 1.0 / m * std::log1p(m * (c - c_i) / p_i);
396-
}
393+
double f = (c - c_i) / p_i;
394+
return x_i + f * log1prel(m * f);
397395
} else if (interp_ == Interpolation::log_log) {
398396
// Log-Log interpolation
399397
double x_i1 = x_[i + 1];
400398
double p_i1 = p_[i + 1];
401399

402400
double m = std::log((x_i1 * p_i1) / (x_i * p_i)) / std::log(x_i1 / x_i);
403-
if (m == 0.0) {
404-
return x_i * std::exp((c - c_i) / (p_i * x_i));
405-
} else {
406-
return x_i * std::pow(1.0 + m * (c - c_i) / (p_i * x_i), 1.0 / m);
407-
}
401+
double f = (c - c_i) / (p_i * x_i);
402+
return x_i * std::exp(f * log1prel(m * f));
408403
} else {
409404
UNREACHABLE();
410405
}

src/math_functions.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,15 @@ double exprel(double x)
929929
}
930930
}
931931

932+
double log1prel(double x)
933+
{
934+
if (std::abs(x) < 1e-16)
935+
return 1.0;
936+
else {
937+
return std::log1p(x) / x;
938+
}
939+
}
940+
932941
double lambert_w0(double x)
933942
{
934943
return LambertW::lambert_w0(x);

0 commit comments

Comments
 (0)