Skip to content

Commit 93cd89c

Browse files
authored
Merge pull request #320 from sosy-lab/293-cvc5-interpolation
[CVC5]: Add support for Craig interpolation based on the interpolation of CVC5.
2 parents d32310b + 5e02942 commit 93cd89c

File tree

8 files changed

+502
-19
lines changed

8 files changed

+502
-19
lines changed

src/org/sosy_lab/java_smt/SolverContextFactory.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ private SolverContext generateContext0(Solvers solverToCreate)
240240
case CVC5:
241241
return CVC5SolverContext.create(
242242
logger,
243+
config,
243244
shutdownNotifier,
244245
(int) randomSeed,
245246
nonLinearArithmetic,

src/org/sosy_lab/java_smt/api/InterpolatingProverEnvironment.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,11 @@ public interface InterpolatingProverEnvironment<T> extends BasicProverEnvironmen
3030
* {@link #isUnsat()} call that returned <code>true</code>.
3131
*
3232
* <p>There is no direct guarantee that the interpolants returned are part of an inductive
33-
* sequence', however this seems to work for most (all?) solvers as long as the same proof is
34-
* used, i.e. all interpolants are computed after the same SAT-check.
33+
* sequence', however this seems to work for most solvers as long as the same proof is used, i.e.
34+
* all interpolants are computed after the same SAT-check. If a solver does not use the same
35+
* internal proof for several interpolation queries (such as CVC5), then the returned interpolants
36+
* might not satisfy the sequence-criteria. We suggest the proper method {@link
37+
* #getSeqInterpolants} for that case.
3538
*
3639
* @param formulasOfA A collection of values returned by {@link #push(BooleanFormula)}. All the
3740
* corresponding formulas from group A, the remaining formulas form group B.

src/org/sosy_lab/java_smt/solvers/cvc5/CVC5AbstractProver.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,29 +54,29 @@ protected CVC5AbstractProver(
5454
incremental = !enableSL;
5555
solver = new Solver();
5656

57-
setSolverOptions(randomSeed, pOptions);
57+
setSolverOptions(randomSeed, pOptions, solver);
5858
}
5959

60-
private void setSolverOptions(int randomSeed, Set<ProverOptions> pOptions) {
60+
protected void setSolverOptions(int randomSeed, Set<ProverOptions> pOptions, Solver pSolver) {
6161
if (incremental) {
62-
solver.setOption("incremental", "true");
62+
pSolver.setOption("incremental", "true");
6363
}
6464
if (pOptions.contains(ProverOptions.GENERATE_MODELS)) {
65-
solver.setOption("produce-models", "true");
65+
pSolver.setOption("produce-models", "true");
6666
}
6767
if (pOptions.contains(ProverOptions.GENERATE_UNSAT_CORE)) {
68-
solver.setOption("produce-unsat-cores", "true");
68+
pSolver.setOption("produce-unsat-cores", "true");
6969
}
70-
solver.setOption("produce-assertions", "true");
71-
solver.setOption("dump-models", "true");
72-
solver.setOption("output-language", "smt2");
73-
solver.setOption("seed", String.valueOf(randomSeed));
70+
pSolver.setOption("produce-assertions", "true");
71+
pSolver.setOption("dump-models", "true");
72+
pSolver.setOption("output-language", "smt2");
73+
pSolver.setOption("seed", String.valueOf(randomSeed));
7474

7575
// Set Strings option to enable all String features (such as lessOrEquals)
76-
solver.setOption("strings-exp", "true");
76+
pSolver.setOption("strings-exp", "true");
7777

7878
// Enable more complete quantifier solving (for more info see CVC5QuantifiedFormulaManager)
79-
solver.setOption("full-saturate-quant", "true");
79+
pSolver.setOption("full-saturate-quant", "true");
8080
}
8181

8282
@Override
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
// This file is part of JavaSMT,
2+
// an API wrapper for a collection of SMT solvers:
3+
// https://github.com/sosy-lab/java-smt
4+
//
5+
// SPDX-FileCopyrightText: 2023 Dirk Beyer <https://www.sosy-lab.org>
6+
//
7+
// SPDX-License-Identifier: Apache-2.0
8+
9+
package org.sosy_lab.java_smt.solvers.cvc5;
10+
11+
import static com.google.common.base.Preconditions.checkState;
12+
import static org.sosy_lab.common.collect.Collections3.transformedImmutableSetCopy;
13+
14+
import com.google.common.base.Preconditions;
15+
import com.google.common.collect.FluentIterable;
16+
import com.google.common.collect.ImmutableSet;
17+
import com.google.common.collect.Sets;
18+
import io.github.cvc5.CVC5ApiException;
19+
import io.github.cvc5.Kind;
20+
import io.github.cvc5.Solver;
21+
import io.github.cvc5.Term;
22+
import java.util.ArrayList;
23+
import java.util.Collection;
24+
import java.util.List;
25+
import java.util.Set;
26+
import org.sosy_lab.common.ShutdownNotifier;
27+
import org.sosy_lab.java_smt.api.BooleanFormula;
28+
import org.sosy_lab.java_smt.api.FormulaManager;
29+
import org.sosy_lab.java_smt.api.InterpolatingProverEnvironment;
30+
import org.sosy_lab.java_smt.api.SolverContext.ProverOptions;
31+
import org.sosy_lab.java_smt.api.SolverException;
32+
33+
public class CVC5InterpolatingProver extends CVC5AbstractProver<Term>
34+
implements InterpolatingProverEnvironment<Term> {
35+
36+
private final FormulaManager mgr;
37+
private final Set<ProverOptions> solverOptions;
38+
private final int seed;
39+
private final CVC5BooleanFormulaManager bmgr;
40+
private final boolean validateInterpolants;
41+
42+
CVC5InterpolatingProver(
43+
CVC5FormulaCreator pFormulaCreator,
44+
ShutdownNotifier pShutdownNotifier,
45+
int randomSeed,
46+
Set<ProverOptions> pOptions,
47+
FormulaManager pMgr,
48+
boolean pValidateInterpolants) {
49+
super(pFormulaCreator, pShutdownNotifier, randomSeed, pOptions, pMgr);
50+
mgr = pMgr;
51+
solverOptions = pOptions;
52+
seed = randomSeed;
53+
bmgr = (CVC5BooleanFormulaManager) mgr.getBooleanFormulaManager();
54+
validateInterpolants = pValidateInterpolants;
55+
}
56+
57+
/**
58+
* Sets the same solver Options of the Original Solver to the separate solvertoSet, except for
59+
* produce-interpolants which is set here. From CVC5AbstractProver Line 66
60+
*/
61+
@Override
62+
protected void setSolverOptions(int randomSeed, Set<ProverOptions> pOptions, Solver pSolver) {
63+
super.setSolverOptions(randomSeed, pOptions, pSolver);
64+
pSolver.setOption("produce-interpolants", "true");
65+
}
66+
67+
@Override
68+
public Term addConstraint(BooleanFormula pConstraint) throws InterruptedException {
69+
checkState(!closed);
70+
Term t = creator.extractInfo(pConstraint);
71+
72+
super.addConstraint(pConstraint);
73+
return t; // t is not wrapped in the Abstract Class
74+
}
75+
76+
@Override
77+
public BooleanFormula getInterpolant(Collection<Term> pFormulasOfA)
78+
throws SolverException, InterruptedException {
79+
checkState(!closed);
80+
81+
final Set<Term> assertedFormulas =
82+
transformedImmutableSetCopy(getAssertedFormulas(), creator::extractInfo);
83+
final Set<Term> formulasOfA = ImmutableSet.copyOf(pFormulasOfA);
84+
final Set<Term> formulasOfB = Sets.difference(assertedFormulas, formulasOfA);
85+
86+
Term itp = getCVC5Interpolation(formulasOfA, formulasOfB);
87+
return creator.encapsulateBoolean(itp);
88+
}
89+
90+
@Override
91+
public List<BooleanFormula> getSeqInterpolants(List<? extends Collection<Term>> partitions)
92+
throws SolverException, InterruptedException {
93+
Preconditions.checkArgument(
94+
!partitions.isEmpty(), "at least one partition should be available.");
95+
96+
final int n = partitions.size();
97+
final List<BooleanFormula> itps = new ArrayList<>();
98+
Term previousItp = solver.mkTrue();
99+
for (int i = 1; i < n; i++) {
100+
Collection<Term> formulasA =
101+
ImmutableSet.<Term>builder().addAll(partitions.get(i - 1)).add(previousItp).build();
102+
Collection<Term> formulasB = FluentIterable.concat(partitions.subList(i, n)).toSet();
103+
Term itp = getCVC5Interpolation(formulasA, formulasB);
104+
itps.add(creator.encapsulateBoolean(itp));
105+
previousItp = itp;
106+
}
107+
return itps;
108+
}
109+
110+
@Override
111+
public List<BooleanFormula> getTreeInterpolants(
112+
List<? extends Collection<Term>> partitionedFormulas, int[] startOfSubTree) {
113+
throw new UnsupportedOperationException(
114+
"directly receiving tree interpolants is not supported."
115+
+ "Use another solver or another strategy for interpolants.");
116+
}
117+
118+
/**
119+
* This method computes Craig interpolants for a pair of formulas using CVC5-Interpolation.
120+
*
121+
* <p>CVC5's interpolation returns an interpolant I for two input formulas A and B, such that the
122+
* following holds:
123+
*
124+
* <ol>
125+
* <li>(A -> I) is valid (1CVC5),
126+
* <li>(I -> B) is valid (2CVC5), and
127+
* <li>I only contains symbols from A and B (3CVC5).
128+
* </ol>
129+
*
130+
* <p>Craig interpolation returns an interpolant psi for two input formulas phi- and phi+, such
131+
* that the following holds:
132+
*
133+
* <ol>
134+
* <li>(phi- -> psi) is valid (1Craig),
135+
* <li>(psi && phi+) is unsatisfiable (2Craig), and
136+
* <li>psi only contains symbols from phi- and phi+ (3Craig).
137+
* </ol>
138+
*
139+
* <p>We can transform CVC5 interpolation to Craig interpolation by negating the formula B, i.e.,
140+
* the CVC5 interpolant for input (A, B) represents a Craig interpolant for input (A, not B). Here
141+
* is a proof for this:
142+
*
143+
* <ol>
144+
* <li>(1CVC5) <=> (1Craig): holds, due to substitution of A with phi- and I with psi.
145+
* <li>(2CVC5) <=> (I -> B) is valid <=> ((not I) || B) is valid <=> (not (I && (not B))) is
146+
* valid <=> (I && (not B)) is unsatisfiable <=> (2Craig) holds, due to substitution of I
147+
* with psi and (not B) with phi+.
148+
* <li>(3CVC5) <=> (3Craig): holds, negation does not change symbols.
149+
* </ol>
150+
*
151+
* <p>Hence, CVC5's interpolation produces an equivalent interpolation result to Craig
152+
* interpolation, if the input B is negated.
153+
*
154+
* <p>Please note, that this method will use a different proof for each call, and thus, a sequence
155+
* of interpolation queries will most likely not produce sequential Craig interpolants on its own.
156+
* Therefore, the caller has to use constraints based on previously computed interpolants.
157+
*
158+
* @param formulasA formulas for psi- of the interpolation query
159+
* @param formulasB formulas for psi+ of the interpolation (will be negated internally)
160+
* @return interpolation result following the definition of Craig interpolation.
161+
*/
162+
private Term getCVC5Interpolation(Collection<Term> formulasA, Collection<Term> formulasB) {
163+
Term phiPlus = bmgr.andImpl(formulasA);
164+
Term phiMinus = bmgr.andImpl(formulasB);
165+
166+
// Uses a separate Solver instance to leave the original solver-context unmodified
167+
Solver itpSolver = new Solver();
168+
setSolverOptions(seed, solverOptions, itpSolver);
169+
170+
Term interpolant;
171+
try {
172+
itpSolver.assertFormula(phiPlus);
173+
interpolant = itpSolver.getInterpolant(itpSolver.mkTerm(Kind.NOT, phiMinus));
174+
} finally {
175+
itpSolver.deletePointer();
176+
}
177+
178+
if (validateInterpolants) {
179+
checkInterpolationCriteria(interpolant, phiPlus, phiMinus);
180+
}
181+
182+
return interpolant;
183+
}
184+
185+
/**
186+
* Checks, whether the returned interpolant indeed satisfies Craig-Interpolation and Symbol Usage.
187+
*
188+
* @param interpolant the given Interpolant for aTerm and bTerm after Craig Interpolation
189+
* @param phiPlus the phi+ Term in Craig Interpolation
190+
* @param phiMinus the phi- Term in Craig Interpolation (before negation for CVC5-Interpolation)
191+
*/
192+
private void checkInterpolationCriteria(Term interpolant, Term phiPlus, Term phiMinus) {
193+
194+
// checks that every Symbol of the interpolant appears either in term A or term B
195+
Set<String> interpolantSymbols =
196+
mgr.extractVariablesAndUFs(creator.encapsulateBoolean(interpolant)).keySet();
197+
Set<String> interpolASymbols =
198+
mgr.extractVariablesAndUFs(creator.encapsulateBoolean(phiPlus)).keySet();
199+
Set<String> interpolBSymbols =
200+
mgr.extractVariablesAndUFs(creator.encapsulateBoolean(phiMinus)).keySet();
201+
Set<String> intersection = Sets.intersection(interpolASymbols, interpolBSymbols);
202+
checkState(
203+
intersection.containsAll(interpolantSymbols),
204+
"Interpolant contains symbols %s that are not part of both input formulas.",
205+
Sets.difference(interpolantSymbols, intersection));
206+
207+
// build and check both Craig interpolation formulas with the generated interpolant.
208+
Solver validationSolver = new Solver();
209+
// interpolation option is not required for validation
210+
super.setSolverOptions(seed, solverOptions, validationSolver);
211+
try {
212+
validationSolver.push();
213+
validationSolver.assertFormula(validationSolver.mkTerm(Kind.IMPLIES, phiPlus, interpolant));
214+
checkState(
215+
validationSolver.checkSat().isSat(),
216+
"Invalid Craig interpolation: phi+ does not imply the interpolant.");
217+
validationSolver.pop();
218+
219+
validationSolver.push();
220+
validationSolver.assertFormula(validationSolver.mkTerm(Kind.AND, interpolant, phiMinus));
221+
checkState(
222+
validationSolver.checkSat().isUnsat(),
223+
"Invalid Craig interpolation: interpolant does not contradict phi-.");
224+
validationSolver.pop();
225+
226+
} catch (CVC5ApiException e) {
227+
throw new IllegalArgumentException(
228+
"Failure when validating interpolant '" + interpolant + "'.", e);
229+
230+
} finally {
231+
validationSolver.deletePointer();
232+
}
233+
}
234+
}

src/org/sosy_lab/java_smt/solvers/cvc5/CVC5NativeAPITest.java

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1232,4 +1232,94 @@ public void termAccessAfterModelClosed() throws CVC5ApiException {
12321232

12331233
System.out.println(valueV);
12341234
}
1235+
1236+
@Test
1237+
public void checkCVC5InterpolationMethod() {
1238+
solver.setOption("produce-interpolants", "true");
1239+
Term xp = solver.mkConst(solver.getIntegerSort(), "xp");
1240+
Term y = solver.mkConst(solver.getIntegerSort(), "y");
1241+
1242+
Term ip0 = solver.mkTerm(Kind.GT, xp, y);
1243+
Term ip1 = solver.mkTerm(Kind.EQUAL, xp, solver.mkInteger(0));
1244+
Term ip2 = solver.mkTerm(Kind.GT, y, solver.mkInteger(0));
1245+
1246+
Term a = ip0;
1247+
Term b = solver.mkTerm(Kind.AND, ip1, ip2);
1248+
1249+
assertThat(!interpolateAndCheck(solver, a, b).isNull()).isTrue();
1250+
}
1251+
1252+
/**
1253+
* Interpolates Terms after CVC5 and checks the Definition of Craig-Interpolation for the
1254+
* interpolation.
1255+
*
1256+
* @return Interpolantion of interpolantA and interpolantB after CVC5 Interpolation Definition
1257+
*/
1258+
private Term interpolateAndCheck(Solver solverP, Term interpolantA, Term interpolantB) {
1259+
// solver.setOption("produce-interpolants", "true");
1260+
solverP.assertFormula(interpolantA);
1261+
System.out.println(
1262+
"Interpolation Pair:\n" + interpolantA + "\n" + solverP.mkTerm(Kind.NOT, interpolantB));
1263+
Term interpolation = solverP.getInterpolant(solverP.mkTerm(Kind.NOT, interpolantB));
1264+
System.out.println("Interpolation: " + interpolation);
1265+
solverP.resetAssertions();
1266+
Term cvc51 = solverP.mkTerm(Kind.IMPLIES, interpolantA, interpolation);
1267+
Term cvc52 =
1268+
solverP.mkTerm(Kind.IMPLIES, interpolation, solverP.mkTerm(Kind.NOT, interpolantB));
1269+
1270+
solverP.assertFormula(cvc51);
1271+
solverP.assertFormula(cvc52);
1272+
if (solverP.checkSat().isUnsat()) {
1273+
System.out.println("Does not satisfy CVC5 Interpolation Definition");
1274+
return null;
1275+
}
1276+
1277+
solverP.resetAssertions();
1278+
solverP.assertFormula(solverP.mkTerm(Kind.NOT, solverP.mkTerm(Kind.AND, cvc51, cvc52)));
1279+
if (solverP.checkSat().isSat()) {
1280+
System.out.println("Does not satisfy generally CVC5 Interpolation Definition");
1281+
return null;
1282+
}
1283+
1284+
solverP.resetAssertions();
1285+
Term craig1 = solverP.mkTerm(Kind.IMPLIES, interpolantA, interpolation);
1286+
Term craig2 =
1287+
solverP.mkTerm(
1288+
Kind.EQUAL,
1289+
solverP.mkTerm(Kind.AND, interpolation, interpolantB),
1290+
solverP.mkBoolean(false));
1291+
solverP.assertFormula(craig1);
1292+
solverP.assertFormula(craig2);
1293+
if (solverP.checkSat().isUnsat()) {
1294+
System.out.println("Does not satisfy Craig Interpolation Definition");
1295+
return null;
1296+
}
1297+
solverP.resetAssertions();
1298+
solverP.assertFormula(solverP.mkTerm(Kind.NOT, solverP.mkTerm(Kind.AND, craig1, craig2)));
1299+
if (solverP.checkSat().isSat()) {
1300+
System.out.println("Does not satisfy generally Craig Interpolation Definition");
1301+
return null;
1302+
}
1303+
System.out.println("------------");
1304+
return interpolation;
1305+
}
1306+
1307+
@Test
1308+
@Ignore // Does not terminate
1309+
public void testSimpleInterpolation() {
1310+
// Out of InterpolatingProverTest.java
1311+
// Line: 65
1312+
solver.setOption("produce-interpolants", "true");
1313+
Term xprime = solver.mkConst(solver.getIntegerSort(), "x");
1314+
Term y = solver.mkConst(solver.getIntegerSort(), "y");
1315+
Term z = solver.mkConst(solver.getIntegerSort(), "z");
1316+
Term f1 = solver.mkTerm(Kind.EQUAL, y, solver.mkTerm(Kind.MULT, solver.mkInteger(2), xprime));
1317+
Term f2 =
1318+
solver.mkTerm(
1319+
Kind.EQUAL,
1320+
y,
1321+
solver.mkTerm(
1322+
Kind.ADD, solver.mkInteger(1), solver.mkTerm(Kind.MULT, z, solver.mkInteger(2))));
1323+
interpolateAndCheck(solver, f1, f2);
1324+
}
12351325
}

0 commit comments

Comments
 (0)