@@ -30,6 +30,11 @@ def exprel2(x):
30
30
return hyp1f1 (1 , 3 , x )
31
31
32
32
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
+
33
38
class Univariate (EqualityMixin , ABC ):
34
39
"""Probability distribution of a single random variable.
35
40
@@ -977,55 +982,75 @@ def cdf(self):
977
982
c [1 :] = p [:x .size - 1 ] * np .diff (x )
978
983
elif self .interpolation == 'linear-linear' :
979
984
c [1 :] = 0.5 * (p [:- 1 ] + p [1 :]) * np .diff (x )
980
- elif self .interpolation == ' linear-log' :
985
+ elif self .interpolation == " linear-log" :
981
986
m = np .diff (p ) / np .diff (np .log (x ))
982
987
c [1 :] = p [:- 1 ] * np .diff (x ) + m * (
983
988
x [1 :] * (np .diff (np .log (x )) - 1.0 ) + x [:- 1 ]
984
989
)
985
- elif self .interpolation == ' log-linear' :
990
+ elif self .interpolation == " log-linear" :
986
991
m = np .diff (np .log (p )) / np .diff (x )
987
992
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 )))
991
996
else :
992
997
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"
995
1000
)
996
1001
997
1002
return np .cumsum (c )
998
1003
999
1004
def mean (self ):
1000
1005
"""Compute the mean of the tabular distribution"""
1001
-
1006
+
1002
1007
# use normalized probabilities when computing mean
1003
1008
p = self .p / self .cdf ().max ()
1004
1009
x = self .x
1005
1010
x_min = x [:- 1 ]
1006
1011
x_max = x [1 :]
1007
- p_min = p [:x .size - 1 ]
1012
+ p_min = p [: x .size - 1 ]
1008
1013
p_max = p [1 :]
1009
1014
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 ()
1023
1048
elif self .interpolation == "histogram" :
1024
1049
mean = (0.5 * (x_min + x_max ) * np .diff (x ) * p_min ).sum ()
1025
1050
else :
1026
1051
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"
1029
1054
)
1030
1055
return mean
1031
1056
@@ -1085,7 +1110,7 @@ def sample(self, n_samples: int = 1, seed: int | None = None):
1085
1110
quad [quad < 0.0 ] = 0.0
1086
1111
m [non_zero ] = x_i [non_zero ] + (np .sqrt (quad ) - p_i [non_zero ]) / m [non_zero ]
1087
1112
samples_out = m
1088
- elif self .interpolation == ' linear-log' :
1113
+ elif self .interpolation == " linear-log" :
1089
1114
# get variable and probability values for the
1090
1115
# next entry
1091
1116
x_i1 = self .x [cdf_idx + 1 ]
@@ -1110,44 +1135,30 @@ def sample(self, n_samples: int = 1, seed: int | None = None):
1110
1135
/ np .real (lambertw ((((xi - c_i ) / (m * x_i ) + a )) * np .exp (a ), - 1.0 ))
1111
1136
)[negative ]
1112
1137
samples_out = m
1113
- elif self .interpolation == ' log-linear' :
1138
+ elif self .interpolation == " log-linear" :
1114
1139
# get variable and probability values for the
1115
1140
# next entry
1116
1141
x_i1 = self .x [cdf_idx + 1 ]
1117
1142
p_i1 = p [cdf_idx + 1 ]
1118
1143
# compute slope between entries
1119
1144
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" :
1130
1149
# get variable and probability values for the
1131
1150
# next entry
1132
1151
x_i1 = self .x [cdf_idx + 1 ]
1133
1152
p_i1 = p [cdf_idx + 1 ]
1134
1153
# compute slope between entries
1135
1154
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 )
1146
1156
1157
+ samples_out = x_i * np .exp (f * log1prel (m * f ))
1147
1158
else :
1148
1159
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 "
1151
1162
)
1152
1163
1153
1164
assert all (samples_out < self .x [- 1 ])
@@ -1212,16 +1223,16 @@ def integral(self):
1212
1223
return np .sum (np .diff (self .x ) * self .p [:self .x .size - 1 ])
1213
1224
elif self .interpolation == 'linear-linear' :
1214
1225
return trapezoid (self .p , self .x )
1215
- elif self .interpolation == ' linear-log' :
1226
+ elif self .interpolation == " linear-log" :
1216
1227
m = np .diff (self .p ) / np .diff (np .log (self .x ))
1217
1228
return np .sum (
1218
1229
self .p [:- 1 ] * np .diff (self .x )
1219
1230
+ m * (self .x [1 :] * (np .diff (np .log (self .x )) - 1.0 ) + self .x [:- 1 ])
1220
1231
)
1221
- elif self .interpolation == ' log-linear' :
1232
+ elif self .interpolation == " log-linear" :
1222
1233
m = np .diff (np .log (self .p )) / np .diff (self .x )
1223
1234
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" :
1225
1236
m = np .diff (np .log (self .p )) / np .diff (np .log (self .x ))
1226
1237
return np .sum (
1227
1238
self .p [:- 1 ]
0 commit comments