/* 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 static edu.mit.csail.sdg.alloy4compiler.ast.Sig.UNIV;
import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.SIGINT;
import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.SEQIDX;
import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.STRING;
import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.NONE;
import static kodkod.engine.Solution.Outcome.UNSATISFIABLE;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import kodkod.ast.BinaryExpression;
import kodkod.ast.BinaryFormula;
import kodkod.ast.Decl;
import kodkod.ast.Expression;
import kodkod.ast.Formula;
import kodkod.ast.IntExpression;
import kodkod.ast.Node;
import kodkod.ast.Relation;
import kodkod.ast.Variable;
import kodkod.ast.operator.ExprOperator;
import kodkod.ast.operator.FormulaOperator;
import kodkod.engine.CapacityExceededException;
import kodkod.engine.Evaluator;
import kodkod.engine.Proof;
import kodkod.engine.Solution;
import kodkod.engine.Solver;
import kodkod.engine.config.AbstractReporter;
import kodkod.engine.config.Options;
import kodkod.engine.config.Reporter;
import kodkod.engine.fol2sat.TranslationRecord;
import kodkod.engine.fol2sat.Translator;
import kodkod.engine.satlab.SATFactory;
import kodkod.engine.ucore.HybridStrategy;
import kodkod.engine.ucore.RCEStrategy;
import kodkod.instance.Bounds;
import kodkod.instance.Instance;
import kodkod.instance.Tuple;
import kodkod.instance.TupleFactory;
import kodkod.instance.TupleSet;
import kodkod.instance.Universe;
import kodkod.util.ints.IndexedEntry;
import edu.mit.csail.sdg.alloy4.A4Reporter;
import edu.mit.csail.sdg.alloy4.ConstList;
import edu.mit.csail.sdg.alloy4.ConstMap;
import edu.mit.csail.sdg.alloy4.Err;
import edu.mit.csail.sdg.alloy4.ErrorAPI;
import edu.mit.csail.sdg.alloy4.ErrorFatal;
import edu.mit.csail.sdg.alloy4.ErrorSyntax;
import edu.mit.csail.sdg.alloy4.Pair;
import edu.mit.csail.sdg.alloy4.Pos;
import edu.mit.csail.sdg.alloy4.SafeList;
import edu.mit.csail.sdg.alloy4.UniqueNameGenerator;
import edu.mit.csail.sdg.alloy4.Util;
import edu.mit.csail.sdg.alloy4compiler.ast.Command;
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.ExprUnary;
import edu.mit.csail.sdg.alloy4compiler.ast.ExprVar;
import edu.mit.csail.sdg.alloy4compiler.ast.Func;
import edu.mit.csail.sdg.alloy4compiler.ast.Sig;
import edu.mit.csail.sdg.alloy4compiler.ast.Type;
import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field;
import edu.mit.csail.sdg.alloy4compiler.ast.Sig.PrimSig;
import edu.mit.csail.sdg.alloy4compiler.translator.A4Options.SatSolver;
/** This class stores a SATISFIABLE or UNSATISFIABLE solution.
* It is also used as a staging area for the solver before generating the solution.
* Once solve() has been called, then this object becomes immutable after that.
*/
public final class A4Solution {
//====== static immutable fields ====================================================================//
/** The constant unary relation representing the smallest Int atom. */
static final Relation KK_MIN = Relation.unary("Int/min");
/** The constant unary relation representing the Int atom "0". */
static final Relation KK_ZERO = Relation.unary("Int/zero");
/** The constant unary relation representing the largest Int atom. */
static final Relation KK_MAX = Relation.unary("Int/max");
/** The constant binary relation representing the "next" relation from each Int atom to its successor. */
static final Relation KK_NEXT = Relation.binary("Int/next");
/** The constant unary relation representing the set of all seq/Int atoms. */
static final Relation KK_SEQIDX = Relation.unary("seq/Int");
/** The constant unary relation representing the set of all String atoms. */
static final Relation KK_STRING = Relation.unary("String");
//====== immutable fields ===========================================================================//
/** The original Alloy options that generated this solution. */
private final A4Options originalOptions;
/** The original Alloy command that generated this solution; can be "" if unknown. */
private final String originalCommand;
/** The bitwidth; always between 1 and 30. */
private final int bitwidth;
/** The maximum allowed sequence length; always between 0 and 2^(bitwidth-1)-1. */
private final int maxseq;
/** The maximum allowed number of loop unrolling and recursion level. */
private final int unrolls;
/** The list of all atoms. */
private final ConstList<String> kAtoms;
/** The Kodkod TupleFactory object. */
private final TupleFactory factory;
/** The set of all Int atoms; immutable. */
private final TupleSet sigintBounds;
/** The set of all seq/Int atoms; immutable. */
private final TupleSet seqidxBounds;
/** The set of all String atoms; immutable. */
private final TupleSet stringBounds;
/** The Kodkod Solver object. */
public final Solver solver;
//====== mutable fields (immutable after solve() has been called) ===================================//
/** True iff the problem is solved. */
private boolean solved = false;
/** The Kodkod Bounds object. */
private Bounds bounds;
/** The list of Kodkod formulas; can be empty if unknown; once a solution is solved we must not modify this anymore */
private ArrayList<Formula> formulas = new ArrayList<Formula>();
/** The list of known Alloy4 sigs. */
private SafeList<Sig> sigs;
/** If solved==true and is satisfiable, then this is the list of known skolems. */
private SafeList<ExprVar> skolems = new SafeList<ExprVar>();
/** If solved==true and is satisfiable, then this is the list of actually used atoms. */
private SafeList<ExprVar> atoms = new SafeList<ExprVar>();
/** If solved==true and is satisfiable, then this maps each Kodkod atom to a short name. */
private Map<Object,String> atom2name = new LinkedHashMap<Object,String>();
/** If solved==true and is satisfiable, then this maps each Kodkod atom to its most specific sig. */
private Map<Object,PrimSig> atom2sig = new LinkedHashMap<Object,PrimSig>();
/** If solved==true and is satisfiable, then this is the Kodkod evaluator. */
private Evaluator eval = null;
/** If not null, you can ask it to get another solution. */
private Iterator<Solution> kEnumerator = null;
/** The map from each Sig/Field/Skolem/Atom to its corresponding Kodkod expression. */
private Map<Expr,Expression> a2k;
/** The map from each String literal to its corresponding Kodkod expression. */
private final ConstMap<String,Expression> s2k;
/** The map from each kodkod Formula to Alloy Expr or Alloy Pos (can be empty if unknown) */
private Map<Formula,Object> k2pos;
/** The map from each Kodkod Relation to Alloy Type (can be empty or incomplete if unknown) */
private Map<Relation,Type> rel2type;
/** The map from each Kodkod Variable to an Alloy Type and Alloy Pos. */
private Map<Variable,Pair<Type,Pos>> decl2type;
//===================================================================================================//
/** Construct a blank A4Solution containing just UNIV, SIGINT, SEQIDX, STRING, and NONE as its only known sigs.
* @param originalCommand - the original Alloy command that generated this solution; can be "" if unknown
* @param bitwidth - the bitwidth; must be between 1 and 30
* @param maxseq - the maximum allowed sequence length; must be between 0 and (2^(bitwidth-1))-1
* @param atoms - the set of atoms
* @param rep - the reporter that will receive diagnostic and progress messages
* @param opt - the Alloy options that will affect the solution and the solver
* @param expected - whether the user expected an instance or not (1 means yes, 0 means no, -1 means the user did not express an expectation)
*/
A4Solution(String originalCommand, int bitwidth, int maxseq, Set<String> stringAtoms, Collection<String> atoms, final A4Reporter rep, A4Options opt, int expected) throws Err {
opt = opt.dup();
this.unrolls = opt.unrolls;
this.sigs = new SafeList<Sig>(Arrays.asList(UNIV, SIGINT, SEQIDX, STRING, NONE));
this.a2k = Util.asMap(new Expr[]{UNIV, SIGINT, SEQIDX, STRING, NONE}, Relation.INTS.union(KK_STRING), Relation.INTS, KK_SEQIDX, KK_STRING, Relation.NONE);
this.k2pos = new LinkedHashMap<Formula,Object>();
this.rel2type = new LinkedHashMap<Relation,Type>();
this.decl2type = new LinkedHashMap<Variable,Pair<Type,Pos>>();
this.originalOptions = opt;
this.originalCommand = (originalCommand==null ? "" : originalCommand);
this.bitwidth = bitwidth;
this.maxseq = maxseq;
if (bitwidth < 0) throw new ErrorSyntax("Cannot specify a bitwidth less than 0");
if (bitwidth > 30) throw new ErrorSyntax("Cannot specify a bitwidth greater than 30");
if (maxseq < 0) throw new ErrorSyntax("The maximum sequence length cannot be negative.");
if (maxseq > 0 && maxseq > max()) throw new ErrorSyntax("With integer bitwidth of "+bitwidth+", you cannot have sequence length longer than "+max());
if (atoms.isEmpty()) {
atoms = new ArrayList<String>(1);
atoms.add("<empty>");
}
kAtoms = ConstList.make(atoms);
bounds = new Bounds(new Universe(kAtoms));
factory = bounds.universe().factory();
TupleSet sigintBounds = factory.noneOf(1);
TupleSet seqidxBounds = factory.noneOf(1);
TupleSet stringBounds = factory.noneOf(1);
final TupleSet next = factory.noneOf(2);
int min=min(), max=max();
if (max >= min) for(int i=min; i<=max; i++) { // Safe since we know 1 <= bitwidth <= 30
Tuple ii = factory.tuple(""+i);
TupleSet is = factory.range(ii, ii);
bounds.boundExactly(i, is);
sigintBounds.add(ii);
if (i>=0 && i<maxseq) seqidxBounds.add(ii);
if (i+1<=max) next.add(factory.tuple(""+i, ""+(i+1)));
if (i==min) bounds.boundExactly(KK_MIN, is);
if (i==max) bounds.boundExactly(KK_MAX, is);
if (i==0) bounds.boundExactly(KK_ZERO, is);
}
this.sigintBounds = sigintBounds.unmodifiableView();
this.seqidxBounds = seqidxBounds.unmodifiableView();
bounds.boundExactly(KK_NEXT, next);
bounds.boundExactly(KK_SEQIDX, this.seqidxBounds);
Map<String,Expression> s2k = new HashMap<String,Expression>();
for(String e: stringAtoms) {
Relation r = Relation.unary("");
Tuple t = factory.tuple(e);
s2k.put(e, r);
bounds.boundExactly(r, factory.range(t, t));
stringBounds.add(t);
}
this.s2k = ConstMap.make(s2k);
this.stringBounds = stringBounds.unmodifiableView();
bounds.boundExactly(KK_STRING, this.stringBounds);
int sym = (expected==1 ? 0 : opt.symmetry);
solver = new Solver();
solver.options().setNoOverflow(opt.noOverflow);
solver.options().setFlatten(false); // added for now, since multiplication and division circuit takes forever to flatten
if (opt.solver.external()!=null) {
String ext = opt.solver.external();
if (opt.solverDirectory.length()>0 && ext.indexOf(File.separatorChar)<0) ext=opt.solverDirectory+File.separatorChar+ext;
try {
File tmp = File.createTempFile("tmp", ".cnf", new File(opt.tempDirectory));
tmp.deleteOnExit();
solver.options().setSolver(SATFactory.externalFactory(ext, tmp.getAbsolutePath(), "", opt.solver.options()));
//solver.options().setSolver(SATFactory.externalFactory(ext, tmp.getAbsolutePath(), opt.solver.options()));
} catch(IOException ex) { throw new ErrorFatal("Cannot create temporary directory.", ex); }
} else if (opt.solver.equals(A4Options.SatSolver.ZChaffJNI)) {
solver.options().setSolver(SATFactory.ZChaff);
} else if (opt.solver.equals(A4Options.SatSolver.MiniSatJNI)) {
solver.options().setSolver(SATFactory.MiniSat);
} else if (opt.solver.equals(A4Options.SatSolver.MiniSatProverJNI)) {
sym=20;
solver.options().setSolver(SATFactory.MiniSatProver);
solver.options().setLogTranslation(2);
solver.options().setCoreGranularity(opt.coreGranularity);
} else {
solver.options().setSolver(SATFactory.DefaultSAT4J); // Even for "KK" and "CNF", we choose SAT4J here; later, just before solving, we'll change it to a Write2CNF solver
}
solver.options().setSymmetryBreaking(sym);
solver.options().setSkolemDepth(opt.skolemDepth);
solver.options().setBitwidth(bitwidth > 0 ? bitwidth : (int) Math.ceil(Math.log(atoms.size())) + 1);
solver.options().setIntEncoding(Options.IntEncoding.TWOSCOMPLEMENT);
}
/** Construct a new A4Solution that is the continuation of the old one, but with the "next" instance. */
private A4Solution(A4Solution old) throws Err {
if (!old.solved) throw new ErrorAPI("This solution is not yet solved, so next() is not allowed.");
if (old.kEnumerator==null) throw new ErrorAPI("This solution was not generated by an incremental SAT solver.\n" + "Solution enumeration is currently only implemented for MiniSat and SAT4J.");
if (old.eval==null) throw new ErrorAPI("This solution is already unsatisfiable, so you cannot call next() to get the next solution.");
Instance inst = old.kEnumerator.next().instance();
unrolls = old.unrolls;
originalOptions = old.originalOptions;
originalCommand = old.originalCommand;
bitwidth = old.bitwidth;
maxseq = old.maxseq;
kAtoms = old.kAtoms;
factory = old.factory;
sigintBounds = old.sigintBounds;
seqidxBounds = old.seqidxBounds;
stringBounds = old.stringBounds;
solver = old.solver;
bounds = old.bounds;
formulas = old.formulas;
sigs = old.sigs;
kEnumerator = old.kEnumerator;
k2pos = old.k2pos;
rel2type = old.rel2type;
decl2type = old.decl2type;
if (inst!=null) {
eval = new Evaluator(inst, old.solver.options());
a2k = new LinkedHashMap<Expr,Expression>();
for(Map.Entry<Expr,Expression> e: old.a2k.entrySet())
if (e.getKey() instanceof Sig || e.getKey() instanceof Field)
a2k.put(e.getKey(), e.getValue());
UniqueNameGenerator un = new UniqueNameGenerator();
rename(this, null, null, un);
a2k = ConstMap.make(a2k);
} else {
skolems = old.skolems;
eval = null;
a2k = old.a2k;
}
s2k = old.s2k;
atoms = atoms.dup();
atom2name = ConstMap.make(atom2name);
atom2sig = ConstMap.make(atom2sig);
solved = true;
}
/** Turn the solved flag to be true, and make all remaining fields immutable. */
private void solved() {
if (solved) return; // already solved
bounds = bounds.clone().unmodifiableView();
sigs = sigs.dup();
skolems = skolems.dup();
atoms = atoms.dup();
atom2name = ConstMap.make(atom2name);
atom2sig = ConstMap.make(atom2sig);
a2k = ConstMap.make(a2k);
k2pos = ConstMap.make(k2pos);
rel2type = ConstMap.make(rel2type);
decl2type = ConstMap.make(decl2type);
solved = true;
}
//===================================================================================================//
/** Returns the bitwidth; always between 1 and 30. */
public int getBitwidth() { return bitwidth; }
/** Returns the maximum allowed sequence length; always between 0 and 2^(bitwidth-1)-1. */
public int getMaxSeq() { return maxseq; }
/** Returns the largest allowed integer, or -1 if no integers are allowed. */
public int max() { return Util.max(bitwidth); }
/** Returns the smallest allowed integer, or 0 if no integers are allowed */
public int min() { return Util.min(bitwidth); }
/** Returns the maximum number of allowed loop unrolling or recursion level. */
public int unrolls() { return unrolls; }
//===================================================================================================//
/** Returns the original Alloy file name that generated this solution; can be "" if unknown. */
public String getOriginalFilename() { return originalOptions.originalFilename; }
/** Returns the original command that generated this solution; can be "" if unknown. */
public String getOriginalCommand() { return originalCommand; }
//===================================================================================================//
/** Returns the Kodkod input used to generate this solution; returns "" if unknown. */
public String debugExtractKInput() {
if (solved)
return TranslateKodkodToJava.convert(Formula.and(formulas), bitwidth, kAtoms, bounds, atom2name);
else
return TranslateKodkodToJava.convert(Formula.and(formulas), bitwidth, kAtoms, bounds.unmodifiableView(), null);
}
//===================================================================================================//
/** Returns the Kodkod TupleFactory object. */
public TupleFactory getFactory() { return factory; }
/** Returns a modifiable copy of the Kodkod Bounds object. */
public Bounds getBounds() { return bounds.clone(); }
/** Add a new relation with the given label and the given lower and upper bound.
* @param label - the label for the new relation; need not be unique
* @param lower - the lowerbound; can be null if you want it to be the empty set
* @param upper - the upperbound; cannot be null; must contain everything in lowerbound
*/
public Relation addRel(String label, TupleSet lower, TupleSet upper) throws ErrorFatal {
if (solved) throw new ErrorFatal("Cannot add a Kodkod relation since solve() has completed.");
Relation rel = Relation.nary(label, upper.arity());
if (lower == upper) {
bounds.boundExactly(rel, upper);
} else if (lower == null) {
bounds.bound(rel, upper);
} else {
if (lower.arity() != upper.arity()) throw new ErrorFatal("Relation "+label+" must have same arity for lowerbound and upperbound.");
bounds.bound(rel, lower, upper);
}
return rel;
}
/** Add a new sig to this solution and associate it with the given expression (and if s.isTopLevel then add this expression into Sig.UNIV).
* <br> The expression must contain only constant Relations or Relations that are already bound in this solution.
* <br> (If the sig was already added by a previous call to addSig(), then this call will return immediately without altering what it is associated with)
*/
void addSig(Sig s, Expression expr) throws ErrorFatal {
if (solved) throw new ErrorFatal("Cannot add an additional sig since solve() has completed.");
if (expr.arity()!=1) throw new ErrorFatal("Sig "+s+" must be associated with a unary relational value.");
if (a2k.containsKey(s)) return;
a2k.put(s, expr);
sigs.add(s);
if (s.isTopLevel()) a2k.put(UNIV, a2k.get(UNIV).union(expr));
}
/** Add a new field to this solution and associate it with the given expression.
* <br> The expression must contain only constant Relations or Relations that are already bound in this solution.
* <br> (If the field was already added by a previous call to addField(), then this call will return immediately without altering what it is associated with)
*/
public void addField(Field f, Expression expr) throws ErrorFatal {
if (solved) throw new ErrorFatal("Cannot add an additional field since solve() has completed.");
if (expr.arity()!=f.type().arity()) throw new ErrorFatal("Field "+f+" must be associated with an "+f.type().arity()+"-ary relational value.");
if (a2k.containsKey(f)) return;
a2k.put(f, expr);
}
/** Add a new skolem to this solution and associate it with the given expression.
* <br> The expression must contain only constant Relations or Relations that are already bound in this solution.
*/
private ExprVar addSkolem(String label, Type type, Expression expr) throws Err {
if (solved) throw new ErrorFatal("Cannot add an additional skolem since solve() has completed.");
int a = type.arity();
if (a<1) throw new ErrorFatal("Skolem "+label+" must be associated with a relational value.");
if (a!=expr.arity()) throw new ErrorFatal("Skolem "+label+" must be associated with an "+a+"-ary relational value.");
ExprVar v = ExprVar.make(Pos.UNKNOWN, label, type);
a2k.put(v, expr);
skolems.add(v);
return v;
}
/** Returns an unmodifiable copy of the map from each Sig/Field/Skolem/Atom to its corresponding Kodkod expression. */
ConstMap<Expr,Expression> a2k() { return ConstMap.make(a2k); }
/** Returns an unmodifiable copy of the map from each String literal to its corresponding Kodkod expression. */
ConstMap<String,Expression> s2k() { return s2k; }
/** Returns the corresponding Kodkod expression for the given Sig, or null if it is not associated with anything. */
public Expression a2k(Sig sig) { return a2k.get(sig); }
/** Returns the corresponding Kodkod expression for the given Field, or null if it is not associated with anything. */
public Expression a2k(Field field) { return a2k.get(field); }
/** Returns the corresponding Kodkod expression for the given Atom/Skolem, or null if it is not associated with anything. */
Expression a2k(ExprVar var) { return a2k.get(var); }
/** Returns the corresponding Kodkod expression for the given String constant, or null if it is not associated with anything. */
Expression a2k(String stringConstant) { return s2k.get(stringConstant); }
/** Returns the corresponding Kodkod expression for the given expression, or null if it is not associated with anything. */
Expression a2k(Expr expr) throws ErrorFatal {
while(expr instanceof ExprUnary) {
if (((ExprUnary)expr).op==ExprUnary.Op.NOOP) { expr = ((ExprUnary)expr).sub; continue; }
if (((ExprUnary)expr).op==ExprUnary.Op.EXACTLYOF) { expr = ((ExprUnary)expr).sub; continue; }
break;
}
if (expr instanceof ExprConstant && ((ExprConstant)expr).op==ExprConstant.Op.EMPTYNESS) return Expression.NONE;
if (expr instanceof ExprConstant && ((ExprConstant)expr).op==ExprConstant.Op.STRING) return s2k.get(((ExprConstant)expr).string);
if (expr instanceof Sig || expr instanceof Field || expr instanceof ExprVar) return a2k.get(expr);
if (expr instanceof ExprBinary) {
Expr a=((ExprBinary)expr).left, b=((ExprBinary)expr).right;
switch(((ExprBinary)expr).op) {
case ARROW: return a2k(a).product(a2k(b));
case PLUS: return a2k(a).union(a2k(b));
case MINUS: return a2k(a).difference(a2k(b));
//TODO: IPLUS, IMINUS???
}
}
return null; // Current only UNION, PRODUCT, and DIFFERENCE of Sigs and Fields and ExprConstant.EMPTYNESS are allowed in a defined field's definition.
}
/** Return a modifiable TupleSet representing a sound overapproximation of the given expression. */
TupleSet approximate(Expression expression) {
return factory.setOf(expression.arity(), Translator.approximate(expression, bounds, solver.options()).denseIndices());
}
/** Query the Bounds object to find the lower/upper bound; throws ErrorFatal if expr is not Relation, nor a {union, product} of Relations. */
TupleSet query(boolean findUpper, Expression expr, boolean makeMutable) throws ErrorFatal {
if (expr==Relation.NONE) return factory.noneOf(1);
if (expr==Relation.INTS) return makeMutable ? sigintBounds.clone() : sigintBounds;
if (expr==KK_SEQIDX) return makeMutable ? seqidxBounds.clone() : seqidxBounds;
if (expr==KK_STRING) return makeMutable ? stringBounds.clone() : stringBounds;
if (expr instanceof Relation) {
TupleSet ans = findUpper ? bounds.upperBound((Relation)expr) : bounds.lowerBound((Relation)expr);
if (ans!=null) return makeMutable ? ans.clone() : ans;
}
else if (expr instanceof BinaryExpression) {
BinaryExpression b = (BinaryExpression)expr;
if (b.op() == ExprOperator.UNION) {
TupleSet left = query(findUpper, b.left(), true);
TupleSet right = query(findUpper, b.right(), false);
left.addAll(right);
return left;
} else if (b.op() == ExprOperator.PRODUCT) {
TupleSet left = query(findUpper, b.left(), true);
TupleSet right = query(findUpper, b.right(), false);
return left.product(right);
}
}
throw new ErrorFatal("Unknown expression encountered during bounds computation: "+expr);
}
/** Shrink the bounds for the given relation; throws an exception if the new bounds is not sameAs/subsetOf the old bounds. */
public void shrink(Relation relation, TupleSet lowerBound, TupleSet upperBound) throws Err {
if (solved) throw new ErrorFatal("Cannot shrink a Kodkod relation since solve() has completed.");
TupleSet oldL = bounds.lowerBound(relation);
TupleSet oldU = bounds.upperBound(relation);
if (oldU.containsAll(upperBound) && upperBound.containsAll(lowerBound) && lowerBound.containsAll(oldL)) {
bounds.bound(relation, lowerBound, upperBound);
} else {
throw new ErrorAPI("Inconsistent bounds shrinking on relation: "+relation);
}
}
//===================================================================================================//
/** Returns true iff the problem has been solved and the result is satisfiable. */
public boolean satisfiable() { return eval!=null; }
/** Returns an unmodifiable copy of the list of all sigs in this solution's model; always contains UNIV+SIGINT+SEQIDX+STRING+NONE and has no duplicates. */
public SafeList<Sig> getAllReachableSigs() { return sigs.dup(); }
/** Returns an unmodifiable copy of the list of all skolems if the problem is solved and is satisfiable; else returns an empty list. */
public Iterable<ExprVar> getAllSkolems() { return skolems.dup(); }
/** Returns an unmodifiable copy of the list of all atoms if the problem is solved and is satisfiable; else returns an empty list. */
public Iterable<ExprVar> getAllAtoms() { return atoms.dup(); }
/** Returns the short unique name corresponding to the given atom if the problem is solved and is satisfiable; else returns atom.toString(). */
String atom2name(Object atom) { String ans=atom2name.get(atom); return ans==null ? atom.toString() : ans; }
/** Returns the most specific sig corresponding to the given atom if the problem is solved and is satisfiable; else returns UNIV. */
PrimSig atom2sig(Object atom) { PrimSig sig=atom2sig.get(atom); return sig==null ? UNIV : sig; }
/** Caches eval(Sig) and eval(Field) results. */
private Map<Expr,A4TupleSet> evalCache = new LinkedHashMap<Expr,A4TupleSet>();
/** Return the A4TupleSet for the given sig (if solution not yet solved, or unsatisfiable, or sig not found, then return an empty tupleset) */
public A4TupleSet eval(Sig sig) {
try {
if (!solved || eval==null) return new A4TupleSet(factory.noneOf(1), this);
A4TupleSet ans = evalCache.get(sig);
if (ans!=null) return ans;
TupleSet ts = eval.evaluate((Expression) TranslateAlloyToKodkod.alloy2kodkod(this, sig));
ans = new A4TupleSet(ts, this);
evalCache.put(sig, ans);
return ans;
} catch(Err er) {
return new A4TupleSet(factory.noneOf(1), this);
}
}
/** Return the A4TupleSet for the given field (if solution not yet solved, or unsatisfiable, or field not found, then return an empty tupleset) */
public A4TupleSet eval(Field field) {
try {
if (!solved || eval==null) return new A4TupleSet(factory.noneOf(field.type().arity()), this);
A4TupleSet ans = evalCache.get(field);
if (ans!=null) return ans;
TupleSet ts = eval.evaluate((Expression) TranslateAlloyToKodkod.alloy2kodkod(this, field));
ans = new A4TupleSet(ts, this);
evalCache.put(field, ans);
return ans;
} catch(Err er) {
return new A4TupleSet(factory.noneOf(field.type().arity()), this);
}
}
/** If this solution is solved and satisfiable, evaluates the given expression and returns an A4TupleSet, a java Integer, or a java Boolean. */
public Object eval(Expr expr) throws Err {
try {
if (expr instanceof Sig) return eval((Sig)expr);
if (expr instanceof Field) return eval((Field)expr);
if (!solved) throw new ErrorAPI("This solution is not yet solved, so eval() is not allowed.");
if (eval==null) throw new ErrorAPI("This solution is unsatisfiable, so eval() is not allowed.");
if (expr.ambiguous && !expr.errors.isEmpty()) expr = expr.resolve(expr.type(), null);
if (!expr.errors.isEmpty()) throw expr.errors.pick();
Object result = TranslateAlloyToKodkod.alloy2kodkod(this, expr);
if (result instanceof IntExpression) return eval.evaluate((IntExpression)result) + (eval.wasOverflow() ? " (OF)" : "");
if (result instanceof Formula) return eval.evaluate((Formula)result);
if (result instanceof Expression) return new A4TupleSet(eval.evaluate((Expression)result), this);
throw new ErrorFatal("Unknown internal error encountered in the evaluator.");
} catch(CapacityExceededException ex) {
throw TranslateAlloyToKodkod.rethrow(ex);
}
}
/** Returns the Kodkod instance represented by this solution; throws an exception if the problem is not yet solved or if it is unsatisfiable. */
public Instance debugExtractKInstance() throws Err {
if (!solved) throw new ErrorAPI("This solution is not yet solved, so instance() is not allowed.");
if (eval==null) throw new ErrorAPI("This solution is unsatisfiable, so instance() is not allowed.");
return eval.instance().unmodifiableView();
}
//===================================================================================================//
/** Maps a Kodkod formula to an Alloy Expr or Alloy Pos (or null if no such mapping) */
Object k2pos(Node formula) { return k2pos.get(formula); }
/** Associates the Kodkod formula to a particular Alloy Expr (if the Kodkod formula is not already associated with an Alloy Expr or Alloy Pos) */
Formula k2pos(Formula formula, Expr expr) throws Err {
if (solved) throw new ErrorFatal("Cannot alter the k->pos mapping since solve() has completed.");
if (formula==null || expr==null || k2pos.containsKey(formula)) return formula;
k2pos.put(formula, expr);
if (formula instanceof BinaryFormula) {
BinaryFormula b = (BinaryFormula)formula;
if (b.op() == FormulaOperator.AND) { k2pos(b.left(), expr); k2pos(b.right(), expr); }
}
return formula;
}
/** Associates the Kodkod formula to a particular Alloy Pos (if the Kodkod formula is not already associated with an Alloy Expr or Alloy Pos) */
Formula k2pos(Formula formula, Pos pos) throws Err {
if (solved) throw new ErrorFatal("Cannot alter the k->pos mapping since solve() has completed.");
if (formula==null || pos==null || pos==Pos.UNKNOWN || k2pos.containsKey(formula)) return formula;
k2pos.put(formula, pos);
if (formula instanceof BinaryFormula) {
BinaryFormula b = (BinaryFormula)formula;
if (b.op() == FormulaOperator.AND) { k2pos(b.left(), pos); k2pos(b.right(), pos); }
}
return formula;
}
//===================================================================================================//
/** Associates the Kodkod relation to a particular Alloy Type (if it is not already associated with something) */
void kr2type(Relation relation, Type newType) throws Err {
if (solved) throw new ErrorFatal("Cannot alter the k->type mapping since solve() has completed.");
if (!rel2type.containsKey(relation)) rel2type.put(relation, newType);
}
/** Remove all mapping from Kodkod relation to Alloy Type. */
void kr2typeCLEAR() throws Err {
if (solved) throw new ErrorFatal("Cannot clear the k->type mapping since solve() has completed.");
rel2type.clear();
}
//===================================================================================================//
/** Caches a constant pair of Type.EMPTY and Pos.UNKNOWN */
private Pair<Type,Pos> cachedPAIR = null;
/** Maps a Kodkod variable to an Alloy Type and Alloy Pos (if no association exists, it will return (Type.EMPTY , Pos.UNKNOWN) */
public Pair<Type,Pos> kv2typepos(Variable var) {
Pair<Type,Pos> ans=decl2type.get(var);
if (ans!=null) return ans;
if (cachedPAIR==null) cachedPAIR=new Pair<Type,Pos>(Type.EMPTY, Pos.UNKNOWN);
return cachedPAIR;
}
/** Associates the Kodkod variable to a particular Alloy Type and Alloy Pos (if it is not already associated with something) */
void kv2typepos(Variable var, Type type, Pos pos) throws Err {
if (solved) throw new ErrorFatal("Cannot alter the k->type mapping since solve() has completed.");
if (type==null) type=Type.EMPTY;
if (pos==null) pos=Pos.UNKNOWN;
if (!decl2type.containsKey(var)) decl2type.put(var, new Pair<Type,Pos>(type, pos));
}
//===================================================================================================//
/** Add the given formula to the list of Kodkod formulas, and associate it with the given Pos object (pos can be null if unknown). */
void addFormula(Formula newFormula, Pos pos) throws Err {
if (solved) throw new ErrorFatal("Cannot add an additional formula since solve() has completed.");
if (formulas.size()>0 && formulas.get(0)==Formula.FALSE) return; // If one formula is false, we don't need the others
if (newFormula==Formula.FALSE) formulas.clear(); // If one formula is false, we don't need the others
formulas.add(newFormula);
if (pos!=null && pos!=Pos.UNKNOWN) k2pos(newFormula, pos);
}
/** Add the given formula to the list of Kodkod formulas, and associate it with the given Expr object (expr can be null if unknown) */
void addFormula(Formula newFormula, Expr expr) throws Err {
if (solved) throw new ErrorFatal("Cannot add an additional formula since solve() has completed.");
if (formulas.size()>0 && formulas.get(0)==Formula.FALSE) return; // If one formula is false, we don't need the others
if (newFormula==Formula.FALSE) formulas.clear(); // If one formula is false, we don't need the others
formulas.add(newFormula);
if (expr!=null) k2pos(newFormula, expr);
}
//===================================================================================================//
/** Helper class that wraps an iterator up where it will pre-fetch the first element (note: it will not prefetch subsequent elements). */
private static final class Peeker<T> implements Iterator<T> {
/** The encapsulated iterator. */
private Iterator<T> iterator;
/** True iff we have captured the first element. */
private boolean hasFirst;
/** If hasFirst is true, then this is the captured first element. */
private T first;
/** Constructrs a Peeker object. */
private Peeker(Iterator<T> it) {
iterator = it;
if (it.hasNext()) { hasFirst=true; first=it.next(); }
}
/** {@inheritDoc} */
public boolean hasNext() {
return hasFirst || iterator.hasNext();
}
/** {@inheritDoc} */
public T next() {
if (hasFirst) { hasFirst=false; T ans=first; first=null; return ans; } else return iterator.next();
}
/** {@inheritDoc} */
public void remove() { throw new UnsupportedOperationException(); }
}
//===================================================================================================//
/** Helper method to determine if a given binary relation is a total order over a given unary relation. */
private static List<Tuple> isOrder(TupleSet b, TupleSet u) {
// Size check
final int n = u.size();
final List<Tuple> list = new ArrayList<Tuple>(n);
if (b.size() == 0 && n <= 1) return list;
if (b.size() != n-1) return null;
// Find the starting element
Tuple head = null;
TupleSet right = b.project(1);
for(Tuple x: u) if (!right.contains(x)) {head = x; break;}
if (head==null) return null;
final TupleFactory f = head.universe().factory();
// Form the list
list.add(head);
while(true) {
// Find head.next
Tuple headnext = null;
for(Tuple x: b) if (x.atom(0)==head.atom(0)) { headnext = f.tuple(x.atom(1)); break; }
// If we've reached the end of the chain, and indeed we've formed exactly n elements (and all are in u), we're done
if (headnext==null) return list.size()==n ? list : null;
// If we've accumulated more than n elements, or if we reached an element not in u, then we declare failure
if (list.size()==n || !u.contains(headnext)) return null;
// Move on to the next step
head = headnext;
list.add(head);
}
}
/** Helper method that chooses a name for each atom based on its most specific sig; (external caller should call this method with s==null and nexts==null) */
private static void rename (A4Solution frame, PrimSig s, Map<Sig,List<Tuple>> nexts, UniqueNameGenerator un) throws Err {
if (s==null) {
for(ExprVar sk:frame.skolems) un.seen(sk.label);
// Store up the skolems
List<Object> skolems = new ArrayList<Object>();
for(Map.Entry<Relation,Type> e: frame.rel2type.entrySet()) {
Relation r = e.getKey(); if (!frame.eval.instance().contains(r)) continue;
Type t = e.getValue(); if (t.arity() > r.arity()) continue; // Something is wrong; let's skip it
while (t.arity() < r.arity()) t = UNIV.type().product(t);
String n = Util.tail(r.name());
while(n.length()>0 && n.charAt(0)=='$') n = n.substring(1);
skolems.add(n);
skolems.add(t);
skolems.add(r);
}
// Find all suitable "next" or "prev" relations
nexts = new LinkedHashMap<Sig,List<Tuple>>();
for(Sig sig:frame.sigs) for(Field f: sig.getFields()) if (f.label.compareToIgnoreCase("next")==0) {
List<List<PrimSig>> fold = f.type().fold();
if (fold.size()==1) {
List<PrimSig> t = fold.get(0);
if (t.size()==3 && t.get(0).isOne!=null && t.get(1)==t.get(2) && !nexts.containsKey(t.get(1))) {
TupleSet set = frame.eval.evaluate(frame.a2k(t.get(1)));
if (set.size()<=1) continue;
TupleSet next = frame.eval.evaluate(frame.a2k(t.get(0)).join(frame.a2k(f)));
List<Tuple> test = isOrder(next, set);
if (test!=null) nexts.put(t.get(1), test);
} else if (t.size()==2 && t.get(0)==t.get(1) && !nexts.containsKey(t.get(0))) {
TupleSet set = frame.eval.evaluate(frame.a2k(t.get(0)));
if (set.size()<=1) continue;
TupleSet next = frame.eval.evaluate(frame.a2k(f));
List<Tuple> test = isOrder(next, set);
if (test!=null) nexts.put(t.get(1), test);
}
}
}
for(Sig sig:frame.sigs) for(Field f: sig.getFields()) if (f.label.compareToIgnoreCase("prev")==0) {
List<List<PrimSig>> fold = f.type().fold();
if (fold.size()==1) {
List<PrimSig> t = fold.get(0);
if (t.size()==3 && t.get(0).isOne!=null && t.get(1)==t.get(2) && !nexts.containsKey(t.get(1))) {
TupleSet set = frame.eval.evaluate(frame.a2k(t.get(1)));
if (set.size()<=1) continue;
TupleSet next = frame.eval.evaluate(frame.a2k(t.get(0)).join(frame.a2k(f)).transpose());
List<Tuple> test = isOrder(next, set);
if (test!=null) nexts.put(t.get(1), test);
} else if (t.size()==2 && t.get(0)==t.get(1) && !nexts.containsKey(t.get(0))) {
TupleSet set = frame.eval.evaluate(frame.a2k(t.get(0)));
if (set.size()<=1) continue;
TupleSet next = frame.eval.evaluate(frame.a2k(f).transpose());
List<Tuple> test = isOrder(next, set);
if (test!=null) nexts.put(t.get(1), test);
}
}
}
// Assign atom->name and atom->MostSignificantSig
for(Tuple t:frame.eval.evaluate(Relation.INTS)) { frame.atom2sig.put(t.atom(0), SIGINT); }
for(Tuple t:frame.eval.evaluate(KK_SEQIDX)) { frame.atom2sig.put(t.atom(0), SEQIDX); }
for(Tuple t:frame.eval.evaluate(KK_STRING)) { frame.atom2sig.put(t.atom(0), STRING); }
for(Sig sig:frame.sigs) if (sig instanceof PrimSig && !sig.builtin && ((PrimSig)sig).isTopLevel()) rename(frame, (PrimSig)sig, nexts, un);
// These are redundant atoms that were not chosen to be in the final instance
int unused=0;
for(Tuple tuple:frame.eval.evaluate(Relation.UNIV)) {
Object atom = tuple.atom(0);
if (!frame.atom2sig.containsKey(atom)) { frame.atom2name.put(atom, "unused"+unused); unused++; }
}
// Add the skolems
for(int num=skolems.size(), i=0; i<num-2; i=i+3) {
String n = (String) skolems.get(i);
while(n.length()>0 && n.charAt(0)=='$') n=n.substring(1);
Type t = (Type) skolems.get(i+1);
Relation r = (Relation) skolems.get(i+2);
frame.addSkolem(un.make("$"+n), t, r);
}
return;
}
for(PrimSig c: s.children()) rename(frame, c, nexts, un);
String signame = un.make(s.label.startsWith("this/") ? s.label.substring(5) : s.label);
List<Tuple> list = new ArrayList<Tuple>();
for(Tuple t: frame.eval.evaluate(frame.a2k(s))) list.add(t);
List<Tuple> order = nexts.get(s);
if (order!=null && order.size()==list.size() && order.containsAll(list)) { list=order; }
int i = 0;
for(Tuple t: list) {
if (frame.atom2sig.containsKey(t.atom(0))) continue; // This means one of the subsig has already claimed this atom.
String x = signame + "$" + i;
i++;
frame.atom2sig.put(t.atom(0), s);
frame.atom2name.put(t.atom(0), x);
ExprVar v = ExprVar.make(null, x, s.type());
TupleSet ts = t.universe().factory().range(t, t);
Relation r = Relation.unary(x);
frame.eval.instance().add(r, ts);
frame.a2k.put(v, r);
frame.atoms.add(v);
}
}
//===================================================================================================//
/** Solve for the solution if not solved already; if cmd==null, we will simply use the lowerbound of each relation as its value. */
A4Solution solve(final A4Reporter rep, Command cmd, Simplifier simp, boolean tryBookExamples) throws Err, IOException {
// If already solved, then return this object as is
if (solved) return this;
// If cmd==null, then all four arguments are ignored, and we simply use the lower bound of each relation
if (cmd==null) {
Instance inst = new Instance(bounds.universe());
for(int max=max(), i=min(); i<=max; i++) {
Tuple it = factory.tuple(""+i);
inst.add(i, factory.range(it, it));
}
for(Relation r: bounds.relations()) inst.add(r, bounds.lowerBound(r));
eval = new Evaluator(inst, solver.options());
rename(this, null, null, new UniqueNameGenerator());
solved();
return this;
}
// Otherwise, prepare to do the solve...
final A4Options opt = originalOptions;
long time = System.currentTimeMillis();
rep.debug("Simplifying the bounds...\n");
if (simp!=null && formulas.size()>0 && !simp.simplify(rep, this, formulas)) addFormula(Formula.FALSE, Pos.UNKNOWN);
rep.translate(opt.solver.id(), bitwidth, maxseq, solver.options().skolemDepth(), solver.options().symmetryBreaking());
Formula fgoal = Formula.and(formulas);
rep.debug("Generating the solution...\n");
kEnumerator = null;
Solution sol = null;
final Reporter oldReporter = solver.options().reporter();
final boolean solved[] = new boolean[]{true};
solver.options().setReporter(new AbstractReporter() { // Set up a reporter to catch the type+pos of skolems
@Override public void skolemizing(Decl decl, Relation skolem, List<Decl> predecl) {
try {
Type t=kv2typepos(decl.variable()).a;
if (t==Type.EMPTY) return;
for(int i=(predecl==null ? -1 : predecl.size()-1); i>=0; i--) {
Type pp=kv2typepos(predecl.get(i).variable()).a;
if (pp==Type.EMPTY) return;
t=pp.product(t);
}
kr2type(skolem, t);
} catch(Throwable ex) { } // Exception here is not fatal
}
@Override public void solvingCNF(int primaryVars, int vars, int clauses) {
if (solved[0]) return; else solved[0]=true; // initially solved[0] is true, so we won't report the # of vars/clauses
if (rep!=null) rep.solve(primaryVars, vars, clauses);
}
});
if (!opt.solver.equals(SatSolver.CNF) && !opt.solver.equals(SatSolver.KK) && tryBookExamples) { // try book examples
A4Reporter r = "yes".equals(System.getProperty("debug")) ? rep : null;
try { sol = BookExamples.trial(r, this, fgoal, solver, cmd.check); } catch(Throwable ex) { sol = null; }
}
solved[0] = false; // this allows the reporter to report the # of vars/clauses
for(Relation r: bounds.relations()) { formulas.add(r.eq(r)); } // Without this, kodkod refuses to grow unmentioned relations
fgoal = Formula.and(formulas);
// Now pick the solver and solve it!
if (opt.solver.equals(SatSolver.KK)) {
File tmpCNF = File.createTempFile("tmp", ".java", new File(opt.tempDirectory));
String out = tmpCNF.getAbsolutePath();
Util.writeAll(out, debugExtractKInput());
rep.resultCNF(out);
return null;
}
if (opt.solver.equals(SatSolver.CNF)) {
File tmpCNF = File.createTempFile("tmp", ".cnf", new File(opt.tempDirectory));
String out = tmpCNF.getAbsolutePath();
solver.options().setSolver(WriteCNF.factory(out));
try { sol = solver.solve(fgoal, bounds); } catch(WriteCNF.WriteCNFCompleted ex) { rep.resultCNF(out); return null; }
// The formula is trivial (otherwise, it would have thrown an exception)
// Since the user wants it in CNF format, we manually generate a trivially satisfiable (or unsatisfiable) CNF file.
Util.writeAll(out, sol.instance()!=null ? "p cnf 1 1\n1 0\n" : "p cnf 1 2\n1 0\n-1 0\n");
rep.resultCNF(out);
return null;
}
if (solver.options().solver()==SATFactory.ZChaffMincost || !solver.options().solver().incremental()) {
if (sol==null) sol = solver.solve(fgoal, bounds);
} else {
kEnumerator = new Peeker<Solution>(solver.solveAll(fgoal, bounds));
if (sol==null) sol = kEnumerator.next();
}
if (!solved[0]) rep.solve(0, 0, 0);
final Instance inst = sol.instance();
// To ensure no more output during SolutionEnumeration
solver.options().setReporter(oldReporter);
// If unsatisfiable, then retreive the unsat core if desired
if (inst==null && solver.options().solver()==SATFactory.MiniSatProver) {
try {
lCore = new LinkedHashSet<Node>();
Proof p = sol.proof();
if (sol.outcome()==UNSATISFIABLE) {
// only perform the minimization if it was UNSATISFIABLE, rather than TRIVIALLY_UNSATISFIABLE
int i = p.highLevelCore().size();
rep.minimizing(cmd, i);
if (opt.coreMinimization==0) try { p.minimize(new RCEStrategy(p.log())); } catch(Throwable ex) {}
if (opt.coreMinimization==1) try { p.minimize(new HybridStrategy(p.log())); } catch(Throwable ex) {}
rep.minimized(cmd, i, p.highLevelCore().size());
}
for(Iterator<TranslationRecord> it=p.core(); it.hasNext();) {
Object n=it.next().node();
if (n instanceof Formula) lCore.add((Formula)n);
}
Map<Formula,Node> map = p.highLevelCore();
hCore = new LinkedHashSet<Node>(map.keySet());
hCore.addAll(map.values());
} catch(Throwable ex) {
lCore = hCore = null;
}
}
// If satisfiable, then add/rename the atoms and skolems
if (inst!=null) {
eval = new Evaluator(inst, solver.options());
rename(this, null, null, new UniqueNameGenerator());
}
// report the result
solved();
time = System.currentTimeMillis() - time;
if (inst!=null) rep.resultSAT(cmd, time, this); else rep.resultUNSAT(cmd, time, this);
return this;
}
public Formula makeFormula(A4Reporter rep, Simplifier simp) throws Err {
if (simp!=null && formulas.size()>0 && !simp.simplify(rep, this, formulas)) addFormula(Formula.FALSE, Pos.UNKNOWN);
ArrayList<Formula> tmpFormulas = new ArrayList<Formula>(formulas);
for(Relation r: bounds.relations()) { tmpFormulas.add(r.eq(r)); } // Without this, kodkod refuses to grow unmentioned relations
Formula fgoal = Formula.and(tmpFormulas);
return fgoal;
}
//===================================================================================================//
/** This caches the toString() output. */
private String toStringCache = null;
/** Dumps the Kodkod solution into String. */
@Override public String toString() {
if (!solved) return "---OUTCOME---\nUnknown.\n";
if (eval == null) return "---OUTCOME---\nUnsatisfiable.\n";
String answer = toStringCache;
if (answer != null) return answer;
Instance sol = eval.instance();
StringBuilder sb = new StringBuilder();
sb.append("---INSTANCE---\n" + "integers={");
boolean firstTuple = true;
for(IndexedEntry<TupleSet> e:sol.intTuples()) {
if (firstTuple) firstTuple=false; else sb.append(", ");
// No need to print e.index() since we've ensured the Int atom's String representation is always equal to ""+e.index()
Object atom = e.value().iterator().next().atom(0);
sb.append(atom2name(atom));
}
sb.append("}\n");
try {
for(Sig s:sigs) {
sb.append(s.label).append("=").append(eval(s)).append("\n");
for(Field f:s.getFields()) sb.append(s.label).append("<:").append(f.label).append("=").append(eval(f)).append("\n");
}
for(ExprVar v:skolems) {
sb.append("skolem ").append(v.label).append("=").append(eval(v)).append("\n");
}
return toStringCache = sb.toString();
} catch(Err er) {
return toStringCache = ("<Evaluator error occurred: "+er+">");
}
}
//===================================================================================================//
/** If nonnull, it caches the result of calling "next()". */
private A4Solution nextCache = null;
/** If this solution is UNSAT, return itself; else return the next solution (which could be SAT or UNSAT).
* @throws ErrorAPI if the solver was not an incremental solver
*/
public A4Solution next() throws Err {
if (!solved) throw new ErrorAPI("This solution is not yet solved, so next() is not allowed.");
if (eval==null) return this;
if (nextCache==null) nextCache=new A4Solution(this);
return nextCache;
}
/** Returns true if this solution was generated by an incremental SAT solver. */
public boolean isIncremental() { return kEnumerator!=null; }
//===================================================================================================//
/** The low-level unsat core; null if it is not available. */
private LinkedHashSet<Node> lCore = null;
/** This caches the result of lowLevelCore(). */
private Set<Pos> lCoreCache = null;
/** If this solution is unsatisfiable and its unsat core is available, then return the core; else return an empty set. */
public Set<Pos> lowLevelCore() {
if (lCoreCache!=null) return lCoreCache;
Set<Pos> ans1 = new LinkedHashSet<Pos>();
if (lCore!=null) for(Node f: lCore) {
Object y = k2pos(f);
if (y instanceof Pos) ans1.add( (Pos)y ); else if (y instanceof Expr) ans1.add( ((Expr)y).span() );
}
return lCoreCache = Collections.unmodifiableSet(ans1);
}
//===================================================================================================//
/** The high-level unsat core; null if it is not available. */
private LinkedHashSet<Node> hCore = null;
/** This caches the result of highLevelCore(). */
private Pair<Set<Pos>,Set<Pos>> hCoreCache = null;
/** If this solution is unsatisfiable and its unsat core is available, then return the core; else return an empty set. */
public Pair<Set<Pos>,Set<Pos>> highLevelCore() {
if (hCoreCache!=null) return hCoreCache;
Set<Pos> ans1 = new LinkedHashSet<Pos>(), ans2 = new LinkedHashSet<Pos>();
if (hCore!=null) for(Node f: hCore) {
Object x = k2pos(f);
if (x instanceof Pos) {
// System.out.println("F: "+f+" at "+x+"\n"); System.out.flush();
ans1.add((Pos)x);
} else if (x instanceof Expr) {
Expr expr = (Expr)x;
Pos p = ((Expr)x).span();
ans1.add(p);
// System.out.println("F: "+f+" by "+p.x+","+p.y+"->"+p.x2+","+p.y2+" for "+x+"\n\n"); System.out.flush();
for(Func func: expr.findAllFunctions()) ans2.add(func.getBody().span());
}
}
return hCoreCache = new Pair<Set<Pos>,Set<Pos>>(Collections.unmodifiableSet(ans1), Collections.unmodifiableSet(ans2));
}
//===================================================================================================//
/** Helper method to write out a full XML file. */
public void writeXML(String filename) throws Err {
writeXML(filename, null, null);
}
/** Helper method to write out a full XML file. */
public void writeXML(String filename, Iterable<Func> macros) throws Err {
writeXML(filename, macros, null);
}
/** Helper method to write out a full XML file. */
public void writeXML(String filename, Iterable<Func> macros, Map<String,String> sourceFiles) throws Err {
PrintWriter out=null;
try {
out=new PrintWriter(filename,"UTF-8");
writeXML(out, macros, sourceFiles);
if (!Util.close(out)) throw new ErrorFatal("Error writing the solution XML file.");
} catch(IOException ex) {
Util.close(out);
throw new ErrorFatal("Error writing the solution XML file.", ex);
}
}
/** Helper method to write out a full XML file. */
public void writeXML(A4Reporter rep, String filename, Iterable<Func> macros, Map<String,String> sourceFiles) throws Err {
PrintWriter out=null;
try {
out=new PrintWriter(filename,"UTF-8");
writeXML(rep, out, macros, sourceFiles);
if (!Util.close(out)) throw new ErrorFatal("Error writing the solution XML file.");
} catch(IOException ex) {
Util.close(out);
throw new ErrorFatal("Error writing the solution XML file.", ex);
}
}
/** Helper method to write out a full XML file. */
public void writeXML(PrintWriter writer, Iterable<Func> macros, Map<String,String> sourceFiles) throws Err {
A4SolutionWriter.writeInstance(null, this, writer, macros, sourceFiles);
if (writer.checkError()) throw new ErrorFatal("Error writing the solution XML file.");
}
/** Helper method to write out a full XML file. */
public void writeXML(A4Reporter rep, PrintWriter writer, Iterable<Func> macros, Map<String,String> sourceFiles) throws Err {
A4SolutionWriter.writeInstance(rep, this, writer, macros, sourceFiles);
if (writer.checkError()) throw new ErrorFatal("Error writing the solution XML file.");
}
}