/*
* Copyright (C) 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.util.LinkedHashSet;
import de.uni_freiburg.informatik.ultimate.logic.ApplicationTerm;
import de.uni_freiburg.informatik.ultimate.logic.ConstantTerm;
import de.uni_freiburg.informatik.ultimate.logic.FunctionSymbol;
import de.uni_freiburg.informatik.ultimate.logic.Rational;
import de.uni_freiburg.informatik.ultimate.logic.Term;
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;
/**
* Helper class that can be used by other term transformers to build partial
* formulas in our internal not-or-tree format.
* @author Juergen Christ
*/
public class Utils {
private final IProofTracker mTracker;
public Utils(IProofTracker tracker) {
mTracker = tracker;
}
/**
* Optimize nots. Transforms (not true) to false, (not false) to true, and
* remove double negation.
* @param arg Term to negate.
* @return Term equivalent to the negation of the input.
*/
public Term createNot(Term arg) {
final Theory theory = arg.getTheory();
if (arg == theory.mFalse) {
mTracker.negation(arg, theory.mTrue, ProofConstants.RW_NOT_SIMP);
return theory.mTrue;
}
if (arg == theory.mTrue) {
mTracker.negation(arg, theory.mFalse, ProofConstants.RW_NOT_SIMP);
return theory.mFalse;
}
if ((arg instanceof ApplicationTerm)
&& ((ApplicationTerm) arg).getFunction().getName().equals("not")) {
final Term res = ((ApplicationTerm) arg).getParameters()[0];
mTracker.negation(arg, res, ProofConstants.RW_NOT_SIMP);
return res;
}
return theory.term("not", arg);
}
public static Term createNotUntracked(Term arg) {
final Theory theory = arg.getTheory();
if (arg == theory.mFalse) {
return theory.mTrue;
}
if (arg == theory.mTrue) {
return theory.mFalse;
}
if ((arg instanceof ApplicationTerm)
&& ((ApplicationTerm) arg).getFunction().getName().equals("not")) {
return ((ApplicationTerm) arg).getParameters()[0];
}
return theory.term("not", arg);
}
/**
* Optimize ors. If true is found in the disjuncts, it is returned.
* Otherwise, we remove false, or disjuncts that occur more than once. The
* result might still be an n-ary or.
* @param args The disjuncts.
* @return Term equivalent to the disjunction of the disjuncts.
*/
public Term createOr(Term... args) {
final LinkedHashSet<Term> ctx = new LinkedHashSet<Term>();
final Theory theory = args[0].getTheory();
final Term trueTerm = theory.mTrue;
final Term falseTerm = theory.mFalse;
for (final Term t : args) {
if (t == trueTerm) {
mTracker.or(args, t, ProofConstants.RW_OR_TAUT);
return t;
}
if (t != falseTerm) {
if (ctx.contains(createNotUntracked(t))) {
mTracker.or(args, trueTerm, ProofConstants.RW_OR_TAUT);
return trueTerm;
}
ctx.add(t);
}
}
// Handle disjunctions of false
if (ctx.isEmpty()) {
mTracker.or(args, theory.mFalse, ProofConstants.RW_OR_SIMP);
return theory.mFalse;
}
// Handle simplifications to unary or
if (ctx.size() == 1) {
final Term res = ctx.iterator().next();
mTracker.or(args, res, ProofConstants.RW_OR_SIMP);
return res;
}
if (ctx.size() == args.length) {
return theory.term(theory.mOr, args);
}
final Term res = theory.term(theory.mOr, ctx.toArray(new Term[ctx.size()]));
mTracker.or(args, res, ProofConstants.RW_OR_SIMP);
return res;
}
public Term createLeq0(Term arg) {
if (arg instanceof SMTAffineTerm) {
final SMTAffineTerm at = (SMTAffineTerm) arg;
if (at.isConstant()) {
final Theory t = arg.getTheory();
if (at.getConstant().compareTo(Rational.ZERO) > 0) {
mTracker.leqSimp(at, t.mFalse, ProofConstants.RW_LEQ_FALSE);
return t.mFalse;
} else {
mTracker.leqSimp(at, t.mTrue, ProofConstants.RW_LEQ_TRUE);
return t.mTrue;
}
}
}
return arg.getTheory().term("<=", arg,
SMTAffineTerm.create(Rational.ZERO, arg.getSort()));
}
/**
* Simplify ite terms. This might destroy the ite if it is Boolean with
* at least one constant leaf, or if the leaves equal.
* @param cond Condition of the ite.
* @param trueBranch What should be true if the condition holds.
* @param falseBranch What should be true if the condition does not hold.
* @return Term equivalent to (ite cond trueBranch falseBranch).
*/
public Term createIte(Term cond, Term trueBranch, Term falseBranch) {
final Theory theory = cond.getTheory();
if (cond == theory.mTrue) {
mTracker.ite(cond, trueBranch, falseBranch, trueBranch,
ProofConstants.RW_ITE_TRUE);
return trueBranch;
}
if (cond == theory.mFalse) {
mTracker.ite(cond, trueBranch, falseBranch, falseBranch,
ProofConstants.RW_ITE_FALSE);
return falseBranch;
}
if (trueBranch == falseBranch) {
mTracker.ite(cond, trueBranch, falseBranch, trueBranch,
ProofConstants.RW_ITE_SAME);
return trueBranch;
}
if (trueBranch == theory.mTrue && falseBranch == theory.mFalse) {
mTracker.ite(cond, trueBranch, falseBranch, cond,
ProofConstants.RW_ITE_BOOL_1);
return cond;
}
if (trueBranch == theory.mFalse && falseBranch == theory.mTrue) {
mTracker.ite(cond, trueBranch, falseBranch, null,
ProofConstants.RW_ITE_BOOL_2);
return createNot(cond);
}
if (trueBranch == theory.mTrue) {
// No need for createOr since we are already sure that we cannot
// simplify further
final Term res = theory.term("or", cond, falseBranch);
mTracker.ite(cond, trueBranch, falseBranch, res,
ProofConstants.RW_ITE_BOOL_3);
return createOr(cond, falseBranch);
}
if (trueBranch == theory.mFalse) {
// /\ !cond falseBranch => !(\/ cond !falseBranch)
mTracker.ite(cond, trueBranch, falseBranch, null,
ProofConstants.RW_ITE_BOOL_4);
return createNot(createOr(cond, createNot(falseBranch)));
}
if (falseBranch == theory.mTrue) {
// => cond trueBranch => \/ !cond trueBranch
mTracker.ite(cond, trueBranch, falseBranch, null,
ProofConstants.RW_ITE_BOOL_5);
return createOr(createNot(cond), trueBranch);
}
if (falseBranch == theory.mFalse) {
// /\ cond trueBranch => !(\/ !cond !trueBranch)
mTracker.ite(cond, trueBranch, falseBranch, null,
ProofConstants.RW_ITE_BOOL_6);
return createNot(createOr(createNot(cond), createNot(trueBranch)));
}
return theory.term("ite", cond, trueBranch, falseBranch);
}
/**
* Optimize equalities. This function creates binary equalities out of
* n-ary equalities. First, we optimize the arguments of the equality by
* removing double entries, multiple constants, and transforms Boolean
* equalities to true, false, and, or or in case of constant parameters.
* @param args The arguments of the equality.
* @return A term equivalent to the equality of all input terms.
*/
public Term createEq(Term... args) {
final LinkedHashSet<Term> tmp = new LinkedHashSet<Term>();
final Theory theory = args[0].getTheory();
if (args[0].getSort().isNumericSort()) {
Rational lastConst = null;
for (final Term t : args) {
if (t instanceof ConstantTerm || t instanceof SMTAffineTerm) {
final SMTAffineTerm at = SMTAffineTerm.create(t);
if (at.isConstant()) {
if (lastConst == null) {
lastConst = at.getConstant();
} else if (!lastConst.equals(at.getConstant())) {
mTracker.equality(args, theory.mFalse,
ProofConstants.RW_CONST_DIFF);
return theory.mFalse;
}
}
}
tmp.add(t);
}
} else if (args[0].getSort() == theory.getBooleanSort()) {
// Idea: if we find false:
// - If we additionally find true: return false
// - Otherwise we have to negate all other occurrences
// if we only find true:
// - conjoin all elements
boolean foundTrue = false;
boolean foundFalse = false;
for (final Term t : args) {
if (t == theory.mTrue) {
foundTrue = true;
if (foundFalse) {
mTracker.equality(args, theory.mFalse,
ProofConstants.RW_TRUE_NOT_FALSE);
return theory.mFalse;
}
} else if (t == theory.mFalse) {
foundFalse = true;
if (foundTrue) {
mTracker.equality(args, theory.mFalse,
ProofConstants.RW_TRUE_NOT_FALSE);
return theory.mFalse;
}
} else {
tmp.add(t);
}
}
if (foundTrue) {
// take care of (= true true ... true)
if (tmp.isEmpty()) {
mTracker.equality(args, theory.mTrue,
ProofConstants.RW_EQ_SAME);
return theory.mTrue;
}
final Term[] tmpArgs = tmp.toArray(new Term[tmp.size()]);
mTracker.equality(args, tmpArgs, ProofConstants.RW_EQ_TRUE);
if (tmpArgs.length == 1) {
return tmpArgs[0];
}
return createAndInplace(tmpArgs);
}
if (foundFalse) {
if (tmp.isEmpty()) {
mTracker.equality(args, theory.mTrue,
ProofConstants.RW_EQ_SAME);
return theory.mTrue;
}
final Term[] tmpArgs = tmp.toArray(new Term[tmp.size()]);
mTracker.equality(args, tmpArgs, ProofConstants.RW_EQ_FALSE);
if (tmpArgs.length == 1) {
return createNot(tmpArgs[0]);
}
// take care of (= false false ... false)
return createNot(createOr(tmpArgs));
}
} else {
for (final Term t : args) {
tmp.add(t);
}
}
// We had (= a ... a)
if (tmp.size() == 1) {
mTracker.equality(args, theory.mTrue, ProofConstants.RW_EQ_SAME);
return theory.mTrue;
}
// Make binary
final Term[] tmpArray = tmp.size() == args.length
? args : tmp.toArray(new Term[tmp.size()]);
if (args != tmpArray) {
mTracker.equality(args, tmpArray, ProofConstants.RW_EQ_SIMP);
}
if (tmpArray.length == 2) {
return makeBinaryEq(tmpArray);
}
final Term[] conj = new Term[tmpArray.length - 1];
for (int i = 0; i < conj.length; ++i) {
conj[i] = theory.term("not",
makeBinaryEq(tmpArray[i], tmpArray[i + 1]));
}
final Term res = theory.term("not", theory.term("or", conj));
mTracker.equality(tmpArray, res, ProofConstants.RW_EQ_BINARY);
return res;
}
private Term storeRewrite(ApplicationTerm store, boolean arrayFirst) {
assert isStore(store) : "Not a store in storeRewrite";
final Theory t = store.getTheory();
// have (store a i v)
// produce (select a i) = v
final Term[] args = store.getParameters();
final Term result = t.term("=", t.term("select", args[0], args[1]), args[2]);
mTracker.storeRewrite(store, result, arrayFirst);
return result;
}
private boolean isStore(Term t) {
if (t instanceof ApplicationTerm) {
final FunctionSymbol fs = ((ApplicationTerm) t).getFunction();
return fs.isIntern() && fs.getName().equals("store");
}
return false;
}
/**
* Make a binary equality. Note that the precondition of this function
* requires the caller to ensure that the argument array contains only two
* terms.
*
* This function is used to detect store-idempotencies.
* @return A binary equality.
*/
private Term makeBinaryEq(Term... args) {
assert args.length == 2 : "Non-binary equality in makeBinaryEq";
if (args[0].getSort().isArraySort()) {
// Check store-rewrite
if (isStore(args[0])) {
final Term array = ((ApplicationTerm) args[0]).getParameters()[0];
if (args[1] == array) {
return storeRewrite((ApplicationTerm) args[0], false);
}
}
if (isStore(args[1])) {
final Term array = ((ApplicationTerm) args[1]).getParameters()[0];
if (args[0] == array) {
return storeRewrite((ApplicationTerm) args[1], true);
}
}
}
return args[0].getTheory().term("=", args);
}
/**
* Simplify distincts. At the moment, we remove distinct constructs and
* replace them by negated equalities. We optimize Boolean distincts, and
* transform non-Boolean distincts to false, if we have multiple times the
* same term.
* @param args Terms that should be distinct.
* @return A term equivalent to the arguments applied to the distinct
* function.
*/
public Term createDistinct(Term... args) {
final Theory theory = args[0].getTheory();
if (args[0].getSort() == theory.getBooleanSort()) {
if (args.length > 2) {
mTracker.distinct(args, theory.mFalse,
ProofConstants.RW_DISTINCT_BOOL);
return theory.mFalse;
}
final Term t0 = args[0];
final Term t1 = args[1];
if (t0 == t1) {
mTracker.distinct(args, theory.mFalse,
ProofConstants.RW_DISTINCT_SAME);
return theory.mFalse;
}
if (t0 == createNotUntracked(t1)) {
mTracker.distinct(args, theory.mTrue,
ProofConstants.RW_DISTINCT_NEG);
return theory.mTrue;
}
if (t0 == theory.mTrue) {
mTracker.distinct(args, null, ProofConstants.RW_DISTINCT_TRUE);
return createNot(t1);
}
if (t0 == theory.mFalse) {
mTracker.distinct(args, t1, ProofConstants.RW_DISTINCT_FALSE);
return t1;
}
if (t1 == theory.mTrue) {
mTracker.distinct(args, null, ProofConstants.RW_DISTINCT_TRUE);
return createNot(t0);
}
if (t1 == theory.mFalse) {
mTracker.distinct(args, t0, ProofConstants.RW_DISTINCT_FALSE);
return t0;
}
// Heuristics: Try to find an already negated term
if (isNegation(t0)) {
mTracker.distinctBoolEq(t0, t1, true);
return theory.term("=", createNot(t0), t1);
}
mTracker.distinctBoolEq(t0, t1, false);
return theory.term("=", t0, createNot(t1));
}
LinkedHashSet<Term> tmp = new LinkedHashSet<Term>();
for (final Term t : args) {
if (!tmp.add(t)) {
// We had (distinct a b a)
mTracker.distinct(args, theory.mFalse,
ProofConstants.RW_DISTINCT_SAME);
return theory.mFalse;
}
}
tmp = null;
if (args.length == 2) {
final Term res = theory.term("not", theory.term("=", args));
mTracker.distinct(args, res, ProofConstants.RW_DISTINCT_BINARY);
return res;
}
// We need n * (n - 1) / 2 conjuncts
final Term[] nconjs = new Term[args.length * (args.length - 1) / 2];
int pos = 0;
for (int i = 0; i < args.length - 1; ++i) {
for (int j = i + 1; j < args.length; ++j) {
nconjs[pos++] = theory.term("=", args[i], args[j]);
}
}
final Term res = theory.term("not", theory.term("or", nconjs));
mTracker.distinct(args, res, ProofConstants.RW_DISTINCT_BINARY);
return res;
// return theory.term("distinct", args);
}
public static boolean isNegation(Term t) {
if (t instanceof ApplicationTerm) {
return ((ApplicationTerm) t).getFunction() == t.getTheory().mNot;
}
return false;
}
public Term createAndInplace(Term... args) {
assert (args.length > 1) : "Invalid and in simplification";
mTracker.removeConnective(args, null, ProofConstants.RW_AND_TO_OR);
for (int i = 0; i < args.length; ++i) {
args[i] = createNot(args[i]);
}
return createNot(createOr(args));
}
public Term createAnd(Term... args) {
args = args.clone();
return createAndInplace(args);
}
}