/*
* Copyright (C) 2019 Matthias Heizmann (heizmann@informatik.uni-freiburg.de)
* Copyright (C) 2019 University of Freiburg
*
* This file is part of the ULTIMATE ModelCheckerUtils Library.
*
* The ULTIMATE ModelCheckerUtils Library 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.
*
* The ULTIMATE ModelCheckerUtils Library 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 the ULTIMATE ModelCheckerUtils Library. If not, see .
*
* Additional permission under GNU GPL version 3 section 7:
* If you modify the ULTIMATE ModelCheckerUtils Library, or any covered work, by linking
* or combining it with Eclipse RCP (or a modified version of Eclipse RCP),
* containing parts covered by the terms of the Eclipse Public License, the
* licensors of the ULTIMATE ModelCheckerUtils Library grant you additional permission
* to convey the resulting work.
*/
package de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.smt;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import de.uni_freiburg.informatik.ultimate.core.model.services.ILogger;
import de.uni_freiburg.informatik.ultimate.core.model.services.IUltimateServiceProvider;
import de.uni_freiburg.informatik.ultimate.lib.smtlibutils.ManagedScript;
import de.uni_freiburg.informatik.ultimate.lib.smtlibutils.SmtUtils;
import de.uni_freiburg.informatik.ultimate.lib.smtlibutils.SmtUtils.SimplificationTechnique;
import de.uni_freiburg.informatik.ultimate.lib.smtlibutils.Substitution;
import de.uni_freiburg.informatik.ultimate.lib.smtlibutils.normalforms.NnfTransformer;
import de.uni_freiburg.informatik.ultimate.lib.smtlibutils.normalforms.NnfTransformer.QuantifierHandling;
import de.uni_freiburg.informatik.ultimate.lib.smtlibutils.quantifier.PartialQuantifierElimination;
import de.uni_freiburg.informatik.ultimate.lib.smtlibutils.quantifier.PrenexNormalForm;
import de.uni_freiburg.informatik.ultimate.lib.smtlibutils.quantifier.QuantifierPusher.PqeTechniques;
import de.uni_freiburg.informatik.ultimate.lib.smtlibutils.quantifier.QuantifierSequence;
import de.uni_freiburg.informatik.ultimate.lib.smtlibutils.quantifier.QuantifierUtils;
import de.uni_freiburg.informatik.ultimate.lib.smtlibutils.quantifier.QuantifierSequence.QuantifiedVariables;
import de.uni_freiburg.informatik.ultimate.logic.FormulaUnLet;
import de.uni_freiburg.informatik.ultimate.logic.QuantifiedFormula;
import de.uni_freiburg.informatik.ultimate.logic.SMTLIBException;
import de.uni_freiburg.informatik.ultimate.logic.Script;
import de.uni_freiburg.informatik.ultimate.logic.Term;
import de.uni_freiburg.informatik.ultimate.logic.TermVariable;
import de.uni_freiburg.informatik.ultimate.logic.WrapperScript;
/**
* Wrapper for an SMT solver that makes sure that the solver does not have to deal with quantifiers. The idea of this
* class is that we overapproximate asserted formulas if they contain quantifiers (e.g., we replace the formula by
* "true"). If the SMT solver responds with 'sat' to a check-sat command we return unknown if we overapproximated an
* asserted formula. If we did not overapproximate or if the response was 'unsat' we may pass the response of the
* solver.
*
* TODO As an optimization we may also try very inexpensive quantifier elimination techniques or skolemization. TODO
* list these once these are implemented
*
*
* @author Matthias Heizmann (heizmann@informatik.uni-freiburg.de)
* @author Max Barth (Max.Barth95@gmx.de)
*
*/
public class QuantifierOverapproximatingSolver extends WrapperScript {
private final IUltimateServiceProvider mServices;
private final ManagedScript mMgdScript;
private final Stack mOverapproxiamtionStack;
private boolean mInUsatCoreMode;
private Set mAdditionalUnsatCoreContent;
public QuantifierOverapproximatingSolver(final IUltimateServiceProvider services, final ILogger logger,
final Script script) {
super(script);
mServices = services;
mMgdScript = new ManagedScript(services, script);
mOverapproxiamtionStack = new Stack<>();
mOverapproxiamtionStack.push(false);
}
@Override
public void setOption(final String opt, final Object value) throws UnsupportedOperationException, SMTLIBException {
if (opt.equals(":produce-unsat-cores")) {
final Boolean b = (Boolean) value;
if (b) {
mInUsatCoreMode = true;
assert mAdditionalUnsatCoreContent == null;
mAdditionalUnsatCoreContent = new HashSet<>();
}
}
mScript.setOption(opt, value);
}
@Override
public void push(final int levels) throws SMTLIBException {
for (int i = 0; i < levels; i++) {
mOverapproxiamtionStack.push(false);
}
mScript.push(levels);
}
@Override
public void pop(final int levels) throws SMTLIBException {
// TODO check if there is still an overapproximated term on the assertion stack (optimization)
for (int i = 0; i < levels; i++) {
mOverapproxiamtionStack.pop();
}
mScript.pop(levels);
}
private Term skolemizeOA(final QuantifiedFormula inputFormula) {
final QuantifierSequence qs = new QuantifierSequence(mMgdScript, inputFormula);
final QuantifiedVariables qb = qs.getQuantifierBlocks().get(0);
Term newInnerTerm = qs.getInnerTerm();
for (int block = 0; block < qs.getQuantifierBlocks().size(); block++) {
final List qVar = qs.getQuantifierBlocks();
if (qVar.get(block).getQuantifier() == QuantifiedFormula.EXISTS && block == 0) {
final Map subMap = new HashMap<>();
for (final TermVariable qtv : qb.getVariables()) {
final TermVariable ctv = mMgdScript.constructFreshTermVariable("skolemconst", qtv.getSort());
final Term newConst = SmtUtils.termVariable2constant(mMgdScript.getScript(), ctv, true);
subMap.put(qtv, newConst);
}
newInnerTerm = Substitution.apply(mMgdScript, subMap, newInnerTerm);
}
}
final Term reAddQuantifiers = QuantifierSequence.prependQuantifierSequence(mMgdScript.getScript(),
qs.getQuantifierBlocks(), newInnerTerm);
return reAddQuantifiers;
}
private Term overApproximate(final Term term) {
final Term nnf = new NnfTransformer(mMgdScript, mServices, QuantifierHandling.KEEP).transform(term);
// Optimization 2
final Term pushed = PartialQuantifierElimination.eliminateCompat(mServices, mMgdScript, true,
PqeTechniques.ALL_LOCAL, SimplificationTechnique.NONE, nnf);
Term qfree = mMgdScript.getScript().term("true");
for (Term cojunct : SmtUtils.getConjuncts(pushed)) {
if (!QuantifierUtils.isQuantifierFree(cojunct)) {
// Optimization 3
final Term pnfTerm = new PrenexNormalForm(mMgdScript).transform(cojunct);
if (!QuantifierUtils.isQuantifierFree(pnfTerm)) {
final Term snfTerm = skolemizeOA((QuantifiedFormula) pnfTerm);
if (snfTerm instanceof QuantifiedFormula) {
cojunct = mMgdScript.getScript().term("true");
} else {
cojunct = snfTerm;
}
}
}
qfree = SmtUtils.and(mMgdScript.getScript(), cojunct, qfree);
}
return qfree;
}
@Override
public LBool assertTerm(final Term term) throws SMTLIBException {
Term withoutLet = new FormulaUnLet().transform(term);
if (!QuantifierUtils.isQuantifierFree(withoutLet)) {
// there is an overapproxiamtion on the current level
if (mOverapproxiamtionStack.peek() == false) {
mOverapproxiamtionStack.pop();
mOverapproxiamtionStack.push(true);
}
withoutLet = overApproximate(withoutLet);
}
final LBool result = mScript.assertTerm(withoutLet);
if (result.equals(LBool.SAT) && wasSomeAssertedTermOverapproximated()) {
return LBool.UNKNOWN;
}
return result;
}
private boolean wasSomeAssertedTermOverapproximated() {
return mOverapproxiamtionStack.stream().anyMatch(x -> x == true);
}
@Override
public LBool checkSat() throws SMTLIBException {
final LBool result = mScript.checkSat();
if (result.equals(LBool.SAT) && wasSomeAssertedTermOverapproximated()) {
return LBool.UNKNOWN;
}
if (mInUsatCoreMode && result.equals(LBool.UNSAT)) {
final Term[] uc = getUnsatCore();
mAdditionalUnsatCoreContent.addAll(Arrays.asList(uc));
}
return result;
}
public boolean isInUsatCoreMode() {
return mInUsatCoreMode;
}
public Set getAdditionalUnsatCoreContent() {
return mAdditionalUnsatCoreContent;
}
}