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.
 
 
 
 
 
 

384 lines
14 KiB

//==============================================================================
//
// Copyright (c) 2015-
// Authors:
// * Dave Parker <d.a.parker@cs.bham.ac.uk> (University of Birmingham/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 parser;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.List;
import parser.ast.Expression;
import parser.ast.ExpressionBinaryOp;
import parser.ast.ExpressionLabel;
import parser.ast.ExpressionProb;
import parser.ast.ExpressionQuant;
import parser.ast.ExpressionReward;
import parser.ast.ExpressionUnaryOp;
import parser.visitor.ASTTraverseModify;
import prism.PrismException;
import prism.PrismLangException;
public class BooleanUtils
{
/**
* Extract the conjuncts from a conjunction in the form of zero or more nested
* binary conjunctions represented by ExpressionBinaryOp objects.
*
* @param expr The conjunction to extract from
* @param conjuncts The list to insert conjuncts into
*/
public static void extractConjuncts(Expression expr, List<Expression> conjuncts)
{
// Traverse depth-first, and add any non-ands to the list
if (Expression.isAnd(expr)) {
extractConjuncts(((ExpressionBinaryOp) expr).getOperand1(), conjuncts);
extractConjuncts(((ExpressionBinaryOp) expr).getOperand2(), conjuncts);
} else {
conjuncts.add(expr);
}
}
/**
* Extract the disjuncts from a disjunction in the form of zero or more nested
* binary disjunctions represented by ExpressionBinaryOp objects.
*
* @param expr The disjunction to extract from
* @param disjuncts The list to insert disjuncts into
*/
public static void extractDisjuncts(Expression expr, List<Expression> disjuncts)
{
// Traverse depth-first, and add any non-ors to the list
if (Expression.isAnd(expr)) {
extractDisjuncts(((ExpressionBinaryOp) expr).getOperand1(), disjuncts);
extractDisjuncts(((ExpressionBinaryOp) expr).getOperand2(), disjuncts);
} else {
disjuncts.add(expr);
}
}
/**
* 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, expressions that results in true/false for all states are converted to an
* actual true/false, and duplicate results (or their negations) reuse the same label. BDDs giving the states which
* satisfy each label are put into the vector labelDDs, which should be empty when this function is called.
*/
public static Expression extractAtomsFromBooleanExpression(Expression expr, List<ExpressionQuant> labelDDs) throws PrismException
{
// A state formula
if (expr instanceof ExpressionProb || expr instanceof ExpressionReward) {
labelDDs.add((ExpressionQuant) expr);
return new ExpressionLabel("L" + (labelDDs.size() - 1));
}
// A path formula (recurse, modify, return)
else if (Expression.isOr(expr)) {
ExpressionBinaryOp exprOr = (ExpressionBinaryOp) expr;
exprOr.setOperand1(extractAtomsFromBooleanExpression(exprOr.getOperand1(), labelDDs));
exprOr.setOperand2(extractAtomsFromBooleanExpression(exprOr.getOperand2(), labelDDs));
} else if (Expression.isAnd(expr)) {
ExpressionBinaryOp exprAnd = (ExpressionBinaryOp) expr;
exprAnd.setOperand1(extractAtomsFromBooleanExpression(exprAnd.getOperand1(), labelDDs));
exprAnd.setOperand2(extractAtomsFromBooleanExpression(exprAnd.getOperand2(), labelDDs));
} else if (Expression.isImplies(expr)) {
ExpressionBinaryOp exprImplies = (ExpressionBinaryOp) expr;
exprImplies.setOperand1(extractAtomsFromBooleanExpression(exprImplies.getOperand1(), labelDDs));
exprImplies.setOperand2(extractAtomsFromBooleanExpression(exprImplies.getOperand2(), labelDDs));
} else if (Expression.isNot(expr)) {
ExpressionUnaryOp exprNot = (ExpressionUnaryOp) expr;
exprNot.setOperand(extractAtomsFromBooleanExpression(exprNot.getOperand(), labelDDs));
} else {
throw new PrismException("arg " + expr);
}
return expr;
}
/**
* Convert a Boolean expression to positive normal form,
* i.e., remove any instances of =>, <=> or () and then push
* all negation inwards so that it only occurs on "propositions".
* A "proposition" is any Expression object that
* is not an operator used to define a Boolean expression (!, &, |, =>, <=>, ()).
* The passed in expression is modified, and the result is returned.
*/
public static Expression convertToPositiveNormalForm(Expression expr) throws PrismLangException
{
// First expand implies/iff/parentheses
Expression exprBool = (Expression) expr.accept(new ASTTraverseModify() {
public Object visit(ExpressionUnaryOp e) throws PrismLangException
{
// Remove parentheses: (a)
if (Expression.isParenth(e)) {
Expression a = (Expression)(e.getOperand().accept(this));
// (a) == a
return a;
}
return super.visit(e);
}
public Object visit(ExpressionBinaryOp e) throws PrismLangException
{
// Remove implication: a => b
if (Expression.isImplies(e)) {
Expression a = (Expression)(e.getOperand1().accept(this));
Expression b = (Expression)(e.getOperand2().accept(this));
// a => b == !a | b
return Expression.Or(Expression.Not(a), b);
}
// Remove iff: a <=> b
if (Expression.isImplies(e)) {
Expression a = (Expression)(e.getOperand1().accept(this));
Expression b = (Expression)(e.getOperand2().accept(this));
// a <=> b == (a | !b) & (!a | b)
return Expression.And(Expression.Or(a, Expression.Not(b)), Expression.Or(Expression.Not(a.deepCopy()), b.deepCopy()));
}
return super.visit(e);
}
});
// Then do conversion to +ve normal form
return doConversionToPositiveNormalForm(exprBool);
}
/**
* Do the main part of the conversion of a Boolean expression to positive normal form,
* i.e., push all negation inwards to the propositions. It is assumed that the Boolean expression
* comprises only the operators !, & and |. Anything else is treated as a proposition.
* The passed in expression is modified, and the result is returned.
*/
private static Expression doConversionToPositiveNormalForm(Expression expr)
{
// Remove negation
if (Expression.isNot(expr)) {
Expression neg = ((ExpressionUnaryOp) expr).getOperand();
if (Expression.isNot(neg)) {
Expression a = ((ExpressionUnaryOp) neg).getOperand();
// !(!a) == a
return doConversionToPositiveNormalForm(a);
} else if (Expression.isOr(neg)) {
Expression a = ((ExpressionBinaryOp) neg).getOperand1();
Expression b = ((ExpressionBinaryOp) neg).getOperand2();
Expression aNeg = Expression.Not(a);
Expression bNeg = Expression.Not(b);
// !(a | b) == !a & !b
return doConversionToPositiveNormalForm(Expression.And(aNeg, bNeg));
} else if (Expression.isAnd(neg)) {
Expression a = ((ExpressionBinaryOp) neg).getOperand1();
Expression b = ((ExpressionBinaryOp) neg).getOperand2();
Expression aNeg = Expression.Not(a);
Expression bNeg = Expression.Not(b);
// !(a & b) == !a | !b
return doConversionToPositiveNormalForm(Expression.Or(aNeg, bNeg));
} else {
// Proposition (negated)
return expr;
}
}
// Preserve and
if (Expression.isAnd(expr)) {
Expression a = doConversionToPositiveNormalForm(((ExpressionBinaryOp) expr).getOperand1());
Expression b = doConversionToPositiveNormalForm(((ExpressionBinaryOp) expr).getOperand2());
return Expression.And(a, b);
}
// Preserve or
if (Expression.isOr(expr)) {
Expression a = doConversionToPositiveNormalForm(((ExpressionBinaryOp) expr).getOperand1());
Expression b = doConversionToPositiveNormalForm(((ExpressionBinaryOp) expr).getOperand2());
return Expression.Or(a, b);
}
// Proposition
return expr;
}
/**
* Convert an expression to disjunctive normal form (DNF).
* The passed in expression is modified, and the result is returned.
*/
public static Expression convertToDNF(Expression expr) throws PrismException
{
return convertDNFListsToExpression(doConversionToDNF(convertToPositiveNormalForm(expr)));
}
/**
* Convert an expression to disjunctive normal form (DNF).
* The passed in expression is modified, and the result is returned as a list of lists of Expression;
*/
public static List<List<Expression>> convertToDNFLists(Expression expr) throws PrismException
{
return doConversionToDNF(convertToPositiveNormalForm(expr));
}
private static List<List<Expression>> doConversionToDNF(Expression expr)
{
// And
if (Expression.isAnd(expr)) {
Expression a = ((ExpressionBinaryOp) expr).getOperand1();
Expression b = ((ExpressionBinaryOp) expr).getOperand2();
List<List<Expression>> aDnf = doConversionToDNF(a);
List<List<Expression>> bDnf = doConversionToDNF(b);
// a1|a2|... & b1|b2|... == a1&b1 | a1&b2 | ...
List<List<Expression>> dnf = new ArrayList<List<Expression>>();
for (List<Expression> ai : aDnf) {
for (List<Expression> bj : bDnf) {
List<Expression> aibj = new ArrayList<Expression>();
aibj.addAll(ai);
aibj.addAll(bj);
dnf.add(aibj);
}
}
return dnf;
}
// Or
if (Expression.isOr(expr)) {
Expression a = ((ExpressionBinaryOp) expr).getOperand1();
Expression b = ((ExpressionBinaryOp) expr).getOperand2();
List<List<Expression>> aDnf = doConversionToDNF(a);
List<List<Expression>> bDnf = doConversionToDNF(b);
// (a1|a2|...) | (b1|b2|...) == a1|a2|...|b1|b2|...
aDnf.addAll(bDnf);
return aDnf;
}
// Convert proposition to trivial DNF
List<List<Expression>> dnf = new ArrayList<List<Expression>>(1);
List<Expression> disjunct = new ArrayList<Expression>(1);
disjunct.add(expr);
dnf.add(disjunct);
return dnf;
}
/**
* Convert an expression to conjunctive normal form (CNF).
* The passed in expression is modified, and the result is returned.
*/
public static Expression convertToCNF(Expression expr) throws PrismException
{
return convertCNFListsToExpression(doConversionToCNF(convertToPositiveNormalForm(expr)));
}
/**
* Convert an expression to conjunctive normal form (CNF).
* The passed in expression is modified, and the result is returned as a list of lists of Expression;
*/
public static List<List<Expression>> convertToCNFLists(Expression expr) throws PrismException
{
return doConversionToCNF(convertToPositiveNormalForm(expr));
}
private static List<List<Expression>> doConversionToCNF(Expression expr)
{
// Remove parentheses
if (Expression.isParenth(expr)) {
return doConversionToCNF(((ExpressionUnaryOp) expr).getOperand());
}
// Or
if (Expression.isOr(expr)) {
Expression a = ((ExpressionBinaryOp) expr).getOperand1();
Expression b = ((ExpressionBinaryOp) expr).getOperand2();
List<List<Expression>> aCnf = doConversionToCNF(a);
List<List<Expression>> bCnf = doConversionToCNF(b);
// a1&a2&... | b1&b2&... == a1|b1 & a1|b2 & ...
List<List<Expression>> cnf = new ArrayList<List<Expression>>();
for (List<Expression> ai : aCnf) {
for (List<Expression> bj : bCnf) {
List<Expression> aibj = new ArrayList<Expression>();
aibj.addAll(ai);
aibj.addAll(bj);
cnf.add(aibj);
}
}
return cnf;
}
// And
if (Expression.isAnd(expr)) {
Expression a = ((ExpressionBinaryOp) expr).getOperand1();
Expression b = ((ExpressionBinaryOp) expr).getOperand2();
List<List<Expression>> aCnf = doConversionToCNF(a);
List<List<Expression>> bCnf = doConversionToCNF(b);
// (a1|a2|...) | (b1|b2|...) == a1|a2|...|b1|b2|...
aCnf.addAll(bCnf);
return aCnf;
}
// Convert proposition to trivial CNF
List<List<Expression>> cnf = new ArrayList<List<Expression>>(1);
List<Expression> conjunct = new ArrayList<Expression>(1);
conjunct.add(expr);
cnf.add(conjunct);
return cnf;
}
// Methods to convert And/Or classes to Expressions
public static Expression convertDNFListsToExpression(List<List<Expression>> dnf)
{
Expression ret = convertConjunctionListToExpression(dnf.get(0));
for (int i = 1; i < dnf.size(); i++) {
ret = Expression.Or(ret, convertConjunctionListToExpression(dnf.get(i)));
}
return ret;
}
public static Expression convertCNFListsToExpression(List<List<Expression>> cnf)
{
Expression ret = convertDisjunctionListToExpression(cnf.get(0));
for (int i = 1; i < cnf.size(); i++) {
ret = Expression.And(ret, convertDisjunctionListToExpression(cnf.get(i)));
}
return ret;
}
public static Expression convertDisjunctionListToExpression(List<Expression> disjunction)
{
Expression ret = disjunction.get(0);
for (int i = 1; i < disjunction.size(); i++) {
ret = Expression.Or(ret, disjunction.get(i));
}
return ret;
}
public static Expression convertConjunctionListToExpression(List<Expression> conjunction)
{
Expression ret = conjunction.get(0);
for (int i = 1; i < conjunction.size(); i++) {
ret = Expression.And(ret, conjunction.get(i));
}
return ret;
}
// Test code
public static void main(String args[])
{
PrismParser parser = new PrismParser();
String ss[] = new String[] { "a&!(b=>c)", "(a|b)&(c|d|e)" };
for (String s : ss) {
try {
Expression expr = parser.parseSingleExpression(new ByteArrayInputStream(s.getBytes()));
System.out.println(expr + " in CNF is " + convertToCNF(expr.deepCopy()));
System.out.println(expr + " in DNF is " + convertToDNF(expr.deepCopy()));
} catch (PrismException e) {
System.out.println("Error: " + e.getMessage());
}
}
}
}