/*
* 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.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import de.uni_freiburg.informatik.ultimate.logic.ConstantTerm;
import de.uni_freiburg.informatik.ultimate.logic.NonRecursive;
import de.uni_freiburg.informatik.ultimate.logic.Rational;
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;
/**
* Represents an affine term. An affine term is a sum
* <pre>Σ c_i * x_i + c,</pre>
* where c_i, c are rational (or integer) constants
* and x_i are flat terms that are not themselves affine terms.
*
* @author hoenicke.
*/
public final class SMTAffineTerm extends Term {
private final Sort mSort;
private final Map<Term, Rational> mSummands;
private final Rational mConstant;
private SMTAffineTerm(
Map<Term, Rational> summands, Rational constant, Sort sort) {
super(constant.hashCode() * 11 + summands.hashCode()
+ 1423 * sort.hashCode());
mSort = sort;
mSummands = summands;
mConstant = constant;
}
public static SMTAffineTerm create(
Map<Term, Rational> summands, Rational constant, Sort sort) {
return new SMTAffineTerm(summands, constant, sort);
}
public static SMTAffineTerm create(Rational rat, Sort sort) {
return create(Collections.<Term,Rational>emptyMap(), rat, sort);
}
public static SMTAffineTerm create(Term term) {
if (term instanceof SMTAffineTerm) {
return (SMTAffineTerm) term;
}
return create(Rational.ONE, term);
}
public static SMTAffineTerm create(Rational factor, Term subterm) {
final Sort sort = subterm.getSort();
Map<Term, Rational> summands;
Rational constant;
if (factor.equals(Rational.ZERO)) {
summands = Collections.emptyMap();
constant = Rational.ZERO;
} else if (subterm instanceof SMTAffineTerm) {
final SMTAffineTerm a = (SMTAffineTerm) subterm;
constant = a.mConstant.mul(factor);
summands = new HashMap<Term, Rational>();
for (final Map.Entry<Term,Rational> me : a.mSummands.entrySet()) {
summands.put(me.getKey(), me.getValue().mul(factor));
}
} else if (subterm instanceof ConstantTerm) {
final Object value = ((ConstantTerm) subterm).getValue();
if (value instanceof BigInteger) {
constant = Rational.valueOf(
(BigInteger) value, BigInteger.ONE).mul(factor);
summands = Collections.emptyMap();
} else if (value instanceof BigDecimal) {
final BigDecimal decimal = (BigDecimal) value;
if (decimal.scale() <= 0) {
final BigInteger num = decimal.toBigInteger();
constant = Rational.valueOf(num, BigInteger.ONE).mul(factor);
} else {
final BigInteger num = decimal.unscaledValue();
final BigInteger denom = BigInteger.TEN.pow(decimal.scale());
constant = Rational.valueOf(num, denom).mul(factor);
}
summands = Collections.emptyMap();
} else if (value instanceof Rational) {
constant = (Rational) value;
summands = Collections.emptyMap();
} else {
summands = Collections.singletonMap(subterm, factor);
constant = Rational.ZERO;
}
} else {
summands = Collections.singletonMap(subterm, factor);
constant = Rational.ZERO;
}
return create(summands, constant, sort);
}
public SMTAffineTerm add(SMTAffineTerm a2) {
assert getSort().equals(a2.getSort());
return addUnchecked(a2, true);
}
public SMTAffineTerm addUnchecked(SMTAffineTerm a2, boolean sortCorrect) {
final Map<Term, Rational> summands = new HashMap<Term, Rational>();
summands.putAll(mSummands);
for (final Map.Entry<Term,Rational> entry : a2.mSummands.entrySet()) {
final Term var = entry.getKey();
if (summands.containsKey(var)) {
final Rational r = summands.get(var).add(entry.getValue());
if (r.equals(Rational.ZERO)) {
summands.remove(var);
} else {
summands.put(var, r);
}
} else {
summands.put(var, entry.getValue());
}
}
return create(summands, mConstant.add(a2.mConstant),
sortCorrect ? mSort
: a2.getSort().getName().equals("Real")
? a2.getSort() : mSort);
}
/**
* Add a rational constant to this affine term.
* @param c the constant to add.
* @return the sum of this and the constant.
*/
public SMTAffineTerm add(Rational c) {
return create(mSummands, mConstant.add(c), mSort);
}
/**
* Convert affine term to a different sort. This should only be used
* to convert from int to real, as it does not truncate.
* @param other the affine term to convert.
* @param sort the new sort.
*/
public SMTAffineTerm typecast(Sort realSort) {
return create(mSummands, mConstant, realSort);
}
/**
* Multiply a rational constant with this affine term.
* @param c the constant to multiply.
* @return the product of this and the constant.
*/
public SMTAffineTerm mul(Rational factor) {
if (factor.equals(Rational.ZERO)) {
return create(Rational.ZERO, mSort);
}
final Rational constant = mConstant.mul(factor);
final HashMap<Term, Rational> summands = new HashMap<Term, Rational>();
for (final Map.Entry<Term,Rational> me : mSummands.entrySet()) {
summands.put(me.getKey(), me.getValue().mul(factor));
}
return create(summands, constant, mSort);
}
public SMTAffineTerm div(Rational c) {
return mul(c.inverse());
}
public SMTAffineTerm negate() {
return mul(Rational.MONE);
}
public boolean isConstant() {
return mSummands.isEmpty();
}
public Rational getConstant() {
return mConstant;
}
public boolean isIntegral() {
return mSort.getName().equals("Int");
}
@Override
public boolean equals(Object o) { // NOCHECKSTYLE
if (!(o instanceof SMTAffineTerm)) {
return false;
}
final SMTAffineTerm l = (SMTAffineTerm) o;
return mSort == l.mSort
&& mConstant.equals(l.mConstant)
&& mSummands.equals(l.mSummands);
}
@Override
public Sort getSort() {
return mSort;
}
Rational getCoefficient(Term subterm) {
final Rational coeff = mSummands.get(subterm);
return coeff == null ? Rational.ZERO : coeff;
}
public Rational getGcd() {
assert (!mSummands.isEmpty());
final Iterator<Rational> it = mSummands.values().iterator();
Rational gcd = it.next().abs();
while (it.hasNext()) {
gcd = gcd.gcd(it.next().abs());
}
return gcd;
}
public Map<Term, Rational> getSummands() {
return mSummands;
}
/**
* Convert the affine term to plain SMTLib term.
* Note that this is does not convert terms inside this term. Instead
* use the static method cleanup() for this, which works on arbitrary
* terms.
* @see SMTAffineTerm.cleanup
*/
private static Term toPlainTerm(
Map<Term, Rational> summands, Rational constant, Sort sort) {
assert sort.isNumericSort();
final Theory t = sort.getTheory();
int size = summands.size();
if (size == 0 || !constant.equals(Rational.ZERO)) {
size++;
}
final Term[] sum = new Term[size];
int i = 0;
for (final Map.Entry<Term,Rational> factor : summands.entrySet()) {
Term convTerm = factor.getKey();
if (!convTerm.getSort().equals(sort)) {
convTerm = t.term("to_real", convTerm);
}
if (factor.getValue().equals(Rational.MONE)) {
convTerm = t.term("-", convTerm);
} else if (!factor.getValue().equals(Rational.ONE)) {
final Term convfac = t.rational(factor.getValue(), sort);
convTerm = t.term("*", convfac, convTerm);
}
sum[i++] = convTerm;
}
if (i < size) {
sum[i++] = t.rational(constant, sort);
}
return size == 1 ? sum[0] : t.term("+", sum);
}
@Override
public void toStringHelper(ArrayDeque<Object> m_Todo) {
m_Todo.addLast(toPlainTerm(mSummands, mConstant, mSort));
}
@Override
public String toString() {
return cleanup(this).toString();
}
/**
* Remove all occurrences of SMTAffineTerm from the given term.
* @param term the term to clean up.
* @return an equivalent term without SMTAffineTerm classes.
*/
public static Term cleanup(Term term) {
return new TermTransformer() {
@Override
public void convert(Term term) {
if (term instanceof SMTAffineTerm) {
final SMTAffineTerm affine = (SMTAffineTerm) term;
enqueueWalker(new Walker() {
@Override
public void walk(NonRecursive engine) {
final HashMap<Term, Rational> summands =
new HashMap<Term, Rational>();
for (final Rational v: affine.mSummands.values()) {
summands.put(getConverted(), v);
}
final Term term = SMTAffineTerm.toPlainTerm(
summands, affine.mConstant, affine.mSort);
setResult(term);
}
});
for (final Term t : affine.mSummands.keySet()) {
pushTerm(t);
}
return;
}
super.convert(term);
}
}.transform(term);
}
/**
* Normalize this term. If this term corresponds to a singleton sum with
* coefficient 1 and constant 0, it will return the singleton term.
* Otherwise, it will return this.
* @param compiler TermCompiler used to unify SMTAffineTerms
* @return this or the singleton term corresponding to this.
*/
public Term normalize(TermCompiler compiler) {
if (mConstant.equals(Rational.ZERO) && mSummands.size() == 1) {
final Map.Entry<Term, Rational> me =
mSummands.entrySet().iterator().next();
if (me.getValue().equals(Rational.ONE)
// Fixes bug for to_real
&& me.getKey().getSort() == mSort) {
return me.getKey();
}
}
return compiler.unify(this);
}
public Term internalize(TermCompiler compiler) {
SMTAffineTerm res = this;
if (getTheory().getLogic().isIRA() && !isIntegral() && isAllInt()) {
res = create(mSummands, mConstant, getTheory().getSort("Int"));
}
return res.normalize(compiler);
}
public boolean isAllIntSummands() {
for (final Map.Entry<Term, Rational> me : mSummands.entrySet()) {
if (!me.getKey().getSort().getName().equals("Int")) {
return false;
}
if (!me.getValue().isIntegral()) {
return false;
}
}
return true;
}
private boolean isAllInt() {
return isAllIntSummands() && mConstant.isIntegral();
}
}