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.
 
 
 
 
 
 

387 lines
14 KiB

//==============================================================================
//
// Copyright (c) 2002-
// Authors:
// * Dave Parker <david.parker@comlab.ox.ac.uk> (University of Oxford)
// * Joachim Klein <klein@tcs.inf.tu-dresden.de> (TU Dresden)
//
//------------------------------------------------------------------------------
//
// 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 automata;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import jhoafparser.consumer.HOAIntermediateStoreAndManipulate;
import jhoafparser.parser.HOAFParser;
import jhoafparser.parser.generated.ParseException;
import jhoafparser.transformations.ToStateAcceptance;
import jltl2ba.APSet;
import jltl2ba.SimpleLTL;
import jltl2ba.LTLFragments;
import jltl2dstar.LTL2Rabin;
import parser.Values;
import parser.ast.Expression;
import prism.PrismComponent;
import prism.PrismException;
import prism.PrismNotSupportedException;
import prism.PrismSettings;
import acceptance.AcceptanceOmega;
import acceptance.AcceptanceRabin;
import acceptance.AcceptanceReach;
import acceptance.AcceptanceType;
/**
* Infrastructure for constructing deterministic automata for LTL formulas.
*/
public class LTL2DA extends PrismComponent
{
public LTL2DA(PrismComponent parent) throws PrismException
{
super(parent);
}
/**
* Convert an LTL formula into a deterministic Rabin automaton.
* The LTL formula is represented as a PRISM Expression,
* in which atomic propositions are represented by ExpressionLabel objects.
* @param ltl the formula
* @param constantValues the values of constants, may be {@code null}
*/
@SuppressWarnings("unchecked")
public DA<BitSet, AcceptanceRabin> convertLTLFormulaToDRA(Expression ltl, Values constantValues) throws PrismException
{
return (DA<BitSet, AcceptanceRabin>) convertLTLFormulaToDA(ltl, constantValues, AcceptanceType.RABIN);
}
/**
* Convert an LTL formula into a deterministic automaton.
* The LTL formula is represented as a PRISM Expression,
* in which atomic propositions are represented by ExpressionLabel objects.
* @param ltl the formula
* @param constants the values of constants, may be {@code null}
* @param allowedAcceptance the AcceptanceTypes that are allowed to be returned
*/
public DA<BitSet, ? extends AcceptanceOmega> convertLTLFormulaToDA(Expression ltl, Values constants, AcceptanceType... allowedAcceptance)
throws PrismException
{
DA<BitSet, ? extends AcceptanceOmega> result = null;
boolean useExternal = useExternal();
boolean containsTemporalBounds = Expression.containsTemporalTimeBounds(ltl);
if (containsTemporalBounds) {
useExternal = false;
}
if (!useExternal) {
try {
// checking the library first
result = LTL2RabinLibrary.getDAforLTL(ltl, constants, allowedAcceptance);
if (result != null) {
getLog().println("Taking "+result.getAutomataType()+" from library...");
}
} catch (Exception e) {
if (containsTemporalBounds) {
// there is (currently) no other way to translate LTL with temporal bounds,
// so treat an exception as a "real" one
throw e;
} else {
// there is the possibility that we might be able to construct
// an automaton below, just issue a warning
getLog().println("Warning: Exception during attempt to construct DRA using the LTL2RabinLibrary:");
getLog().println(" " + e.getMessage());
}
}
}
if (result == null) {
if (!containsTemporalBounds) {
if (useExternal) {
result = convertLTLFormulaToDAWithExternalTool(ltl, constants, allowedAcceptance);
} else {
SimpleLTL simpleLTL = ltl.convertForJltl2ba();
// don't use LTL2WDBA translation yet
boolean allowLTL2WDBA = false;
if (allowLTL2WDBA) {
LTLFragments fragments = LTLFragments.analyse(simpleLTL);
mainLog.println(fragments);
if (fragments.isSyntacticGuarantee() && AcceptanceType.contains(allowedAcceptance, AcceptanceType.REACH)) {
// a co-safety property
mainLog.println("Generating DFA for co-safety property...");
LTL2WDBA ltl2wdba = new LTL2WDBA(this);
result = ltl2wdba.cosafeltl2dfa(simpleLTL);
} else if (allowLTL2WDBA && fragments.isSyntacticObligation() && AcceptanceType.contains(allowedAcceptance, AcceptanceType.BUCHI)) {
// an obligation property
mainLog.println("Generating DBA for obligation property...");
LTL2WDBA ltl2wdba = new LTL2WDBA(this);
result = ltl2wdba.obligation2wdba(simpleLTL);
}
}
if (result == null) {
// use jltl2dstar LTL2DA
result = LTL2Rabin.ltl2da(simpleLTL, allowedAcceptance);
}
}
} else {
throw new PrismNotSupportedException("Could not convert LTL formula to deterministic automaton, formula had time-bounds");
}
}
if (result == null) {
throw new PrismNotSupportedException("Could not convert LTL formula to deterministic automaton");
}
if (!getSettings().getBoolean(PrismSettings.PRISM_NO_DA_SIMPLIFY)) {
result = DASimplifyAcceptance.simplifyAcceptance(this, result, allowedAcceptance);
}
return result;
}
public DA<BitSet, ? extends AcceptanceOmega> convertLTLFormulaToDAWithExternalTool(Expression ltl, Values constants, AcceptanceType... allowedAcceptance)
throws PrismException
{
String ltl2daTool = getSettings().getString(PrismSettings.PRISM_LTL2DA_TOOL);
SimpleLTL ltlFormula = ltl.convertForJltl2ba();
// switch from the L0, L1, ... APs of PRISM to the
// safer p0, p1, ... APs for the external tool
SimpleLTL ltlFormulaSafeAP = ltlFormula.clone();
ltlFormulaSafeAP.renameAP("L", "p");
DA<BitSet, ? extends AcceptanceOmega> result = null;
try {
String syntax = getSettings().getString(PrismSettings.PRISM_LTL2DA_SYNTAX);
if (syntax == null || syntax.isEmpty()) {
throw new PrismException("No LTL syntax option provided");
}
String ltlOutput;
switch (syntax) {
case "LBT":
ltlOutput = ltlFormulaSafeAP.toStringLBT();
break;
case "Spin":
ltlOutput = ltlFormulaSafeAP.toStringSpin();
break;
case "Spot":
ltlOutput = ltlFormulaSafeAP.toStringSpot();
break;
case "Rabinizer":
ltlFormulaSafeAP = ltlFormulaSafeAP.toBasicOperators();
ltlOutput = ltlFormulaSafeAP.toStringSpot();
break;
default:
throw new PrismException("Unknown LTL syntax option \"" + syntax + "\"");
}
File ltl_file = File.createTempFile("prism-ltl-external-", ".ltl", null);
File da_file = File.createTempFile("prism-ltl-external-", ".hoa", null);
File tool_output = File.createTempFile("prism-ltl-external-", ".output", null);
FileWriter ltlWriter = new FileWriter(ltl_file);
ltlWriter.write(ltlOutput);
ltlWriter.close();
List<String> arguments = new ArrayList<String>();
arguments.add(ltl2daTool);
getLog().print("Calling external LTL->DA tool: ");
for (String s : arguments) {
getLog().print(" " + s);
}
getLog().println();
getLog().print("LTL formula (in " + syntax + " syntax): ");
getLog().println(ltlOutput);
getLog().println();
arguments.add(ltl_file.getAbsolutePath());
arguments.add(da_file.getAbsolutePath());
ProcessBuilder builder = new ProcessBuilder(arguments);
builder.redirectOutput(tool_output);
builder.redirectErrorStream(true);
// if we are running under the Nailgun environment, setup the
// environment to include the environment variables of the Nailgun client
prism.PrismNG.setupChildProcessEnvironment(builder);
Process p = builder.start();
p.getInputStream().close();
int rv;
while (true) {
try {
rv = p.waitFor();
break;
} catch (InterruptedException e) {
}
}
if (rv != 0) {
throw new PrismException("Call to external LTL->DA tool failed, return value = " + rv + ".\n"
+ "To investigate, please consult the following files:" + "\n LTL formula: " + ltl_file.getAbsolutePath()
+ "\n Automaton output: " + da_file.getAbsolutePath() + "\n Tool output (stdout and stderr): "
+ tool_output.getAbsolutePath() + "\n");
}
tool_output.delete();
try {
try {
HOAF2DA consumerDA = new HOAF2DA();
InputStream input = new FileInputStream(da_file);
HOAFParser.parseHOA(input, consumerDA);
result = consumerDA.getDA();
} catch (HOAF2DA.TransitionBasedAcceptanceException e) {
// try again, this time transforming to state acceptance
getLog().println("Automaton with transition-based acceptance, automatically converting to state-based acceptance...");
HOAF2DA consumerDA = new HOAF2DA();
HOAIntermediateStoreAndManipulate consumerTransform = new HOAIntermediateStoreAndManipulate(consumerDA, new ToStateAcceptance());
InputStream input = new FileInputStream(da_file);
HOAFParser.parseHOA(input, consumerTransform);
result = consumerDA.getDA();
}
if (result == null) {
throw new PrismException("Could not construct DA");
}
checkAPs(ltlFormulaSafeAP, result.getAPList());
// rename back from safe APs, i.e., p0, p1, ... to L0, L1, ...
List<String> automatonAPList = result.getAPList();
for (int i = 0; i < automatonAPList.size(); i++) {
if (automatonAPList.get(i).startsWith("p")) {
String renamed = "L" + automatonAPList.get(i).substring("p".length());
automatonAPList.set(i, renamed);
}
}
} catch (ParseException e) {
throw new PrismException("Parse error: " + e.getMessage() + ".\n" + "To investigate, please consult the following files:\n"
+ " LTL formula: " + ltl_file.getAbsolutePath() + "\n Automaton output: " + da_file.getAbsolutePath() + "\n");
} catch (PrismException e) {
throw new PrismException(e.getMessage() + ".\n" + "To investigate, please consult the following files:" + "\n LTL formula: "
+ ltl_file.getAbsolutePath() + "\n Automaton output: " + da_file.getAbsolutePath() + "\n");
}
da_file.delete();
ltl_file.delete();
} catch (IOException e) {
throw new PrismException(e.getMessage());
}
if (!getSettings().getBoolean(PrismSettings.PRISM_NO_DA_SIMPLIFY)) {
result = DASimplifyAcceptance.simplifyAcceptance(this, result, allowedAcceptance);
}
AcceptanceOmega acceptance = result.getAcceptance();
if (AcceptanceType.contains(allowedAcceptance, acceptance.getType())) {
return result;
} else if (AcceptanceType.contains(allowedAcceptance, AcceptanceType.GENERIC)) {
// The specific acceptance type is not allowed, but GENERIC is allowed
// -> transform to generic acceptance and switch acceptance condition
DA.switchAcceptance(result, acceptance.toAcceptanceGeneric());
return result;
} else {
throw new PrismException("The external LTL->DA tool returned an automaton with " + acceptance.getType()
+ " acceptance, which is not yet supported for model checking this model / property");
}
}
/** Check whether we should use an external LTL->DA tool */
private boolean useExternal()
{
String ltl2da_tool = getSettings().getString(PrismSettings.PRISM_LTL2DA_TOOL);
if (ltl2da_tool != null && !ltl2da_tool.isEmpty()) {
return true;
}
return false;
}
/** Check the atomic propositions of the (externally generated) automaton */
private void checkAPs(SimpleLTL ltl, List<String> automatonAPs) throws PrismException
{
APSet ltlAPs = ltl.getAPs();
for (String ap : automatonAPs) {
if (!ltlAPs.hasAP(ap)) {
throw new PrismException("Generated automaton has extra atomic proposition \"" + ap + "\"");
}
}
// It's fine for the automaton to not have APs that occur in the formula, e.g., for
// p0 | !p0, the external tool could simplify to 'true' and omit all APs
}
/**
* Simple test method: convert LTL formula (in LBT format) to HOA/Dot/txt
*/
public static void main(String args[])
{
try {
// Usage:
// * ... 'X p1'
// * ... 'X p1' da.hoa
// * ... 'X p1' da.hoa hoa
// * ... 'X p1' da.dot dot
// * ... 'X p1' - hoa
// * ... 'X p1' - txt
// Convert to Expression (from PRISM format)
/*String pltl = "P=?[" + ltl + "]";
PropertiesFile pf = Prism.getPrismParser().parsePropertiesFile(new ModulesFile(), new ByteArrayInputStream(pltl.getBytes()));
Prism.releasePrismParser();
Expression expr = pf.getProperty(0);
expr = ((ExpressionProb) expr).getExpression();
System.out.println("LTL: " + expr);*/
// Convert to Expression (from LBT format)
// String ltl = args[0];
SimpleLTL sltl = SimpleLTL.parseFormulaLBT(args[0]);
Expression expr = Expression.createFromJltl2ba(sltl);
// System.out.println("LBT: " + ltl);
// System.out.println("LTL: " + expr);
// Build/export DA
LTL2DA ltl2da = new LTL2DA(new PrismComponent());
DA<BitSet, ? extends AcceptanceOmega> da = ltl2da.convertLTLFormulaToDA(expr, null, AcceptanceType.RABIN, AcceptanceType.REACH);
PrintStream out = (args.length < 2 || "-".equals(args[1])) ? System.out : new PrintStream(args[1]);
String format = (args.length < 3) ? "hoa" : args[2];
da.print(out, format);
} catch (Exception e) {
e.printStackTrace();
System.err.print("Error: " + e);
}
}
}