/*
* Copyright (C) 2009-2012 University of Freiburg
*
* This file is part of SMTInterpol.
*
* SMTInterpol is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* SMTInterpol is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with SMTInterpol. If not, see <http://www.gnu.org/licenses/>.
*/
package de.uni_freiburg.informatik.ultimate.smtinterpol.convert;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import de.uni_freiburg.informatik.ultimate.logic.AnnotatedTerm;
import de.uni_freiburg.informatik.ultimate.logic.Annotation;
import de.uni_freiburg.informatik.ultimate.logic.ApplicationTerm;
import de.uni_freiburg.informatik.ultimate.logic.ConstantTerm;
import de.uni_freiburg.informatik.ultimate.logic.FormulaUnLet;
import de.uni_freiburg.informatik.ultimate.logic.FunctionSymbol;
import de.uni_freiburg.informatik.ultimate.logic.NonRecursive;
import de.uni_freiburg.informatik.ultimate.logic.QuantifiedFormula;
import de.uni_freiburg.informatik.ultimate.logic.Rational;
import de.uni_freiburg.informatik.ultimate.logic.SMTLIBException;
import de.uni_freiburg.informatik.ultimate.logic.Sort;
import de.uni_freiburg.informatik.ultimate.logic.Term;
import de.uni_freiburg.informatik.ultimate.logic.TermTransformer;
import de.uni_freiburg.informatik.ultimate.logic.Theory;
import de.uni_freiburg.informatik.ultimate.smtinterpol.proof.IProofTracker;
import de.uni_freiburg.informatik.ultimate.smtinterpol.proof.ProofConstants;
import de.uni_freiburg.informatik.ultimate.util.datastructures.UnifyHash;
/**
* Build a representation of the formula where only not, or, ite and =/2 are
* present. Linear arithmetic terms are converted into SMTAffineTerms. We
* normalize quantifiers to universal quantifiers. Additionally, this term
* transformer removes all annotations from the formula.
* @author Jochen Hoenicke, Juergen Christ
*/
public class TermCompiler extends TermTransformer {
private boolean mBy0Seen = false;
private Map<Term, Set<String>> mNames;
private IProofTracker mTracker;
private Utils mUtils;
private final FormulaUnLet mUnletter = new FormulaUnLet();
private final UnifyHash<SMTAffineTerm> mAffineUnifier
= new UnifyHash<SMTAffineTerm>();
public void setProofTracker(IProofTracker tracker) {
mTracker = tracker;
mUtils = new Utils(tracker);
}
public void setAssignmentProduction(boolean on) {
if (on) {
mNames = new HashMap<Term, Set<String>>();
} else {
mNames = null;
}
}
public Map<Term, Set<String>> getNames() {
return mNames;
}
@Override
public void convert(Term term) {
if (term instanceof ApplicationTerm) {
final ApplicationTerm appTerm = (ApplicationTerm) term;
final FunctionSymbol fsym = appTerm.getFunction();
if (fsym.isModelValue()) {
throw new SMTLIBException("Model values not allowed in input");
}
final Term[] params = appTerm.getParameters();
if (fsym.isLeftAssoc() && params.length > 2) {
final Theory theory = appTerm.getTheory();
if (fsym == theory.mAnd || fsym == theory.mOr) {
// We keep n-ary and/or
enqueueWalker(new BuildApplicationTerm(appTerm));
pushTerms(params);
return;
}
// m_Tracker.expand(appTerm);
enqueueWalker(new RewriteAdder(appTerm, null));
final BuildApplicationTerm dummy = new BuildApplicationTerm(
theory.term(fsym, params[0], params[1]));
for (int i = params.length - 1; i > 0; i--) {
enqueueWalker(dummy);
pushTerm(params[i]);
}
pushTerm(params[0]);
return;
}
if (fsym.isRightAssoc() && params.length > 2) {
final Theory theory = appTerm.getTheory();
if (fsym == theory.mImplies) {
// We keep n-ary implies
enqueueWalker(new BuildApplicationTerm(appTerm));
pushTerms(params);
return;
}
// m_Tracker.expand(appTerm);
enqueueWalker(new RewriteAdder(appTerm, null));
final BuildApplicationTerm dummy = new BuildApplicationTerm(
theory.term(fsym, params[params.length - 2],
params[params.length - 1]));
for (int i = params.length - 1; i > 0; i--) {
enqueueWalker(dummy);
}
pushTerms(params);
return;
}
if (fsym.isChainable() && params.length > 2
&& !fsym.getName().equals("=")) {
// m_Tracker.expand(appTerm);
enqueueWalker(new RewriteAdder(appTerm, null));
final Theory theory = appTerm.getTheory();
final BuildApplicationTerm and = new BuildApplicationTerm(
theory.term("and", theory.mTrue, theory.mTrue));
final BuildApplicationTerm dummy = new BuildApplicationTerm(
theory.term(fsym, params[0], params[1]));
for (int i = params.length - 1; i > 1; i--) {
enqueueWalker(and);
enqueueWalker(dummy);
pushTerm(params[i]);
pushTerm(params[i - 1]);
}
enqueueWalker(dummy);
pushTerm(params[1]);
pushTerm(params[0]);
return;
}
} else if (term instanceof ConstantTerm) {
final SMTAffineTerm res = SMTAffineTerm.create(term);
mTracker.normalized((ConstantTerm) term, res);
setResult(res);
return;
}
super.convert(term);
}
/**
* Add the rewrite that expands a function definition to the beginning
* of the rewrite list. Since this definition must come before the
* definition used inside, we use a Walker to add the definition after
* we transformed the expanded term.
*/
private static class RewriteAdder implements Walker {
private final ApplicationTerm mAppTerm;
private final Term mExpanded;
public RewriteAdder(ApplicationTerm term, Term expanded) {
mAppTerm = term;
mExpanded = expanded;
}
@Override
public void walk(NonRecursive engine) {
final TermCompiler transformer = (TermCompiler) engine;
if (mExpanded == null) {
transformer.mTracker.expand(mAppTerm);
} else {
transformer.mTracker.expandDef(mAppTerm, mExpanded);
}
}
@Override
public String toString() {
return "addrewrite " + mAppTerm.getFunction().getApplicationString();
}
}
@Override
public void convertApplicationTerm(ApplicationTerm appTerm, Term[] args) {
final FunctionSymbol fsym = appTerm.getFunction();
final Theory theory = appTerm.getTheory();
if (fsym.getDefinition() != null) {
final Term definition =
fsym.getDefinitionVars().length == 0
? fsym.getDefinition() : theory.let(
fsym.getDefinitionVars(), appTerm.getParameters(),
fsym.getDefinition());
final Term expanded = mUnletter.unlet(definition);
enqueueWalker(new RewriteAdder(appTerm, expanded));
pushTerm(expanded);
return;
}
final Sort[] paramSorts = fsym.getParameterSorts();
Term[] origArgs = null;
if (theory.getLogic().isIRA()
&& paramSorts.length == 2
&& paramSorts[0].getName().equals("Real")
&& paramSorts[1] == paramSorts[0]) {
// IRA-Hack
if (args == appTerm.getParameters()) {
args = args.clone();
}
for (int i = 0; i < args.length; i++) {
if (args[i].getSort().getName().equals("Int")) {
if (origArgs == null) {
origArgs = mTracker.prepareIRAHack(args);
}
args[i] = SMTAffineTerm.create(args[i])
.typecast(paramSorts[0]);
}
}
}
// for (int i = 0; i < args.length; ++i) {
// if (args[i] instanceof SMTAffineTerm) {
// args[i] = ((SMTAffineTerm) args[i]).normalize(this);
// }
// }
if (origArgs != null) {
mTracker.desugar(appTerm, origArgs, args);
}
if (fsym.isIntern()) {
if (fsym == theory.mNot) {
setResult(mUtils.createNot(args[0]));
return;
}
if (fsym == theory.mAnd) {
setResult(mUtils.createAnd(args));
return;
}
if (fsym == theory.mOr) {
setResult(mUtils.createOr(args));
return;
}
if (fsym == theory.mXor) {
mTracker.removeConnective(args,
null, ProofConstants.RW_XOR_TO_DISTINCT);
setResult(mUtils.createDistinct(args));
return;
}
if (fsym == theory.mImplies) {
mTracker.removeConnective(
args, null, ProofConstants.RW_IMP_TO_OR);
final Term[] tmp = new Term[args.length];
// We move the conclusion in front (see Simplify tech report)
for (int i = 1; i < args.length; ++i) {
tmp[i] = mUtils.createNot(args[i - 1]);
}
tmp[0] = args[args.length - 1];
setResult(mUtils.createOr(tmp));
return;
}
if (fsym.getName().equals("ite")) {
setResult(mUtils.createIte(args[0], args[1], args[2]));
return;
}
if (fsym.getName().equals("=")) {
setResult(mUtils.createEq(args));
return;
}
if (fsym.getName().equals("distinct")) {
setResult(mUtils.createDistinct(args));
return;
}
if (fsym.getName().equals("<=")) {
final Term res = SMTAffineTerm.create(args[0])
.add(SMTAffineTerm.create(Rational.MONE, args[1]))
.normalize(this);
mTracker.removeConnective(
args, res, ProofConstants.RW_LEQ_TO_LEQ0);
setResult(mUtils.createLeq0(res));
return;
}
if (fsym.getName().equals(">=")) {
final Term res = SMTAffineTerm.create(args[1])
.add(SMTAffineTerm.create(Rational.MONE, args[0]))
.normalize(this);
mTracker.removeConnective(
args, res, ProofConstants.RW_GEQ_TO_LEQ0);
setResult(mUtils.createLeq0(res));
return;
}
if (fsym.getName().equals(">")) {
final Term res = SMTAffineTerm.create(args[0])
.add(SMTAffineTerm.create(Rational.MONE, args[1]))
.normalize(this);
mTracker.removeConnective(
args, res, ProofConstants.RW_GT_TO_LEQ0);
setResult(mUtils.createNot(mUtils.createLeq0(res)));
return;
}
if (fsym.getName().equals("<")) {
final Term res = SMTAffineTerm.create(args[1])
.add(SMTAffineTerm.create(Rational.MONE, args[0]))
.normalize(this);
mTracker.removeConnective(
args, res, ProofConstants.RW_LT_TO_LEQ0);
setResult(mUtils.createNot(mUtils.createLeq0(res)));
return;
}
if (fsym.getName().equals("+")) {
final Term res = SMTAffineTerm.create(args[0])
.add(SMTAffineTerm.create(args[1]))
.normalize(this);
mTracker.sum(fsym, args, res);
setResult(res);
return;
} else if (fsym.getName().equals("-") && paramSorts.length == 2) {
final Term res = SMTAffineTerm.create(args[0])
.add(SMTAffineTerm.create(Rational.MONE, args[1]))
.normalize(this);
mTracker.sum(fsym, args, res);
setResult(res);
return;
} else if (fsym.getName().equals("*")) {
final SMTAffineTerm arg0 = SMTAffineTerm.create(args[0]);
final SMTAffineTerm arg1 = SMTAffineTerm.create(args[1]);
SMTAffineTerm res;
if (arg0.isConstant()) {
res = arg1.mul(arg0.getConstant());
} else if (arg1.isConstant()) {
res = arg0.mul(arg1.getConstant());
} else {
throw new UnsupportedOperationException("Unsupported non-linear arithmetic");
}
final Term result = res.normalize(this);
mTracker.sum(fsym, args, result);
setResult(result);
return;
} else if (fsym.getName().equals("/")) {
final SMTAffineTerm arg0 = SMTAffineTerm.create(args[0]);
final SMTAffineTerm arg1 = SMTAffineTerm.create(args[1]);
if (arg1.isConstant()) {
if (arg1.getConstant().equals(Rational.ZERO)) {
mBy0Seen = true;
setResult(theory.term("@/0", arg0));
} else {
final Term res = arg0.mul(arg1.getConstant().inverse())
.normalize(this);
mTracker.sum(fsym, args, res);
setResult(res);
}
return;
} else {
throw new UnsupportedOperationException("Unsupported non-linear arithmetic");
}
} else if (fsym.getName().equals("div")) {
final SMTAffineTerm arg0 = SMTAffineTerm.create(args[0]);
final SMTAffineTerm arg1 = SMTAffineTerm.create(args[1]);
final Term narg0 = arg0.normalize(this);
final Term narg1 = arg1.normalize(this);
final Rational divisor = arg1.getConstant();
if (arg1.isConstant() && divisor.isIntegral()) {
if (divisor.equals(Rational.ZERO)) {
mBy0Seen = true;
setResult(theory.term("@div0", narg0));
} else if (divisor.equals(Rational.ONE)) {
mTracker.div(narg0, narg1, narg0,
ProofConstants.RW_DIV_ONE);
setResult(narg0);
} else if (divisor.equals(Rational.MONE)) {
final Term res = arg0.negate().normalize(this);
mTracker.div(narg0, narg1, res,
ProofConstants.RW_DIV_MONE);
setResult(res);
} else if (arg0.isConstant()) {
// We have (div c0 c1) ==> constDiv(c0, c1)
final Rational div = constDiv(arg0.getConstant(),
arg1.getConstant());
final Term res = SMTAffineTerm.create(
div.toTerm(arg0.getSort())).normalize(this);
mTracker.div(narg0, narg1, res,
ProofConstants.RW_DIV_CONST);
setResult(res);
} else {
setResult(theory.term(fsym, narg0, narg1));
}
return;
} else {
throw new UnsupportedOperationException("Unsupported non-linear arithmetic");
}
} else if (fsym.getName().equals("mod")) {
final SMTAffineTerm arg0 = SMTAffineTerm.create(args[0]);
final SMTAffineTerm arg1 = SMTAffineTerm.create(args[1]);
final Term narg0 = arg0.normalize(this);
final Term narg1 = arg1.normalize(this);
final Rational divisor = arg1.getConstant();
if (arg1.isConstant() && divisor.isIntegral()) {
if (divisor.equals(Rational.ZERO)) {
mBy0Seen = true;
setResult(theory.term("@mod0", narg0));
} else if (divisor.equals(Rational.ONE)) {
// (mod x 1) == 0
final Term res = SMTAffineTerm.create(
Rational.ZERO.toTerm(arg0.getSort()))
.normalize(this);
mTracker.mod(narg0, narg1, res,
ProofConstants.RW_MODULO_ONE);
setResult(res);
} else if (divisor.equals(Rational.MONE)) {
// (mod x -1) == 0
final Term res = SMTAffineTerm.create(
Rational.ZERO.toTerm(arg0.getSort()))
.normalize(this);
mTracker.mod(arg0, arg1, res,
ProofConstants.RW_MODULO_MONE);
setResult(res);
} else if (arg0.isConstant()) {
// We have (mod c0 c1) ==> c0 - c1 * constDiv(c0, c1)
final Rational c0 = arg0.getConstant();
final Rational c1 = arg1.getConstant();
final Rational mod = c0.sub(constDiv(c0, c1).mul(c1));
final Term res = SMTAffineTerm.create(
mod.toTerm(arg0.getSort())).normalize(this);
mTracker.mod(arg0, arg1, res,
ProofConstants.RW_MODULO_CONST);
setResult(res);
} else {
final SMTAffineTerm ydiv =
SMTAffineTerm.create(theory.term(
"div", arg0, arg1)).
mul(arg1.getConstant());
final Term res = arg0.add(ydiv.negate()).normalize(this);
setResult(res);
mTracker.modulo(appTerm, res);
}
return;
} else {
throw new UnsupportedOperationException("Unsupported non-linear arithmetic");
}
} else if (fsym.getName().equals("-") && paramSorts.length == 1) {
final Term res = SMTAffineTerm.create(args[0]).negate()
.normalize(this);
mTracker.sum(fsym, args, res);
setResult(res);
return;
} else if (fsym.getName().equals("to_real")) {
final SMTAffineTerm arg = SMTAffineTerm.create(args[0]);
final Term res = arg.typecast(fsym.getReturnSort()).normalize(this);
setResult(res);
if (arg.isConstant()) {
mTracker.toReal(arg, res);
}
return;
} else if (fsym.getName().equals("to_int")) {
// We don't convert to_int here but defer it to the clausifier
// But we simplify it here...
final SMTAffineTerm arg0 = SMTAffineTerm.create(args[0]);
if (arg0.isConstant()) {
final Term res = SMTAffineTerm.create(
arg0.getConstant().floor().toTerm(
fsym.getReturnSort())).normalize(this);
mTracker.toInt(arg0, res);
setResult(res);
return;
}
} else if (fsym.getName().equals("divisible")) {
final SMTAffineTerm arg0 = SMTAffineTerm.create(args[0]);
final SMTAffineTerm arg1 = SMTAffineTerm.create(
Rational.valueOf(fsym.getIndices()[0], BigInteger.ONE),
arg0.getSort());
Term res;
if (arg1.getConstant().equals(Rational.ONE)) {
res = theory.mTrue;
} else if (arg0.isConstant()) {
final Rational c0 = arg0.getConstant();
final Rational c1 = arg1.getConstant();
final Rational mod = c0.sub(constDiv(c0, c1).mul(c1));
res = mod.equals(Rational.ZERO) ? theory.mTrue : theory.mFalse;
} else {
res = theory.term("=", arg0, SMTAffineTerm.create(
theory.term("div", arg0, arg1)).mul(arg1.getConstant())
.normalize(this));
}
setResult(res);
mTracker.divisible(appTerm.getFunction(), arg0, res);
return;
} else if (fsym.getName().equals("store")) {
final Term array = args[0];
final Term idx = args[1];
final Term nestedIdx = getArrayStoreIdx(array);
if (nestedIdx != null) {
// Check for store-over-store
final SMTAffineTerm diff = SMTAffineTerm.create(idx).add(
SMTAffineTerm.create(nestedIdx).negate());
if (diff.isConstant() && diff.getConstant().equals(
Rational.ZERO)) {
// Found store-over-store => ignore inner store
final ApplicationTerm appArray = (ApplicationTerm) array;
final Term result = theory.term(fsym,
appArray.getParameters()[0], args[1], args[2]);
mTracker.arrayRewrite(args, result,
ProofConstants.RW_STORE_OVER_STORE);
setResult(result);
return;
}
}
} else if (fsym.getName().equals("select")) {
final Term array = args[0];
final Term idx = args[1];
final Term nestedIdx = getArrayStoreIdx(array);
if (nestedIdx != null) {
// Check for select-over-store
final SMTAffineTerm diff = SMTAffineTerm.create(idx).add(
SMTAffineTerm.create(nestedIdx).negate());
if (diff.isConstant()) {
// Found select-over-store
final ApplicationTerm appArray = (ApplicationTerm) array;
if (diff.getConstant().equals(Rational.ZERO)) {
// => transform into value
final Term result = appArray.getParameters()[2];
mTracker.arrayRewrite(args, result,
ProofConstants.RW_SELECT_OVER_STORE);
setResult(result);
return;
} else { // Both indices are numerical and distinct.
// => transform into (select a idx)
final Term result = theory.term("select",
appArray.getParameters()[0], idx);
mTracker.arrayRewrite(args, result,
ProofConstants.RW_SELECT_OVER_STORE);
setResult(result);
return;
}
}
}
} else if (fsym.getName().equals("@undefined")) {
throw new SMTLIBException("Undefined value in input");
}
}
// not an intern function symbols
super.convertApplicationTerm(appTerm, args);
}
public final static Rational constDiv(Rational c0, Rational c1) {
final Rational div = c0.div(c1);
return c1.isNegative() ? div.ceil() : div.floor();
}
private final static Term getArrayStoreIdx(Term array) {
if (array instanceof ApplicationTerm) {
final ApplicationTerm appArray = (ApplicationTerm) array;
final FunctionSymbol arrayFunc = appArray.getFunction();
if (arrayFunc.isIntern() && arrayFunc.getName().equals("store")) {
// (store a i v)
return appArray.getParameters()[1];
}
}
return null;
}
@Override
public void postConvertQuantifier(QuantifiedFormula old, Term newBody) {
if (old.getQuantifier() == QuantifiedFormula.EXISTS) {
super.postConvertQuantifier(old, newBody);
} else {
// We should create (forall (x) (newBody x))
// This becomes (not (exists (x) (not (newBody x))))
final Term negNewBody = mUtils.createNot(newBody);
final Theory t = old.getTheory();
final Term res = t.term(t.mNot,
t.exists(old.getVariables(), negNewBody));
setResult(res);
}
}
@Override
public void postConvertAnnotation(AnnotatedTerm old,
Annotation[] newAnnots, Term newBody) {
if (mNames != null
&& newBody.getSort() == newBody.getTheory().getBooleanSort()) {
final Annotation[] oldAnnots = old.getAnnotations();
for (final Annotation annot : oldAnnots) {
if (annot.getKey().equals(":named")) {
Set<String> oldNames = mNames.get(newBody);
if (oldNames == null) {
oldNames = new HashSet<String>();
mNames.put(newBody, oldNames);
}
oldNames.add(annot.getValue().toString());
}
}
}
mTracker.strip(old);
setResult(newBody);
}
/**
* Get and reset the division-by-0 seen flag.
* @return The old division-by-0 seen flag.
*/
public boolean resetBy0Seen() {
final boolean old = mBy0Seen;
mBy0Seen = false;
return old;
}
public SMTAffineTerm unify(SMTAffineTerm affine) {
return mAffineUnifier.unify(affine);
}
}