Skip to content

Commit 025675a

Browse files
authored
Fix unsat core generation (#114)
* Fix multiple close issue * Special handling for trivial unsat assumptions in Z3
1 parent 7df2cbb commit 025675a

5 files changed

Lines changed: 62 additions & 7 deletions

File tree

ksmt-bitwuzla/src/main/kotlin/io/ksmt/solver/bitwuzla/KBitwuzlaContext.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,9 @@ open class KBitwuzlaContext(val ctx: KContext) : AutoCloseable {
298298
}
299299

300300
override fun close() {
301+
if (isClosed) return
301302
isClosed = true
303+
302304
sorts.clear()
303305
bitwuzlaSorts.clear()
304306

ksmt-cvc5/src/main/kotlin/io/ksmt/solver/cvc5/KCvc5Context.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ class KCvc5Context(
351351

352352

353353
override fun close() {
354+
if (isClosed) return
354355
isClosed = true
355356

356357
currentScopeExpressions.clear()

ksmt-test/src/test/kotlin/io/ksmt/test/CheckWithAssumptionsTest.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,18 @@ class CheckWithAssumptionsTest {
3838
@Test
3939
fun testUnsatCoreGenerationCvc() = testUnsatCoreGeneration { KCvc5Solver(it) }
4040

41+
@Test
42+
fun testTrivialUnsatCoreZ3() = testTrivialUnsatCore { KZ3Solver(it) }
43+
44+
@Test
45+
fun testTrivialUnsatCoreBitwuzla() = testTrivialUnsatCore { KBitwuzlaSolver(it) }
46+
47+
@Test
48+
fun testTrivialUnsatCoreYices() = testTrivialUnsatCore { KYicesSolver(it) }
49+
50+
@Test
51+
fun testTrivialUnsatCoreCvc() = testTrivialUnsatCore { KCvc5Solver(it) }
52+
4153
private fun testComplexAssumption(mkSolver: (KContext) -> KSolver<*>) = with(KContext()) {
4254
mkSolver(this).use { solver ->
4355
val a by bv32Sort
@@ -76,4 +88,15 @@ class CheckWithAssumptionsTest {
7688
assertTrue(e3 in core)
7789
}
7890
}
91+
92+
private fun testTrivialUnsatCore(mkSolver: (KContext) -> KSolver<*>) = with(KContext()) {
93+
mkSolver(this).use { solver ->
94+
val status = solver.checkWithAssumptions(listOf(falseExpr))
95+
assertEquals(KSolverStatus.UNSAT, status)
96+
97+
val core = solver.unsatCore()
98+
assertEquals(1, core.size)
99+
assertTrue(falseExpr in core)
100+
}
101+
}
79102
}

ksmt-z3/src/main/kotlin/io/ksmt/solver/z3/KZ3Context.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ class KZ3Context(
261261
}
262262

263263
override fun close() {
264+
if (isClosed) return
264265
isClosed = true
265266

266267
z3Expressions.keys.decRefAll()

ksmt-z3/src/main/kotlin/io/ksmt/solver/z3/KZ3Solver.kt

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,12 @@ import kotlin.time.DurationUnit
2525
open class KZ3Solver(private val ctx: KContext) : KSolver<KZ3SolverConfiguration> {
2626
private val z3Ctx = KZ3Context(ctx)
2727
private val solver = createSolver()
28+
2829
private var lastCheckStatus = KSolverStatus.UNKNOWN
2930
private var lastReasonOfUnknown: String? = null
31+
private var lastModel: KZ3Model? = null
32+
private var lastUnsatCore: List<KExpr<KBoolSort>>? = null
33+
3034
private var currentScope: UInt = 0u
3135

3236
@Suppress("LeakingThis")
@@ -104,7 +108,20 @@ open class KZ3Solver(private val ctx: KContext) : KSolver<KZ3SolverConfiguration
104108
ctx.ensureContextMatch(assumptions)
105109

106110
val z3Assumptions = with(exprInternalizer) {
107-
LongArray(assumptions.size) { assumptions[it].internalizeExpr() }
111+
LongArray(assumptions.size) {
112+
val assumption = assumptions[it]
113+
114+
/**
115+
* Assumptions are trivially unsat and no check-sat is required.
116+
* */
117+
if (assumption == ctx.falseExpr) {
118+
lastUnsatCore = listOf(ctx.falseExpr)
119+
lastCheckStatus = KSolverStatus.UNSAT
120+
return KSolverStatus.UNSAT
121+
}
122+
123+
assumption.internalizeExpr()
124+
}
108125
}
109126

110127
solver.updateTimeout(timeout)
@@ -116,19 +133,28 @@ open class KZ3Solver(private val ctx: KContext) : KSolver<KZ3SolverConfiguration
116133
require(lastCheckStatus == KSolverStatus.SAT) {
117134
"Model are only available after SAT checks, current solver status: $lastCheckStatus"
118135
}
119-
val model = solver.model
120136

121-
KZ3Model(model, ctx, z3Ctx, exprInternalizer)
137+
val model = lastModel ?: KZ3Model(
138+
model = solver.model,
139+
ctx = ctx,
140+
z3Ctx = z3Ctx,
141+
internalizer = exprInternalizer
142+
)
143+
lastModel = model
144+
145+
model
122146
}
123147

124148
override fun unsatCore(): List<KExpr<KBoolSort>> = z3Try {
125149
require(lastCheckStatus == KSolverStatus.UNSAT) { "Unsat cores are only available after UNSAT checks" }
126150

127-
val unsatCore = solver.solverGetUnsatCore()
128-
129-
with(exprConverter) {
130-
unsatCore.map { trackedAssertions.get(it) ?: it.convertExpr() }
151+
val unsatCore = lastUnsatCore ?: with(exprConverter) {
152+
val solverUnsatCore = solver.solverGetUnsatCore()
153+
solverUnsatCore.map { trackedAssertions.get(it) ?: it.convertExpr() }
131154
}
155+
lastUnsatCore = unsatCore
156+
157+
unsatCore
132158
}
133159

134160
override fun reasonOfUnknown(): String = z3Try {
@@ -173,6 +199,8 @@ open class KZ3Solver(private val ctx: KContext) : KSolver<KZ3SolverConfiguration
173199
private fun invalidateSolverState() {
174200
lastReasonOfUnknown = null
175201
lastCheckStatus = KSolverStatus.UNKNOWN
202+
lastModel = null
203+
lastUnsatCore = null
176204
}
177205

178206
private inline fun z3TryCheck(body: () -> KSolverStatus): KSolverStatus = try {

0 commit comments

Comments
 (0)