You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
290 lines
14 KiB
290 lines
14 KiB
#include <vector>
|
|
#include <tuple>
|
|
#include <cmath>
|
|
|
|
#include <boost/math/constants/constants.hpp>
|
|
|
|
#include "storm/utility/macros.h"
|
|
#include "storm/utility/constants.h"
|
|
#include "storm/exceptions/InvalidArgumentException.h"
|
|
#include "storm/exceptions/OutOfRangeException.h"
|
|
#include "storm/exceptions/PrecisionExceededException.h"
|
|
|
|
namespace storm {
|
|
namespace utility {
|
|
namespace numerical {
|
|
|
|
template<typename ValueType>
|
|
FoxGlynnResult<ValueType>::FoxGlynnResult() : left(0), right(0), totalWeight(0) {
|
|
// Intentionally left empty.
|
|
}
|
|
|
|
template<typename ValueType>
|
|
FoxGlynnResult<ValueType> foxGlynnFinder(ValueType lambda, ValueType epsilon) {
|
|
int left, right;
|
|
FoxGlynn * pFG;
|
|
|
|
/* tau is only used in underflow checks, which we are going to do in the
|
|
logarithm domain. */
|
|
tau = log(tau);
|
|
/* In error bound comparisons, we always compare with epsilon*sqrt_2_pi.*/
|
|
epsilon *= sqrt_2_pi;
|
|
/****Compute pFG->left truncation point****/
|
|
if ( m < 25 )
|
|
{
|
|
/* for lambda below 25 the exponential can be smaller than tau */
|
|
/* if that is the case we expect underflows and warn the user */
|
|
if ( -lambda <= tau )
|
|
{
|
|
printf("ERROR: Fox-Glynn: 0 < lambda < 25, underflow near Poi(%g,"
|
|
"0) = %.2e. The results are UNRELIABLE.\n",
|
|
lambda, exp(-lambda));
|
|
}
|
|
/* zero is used as left truncation point for lambda <= 25 */
|
|
left = 0;
|
|
}
|
|
/* compute the left truncation point for lambda >= 25 */
|
|
/* for lambda < 25 we use zero as left truncation point */
|
|
else
|
|
{
|
|
const double bl = (1 + 1 / lambda) * exp((1/lambda) * 0.125);
|
|
const double sqrt_lambda = sqrt(lambda);
|
|
int k;
|
|
/*Start looking for the left truncation point*/
|
|
/* start search at k=4 (taken from original Fox-Glynn paper) */
|
|
/* increase the left truncation point until we fulfil the error
|
|
condition */
|
|
|
|
for ( k = 4 ; TRUE ; k++ ) {
|
|
double max_err;
|
|
|
|
left = m - (int) ceil(k*sqrt_lambda + 0.5);
|
|
/* for small lambda the above calculation can yield negative truncation points, crop them here */
|
|
if ( left <= 0 ) {
|
|
left = 0;
|
|
break;
|
|
}
|
|
/* Note that Propositions 2-4 in Fox--Glynn mix up notation: they
|
|
write Phi where they mean 1 - Phi. (In Corollaries 1 and 2, phi is
|
|
used correctly again.) */
|
|
max_err = bl * exp(-0.5 * (k*k)) / k;
|
|
if ( max_err * 2 <= epsilon ) {
|
|
/* If the error on the left hand side is smaller, we can be
|
|
more lenient on the right hand side. To this end, we now set
|
|
epsilon to the part of the error that has not yet been eaten
|
|
up by the left-hand truncation. */
|
|
epsilon -= max_err;
|
|
break;
|
|
}
|
|
}
|
|
/*Finally the left truncation point is found*/
|
|
}
|
|
|
|
/****Compute pFG->right truncation point****/
|
|
{
|
|
double lambda_max;
|
|
int m_max, k;
|
|
/*According to Fox-Glynn, if lambda < 400 we should take lambda = 400,
|
|
otherwise use the original value. This is for computing the right truncation point*/
|
|
if ( m < 400 ) {
|
|
lambda_max = lambda_400;
|
|
m_max = 400;
|
|
epsilon *= 0.662608824988162441697980;
|
|
/* i.e. al = (1+1/400) * exp(1/16) * sqrt_2; epsilon /= al; */
|
|
} else {
|
|
lambda_max = lambda;
|
|
m_max = m;
|
|
epsilon *= (1 - 1 / (lambda + 1)) * 0.664265347050632847802225;
|
|
/* i.e. al = (1+1/lambda) * exp(1/16) * sqrt_2; epsilon /= al; */
|
|
}
|
|
/* find right truncation point */
|
|
|
|
/* This loop is a modification to the original Fox-Glynn paper.
|
|
The search for the right truncation point is only terminated by
|
|
the error condition and not by the stop index from the FG paper.
|
|
This can yield more accurate results if necessary.*/
|
|
for ( k = 4 ; TRUE ; k++ )
|
|
{
|
|
/* dkl_inv is between 1 - 1e-33 and 1 if lambda_max >= 400 and
|
|
k >= 4; this will always be rounded to 1.0. We therefore leave the
|
|
factor out.
|
|
double dkl_inv=1 - exp(-266/401.0 * (k*sqrt(2*lambda_max) + 1.5));
|
|
*/
|
|
if ( k * epsilon /* actually: "k * (dkl_inv*epsilon/al)", which
|
|
has been calculated above */ >= exp(-0.5*(k*k)) )
|
|
break;
|
|
}
|
|
right = m_max + (int) ceil(k * sqrt(2 * lambda_max) + 0.5);
|
|
if ( right > m_max + (int) ceil((lambda_max + 1) * 0.5) ) {
|
|
printf("ERROR: Fox-Glynn: right = %d >> lambda = %g, cannot "
|
|
"bound the right tail. The results are "
|
|
"UNRELIABLE.\n", right, lambda_max);
|
|
}
|
|
}
|
|
|
|
/*Time to set the initial value for weights*/
|
|
pFG = calloc((size_t) 1, sizeof(FoxGlynn) +
|
|
(right - left) * sizeof(pFG->weights[0]));
|
|
if ( NULL == pFG ) {
|
|
err_msg_3(err_MEMORY, "finder(%d,%g,_,%g,_)", m, lambda, omega, NULL);
|
|
}
|
|
pFG->right = right;
|
|
pFG->left = left;
|
|
pFG->weights[m - left] = omega / ( 1.0e+10 * (right - left) );
|
|
|
|
if ( m >= 25 )
|
|
{
|
|
/* perform underflow check */
|
|
double result, log_c_m_inf;
|
|
int i;
|
|
|
|
/* we are going to compare with tau - log(w[m]) */
|
|
tau -= log(pFG->weights[m - left]);
|
|
/*We take the c_m_inf = 0.14627 / sqrt( m ), as for lambda >= 25
|
|
c_m = 1 / ( sqrt( 2.0 * pi * m ) ) * exp( m - lambda - 1 / ( 12.0 * m ) ) => c_m_inf*/
|
|
/* Note that m-lambda is in the interval (-1,0],
|
|
and -1/(12*m) is in [-1/(12*25),0).
|
|
So, exp(m-lambda - 1/(12*m)) is in (exp(-1-1/(12*25)),exp(0)).
|
|
Therefore, we can improve the lower bound on c_m to
|
|
exp(-1-1/(12*25)) / sqrt(2*pi) = ~0.14627. Its logarithm is
|
|
-1 - 1/(12*25) - log(2*pi) * 0.5 = ~ -1.922272 (rounded towards
|
|
-infinity). */
|
|
log_c_m_inf = -1.922272 - log((double) m) * 0.5;
|
|
|
|
/* We use FG's Proposition 6 directly (and not Corollary 4 i and ii),
|
|
as k_prime may be too large if pFG->left == 0. */
|
|
i = m - left;
|
|
if ( i <= left /* equivalent to 2*i <= m,
|
|
equivalent to i <= lambda/2 */ )
|
|
{
|
|
/* Use Proposition 6 (i). Note that Fox--Glynn are off by one in
|
|
the proof of this proposition; they sum up to i-1, but should have
|
|
summed up to i. */
|
|
result = log_c_m_inf
|
|
- i * (i+1) * (0.5 + (2*i+1)/(6*lambda)) / lambda;
|
|
}
|
|
else
|
|
{
|
|
/* Use Corollary 4 (iii). Note that k_prime <= sqrt(m+1)/m is a
|
|
misprint for k_prime <= m/sqrt(m+1), which is equivalent to
|
|
left >= 0, which holds trivially. */
|
|
result = -lambda;
|
|
if ( 0 != left ) {
|
|
/* also use Proposition 6 (ii) */
|
|
double result_1 = log_c_m_inf + i * log(1 - i/(double) (m+1));
|
|
/*Take the maximum*/
|
|
if ( result_1 > result )
|
|
result = result_1;
|
|
}
|
|
}
|
|
if ( result <= tau )
|
|
{
|
|
const int log10_result = (int) floor(result * log10_e);
|
|
printf("ERROR: Fox-Glynn: lambda >= 25, underflow near Poi(%g,%d)"
|
|
" <= %.2fe%+d. The results are UNRELIABLE.\n",
|
|
lambda, left, exp(result - log10_result/log10_e),
|
|
log10_result);
|
|
}
|
|
|
|
/*We still have to perform an underflow check for the right truncation point when lambda >= 400*/
|
|
if ( m >= 400 )
|
|
{
|
|
/* Use Proposition 5 of Fox--Glynn */
|
|
i = right - m;
|
|
result = log_c_m_inf - i * (i + 1) / (2 * lambda);
|
|
if ( result <= tau )
|
|
{
|
|
const int log10_result = (int) floor(result * log10_e);
|
|
printf("ERROR: Fox-Glynn: lambda >= 400, underflow near "
|
|
"Poi(%g,%d) <= %.2fe%+d. The results are "
|
|
"UNRELIABLE.\n", lambda, right,
|
|
exp(result - log10_result / log10_e),
|
|
log10_result);
|
|
}
|
|
}
|
|
}
|
|
|
|
return pFG;
|
|
}
|
|
|
|
template<typename ValueType>
|
|
FoxGlynnResult<ValueType> foxGlynnWeighter(ValueType lambda, ValueType epsilon) {
|
|
/*The magic m point*/
|
|
const uint64_t m = (int) floor(lambda);
|
|
int j, t;
|
|
FoxGlynn * pFG;
|
|
|
|
pFG = finder(m, lambda, tau, omega, epsilon);
|
|
if ( NULL == pFG ) {
|
|
err_msg_4(err_CALLBY, "weighter(%g,%g,%g,%g)", lambda,
|
|
tau, omega, epsilon, NULL);
|
|
}
|
|
|
|
/*Fill the left side of the array*/
|
|
for ( j = m - pFG->left ; j > 0 ; j-- )
|
|
pFG->weights[j-1] = (j+pFG->left) / lambda * pFG->weights[j];
|
|
|
|
t = pFG->right - pFG->left;
|
|
/*Fill the right side of the array, have two cases lambda < 400 & lambda >= 400*/
|
|
if ( m < 400 )
|
|
{
|
|
/*Perform the underflow check, according to Fox-Glynn*/
|
|
if( pFG->right > 600 )
|
|
{
|
|
printf("ERROR: Fox-Glynn: pFG->right > 600, underflow is possible\n");
|
|
freeFG(pFG);
|
|
return NULL;
|
|
}
|
|
/*Compute weights*/
|
|
for ( j = m - pFG->left ; j < t ; j++ )
|
|
{
|
|
double q = lambda / (j + 1 + pFG->left);
|
|
if ( pFG->weights[j] > tau / q )
|
|
{
|
|
pFG->weights[j + 1] = q * pFG->weights[j];
|
|
}else{
|
|
t = j;
|
|
pFG->right = j + pFG->left;
|
|
break; /*It's time to compute W*/
|
|
}
|
|
}
|
|
}else{
|
|
/*Compute weights*/
|
|
for ( j = m - pFG->left ; j < t ; j++ )
|
|
pFG->weights[j+1] = lambda / (j+1 + pFG->left) * pFG->weights[j];
|
|
}
|
|
|
|
/*It is time to compute the normalization weight W*/
|
|
pFG->total_weight = 0.0;
|
|
j = 0;
|
|
/* t was set above */
|
|
|
|
while( j < t )
|
|
{
|
|
if ( pFG->weights[j] <= pFG->weights[t] )
|
|
{
|
|
pFG->total_weight += pFG->weights[j];
|
|
j++;
|
|
}else{
|
|
pFG->total_weight += pFG->weights[t];
|
|
t--;
|
|
}
|
|
}
|
|
pFG->total_weight += pFG->weights[j];
|
|
|
|
/* printf("Fox-Glynn: ltp = %d, rtp = %d, w = %10.15le \n", pFG->left, pFG->right, pFG->total_weight); */
|
|
|
|
return pFG;
|
|
}
|
|
|
|
template<typename ValueType>
|
|
FoxGlynnResult<ValueType> foxGlynn(ValueType lambda, ValueType epsilon) {
|
|
STORM_LOG_THROW(lambda > 0, storm::exceptions::InvalidArgumentException, "Fox-Glynn requires positive lambda.");
|
|
return foxGlynnWeighter(lambda, epsilon);
|
|
}
|
|
|
|
template FoxGlynnResult<double> foxGlynn(double lambda, double epsilon);
|
|
|
|
}
|
|
}
|
|
}
|