/* 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.ArrayList;
import java.util.List;
import java.util.LinkedHashMap;
import java.util.Map;
import kodkod.ast.Decls;
import kodkod.ast.Expression;
import kodkod.ast.Formula;
import kodkod.ast.Relation;
import kodkod.ast.Variable;
import kodkod.instance.Tuple;
import kodkod.instance.TupleFactory;
import kodkod.instance.TupleSet;
import kodkod.instance.Universe;
import edu.mit.csail.sdg.alloy4.A4Reporter;
import edu.mit.csail.sdg.alloy4.Err;
import edu.mit.csail.sdg.alloy4.Pos;
import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field;
import edu.mit.csail.sdg.alloy4compiler.ast.Sig.PrimSig;
import edu.mit.csail.sdg.alloy4compiler.ast.Expr;
import edu.mit.csail.sdg.alloy4compiler.ast.ExprBinary;
import edu.mit.csail.sdg.alloy4compiler.ast.ExprConstant;
import edu.mit.csail.sdg.alloy4compiler.ast.ExprList;
import edu.mit.csail.sdg.alloy4compiler.ast.ExprUnary;
import edu.mit.csail.sdg.alloy4compiler.ast.Sig;
import edu.mit.csail.sdg.alloy4compiler.ast.Type;
import edu.mit.csail.sdg.alloy4compiler.ast.Sig.SubsetSig;
/** Immutable; this class assigns each sig and field to some Kodkod relation or expression, then set the bounds. */
public final class BoundsComputer {
// It calls these A4Solution methods...
// getFactory(), query(), a2k(), addRel(), addSig(), addField(), addFormula()
/** Stores the reporter that will receive diagnostic messages. */
private final A4Reporter rep;
/** Stores the scope, bounds, and other settings necessary for performing a solve. */
private final A4Solution sol;
/** Stores the factory. */
private final TupleFactory factory;
/** Stores the computed scope for each sig. */
private final ScopeComputer sc;
/** Stores the upperbound for each sig. */
private final Map<Sig,TupleSet> ub = new LinkedHashMap<Sig,TupleSet>();
/** Stores the lowerbound for each sig. */
private final Map<Sig,TupleSet> lb = new LinkedHashMap<Sig,TupleSet>();
//==============================================================================================================//
/** Computes the lowerbound from bottom-up; it will also set a suitable initial value for each sig's upperbound.
* Precondition: sig is not a builtin sig
*/
private TupleSet computeLowerBound(List<Tuple> atoms, final PrimSig sig) throws Err {
int n = sc.sig2scope(sig);
TupleSet lower = factory.noneOf(1);
for(PrimSig c:sig.children()) lower.addAll(computeLowerBound(atoms, c));
TupleSet upper = lower.clone();
boolean isExact = sc.isExact(sig);
if (isExact || sig.isTopLevel()) for(n=n-upper.size(); n>0; n--) {
Tuple atom = atoms.remove(atoms.size()-1);
// If MUST<SCOPE and s is exact, then add fresh atoms to both LOWERBOUND and UPPERBOUND.
// If MUST<SCOPE and s is inexact but toplevel, then add fresh atoms to the UPPERBOUND.
if (isExact) lower.add(atom);
upper.add(atom);
}
lb.put(sig, lower);
ub.put(sig, upper);
return lower;
}
//==============================================================================================================//
/** Computes the upperbound from top-down, then allocate a relation for it.
* Precondition: sig is not a builtin sig
*/
private void computeUpperBound(PrimSig sig) throws Err {
// Sig's upperbound is fully computed. We recursively compute the upperbound for children...
TupleSet x = ub.get(sig).clone();
// We remove atoms that MUST be in a subsig
for(PrimSig c: sig.children()) x.removeAll(lb.get(c));
// So now X is the set of atoms that MIGHT be in this sig, but MIGHT NOT be in any particular subsig.
// For each subsig that may need more atom, we say it could potentionally get any of the atom from X.
for(PrimSig c: sig.children()) {
if (sc.sig2scope(c) > lb.get(c).size()) ub.get(c).addAll(x);
computeUpperBound(c);
}
}
//==============================================================================================================//
/** Allocate relations for nonbuiltin PrimSigs bottom-up. */
private Expression allocatePrimSig(PrimSig sig) throws Err {
// Recursively allocate all children expressions, and form the union of them
Expression sum = null;
for(PrimSig child:sig.children()) {
Expression childexpr=allocatePrimSig(child);
if (sum==null) { sum=childexpr; continue; }
// subsigs are disjoint
sol.addFormula(sum.intersection(childexpr).no(), child.isSubsig);
sum = sum.union(childexpr);
}
TupleSet lower = lb.get(sig).clone(), upper = ub.get(sig).clone();
if (sum == null) {
// If sig doesn't have children, then sig should make a fresh relation for itself
sum = sol.addRel(sig.label, lower, upper);
} else if (sig.isAbstract == null) {
// If sig has children, and sig is not abstract, then create a new relation to act as the remainder.
for(PrimSig child:sig.children()) {
// Remove atoms that are KNOWN to be in a subsig;
// it's okay to mistakenly leave some atoms in, since we will never solve for the "remainder" relation directly;
// instead, we union the remainder with the children, then solve for the combined solution.
// (Thus, the more we can remove, the more efficient it gets, but it is not crucial for correctness)
TupleSet childTS = sol.query(false, sol.a2k(child), false);
lower.removeAll(childTS);
upper.removeAll(childTS);
}
sum = sum.union(sol.addRel(sig.label+" remainder", lower, upper));
}
sol.addSig(sig, sum);
return sum;
}
//==============================================================================================================//
/** Allocate relations for SubsetSig top-down. */
private Expression allocateSubsetSig(SubsetSig sig) throws Err {
// We must not visit the same SubsetSig more than once, so if we've been here already, then return the old value right away
Expression sum = sol.a2k(sig);
if (sum!=null && sum!=Expression.NONE) return sum;
// Recursively form the union of all parent expressions
TupleSet ts = factory.noneOf(1);
for(Sig parent:sig.parents) {
Expression p = (parent instanceof PrimSig) ? sol.a2k(parent) : allocateSubsetSig((SubsetSig)parent);
ts.addAll(sol.query(true, p, false));
if (sum==null) sum=p; else sum=sum.union(p);
}
// If subset is exact, then just use the "sum" as is
if (sig.exact) { sol.addSig(sig, sum); return sum; }
// Allocate a relation for this subset sig, then bound it
rep.bound("Sig "+sig+" in "+ts+"\n");
Relation r = sol.addRel(sig.label, null, ts);
sol.addSig(sig, r);
// Add a constraint that it is INDEED a subset of the union of its parents
sol.addFormula(r.in(sum), sig.isSubset);
return r;
}
//==============================================================================================================//
/** Helper method that returns the constraint that the sig has exactly "n" elements, or at most "n" elements */
private Formula size(Sig sig, int n, boolean exact) {
Expression a = sol.a2k(sig);
if (n<=0) return a.no();
if (n==1) return exact ? a.one() : a.lone();
Formula f = exact ? Formula.TRUE : null;
Decls d = null;
Expression sum = null;
while(n>0) {
n--;
Variable v = Variable.unary("v" + Integer.toString(TranslateAlloyToKodkod.cnt++));
kodkod.ast.Decl dd = v.oneOf(a);
if (d==null) d=dd; else d=dd.and(d);
if (sum==null) sum=v; else { if (f!=null) f=v.intersection(sum).no().and(f); sum=v.union(sum); }
}
if (f!=null) return sum.eq(a).and(f).forSome(d); else return a.no().or(sum.eq(a).forSome(d));
}
//==============================================================================================================//
/** If ex is a simple combination of Relations, then return that combination, else return null. */
private Expression sim(Expr ex) {
while(ex instanceof ExprUnary) {
ExprUnary u = (ExprUnary)ex;
if (u.op!=ExprUnary.Op.NOOP && u.op!=ExprUnary.Op.EXACTLYOF) break;
ex = u.sub;
}
if (ex instanceof ExprBinary) {
ExprBinary b = (ExprBinary)ex;
if (b.op==ExprBinary.Op.ARROW || b.op==ExprBinary.Op.PLUS || b.op==ExprBinary.Op.JOIN) {
Expression left = sim(b.left); if (left==null) return null;
Expression right = sim(b.right); if (right==null) return null;
if (b.op==ExprBinary.Op.ARROW) return left.product(right);
if (b.op==ExprBinary.Op.PLUS) return left.union(right); else return left.join(right);
}
}
if (ex instanceof ExprConstant) {
switch(((ExprConstant)ex).op) {
case EMPTYNESS: return Expression.NONE;
}
}
if (ex==Sig.NONE) return Expression.NONE;
if (ex==Sig.SIGINT) return Expression.INTS;
if (ex instanceof Sig) return sol.a2k((Sig)ex);
if (ex instanceof Field) return sol.a2k((Field)ex);
return null;
}
/** Computes the bounds for sigs/fields, then construct a BoundsComputer object that you can query. */
private BoundsComputer(A4Reporter rep, A4Solution sol, ScopeComputer sc, Iterable<Sig> sigs) throws Err {
this.sc = sc;
this.factory = sol.getFactory();
this.rep = rep;
this.sol = sol;
// Figure out the sig bounds
final Universe universe = factory.universe();
final int atomN = universe.size();
final List<Tuple> atoms = new ArrayList<Tuple>(atomN);
for(int i=atomN-1; i>=0; i--) atoms.add(factory.tuple(universe.atom(i)));
for(Sig s:sigs) if (!s.builtin && s.isTopLevel()) computeLowerBound(atoms, (PrimSig)s);
for(Sig s:sigs) if (!s.builtin && s.isTopLevel()) computeUpperBound((PrimSig)s);
// Bound the sigs
for(Sig s:sigs) if (!s.builtin && s.isTopLevel()) allocatePrimSig((PrimSig)s);
for(Sig s:sigs) if (s instanceof SubsetSig) allocateSubsetSig((SubsetSig)s);
// Bound the fields
again:
for(Sig s:sigs) {
while (s.isOne!=null && s.getFieldDecls().size()==2 && s.getFields().size()==2 && s.getFacts().size()==1) {
// Let's check whether this is a total ordering on an enum...
Expr fact = s.getFacts().get(0).deNOP(), b1 = s.getFieldDecls().get(0).expr.deNOP(), b2 = s.getFieldDecls().get(1).expr.deNOP(), b3;
if (!(fact instanceof ExprList) || !(b1 instanceof ExprUnary) || !(b2 instanceof ExprBinary)) break;
ExprList list = (ExprList)fact;
if (list.op!=ExprList.Op.TOTALORDER || list.args.size()!=3) break;
if (((ExprUnary)b1).op!=ExprUnary.Op.SETOF) break; else b1 = ((ExprUnary)b1).sub.deNOP();
if (((ExprBinary)b2).op!=ExprBinary.Op.ARROW) break; else { b3 = ((ExprBinary)b2).right.deNOP(); b2 = ((ExprBinary)b2).left.deNOP(); }
if (!(b1 instanceof PrimSig) || b1!=b2 || b1!=b3) break;
PrimSig sub = (PrimSig)b1;
Field f1 = s.getFields().get(0), f2 = s.getFields().get(1);
if (sub.isEnum==null || !list.args.get(0).isSame(sub) || !list.args.get(1).isSame(s.join(f1)) || !list.args.get(2).isSame(s.join(f2))) break;
// Now, we've confirmed it is a total ordering on an enum. Let's pre-bind the relations
TupleSet me = sol.query(true, sol.a2k(s), false), firstTS = factory.noneOf(2), lastTS = null, nextTS = factory.noneOf(3);
if (me.size()!=1 || me.arity()!=1) break;
int n = sub.children().size();
for(PrimSig c: sub.children()) {
TupleSet TS = sol.query(true, sol.a2k(c), false);
if (TS.size()!=1 || TS.arity()!=1) { firstTS=factory.noneOf(2); nextTS=factory.noneOf(3); break; }
if (lastTS==null) { firstTS=me.product(TS); lastTS=TS; continue; }
nextTS.addAll(me.product(lastTS).product(TS));
lastTS=TS;
}
if (firstTS.size()!=(n>0 ? 1 : 0) || nextTS.size() != n-1) break;
sol.addField(f1, sol.addRel(s.label+"."+f1.label, firstTS, firstTS));
sol.addField(f2, sol.addRel(s.label+"."+f2.label, nextTS, nextTS));
rep.bound("Field "+s.label+"."+f1.label+" == "+firstTS+"\n");
rep.bound("Field "+s.label+"."+f2.label+" == "+nextTS+"\n");
continue again;
}
for(Field f:s.getFields()) {
boolean isOne = s.isOne!=null;
if (isOne && f.decl().expr.mult()==ExprUnary.Op.EXACTLYOF) {
Expression sim = sim(f.decl().expr);
if (sim!=null) {
rep.bound("Field "+s.label+"."+f.label+" defined to be "+sim+"\n");
sol.addField(f, sol.a2k(s).product(sim));
continue;
}
}
Type t = isOne ? Sig.UNIV.type().join(f.type()) : f.type();
TupleSet ub = factory.noneOf(t.arity());
for(List<PrimSig> p:t.fold()) {
TupleSet upper=null;
for(PrimSig b:p) {
TupleSet tmp = sol.query(true, sol.a2k(b), false);
if (upper==null) upper=tmp; else upper=upper.product(tmp);
}
ub.addAll(upper);
}
Relation r = sol.addRel(s.label+"."+f.label, null, ub);
sol.addField(f, isOne ? sol.a2k(s).product(r) : r);
}
}
// Add any additional SIZE constraints
for(Sig s:sigs) if (!s.builtin) {
Expression exp = sol.a2k(s);
TupleSet upper = sol.query(true,exp,false), lower=sol.query(false,exp,false);
final int n = sc.sig2scope(s);
if (s.isOne!=null && (lower.size()!=1 || upper.size()!=1)) {
rep.bound("Sig "+s+" in "+upper+" with size==1\n");
sol.addFormula(exp.one(), s.isOne);
continue;
}
if (s.isSome!=null && lower.size()<1) sol.addFormula(exp.some(), s.isSome);
if (s.isLone!=null && upper.size()>1) sol.addFormula(exp.lone(), s.isLone);
if (n<0) continue; // This means no scope was specified
if (lower.size()==n && upper.size()==n && sc.isExact(s)) {
rep.bound("Sig "+s+" == "+upper+"\n");
}
else if (sc.isExact(s)) {
rep.bound("Sig "+s+" in "+upper+" with size=="+n+"\n");
sol.addFormula(size(s,n,true), Pos.UNKNOWN);
}
else if (upper.size()<=n){
rep.bound("Sig "+s+" in "+upper+"\n");
}
else {
rep.bound("Sig "+s+" in "+upper+" with size<="+n+"\n");
sol.addFormula(size(s,n,false), Pos.UNKNOWN);
}
}
}
//==============================================================================================================//
/** Assign each sig and field to some Kodkod relation or expression, then set the bounds. */
public static void compute(A4Reporter rep, A4Solution sol, ScopeComputer sc, Iterable<Sig> sigs) throws Err {
new BoundsComputer(rep, sol, sc, sigs);
}
}