diff --git a/.idea/misc.xml b/.idea/misc.xml
index 79d44e380e..b9159b5c43 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,19 +1,5 @@
-
-
-
-
-
-
@@ -54,4 +40,4 @@ SPDX-License-Identifier: Apache-2.0
-
+
\ No newline at end of file
diff --git a/src/org/sosy_lab/java_smt/api/InterpolatingProverEnvironment.java b/src/org/sosy_lab/java_smt/api/InterpolatingProverEnvironment.java
index 11767bcf4d..6ac45b33e5 100644
--- a/src/org/sosy_lab/java_smt/api/InterpolatingProverEnvironment.java
+++ b/src/org/sosy_lab/java_smt/api/InterpolatingProverEnvironment.java
@@ -24,7 +24,6 @@
* @param The type of the objects which can be used to select formulas for interpolant creation.
*/
public interface InterpolatingProverEnvironment extends BasicProverEnvironment {
-
/**
* Get an interpolant for two groups of formulas. This should be called only immediately after an
* {@link #isUnsat()} call that returned true.
diff --git a/src/org/sosy_lab/java_smt/api/SolverContext.java b/src/org/sosy_lab/java_smt/api/SolverContext.java
index 6658e57c9e..95958966a0 100644
--- a/src/org/sosy_lab/java_smt/api/SolverContext.java
+++ b/src/org/sosy_lab/java_smt/api/SolverContext.java
@@ -58,7 +58,37 @@ enum ProverOptions {
GENERATE_UNSAT_CORE_OVER_ASSUMPTIONS,
/** Whether the solver should enable support for formulae build in SL theory. */
- ENABLE_SEPARATION_LOGIC
+ ENABLE_SEPARATION_LOGIC,
+
+ /**
+ * Enables Craig interpolation, using the model-based interpolation strategy. This strategy
+ * constructs interpolants based on the model provided by a solver, i.e. model generation must
+ * be enabled. This interpolation strategy is only usable for solvers supporting quantified
+ * solving over the theories interpolated upon. The solver does not need to support
+ * interpolation itself.
+ */
+ GENERATE_PROJECTION_BASED_INTERPOLANTS,
+
+ /**
+ * Enables (uniform) Craig interpolation, using the quantifier-based interpolation strategy
+ * utilizing quantifier-elimination in the forward direction. Forward means, that the set of
+ * formulas A, used to interpolate, interpolates towards the set of formulas B (B == all
+ * formulas that are currently asserted, but not in the given set of formulas A used to
+ * interpolate). This interpolation strategy is only usable for solvers supporting
+ * quantifier-elimination over the theories interpolated upon. The solver does not need to
+ * support interpolation itself.
+ */
+ GENERATE_UNIFORM_FORWARD_INTERPOLANTS,
+
+ /**
+ * Enables (uniform) Craig interpolation, using the quantifier-based interpolation strategy
+ * utilizing quantifier-elimination in the backward direction. Backward means, that the set of
+ * formulas B (B == all formulas that are currently asserted, but not in the given set of
+ * formulas A used to interpolate) interpolates towards the set of formulas A. This
+ * interpolation strategy is only usable for solvers supporting quantifier-elimination over the
+ * theories interpolated upon. The solver does not need to support interpolation itself.
+ */
+ GENERATE_UNIFORM_BACKWARD_INTERPOLANTS
}
/**
@@ -73,7 +103,6 @@ enum ProverOptions {
/**
* Create a fresh new {@link InterpolatingProverEnvironment} which encapsulates an assertion stack
* and allows generating and retrieve interpolants for unsatisfiable formulas. If the SMT solver
- * is able to handle satisfiability tests with assumptions please consider implementing the {@link
* InterpolatingProverEnvironment} interface, and return an Object of this type here.
*
* @param options Options specified for the prover environment. All the options specified in
diff --git a/src/org/sosy_lab/java_smt/basicimpl/AbstractProver.java b/src/org/sosy_lab/java_smt/basicimpl/AbstractProver.java
index 2b588345af..e484346b1d 100644
--- a/src/org/sosy_lab/java_smt/basicimpl/AbstractProver.java
+++ b/src/org/sosy_lab/java_smt/basicimpl/AbstractProver.java
@@ -254,6 +254,28 @@ protected ImmutableSet getAssertedFormulas() {
return builder.build();
}
+ /**
+ * @param nativeFormulasOfA a group of formulas that has been asserted and is to be interpolated
+ * against.
+ * @return The de-duplicated collection of the 2 interpolation groups currently asserted as {@link
+ * BooleanFormula}s.
+ */
+ protected InterpolationGroups getInterpolationGroups(Collection nativeFormulasOfA) {
+ ImmutableSet.Builder formulasOfA = ImmutableSet.builder();
+ ImmutableSet.Builder formulasOfB = ImmutableSet.builder();
+ for (Multimap assertedFormulasPerLevel : assertedFormulas) {
+ for (Entry assertedFormulaAndItpPoint :
+ assertedFormulasPerLevel.entries()) {
+ if (nativeFormulasOfA.contains(assertedFormulaAndItpPoint.getValue())) {
+ formulasOfA.add(assertedFormulaAndItpPoint.getKey());
+ } else {
+ formulasOfB.add(assertedFormulaAndItpPoint.getKey());
+ }
+ }
+ }
+ return InterpolationGroups.of(formulasOfA.build(), formulasOfB.build());
+ }
+
protected ImmutableSet getAssertedConstraintIds() {
ImmutableSet.Builder builder = ImmutableSet.builder();
for (Multimap level : assertedFormulas) {
@@ -303,4 +325,31 @@ public void close() {
closeAllEvaluators();
closed = true;
}
+
+ /** Provides the set of BooleanFormulas to interpolate on. */
+ public static final class InterpolationGroups {
+ private final Collection formulasOfA;
+ private final Collection formulasOfB;
+
+ private InterpolationGroups(
+ Collection pFormulasOfA, Collection pFormulasOfB) {
+ Preconditions.checkNotNull(pFormulasOfA);
+ Preconditions.checkNotNull(pFormulasOfB);
+ formulasOfA = pFormulasOfA;
+ formulasOfB = pFormulasOfB;
+ }
+
+ public static InterpolationGroups of(
+ Collection pFormulasOfA, Collection pFormulasOfB) {
+ return new InterpolationGroups(pFormulasOfA, pFormulasOfB);
+ }
+
+ public Collection getFormulasOfA() {
+ return formulasOfA;
+ }
+
+ public Collection getFormulasOfB() {
+ return formulasOfB;
+ }
+ }
}
diff --git a/src/org/sosy_lab/java_smt/basicimpl/AbstractSolverContext.java b/src/org/sosy_lab/java_smt/basicimpl/AbstractSolverContext.java
index f92574f031..a743ff6c62 100644
--- a/src/org/sosy_lab/java_smt/basicimpl/AbstractSolverContext.java
+++ b/src/org/sosy_lab/java_smt/basicimpl/AbstractSolverContext.java
@@ -8,12 +8,16 @@
package org.sosy_lab.java_smt.basicimpl;
+import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableSet;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.sosy_lab.java_smt.api.FormulaManager;
import org.sosy_lab.java_smt.api.InterpolatingProverEnvironment;
import org.sosy_lab.java_smt.api.OptimizationProverEnvironment;
@@ -54,9 +58,10 @@ public final ProverEnvironment newProverEnvironment(ProverOptions... options) {
public final InterpolatingProverEnvironment> newProverEnvironmentWithInterpolation(
ProverOptions... options) {
+ // TODO: unify InterpolatingProverDelegate with IndependentInterpolatingSolverDelegate and
+ // InterpolatingSolverDelegate!
InterpolatingProverEnvironment> out =
- new InterpolatingProverDelegate<>(newProverEnvironmentWithInterpolation0(toSet(options)));
-
+ new InterpolatingProverDelegate<>(newProverEnvironmentWithInterpolation1(toSet(options)));
if (!supportsAssumptionSolving()) {
// In the case we do not already have a prover environment with assumptions,
// we add a wrapper to it
@@ -65,6 +70,32 @@ public final InterpolatingProverEnvironment> newProverEnvironmentWithInterpola
return out;
}
+ @SuppressWarnings({"ResultOfMethodCallIgnored", "resource"})
+ private InterpolatingProverEnvironment> newProverEnvironmentWithInterpolation1(
+ Set options) {
+ InterpolatingProverEnvironment> out;
+ // Try to get a new prover environment w native interpolation with the current options
+ try {
+ out = newProverEnvironmentWithInterpolation0(options);
+ } catch (UnsupportedOperationException e) {
+ // Check if QuantifiedFormulaManager is available before attempting independent interpolation
+ try {
+ getFormulaManager().getQuantifiedFormulaManager();
+ } catch (UnsupportedOperationException error) {
+ e.addSuppressed(error);
+ throw e;
+ }
+ // If native interpolation is not available, we wrap a normal prover such that it returns
+ // interpolation points
+ ProverEnvironment normalProver = newProverEnvironment0(options);
+ // TODO: only allow this if there is a quantified formula manager available!
+ out = new InterpolatingSolverDelegate(normalProver, options);
+ }
+
+ // TODO: do we need the assumptions inside of the interpolation delegate?
+ return new IndependentInterpolatingSolverDelegate<>(this, out, options);
+ }
+
protected abstract InterpolatingProverEnvironment> newProverEnvironmentWithInterpolation0(
Set pSet);
@@ -95,6 +126,42 @@ protected abstract OptimizationProverEnvironment newOptimizationProverEnvironmen
*/
protected abstract boolean supportsAssumptionSolving();
+ private static final Set ALL_INDEPENDENT_INTERPOLATION_STRATEGIES =
+ ImmutableSet.of(
+ ProverOptions.GENERATE_PROJECTION_BASED_INTERPOLANTS,
+ ProverOptions.GENERATE_UNIFORM_BACKWARD_INTERPOLANTS,
+ ProverOptions.GENERATE_UNIFORM_FORWARD_INTERPOLANTS);
+
+ protected boolean useNativeInterpolation(Set options) {
+ return getIndependentInterpolationStrategy(options) == null;
+ }
+
+ @SuppressWarnings("CheckReturnValue")
+ protected @Nullable ProverOptions getIndependentInterpolationStrategy(
+ Set options) {
+ List itpStrat = new ArrayList<>(options);
+ itpStrat.retainAll(ALL_INDEPENDENT_INTERPOLATION_STRATEGIES);
+
+ if (itpStrat.isEmpty()) {
+ return null;
+ } else if (itpStrat.size() != 1) {
+ throw new IllegalArgumentException(
+ "Only a single independent interpolation strategy can be"
+ + " chosen for a prover, but chosen were: "
+ + Joiner.on(", ").join(options));
+ }
+
+ ProverOptions interpolationOption = itpStrat.get(0);
+ try {
+ fmgr.getQuantifiedFormulaManager();
+ } catch (UnsupportedOperationException e) {
+ throw new UnsupportedOperationException(
+ "Solver does not support independent interpolation based on the current strategy, as"
+ + " it is lacking quantifier support.");
+ }
+ return interpolationOption;
+ }
+
private static Set toSet(ProverOptions... options) {
Set opts = EnumSet.noneOf(ProverOptions.class);
Collections.addAll(opts, options);
diff --git a/src/org/sosy_lab/java_smt/basicimpl/IndependentInterpolatingSolverDelegate.java b/src/org/sosy_lab/java_smt/basicimpl/IndependentInterpolatingSolverDelegate.java
new file mode 100644
index 0000000000..8975043ffd
--- /dev/null
+++ b/src/org/sosy_lab/java_smt/basicimpl/IndependentInterpolatingSolverDelegate.java
@@ -0,0 +1,267 @@
+/*
+ * This file is part of JavaSMT,
+ * an API wrapper for a collection of SMT solvers:
+ * https://github.com/sosy-lab/java-smt
+ *
+ * SPDX-FileCopyrightText: 2024 Dirk Beyer
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.sosy_lab.java_smt.basicimpl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.sosy_lab.common.UniqueIdGenerator;
+import org.sosy_lab.java_smt.api.BooleanFormula;
+import org.sosy_lab.java_smt.api.BooleanFormulaManager;
+import org.sosy_lab.java_smt.api.FormulaManager;
+import org.sosy_lab.java_smt.api.InterpolatingProverEnvironment;
+import org.sosy_lab.java_smt.api.Model;
+import org.sosy_lab.java_smt.api.ProverEnvironment;
+import org.sosy_lab.java_smt.api.SolverContext;
+import org.sosy_lab.java_smt.api.SolverContext.ProverOptions;
+import org.sosy_lab.java_smt.api.SolverException;
+import org.sosy_lab.java_smt.basicimpl.interpolation_techniques.AbstractInterpolationTechnique;
+import org.sosy_lab.java_smt.basicimpl.interpolation_techniques.ModelBasedProjectionInterpolation;
+import org.sosy_lab.java_smt.basicimpl.interpolation_techniques.QuantifierEliminationInterpolation;
+
+public class IndependentInterpolatingSolverDelegate extends AbstractProver
+ implements InterpolatingProverEnvironment {
+
+ private final SolverContext solverContext;
+
+ private final InterpolatingProverEnvironment delegate;
+
+ private final @Nullable AbstractInterpolationTechnique interpolationTechnique;
+
+ private final @Nullable ProverOptions interpolationStrategy;
+
+ private final FormulaManager mgr;
+ private final BooleanFormulaManager bmgr;
+
+ private static final String PREFIX = "javasmt_itp_term_"; // for term-names
+ private static final UniqueIdGenerator termIdGenerator =
+ new UniqueIdGenerator(); // for different term-names
+
+ protected IndependentInterpolatingSolverDelegate(
+ AbstractSolverContext pSourceContext,
+ InterpolatingProverEnvironment pDelegate,
+ Set pOptions) {
+ super(checkNotNull(pOptions));
+ solverContext = checkNotNull(pSourceContext);
+ delegate = checkNotNull(pDelegate);
+ interpolationStrategy = pSourceContext.getIndependentInterpolationStrategy(pOptions);
+ mgr = pSourceContext.getFormulaManager();
+ bmgr = mgr.getBooleanFormulaManager();
+
+ // TODO: refactor the selection of interpolationTechnique by introducing a method for it
+ if (interpolationStrategy == null) {
+ interpolationTechnique = null;
+ } else {
+ interpolationTechnique =
+ switch (interpolationStrategy) {
+ case GENERATE_PROJECTION_BASED_INTERPOLANTS ->
+ new ModelBasedProjectionInterpolation(solverContext);
+ case GENERATE_UNIFORM_FORWARD_INTERPOLANTS, GENERATE_UNIFORM_BACKWARD_INTERPOLANTS ->
+ new QuantifierEliminationInterpolation(mgr, interpolationStrategy);
+ default ->
+ throw new AssertionError(
+ "Unknown interpolation strategy: " + interpolationStrategy);
+ };
+ }
+ }
+
+ // TODO: also present in SMTInterpol, generalize
+ protected static String generateTermName() {
+ return PREFIX + termIdGenerator.getFreshId();
+ }
+
+ @Override
+ public BooleanFormula getInterpolant(Collection identifiersForA)
+ throws SolverException, InterruptedException {
+
+ if (identifiersForA.isEmpty()) {
+ return bmgr.makeTrue();
+ }
+
+ InterpolationGroups interpolationGroups = super.getInterpolationGroups(identifiersForA);
+ Collection formulasOfA = interpolationGroups.getFormulasOfA();
+ Collection formulasOfB = interpolationGroups.getFormulasOfB();
+
+ if (formulasOfB.isEmpty()) {
+ return bmgr.makeFalse();
+ }
+
+ BooleanFormula conjugatedFormulasOfA = bmgr.and(formulasOfA);
+ BooleanFormula conjugatedFormulasOfB = bmgr.and(formulasOfB);
+
+ if (bmgr.isFalse(conjugatedFormulasOfA)) {
+ return bmgr.makeFalse();
+ } else if (bmgr.isFalse(conjugatedFormulasOfB)) {
+ return bmgr.makeTrue();
+ }
+
+ BooleanFormula interpolant;
+
+ if (interpolationTechnique == null) {
+ interpolant = delegate.getInterpolant(identifiersForA);
+ } else {
+ interpolant =
+ interpolationTechnique.getInterpolant(conjugatedFormulasOfA, conjugatedFormulasOfB);
+ }
+
+ assert satisfiesInterpolationCriteria(
+ interpolant, conjugatedFormulasOfA, conjugatedFormulasOfB);
+
+ return interpolant;
+ }
+
+ @Override
+ public List getTreeInterpolants(
+ List extends Collection> partitionedFormulas, int[] startOfSubTree)
+ throws SolverException, InterruptedException {
+ if (interpolationStrategy == null) {
+ // Use native solver interpolation
+ return delegate.getTreeInterpolants(partitionedFormulas, startOfSubTree);
+ }
+ throw new UnsupportedOperationException(
+ "Tree interpolants are not supported for independent interpolation currently.");
+ }
+
+ @Override
+ public List getSeqInterpolants(List extends Collection> pPartitionedFormulas)
+ throws SolverException, InterruptedException {
+ if (interpolationStrategy == null) {
+ // Use native solver interpolation
+ return delegate.getSeqInterpolants(pPartitionedFormulas);
+ }
+ throw new UnsupportedOperationException(
+ "Sequential interpolants are not supported for independent interpolation currently.");
+ }
+
+ /**
+ * Checks the following 3 criteria for Craig interpolants:
+ *
+ *
1. the implication A ⇒ interpolant holds,
+ *
+ *
2. the conjunction interpolant ∧ B is unsatisfiable, and
+ *
+ *
3. interpolant only contains symbols that occur in both A and B.
+ */
+ private boolean satisfiesInterpolationCriteria(
+ BooleanFormula interpolant,
+ BooleanFormula conjugatedFormulasOfA,
+ BooleanFormula conjugatedFormulasOfB)
+ throws InterruptedException, SolverException {
+
+ // checks that every Symbol of the interpolant appears either in A or B
+ Set interpolantSymbols = mgr.extractVariablesAndUFs(interpolant).keySet();
+ Set interpolASymbols = mgr.extractVariablesAndUFs(conjugatedFormulasOfA).keySet();
+ Set interpolBSymbols = mgr.extractVariablesAndUFs(conjugatedFormulasOfB).keySet();
+ Set intersection = Sets.intersection(interpolASymbols, interpolBSymbols);
+ checkState(
+ intersection.containsAll(interpolantSymbols),
+ "Interpolant contains symbols %s that are not part of both input formula groups A and B.",
+ Sets.difference(interpolantSymbols, intersection));
+
+ try (ProverEnvironment validationSolver = getDistinctProver()) {
+ validationSolver.push();
+ // A -> interpolant is SAT
+ validationSolver.addConstraint(bmgr.implication(conjugatedFormulasOfA, interpolant));
+ checkState(
+ !validationSolver.isUnsat(),
+ "Invalid Craig interpolation: formula group A does not imply the interpolant.");
+ validationSolver.pop();
+
+ validationSolver.push();
+ // interpolant AND B is UNSAT
+ validationSolver.addConstraint(bmgr.and(interpolant, conjugatedFormulasOfB));
+ checkState(
+ validationSolver.isUnsat(),
+ "Invalid Craig interpolation: interpolant does not contradict formula group B.");
+ validationSolver.pop();
+ }
+ return true;
+ }
+
+ /**
+ * Create a new, distinct prover to interpolate on. Will be able to generate models.
+ *
+ * @return A new {@link ProverEnvironment} configured to generate models.
+ */
+ private ProverEnvironment getDistinctProver() {
+ // TODO: we should include the possibility to choose from options here. E.g. CHC/Horn solvers.
+ return solverContext.newProverEnvironment(ProverOptions.GENERATE_MODELS);
+ }
+
+ @Override
+ protected void popImpl() {
+ delegate.pop();
+ }
+
+ @Override
+ protected T addConstraintImpl(BooleanFormula constraint) throws InterruptedException {
+ return delegate.addConstraint(constraint);
+ }
+
+ @Override
+ protected void pushImpl() throws InterruptedException {
+ delegate.push();
+ }
+
+ @Override
+ public int size() {
+ return delegate.size();
+ }
+
+ @Override
+ public boolean isUnsatImpl() throws SolverException, InterruptedException {
+ return delegate.isUnsat();
+ }
+
+ @Override
+ public boolean hasPersistentModel() {
+ return false;
+ }
+
+ @Override
+ public boolean isUnsatWithAssumptions(Collection assumptions)
+ throws SolverException, InterruptedException {
+ return delegate.isUnsatWithAssumptions(assumptions);
+ }
+
+ @Override
+ public Model getModel() throws SolverException {
+ return delegate.getModel();
+ }
+
+ @Override
+ public List getUnsatCore() {
+ return delegate.getUnsatCore();
+ }
+
+ @Override
+ public Optional> unsatCoreOverAssumptions(
+ Collection assumptions) throws SolverException, InterruptedException {
+ return delegate.unsatCoreOverAssumptions(assumptions);
+ }
+
+ @Override
+ public void close() {
+ delegate.close();
+ }
+
+ @Override
+ public R allSat(AllSatCallback callback, List important)
+ throws InterruptedException, SolverException {
+ return delegate.allSat(callback, important);
+ }
+}
diff --git a/src/org/sosy_lab/java_smt/basicimpl/InterpolatingSolverDelegate.java b/src/org/sosy_lab/java_smt/basicimpl/InterpolatingSolverDelegate.java
new file mode 100644
index 0000000000..00e05a6866
--- /dev/null
+++ b/src/org/sosy_lab/java_smt/basicimpl/InterpolatingSolverDelegate.java
@@ -0,0 +1,121 @@
+/*
+ * This file is part of JavaSMT,
+ * an API wrapper for a collection of SMT solvers:
+ * https://github.com/sosy-lab/java-smt
+ *
+ * SPDX-FileCopyrightText: 2024 Dirk Beyer
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.sosy_lab.java_smt.basicimpl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static org.sosy_lab.java_smt.basicimpl.IndependentInterpolatingSolverDelegate.generateTermName;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import org.sosy_lab.java_smt.api.BasicProverEnvironment;
+import org.sosy_lab.java_smt.api.BooleanFormula;
+import org.sosy_lab.java_smt.api.InterpolatingProverEnvironment;
+import org.sosy_lab.java_smt.api.Model;
+import org.sosy_lab.java_smt.api.SolverContext.ProverOptions;
+import org.sosy_lab.java_smt.api.SolverException;
+
+/** Delegate that wraps non-interpolating provers, allowing them to return itp tracking points. */
+public class InterpolatingSolverDelegate extends AbstractProver
+ implements InterpolatingProverEnvironment {
+
+ private final BasicProverEnvironment> delegate;
+
+ protected InterpolatingSolverDelegate(
+ BasicProverEnvironment> pDelegate, Set pOptions) {
+ super(checkNotNull(pOptions));
+ // TODO: is the delegate also saving all info of AbstractProver additionally, or does VOID
+ // prevent that?
+ delegate = checkNotNull(pDelegate);
+ }
+
+ @Override
+ public BooleanFormula getInterpolant(Collection formulasOfA)
+ throws SolverException, InterruptedException {
+ throw new UnsupportedOperationException("Solver does not support native interpolation.");
+ }
+
+ @Override
+ public List getTreeInterpolants(
+ List extends Collection> partitionedFormulas, int[] startOfSubTree)
+ throws SolverException, InterruptedException {
+ throw new UnsupportedOperationException(
+ "Tree interpolants are currently not supported using " + "independent interpolation");
+ }
+
+ @Override
+ protected void popImpl() {
+ delegate.pop();
+ }
+
+ @Override
+ protected String addConstraintImpl(BooleanFormula constraint) throws InterruptedException {
+ checkState(!closed);
+ delegate.addConstraint(constraint);
+ String termName = generateTermName();
+ return termName;
+ }
+
+ @Override
+ protected void pushImpl() throws InterruptedException {
+ delegate.push();
+ }
+
+ @Override
+ protected boolean hasPersistentModel() {
+ return false;
+ }
+
+ @Override
+ public int size() {
+ return delegate.size();
+ }
+
+ @Override
+ public boolean isUnsatImpl() throws SolverException, InterruptedException {
+ return delegate.isUnsat();
+ }
+
+ @Override
+ public boolean isUnsatWithAssumptions(Collection assumptions)
+ throws SolverException, InterruptedException {
+ return delegate.isUnsatWithAssumptions(assumptions);
+ }
+
+ @Override
+ public Model getModel() throws SolverException {
+ return delegate.getModel();
+ }
+
+ @Override
+ public List getUnsatCore() {
+ return delegate.getUnsatCore();
+ }
+
+ @Override
+ public Optional> unsatCoreOverAssumptions(
+ Collection assumptions) throws SolverException, InterruptedException {
+ return delegate.unsatCoreOverAssumptions(assumptions);
+ }
+
+ @Override
+ public void close() {
+ delegate.close();
+ }
+
+ @Override
+ public R allSat(AllSatCallback callback, List important)
+ throws InterruptedException, SolverException {
+ return delegate.allSat(callback, important);
+ }
+}
diff --git a/src/org/sosy_lab/java_smt/basicimpl/PackageSanityTest.java b/src/org/sosy_lab/java_smt/basicimpl/PackageSanityTest.java
index 375f70d390..6ccbbc4379 100644
--- a/src/org/sosy_lab/java_smt/basicimpl/PackageSanityTest.java
+++ b/src/org/sosy_lab/java_smt/basicimpl/PackageSanityTest.java
@@ -11,13 +11,28 @@
import com.google.common.testing.AbstractPackageSanityTests;
import org.sosy_lab.common.ShutdownManager;
import org.sosy_lab.common.ShutdownNotifier;
+import org.sosy_lab.common.configuration.InvalidConfigurationException;
+import org.sosy_lab.java_smt.SolverContextFactory;
+import org.sosy_lab.java_smt.SolverContextFactory.Solvers;
import org.sosy_lab.java_smt.api.FormulaType;
+@SuppressWarnings("resource")
public class PackageSanityTest extends AbstractPackageSanityTests {
{
setDistinctValues(FormulaType.class, FormulaType.BooleanType, FormulaType.IntegerType);
setDefault(ShutdownNotifier.class, ShutdownManager.create().getNotifier());
+ try {
+ // Due to solver independent interpolation we need a default solver for AbstractSolverContext
+ // Use Princess as it is always available
+ // TODO: look into this some more. Can we get rid of this? What is going on with the
+ // ressource suppression?
+ setDefault(
+ AbstractSolverContext.class,
+ (AbstractSolverContext) SolverContextFactory.createSolverContext(Solvers.PRINCESS));
+ } catch (InvalidConfigurationException e) {
+ throw new RuntimeException(e);
+ }
ignoreClasses(c -> c.equals(InterpolatingProverDelegate.class));
}
}
diff --git a/src/org/sosy_lab/java_smt/basicimpl/interpolation_techniques/AbstractInterpolationTechnique.java b/src/org/sosy_lab/java_smt/basicimpl/interpolation_techniques/AbstractInterpolationTechnique.java
new file mode 100644
index 0000000000..ad8a9a2830
--- /dev/null
+++ b/src/org/sosy_lab/java_smt/basicimpl/interpolation_techniques/AbstractInterpolationTechnique.java
@@ -0,0 +1,57 @@
+/*
+ * SPDX-FileCopyrightText: 2026 Dirk Beyer
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.sosy_lab.java_smt.basicimpl.interpolation_techniques;
+
+import com.google.common.collect.ImmutableList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import org.sosy_lab.java_smt.api.BooleanFormula;
+import org.sosy_lab.java_smt.api.BooleanFormulaManager;
+import org.sosy_lab.java_smt.api.Formula;
+import org.sosy_lab.java_smt.api.FormulaManager;
+import org.sosy_lab.java_smt.api.SolverException;
+
+public abstract class AbstractInterpolationTechnique {
+
+ protected final FormulaManager mgr;
+ protected final BooleanFormulaManager bmgr;
+
+ protected AbstractInterpolationTechnique(FormulaManager pMgr) {
+ mgr = pMgr;
+ bmgr = mgr.getBooleanFormulaManager();
+ }
+
+ public abstract BooleanFormula getInterpolant(
+ BooleanFormula formulasOfA, BooleanFormula formulasOfB)
+ throws SolverException, InterruptedException;
+
+ /** Extracts all variables (not UFs) from the given formula. */
+ protected List extends Formula> getAllVariables(BooleanFormula formula) {
+ return mgr.extractVariables(formula).values().asList();
+ }
+
+ /** Returns common Formulas of the 2 given lists. */
+ protected List getCommonFormulas(
+ List extends Formula> variables1, List extends Formula> variables2) {
+ Set set = new LinkedHashSet<>(variables1);
+ set.retainAll(variables2);
+ return ImmutableList.copyOf(set);
+ }
+
+ /** Removes variablesToRemove from variablesToRemoveFrom. */
+ protected List removeVariablesFrom(
+ List extends Formula> variablesToRemoveFrom, List extends Formula> variablesToRemove) {
+ ImmutableList.Builder builder = ImmutableList.builder();
+ for (Formula var : variablesToRemoveFrom) {
+ if (!variablesToRemove.contains(var)) {
+ builder.add(var);
+ }
+ }
+ return builder.build();
+ }
+}
diff --git a/src/org/sosy_lab/java_smt/basicimpl/interpolation_techniques/ModelBasedProjectionInterpolation.java b/src/org/sosy_lab/java_smt/basicimpl/interpolation_techniques/ModelBasedProjectionInterpolation.java
new file mode 100644
index 0000000000..1f6a93aff5
--- /dev/null
+++ b/src/org/sosy_lab/java_smt/basicimpl/interpolation_techniques/ModelBasedProjectionInterpolation.java
@@ -0,0 +1,83 @@
+/*
+ * SPDX-FileCopyrightText: 2026 Dirk Beyer
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.sosy_lab.java_smt.basicimpl.interpolation_techniques;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.List;
+import org.sosy_lab.common.UniqueIdGenerator;
+import org.sosy_lab.java_smt.api.BooleanFormula;
+import org.sosy_lab.java_smt.api.Formula;
+import org.sosy_lab.java_smt.api.FormulaType;
+import org.sosy_lab.java_smt.api.Model;
+import org.sosy_lab.java_smt.api.ProverEnvironment;
+import org.sosy_lab.java_smt.api.QuantifiedFormulaManager;
+import org.sosy_lab.java_smt.api.SolverContext;
+import org.sosy_lab.java_smt.api.SolverContext.ProverOptions;
+import org.sosy_lab.java_smt.api.SolverException;
+import org.sosy_lab.java_smt.api.UFManager;
+
+public class ModelBasedProjectionInterpolation extends AbstractInterpolationTechnique {
+
+ private final SolverContext solverContext;
+ private final UFManager ufmgr;
+ private final QuantifiedFormulaManager qfmgr;
+
+ private static final UniqueIdGenerator termIdGenerator = new UniqueIdGenerator();
+
+ @Override
+ public BooleanFormula getInterpolant(BooleanFormula formulasOfA, BooleanFormula formulasOfB)
+ throws SolverException, InterruptedException {
+
+ List extends Formula> variablesInA = getAllVariables(formulasOfA);
+ List extends Formula> variablesInB = getAllVariables(formulasOfB);
+ List sharedVars = getCommonFormulas(variablesInA, variablesInB);
+
+ BooleanFormula itp =
+ ufmgr.declareAndCallUF(
+ "__itp_internal_javasmt_" + termIdGenerator.getFreshId(),
+ FormulaType.BooleanType,
+ sharedVars);
+
+ BooleanFormula left;
+ BooleanFormula right;
+
+ if (variablesInA.isEmpty()) {
+ left = bmgr.implication(formulasOfA, itp);
+ } else {
+ left = qfmgr.forall(variablesInA, bmgr.implication(formulasOfA, itp));
+ }
+
+ if (variablesInB.isEmpty()) {
+ right = bmgr.implication(itp, bmgr.not(formulasOfB));
+ } else {
+ right = qfmgr.forall(variablesInB, bmgr.implication(itp, bmgr.not(formulasOfB)));
+ }
+
+ BooleanFormula interpolant = bmgr.makeFalse();
+ try (ProverEnvironment itpProver =
+ solverContext.newProverEnvironment(ProverOptions.GENERATE_MODELS)) {
+ itpProver.push(left);
+ itpProver.push(right);
+
+ if (!itpProver.isUnsat()) {
+ try (Model model = itpProver.getModel()) {
+ interpolant = model.eval(itp);
+ }
+ checkNotNull(interpolant);
+ }
+ }
+ return mgr.simplify(interpolant);
+ }
+
+ public ModelBasedProjectionInterpolation(SolverContext pContext) {
+ super(pContext.getFormulaManager());
+ solverContext = pContext;
+ ufmgr = mgr.getUFManager();
+ qfmgr = mgr.getQuantifiedFormulaManager();
+ }
+}
diff --git a/src/org/sosy_lab/java_smt/basicimpl/interpolation_techniques/QuantifierEliminationInterpolation.java b/src/org/sosy_lab/java_smt/basicimpl/interpolation_techniques/QuantifierEliminationInterpolation.java
new file mode 100644
index 0000000000..541bc110e9
--- /dev/null
+++ b/src/org/sosy_lab/java_smt/basicimpl/interpolation_techniques/QuantifierEliminationInterpolation.java
@@ -0,0 +1,100 @@
+/*
+ * SPDX-FileCopyrightText: 2026 Dirk Beyer
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.sosy_lab.java_smt.basicimpl.interpolation_techniques;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.sosy_lab.java_smt.api.BooleanFormula;
+import org.sosy_lab.java_smt.api.Formula;
+import org.sosy_lab.java_smt.api.FormulaManager;
+import org.sosy_lab.java_smt.api.QuantifiedFormulaManager;
+import org.sosy_lab.java_smt.api.QuantifiedFormulaManager.Quantifier;
+import org.sosy_lab.java_smt.api.SolverContext.ProverOptions;
+import org.sosy_lab.java_smt.api.SolverException;
+import org.sosy_lab.java_smt.api.visitors.DefaultFormulaVisitor;
+import org.sosy_lab.java_smt.api.visitors.TraversalProcess;
+
+public class QuantifierEliminationInterpolation extends AbstractInterpolationTechnique {
+
+ private final ProverOptions strategy;
+ private final QuantifiedFormulaManager qfmgr;
+
+ public QuantifierEliminationInterpolation(FormulaManager pMgr, ProverOptions pStrategy) {
+ super(pMgr);
+ strategy = pStrategy;
+ qfmgr = mgr.getQuantifiedFormulaManager();
+ }
+
+ @Override
+ public BooleanFormula getInterpolant(BooleanFormula formulasOfA, BooleanFormula formulasOfB)
+ throws SolverException, InterruptedException {
+
+ List extends Formula> variablesInA = getAllVariables(formulasOfA);
+ List extends Formula> variablesInB = getAllVariables(formulasOfB);
+ List sharedVariables = getCommonFormulas(variablesInA, variablesInB);
+
+ BooleanFormula interpolant;
+
+ if (strategy == ProverOptions.GENERATE_UNIFORM_FORWARD_INTERPOLANTS) {
+ // Forward: interpolate(A(x,y),B(y,z)) = ∃x.A(x,y)
+ List exclusiveVariablesInA = removeVariablesFrom(variablesInA, sharedVariables);
+
+ if (exclusiveVariablesInA.isEmpty()) {
+ return formulasOfA;
+ }
+
+ BooleanFormula itpForwardQuantified = qfmgr.exists(exclusiveVariablesInA, formulasOfA);
+ interpolant = qfmgr.eliminateQuantifiers(itpForwardQuantified);
+
+ } else if (strategy == ProverOptions.GENERATE_UNIFORM_BACKWARD_INTERPOLANTS) {
+ // Backward: interpolate(A(x,y),B(y,z))=∀z.¬B(y,z)
+ List exclusiveVariablesInB = removeVariablesFrom(variablesInB, sharedVariables);
+
+ if (exclusiveVariablesInB.isEmpty()) {
+ return bmgr.not(formulasOfB);
+ }
+
+ BooleanFormula itpBackwardQuantified =
+ qfmgr.forall(exclusiveVariablesInB, bmgr.not(formulasOfB));
+ interpolant = qfmgr.eliminateQuantifiers(itpBackwardQuantified);
+
+ } else {
+ throw new AssertionError("Unknown interpolation strategy for QE: " + strategy);
+ }
+
+ if (isQuantifiedFormula(interpolant)) {
+ throw new SolverException(
+ "Error when calculating uniform interpolant, quantifier elimination failed.");
+ }
+
+ return mgr.simplify(interpolant);
+ }
+
+ /** Checks the formula for a quantifier at an arbitrary position/depth. */
+ private boolean isQuantifiedFormula(BooleanFormula maybeQuantifiedFormula) {
+ final AtomicBoolean isQuantified = new AtomicBoolean(false);
+ mgr.visitRecursively(
+ maybeQuantifiedFormula,
+ new DefaultFormulaVisitor<>() {
+ @Override
+ protected TraversalProcess visitDefault(Formula pF) {
+ return TraversalProcess.CONTINUE;
+ }
+
+ @Override
+ public TraversalProcess visitQuantifier(
+ BooleanFormula pF,
+ Quantifier pQ,
+ List pBoundVariables,
+ BooleanFormula pBody) {
+ isQuantified.set(true);
+ return TraversalProcess.ABORT;
+ }
+ });
+ return isQuantified.get();
+ }
+}
diff --git a/src/org/sosy_lab/java_smt/basicimpl/interpolation_techniques/package-info.java b/src/org/sosy_lab/java_smt/basicimpl/interpolation_techniques/package-info.java
new file mode 100644
index 0000000000..4392fe8265
--- /dev/null
+++ b/src/org/sosy_lab/java_smt/basicimpl/interpolation_techniques/package-info.java
@@ -0,0 +1,11 @@
+/*
+ * This file is part of JavaSMT,
+ * an API wrapper for a collection of SMT solvers:
+ * https://github.com/sosy-lab/java-smt
+ *
+ * SPDX-FileCopyrightText: 2026 Dirk Beyer
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.sosy_lab.java_smt.basicimpl.interpolation_techniques;
diff --git a/src/org/sosy_lab/java_smt/example/IndependentInterpolation.java b/src/org/sosy_lab/java_smt/example/IndependentInterpolation.java
new file mode 100644
index 0000000000..725ccb1bb6
--- /dev/null
+++ b/src/org/sosy_lab/java_smt/example/IndependentInterpolation.java
@@ -0,0 +1,169 @@
+/*
+ * This file is part of JavaSMT,
+ * an API wrapper for a collection of SMT solvers:
+ * https://github.com/sosy-lab/java-smt
+ *
+ * SPDX-FileCopyrightText: 2026 Dirk Beyer
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.sosy_lab.java_smt.example;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import java.util.logging.Level;
+import org.sosy_lab.common.ShutdownNotifier;
+import org.sosy_lab.common.configuration.Configuration;
+import org.sosy_lab.common.configuration.InvalidConfigurationException;
+import org.sosy_lab.common.log.BasicLogManager;
+import org.sosy_lab.common.log.LogManager;
+import org.sosy_lab.java_smt.SolverContextFactory;
+import org.sosy_lab.java_smt.SolverContextFactory.Solvers;
+import org.sosy_lab.java_smt.api.BooleanFormula;
+import org.sosy_lab.java_smt.api.BooleanFormulaManager;
+import org.sosy_lab.java_smt.api.IntegerFormulaManager;
+import org.sosy_lab.java_smt.api.InterpolatingProverEnvironment;
+import org.sosy_lab.java_smt.api.NumeralFormula.IntegerFormula;
+import org.sosy_lab.java_smt.api.SolverContext;
+import org.sosy_lab.java_smt.api.SolverContext.ProverOptions;
+import org.sosy_lab.java_smt.api.SolverException;
+
+public final class IndependentInterpolation {
+
+ private IndependentInterpolation() {
+ // never called
+ }
+
+ private static final ProverOptions STRATEGY =
+ ProverOptions.GENERATE_UNIFORM_BACKWARD_INTERPOLANTS;
+
+ public static void main(String[] args)
+ throws InvalidConfigurationException, SolverException, InterruptedException {
+
+ // set up a basic environment
+ Configuration config = Configuration.defaultConfiguration();
+ LogManager logger = BasicLogManager.create(config);
+ ShutdownNotifier notifier = ShutdownNotifier.createDummy();
+
+ // choose solver
+ Solvers solver = Solvers.Z3;
+
+ // setup context
+ try (SolverContext context =
+ SolverContextFactory.createSolverContext(config, logger, notifier, solver);
+ InterpolatingProverEnvironment> prover =
+ context.newProverEnvironmentWithInterpolation(STRATEGY)) {
+ logger.log(Level.WARNING, "Using solver " + solver + " in version " + context.getVersion());
+ logger.log(Level.INFO, "Interpolation strategy: " + STRATEGY);
+
+ BooleanFormulaManager bmgr = context.getFormulaManager().getBooleanFormulaManager();
+ IntegerFormulaManager imgr = context.getFormulaManager().getIntegerFormulaManager();
+
+ // example
+ prover.push();
+ interpolateExample(prover, bmgr, imgr, logger);
+ prover.pop();
+
+ // another example
+ prover.push();
+ interpolateExample2(prover, bmgr, imgr, logger);
+ prover.pop();
+
+ } catch (InvalidConfigurationException | UnsatisfiedLinkError e) {
+
+ // on some machines we support only some solvers,
+ // thus we can ignore these errors.
+ logger.logUserException(Level.INFO, e, "Solver " + solver + " is not available.");
+
+ } catch (UnsupportedOperationException e) {
+ logger.logUserException(Level.INFO, e, e.getMessage());
+ }
+ }
+
+ private static void interpolateExample(
+ InterpolatingProverEnvironment prover,
+ BooleanFormulaManager bmgr,
+ IntegerFormulaManager imgr,
+ LogManager logger)
+ throws InterruptedException, SolverException {
+
+ // A: x = 1 && x = y
+ // B: y = z && z = 2
+ // -> y = 1, y != 2
+
+ // create some variables
+ IntegerFormula x = imgr.makeVariable("x");
+ IntegerFormula y = imgr.makeVariable("y");
+ IntegerFormula z = imgr.makeVariable("z");
+ IntegerFormula one = imgr.makeNumber(1);
+ IntegerFormula two = imgr.makeNumber(2);
+
+ // create and assert some formulas
+ // instead of 'named' formulas, we return a 'handle' (of generic type T)
+
+ BooleanFormula formulaB = bmgr.and(imgr.equal(y, z), imgr.equal(z, two));
+ BooleanFormula formulaA = bmgr.and(imgr.equal(x, one), imgr.equal(y, x));
+ prover.addConstraint(formulaB);
+ T ip1 = prover.addConstraint(formulaA);
+
+ // check for satisfiability
+ boolean unsat = prover.isUnsat();
+ Preconditions.checkState(unsat, "The example for interpolation should be UNSAT");
+
+ BooleanFormula itp = prover.getInterpolant(ImmutableList.of(ip1));
+ logger.logf(
+ Level.INFO,
+ "Interpolation Result:%n"
+ + " Strategy: %s%n"
+ + " Formula A: %s%n"
+ + " Formula B: %s%n"
+ + " Interpolant: %s",
+ STRATEGY,
+ formulaA.toString().length() > 500 ? "Too large to display" : formulaA,
+ formulaB.toString().length() > 500 ? "Too large to display" : formulaB,
+ itp);
+ }
+
+ private static void interpolateExample2(
+ InterpolatingProverEnvironment prover,
+ BooleanFormulaManager bmgr,
+ IntegerFormulaManager imgr,
+ LogManager logger)
+ throws InterruptedException, SolverException {
+
+ // A: x > 0 && y = x + 1
+ // B: y < 0
+ // -> y > 0
+
+ // create some variables
+ IntegerFormula x = imgr.makeVariable("x");
+ IntegerFormula y = imgr.makeVariable("y");
+ IntegerFormula one = imgr.makeNumber(1);
+ IntegerFormula zero = imgr.makeNumber(0);
+
+ BooleanFormula formulaB = imgr.lessThan(y, zero);
+ BooleanFormula formulaA = bmgr.and(imgr.greaterThan(x, zero), imgr.equal(y, imgr.add(x, one)));
+
+ prover.addConstraint(formulaB);
+ T ip1 = prover.addConstraint(formulaA);
+
+ // check for satisfiability
+ boolean unsat = prover.isUnsat();
+ Preconditions.checkState(unsat, "The example for interpolation should be UNSAT");
+
+ BooleanFormula itp = prover.getInterpolant(ImmutableList.of(ip1));
+ logger.log(Level.INFO, "Interpolants are:", itp);
+ logger.logf(
+ Level.INFO,
+ "Interpolation Result:%n"
+ + " Strategy: %s%n"
+ + " Formula A: %s%n"
+ + " Formula B: %s%n"
+ + " Interpolant: %s",
+ STRATEGY,
+ formulaA.toString().length() > 500 ? "Too large to display" : formulaA,
+ formulaB.toString().length() > 500 ? "Too large to display" : formulaB,
+ itp);
+ }
+}
diff --git a/src/org/sosy_lab/java_smt/test/BooleanFormulaSubject.java b/src/org/sosy_lab/java_smt/test/BooleanFormulaSubject.java
index a9dc67113e..11eff19f8b 100644
--- a/src/org/sosy_lab/java_smt/test/BooleanFormulaSubject.java
+++ b/src/org/sosy_lab/java_smt/test/BooleanFormulaSubject.java
@@ -9,10 +9,12 @@
package org.sosy_lab.java_smt.test;
import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
import static com.google.common.truth.Truth.assert_;
import com.google.common.base.Joiner;
import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Sets;
import com.google.common.truth.Fact;
import com.google.common.truth.FailureMetadata;
import com.google.common.truth.SimpleSubjectBuilder;
@@ -21,8 +23,10 @@
import com.google.common.truth.Truth;
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
import org.sosy_lab.java_smt.api.BooleanFormula;
import org.sosy_lab.java_smt.api.BooleanFormulaManager;
+import org.sosy_lab.java_smt.api.FormulaManager;
import org.sosy_lab.java_smt.api.Model;
import org.sosy_lab.java_smt.api.Model.ValueAssignment;
import org.sosy_lab.java_smt.api.ProverEnvironment;
@@ -59,6 +63,14 @@ public static Subject.Factory booleanForm
return (metadata, formula) -> new BooleanFormulaSubject(metadata, formula, context);
}
+ /**
+ * Use this for checking assertions about BooleanFormulas with Truth:
+ * assertThatFormula(formula).is...().
+ */
+ public static BooleanFormulaSubject assertThat(BooleanFormula formula, SolverContext context) {
+ return assert_().about(booleanFormulasOf(context)).that(formula);
+ }
+
/**
* Use this for checking assertions about BooleanFormulas (given the corresponding solver) with
* Truth: assertUsing(context)).that(formula).is...().
@@ -255,4 +267,51 @@ public void implies(final BooleanFormula expected) throws SolverException, Inter
checkIsUnsat(bmgr.not(implication), Fact.fact("expected to imply", expected));
}
+
+ /**
+ * Checks if the subject is a valid Craig interpolant for the given formulas A and B.
+ *
+ *
Checks 3 properties:
+ *
+ *
+ *
I is a subset of A\B
+ *
A implies I
+ *
I and B are mutually contradictory
+ *
+ */
+ public void isValidInterpolant(BooleanFormula formulaA, BooleanFormula formulaB)
+ throws SolverException, InterruptedException {
+ isValidInterpolant(List.of(formulaA), List.of(formulaB));
+ }
+
+ /** Checks if the subject is a valid Craig interpolant for the lists of formulas A and B. */
+ public void isValidInterpolant(List formulasA, List formulasB)
+ throws SolverException, InterruptedException {
+
+ BooleanFormulaManager bmgr = context.getFormulaManager().getBooleanFormulaManager();
+ BooleanFormula conjugatedFormulasOfA = bmgr.and(formulasA);
+ BooleanFormula conjugatedFormulasOfB = bmgr.and(formulasB);
+
+ // checks that every Symbol of the interpolant appears either in A or B
+ FormulaManager mgr = context.getFormulaManager();
+ Set interpolantSymbols = mgr.extractVariablesAndUFs(formulaUnderTest).keySet();
+ Set interpolASymbols = mgr.extractVariablesAndUFs(conjugatedFormulasOfA).keySet();
+ Set interpolBSymbols = mgr.extractVariablesAndUFs(conjugatedFormulasOfB).keySet();
+ Set intersection = Sets.intersection(interpolASymbols, interpolBSymbols);
+
+ checkState(
+ intersection.containsAll(interpolantSymbols),
+ "Interpolant contains symbols %s that are not part of both input formula groups A and B.",
+ Sets.difference(interpolantSymbols, intersection));
+
+ checkIsUnsat(
+ bmgr.and(conjugatedFormulasOfA, bmgr.not(formulaUnderTest)),
+ Fact.simpleFact("Interpolant expected to be implied by A (A => I)"),
+ ProverOptions.GENERATE_MODELS);
+
+ checkIsUnsat(
+ bmgr.and(formulaUnderTest, conjugatedFormulasOfB),
+ Fact.simpleFact("Interpolant expected to contradict B (I /\\ B => UNSAT)"),
+ ProverOptions.GENERATE_MODELS);
+ }
}
diff --git a/src/org/sosy_lab/java_smt/test/InterpolatingProverTest.java b/src/org/sosy_lab/java_smt/test/InterpolatingProverTest.java
index 96f4c34301..414c25d735 100644
--- a/src/org/sosy_lab/java_smt/test/InterpolatingProverTest.java
+++ b/src/org/sosy_lab/java_smt/test/InterpolatingProverTest.java
@@ -28,12 +28,14 @@
import org.sosy_lab.java_smt.api.BooleanFormula;
import org.sosy_lab.java_smt.api.Formula;
import org.sosy_lab.java_smt.api.InterpolatingProverEnvironment;
+import org.sosy_lab.java_smt.api.SolverContext.ProverOptions;
import org.sosy_lab.java_smt.api.SolverException;
import org.sosy_lab.java_smt.solvers.opensmt.Logics;
+import org.sosy_lab.java_smt.test.SolverBasedTest0.ParameterizedInterpolatingSolverBasedTest0;
/** This class contains some simple Junit-tests to check the interpolation-API of our solvers. */
@SuppressWarnings({"resource", "LocalVariableName"})
-public class InterpolatingProverTest extends SolverBasedTest0.ParameterizedSolverBasedTest0 {
+public class InterpolatingProverTest extends ParameterizedInterpolatingSolverBasedTest0 {
// INFO: OpenSmt only support interpolation for QF_LIA, QF_LRA and QF_UF
@Override
@@ -44,8 +46,14 @@ protected Logics logicToUse() {
/** Generate a prover environment depending on the parameter above. */
@SuppressWarnings("unchecked")
private InterpolatingProverEnvironment newEnvironmentForTest() {
- requireInterpolation();
- return (InterpolatingProverEnvironment) context.newProverEnvironmentWithInterpolation();
+ requireInterpolation(itpStrategyToUse());
+ ProverOptions itpStrat = itpStrategyToUse();
+ if (itpStrat == null) {
+ return (InterpolatingProverEnvironment) context.newProverEnvironmentWithInterpolation();
+ } else {
+ return (InterpolatingProverEnvironment)
+ context.newProverEnvironmentWithInterpolation(itpStrat);
+ }
}
private static final UniqueIdGenerator index = new UniqueIdGenerator(); // to get different names
@@ -53,6 +61,7 @@ private InterpolatingProverEnvironment newEnvironmentForTest() {
@Test
@SuppressWarnings("CheckReturnValue")
public void simpleInterpolation() throws SolverException, InterruptedException {
+ requireIntegers();
try (InterpolatingProverEnvironment prover = newEnvironmentForTest()) {
var f1 = lessThanNumber(makeVariable("x"), makeNumber(0));
var f2 = greaterThanNumber(makeVariable("x"), makeNumber(0));
@@ -69,10 +78,20 @@ public void simpleInterpolation() throws SolverException, InterruptedExcepti
@Test
@SuppressWarnings("CheckReturnValue")
public void notSoSimpleInterpolation() throws SolverException, InterruptedException {
+ requireIntegers();
assume()
.withMessage("Solver %s runs into timeout on this test", solverToUse())
.that(solverToUse())
- .isNoneOf(Solvers.CVC5, Solvers.YICES2, Solvers.OPENSMT);
+ .isNoneOf(Solvers.CVC5, Solvers.YICES2, Solvers.OPENSMT, Solvers.Z3);
+
+ if (itpStrategyToUse() == ProverOptions.GENERATE_UNIFORM_BACKWARD_INTERPOLANTS
+ || itpStrategyToUse() == ProverOptions.GENERATE_UNIFORM_FORWARD_INTERPOLANTS) {
+ assume()
+ .withMessage("Solver %s fails quantifier elimination in this test", solverToUse())
+ .that(solverToUse())
+ .isNotEqualTo(Solvers.PRINCESS);
+ // TODO: forward must be investigated, as it returns a weird error that we might cause!
+ }
try (InterpolatingProverEnvironment prover = newEnvironmentForTest()) {
Formula x = makeVariable("x");
@@ -111,6 +130,8 @@ public void emptyInterpolationGroup() throws SolverException, InterruptedExc
@Test
public void binaryInterpolation() throws SolverException, InterruptedException {
+ requireBitvectors();
+ requireIntegers();
InterpolatingProverEnvironment stack = newEnvironmentForTest();
int i = index.getFreshId();
@@ -176,14 +197,16 @@ public void binaryInterpolationWithConstantFalse()
// some interpolant needs to be FALSE, however, it can be at arbitrary position.
BooleanFormula expectedInterpolant = bmgr.makeFalse();
- if (solverToUse() == Solvers.Z3_WITH_INTERPOLATION || solverToUse() == Solvers.YICES2) {
- // FIXME This test seems wrong to me. Solvers are not guaranteed to return an inductive
- // sequence if getInterpolant is used multiple times. And even if it was an inductive
- // sequence, 'false' doesn't have to appear in it:
- // formulas F F F
- // interplants T T T F
- // (getInterpolants would return [T,T] in this case)
- expectedInterpolant = bmgr.makeTrue();
+ if (itpStrategyToUse() == null) {
+ if (solverToUse() == Solvers.Z3_WITH_INTERPOLATION || solverToUse() == Solvers.YICES2) {
+ // FIXME This test seems wrong to me. Solvers are not guaranteed to return an inductive
+ // sequence if getInterpolant is used multiple times. And even if it was an inductive
+ // sequence, 'false' doesn't have to appear in it:
+ // formulas F F F
+ // interplants T T T F
+ // (getInterpolants would return [T,T] in this case)
+ expectedInterpolant = bmgr.makeTrue();
+ }
}
assertThat(
ImmutableList.of(
@@ -246,6 +269,17 @@ public void illegalStateTest() throws InterruptedException, SolverException
@Test
public void binaryBVInterpolation1() throws SolverException, InterruptedException {
+ assume()
+ .withMessage("Solver %s is not supported or times out", solverToUse())
+ .that(solverToUse())
+ .isNotEqualTo(Solvers.BITWUZLA);
+
+ assume()
+ .withMessage("Z3 with strategy %s is not supported or times out", itpStrategyToUse())
+ .that(
+ solverToUse() == Solvers.Z3
+ && itpStrategyToUse() == ProverOptions.GENERATE_PROJECTION_BASED_INTERPOLANTS)
+ .isFalse();
requireBitvectors();
assume()
.withMessage("Solver %s does not support interpolation over bitvectors", solverToUse())
@@ -297,16 +331,9 @@ public void binaryBVInterpolation1() throws SolverException, InterruptedExce
checkItpSequence(ImmutableList.of(D, C, B, A), ImmutableList.of(itpD, itpDC, itpDCB));
}
- private void requireTreeItp() {
- requireInterpolation();
- assume()
- .withMessage("Solver does not support tree-interpolation.")
- .that(solver)
- .isAnyOf(Solvers.SMTINTERPOL, Solvers.PRINCESS, Solvers.Z3_WITH_INTERPOLATION);
- }
-
@Test
public void sequentialInterpolation() throws SolverException, InterruptedException {
+ requireSeqItp();
InterpolatingProverEnvironment stack = newEnvironmentForTest();
int i = index.getFreshId();
@@ -358,6 +385,8 @@ public void sequentialInterpolation() throws SolverException, InterruptedExc
public void sequentialInterpolationIsNotRepeatedIndividualInterpolation()
throws SolverException, InterruptedException {
InterpolatingProverEnvironment stack = newEnvironmentForTest();
+ requireSeqItp();
+ requireIntegers();
Formula zero = makeNumber(0);
Formula one = makeNumber(1);
@@ -396,7 +425,8 @@ public void sequentialInterpolationIsNotRepeatedIndividualInterpolation()
@Test
public void sequentialInterpolationWithoutPartition()
throws SolverException, InterruptedException {
-
+ requireIntegers();
+ requireSeqItp();
InterpolatingProverEnvironment stack = newEnvironmentForTest();
stack.push(mgr.makeEqual(makeNumber(0), makeNumber(1)));
@@ -410,6 +440,8 @@ public void sequentialInterpolationWithoutPartition()
@Test
public void sequentialInterpolationWithOnePartition()
throws SolverException, InterruptedException {
+ requireIntegers();
+ requireSeqItp();
InterpolatingProverEnvironment stack = newEnvironmentForTest();
int i = index.getFreshId();
@@ -437,6 +469,8 @@ public void sequentialInterpolationWithOnePartition()
@Test
public void sequentialInterpolationWithFewPartitions()
throws SolverException, InterruptedException {
+ requireIntegers();
+ requireSeqItp();
InterpolatingProverEnvironment stack = newEnvironmentForTest();
int i = index.getFreshId();
@@ -469,6 +503,8 @@ public void sequentialInterpolationWithFewPartitions()
@Test
public void sequentialBVInterpolation() throws SolverException, InterruptedException {
requireBitvectors();
+ requireSeqItp();
+ requireTreeItp();
InterpolatingProverEnvironment stack = newEnvironmentForTest();
@@ -1023,7 +1059,6 @@ public void treeInterpolationWithoutPartition() throws SolverException, Inte
@Test
public void treeInterpolationWithOnePartition() throws SolverException, InterruptedException {
requireTreeItp();
-
InterpolatingProverEnvironment stack = newEnvironmentForTest();
int i = index.getFreshId();
@@ -1053,6 +1088,8 @@ public void treeInterpolationWithOnePartition() throws SolverException, Inte
public void bigSeqInterpolationTest() throws InterruptedException, SolverException {
requireBitvectors();
requireInterpolation();
+ requireSeqItp();
+ requireTreeItp();
assume()
.withMessage("Solver %s does not support interpolation over bitvectors", solverToUse())
@@ -1126,7 +1163,7 @@ public void bigSeqInterpolationTest() throws InterruptedException, SolverExc
@Test
public void testTrivialInterpolation() throws InterruptedException, SolverException {
- requireInterpolation();
+ requireIntegers();
InterpolatingProverEnvironment stack = newEnvironmentForTest();
Formula zero = makeNumber(0);
Formula one = makeNumber(1);
@@ -1173,7 +1210,7 @@ private void checkItpSequence(List formulas, List void testInvalidToken() throws InterruptedException, SolverException {
- requireInterpolation();
+ requireIntegers();
InterpolatingProverEnvironment stack = newEnvironmentForTest();
// create and push formulas and solve them
@@ -1193,13 +1230,13 @@ public void testInvalidToken() throws InterruptedException, SolverException
final Object p3 =
switch (solverToUse()) {
case CVC5 -> bmgr.makeVariable("c");
- case MATHSAT5 -> 12345;
+ case MATHSAT5, Z3, CVC4 -> 12345;
case OPENSMT -> 12347;
case PRINCESS -> 12349;
case SMTINTERPOL -> "some string";
case Z3_WITH_INTERPOLATION -> 12350;
case BITWUZLA, YICES2 -> -1;
- default -> null; // unexpected solver for interpolation
+ case BOOLECTOR -> throw new AssertionError("Unexpected solver for interpolation");
};
// and try to solve with the token
@@ -1214,6 +1251,8 @@ public void testInvalidToken() throws InterruptedException, SolverException
*/
@Test
public void issue381InterpolationTest1() throws InterruptedException, SolverException {
+ requireIntegers();
+ requireSeqItp();
try (InterpolatingProverEnvironment prover = newEnvironmentForTest()) {
var x = makeVariable("x");
var one = makeNumber(1);
@@ -1240,6 +1279,8 @@ public void issue381InterpolationTest1() throws InterruptedException, Solver
*/
@Test
public void issue381InterpolationTest2() throws InterruptedException, SolverException {
+ requireIntegers();
+ requireSeqItp();
try (InterpolatingProverEnvironment prover = newEnvironmentForTest()) {
var x = makeVariable("x");
var one = makeNumber(1);
@@ -1266,6 +1307,7 @@ public void issue381InterpolationTest2() throws InterruptedException, Solver
*/
@Test
public void issue381InterpolationTest3() throws InterruptedException, SolverException {
+ requireIntegers();
try (InterpolatingProverEnvironment prover = newEnvironmentForTest()) {
var x = makeVariable("x");
var one = makeNumber(1);
diff --git a/src/org/sosy_lab/java_smt/test/SolverBasedTest0.java b/src/org/sosy_lab/java_smt/test/SolverBasedTest0.java
index 67de75af49..21255676fc 100644
--- a/src/org/sosy_lab/java_smt/test/SolverBasedTest0.java
+++ b/src/org/sosy_lab/java_smt/test/SolverBasedTest0.java
@@ -10,11 +10,20 @@
import static com.google.common.truth.TruthJUnit.assume;
import static org.sosy_lab.java_smt.api.FormulaType.getSinglePrecisionFloatingPointType;
+import static org.sosy_lab.java_smt.api.SolverContext.ProverOptions.GENERATE_PROJECTION_BASED_INTERPOLANTS;
+import static org.sosy_lab.java_smt.api.SolverContext.ProverOptions.GENERATE_UNIFORM_BACKWARD_INTERPOLANTS;
+import static org.sosy_lab.java_smt.api.SolverContext.ProverOptions.GENERATE_UNIFORM_FORWARD_INTERPOLANTS;
import static org.sosy_lab.java_smt.test.BooleanFormulaSubject.assertUsing;
import static org.sosy_lab.java_smt.test.ProverEnvironmentSubject.assertThat;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.truth.Truth;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
+import java.util.Set;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.junit.After;
import org.junit.Before;
@@ -85,8 +94,8 @@
*
*
*
- * {@link #assertThatFormula(BooleanFormula)} can be used to easily write assertions about formulas
- * using Truth.
+ *
{@link #assertThatFormula(BooleanFormula)} can be used to easily write assertions about
+ * formulas using Truth.
*
*
Test that rely on a theory that not all solvers support should call one of the {@code require}
* methods at the beginning.
@@ -154,6 +163,12 @@ protected ConfigurationBuilder createTestConfigBuilder() throws InvalidConfigura
return newConfig;
}
+ private static final ImmutableList INDEPENDENT_INTERPOLATION_STRATEGIES =
+ ImmutableList.of(
+ GENERATE_UNIFORM_BACKWARD_INTERPOLANTS,
+ GENERATE_PROJECTION_BASED_INTERPOLANTS,
+ GENERATE_UNIFORM_FORWARD_INTERPOLANTS);
+
/**
* Determines whether execution tracing is enabled for the test suite.
*
@@ -391,17 +406,83 @@ protected final void requireOptimization() {
}
}
- protected final void requireInterpolation() {
+ protected final void requireInterpolation(ProverOptions... options) {
+ List optionList =
+ (options == null) ? ImmutableList.of() : Arrays.asList(options);
+ if (optionList.contains(null) || optionList.isEmpty()) {
+ assume()
+ .withMessage("Solver %s does not support native interpolation", solverToUse())
+ .that(solverToUse())
+ .isAnyOf(
+ Solvers.SMTINTERPOL,
+ Solvers.PRINCESS,
+ Solvers.OPENSMT,
+ Solvers.MATHSAT5,
+ Solvers.BOOLECTOR,
+ Solvers.CVC5,
+ Solvers.BITWUZLA);
+ } else if (optionList.contains(GENERATE_PROJECTION_BASED_INTERPOLANTS)) {
+ assume()
+ .withMessage("Only Z3 is enabled for projection-based interpolation")
+ .that(solverToUse())
+ .isEqualTo(Solvers.Z3);
+ } else if (optionList.contains(GENERATE_UNIFORM_FORWARD_INTERPOLANTS)
+ || optionList.contains(GENERATE_UNIFORM_BACKWARD_INTERPOLANTS)) {
+ assume()
+ .withMessage("Solver %s does not support Quantifier Elimination", solverToUse())
+ .that(solverToUse())
+ .isNoneOf(Solvers.OPENSMT, Solvers.SMTINTERPOL, Solvers.YICES2);
+ }
try {
- context.newProverEnvironmentWithInterpolation().close();
+ if (optionList.contains(null)) {
+ context.newProverEnvironmentWithInterpolation().close();
+ } else {
+ context.newProverEnvironmentWithInterpolation(options).close();
+ }
} catch (UnsupportedOperationException e) {
assume()
- .withMessage("Solver %s does not support interpolation", solverToUse())
+ .withMessage(
+ "Solver %s threw UnsupportedOperationException for options %s",
+ solverToUse(), Arrays.toString(options))
.that(e)
.isNull();
}
}
+ protected void requireSeqItp(ProverOptions... options) {
+ assume()
+ .withMessage(
+ "Solver independent interpolation strategy %s does not support sequential "
+ + "interpolation",
+ solverToUse())
+ .that(options)
+ .asList()
+ .containsNoneIn(INDEPENDENT_INTERPOLATION_STRATEGIES);
+ }
+
+ protected void requireTreeItp(ProverOptions... options) {
+ requireInterpolation();
+ assume()
+ .withMessage(
+ "Solver independent interpolation strategy %s does not support tree " + "interpolation",
+ solverToUse())
+ .that(options)
+ .asList()
+ .containsNoneIn(INDEPENDENT_INTERPOLATION_STRATEGIES);
+ assume()
+ .withMessage("Solver does not support tree-interpolation.")
+ .that(solverToUse())
+ .isAnyOf(Solvers.SMTINTERPOL, Solvers.PRINCESS, Solvers.Z3_WITH_INTERPOLATION);
+
+ assume()
+ .withMessage(
+ "Strategy %s does not support tree interpolation",
+ Arrays.toString(options)) // Optional: print the options for clarity
+ .that(options)
+ .asList()
+ .containsNoneIn(INDEPENDENT_INTERPOLATION_STRATEGIES);
+ }
+
protected void requireParser() {
assume()
.withMessage("Solver %s does not support parsing formulae", solverToUse())
@@ -596,4 +677,56 @@ protected Solvers solverToUse() {
return solver;
}
}
+
+ @RunWith(Parameterized.class)
+ public abstract static class ParameterizedInterpolatingSolverBasedTest0
+ extends ParameterizedSolverBasedTest0 {
+
+ // GENERATE_MODELS as stand-in for native
+ private static final Set ALL_INTERPOLATION_STRATEGIES =
+ ImmutableSet.of(
+ GENERATE_PROJECTION_BASED_INTERPOLANTS,
+ GENERATE_UNIFORM_BACKWARD_INTERPOLANTS,
+ GENERATE_UNIFORM_FORWARD_INTERPOLANTS);
+
+ @Parameters(name = "solver {0} with interpolation strategy {1}")
+ public static List