/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang * * 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 edu.mit.csail.sdg.alloy4compiler.translator; import java.util.Iterator; import java.util.List; import edu.mit.csail.sdg.alloy4.A4Reporter; import edu.mit.csail.sdg.alloy4.Err; import edu.mit.csail.sdg.alloy4.MailBug; import kodkod.ast.BinaryExpression; import kodkod.ast.BinaryFormula; import kodkod.ast.ComparisonFormula; import kodkod.ast.Expression; import kodkod.ast.Formula; import kodkod.ast.NaryFormula; import kodkod.ast.Relation; import kodkod.ast.operator.ExprCompOperator; import kodkod.ast.operator.ExprOperator; import kodkod.ast.operator.FormulaOperator; import kodkod.instance.TupleSet; /** Immutable; this class shrinks the unknowns as much as possible in order to reduce the number of variables in final CNF. * * <p> Currently it recognizes the following patterns: * * <p> (1) When it sees "A in B", it will try to derive a safe upperbound for B, and then remove * any excess unknowns from A's upperbound. * * <p> (2) When it sees "A = B", it will try to simplify A assuming "A in B", and then simplify B assuming "B in A". */ public class Simplifier { /** Reporter for receiving debug messages. */ private A4Reporter rep = null; /** The A4Solution object we are attempting to simplify. */ private A4Solution sol = null; /** Construct a Simplifier object. */ public Simplifier() { } /* Stores the equivalence relation discovered so far. */ //private final IdentityHashMap<Node,List<Expression>> equiv = new IdentityHashMap<Node,List<Expression>>(); /** Simplify sol.bounds() based on the set of formulas, or to modify the formulas list itself. * Subclasses should override this method to implement different simplification algorithms. * (Note: this method is allowed to modify the "formulas" array if it sees an opportunity for optimization) */ public boolean simplify(A4Reporter rep, A4Solution sol, List<Formula> formulas) throws Err { this.rep = rep; this.sol = sol; while(true) { //equiv.clear(); for(Formula f: formulas) if (!simplify_eq(f)) return false; for(Formula f: formulas) if (!simplify_in(f)) return false; /* if (equiv.size()==0) return true; // We have to construct this replacer from scratch, since each time it will retain some info we don't want final AbstractReplacer ar = new AbstractReplacer(new kodkod.util.collections.IdentityHashSet<Node>()) { @Override public Expression visit(Relation relation) { List<Expression> list = equiv.get(relation); if (list!=null) return list.get(0); else return relation; } }; for(Map.Entry<Node,List<Expression>> e: equiv.entrySet()) System.out.println("Equiv: "+e); System.out.flush(); for(int i=formulas.size()-1; i>=0; i--) { Formula OLD = formulas.get(i); Formula NEW = OLD.accept(ar); if (OLD!=NEW) { System.out.println("OLD->NEW: "+OLD+" ===> "+NEW); System.out.flush(); } formulas.set(i, NEW); } */ return true; } } /** Simplify (a.(a->b)) into b when semantically equivalent */ private final Expression condense(Expression x) { while (x instanceof BinaryExpression) { BinaryExpression b = (BinaryExpression)x; if (b.op() == ExprOperator.JOIN && b.left() instanceof Relation && b.right() instanceof BinaryExpression) { Relation r = (Relation) (b.left()); try { if (sol.query(true, r, false).size()!=1) return x; if (sol.query(false, r, false).size()!=1) return x; } catch(Err er) { return x; } b = (BinaryExpression)(b.right()); if (b.op() == ExprOperator.PRODUCT && b.left()==r) { x = b.right(); continue; } } break; } return x; } /** Simplify the bounds based on the fact that "a == b"; return false if we discover the formula is unsat. */ private final boolean simplify_equal(Expression a, Expression b) { a = condense(a); b = condense(b); /* System.out.println("A: "+a+" B: "+b); System.out.flush(); if (a instanceof Relation && b instanceof Relation && a!=b) { List<Expression> al = equiv.get(a); List<Expression> bl = equiv.get(b); if (al==null) { Expression oldA=a; al=bl; bl=null; a=b; b=oldA; } if (al==null) { al = new ArrayList<Expression>(); al.add(a); al.add(b); equiv.put(a, al); equiv.put(b, al); System.out.println("BothNewEquivalent: "+a+" "+b); System.out.flush(); } else if (bl==null) { al.add(b); equiv.put(b, al); System.out.println("NewEquivalent: "+a+" "+b); System.out.flush(); } else if (al!=bl) { al.addAll(bl); for(Node x: bl) equiv.put(x, al); System.out.println("MergeEquivalent: "+a+" "+b); System.out.flush(); } } else*/ if (a instanceof Relation || b instanceof Relation) { try { TupleSet a0 = sol.query(false, a, false), a1 = sol.query(true, a, false); TupleSet b0 = sol.query(false, b, false), b1 = sol.query(true, b, false); if (a instanceof Relation && a0.size()<b0.size() && b0.containsAll(a0) && a1.containsAll(b0)) { rep.debug("Comment: Simplify "+a+" "+(a1.size()-a0.size())+"->"+(a1.size()-b0.size())+"\n"); sol.shrink((Relation)a, a0=b0, a1); } if (a instanceof Relation && a1.size()>b1.size() && b1.containsAll(a0) && a1.containsAll(b1)) { rep.debug("Comment: Simplify "+a+" "+(a1.size()-a0.size())+"->"+(b1.size()-a0.size())+"\n"); sol.shrink((Relation)a, a0, a1=b1); } if (b instanceof Relation && b0.size()<a0.size() && a0.containsAll(b0) && b1.containsAll(a0)) { rep.debug("Comment: Simplify "+b+" "+(b1.size()-b0.size())+"->"+(b1.size()-a0.size())+"\n"); sol.shrink((Relation)b, b0=a0, b1); } if (b instanceof Relation && b1.size()>a1.size() && a1.containsAll(b0) && b1.containsAll(a1)) { rep.debug("Comment: Simplify "+b+" "+(b1.size()-b0.size())+"->"+(a1.size()-b0.size())+"\n"); sol.shrink((Relation)b, b0, b1=a1); } } catch(Exception ex) {} } return true; } /** Simplify the bounds based on the fact that "a is subset of b"; return false if we discover the formula is unsat. */ private final boolean simplify_in(Expression a, Expression b) { a = condense(a); b = condense(b); if (a instanceof Relation) { try { Relation r = (Relation)a; TupleSet ub = sol.query(true, r, false), lb = sol.query(false, r, false), t = sol.approximate(b); t.retainAll(ub); if (!t.containsAll(lb)) { rep.debug("Comment: Simplify "+a+" "+ub.size()+"->false\n"); return false; } // This means the upperbound is shrunk BELOW the lowerbound. if (t.size() < ub.size()) { rep.debug("Comment: Simplify "+a+" "+ub.size()+"->"+t.size()+"\n"); sol.shrink(r,lb,t); } } catch(Throwable ex) { rep.debug("Comment: Simplify "+a+" exception: "+ex+"\n"+MailBug.dump(ex).trim()+"\n"); // Not fatal; let's report it to the debug() reporter } } return true; } // ALTERNATIVE VERSION THAT COMPUTES LOWER BOUNDS AS WELL // /** Simplify the bounds based on the fact that "a is subset of b"; return false if we discover the formula is unsat. */ // private final boolean simplify_in(Expression a, Expression b) { // a = condense(a); // b = condense(b); // if (a instanceof Relation) { // return simpIn((Relation)a, b, true); // } // if (b instanceof Relation) { // return simpIn((Relation)b, a, false); // } // return true; // } // // private final boolean simpIn(Relation r, Expression b, boolean bIsUpper) { // try { // TupleSet ub = sol.query(true, r, false); // TupleSet lb = sol.query(false, r, false); // TupleSet t = sol.approximate(b); // t.retainAll(ub); // if (bIsUpper) { // if (!t.containsAll(lb)) { // // This means the upperbound is shrunk BELOW the lowerbound. // rep.debug("Comment: Simplify upper "+r+" "+ub.size()+"->false\n"); // return false; // } // if (t.size() < ub.size()) { // rep.debug("Comment: Simplify upper "+r+" "+ub.size()+"->"+t.size()+"\n"); // sol.shrink(r,lb,t); // } // } else { // if (!ub.containsAll(t)) { // // This means the upperbound is shrunk BELOW the lowerbound. // rep.debug("Comment: Simplify lower "+r+" "+lb.size()+"->false\n"); // return false; // } // if (lb.size() < t.size()) { // rep.debug("Comment: Simplify lower "+r+" "+lb.size()+"->"+t.size()+"\n"); // sol.shrink(r,t,ub); // } // } // } catch(Throwable ex) { // rep.debug("Comment: Simplify "+r+" exception: "+ex+"\n"+MailBug.dump(ex).trim()+"\n"); // Not fatal; let's report it to the debug() reporter // } // return true; // } /** Simplify the bounds based on the fact that "form is true"; return false if we discover the formula is unsat. */ private final boolean simplify_in (Formula form) { if (form instanceof NaryFormula) { NaryFormula f = (NaryFormula)form; if (f.op() == FormulaOperator.AND) { for(Iterator<Formula> i = f.iterator(); i.hasNext();) if (!simplify_in(i.next())) return false; } } if (form instanceof BinaryFormula) { BinaryFormula f = (BinaryFormula)form; if (f.op() == FormulaOperator.AND) { return simplify_in(f.left()) && simplify_in(f.right()); } } if (form instanceof ComparisonFormula) { ComparisonFormula f = (ComparisonFormula)form; if (f.op() == ExprCompOperator.SUBSET) { if (!simplify_in(f.left(), f.right())) return false; } if (f.op() == ExprCompOperator.EQUALS) { if (!simplify_in(f.left(), f.right())) return false; if (!simplify_in(f.right(), f.left())) return false; } } return true; } /** Simplify the bounds based on the fact that "form is true"; return false if we discover the formula is unsat. */ private final boolean simplify_eq (Formula form) { if (form instanceof NaryFormula) { NaryFormula f = (NaryFormula)form; if (f.op() == FormulaOperator.AND) { for(Iterator<Formula> i = f.iterator(); i.hasNext();) if (!simplify_eq(i.next())) return false; } } if (form instanceof BinaryFormula) { BinaryFormula f = (BinaryFormula)form; if (f.op() == FormulaOperator.AND) { return simplify_eq(f.left()) && simplify_eq(f.right()); } } if (form instanceof ComparisonFormula) { ComparisonFormula f = (ComparisonFormula)form; if (f.op() == ExprCompOperator.EQUALS) { if (!simplify_equal(f.left(), f.right())) return false; } } return true; } }