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.
232 lines
6.9 KiB
232 lines
6.9 KiB
//==============================================================================
|
|
//
|
|
// Copyright (c) 2002-
|
|
// Authors:
|
|
// * Dave Parker <david.parker@comlab.ox.ac.uk> (University of Oxford, formerly University of Birmingham)
|
|
// * Vojtech Forejt <forejt@fi.muni.cz> (Masaryk University)
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// This file is part of PRISM.
|
|
//
|
|
// PRISM is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation; either version 2 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// PRISM is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with PRISM; if not, write to the Free Software Foundation,
|
|
// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
//
|
|
//==============================================================================
|
|
|
|
package explicit;
|
|
|
|
import prism.PrismException;
|
|
|
|
public final class FoxGlynn
|
|
{
|
|
// constructor parameters
|
|
private double underflow, overflow, accuracy;
|
|
private double q_tmax;
|
|
|
|
// returned values
|
|
private int left, right;
|
|
private double totalWeight;
|
|
private double[] weights;
|
|
|
|
public FoxGlynn(double qtmax, double uf, double of, double acc) throws PrismException
|
|
{
|
|
q_tmax = qtmax;
|
|
underflow = uf;
|
|
overflow = of;
|
|
accuracy = acc;
|
|
run();
|
|
}
|
|
|
|
public final double[] getWeights()
|
|
{
|
|
return weights;
|
|
}
|
|
|
|
public final int getLeftTruncationPoint()
|
|
{
|
|
return left;
|
|
}
|
|
|
|
public final int getRightTruncationPoint()
|
|
{
|
|
return right;
|
|
}
|
|
|
|
public final double getTotalWeight()
|
|
{
|
|
return totalWeight;
|
|
}
|
|
|
|
private final void run() throws PrismException
|
|
{
|
|
if (q_tmax == 0.0) {
|
|
throw new PrismException("Overflow: TA parameter qtmax = time * maxExitRate = 0.");
|
|
}
|
|
else if (q_tmax < 400)
|
|
{ //here naive approach should have better performance than Fox Glynn
|
|
final double expcoef = Math.exp(-q_tmax); //the "e^-lambda" part of p.m.f. of Poisson dist.
|
|
int k; //denotes that we work with event "k steps occur"
|
|
double lastval; //(probability that exactly k events occur)/expcoef
|
|
double accum; //(probability that 0 to k events occur)/expcoef
|
|
double desval = (1-(accuracy/2.0)) / expcoef; //value that we want to accumulate in accum before we stop
|
|
java.util.Vector<Double> w = new java.util.Vector<Double>(); //stores weights computed so far.
|
|
|
|
//k=0 is simple
|
|
lastval = 1;
|
|
accum = lastval;
|
|
w.add(lastval * expcoef);
|
|
|
|
//add further steps until you have accumulated enough
|
|
k = 1;
|
|
do {
|
|
lastval *= q_tmax / k; // invariant: lastval = q_tmax^k / k!
|
|
accum += lastval;
|
|
w.add(lastval * expcoef);
|
|
k++;
|
|
} while (accum < desval);
|
|
|
|
//store all data
|
|
this.left=0;
|
|
this.right=k-1;
|
|
this.weights = new double[k];
|
|
|
|
for(int i = 0; i < w.size(); i++)
|
|
{
|
|
this.weights[i] = w.get(i);
|
|
}
|
|
|
|
//we return actual weights, so no reweighting should be done
|
|
this.totalWeight = 1.0;
|
|
}
|
|
else
|
|
{ //use actual Fox Glynn for q_tmax>400
|
|
if (accuracy < 1e-10) {
|
|
throw new PrismException("Overflow: Accuracy is smaller than Fox Glynn can handle (must be at least 1e-10).");
|
|
}
|
|
final double factor = 1e+10; //factor from the paper, it has no real explanation there
|
|
final int m = (int) q_tmax; //mode
|
|
//run FINDER to get left, right and weight[m]
|
|
{
|
|
final double sqrtpi = 1.7724538509055160; //square root of PI
|
|
final double sqrt2 = 1.4142135623730950; //square root of 2
|
|
final double sqrtq = Math.sqrt(q_tmax);
|
|
final double aq = (1.0 + 1.0/q_tmax) * Math.exp(0.0625) * sqrt2; //a_\lambda from the paper
|
|
final double bq = (1.0 + 1.0/q_tmax) * Math.exp(0.125/q_tmax); //b_\lambda from the paper
|
|
|
|
//use Corollary 1 to find right truncation point
|
|
final double lower_k_1 = 1.0 / (2.0*sqrt2*q_tmax); //lower bound on k from Corollary 1
|
|
final double upper_k_1 = sqrtq / (2.0*sqrt2); //upper bound on k from Corollary 1
|
|
double k;
|
|
|
|
//justification for increment is in the paper:
|
|
//"increase k through the positive integers greater than 3"
|
|
for(k=lower_k_1; k <= upper_k_1;
|
|
k=(k==lower_k_1)? k+4 : k+1 )
|
|
{
|
|
double dkl = 1.0/(1 - Math.exp(-(2.0/9.0)*(k*sqrt2*sqrtq+1.5))); //d(k,\lambda) from the paper
|
|
double res = aq*dkl*Math.exp(-k*k/2.0)/(k*sqrt2*sqrtpi); //right hand side of the equation in Corollary 1
|
|
if (res <= accuracy/2.0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (k>upper_k_1)
|
|
k=upper_k_1;
|
|
|
|
this.right = (int) Math.ceil(m+k*sqrt2*sqrtq + 1.5);
|
|
|
|
//use Corollary 2 to find left truncation point
|
|
//NOTE: the original implementation used some upper bound on k,
|
|
// however, I didn't find it in the paper and I think it is not needed
|
|
final double lower_k_2 = 1.0/(sqrt2*sqrtq); //lower bound on k from Corollary 2
|
|
|
|
double res;
|
|
k=lower_k_2;
|
|
do
|
|
{
|
|
res = bq*Math.exp(-k*k/2.0)/(k*sqrt2*sqrtpi); //right hand side of the equation in Corollary 2
|
|
k++;
|
|
}
|
|
while (res > accuracy/2.0);
|
|
|
|
this.left = (int) (m - k*sqrtq - 1.5);
|
|
|
|
//According to the paper, we should check underflow of lower bound.
|
|
//However, it seems that for no reasonable values this can happen.
|
|
//And neither the original implementation checked it
|
|
|
|
double wm = overflow / (factor*(this.right - this.left));
|
|
|
|
this.weights = new double[this.right-this.left+1];
|
|
this.weights[m-this.left] = wm;
|
|
}
|
|
//end of FINDER
|
|
|
|
//compute weights
|
|
//(at this point this.left, this.right and this.weight[m] is known)
|
|
|
|
//Down from m
|
|
for(int j=m; j>this.left; j--)
|
|
this.weights[j-1-this.left] = (j/q_tmax)*this.weights[j-this.left];
|
|
//Up from m
|
|
for(int j=m; j<this.right; j++)
|
|
this.weights[j+1-this.left] = (q_tmax/(j+1))*this.weights[j-this.left];
|
|
|
|
//Compute totalWeight (i.e. W in the paper)
|
|
//instead of summing from left to right, start from smallest
|
|
//and go to highest weights to prevent roundoff
|
|
this.totalWeight = 0.0;
|
|
int s = this.left;
|
|
int t = this.right;
|
|
while (s<t)
|
|
{
|
|
if(this.weights[s - this.left] <= this.weights[t - this.left])
|
|
{
|
|
this.totalWeight += this.weights[s-this.left];
|
|
s++;
|
|
}
|
|
else
|
|
{
|
|
this.totalWeight += this.weights[t-this.left];
|
|
t--;
|
|
}
|
|
}
|
|
this.totalWeight += this.weights[s-this.left];
|
|
}
|
|
}
|
|
|
|
public static void test()
|
|
{
|
|
double[] weights;
|
|
double totalWeight = 0.0;
|
|
int left, right;
|
|
|
|
FoxGlynn w = null;
|
|
try {
|
|
// q = maxDiagRate, time = time parameter (a U<time b)
|
|
double q = 1, time = 1;
|
|
w = new FoxGlynn(q * time, 1.0e-300, 1.0e+300, 1.0e-6);
|
|
} catch (PrismException e) {
|
|
// ...
|
|
}
|
|
weights = w.getWeights();
|
|
left = w.getLeftTruncationPoint();
|
|
right = w.getRightTruncationPoint();
|
|
totalWeight = w.getTotalWeight();
|
|
w = null;
|
|
}
|
|
|
|
}
|