/*
* 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.logic;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Compute the common-subexpression-elimination (cse) form of a term. A term is
* in cse form if all nodes with an out-degree of at least 1 and an in-degree of
* at least 2 are eliminated, i.e., factored out into a let.
* @author hoenicke
*/
public class FormulaLet extends NonRecursive {
private ArrayDeque<Map<Term, TermInfo>> mVisited;
private ArrayDeque<Term> mResultStack;
private int mCseNum;
private final LetFilter mFilter;
public static interface LetFilter {
public boolean isLettable(Term t);
}
public FormulaLet() {
this(null);
}
public FormulaLet(LetFilter filter) {
mFilter = filter;
}
/**
* Compute the cse form of a term. Note that all lets will be removed from
* the input before computing the cse form.
* @param input The input term.
* @return A term in cse form that represents the same DAG than the input.
*/
public Term let(Term input) {
input = new FormulaUnLet().unlet(input);
mCseNum = 0;
mVisited = new ArrayDeque<Map<Term,TermInfo>>();
mResultStack = new ArrayDeque<Term>();
run(new Letter(input));
final Term result = mResultStack.removeLast();
assert mResultStack.size() == 0 && mVisited.size() == 0;
assert new TermEquivalence().equal(
new FormulaUnLet().unlet(result), input);
mResultStack = null;
mVisited = null;
return result;
}
static class Letter implements Walker {
final Term mTerm;
public Letter(Term term) {
mTerm = term;
}
@Override
public void walk(NonRecursive engine) {
if (mTerm instanceof TermVariable
|| mTerm instanceof ConstantTerm) {
((FormulaLet) engine).mResultStack.addLast(mTerm);
return;
}
((FormulaLet) engine).mVisited.addLast(
new HashMap<Term, FormulaLet.TermInfo>());
final TermInfo info = new TermInfo(mTerm);
((FormulaLet) engine).mVisited.getLast().put(mTerm, info);
engine.enqueueWalker(new Walker() {
@Override
public void walk(NonRecursive engine) {
((FormulaLet) engine).mVisited.removeLast();
}
});
engine.enqueueWalker(new Transformer(info, true));
engine.enqueueWalker(info);
}
}
private final static class TermInfo extends TermWalker {
int mCount;
int mSeen;
ArrayList<TermInfo> mLettedTerms;
TermVariable mSubst;
TermInfo mParent;
int mPDepth;
public TermInfo(Term term) {
super(term);
mCount = 1;
}
public boolean shouldBuildLet() {
TermInfo info = this;
while (info.mCount == 1) {
info = info.mParent;
if (info == null) {
return false;
}
if (info.mSubst != null) {
return false;
}
}
return true;
}
public void mergeParent(TermInfo parent) {
if (mParent == null) {
mParent = parent;
mPDepth = parent.mPDepth + 1;
return;
}
while (mParent != parent) {
if (parent.mPDepth == mParent.mPDepth) {
parent = parent.mParent;
mParent = mParent.mParent;
} else if (parent.mPDepth > mParent.mPDepth) {
parent = parent.mParent;
} else {
mParent = mParent.mParent;
}
}
mPDepth = mParent.mPDepth + 1;
}
@Override
public void walk(NonRecursive walker, ConstantTerm term) {
throw new InternalError("No TermInfo for ConstantTerm allowed");
}
@Override
public void walk(NonRecursive walker, AnnotatedTerm term) {
if (!isNamed(term)) {
visitChild((FormulaLet) walker, term.getSubterm());
}
}
@Override
public void walk(NonRecursive walker, ApplicationTerm term) {
final Term[] args = term.getParameters();
for (final Term t : args) {
visitChild((FormulaLet) walker, t);
}
}
@Override
public void walk(NonRecursive walker, LetTerm term) {
throw new InternalError(
"Let-Terms should not be in the formula anymore");
}
@Override
public void walk(NonRecursive walker, QuantifiedFormula term) {
// do not recurse into quantified formulas
// this avoids problem with common terms containing free
// variables
//((FormulaLet) walker).visit(term.getSubformula(), this);
}
@Override
public void walk(NonRecursive walker, TermVariable term) {
throw new InternalError("No TermInfo for TermVariable allowed");
}
public void visitChild(FormulaLet let, Term term) {
if (term instanceof TermVariable
|| term instanceof ConstantTerm) {
return;
}
if (term instanceof ApplicationTerm
&& ((ApplicationTerm) term).getParameters().length == 0) {
return;
}
TermInfo child = let.mVisited.getLast().get(term);
if (child == null) {
child = new TermInfo(term);
let.mVisited.getLast().put(term, child);
let.enqueueWalker(child);
} else {
child.mCount++;
}
}
}
static class Transformer implements Walker {
TermInfo mTermInfo;
boolean mIsCounted;
public Transformer(TermInfo parent, boolean isCounted) {
mTermInfo = parent;
mIsCounted = isCounted;
}
@Override
public void walk(NonRecursive engine) {
final FormulaLet let = ((FormulaLet) engine);
final Term term = mTermInfo.mTerm;
if (mIsCounted) {
let.enqueueWalker(new BuildLet(mTermInfo));
mTermInfo.mLettedTerms = new ArrayList<TermInfo>();
}
if (term instanceof QuantifiedFormula) {
final QuantifiedFormula quant = (QuantifiedFormula) term;
let.enqueueWalker(new BuildQuantifier(quant));
let.enqueueWalker(new Letter(quant.getSubformula()));
} else if (term instanceof AnnotatedTerm) {
final AnnotatedTerm at = (AnnotatedTerm) term;
let.enqueueWalker(new BuildAnnotatedTerm(at));
if (isNamed(at)) {
let.enqueueWalker(new Letter(at.getSubterm()));
} else {
let.enqueueWalker(
new Converter(mTermInfo, at.getSubterm(), mIsCounted));
}
} else if (term instanceof ApplicationTerm) {
final ApplicationTerm appTerm = (ApplicationTerm) term;
let.enqueueWalker(new BuildApplicationTerm(appTerm));
final Term[] params = appTerm.getParameters();
for (int i = params.length - 1; i >= 0; i--) {
let.enqueueWalker(
new Converter(mTermInfo, params[i], mIsCounted));
}
} else {
let.mResultStack.addLast(term);
}
}
}
static class Converter implements Walker {
TermInfo mParent;
Term mTerm;
boolean mIsCounted;
public Converter(TermInfo parent, Term term, boolean isCounted) {
mParent = parent;
mTerm = term;
mIsCounted = isCounted;
}
@Override
public void walk(NonRecursive engine) {
final FormulaLet let = ((FormulaLet) engine);
final Term child = mTerm;
final TermInfo info = let.mVisited.getLast().get(child);
if (info == null) {
let.mResultStack.addLast(child);
return;
}
info.mergeParent(mParent);
if (info.shouldBuildLet() && info.mSubst == null
&& (let.mFilter == null || let.mFilter.isLettable(child))) {
final Term t = info.mTerm;
info.mSubst = t.getTheory().
createTermVariable(".cse" + let.mCseNum++, t.getSort());
}
if (mIsCounted && ++info.mSeen == info.mCount) {
if (info.mSubst == null) {
let.enqueueWalker(new Transformer(info, true));
} else {
let.mResultStack.addLast(info.mSubst);
TermInfo ancestor = info.mParent;
TermInfo letPos = ancestor;
while (ancestor != null && ancestor.mSubst == null) {
if (ancestor.mCount > 1) {
letPos = ancestor.mParent;
}
ancestor = ancestor.mParent;
}
letPos.mLettedTerms.add(info);
}
return;
}
if (info.mSubst == null) {
let.enqueueWalker(new Transformer(info, false));
} else {
let.mResultStack.addLast(info.mSubst);
}
}
}
static class BuildLet implements Walker {
final TermInfo mTermInfo;
public BuildLet(TermInfo parent) {
mTermInfo = parent;
}
@Override
public void walk(NonRecursive engine) {
final List<TermInfo> lettedTerms = mTermInfo.mLettedTerms;
if (lettedTerms.isEmpty()) {
return;
}
final FormulaLet let = ((FormulaLet) engine);
final TermVariable[] tvs = new TermVariable[lettedTerms.size()];
let.enqueueWalker(this);
let.enqueueWalker(new BuildLetTerm(tvs));
int i = 0;
for (final TermInfo ti : lettedTerms) {
tvs[i++] = ti.mSubst;
let.enqueueWalker(new Transformer(ti, true));
}
lettedTerms.clear();
}
}
static class BuildLetTerm implements Walker {
final TermVariable[] mVars;
public BuildLetTerm(TermVariable[] vars) {
mVars = vars;
}
@Override
public void walk(NonRecursive engine) {
final FormulaLet let = (FormulaLet)engine;
final Term[] values = new Term[mVars.length];
for (int i = 0; i < values.length; i++) {
values[i] = let.mResultStack.removeLast();
}
final Term newBody = let.mResultStack.removeLast();
final Theory theory = newBody.getTheory();
final Term result = theory.let(mVars, values, newBody);
let.mResultStack.addLast(result);
}
}
static class BuildApplicationTerm implements Walker {
final ApplicationTerm mOldTerm;
public BuildApplicationTerm(ApplicationTerm term) {
mOldTerm = term;
}
@Override
public void walk(NonRecursive engine) {
final FormulaLet let = (FormulaLet)engine;
final Term[] newParams = let.getTerms(mOldTerm.getParameters());
Term result = mOldTerm;
if (newParams != mOldTerm.getParameters()) {
final Theory theory = mOldTerm.getTheory();
result = theory.term(mOldTerm.getFunction(), newParams);
}
let.mResultStack.addLast(result);
}
}
static class BuildQuantifier implements Walker {
final QuantifiedFormula mOldTerm;
public BuildQuantifier(QuantifiedFormula term) {
mOldTerm = term;
}
@Override
public void walk(NonRecursive engine) {
final FormulaLet let = (FormulaLet)engine;
final Term newBody = let.mResultStack.removeLast();
Term result = mOldTerm;
if (newBody != mOldTerm.getSubformula()) {
final Theory theory = mOldTerm.getTheory();
if (mOldTerm.getQuantifier() == QuantifiedFormula.EXISTS) {
result = theory.exists(mOldTerm.getVariables(), newBody);
} else {
result = theory.forall(mOldTerm.getVariables(), newBody);
}
}
let.mResultStack.addLast(result);
}
}
static class BuildAnnotatedTerm implements Walker {
final AnnotatedTerm mOldTerm;
public BuildAnnotatedTerm(AnnotatedTerm term) {
mOldTerm = term;
}
@Override
public void walk(NonRecursive engine) {
final FormulaLet let = (FormulaLet)engine;
final Term newBody = let.mResultStack.removeLast();
Term result = mOldTerm;
if (newBody != mOldTerm.getSubterm()) {
final Theory theory = mOldTerm.getTheory();
result = theory.annotatedTerm(
mOldTerm.getAnnotations(), newBody);
}
let.mResultStack.addLast(result);
}
}
private static boolean isNamed(AnnotatedTerm at) {
for (final Annotation a : at.getAnnotations()) {
if (a.getKey().equals(":named")) {
return true;
}
}
return false;
}
public Term[] getTerms(Term[] oldArgs) {
Term[] newArgs = oldArgs;
for (int i = oldArgs.length - 1; i >= 0; i--) {
final Term newTerm = mResultStack.removeLast();
if (newTerm != oldArgs[i]) {
if (newArgs == oldArgs) {
newArgs = oldArgs.clone();
}
newArgs[i] = newTerm;
}
}
return newArgs;
}
}