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.IntExpression; 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.AbstractReplacer; import kodkod.util.nodes.AnnotatedNode; public class NNFConverter extends AbstractReplacer { public static AnnotatedNode<Formula> flatten(AnnotatedNode<Formula> annotated) { final NNFConverter flat = new NNFConverter(annotated.sharedNodes()); Formula f = 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(f, flat.annotations); } private Map<Formula, Node> annotations; private boolean negated; protected NNFConverter(Set<Node> shared) { this(shared, new LinkedHashMap<Formula, Node>(), new IdentityHashMap<Node,Boolean>()); } protected NNFConverter(Set<Node> shared, Map<Formula, Node> annotations, Map<Node, Boolean> visited) { super(shared); this.annotations = annotations; this.negated = false; } protected Formula addMapping(Formula f, Node source) { annotations.put(f, source); return f; } /** * Calls nf.formula.accept(this) after flipping the negation flag. * @see kodkod.ast.visitor.AbstractVoidVisitor#visit(kodkod.ast.NotFormula) */ public final Formula visit(NotFormula nf) { //if (visited(bf)) return; negated = !negated; Formula f = nf.formula().accept(this); negated = !negated; if (f instanceof NotFormula) { if (((NotFormula) f).formula() == nf.formula()) { return addMapping(nf, nf); } } return addMapping(f, nf); } /** * 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 Formula visit(BinaryFormula bf) { //if (visited(bf)) return; final FormulaOperator op = bf.op(); switch (op) { case AND: if (!negated) { // left && right Formula lf = bf.left().accept(this); Formula rf = bf.right().accept(this); if (lf == bf.left() && rf == bf.right()) { return addMapping(bf, bf); } else { return addMapping(lf.and(rf), bf); } } else { // !(left && right) --> !left || !right Formula lf = bf.left().accept(this); Formula rf = bf.right().accept(this); return addMapping(lf.or(rf), bf); } case OR: if (!negated) { // left || right Formula lf = bf.left().accept(this); Formula rf = bf.right().accept(this); if (lf == bf.left() && rf == bf.right()) { return addMapping(bf, bf); } else { return addMapping(lf.or(rf), bf); } } else { // !(left || right) --> !left && !right Formula lf = bf.left().accept(this); Formula rf = bf.right().accept(this); return addMapping(lf.or(rf), bf); } case IMPLIES: if (!negated) { // left => right --> !left || right Formula lf = bf.left().not().accept(this); Formula rf = bf.right().accept(this); return addMapping(lf.or(rf), bf); } else { // !(left => right) --> left && !right negated = false; Formula lf = bf.left().accept(this); negated = true; Formula rf = bf.right().accept(this); return addMapping(lf.and(rf), bf); } case IFF: if (!negated) { // a = b --> (a && b) || (!a && !b) Formula lf = bf.left().and(bf.right()).accept(this); Formula rf = bf.left().not().and(bf.right().not()).accept(this); return addMapping(lf.or(rf), bf); } else { // !(a = b) --> (a && !b) || (!a && b) negated = false; Formula lf = bf.left().and(bf.right().not()).accept(this); Formula rf = bf.left().not().and(bf.right()).accept(this); negated = true; return addMapping(lf.or(rf), bf); } default: return addMapping(bf, 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) */ //TODO: probably don't needed public final Formula 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) { formulas.add(f.accept(this)); } return addMapping(Formula.or(formulas), nf); } else if (negated && op==OR) { List<Formula> formulas = new LinkedList<Formula>(); for (Formula f : nf) { formulas.add(f.accept(this)); } return addMapping(Formula.and(formulas), nf); } else { List<Formula> formulas = new LinkedList<Formula>(); boolean changed = false; for (Formula f : nf) { Formula ff = f.accept(this); changed = changed || ff != f; formulas.add(ff); } if (changed) { if (op==AND) return addMapping(Formula.and(formulas), nf); else return addMapping(Formula.or(formulas), nf); } else { return addMapping(nf, nf); } } } /** @see #visitFormula(Formula) */ public final Formula visit(IntComparisonFormula cf) { //if (visited(cf)) return; IntExpression lh = cf.left().accept(this); IntExpression rh = cf.right().accept(this); if (!negated) { if (lh == cf.left() && rh == cf.right()) { return addMapping(cf, cf); } else { return addMapping(lh.compare(cf.op(), rh), cf); } } else { switch (cf.op()) { case GT: return addMapping(lh.lte(rh), cf); case GTE: return addMapping(lh.lt(rh), cf); case LT: return addMapping(lh.gte(rh), cf); case LTE: return addMapping(lh.gt(rh), cf); case EQ: return addMapping(lh.neq(rh), cf); case NEQ: return addMapping(lh.eq(rh), cf); default: return addMapping(cf, cf); } } } protected Formula addFormula(Formula f, Node src, boolean negOld) { negated = negOld; if (negated) { return addMapping(f.not(), src); } else { return addMapping(f, src); } } @Override public Formula visit(ConstantFormula constant) { boolean negOld = negated; negated = false; return addFormula(super.visit(constant), constant, negOld); } @Override public Formula visit(QuantifiedFormula quantFormula) { boolean negOld = negated; negated = false; return addFormula(super.visit(quantFormula), quantFormula, negOld); } @Override public Formula visit(ComparisonFormula compFormula) { boolean negOld = negated; negated = false; return addFormula(super.visit(compFormula), compFormula, negOld); } @Override public Formula visit(MultiplicityFormula multFormula) { boolean negOld = negated; negated = false; return addFormula(super.visit(multFormula), multFormula, negOld); } @Override public Formula visit(RelationPredicate pred) { boolean negOld = negated; negated = false; return addFormula(super.visit(pred), pred, negOld); } }