Storm
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
numerical.cpp
Go to the documentation of this file.
2
3#include <boost/math/constants/constants.hpp>
4#include <cmath>
5
10
11namespace storm {
12namespace utility {
13namespace numerical {
14
15template<typename ValueType>
16FoxGlynnResult<ValueType>::FoxGlynnResult() : left(0), right(0), totalWeight(storm::utility::zero<ValueType>()) {
17 // Intentionally left empty.
18}
19
29template<typename ValueType>
30FoxGlynnResult<ValueType> foxGlynnFinder(ValueType lambda, ValueType epsilon) {
31 ValueType tau = std::numeric_limits<ValueType>::min();
32 ValueType omega = std::numeric_limits<ValueType>::max();
33 ValueType const sqrt_2_pi = boost::math::constants::root_two_pi<ValueType>();
34 ValueType const log10_e = std::log10(boost::math::constants::e<ValueType>());
35
36 uint64_t m = static_cast<uint64_t>(lambda);
37
38 int64_t left = 0;
39 int64_t right = 0;
40
41 // tau is only used in underflow checks, which we are going to do in the logarithm domain.
42 tau = log(tau);
43
44 // In error bound comparisons, we always compare with epsilon*sqrt_2_pi.
45 epsilon *= sqrt_2_pi;
46
47 // Compute left truncation point.
48 if (m < 25) {
49 // For lambda below 25 the exponential can be smaller than tau. If that is the case we expect
50 // underflows and warn the user.
51 if (-lambda <= tau) {
52 STORM_LOG_WARN("Fox-Glynn: 0 < lambda < 25, underflow near Poi(" << lambda << ", 0) = " << std::exp(-lambda) << ". The results are unreliable.");
53 }
54
55 // Zero is used as left truncation point for lambda <= 25.
56 left = 0;
57 } else {
58 // Compute the left truncation point for lambda >= 25 (for lambda < 25 we use zero as left truncation point).
59
60 ValueType const bl = (1 + 1 / lambda) * std::exp((1 / lambda) * 0.125);
61 ValueType const sqrt_lambda = std::sqrt(lambda);
62 int64_t k;
63
64 // Start looking for the left truncation point:
65 // * start search at k=4 (taken from original Fox-Glynn paper)
66 // * increase the left truncation point until we fulfil the error condition
67
68 for (k = 4;; ++k) {
69 ValueType max_err;
70
71 left = m - static_cast<int64_t>(std::ceil(k * sqrt_lambda + 0.5));
72
73 // For small lambda the above calculation can yield negative truncation points, crop them here.
74 if (left <= 0) {
75 left = 0;
76 break;
77 }
78
79 // Note that Propositions 2-4 in Fox--Glynn mix up notation: they write Phi where they mean
80 // 1 - Phi. (In Corollaries 1 and 2, phi is used correctly again.)
81 max_err = bl * exp(-0.5 * (k * k)) / k;
82 if (max_err * 2 <= epsilon) {
83 // If the error on the left hand side is smaller, we can be more lenient on the right hand
84 // side. To this end, we now set epsilon to the part of the error that has not yet been eaten
85 // up by the left-hand truncation.
86 epsilon -= max_err;
87 break;
88 }
89 }
90
91 // Finally the left truncation point is found.
92 }
93
94 // Compute right truncation point.
95 {
96 ValueType lambda_max;
97 int64_t m_max, k;
98
99 // According to Fox-Glynn, if lambda < 400 we should take lambda = 400, otherwise use the original
100 // value. This is for computing the right truncation point.
101 if (m < 400) {
102 lambda_max = 400;
103 m_max = 400;
104 epsilon *= 0.662608824988162441697980;
105 /* i.e. al = (1+1/400) * exp(1/16) * sqrt_2; epsilon /= al; */
106 } else {
107 lambda_max = lambda;
108 m_max = m;
109 epsilon *= (1 - 1 / (lambda + 1)) * 0.664265347050632847802225;
110 /* i.e. al = (1+1/lambda) * exp(1/16) * sqrt_2; epsilon /= al; */
111 }
112
113 // Find right truncation point.
114
115 // This loop is a modification to the original Fox-Glynn paper.
116 // The search for the right truncation point is only terminated by the error condition and not by
117 // the stop index from the FG paper. This can yield more accurate results if necessary.
118 for (k = 4;; ++k) {
119 // dkl_inv is between 1 - 1e-33 and 1 if lambda_max >= 400 and k >= 4; this will always be
120 // rounded to 1.0. We therefore leave the factor out.
121 // double dkl_inv=1 - exp(-266/401.0 * (k*sqrt(2*lambda_max) + 1.5));
122
123 // actually: "k * (dkl_inv*epsilon/al) >= exp(-0.5 * k^2)", but epsilon has been changed appropriately.
124 if (k * epsilon >= exp(-0.5 * (k * k))) {
125 break;
126 }
127 }
128 right = m_max + static_cast<int64_t>(std::ceil(k * std::sqrt(2 * lambda_max) + 0.5));
129 if (right > m_max + static_cast<int64_t>(std::ceil((lambda_max + 1) * 0.5))) {
130 STORM_LOG_WARN("Fox-Glynn: right = " << right << " >> lambda = " << lambda_max << ", cannot bound the right tail. The results are unreliable.");
131 }
132 }
133
134 // Time to set the initial value for weights.
136 fgresult.left = static_cast<uint64_t>(left);
137 fgresult.right = static_cast<uint64_t>(right);
138 fgresult.weights.resize(fgresult.right - fgresult.left + 1);
139
140 fgresult.weights[m - left] = omega / (1.0e+10 * (right - left));
141
142 if (m >= 25) {
143 // Perform underflow check.
144 ValueType result, log_c_m_inf;
145 int64_t i;
146
147 // we are going to compare with tau - log(w[m]).
148 tau -= std::log(fgresult.weights[m - left]);
149
150 // We take the c_m_inf = 0.14627 / sqrt( m ), as for lambda >= 25
151 // c_m = 1 / ( sqrt( 2.0 * pi * m ) ) * exp( m - lambda - 1 / ( 12.0 * m ) ) => c_m_inf.
152 // Note that m-lambda is in the interval (-1,0], and -1/(12*m) is in [-1/(12*25),0).
153 // So, exp(m-lambda - 1/(12*m)) is in (exp(-1-1/(12*25)),exp(0)).
154 // Therefore, we can improve the lower bound on c_m to exp(-1-1/(12*25)) / sqrt(2*pi) = ~0.14627.
155 // Its logarithm is -1 - 1/(12*25) - log(2*pi) * 0.5 = ~ -1.922272 (rounded towards -infinity).
156 log_c_m_inf = -1.922272 - log((double)m) * 0.5;
157
158 // We use FG's Proposition 6 directly (and not Corollary 4 i and ii), as k_prime may be too large
159 // if pFG->left == 0.
160 i = m - left;
161
162 // Equivalent to 2*i <= m, equivalent to i <= lambda/2.
163 if (i <= left) {
164 // Use Proposition 6 (i). Note that Fox--Glynn are off by one in the proof of this proposition;
165 // they sum up to i-1, but should have summed up to i. */
166 result = log_c_m_inf - i * (i + 1) * (0.5 + (2 * i + 1) / (6 * lambda)) / lambda;
167 } else {
168 // Use Corollary 4 (iii). Note that k_prime <= sqrt(m+1)/m is a misprint for k_prime <= m/sqrt(m+1),
169 // which is equivalent to left >= 0, which holds trivially.
170 result = -lambda;
171 if (left != 0) {
172 // Also use Proposition 6 (ii).
173 double result_1 = log_c_m_inf + i * log(1 - i / (double)(m + 1));
174
175 // Take the maximum.
176 if (result_1 > result) {
177 result = result_1;
178 }
179 }
180 }
181 if (result <= tau) {
182 int64_t const log10_result = static_cast<int64_t>(std::floor(result * log10_e));
183 STORM_LOG_WARN("Fox-Glynn: lambda >= 25, underflow near Poi(" << lambda << "," << left << ") <= " << std::exp(result - log10_result / log10_e)
184 << log10_result << ". The results are unreliable.");
185 }
186
187 // We still have to perform an underflow check for the right truncation point when lambda >= 400.
188 if (m >= 400) {
189 // Use Proposition 5 of Fox--Glynn.
190 i = right - m;
191 result = log_c_m_inf - i * (i + 1) / (2 * lambda);
192 if (result <= tau) {
193 int64_t const log10_result = static_cast<int64_t>(std::floor(result * log10_e));
194 STORM_LOG_WARN("Fox-Glynn: lambda >= 25, underflow near Poi(" << lambda << "," << right << ") <= " << std::exp(result - log10_result / log10_e)
195 << log10_result << ". The results are unreliable.");
196 }
197 }
198 }
199
200 return fgresult;
201}
202
203template<typename ValueType>
204FoxGlynnResult<ValueType> foxGlynnWeighter(ValueType lambda, ValueType epsilon) {
205 ValueType tau = std::numeric_limits<ValueType>::min();
206
207 // The magic m point.
208 uint64_t m = static_cast<uint64_t>(lambda);
209 int64_t j, t;
210
211 FoxGlynnResult<ValueType> result = foxGlynnFinder(lambda, epsilon);
212
213 // Fill the left side of the array.
214 for (j = m - result.left; j > 0; --j) {
215 result.weights[j - 1] = (j + result.left) / lambda * result.weights[j];
216 }
217
218 t = result.right - result.left;
219
220 // Fill the right side of the array, have two cases lambda < 400 & lambda >= 400.
221 if (m < 400) {
222 // Perform the underflow check, according to Fox-Glynn.
223 STORM_LOG_ERROR_COND(result.right <= 600, "Fox-Glynn: " << result.right << " > 600, underflow is possible.");
224
225 // Compute weights.
226 for (j = m - result.left; j < t; ++j) {
227 ValueType q = lambda / (j + 1 + result.left);
228 if (result.weights[j] > tau / q) {
229 result.weights[j + 1] = q * result.weights[j];
230 } else {
231 t = j;
232 result.right = j + result.left;
233 result.weights.resize(result.right - result.left + 1);
234
235 // It's time to compute W.
236 break;
237 }
238 }
239 } else {
240 // Compute weights.
241 for (j = m - result.left; j < t; ++j) {
242 result.weights[j + 1] = lambda / (j + 1 + result.left) * result.weights[j];
243 }
244 }
245
246 // It is time to compute the normalization weight W.
247 result.totalWeight = storm::utility::zero<ValueType>();
248 j = 0;
249
250 // t was set above.
251 while (j < t) {
252 if (result.weights[j] <= result.weights[t]) {
253 result.totalWeight += result.weights[j];
254 j++;
255 } else {
256 result.totalWeight += result.weights[t];
257 t--;
258 }
259 }
260 result.totalWeight += result.weights[j];
261
262 STORM_LOG_TRACE("Fox-Glynn: ltp = " << result.left << ", rtp = " << result.right << ", w = " << result.totalWeight << ", " << result.weights.size()
263 << " weights.");
264
265 return result;
266}
267
268template<typename ValueType>
269FoxGlynnResult<ValueType> foxGlynn(ValueType lambda, ValueType epsilon) {
270 STORM_LOG_THROW(lambda > 0, storm::exceptions::InvalidArgumentException, "Fox-Glynn requires positive lambda.");
271 return foxGlynnWeighter(lambda, epsilon);
272}
273
274template FoxGlynnResult<double> foxGlynn(double lambda, double epsilon);
275
276} // namespace numerical
277} // namespace utility
278} // namespace storm
#define STORM_LOG_WARN(message)
Definition logging.h:30
#define STORM_LOG_TRACE(message)
Definition logging.h:17
#define STORM_LOG_ERROR_COND(cond, message)
Definition macros.h:52
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:30
FoxGlynnResult< ValueType > foxGlynn(ValueType lambda, ValueType epsilon)
FoxGlynnResult< ValueType > foxGlynnFinder(ValueType lambda, ValueType epsilon)
The following implementation of Fox and Glynn's algorithm is taken from David Jansen's patched versio...
Definition numerical.cpp:30
FoxGlynnResult< ValueType > foxGlynnWeighter(ValueType lambda, ValueType epsilon)
ValueType zero()
Definition constants.cpp:26
ValueType log(ValueType const &number)
LabParser.cpp.
Definition cli.cpp:18
std::vector< ValueType > weights
Definition numerical.h:15