/* * Copyright (C) 2012-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.smtinterpol.proofcheck; import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; import java.util.HashMap; import java.util.Map; import de.uni_freiburg.informatik.ultimate.logic.AnnotatedTerm; 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.Logics; 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.Theory; /** * This class is used to convert a lemma of linear arithmetic (LA). * * @author Christian Schilling */ public class LemmaLAConverter extends AConverter { // true iff fast proofs shall be printed private final boolean mFastProofs; // appendable for the lemmata private final Appendable mLemmaAppendable; // index number of the pattern lemmata private int mLemmaNumber; // strings for handling fast proofs private final String mStartLine; private final String mEndLine; // variable name in the pattern proof private static final String LA_LEMMA_VAR = "x"; /** * @param appendable appendable to write the proof to * @param theory the theory * @param converter term converter * @param simplifier computation simplifier * @param fastProofs true iff fast proofs shall be printed * @param lemmaAppendable the theory file for the lemmata */ public LemmaLAConverter(final Appendable appendable, final Theory theory, final TermConverter converter, final ComputationSimplifier simplifier, final boolean fastProofs, final Appendable lemmaAppendable) { super(appendable, theory, converter, simplifier); mFastProofs = fastProofs; mStartLine = mFastProofs ? "" : "apply ("; mEndLine = mFastProofs ? ",\n" : ")\n"; mLemmaAppendable = lemmaAppendable; mLemmaNumber = 0; } /** * This method converts a lemma of linear arithmetic (LA). * * First a copy of the disjunction (the pattern) is created where the * variable terms are replaced by pattern variables. Then the lemma is * proven as a pattern lemma and in the actual proof file just a recall * of the pattern lemma is applied. * * The proof goes by contraposition. The following steps are applied * stepwise to the first two literals: * - de Morgan's rule * - multiplication with Farkas coefficient * - calculation with distributivity rule * - raw merging of the first two literals * - simplification of merged terms * * @param lemma the disjunction of linear inequalities * @param factors factors to multiply the disjuncts with */ public void convert(final ApplicationTerm lemma, final Object[] factors) { final Term[] disjuncts = lemma.getParameters(); assert ((disjuncts.length == factors.length) && (disjuncts.length > 1)); // find the correct theory: integer, real, or mixed final EArith arithType; final Logics logic = mTheory.getLogic(); if (logic.isIRA()) { arithType = EArith.mixed; } else if (logic.hasReals()) { arithType = EArith.real; } else { arithType = EArith.integer; } // data structure for the literals final IneqInfo ineqs = new IneqInfo(disjuncts.length, factors, arithType); final ApplicationTerm patternLemma = createPatternLemma(disjuncts, ineqs, arithType); // header (proof by contraposition) writeLemmaString("\nlemma "); writeLemmaString(LA_LEMMA_PREFIX); writeLemmaString(Integer.toString(++mLemmaNumber)); writeLemmaString(": \""); mConverter.convertWithTypes(patternLemma, mLemmaAppendable); writeLemmaString("\"\nproof (rule classical)\nassume \"~?thesis\"" + "\nhence \"False\"\n"); // map from term to factor final Var2FactorMap map = new Var2FactorMap(); // keep track of the order sign ('<' vs. '<=') to use the correct rule FarkasResult farkas = new FarkasResult(false, EOrder.le_le, ineqs.mLiterals[0].mIsIntegral); /* * The first literal has no predecessor and hence cannot be merged. * That is why it is handled differently. */ deMorgan(ineqs, 0); farkas = farkasCoefficient(ineqs, 0, farkas, arithType); distributivity(ineqs, 0, map, farkas, arithType); // binary processing of the literals for (int i = 1; i < disjuncts.length; ++i) { // apply de Morgan's rule deMorgan(ineqs, i); // multiplication with Farkas coefficients farkas = farkasCoefficient(ineqs, i, farkas, arithType); // eliminate distributivity */ distributivity(ineqs, i, map, farkas, arithType); // merge literals farkas = mergeLiterals(ineqs, i, farkas, arithType); // simplify expressions simplify(ineqs, i, farkas.mOrder, map); } /* * Removing zero factors is trivial for Isabelle, so the simplifier * without any rules is used here. * * NOTE: This is always the last rule, so there is no special case * treatment necessary. */ if (mFastProofs) { writeLemmaString(mSimplifier.getRule()); writeLemmaString(")"); } else { writeLemmaString("by "); writeLemmaString(mSimplifier.getRule()); } // finish proof writeLemmaString("\nthus ?thesis by (rule HOL.FalseE)\nqed\n"); // real proof mConverter.convert(lemma); writeString("\"\nby (rule "); writeString(LA_LEMMA_PREFIX); writeString(Integer.toString(mLemmaNumber)); writeString(")\n"); } /** * This method creates the pattern from the lemma. It has to be created * anyway to write it to the output, but this way also no term mapping to * pattern variables is needed. * The procedure is to unpack the term up to the summands level of the * canonical sum, then insert the according pattern variables, and finally * pack the term again. * * @param disjuncts the original disjuncts * @param ineqs data structure * @param arithType type of logic * @return the disjunction with the terms replaced by pattern variables */ private ApplicationTerm createPatternLemma(Term[] disjuncts, final IneqInfo ineqs, final EArith arithType) { final Term[] newDisjuncts = new Term[disjuncts.length]; // map for pattern variable indices final HashMap<Term, Integer> term2index = new HashMap<Term, Integer>(); final Term[] params = new Term[0]; final Sort[] paramSorts = new Sort[0]; // unpack terms and pack them again for (int i = 0; i < disjuncts.length; ++i) { Term next = disjuncts[i]; final boolean isNegated = next instanceof ApplicationTerm; // unpack negation to handle terms equally if (isNegated) { assert ((((ApplicationTerm)next).getFunction() == mTheory.mNot) && (((ApplicationTerm)next).getParameters().length == 1)); next = ((ApplicationTerm)next).getParameters()[0]; } // unpack :quoted literal assert ((next instanceof AnnotatedTerm) && (((AnnotatedTerm)next).getAnnotations().length == 1) && (((AnnotatedTerm)next).getAnnotations()[0].getKey() == ":quoted") && (((AnnotatedTerm)next).getSubterm() instanceof ApplicationTerm)); final ApplicationTerm laTerm = (ApplicationTerm)((AnnotatedTerm)next).getSubterm(); // unpack (in)equality assert (laTerm.getParameters().length == 2); final Term lhs = laTerm.getParameters()[0]; // go through summands final Term[] summands; final FunctionSymbol plus; if (lhs instanceof ApplicationTerm) { final ApplicationTerm aLhs = (ApplicationTerm)lhs; plus = aLhs.getFunction(); // sum if (plus.getName() != "+") { // NOPMD summands = new Term[1]; summands[0] = lhs; } else { // single summand summands = aLhs.getParameters(); } } else { // single summand plus = null; summands = new Term[1]; summands[0] = lhs; } // unpack factors and minus and replace variable, then pack again final Term[] newSummands = new Term[summands.length]; for (int j = 0; j < summands.length; ++j) { final Term summand = summands[j]; // last summand could be the constant if (j == summands.length - 1) { if (summand instanceof ConstantTerm) { newSummands[j] = summand; break; } else if (summand instanceof ApplicationTerm) { final ApplicationTerm aSummand = (ApplicationTerm)summand; // negative constant if ((aSummand.getFunction().getName() == "-") && (aSummand.getParameters()[0] instanceof ConstantTerm)) { assert (((ApplicationTerm)summand). getParameters().length == 1); newSummands[j] = summand; break; } else if (aSummand.getFunction().getName() == "/") { // constant fraction assert (((ApplicationTerm)summand). getParameters().length == 2); if (aSummand.getParameters()[1] instanceof ConstantTerm) { assert ((aSummand.getParameters()[0] instanceof ConstantTerm) || ((aSummand.getParameters()[0] instanceof ApplicationTerm) && (((ApplicationTerm)aSummand. getParameters()[0]). getFunction().getName() == "-"))); newSummands[j] = summand; break; } } } } if (summand instanceof ApplicationTerm) { final ApplicationTerm aSummand = (ApplicationTerm)summand; final FunctionSymbol summandSymbol = aSummand.getFunction(); final String summandName = summandSymbol.getName(); // factor if (summandName == "*") { final Term[] factors = aSummand.getParameters(); assert (factors.length == 2); newSummands[j] = mTheory.term(summandSymbol, factors[0], getPatternVar(term2index, factors[1], params, paramSorts, arithType)); } else if (summandName == "-") { assert (aSummand.getParameters().length == 1); newSummands[j] = mTheory.term(summandSymbol, getPatternVar(term2index, aSummand.getParameters()[0], params, paramSorts, arithType)); } else { newSummands[j] = getPatternVar(term2index, summand, params, paramSorts, arithType); } } } // pack sum again, but not if no sum before final Term newLhs; if (newSummands.length == 1) { newLhs = newSummands[0]; } else { assert (newSummands.length > 1); newLhs = mTheory.term(plus, newSummands); } // pack LA term final ApplicationTerm newLaTerm = mTheory.term(laTerm.getFunction(), newLhs, laTerm.getParameters()[1]); // pack :quoted annotation Term newNext = mTheory.annotatedTerm( ((AnnotatedTerm)next).getAnnotations(), newLaTerm); // pack negation if existent before if (isNegated) { newNext = mTheory.not(newNext); } newDisjuncts[i] = newNext; /* * NOTE: The negation is set in the counter-intuitive way, * because it will be inverted by de Morgan's rule later. */ ineqs.add(i, newLaTerm, !isNegated, arithType); } return mTheory.term(mTheory.mOr, newDisjuncts); } /** * This method returns the variable term given the original term and a map. * * @param term2index map from original terms to indices * @param term original term * @param parameters parameters * @param parameterSorts parameter sorts * @return the pattern variable */ private ApplicationTerm getPatternVar( final HashMap<Term, Integer> term2index, Term term, final Term[] parameters, final Sort[] parameterSorts, final EArith arithType) { // ignore 'to_real' prefix to talk about the same terms FunctionSymbol toReal = null; if ((arithType == EArith.mixed) && (term instanceof ApplicationTerm)) { final ApplicationTerm aTerm = (ApplicationTerm)term; toReal = aTerm.getFunction(); if (toReal.getName() == "to_real") { assert (aTerm.getParameters().length == 1); term = aTerm.getParameters()[0]; } else { toReal = null; } } Integer index = term2index.get(term); if (index == null) { index = term2index.size() + 1; term2index.put(term, index); } final String name = LA_LEMMA_VAR + index; ApplicationTerm result = mTheory.term(name, parameters); if (result == null) { result = mTheory.term( mTheory.declareFunction( name, parameterSorts, term.getSort()), parameters); } // add to_real again if (toReal != null) { result = mTheory.term(toReal, result); } return result; } /** * This method translates one step of de Morgan's rule without any use of * substitution. * * Note that the negation is swapped here and hence its internal status was * already set to the inverted value before. That is why the flag in the * data structure seems to be set wrong, but it is correct. * * @param ineqs data structure * @param index index */ private void deMorgan(final IneqInfo ineqs, final int index) { final IneqInfo.IneqLiteral literal = ineqs.mLiterals[index]; /* * NOTE: The first 'apply rule' works like a later 'apply erule', * so no 'erule' here. This only works, since this is the first rule * in the lemma proof. * Also, in the fast proof the first 'apply' has to be executed outside * the 'by'. */ if (index == 0) { if (literal.isNegated()) { writeLemmaString("apply (rule de_Morgan_disj_pos_first)\n"); } else { writeLemmaString("apply (rule de_Morgan_disj_neg_first)\n"); } // start fast proof here if (mFastProofs) { writeLemmaString("by ("); } } else if (ineqs.isLast(index)) { if (!literal.isNegated()) { writeRule("erule de_Morgan_disj_neg_last"); } } else { if (literal.isNegated()) { writeRule("erule de_Morgan_disj_pos"); } else { writeRule("erule de_Morgan_disj_neg"); } } } /** * This method multiplies a linear inequality with a coefficient * (the Farkas coefficient). * * @param ineqs data structure * @param index index * @param fRes farkas result wrapper * @param arithType type of logic * @return tuple: (true iff factor is not 1; order signs of the literals) */ private FarkasResult farkasCoefficient(final IneqInfo ineqs, final int index, final FarkasResult fRes, final EArith arithType) { final Term factor = ineqs.getFactorTerm(index); EOrder order = fRes.mOrder; // ignore factor 1 if (factor instanceof ConstantTerm) { final ConstantTerm cFactor = (ConstantTerm)factor; assert (cFactor.getValue() instanceof BigInteger); if (((BigInteger)cFactor.getValue()).equals(BigInteger.ONE)) { switch (ineqs.mLiterals[index].mIneqType) { case pos_le: if (index == 0) { return new FarkasResult(false, EOrder.le_le, fRes.mFirstInt); } else { return new FarkasResult(false, convertOrder(order, true), fRes.mFirstInt); } case pos_less: switch (arithType) { case real: if (index == 0) { return new FarkasResult(false, EOrder.less_le, fRes.mFirstInt); } else { return new FarkasResult(false, convertOrder(order, false), fRes.mFirstInt); } case mixed: if (index == 0) { return new FarkasResult(false, (ineqs.mLiterals[index].mIsIntegral) ? EOrder.le_le : EOrder.less_le, fRes.mFirstInt); } else { return new FarkasResult(false, convertOrder(order, ineqs.mLiterals[index]. mIsIntegral), fRes.mFirstInt); } default: assert false; throw new IllegalArgumentException( "For integers '<' never occurs."); } // do not return here, since equality has to be unpacked case eq: if (index == 0) { order = EOrder.le_le; } else { order = convertOrder(order, true); } break; default: assert false; throw new IllegalArgumentException( "for factor '1' the literal can only be positive or equality."); } } } final String rule; switch (ineqs.mLiterals[index].mIneqType) { case pos_le: assert (factor instanceof ConstantTerm); rule = "farkas_pos_le"; order = convertOrder(order, true); break; case neg_le: assert (factor instanceof ApplicationTerm); switch (arithType) { case integer: rule = "int_farkas_neg_le"; order = convertOrder(order, true); break; case real: rule = "real_farkas_neg_le"; order = convertOrder(order, false); break; case mixed: if (ineqs.mLiterals[index].mIsIntegral) { rule = "int_farkas_neg_le"; order = convertOrder(order, true); } else { rule = "real_farkas_neg_le"; order = convertOrder(order, false); } break; default: assert false; throw new IllegalArgumentException( "The logics type is unknown."); } break; case pos_less: assert (factor instanceof ConstantTerm); switch (arithType) { case integer: rule = "int_farkas_pos_less"; order = convertOrder(order, true); break; case real: rule = "real_farkas_pos_less"; order = convertOrder(order, false); break; case mixed: if (ineqs.mLiterals[index].mIsIntegral) { rule = "int_farkas_pos_less"; order = convertOrder(order, true); } else { rule = "real_farkas_pos_less"; order = convertOrder(order, false); } break; default: assert false; throw new IllegalArgumentException( "The logics type is unknown."); } break; case neg_less: assert (factor instanceof ApplicationTerm); rule = "farkas_neg_less"; order = convertOrder(order, true); break; // equality is handled differently case eq: rule = "farkas_eq"; order = convertOrder(order, true); break; default: assert false; throw new IllegalArgumentException( "The ordering type is unknown."); } // all inequalities are handled very similarly writeLemmaString(mStartLine); writeLemmaString("erule "); writeLemmaString(rule); if (index == 0) { writeLemmaString("_first"); } else if (ineqs.isLast(index)) { writeLemmaString("_last"); } writeLemmaString(" [where c = \""); /* * Do not annotate the number with a type since Isabelle then adds a * cast and messes up everything. */ if (factor instanceof ApplicationTerm) { assert (((ApplicationTerm)factor).getFunction().getName() == "-"); writeLemmaString("- "); writeLemmaString(((ApplicationTerm)factor).getParameters()[0]. toString()); } else { assert (factor instanceof ConstantTerm); writeLemmaString(((ConstantTerm)factor).getValue().toString()); } writeLemmaString("\"]"); writeLemmaString(mEndLine); // additional simplifier application for inequalities if (ineqs.mLiterals[index].mIneqType != EIneqType.eq) { writeRule(mSimplifier.getRule()); } return new FarkasResult(true, order, fRes.mFirstInt); } /** * This method applies the distributivity rule. Constant factors are * charged against each other to have a resulting normal form. * * Since the factors are the Farkas coefficients, they can only be * integers. * * In a simplified version (no corner cases) this looks as follows: * * Before: c * (s1 + ... + sn + d) * After: c1 * x1 + ... + cn * xn + e * where 'si = (ci/c) * xi' and 'e = c * d' * * @param ineqs data structure * @param index index * @param map maps terms to factors * @param fRes farkas result wrapper * @param arithType type of logic */ private void distributivity(final IneqInfo ineqs, final int index, final Var2FactorMap map, final FarkasResult fRes, final EArith arithType) { final boolean write = fRes.mApplyDistributivity; final EOrder order = fRes.mOrder; StringBuilder firstLine = null; if (write) { firstLine = new StringBuilder(); // first step firstLine.append(mStartLine); firstLine.append("erule dist"); // <= or < ? if (index == 0) { switch (order) { case le_le: case le_less: firstLine.append("_le"); break; default: firstLine.append("_less"); } } else { switch (order) { case le_le: case less_le: firstLine.append("_le"); break; default: firstLine.append("_less"); } } // first or last literal? if (index == 0) { firstLine.append("_first"); } else if (ineqs.isLast(index)) { firstLine.append("_last"); } firstLine.append(mEndLine); } // Farkas coefficient final BigInteger factor; if (ineqs.mFactors[index] instanceof ConstantTerm) { factor = (BigInteger) ((ConstantTerm)ineqs.mFactors[index]).getValue(); } else { assert ((ineqs.mFactors[index] instanceof ApplicationTerm) && (((ApplicationTerm)ineqs.mFactors[index]).getFunction(). getName() == "-") && (((ApplicationTerm)ineqs.mFactors[index]).getParameters(). length == 1)); factor = ((BigInteger) ((ConstantTerm)((ApplicationTerm)ineqs.mFactors[index]). getParameters()[0]).getValue()).negate(); } // prefix final String prefix; if (factor.compareTo(BigInteger.ZERO) == 1) { prefix = "subst s_dist_pos_"; } else { prefix = "subst s_dist_neg_"; } // for integers, a constant has been added, so process this first switch (arithType) { // if literal is integral, exploit non-breaking switch case mixed: if (!ineqs.mLiterals[index].mIsIntegral) { break; } case integer: switch (ineqs.mLiterals[index].mIneqType) { case pos_less: map.updateConstant(Rational.valueOf(factor, BigInteger.ONE)); if (write) { writeLemmaString(firstLine.toString()); firstLine = null; writeLemmaString(mStartLine); writeLemmaString(prefix); writeLemmaString("pos"); writeLemmaString(mEndLine); } break; case neg_le: map.updateConstant(Rational.valueOf(factor, BigInteger.ONE).negate()); if (write) { writeLemmaString(firstLine.toString()); firstLine = null; writeLemmaString(mStartLine); writeLemmaString(prefix); writeLemmaString("neg"); writeLemmaString(mEndLine); } break; default: } break; case real: break; default: assert false; throw new IllegalArgumentException( "The logics type is unknown."); } final Term[] summands = ineqs.mLiterals[index].mSummands; // first line may only be written if there is a distributivity if (write && (firstLine != null) && ((summands.length > 1) || ineqs.hasIntegerConstant(index))) { writeLemmaString(firstLine.toString()); firstLine = null; } final Term falseTerm = mTheory.mFalse; // distributivity steps (from right to left) for (int i = summands.length - 1; i >= 0; --i) { // remember inner term ('False' for constants) Term innerTerm; final FactorWrapper summandFactor; /* find factor and inner term */ // positive constant if (summands[i] instanceof ConstantTerm) { innerTerm = falseTerm; final ConstantTerm cTerm = (ConstantTerm)summands[i]; if (cTerm.getValue() instanceof BigInteger) { summandFactor = new FactorWrapper((BigInteger) ((ConstantTerm)summands[i]).getValue()); } else { assert (cTerm.getValue() instanceof BigDecimal); summandFactor = new FactorWrapper((BigDecimal) ((ConstantTerm)summands[i]).getValue()); } if (write && (i > 0)) { writeLemmaString(mStartLine); writeLemmaString(prefix); writeLemmaString("pos"); writeLemmaString(mEndLine); } } else { assert (summands[i] instanceof ApplicationTerm); final ApplicationTerm summand = (ApplicationTerm)summands[i]; // negative summand if (summand.getFunction().getName() == "-") { assert (summand.getParameters().length == 1); innerTerm = summand.getParameters()[0]; // negative constant if (innerTerm instanceof ConstantTerm) { if (((ConstantTerm)innerTerm).getValue() instanceof BigInteger) { summandFactor = new FactorWrapper( ((BigInteger)((ConstantTerm)innerTerm). getValue()).negate()); } else { assert (((ConstantTerm)innerTerm).getValue() instanceof BigDecimal); summandFactor = new FactorWrapper( ((BigDecimal)((ConstantTerm)innerTerm). getValue()).negate()); } innerTerm = falseTerm; } else { // negative variable summandFactor = new FactorWrapper(Rational.ONE.negate()); } if (write) { if (i == 0) { if (factor.compareTo(BigInteger.ZERO) == 1) { writeRule("subst s_plus_minus"); } else { writeRule("subst s_minus_minus"); } } else { writeLemmaString(mStartLine); writeLemmaString(prefix); writeLemmaString("neg"); writeLemmaString(mEndLine); } } } else if (summand.getFunction().getName() == "*") { // variable with factor assert (summand.getParameters().length == 2); // temporarily bind the factor here innerTerm = summand.getParameters()[0]; // positive factor if (innerTerm instanceof ConstantTerm) { final ConstantTerm cTerm = (ConstantTerm)innerTerm; if (cTerm.getValue() instanceof BigInteger) { summandFactor = new FactorWrapper((BigInteger) cTerm.getValue()); } else { assert (cTerm.getValue() instanceof BigDecimal); summandFactor = new FactorWrapper((BigDecimal) cTerm.getValue()); } } else { assert (innerTerm instanceof ApplicationTerm); final ApplicationTerm aInnerTerm = (ApplicationTerm)innerTerm; // negative factor if (aInnerTerm.getFunction().getName() == "-") { assert ((aInnerTerm.getParameters().length == 1) && (aInnerTerm.getParameters()[0] instanceof ConstantTerm)); final ConstantTerm cTerm = (ConstantTerm) aInnerTerm.getParameters()[0]; if (cTerm.getValue() instanceof BigInteger) { summandFactor = new FactorWrapper((BigInteger) cTerm.getValue()); } else { assert (cTerm.getValue() instanceof BigDecimal); summandFactor = new FactorWrapper((BigDecimal) cTerm.getValue()); } summandFactor.negate(); } else { // fraction factor assert ((aInnerTerm.getFunction().getName() == "/") && (aInnerTerm.getParameters().length == 2) && (aInnerTerm.getParameters()[1] instanceof ConstantTerm) && (((ConstantTerm)aInnerTerm. getParameters()[1]). getValue() instanceof BigInteger)); // temporarily bind the factor here innerTerm = aInnerTerm.getParameters()[0]; final BigInteger denominator = (BigInteger)((ConstantTerm) aInnerTerm.getParameters()[1]). getValue(); // negative fraction if (innerTerm instanceof ApplicationTerm) { assert ((((ApplicationTerm)innerTerm). getFunction().getName() == "-") && (((ApplicationTerm)innerTerm). getParameters().length == 1) && (((ApplicationTerm)innerTerm). getParameters()[0] instanceof ConstantTerm) && (((ConstantTerm) ((ApplicationTerm)innerTerm). getParameters()[0]).getValue() instanceof BigInteger)); summandFactor = new FactorWrapper( Rational.valueOf( (BigInteger)((ConstantTerm) ((ApplicationTerm)innerTerm). getParameters()[0]). getValue(), denominator)); summandFactor.negate(); } else { // positive fraction assert ((innerTerm instanceof ConstantTerm) && ((ConstantTerm)innerTerm).getValue() instanceof BigInteger); summandFactor = new FactorWrapper( Rational.valueOf((BigInteger) ((ConstantTerm)innerTerm). getValue(), denominator)); } } } innerTerm = summand.getParameters()[1]; if (write) { if (i == 0) { writeRule("subst s_factor"); } else { writeRule("subst s_dist_factor"); } writeRule(mSimplifier.getRule()); } } else if (summand.getFunction().getName() == "/") { // constant fraction assert ((summand.getParameters().length == 2) && (summand.getParameters()[1] instanceof ConstantTerm) && (((ConstantTerm)summand. getParameters()[1]). getValue() instanceof BigInteger) && (i > 0)); // temporarily bind the factor here innerTerm = summand.getParameters()[0]; final BigInteger denominator = (BigInteger)((ConstantTerm) summand.getParameters()[1]). getValue(); // negative fraction if (innerTerm instanceof ApplicationTerm) { assert ((((ApplicationTerm)innerTerm).getFunction(). getName() == "-") && (((ApplicationTerm)innerTerm).getParameters(). length == 1) && (((ApplicationTerm)innerTerm). getParameters()[0] instanceof ConstantTerm) && (((ConstantTerm) ((ApplicationTerm)innerTerm). getParameters()[0]).getValue() instanceof BigInteger)); summandFactor = new FactorWrapper( Rational.valueOf( (BigInteger)((ConstantTerm) ((ApplicationTerm)innerTerm). getParameters()[0]). getValue(), denominator)); summandFactor.negate(); } else { assert ((innerTerm instanceof ConstantTerm) && ((ConstantTerm)innerTerm).getValue() instanceof BigInteger); summandFactor = new FactorWrapper( Rational.valueOf((BigInteger) ((ConstantTerm)innerTerm). getValue(), denominator)); } // fractions have minus sign inside, so always use pos rule if (write) { writeLemmaString(mStartLine); writeLemmaString(prefix); writeLemmaString("pos"); writeLemmaString(mEndLine); } innerTerm = falseTerm; } else { // positive variable innerTerm = summand; summandFactor = new FactorWrapper(Rational.ONE); if (write && (i > 0)) { writeLemmaString(mStartLine); writeLemmaString(prefix); writeLemmaString("pos"); writeLemmaString(mEndLine); } } } summandFactor.mul(Rational.valueOf(factor, BigInteger.ONE)); // constant if (innerTerm == falseTerm) { map.updateConstant(summandFactor.mFactor); } else { // variable // ignore 'to_real' prefix to talk about the same terms if ((arithType == EArith.mixed) && (innerTerm instanceof ApplicationTerm)) { final ApplicationTerm aTerm = (ApplicationTerm)innerTerm; final String function = aTerm.getFunction().getName(); if (function == "to_real") { assert (aTerm.getParameters().length == 1); map.update(aTerm.getParameters()[0], summandFactor.mFactor); } else { map.update(innerTerm, summandFactor.mFactor); } } else { map.update(innerTerm, summandFactor.mFactor); } } } /* * Only write this in case the distributivity is needed, i.e., there * are at least two summands. This can also be the case if there is a * single term, but the integer constant (1) was inserted. */ if (write && ((summands.length > 1) || ineqs.hasIntegerConstant(index))) { writeRule("rule HOL.refl"); } } /** * This method merges two literals. This is very easy, since the rule * does everything automatically. Only for the last two literals the * rule slightly differs. * * @param ineqs data structure * @param index index * @param fRes farkas result wrapper * @param arithType type of logic * @return 'le_le' iff the sign stays '<=', 'less_less' otherwise */ private FarkasResult mergeLiterals(final IneqInfo ineqs, final int index, final FarkasResult fRes, final EArith arithType) { // keeps track of the type of the sum so far in mixed logic final boolean resultIsInt; writeLemmaString(mStartLine); // special rule for mixed case: adding integer and real literals if (arithType == EArith.mixed) { assert (index > 0); final boolean secondInt = ineqs.mLiterals[index].mIsIntegral; if (fRes.mFirstInt && (!secondInt)) { writeLemmaString("erule ir_merge_ineqs_"); resultIsInt = false; } else if ((!fRes.mFirstInt) && secondInt) { writeLemmaString("erule ri_merge_ineqs_"); resultIsInt = false; } else { writeLemmaString("erule merge_ineqs_"); resultIsInt = true; } } else { writeLemmaString("erule merge_ineqs_"); resultIsInt = true; } final EOrder order; switch (fRes.mOrder) { case le_le: writeLemmaString("le_le"); order = EOrder.le_le; break; case le_less: writeLemmaString("le_less"); order = EOrder.less_less; break; case less_le: writeLemmaString("less_le"); order = EOrder.less_less; break; case less_less: writeLemmaString("less_less"); order = EOrder.less_less; break; default: assert false; throw new IllegalArgumentException( "The ordering type is unknown."); } if (ineqs.isLast(index)) { writeLemmaString("_last"); } writeLemmaString(mEndLine); return new FarkasResult(fRes.mApplyDistributivity, order, fRes.mFirstInt && resultIsInt); } /** * This method computes the simplified sum after merging two literals. * For that a map has been updated with the new factors before, so the * only thing that happens here is extracting the data and writing the * resulting term in Isabelle syntax. * * The equality of the old and the new (simplified) term is proven with * the simplifier. * * @param ineqs data structure * @param index index * @param order order sign of the literals * @param map maps terms to factors */ private void simplify(final IneqInfo ineqs, final int index, final EOrder order, final Var2FactorMap map) { writeLemmaString(mStartLine); if (order == EOrder.le_le) { writeLemmaString("erule simplify_le"); } else { writeLemmaString("erule simplify_less"); } if (ineqs.isLast(index)) { writeLemmaString("_last"); } writeLemmaString(" [where y = \""); mConverter.convertFactorMap( map.mConstant, map.mMap.entrySet(), mLemmaAppendable); writeLemmaString("\"]"); writeLemmaString(mEndLine); writeRule(mSimplifier.getRule()); } /** * This class is used as a data structure for the LA lemma conversion. * It represents the literals in the conjunction of literals (after * applying de Morgan's rule), and the Farkas coefficients. * */ private class IneqInfo { /** * This class represents a literal (see {@link IneqInfo}). * It consists of a summand array and an (in)equality type. The * right-hand-side is known to be always zero. */ private class IneqLiteral { // summands final Term[] mSummands; // type of inequality final EIneqType mIneqType; // integrality cache for not computing it more than once boolean mIsIntegral; /** * @param summands the summands * @param ineqType type of inequality * @param arithType arithType type of logic */ public IneqLiteral(final Term[] summands, final EIneqType ineqType, final EArith arithType) { mSummands = summands; mIneqType = ineqType; if (arithType == EArith.mixed) { final Sort intSort = mTheory.getNumericSort(); mIsIntegral = true; for (int i = 0; i < summands.length; ++i) { if (!summands[i].getSort().equals(intSort)) { mIsIntegral = false; break; } } } else { mIsIntegral = (arithType == EArith.integer); } } /** * This method tells if the literal is negated. * * @return true iff the literal is negated */ public boolean isNegated() { switch (mIneqType) { case neg_le: case neg_less: return true; case pos_le: case pos_less: case eq: return false; default: assert false; throw new IllegalArgumentException( "The ordering type is unknown."); } } @Override public String toString() { final StringBuilder builder = new StringBuilder(); String append = ""; builder.append((isNegated() ? "[(+ " : "[(+ ")); for (int i = 0; i < mSummands.length; ++i) { builder.append(append); append = ", "; builder.append(mSummands[i]); } switch (mIneqType) { case neg_le: case pos_le: builder.append(") <= 0]"); break; case neg_less: case pos_less: builder.append(") < 0]"); break; case eq: builder.append(") = 0]"); break; default: assert false; throw new IllegalArgumentException( "The ordering type is unknown."); } return builder.toString(); } } // literals final IneqLiteral[] mLiterals; // factors final Object[] mFactors; /** * @param length length of the disjuncts * @param factors the Farkas coefficients * @param arithType type of logic */ public IneqInfo(final int length, final Object[] factors, final EArith arithType) { mFactors = factors; mLiterals = new IneqLiteral[factors.length]; } /** * This method tells if an index is the last one in the conjunction. * * @param index index * @return true iff the index is the last one */ public boolean isLast(final int index) { return (index == mLiterals.length - 1); } /** * This method adds a new literal. * * @param index the index * @param literal the literal * @param isNegated true iff literal is negated * @param arithType type of logic */ public void add(final int index, final ApplicationTerm literal, final boolean isNegated, final EArith arithType) { final String function = literal.getFunction().getName(); final EIneqType ineqType; if (function == "<=") { if (isNegated) { ineqType = EIneqType.neg_le; } else { ineqType = EIneqType.pos_le; } } else if (function == "<") { if (isNegated) { ineqType = EIneqType.neg_less; } else { ineqType = EIneqType.pos_less; } } else { assert ((function == "=") && (!isNegated)); ineqType = EIneqType.eq; } // left-hand side is either a sum or a term seen as a variable assert ((literal.getParameters().length == 2) && (literal.getParameters()[0] instanceof ApplicationTerm)); if (((ApplicationTerm)literal.getParameters()[0]).getFunction(). getName() == "+") { mLiterals[index] = new IneqLiteral( ((ApplicationTerm)literal.getParameters()[0]). getParameters(), ineqType, arithType); } else { final Term[] unarySummand = new Term[1]; unarySummand[0] = literal.getParameters()[0]; mLiterals[index] = new IneqLiteral(unarySummand, ineqType, arithType); } } /** * This method gives the Farkas coefficient as a term. * * @param i index * @return Farkas coefficient as a term */ public Term getFactorTerm(final int i) { return (Term)mFactors[i]; } /** * This method indicates whether a literal has inserted the integer * constant 1. * * @param index the index of the literal * @return true iff the literal inserted the integer constant */ public boolean hasIntegerConstant(final int index) { return (mLiterals[index].mIsIntegral) && ((mLiterals[index].mIneqType == EIneqType.pos_less) || (mLiterals[index].mIneqType == EIneqType.neg_le)); } @Override public String toString() { final StringBuilder builder = new StringBuilder(); String append = ""; builder.append('{'); for (int i = 0; i < mLiterals.length; ++i) { builder.append(append); append = ", "; builder.append(mFactors[i]); builder.append(" * "); builder.append(mLiterals[i]); } builder.append('}'); return builder.toString(); } } /** * This enum gives the type of linear inequality of the LA lemma literals. */ private enum EIneqType { /** * positive less-equal */ pos_le, /** * negative less-equal */ neg_le, /** * positive less */ pos_less, /** * negative less */ neg_less, /** * positive equality */ eq, } /** * type of arithmetic logics */ private enum EArith { integer, real, mixed; } /** * type of order signs ('<' vs. '<=') of the first and the second literal */ private enum EOrder { /** * <=, <= */ le_le, /** * <=, < */ le_less, /** * <, <= */ less_le, /** * <, < */ less_less } /** * This method converts the order sign according to the next one. * * @param order the old order * @param isLe true iff second order sign shall become '<=' * @return changed order */ private EOrder convertOrder(final EOrder order, final boolean isLe) { switch (order) { case le_le: if (!isLe) { return EOrder.le_less; } break; case le_less: if (isLe) { return EOrder.le_le; } break; case less_le: if (!isLe) { return EOrder.less_less; } break; case less_less: if (isLe) { return EOrder.less_le; } break; default: assert false; throw new IllegalArgumentException( "The ordering type is unknown."); } return order; } /** * This class wraps the result of the methods, especially inserting Farkas * coefficients, since Java uses call-by-value for booleans and enums. */ private class FarkasResult { // true iff distributivity has to be used final boolean mApplyDistributivity; // order sign of the literals final EOrder mOrder; // true iff sum so far is of type integer in mixed logic final boolean mFirstInt; /** * @param applyDistributivity true iff distributivity has to be used * @param order order sign of the literals * @param firstInt true iff sum so far is of type integer (mixed logic) */ public FarkasResult(final boolean applyDistributivity, final EOrder order, final boolean firstInt) { mApplyDistributivity = applyDistributivity; mOrder = order; mFirstInt = firstInt; } @Override public String toString() { return "(" + mApplyDistributivity + ", " + mOrder + ")"; } } /** * This class represents a factor of a variable in LA literals. * It is mainly used to convert integers and decimals with polymorphism. */ class FactorWrapper { // factor Rational mFactor; /** * @param bigInt the integer factor */ public FactorWrapper(final BigInteger bigInt) { mFactor = toRational(bigInt); } /** * @param bigDec the decimal factor */ public FactorWrapper(final BigDecimal bigDec) { mFactor = toRational(bigDec); } /** * @param rational the rational factor */ public FactorWrapper(final Rational rational) { mFactor = rational; } /** * This method converts a BigInteger to a Rational. * * @param bigInt the BigInteger number * @return the converted Rational */ private Rational toRational(final BigInteger bigInt) { return Rational.valueOf(bigInt, BigInteger.ONE); } /** * This method converts a BigDecimal to a Rational. * * @param bigDec the BigDecimal number * @return the converted Rational */ private Rational toRational(final BigDecimal bigDec) { final int scale = bigDec.scale(); if (scale == 0) { return Rational.valueOf(bigDec.toBigIntegerExact(), BigInteger.ONE); } else { assert (scale > 0); return Rational.valueOf( bigDec.scaleByPowerOfTen(scale).toBigIntegerExact(), BigInteger.TEN.pow(scale)); } } /** * This method tells if the rational number is integral. * * @return true iff the rational is integral */ public boolean isIntegral() { return mFactor.isIntegral(); } /** * This method adds a number to the factor. * * @param summand the additional factor */ public void add(final Rational summand) { mFactor = mFactor.add(summand); } /** * This method negates the value of the factor. */ public void negate() { assert (!mFactor.isNegative()); mFactor = mFactor.negate(); } /** * This method multiplies the factor with another factor. * * @param factor the additional factor */ public void mul(Rational factor) { mFactor = mFactor.mul(factor); } @Override public String toString() { return mFactor.toString(); } } /** * This class abstracts a map from terms to factors. */ class Var2FactorMap { // map: term -> factor final HashMap<Term, FactorWrapper> mMap; // constant Rational mConstant; public Var2FactorMap() { mMap = new HashMap<Term, FactorWrapper>(); mConstant = Rational.ZERO; } /** * This method updates the constant number (addition). * * @param summand summand */ public void updateConstant(final Rational summand) { mConstant = mConstant.add(summand); } /** * This method updates the factor in the map associated to the given * term (addition), if it exists, or inserts it otherwise. * * @param term term * @param summand summand */ public void update(final Term term, final Rational summand) { FactorWrapper wrapper = mMap.get(term); if (wrapper == null) { wrapper = new FactorWrapper(summand); mMap.put(term, wrapper); } else { wrapper.add(summand); } } @Override public String toString() { final StringBuilder builder = new StringBuilder(); builder.append('{'); if (!mMap.isEmpty()) { for (final Map.Entry<Term, FactorWrapper> tuple : mMap.entrySet()) { builder.append(tuple); builder.append(", "); } } builder.append("constant="); builder.append(mConstant.toString()); builder.append('}'); return builder.toString(); } } /** * This method writes a string to the lemma appendable. * * @param string string that is written * @throws RuntimeException thrown if an IOException is caught */ private void writeLemmaString(String string) { try { mLemmaAppendable.append(string); } catch (final IOException e) { throw new RuntimeException("Appender throws IOException", e); } } /** * This method is used to have shorter code with the fast proof option. * * @param rule the rule string */ private void writeRule(String rule) { writeLemmaString(mStartLine); writeLemmaString(rule); writeLemmaString(mEndLine); } }