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.
383 lines
13 KiB
383 lines
13 KiB
//==============================================================================
|
|
//
|
|
// Copyright (c) 2002-
|
|
// Authors:
|
|
// * Alessandro Bruni <albr@dtu.dk> (Technical University of Denmark)
|
|
// * Dave Parker <david.parker@comlab.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.awt.Point;
|
|
import java.util.Arrays;
|
|
import java.util.BitSet;
|
|
import java.util.Iterator;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Vector;
|
|
|
|
import parser.ast.Expression;
|
|
import parser.ast.ExpressionBinaryOp;
|
|
import parser.ast.ExpressionLabel;
|
|
import parser.ast.ExpressionTemporal;
|
|
import parser.ast.ExpressionUnaryOp;
|
|
import parser.type.TypeBool;
|
|
import parser.type.TypePathBool;
|
|
import prism.DRA;
|
|
import prism.LTL2RabinLibrary;
|
|
import prism.Pair;
|
|
import prism.PrismComponent;
|
|
import prism.PrismException;
|
|
|
|
/**
|
|
* LTL model checking functionality
|
|
*/
|
|
public class LTLModelChecker extends PrismComponent
|
|
{
|
|
/**
|
|
* Create a new LTLModelChecker, inherit basic state from parent (unless null).
|
|
*/
|
|
public LTLModelChecker(PrismComponent parent) throws PrismException
|
|
{
|
|
super(parent);
|
|
}
|
|
|
|
/**
|
|
* Convert an LTL formula into a DRA. The LTL formula is represented as a PRISM Expression,
|
|
* in which atomic propositions are represented by ExpressionLabel objects.
|
|
*/
|
|
public static DRA<BitSet> convertLTLFormulaToDRA(Expression ltl) throws PrismException
|
|
{
|
|
return LTL2RabinLibrary.convertLTLFormulaToDRA(ltl);
|
|
}
|
|
|
|
/**
|
|
* Extract maximal state formula from an LTL path formula, model check them (with passed in model checker) and
|
|
* replace them with ExpressionLabel objects L0, L1, etc. Expression passed in is modified directly, but the result
|
|
* is also returned. As an optimisation, model checking that results in true/false for all states is converted to an
|
|
* actual true/false, and duplicate results are given the same label. BitSets giving the states which satisfy each label
|
|
* are put into the vector {@code labelBS}, which should be empty when this function is called.
|
|
*/
|
|
public Expression checkMaximalStateFormulas(ProbModelChecker mc, Model model, Expression expr, Vector<BitSet> labelBS) throws PrismException
|
|
{
|
|
// A state formula
|
|
if (expr.getType() instanceof TypeBool) {
|
|
// Model check
|
|
StateValues sv = mc.checkExpression(model, expr);
|
|
BitSet bs = sv.getBitSet();
|
|
// Detect special cases (true, false) for optimisation
|
|
if (bs.isEmpty()) {
|
|
return Expression.False();
|
|
}
|
|
if (bs.cardinality() == model.getNumStates()) {
|
|
return Expression.True();
|
|
}
|
|
// See if we already have an identical result
|
|
// (in which case, reuse it)
|
|
int i = labelBS.indexOf(bs);
|
|
if (i != -1) {
|
|
sv.clear();
|
|
return new ExpressionLabel("L" + i);
|
|
}
|
|
// Otherwise, add result to list, return new label
|
|
labelBS.add(bs);
|
|
return new ExpressionLabel("L" + (labelBS.size() - 1));
|
|
}
|
|
// A path formula (recurse, modify, return)
|
|
else if (expr.getType() instanceof TypePathBool) {
|
|
if (expr instanceof ExpressionBinaryOp) {
|
|
ExpressionBinaryOp exprBinOp = (ExpressionBinaryOp) expr;
|
|
exprBinOp.setOperand1(checkMaximalStateFormulas(mc, model, exprBinOp.getOperand1(), labelBS));
|
|
exprBinOp.setOperand2(checkMaximalStateFormulas(mc, model, exprBinOp.getOperand2(), labelBS));
|
|
} else if (expr instanceof ExpressionUnaryOp) {
|
|
ExpressionUnaryOp exprUnOp = (ExpressionUnaryOp) expr;
|
|
exprUnOp.setOperand(checkMaximalStateFormulas(mc, model, exprUnOp.getOperand(), labelBS));
|
|
} else if (expr instanceof ExpressionTemporal) {
|
|
ExpressionTemporal exprTemp = (ExpressionTemporal) expr;
|
|
if (exprTemp.getOperand1() != null) {
|
|
exprTemp.setOperand1(checkMaximalStateFormulas(mc, model, exprTemp.getOperand1(), labelBS));
|
|
}
|
|
if (exprTemp.getOperand2() != null) {
|
|
exprTemp.setOperand2(checkMaximalStateFormulas(mc, model, exprTemp.getOperand2(), labelBS));
|
|
}
|
|
}
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
/**
|
|
* Construct the product of a DRA and a DTMC.
|
|
* @param dra The DRA
|
|
* @param dtmc The DTMC
|
|
* @param labelBS BitSets giving the set of states for each AP in the DRA
|
|
* @return a Pair consisting of the product DTMC and a map from
|
|
* (s_i * draSize + q_j) to the right state in the DRA product
|
|
*/
|
|
public Pair<Model, int[]> constructProductMC(DRA<BitSet> dra, DTMC dtmc, Vector<BitSet> labelBS) throws PrismException
|
|
{
|
|
DTMCSimple prodModel = new DTMCSimple();
|
|
|
|
int draSize = dra.size();
|
|
int numAPs = dra.getAPList().size();
|
|
int modelNumStates = dtmc.getNumStates();
|
|
int prodNumStates = modelNumStates * draSize;
|
|
int s_1, s_2, q_1, q_2;
|
|
BitSet s_labels = new BitSet(numAPs);
|
|
|
|
// Encoding:
|
|
// each state s' = <s, q> = s * draSize + q
|
|
// s(s') = s' / draSize
|
|
// q(s') = s' % draSize
|
|
|
|
LinkedList<Point> queue = new LinkedList<Point>();
|
|
int map[] = new int[prodNumStates];
|
|
Arrays.fill(map, -1);
|
|
|
|
// Initial states
|
|
for (int s_0 : dtmc.getInitialStates()) {
|
|
// Get BitSet representing APs (labels) satisfied by initial state s_0
|
|
for (int k = 0; k < numAPs; k++) {
|
|
s_labels.set(k, labelBS.get(k).get(s_0));
|
|
}
|
|
// Find corresponding initial state in DRA
|
|
int q_0 = dra.getEdgeDestByLabel(dra.getStartState(), s_labels);
|
|
queue.add(new Point(s_0, q_0));
|
|
prodModel.addState();
|
|
prodModel.addInitialState(prodModel.getNumStates() - 1);
|
|
map[s_0 * draSize + q_0] = prodModel.getNumStates() - 1;
|
|
}
|
|
|
|
// Product states
|
|
BitSet visited = new BitSet(prodNumStates);
|
|
while (!queue.isEmpty()) {
|
|
Point p = queue.pop();
|
|
s_1 = p.x;
|
|
q_1 = p.y;
|
|
visited.set(s_1 * draSize + q_1);
|
|
|
|
// Go through transitions from state s_1 in original DTMC
|
|
Iterator<Map.Entry<Integer, Double>> iter = dtmc.getTransitionsIterator(s_1);
|
|
while (iter.hasNext()) {
|
|
Map.Entry<Integer, Double> e = iter.next();
|
|
s_2 = e.getKey();
|
|
double prob = e.getValue();
|
|
// Get BitSet representing APs (labels) satisfied by successor state s_2
|
|
for (int k = 0; k < numAPs; k++) {
|
|
s_labels.set(k, labelBS.get(k).get(s_2));
|
|
}
|
|
// Find corresponding successor in DRA
|
|
q_2 = dra.getEdgeDestByLabel(q_1, s_labels);
|
|
// Add state/transition to model
|
|
if (!visited.get(s_2 * draSize + q_2) && map[s_2 * draSize + q_2] == -1) {
|
|
queue.add(new Point(s_2, q_2));
|
|
prodModel.addState();
|
|
map[s_2 * draSize + q_2] = prodModel.getNumStates() - 1;
|
|
}
|
|
prodModel.setProbability(map[s_1 * draSize + q_1], map[s_2 * draSize + q_2], prob);
|
|
}
|
|
}
|
|
|
|
int invMap[] = new int[prodModel.getNumStates()];
|
|
for (int i = 0; i < map.length; i++) {
|
|
if (map[i] != -1) {
|
|
invMap[map[i]] = i;
|
|
}
|
|
}
|
|
|
|
prodModel.findDeadlocks(false);
|
|
|
|
return new Pair<Model, int[]>(prodModel, invMap);
|
|
}
|
|
|
|
/**
|
|
* Construct the product of a DRA and an MDP.
|
|
* @param dra The DRA
|
|
* @param mdp The MDP
|
|
* @param labelBS BitSets giving the set of states for each AP in the DRA
|
|
* @return a Pair consisting of the product DTMC and a map from
|
|
* (s_i * draSize + q_j) to the right state in the DRA product
|
|
*/
|
|
public Pair<NondetModel, int[]> constructProductMDP(DRA<BitSet> dra, MDP mdp, Vector<BitSet> labelBS) throws PrismException
|
|
{
|
|
MDPSimple prodModel = new MDPSimple();
|
|
|
|
int draSize = dra.size();
|
|
int numAPs = dra.getAPList().size();
|
|
int modelNumStates = mdp.getNumStates();
|
|
int prodNumStates = modelNumStates * draSize;
|
|
int s_1, s_2, q_1, q_2;
|
|
BitSet s_labels = new BitSet(numAPs);
|
|
|
|
// Encoding:
|
|
// each state s' = <s, q> = s * draSize + q
|
|
// s(s') = s' / draSize
|
|
// q(s') = s' % draSize
|
|
|
|
LinkedList<Point> queue = new LinkedList<Point>();
|
|
int map[] = new int[prodNumStates];
|
|
Arrays.fill(map, -1);
|
|
|
|
// Initial states
|
|
for (int s_0 : mdp.getInitialStates()) {
|
|
// Get BitSet representing APs (labels) satisfied by initial state s_0
|
|
for (int k = 0; k < numAPs; k++) {
|
|
s_labels.set(k, labelBS.get(k).get(s_0));
|
|
}
|
|
// Find corresponding initial state in DRA
|
|
int q_0 = dra.getEdgeDestByLabel(dra.getStartState(), s_labels);
|
|
queue.add(new Point(s_0, q_0));
|
|
prodModel.addState();
|
|
prodModel.addInitialState(prodModel.getNumStates() - 1);
|
|
map[s_0 * draSize + q_0] = prodModel.getNumStates() - 1;
|
|
}
|
|
|
|
// Product states
|
|
BitSet visited = new BitSet(prodNumStates);
|
|
while (!queue.isEmpty()) {
|
|
Point p = queue.pop();
|
|
s_1 = p.x;
|
|
q_1 = p.y;
|
|
visited.set(s_1 * draSize + q_1);
|
|
|
|
// Go through transitions from state s_1 in original DTMC
|
|
int numChoices = mdp.getNumChoices(s_1);
|
|
for (int j = 0; j < numChoices; j++) {
|
|
Distribution prodDistr = new Distribution();
|
|
Iterator<Map.Entry<Integer, Double>> iter = mdp.getTransitionsIterator(s_1, j);
|
|
while (iter.hasNext()) {
|
|
Map.Entry<Integer, Double> e = iter.next();
|
|
s_2 = e.getKey();
|
|
double prob = e.getValue();
|
|
// Get BitSet representing APs (labels) satisfied by successor state s_2
|
|
for (int k = 0; k < numAPs; k++) {
|
|
s_labels.set(k, labelBS.get(k).get(s_2));
|
|
}
|
|
// Find corresponding successor in DRA
|
|
q_2 = dra.getEdgeDestByLabel(q_1, s_labels);
|
|
// Add state/transition to model
|
|
if (!visited.get(s_2 * draSize + q_2) && map[s_2 * draSize + q_2] == -1) {
|
|
queue.add(new Point(s_2, q_2));
|
|
prodModel.addState();
|
|
map[s_2 * draSize + q_2] = prodModel.getNumStates() - 1;
|
|
}
|
|
prodDistr.set(map[s_2 * draSize + q_2], prob);
|
|
}
|
|
prodModel.addActionLabelledChoice(map[s_1 * draSize + q_1], prodDistr, mdp.getAction(s_1, j));
|
|
}
|
|
}
|
|
|
|
int invMap[] = new int[prodModel.getNumStates()];
|
|
for (int i = 0; i < map.length; i++) {
|
|
if (map[i] != -1) {
|
|
invMap[map[i]] = i;
|
|
}
|
|
}
|
|
|
|
prodModel.findDeadlocks(false);
|
|
|
|
return new Pair<NondetModel, int[]>(prodModel, invMap);
|
|
}
|
|
|
|
/**
|
|
* Find the set of states belong to accepting BSCCs in a model wrt a Rabin acceptance condition.
|
|
* @param dra The DRA
|
|
* @param model The model
|
|
* @param invMap The map returned by the constructProduct method(s)
|
|
*/
|
|
public BitSet findAcceptingBSCCsForRabin(DRA<BitSet> dra, Model model, int invMap[]) throws PrismException
|
|
{
|
|
// Compute bottom strongly connected components (BSCCs)
|
|
SCCComputer sccComputer = SCCComputer.createSCCComputer(this, model);
|
|
sccComputer.computeBSCCs();
|
|
List<BitSet> bsccs = sccComputer.getBSCCs();
|
|
|
|
int draSize = dra.size();
|
|
int numAcceptancePairs = dra.getNumAcceptancePairs();
|
|
BitSet result = new BitSet();
|
|
|
|
for (BitSet bscc : bsccs) {
|
|
boolean isLEmpty = true;
|
|
boolean isKEmpty = true;
|
|
for (int acceptancePair = 0; acceptancePair < numAcceptancePairs && isLEmpty && isKEmpty; acceptancePair++) {
|
|
BitSet L = dra.getAcceptanceL(acceptancePair);
|
|
BitSet K = dra.getAcceptanceK(acceptancePair);
|
|
for (int state = bscc.nextSetBit(0); state != -1; state = bscc.nextSetBit(state + 1)) {
|
|
int draState = invMap[state] % draSize;
|
|
isLEmpty &= !L.get(draState);
|
|
isKEmpty &= !K.get(draState);
|
|
}
|
|
// Stop as soon as we find the first acceptance pair that is satisfied
|
|
if (isLEmpty && !isKEmpty) {
|
|
result.or(bscc);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Find the set of states in accepting end components (ECs) in a nondeterministic model wrt a Rabin acceptance condition.
|
|
* @param dra The DRA
|
|
* @param model The model
|
|
* @param invMap The map returned by the constructProduct method(s)
|
|
*/
|
|
public BitSet findAcceptingECStatesForRabin(DRA<BitSet> dra, NondetModel model, int invMap[]) throws PrismException
|
|
{
|
|
BitSet allAcceptingStates = new BitSet();
|
|
int numStates = model.getNumStates();
|
|
int draSize = dra.size();
|
|
|
|
// Go through the DRA acceptance pairs (L_i, K_i)
|
|
for (int i = 0; i < dra.getNumAcceptancePairs(); i++) {
|
|
// Find model states *not* satisfying L_i
|
|
BitSet bitsetLi = dra.getAcceptanceL(i);
|
|
BitSet statesLi_not = new BitSet();
|
|
for (int s = 0; s < numStates; s++) {
|
|
if (!bitsetLi.get(invMap[s] % draSize)) {
|
|
statesLi_not.set(s);
|
|
}
|
|
}
|
|
// Skip pairs with empty !L_i
|
|
if (statesLi_not.cardinality() == 0)
|
|
continue;
|
|
// Compute maximum end components (MECs) in !L_i
|
|
ECComputer ecComputer = ECComputer.createECComputer(this, model);
|
|
ecComputer.computeMECStates(statesLi_not);
|
|
List<BitSet> mecs = ecComputer.getMECStates();
|
|
// Check with MECs contain a K_i state
|
|
BitSet bitsetKi = dra.getAcceptanceK(i);
|
|
for (BitSet mec : mecs) {
|
|
for (int s = mec.nextSetBit(0); s != -1; s = mec.nextSetBit(s + 1)) {
|
|
if (bitsetKi.get(invMap[s] % draSize)) {
|
|
allAcceptingStates.or(mec);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return allAcceptingStates;
|
|
}
|
|
}
|