/*
* Copyright (C) 2009-2013 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.logic.simplification;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import de.uni_freiburg.informatik.ultimate.logic.ApplicationTerm;
import de.uni_freiburg.informatik.ultimate.logic.FormulaUnLet;
import de.uni_freiburg.informatik.ultimate.logic.NonRecursive;
import de.uni_freiburg.informatik.ultimate.logic.QuotedObject;
import de.uni_freiburg.informatik.ultimate.logic.SMTLIBException;
import de.uni_freiburg.informatik.ultimate.logic.Script;
import de.uni_freiburg.informatik.ultimate.logic.Script.LBool;
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.TermVariable;
import de.uni_freiburg.informatik.ultimate.logic.Util;
import de.uni_freiburg.informatik.ultimate.util.PushPopChecker;
/**
* Simplify formulas, but keep their Boolean structure.
* Replace subformulas by true or false if this replacement leads to an
* equivalent formula.
* Based on the paper "Small Formulas for Large Programms: On-Line Constraint
* Simplification in Scalable Static Analysis" by Isil Dillig, Thomas Dillig
* and Alex Aiken.
* This implementation extends this approach to formulas which are not in NNF,
* contain "=>" and "ite".
*
* The new implementation is DAG-based an non-recursive. We collect contexts
*
* @author Matthias Heizmann, Jochen Hoenicke, Markus Pomrehn
*
*/
public class SimplifyDDA extends NonRecursive {
private static class TermInfo {
int mNumPredecessors;
int mSeen;
int mPrepared;
Term[] mContext;
Term mSimplified;
@Override
public String toString() {
return "TermInfo[" + mNumPredecessors + "," + mSeen + ","
+ mPrepared
+ (mContext == null
? "" : ",context:" + Arrays.toString(mContext))
+ (mSimplified == null ? "" : "->" + mSimplified) + "]";
}
}
HashMap<Term, TermInfo> mTermInfos;
Term mResult;
final Script mScript;
final Term mTrue;
final Term mFalse;
final boolean mSimplifyRepeatedly;
/**
* If asserting a term returns UNSAT, we use this flag to store that the
* context is inconsistent. The flag it set to false whenever we pop
* the context.
*/
protected boolean mInconsistencyOfContextDetected;
/**
* This class counts the predecessors of every term to enable the
* next passes to determine whether we need to collect information.
*
* @author hoenicke
*/
private static class TermCounter implements Walker {
protected Term mTerm;
public TermCounter(Term term) {
/* directly descend into not-terms as if the not is not there */
while (term instanceof ApplicationTerm) {
final ApplicationTerm appTerm = (ApplicationTerm) term;
if (appTerm.getFunction().getName() == "not") {
term = appTerm.getParameters()[0];
} else {
break;
}
}
mTerm = term;
}
@Override
public void walk(NonRecursive engine) {
final SimplifyDDA simplifier = (SimplifyDDA) engine;
TermInfo info = simplifier.mTermInfos.get(mTerm);
if (info == null) {
info = new TermInfo();
simplifier.mTermInfos.put(mTerm, info);
if (mTerm instanceof ApplicationTerm) {
final ApplicationTerm appTerm = (ApplicationTerm) mTerm;
final String connective = appTerm.getFunction().getName();
if (connective == "ite" || connective == "and"
|| connective == "or" || connective == "=>") {
for (final Term subTerm : appTerm.getParameters()) {
engine.enqueueWalker(new TermCounter(subTerm));
}
}
}
}
info.mNumPredecessors++;
}
}
/**
* This class collects the contexts (for the context simplifier)
* in which a term occurs. It does not simplify the term.
*
* @author hoenicke
*/
private static class ContextCollector implements Walker {
final boolean mNegated;
final Term mTerm;
ArrayDeque<Term> mContext;
int mParamCtr;
public ContextCollector(boolean negated, Term term,
ArrayDeque<Term> context) {
/* directly descend into not-terms as if the not is not there */
while (term instanceof ApplicationTerm) {
final ApplicationTerm appTerm = (ApplicationTerm) term;
if (appTerm.getFunction().getName() == "not") {
term = appTerm.getParameters()[0];
negated ^= true;
} else {
break;
}
}
mNegated = negated;
mTerm = term;
mContext = context;
mParamCtr = 0;
}
@Override
public void walk(NonRecursive engine) {
final SimplifyDDA simplifier = (SimplifyDDA) engine;
if (mParamCtr > 0) {
walkNextParameter(simplifier);
return;
}
final TermInfo info = simplifier.mTermInfos.get(mTerm);
assert info != null;
info.mSeen++;
assert info.mSeen <= info.mNumPredecessors;
if (info.mNumPredecessors > 1) {
// merge context
if (info.mContext == null) {
info.mContext = mContext.toArray(new Term[mContext.size()]);
} else {
final HashSet<Term> oldContext =
new HashSet<Term>(info.mContext.length);
oldContext.addAll(Arrays.asList(info.mContext));
final ArrayDeque<Term> newContext =
new ArrayDeque<Term>(info.mContext.length);
for (final Term t : mContext) {
if (oldContext.contains(t)) {
newContext.add(t);
}
}
mContext = newContext;
info.mContext =
newContext.toArray(new Term[newContext.size()]);
}
if (info.mSeen < info.mNumPredecessors) {
return;
}
}
if (mTerm instanceof ApplicationTerm) {
walkNextParameter(simplifier);
}
}
public void walkNextParameter(SimplifyDDA simplifier) {
final ApplicationTerm appTerm = (ApplicationTerm) mTerm;
final String connective = appTerm.getFunction().getName();
final Term[] params = appTerm.getParameters();
if (connective == "ite") {
final Term cond = params[0];
if (mParamCtr == 0) {
simplifier.enqueueWalker(this);
simplifier.enqueueWalker(
new ContextCollector(false, cond, mContext));
} else if (mParamCtr == 1) {
mContext.push(cond);
simplifier.enqueueWalker(this);
simplifier.enqueueWalker(new ContextCollector(
mNegated, params[1], mContext));
} else if (mParamCtr == 2) {
mContext.pop();
mContext.push(Util.not(simplifier.mScript, cond));
simplifier.enqueueWalker(this);
simplifier.enqueueWalker(new ContextCollector(
mNegated, params[2], mContext));
} else if (mParamCtr == 3) { // NOCHECKSTYLE
mContext.pop();
}
mParamCtr++;
} else if (connective == "and"
|| connective == "or" || connective == "=>") {
if (mParamCtr == 0) {
for (int i = params.length - 1; i > 0; i--) {
final Term sibling = simplifier.negateSibling(
params[i], connective, i, params.length);
mContext.push(sibling);
}
simplifier.enqueueWalker(this);
simplifier.enqueueWalker(new ContextCollector(
mNegated, params[mParamCtr], mContext));
} else if (mParamCtr < params.length) {
// The context contains:
// param[len-1] ... param[mParamCtr]
// simplify(param[mParamCtr-2])... simplify(param[0])
// we need to replace param[mParamCtr]
// by simplify(param[mParamCtr-1]).
/* this is dangerous: the simplified formulas may depend
* on their context, therefore we cannot simply merge them.
for (int i = 0; i < mParamCtr; i++) {
mContext.pop();
}
for (int i = mParamCtr-1; i >= 0; i--) {
Term sibling = simplifier.negateSibling(
params[i], connective, i, params.length);
sibling = simplifier.createSimplify(sibling);
mContext.push(sibling);
}
*/
mContext.pop();
simplifier.enqueueWalker(this);
simplifier.enqueueWalker(new ContextCollector(
mNegated, params[mParamCtr], mContext));
} else {
/*
for (int i = 0; i < mParamCtr-1; i++) {
mContext.pop();
}
*/
}
mParamCtr++;
}
}
}
/**
* This class simplifies the terms in a post-order traversal. First we
* descend into children doing nothing. When getting back to the parent
* again we simplify it, provided it has more than one predecessor.
*
* @author hoenicke
*/
private static class PrepareSimplifier implements Walker {
final Term mTerm;
public PrepareSimplifier(boolean negated, Term term) {
/* directly descend into not-terms as if the not is not there */
while (term instanceof ApplicationTerm) {
final ApplicationTerm appTerm = (ApplicationTerm) term;
if (appTerm.getFunction().getName() == "not") {
term = appTerm.getParameters()[0];
} else {
break;
}
}
mTerm = term;
}
@Override
public void walk(NonRecursive engine) {
final SimplifyDDA simplifier = (SimplifyDDA) engine;
final TermInfo info = simplifier.mTermInfos.get(mTerm);
if (info.mPrepared++ > 0) {
return;
}
if (info.mNumPredecessors > 1) {
engine.enqueueWalker(new StoreSimplified(mTerm));
engine.enqueueWalker(
new Simplifier(false, mTerm, info.mContext));
}
if (mTerm instanceof ApplicationTerm) {
final ApplicationTerm appTerm = (ApplicationTerm) mTerm;
final String connective = appTerm.getFunction().getName();
final Term[] params = appTerm.getParameters();
if (connective == "ite" || connective == "and"
|| connective == "or" || connective == "=>") {
for (int i = 0; i < params.length; i++) {
engine.enqueueWalker(
new PrepareSimplifier(false, params[i]));
}
}
}
}
@Override
public String toString() {
return "PrepareSimplifier[" + mTerm + "]";
}
}
private static class StoreSimplified implements Walker {
Term mTerm;
public StoreSimplified(Term term) {
mTerm = term;
}
@Override
public void walk(NonRecursive engine) {
final SimplifyDDA simplifier = (SimplifyDDA) engine;
final TermInfo info = simplifier.mTermInfos.get(mTerm);
info.mSimplified = simplifier.popResult();
}
@Override
public String toString() {
return "StoreSimplified[" + mTerm + "]";
}
}
private static class Simplifier implements Walker {
final boolean mNegated;
final Term mTerm;
final Term[] mContext;
int mParamCtr;
Term[] mSimplifiedParams;
public Simplifier(boolean negated, Term term, Term[] context) {
/* directly descend into not-terms as if the not is not there */
while (term instanceof ApplicationTerm) {
final ApplicationTerm appTerm = (ApplicationTerm) term;
if (appTerm.getFunction().getName() == "not") {
term = appTerm.getParameters()[0];
negated ^= true;
} else {
break;
}
}
mNegated = negated;
mTerm = term;
mContext = context;
mParamCtr = 0;
}
public Simplifier(boolean negated, Term term) {
/* directly descend into not-terms as if the not is not there */
while (term instanceof ApplicationTerm) {
final ApplicationTerm appTerm = (ApplicationTerm) term;
if (appTerm.getFunction().getName() == "not") {
term = appTerm.getParameters()[0];
negated ^= true;
} else {
break;
}
}
mNegated = negated;
mTerm = term;
mContext = null;
mParamCtr = 0;
}
@Override
public void walk(NonRecursive engine) {
final SimplifyDDA simplifier = (SimplifyDDA) engine;
if (mParamCtr > 0) {
walkParam(simplifier);
return;
}
if (mContext == null) {
/* check for redundancy, then for info */
final Redundancy red = simplifier.getRedundancy(mTerm);
if (red != Redundancy.NOT_REDUNDANT) {
if (red == Redundancy.NON_RELAXING) {
simplifier.setResult(mNegated, simplifier.mFalse);
} else {
simplifier.setResult(mNegated, simplifier.mTrue);
}
return;
}
final TermInfo info = simplifier.mTermInfos.get(mTerm);
if (info.mNumPredecessors > 1) {
assert info.mSimplified != null;
simplifier.setResult(mNegated, info.mSimplified);
return;
}
}
if (mContext != null) {
simplifier.pushContext(mContext);
/* check for redundancy */
final Redundancy red = simplifier.getRedundancy(mTerm);
if (red != Redundancy.NOT_REDUNDANT) {
if (red == Redundancy.NON_RELAXING) {
simplifier.setResult(mNegated, simplifier.mFalse);
} else {
simplifier.setResult(mNegated, simplifier.mTrue);
}
simplifier.popContext();
return;
}
}
if (mTerm instanceof ApplicationTerm) {
final ApplicationTerm appTerm = (ApplicationTerm) mTerm;
final String connective = appTerm.getFunction().getName();
final Term[] params = appTerm.getParameters();
if (connective == "ite" || connective == "and"
|| connective == "or" || connective == "=>") {
mSimplifiedParams = new Term[params.length];
walkParam(simplifier);
return;
}
}
/* we could not simplify this term */
simplifier.setResult(mNegated, mTerm);
if (mContext != null) {
simplifier.popContext();
}
}
private void walkParam(SimplifyDDA simplifier) {
final ApplicationTerm appTerm = (ApplicationTerm) mTerm;
final String connective = appTerm.getFunction().getName();
final Term[] params = appTerm.getParameters();
if (mParamCtr > 0) {
mSimplifiedParams[mParamCtr - 1] = simplifier.popResult();
}
if (connective == "ite") {
switch (mParamCtr++) {
case 0:
simplifier.enqueueWalker(this);
simplifier.enqueueWalker(new Simplifier(false, params[0]));
break;
case 1:
if (mSimplifiedParams[0] != simplifier.mFalse) {
simplifier.enqueueWalker(this);
simplifier.pushContext(mSimplifiedParams[0]);
simplifier.enqueueWalker(
new Simplifier(mNegated, params[1]));
break;
}
mSimplifiedParams[mParamCtr - 1] = simplifier.mFalse;
mParamCtr++;
/* fall through */
case 2:
if (mSimplifiedParams[0] != simplifier.mFalse) {
simplifier.popContext();
}
if (mSimplifiedParams[0] != simplifier.mTrue) {
simplifier.enqueueWalker(this);
simplifier.pushContext(
Util.not(simplifier.mScript, mSimplifiedParams[0]));
simplifier.enqueueWalker(
new Simplifier(mNegated, params[2]));
break;
}
mSimplifiedParams[mParamCtr - 1] = simplifier.mFalse;
mParamCtr++;
/* fall through */
case 3: // NOCHECKSTYLE
if (mSimplifiedParams[0] != simplifier.mTrue) {
simplifier.popContext();
}
final Term result = Util.ite(simplifier.mScript,
mSimplifiedParams[0], mSimplifiedParams[1],
mSimplifiedParams[2]);
simplifier.setResult(false, result);
if (mContext != null) {
simplifier.popContext();
}
break;
default:
throw new InternalError("BUG!");
}
} else {
assert (connective == "and" || connective == "or"
|| connective == "=>");
if (mParamCtr == params.length) {
simplifier.popContext();
final ArrayList<Term> newparams = new ArrayList<Term>();
Term result = null;
for (int i = 0; i < mSimplifiedParams.length; i++) {
final Term param = mSimplifiedParams[i];
if (param == simplifier.mTrue) {
if (connective == "and"
|| (connective == "=>"
&& i < mSimplifiedParams.length - 1)) {
continue;
}
if (connective == "or"
|| (connective == "=>"
&& i == mSimplifiedParams.length - 1)) {
result = simplifier.mTrue;
break;
}
} else if (param == simplifier.mFalse) {
if (connective == "or") {
continue;
}
if (connective == "and") {
result = simplifier.mFalse;
break;
}
if (connective == "=>"
&& i < mSimplifiedParams.length - 1) {
result = simplifier.mTrue;
break;
}
}
newparams.add(param);
}
if (result == null) {
if (newparams.isEmpty()) {
result = connective == "and" ? simplifier.mTrue
: simplifier.mFalse;
} else if (newparams.size() == 1) {
result = newparams.get(0);
} else {
final Term[] p =
newparams.toArray(new Term[newparams.size()]);
result = simplifier.mScript.term(connective, p);
}
}
simplifier.setResult(mNegated, result);
if (mContext != null) {
simplifier.popContext();
}
return;
}
if (mParamCtr == 0) {
simplifier.pushContext();
for (int i = params.length - 1; i >= 1; i--) {
final Term sibling = simplifier.negateSibling(
params[i], connective, i, params.length);
simplifier.pushContext(sibling);
}
} else {
simplifier.popContext();
for (int i = 0; i < mParamCtr; i++) {
final Term sibling = simplifier.negateSibling(
mSimplifiedParams[i], connective, i, params.length);
final LBool sat = simplifier.mScript.assertTerm(sibling);
if (sat == LBool.UNSAT) {
simplifier.mInconsistencyOfContextDetected = true;
break;
}
}
}
simplifier.enqueueWalker(this);
simplifier.enqueueWalker(
new Simplifier(false, params[mParamCtr]));
mParamCtr++;
}
}
@Override
public String toString() {
return "Simplifier["+mTerm+", param: "+mParamCtr+"]";
}
}
/**
* Creates a simplifier. This will simplify repeatedly until a fixpoint
* is reached.
* @param script A Script object that will be used to check for equivalent
* formulas.
*/
public SimplifyDDA(Script script) {
this(script, true);
}
/**
* Creates a simplifier.
* @param script A Script object that will be used to check for equivalent
* formulas.
* @param simplifyRepeatedly true if the simplifier should run until a
* fixpoint is reached.
*/
public SimplifyDDA(final Script script, boolean simplifyRepeatedly) {
mScript = script;
mTrue = mScript.term("true");
mFalse = mScript.term("false");
mSimplifyRepeatedly = simplifyRepeatedly;
}
/**
* Redundancy is a property of a subterm B with respect to its term A.
* The subterm B is called:
* <ul>
* <li> NON_RELAXING if term A is equivalent to the term, where B is
* replaced by false.
* <li> NON_CONSTRAINING if term A is equivalent to the term, where B is
* replaced by true.
* <li> NOT_REDUNDANT B is neither NON_REAXING nor NON_CONSTRAINING with
* respect to A.
* </ul>
*/
public enum Redundancy {
NON_RELAXING, NON_CONSTRAINING, NOT_REDUNDANT
}
/**
* Checks if termA is equivalent to termB.
* Returns unsat if the terms are equivalent, sat if they are not and
* unknown if it is not possible to determine.
*/
public LBool checkEquivalence(Term termA, Term termB) {
final Term equivalentTestTerm = mScript.term("=", termA, termB);
String checktype = null;
try {
checktype = (String) mScript.getOption(":check-type");
mScript.setOption(":check-type", "FULL");
} catch (final UnsupportedOperationException ignored) {
// Solver is not SMTInterpol
}
final LBool areTermsEquivalent =
Util.checkSat(mScript, Util.not(mScript, equivalentTestTerm));
if (checktype != null) {
mScript.setOption(":check-type", checktype);
}
return areTermsEquivalent;
}
/**
* Checks if term is redundant, with respect to the critical constraint
* on the assertion stack.
* @return NON_CONSTRAINING if term is equivalent to true,
* NON_RELAXING if term is equivalent to true,
* NOT_REDUNDANT if term is not redundant.
*/
protected Redundancy getRedundancy(Term term) {
if (mInconsistencyOfContextDetected) {
// context already inconsistent, hence term is
// NON_CONSTRAINING and NON_RELAXING
return Redundancy.NON_CONSTRAINING;
}
final LBool isTermConstraining =
Util.checkSat(mScript, Util.not(mScript, term));
if (isTermConstraining == LBool.UNSAT) {
return Redundancy.NON_CONSTRAINING;
}
final LBool isTermRelaxing = Util.checkSat(mScript, term);
if (isTermRelaxing == LBool.UNSAT) {
return Redundancy.NON_RELAXING;
}
return Redundancy.NOT_REDUNDANT;
}
private static Term termVariable2constant(Script script, TermVariable tv) {
final String name = tv.getName() + "_const_" + tv.hashCode();
final Sort[] paramSorts = {};
final Sort resultSort = tv.getSort();
script.declareFun(name, paramSorts, resultSort);
final Term result = script.term(name);
return result;
}
public Term simplifyOnce(Term term) {
mInconsistencyOfContextDetected = false;
mTermInfos = new HashMap<Term, TermInfo>();
run(new TermCounter(term));
run(new ContextCollector(false, term, new ArrayDeque<Term>()));
run(new PrepareSimplifier(false, term));
run(new Simplifier(false, term));
final Term output = popResult();
mTermInfos = null;
return output;
}
/**
* Return a Term which is equivalent to term but whose number of leaves is
* less than or equal to the number of leaves in term.
* @return term if each subterm of term is neither NON_CONSTRAINING
* nor NON_RELAXING. Otherwise return a copy of term, where each
* NON_CONSTRAINING subterm is replaced by true and each NON_RELAXING
* subterm is replaced by false
* @param mTerm whose Sort is Boolean
*/
public Term getSimplifiedTerm(Term inputTerm) throws SMTLIBException {
// mLogger.debug("Simplifying " + term);
/* We can only simplify boolean terms. */
if (!inputTerm.getSort().getName().equals("Bool")) {
return inputTerm;
}
int lvl = 0;// Java requires initialization
assert (lvl = PushPopChecker.currentLevel(mScript)) >= -1;
Term term = inputTerm;
mScript.echo(new QuotedObject("Begin Simplifier"));
mScript.push(1);
final TermVariable[] vars = term.getFreeVars();
final Term[] values = new Term[vars.length];
for (int i = 0; i < vars.length; i++) {
values[i] = termVariable2constant(mScript, vars[i]);
}
term = mScript.let(vars, values, term);
term = new FormulaUnLet().unlet(term);
Term output = simplifyOnce(term);
if (mSimplifyRepeatedly) {
while (output != term) {
term = output;
output = simplifyOnce(term);
}
} else {
term = output;
}
term = new TermTransformer() {
@Override
public void convert(Term term) {
for (int i = 0; i < vars.length; i++) {
if (term == values[i]) {
term = vars[i];
}
}
super.convert(term);
}
}.transform(term);// NOCHECKSTYLE
mScript.pop(1);
assert (checkEquivalence(inputTerm, term) != LBool.SAT)
: "Simplification unsound?";
mScript.echo(new QuotedObject("End Simplifier"));
assert PushPopChecker.atLevel(mScript, lvl);
return term;
}
/**
* Returns the contribution of a sibling to the critical constraint. This is
* either the sibling itself or its negation. The result is the negated
* sibling iff
* <ul>
* <li> the connective is "or"
* <li> the connective is "=>" and i==n-1
* </ul>
*
* @param i Index of this sibling. E.g., sibling B has index 1 in the term
* (and A B C)
* @param n Number of all siblings. E.g., the term (and A B C) has three
* siblings.
*/
private Term negateSibling(Term sibling, String connective, int i, int n) {
final boolean negate =
(connective == "or" || connective == "=>" && i == n - 1);
if (negate) {
return Util.not(mScript, sibling);
} else {
return sibling;
}
}
void pushContext(Term... context) {
mScript.push(1);
for (final Term t : context) {
final LBool sat = mScript.assertTerm(t);
if (sat == LBool.UNSAT) {
mInconsistencyOfContextDetected = true;
return;
}
}
}
void popContext() {
mInconsistencyOfContextDetected = false;
mScript.pop(1);
}
void setResult(boolean negated, Term term) {
if (negated) {
term = Util.not(mScript, term);
}
assert (mResult == null);
mResult = term;
}
Term popResult() {
final Term result = mResult;
mResult = null;
return result;
}
}