/* * Kodkod -- Copyright (c) 2005-2011, Emina Torlak * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package kodkod.engine.fol2sat; import static kodkod.ast.operator.FormulaOperator.AND; import static kodkod.ast.operator.FormulaOperator.OR; import java.util.ArrayList; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import kodkod.ast.BinaryFormula; import kodkod.ast.ComparisonFormula; import kodkod.ast.ConstantFormula; import kodkod.ast.Formula; import kodkod.ast.IntComparisonFormula; import kodkod.ast.MultiplicityFormula; import kodkod.ast.NaryFormula; import kodkod.ast.Node; import kodkod.ast.NotFormula; import kodkod.ast.QuantifiedFormula; import kodkod.ast.RelationPredicate; import kodkod.ast.operator.FormulaOperator; import kodkod.ast.visitor.AbstractVoidVisitor; import kodkod.util.nodes.AnnotatedNode; /** * Propagates negations all the way down to the leafs, but without crossing * quantification boundaries. It also eliminates negations wherever possible * (e.g. double negation, !(a>b) --> a<=b, etc.) * * Breaks up all implications (=>) and two-way implications (<=>), so that * the resulting formula only contains the following boolean operators: * AND (&&), OR (||), and NOT (!) at the leaf positions. */ final class FullNegationPropagator extends AbstractVoidVisitor { /** * */ public static AnnotatedNode<Formula> flatten(AnnotatedNode<Formula> annotated) { final FullNegationPropagator flat = new FullNegationPropagator(annotated.sharedNodes()); annotated.node().accept(flat); final List<Formula> roots = new ArrayList<Formula>(flat.annotations.size()); roots.addAll(flat.annotations.keySet()); for(Iterator<Map.Entry<Formula,Node>> itr = flat.annotations.entrySet().iterator(); itr.hasNext(); ) { final Map.Entry<Formula, Node> entry = itr.next(); final Node source = annotated.sourceOf(entry.getValue()); if (entry.getKey()==source) { itr.remove(); /* TODO: what is this for? */ } else { entry.setValue(source); } } return AnnotatedNode.annotate(Formula.and(flat.conjuncts), flat.annotations); } private List<Formula> conjuncts; private Map<Formula, Node> annotations; private final Map<Node,Boolean> visited; private final Set<Node> shared; private boolean negated; private boolean hasChanged; /** * Constructs a flattener for a formula in which the given nodes are shared. */ private FullNegationPropagator(Set<Node> shared) { this(shared, new LinkedHashMap<Formula, Node>(), new IdentityHashMap<Node,Boolean>()); } private FullNegationPropagator(Set<Node> shared, Map<Formula, Node> annotations, Map<Node, Boolean> visited) { this.conjuncts = new LinkedList<Formula>(); this.annotations = annotations; this.shared = shared; this.visited = visited; this.negated = false; } /** * {@inheritDoc} * @see kodkod.ast.visitor.AbstractVoidVisitor#visited(kodkod.ast.Node) */ @Override protected boolean visited(Node n) { if (shared.contains(n)) { if (visited.containsKey(n)) { final Boolean val = visited.get(n); if (val==null || val.booleanValue()==negated) { return true; } else { visited.put(n, null); return false; } } else { visited.put(n, Boolean.valueOf(negated)); return false; } } return false; } /** * Calls nf.formula.accept(this) after flipping the negation flag. * @see kodkod.ast.visitor.AbstractVoidVisitor#visit(kodkod.ast.NotFormula) */ public final void visit(NotFormula nf) { if (visited(nf)) return; FullNegationPropagator fne = new FullNegationPropagator(shared, annotations, visited); fne.negated = !negated; nf.formula().accept(fne); if (fne.hasChanged) { addConjunct(Formula.and(fne.conjuncts), false, nf); hasChanged = true; } else { addConjunct(nf); } } /** * Adds the given formula (or its negation, depending on the value of the negated flag) * to this.conjuncts. */ private final void addConjunct(Formula conjunct) { Formula f = negated ? conjunct.not() : conjunct; conjuncts.add(f); annotations.put(f, conjunct); } private final void addConjunct(Formula conjunct, boolean neg, Node source) { Formula f = neg ? conjunct.not() : conjunct; conjuncts.add(f); annotations.put(f, source); } /** * Visits the formula's children with appropriate settings * for the negated flag if bf has not been visited before. * @see kodkod.ast.visitor.AbstractVoidVisitor#visit(kodkod.ast.BinaryFormula) */ public final void visit(BinaryFormula bf) { if (visited(bf)) return; final FormulaOperator op = bf.op(); switch (op) { case AND: if (!negated) { // left && right bf.left().accept(this); bf.right().accept(this); } else { // !(left && right) --> !left || !right FullNegationPropagator fne1 = new FullNegationPropagator(shared, annotations, visited); bf.left().not().accept(fne1); FullNegationPropagator fne2 = new FullNegationPropagator(shared, annotations, visited); bf.right().not().accept(fne2); addConjunct(Formula.and(fne1.conjuncts).or(Formula.and(fne2.conjuncts)), false, bf); hasChanged = true; } break; case OR: if (!negated) { // left || right FullNegationPropagator fne1 = new FullNegationPropagator(shared, annotations, visited); bf.left().accept(fne1); FullNegationPropagator fne2 = new FullNegationPropagator(shared, annotations, visited); bf.right().accept(fne2); if (!fne1.hasChanged && !fne2.hasChanged) { addConjunct(bf); } else { addConjunct(Formula.and(fne1.conjuncts).or(Formula.and(fne2.conjuncts)), false, bf); hasChanged = true; } } else { // !(left || right) --> !left && !right bf.left().accept(this); bf.right().accept(this); hasChanged = true; } break; case IMPLIES: if (!negated) { // left => right --> !left || right FullNegationPropagator fne1 = new FullNegationPropagator(shared, annotations, visited); bf.left().not().accept(fne1); FullNegationPropagator fne2 = new FullNegationPropagator(shared, annotations, visited); bf.right().accept(fne2); addConjunct(Formula.and(fne1.conjuncts).or(Formula.and(fne2.conjuncts)), false, bf); } else { // !(left => right) --> left && !right negated = false; bf.left().accept(this); negated = true; bf.right().accept(this); } hasChanged = true; break; case IFF: FullNegationPropagator fne1 = new FullNegationPropagator(shared, annotations, visited); FullNegationPropagator fne2 = new FullNegationPropagator(shared, annotations, visited); if (!negated) { // a = b --> (a && b) || (!a && !b) bf.left().and(bf.right()).accept(fne1); bf.left().not().and(bf.right().not()).accept(fne2); } else { // !(a = b) --> (a && !b) || (!a && b) bf.left().and(bf.right().not()).accept(fne1); bf.left().not().and(bf.right()).accept(fne2); } addConjunct(Formula.and(fne1.conjuncts).or(Formula.and(fne2.conjuncts)), false, bf); hasChanged = true; break; default: addConjunct(bf); } } /** * Visits the formula's children with appropriate settings * for the negated flag if bf has not been visited before. * @see kodkod.ast.visitor.AbstractVoidVisitor#visit(kodkod.ast.NaryFormula) */ public final void visit(NaryFormula nf) { if (visited(nf)) return; final FormulaOperator op = nf.op(); if (negated && op==AND) { List<Formula> formulas = new LinkedList<Formula>(); for (Formula f : nf) { FullNegationPropagator fne = new FullNegationPropagator(shared, annotations, visited); f.not().accept(fne); formulas.add(Formula.and(fne.conjuncts)); } addConjunct(Formula.or(formulas), false, nf); } else if (!negated && op==OR) { List<Formula> formulas = new LinkedList<Formula>(); boolean changed = false; for (Formula f : nf) { FullNegationPropagator fne = new FullNegationPropagator(shared, annotations, visited); f.accept(fne); changed = changed || fne.hasChanged; formulas.add(Formula.and(fne.conjuncts)); } if (changed) { addConjunct(Formula.or(formulas), false, nf); hasChanged = true; } else { addConjunct(nf); } } else { for(Formula f : nf) { f.accept(this); } } } /** * Adds f (resp. f.not()) to this.conjuncts if the negated flag is false (resp. true) and * the given node has not been visited; otherwise does nothing. * @ensures !this.visited(f) => * (this.conjuncts' = conjuncts + (negated => f.not() else f)) else * (this.conjuncts' = this.conjuncts) */ final void visitFormula(Formula f) { if (visited(f)) return; addConjunct(f); } /** * {@inheritDoc} * @see kodkod.ast.visitor.AbstractVoidVisitor#visit(kodkod.ast.QuantifiedFormula) */ public final void visit(QuantifiedFormula qf) { if (visited(qf)) return; FullNegationPropagator fne = new FullNegationPropagator(shared, annotations, visited); qf.formula().accept(fne); if (fne.hasChanged) { Formula f = Formula.and(fne.conjuncts); addConjunct(f.quantify(qf.quantifier(), qf.decls()), negated, qf); hasChanged = true; } else { addConjunct(qf); } } /** @see #visitFormula(Formula) */ public final void visit(ComparisonFormula cf) { visitFormula(cf); } /** @see #visitFormula(Formula) */ public final void visit(IntComparisonFormula cf) { if (visited(cf)) return; if (!negated) { addConjunct(cf); } else { switch (cf.op()) { case GT: addConjunct(cf.left().lte(cf.right()), false, cf); hasChanged = true; break; case GTE: addConjunct(cf.left().lt(cf.right()), false, cf); hasChanged = true; break; case LT: addConjunct(cf.left().gte(cf.right()), false, cf); hasChanged = true; break; case LTE: addConjunct(cf.left().gt(cf.right()), false, cf); hasChanged = true; break; case EQ: addConjunct(cf.left().neq(cf.right()), false, cf); hasChanged = true; break; case NEQ: addConjunct(cf.left().eq(cf.right()), false, cf); hasChanged = true; break; default: addConjunct(cf); } } } /** @see #visitFormula(Formula) */ public final void visit(MultiplicityFormula mf) { visitFormula(mf); } /** @see #visitFormula(Formula) */ public final void visit(ConstantFormula constant) { visitFormula(constant); } /** @see #visitFormula(Formula) */ public final void visit(RelationPredicate pred) { visitFormula(pred); } }