/*
* 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.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map.Entry;
import de.uni_freiburg.informatik.ultimate.logic.AnnotatedTerm;
import de.uni_freiburg.informatik.ultimate.logic.Annotation;
import de.uni_freiburg.informatik.ultimate.logic.FormulaUnLet;
import de.uni_freiburg.informatik.ultimate.logic.Logics;
import de.uni_freiburg.informatik.ultimate.logic.SMTLIBException;
import de.uni_freiburg.informatik.ultimate.logic.Sort;
import de.uni_freiburg.informatik.ultimate.logic.Term;
import de.uni_freiburg.informatik.ultimate.logic.TermVariable;
import de.uni_freiburg.informatik.ultimate.smtinterpol.smtlib2.SMTInterpol;
/**
* This is the main controller to check the correctness of an SMTInterpol proof
* by translating and transferring it to Isabelle.
*
* NOTE: Quantifiers were not supported up until this implementation, so they
* are not supported. However, the most important places that must be changed
* to support them are marked with '<quantifiers>'
* (ProofConverter, TermConverter, and SubstitutionConverter only).
*
* @author Christian Schilling <schillic@informatik.uni-freiburg.de>
*/
public class ProofChecker extends SMTInterpol {
// name of the parsed file (needed for output)
private final String mFileName;
// name of the lemma theory file
private final String mLemmaFileName;
// files for the proof to be written to
private final BufferedWriter mFile, mLemmaFile;
// term converter
private TermConverter mConverter;
// proof converter
private ProofConverter mProofConverter;
// true iff Isabelle shall be invoked directly during the process
private final boolean mUseIsabelle;
// true iff output file is printed in more convenient human-readable way
private final boolean mPrettyOutput;
// true iff fast proofs shall be printed
private final boolean mFastProofs;
// true iff only the partial proof is given
private final boolean mPartialProof;
// set of already bound :named definitions (for more than one check-sat)
private final HashSet<String> mNamedSet;
// number, prefix, and map for the assertions
private int mAssertionNumber;
protected static final String ASSERTION_PREFIX = "smt_prm";
private final HashMap<Term, Integer> mAssertion2index;
protected static final String PARTIAL_ANNOTATION = ":named";
/**
* @param fileName name of the parsed file
* @param useIsabelle true iff Isabelle shall be invoked as well
* @param prettyOutput true iff output file shall be
*/
public ProofChecker(final String fileName, final boolean useIsabelle,
final boolean prettyOutput, final boolean fastProofs,
final boolean partialProof) {
super();
// write Isabelle files
mFileName = fileName;
mLemmaFileName = fileName + "_lemma";
try {
final File isaFile = createIsabelleFile();
mFile = new BufferedWriter(new FileWriter(isaFile));
final File lemmaFile = createTheoryFile();
mLemmaFile = new BufferedWriter(new FileWriter(lemmaFile));
} catch (final IOException e) {
throw new RuntimeException(e);
}
mUseIsabelle = useIsabelle;
mPrettyOutput = prettyOutput;
mFastProofs = fastProofs;
mPartialProof = partialProof;
mNamedSet = new HashSet<String>();
mAssertionNumber = 0;
mAssertion2index = mPartialProof
? null
: new HashMap<Term, Integer>();
// set proof producing options (never overwritten)
if (mPartialProof) {
super.setOption(":produce-interpolants", true);
super.setOption(":produce-proofs", false);
} else {
super.setOption(":produce-proofs", true);
}
super.setOption(":interactive-mode", true);
}
/**
* {@inheritDoc}
*
* The method is overwritten to prohibit change of proof production.
*/
@Override
public void setOption(String opt, Object value)
throws UnsupportedOperationException, SMTLIBException {
if ((opt.equals(":produce-proofs"))
|| (opt.equals(":interactive-mode"))
|| (opt.equals(":produce-interpolants"))) {
return;
}
super.setOption(opt, value);
}
/**
* NOTE: Setting the logics more than once messes up the Isabelle files.
*/
@Override
public void setLogic(Logics logic) {
super.setLogic(logic);
// write Isabelle header
try {
// import linear arithmetic theory only when necessary
final String imports;
if (logic.isArithmetic()) {
imports = "XLinearArithmetic";
} else {
imports = "XBool";
}
// main file
mFile.append("theory SMTTheory\nimports ");
mFile.append(imports);
mFile.append(" ");
mFile.append(mLemmaFileName);
mFile.append("\nbegin\n");
// lemma file
mLemmaFile.append("theory ");
mLemmaFile.append(mLemmaFileName);
mLemmaFile.append("\nimports ");
mLemmaFile.append(imports);
mLemmaFile.append("\nbegin\n");
} catch (final IOException e) {
throw new RuntimeException(e);
}
// set up the term converter
mConverter = new TermConverter(mFile, logic, mPrettyOutput);
mProofConverter = new ProofConverter(mFile, super.getTheory(),
mPrettyOutput, mConverter, mFastProofs, mPartialProof,
mLemmaFile);
}
@Override
public void defineFun(String fun, TermVariable[] params, Sort resultSort,
Term definition) throws SMTLIBException {
super.defineFun(fun, params, resultSort, definition);
assert (mConverter != null);
final String name = mConverter.toCompatibleString(fun);
final String arrow = mPrettyOutput ? "\\<Rightarrow>" : "=>";
try {
mLemmaFile.append("\ndefinition ");
mLemmaFile.append(name);
mLemmaFile.append("::\"");
for (int i = 0; i < params.length; ++i) {
mLemmaFile.append(mConverter.getSingleSortString(
params[i].getDeclaredSort()));
mLemmaFile.append(arrow);
}
mLemmaFile.append(
mConverter.getSingleSortString(resultSort));
mLemmaFile.append("\" where ");
mLemmaFile.append(name);
mLemmaFile.append("_def:\"");
mLemmaFile.append(name);
mLemmaFile.append(" == ");
if (params.length > 0) {
String append = "%";
for (int i = 0; i < params.length; ++i) {
mLemmaFile.append(append);
append = " ";
mConverter.convertToAppendable(params[i], mLemmaFile);
}
mLemmaFile.append(". ");
}
mConverter.convertToAppendable(definition, mLemmaFile);
mLemmaFile.append("\"\n");
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
/**
* {@inheritDoc}
*
* The term must be recognized later in @asserted proof nodes
* In the extended proof mode, the term is put in a hash map.
* In the partial proof mode the term is annotated with a unique string,
* since it may change.
*/
@Override
public LBool assertTerm(Term term) throws SMTLIBException,
UnsupportedOperationException {
if (mPartialProof) {
if (term instanceof AnnotatedTerm) {
final AnnotatedTerm aTerm = (AnnotatedTerm)term;
// unpack :named annotation and pack it into a new one
if ((aTerm.getAnnotations().length == 1)
&& (aTerm.getAnnotations()[0].getKey().
equals(PARTIAL_ANNOTATION))) {
term = aTerm.getSubterm();
}
}
return super.assertTerm(annotate(term,
new Annotation(PARTIAL_ANNOTATION, ASSERTION_PREFIX
+ ++mAssertionNumber)));
}
mAssertion2index.put(new FormulaUnLet().unlet(term),
++mAssertionNumber);
return super.assertTerm(term);
}
/**
* {@inheritDoc}
*
* The method is overwritten to extract the proof.
* Both the theorem and the proof are translated into a form Isabelle
* understands and then Isabelle checks the proof.
*/
@Override
public LBool checkSat() throws SMTLIBException {
final LBool result = super.checkSat();
// write unsatisfiability proof
if (result == LBool.UNSAT) {
final Term proof = super.getProof();
try {
// convert theorem and proof to a form Isabelle understands
convertTheorem();
mProofConverter.convert(proof, mAssertion2index);
} catch (final IOException e) {
e.printStackTrace();
// close the writer before returning
try {
mFile.close();
mLemmaFile.close();
} catch (final IOException eWriter) {
eWriter.printStackTrace();
}
}
}
return result;
}
@Override
public void exit() {
// write last line and close files
try {
mFile.append("\nend");
mFile.close();
mLemmaFile.append("\nend");
mLemmaFile.close();
// call Isabelle on the proof file
if (mUseIsabelle) {
callIsabelle(createIsabelleFile().getName());
}
} catch (final IOException e) {
e.printStackTrace();
}
super.exit();
}
/**
* This method adds a function declaration to the lemma file.
*
* @param fun the function name
* @param paramSorts the parameter sorts
* @param resultSort the result sort
* @param lineFeed true iff a line feed should be printed
*/
private void declareConst(final String fun, final Sort[] paramSorts,
final Sort resultSort, final boolean lineFeed) {
assert (mConverter != null);
final String name = mConverter.toCompatibleString(fun);
final String arrow = mPrettyOutput ? "\\<Rightarrow>" : "=>";
try {
mLemmaFile.append("consts ");
mLemmaFile.append(name);
mLemmaFile.append("::\"");
for (int i = 0; i < paramSorts.length; ++i) {
mLemmaFile.append(
mConverter.getSingleSortString(paramSorts[i]));
mLemmaFile.append(arrow);
}
mLemmaFile.append(
mConverter.getSingleSortString(resultSort));
if (lineFeed) {
mLemmaFile.append("\"\n");
} else {
mLemmaFile.append("\"");
}
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
/**
* This method converts an SMT-LIB theorem to an Isabelle theorem.
* The SMT-LIB theorem is given in the form of asserted formulae.
*
* @throws IOException thrown by appendable
*/
public void convertTheorem() throws IOException {
// theorem
mFile.append("\ntheorem\n");
// antecedents
mFile.append("assumes ");
String append = "";
if (mPartialProof) {
for (final Term term : super.getAssertions()) {
mFile.append(append);
append = "and ";
// give the premise a name
assert (term instanceof AnnotatedTerm);
final AnnotatedTerm aTerm = (AnnotatedTerm)term;
final Annotation[] annotations = aTerm.getAnnotations();
assert (annotations.length == 1);
assert annotations[0].getKey().equals(PARTIAL_ANNOTATION);
mFile.append(annotations[0].getValue().toString());
mFile.append(": \"");
final LinkedList<NamedWrapper> namedFuns =
mConverter.convertAssertion(aTerm.getSubterm());
// add functions bound by :named annotation
for (final NamedWrapper wrapper : namedFuns) {
addNamedBond(wrapper);
}
mFile.append("\"\n");
}
} else {
assert (mAssertion2index.size() <= super.getAssertions().length);
for (final Entry<Term, Integer> entry : mAssertion2index.entrySet()) {
mFile.append(append);
append = "and ";
mFile.append(ASSERTION_PREFIX);
mFile.append(Integer.toString(entry.getValue()));
mFile.append(": \"");
final LinkedList<NamedWrapper> namedFuns =
mConverter.convertAssertion(entry.getKey());
// add functions bound by :named annotation
for (final NamedWrapper wrapper : namedFuns) {
addNamedBond(wrapper);
}
mFile.append("\"\n");
}
}
// consequent
mFile.append("shows \"False\"\n");
}
/**
* This method adds a function definition via the :named annotation
* to the Isabelle lemma file.
*
* @param wrapper function wrapper
*/
private void addNamedBond(final NamedWrapper wrapper) {
// ignore already defined terms
if (!mNamedSet.add(wrapper.mName)) {
return;
}
// constant declaration
declareConst(wrapper.mName, new Sort[0],
wrapper.mSubterm.getSort(), false);
// constant definition
assert (mConverter != null);
final String name = mConverter.toCompatibleString(wrapper.mName);
try {
mLemmaFile.append(" defs ");
mLemmaFile.append(name);
mLemmaFile.append("_def: \"");
mLemmaFile.append(name);
mLemmaFile.append(" == ");
mConverter.convertWithTypes(wrapper.mSubterm, mLemmaFile);
mLemmaFile.append("\"\n");
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
/**
* This method creates a file for Isabelle to the disc.
*
* @return empty file
* @throws IOException thrown iff creation fails
*/
private File createIsabelleFile() throws IOException {
return new File(mFileName + ".thy");
}
/**
* This method creates a file for the lemmata to the disc.
*
* @return empty file
* @throws IOException thrown iff creation fails
*/
private File createTheoryFile() throws IOException {
return new File(mLemmaFileName + ".thy");
}
/**
* This method calls Isabelle on the written proof file and receives the
* result (which is: has Isabelle accepted the proof or not).
*
* @param filename the file name
* @throws IOException thrown iff reading Isabelle output fails
*/
private void callIsabelle(String filename) throws IOException {
Runtime.getRuntime().exec(new String[]{"isabelle", "emacs", filename});
}
}