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.
264 lines
8.0 KiB
264 lines
8.0 KiB
//==============================================================================
|
|
//
|
|
// Copyright (c) 2013-
|
|
// Authors:
|
|
// * Dave Parker <david.parker@comlab.ox.ac.uk> (University of Oxford)
|
|
// * Frits Dannenberg <frits.dannenberg@cs.ox.ac.uk> (University of Oxford)
|
|
// * Ernst Moritz Hahn <emhahn@cs.ox.ac.uk> (University of Oxford)
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// 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 java.util.ArrayList;
|
|
import prism.PrismException;
|
|
|
|
/**
|
|
* Class to efficiently compute transient probabilities of a birth process.
|
|
*
|
|
* @author Dave Parker <david.parker@comlab.ox.ac.uk> (University of Oxford)
|
|
* @author Frits Dannenberg <frits.dannenberg@cs.ox.ac.uk> (University of Oxford)
|
|
* @author Ernst Moritz Hahn <emhahn@cs.ox.ac.uk> (University of Oxford)
|
|
*/
|
|
public class BirthProcess
|
|
{
|
|
/* uniformisation rate to compute probabilities.
|
|
* Must be at least as large as largest rate. */
|
|
double unifRate;
|
|
/* precision of computations using Fox-Glynn algorithm */
|
|
double epsilon;
|
|
/* time to compute probabilities for */
|
|
double time;
|
|
/* values used to compute probability to be in a given stage */
|
|
double[] probs;
|
|
/* same as above */
|
|
double[] newProbs;
|
|
/* whether rates added by calculateNextRate will be stored */
|
|
boolean withRateArray;
|
|
ArrayList<Double> jumpRates;
|
|
boolean initialising;
|
|
/* current birth process stage */
|
|
int stageNr;
|
|
/* whether to try to avoid birth process computations in
|
|
* case all rates are the same
|
|
*/
|
|
boolean avoidBirthComputation;
|
|
|
|
/**
|
|
* Construct birth process.
|
|
*/
|
|
public BirthProcess()
|
|
{
|
|
stageNr = 0;
|
|
epsilon = 1E-7;
|
|
withRateArray = true;
|
|
initialising = true;
|
|
avoidBirthComputation = true;
|
|
}
|
|
|
|
/**
|
|
* Sets whether rates added by calculateNextRate will be stored.
|
|
* The default is to do so. Setting it to false allows to save space,
|
|
* but will not allow rates larger than @a unifRate to be added.
|
|
*
|
|
* @param withRateArray whether to store rates in array
|
|
*/
|
|
public void setWithRateArray(boolean withRateArray)
|
|
{
|
|
if (!initialising) {
|
|
throw new IllegalArgumentException("this method might not be called after calculateNextRate");
|
|
}
|
|
this.withRateArray = withRateArray;
|
|
}
|
|
|
|
/**
|
|
* Sets the time to compute probabilities for.
|
|
*
|
|
* @param time time to compute probabilities for
|
|
*/
|
|
public void setTime(double time)
|
|
{
|
|
if (!initialising) {
|
|
throw new IllegalArgumentException("this method might not be called after calculateNextRate");
|
|
}
|
|
if (time < 0.0) {
|
|
throw new IllegalArgumentException("time must be nonnegative");
|
|
}
|
|
this.time = time;
|
|
}
|
|
|
|
/**
|
|
* Sets precision to be used to compute probabilities.
|
|
*
|
|
* @param epsilon precision to be used to compute probabilities
|
|
*/
|
|
public void setEpsilon(double epsilon)
|
|
{
|
|
if (!initialising) {
|
|
throw new IllegalArgumentException("this method might not be called after calculateNextRate");
|
|
}
|
|
|
|
this.epsilon = epsilon;
|
|
}
|
|
|
|
/**
|
|
* Chooses whether to try to avoid birth process construction.
|
|
* In case all rates provided to calculateNextProb are the same, it is
|
|
* possible to use the Fox-Glynn algorithm to compute probabilities in
|
|
* the birth process, which is then a Poisson process. If delayBirthComputation
|
|
* is true, the more expensive computations will only be computed in case
|
|
* this is necessary. Thus, if calculateNextProb is called with the same
|
|
* rate all the time, computations are significantly faster.
|
|
*
|
|
* @param avoidBirthComputation true iff birth process construction shall be delayed
|
|
*/
|
|
public void setAvoidBirthComputation(boolean avoidBirthComputation)
|
|
{
|
|
if (!initialising) {
|
|
throw new IllegalArgumentException("this method might not be called after calculateNextRate");
|
|
}
|
|
this.avoidBirthComputation = avoidBirthComputation;
|
|
}
|
|
|
|
/**
|
|
* Computes probability to reside in next process stage at given time.
|
|
* If this is the nth call to the process, computes the probability
|
|
* to reside in the nth stage of the birth process. The rates of the
|
|
* birth process up to the n-1th stage have been set by previous calls
|
|
* to the function, the nth rate is set by the current call. The time
|
|
* probabilities are computed for must have been set previously by
|
|
* setTime.
|
|
*
|
|
* @param rate leaving rate of current state
|
|
* @return probability to reside in current stage at given time
|
|
* @throws PrismException
|
|
*/
|
|
public double calculateNextProb(double rate) throws PrismException
|
|
{
|
|
if (initialising && 0.0 == unifRate && !withRateArray) {
|
|
throw new IllegalArgumentException("unifRate must be set if withRateArray is false");
|
|
}
|
|
if (withRateArray && initialising) {
|
|
jumpRates = new ArrayList<Double>();
|
|
}
|
|
initialising = false;
|
|
if (!withRateArray && rate > unifRate) {
|
|
throw new IllegalArgumentException("cannot use rates larger than initial rate if withRateArray is false");
|
|
}
|
|
if (withRateArray) {
|
|
jumpRates.add(rate);
|
|
}
|
|
boolean recompute = false;
|
|
if (rate > unifRate) {
|
|
if (!avoidBirthComputation) {
|
|
recompute = true;
|
|
unifRate = rate * 1.25 * 1.02;
|
|
} else {
|
|
unifRate = rate;
|
|
}
|
|
}
|
|
if ((jumpRates.size() != 1) && (Math.abs(rate - jumpRates.get(jumpRates.size() - 2)) > 1E-100)) {
|
|
if (avoidBirthComputation) {
|
|
recompute = true;
|
|
}
|
|
avoidBirthComputation = false;
|
|
}
|
|
|
|
if (null == probs || recompute) {
|
|
initPoisson();
|
|
}
|
|
double result = 0.0;
|
|
if (recompute) {
|
|
for (stageNr = 0; stageNr < jumpRates.size(); stageNr++) {
|
|
result = compNextStageProb(jumpRates.get(stageNr));
|
|
}
|
|
} else {
|
|
if (avoidBirthComputation) {
|
|
result = (stageNr < probs.length) ? probs[stageNr] : 0.0;
|
|
stageNr++;
|
|
} else {
|
|
result = compNextStageProb(rate);
|
|
stageNr++;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private double compNextStageProb(double rate)
|
|
{
|
|
assert(rate > 0.0);
|
|
double prob = rate / unifRate; // p = r / q
|
|
double omprob = 1.0 - prob; // 1-p
|
|
double result = 0.0;
|
|
|
|
double omprobtti = 1.0; // (1-p)^i
|
|
for (int i = 0; i < probs.length; i++) {
|
|
result += omprobtti * probs[i];
|
|
omprobtti *= omprob;
|
|
}
|
|
|
|
newProbs[newProbs.length - 1] = 0.0;
|
|
for (int i = probs.length - 1; i >= 1; i--) {
|
|
newProbs[i - 1] = newProbs[i] * omprob + probs[i] * prob;
|
|
}
|
|
double[] temp = probs;
|
|
probs = newProbs;
|
|
newProbs = temp;
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Initialises probability vectors by Poisson probabilities.
|
|
*/
|
|
private void initPoisson() throws PrismException
|
|
{
|
|
long left, right;
|
|
double qt = unifRate * time;
|
|
double acc = epsilon / 8.0;
|
|
double[] weights;
|
|
double totalWeight;
|
|
if (unifRate * time == 0.0) {
|
|
left = 0;
|
|
right = 0;
|
|
totalWeight = 1.0;
|
|
weights = new double[1];
|
|
weights[0] = 1.0;
|
|
} else {
|
|
FoxGlynn fg = new FoxGlynn(qt, 1e-300, 1e+300, acc);
|
|
left = fg.getLeftTruncationPoint();
|
|
right = fg.getRightTruncationPoint();
|
|
if (right < 0 || right == Integer.MAX_VALUE) {
|
|
throw new PrismException("Overflow in Fox-Glynn computation (time bound too big?)");
|
|
}
|
|
weights = fg.getWeights();
|
|
totalWeight = fg.getTotalWeight();
|
|
}
|
|
for (long i = left; i <= right; i++) {
|
|
weights[(int) (i - left)] /= totalWeight;
|
|
}
|
|
probs = new double[(int) (right + 1)];
|
|
newProbs = new double[(int) (right + 1)];
|
|
for (long entryNr = left; entryNr <= right; entryNr++) {
|
|
probs[(int) entryNr] = weights[(int) (entryNr - left)];
|
|
}
|
|
}
|
|
}
|