Skip to content

Commit f0f0fc6

Browse files
authored
Resolve track vars in unsat cores (#105)
* Get rid of track vars in unsat cores * Handle check-sat errors as unknowns * Update examples * Upgrade version to 0.5.1
1 parent 2fd0701 commit f0f0fc6

File tree

28 files changed

+424
-350
lines changed

28 files changed

+424
-350
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ repositories {
1414
}
1515

1616
// core
17-
implementation("com.github.UnitTestBot.ksmt:ksmt-core:0.5.0")
17+
implementation("com.github.UnitTestBot.ksmt:ksmt-core:0.5.1")
1818
// z3 solver
19-
implementation("com.github.UnitTestBot.ksmt:ksmt-z3:0.5.0")
19+
implementation("com.github.UnitTestBot.ksmt:ksmt-z3:0.5.1")
2020
```
2121

2222
## Usage

buildSrc/src/main/kotlin/org.ksmt.ksmt-base.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ plugins {
99
}
1010

1111
group = "org.ksmt"
12-
version = "0.5.0"
12+
version = "0.5.1"
1313

1414
repositories {
1515
mavenCentral()

docs/custom-expressions.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,8 @@ class CustomSolver<C : KSolverConfiguration>(
216216
solver.assert(transformer.apply(expr))
217217

218218
// expr can contain custom expressions -> rewrite
219-
override fun assertAndTrack(expr: KExpr<KBoolSort>, trackVar: KConstDecl<KBoolSort>) =
220-
solver.assertAndTrack(transformer.apply(expr), trackVar)
219+
override fun assertAndTrack(expr: KExpr<KBoolSort>) =
220+
solver.assertAndTrack(transformer.apply(expr))
221221

222222
// assumptions can contain custom expressions -> rewrite
223223
override fun checkWithAssumptions(assumptions: List<KExpr<KBoolSort>>, timeout: Duration): KSolverStatus =
@@ -262,10 +262,10 @@ class CustomModel(
262262
override val uninterpretedSorts: Set<KUninterpretedSort>
263263
get() = model.uninterpretedSorts
264264

265-
override fun <T : KSort> interpretation(decl: KDecl<T>): KModel.KFuncInterp<T>? =
265+
override fun <T : KSort> interpretation(decl: KDecl<T>): KFuncInterp<T>? =
266266
model.interpretation(decl)
267267

268-
override fun uninterpretedSortUniverse(sort: KUninterpretedSort): Set<KExpr<KUninterpretedSort>>? =
268+
override fun uninterpretedSortUniverse(sort: KUninterpretedSort): Set<KUninterpretedSortValue>? =
269269
model.uninterpretedSortUniverse(sort)
270270

271271
override fun detach(): KModel = CustomModel(model.detach(), transformer)

docs/getting-started.md

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,17 @@ repositories {
1818
```kotlin
1919
dependencies {
2020
// core
21-
implementation("com.github.UnitTestBot.ksmt:ksmt-core:0.5.0")
21+
implementation("com.github.UnitTestBot.ksmt:ksmt-core:0.5.1")
2222
}
2323
```
2424

2525
#### 3. Add one or more SMT solver dependencies:
2626
```kotlin
2727
dependencies {
2828
// z3
29-
implementation("com.github.UnitTestBot.ksmt:ksmt-z3:0.5.0")
29+
implementation("com.github.UnitTestBot.ksmt:ksmt-z3:0.5.1")
3030
// bitwuzla
31-
implementation("com.github.UnitTestBot.ksmt:ksmt-bitwuzla:0.5.0")
31+
implementation("com.github.UnitTestBot.ksmt:ksmt-bitwuzla:0.5.1")
3232
}
3333
```
3434
SMT solver specific packages are provided with solver native binaries.
@@ -315,9 +315,9 @@ with(ctx) {
315315

316316
/**
317317
* Assert and track e2
318-
* Track variable e2Track will appear in unsat core
318+
* e2 will appear in unsat core
319319
* */
320-
val e2Track = solver.assertAndTrack(e2)
320+
solver.assertAndTrack(e2)
321321

322322
/**
323323
* Check satisfiability with e3 assumed.
@@ -328,18 +328,15 @@ with(ctx) {
328328

329329
// retrieve unsat core
330330
val core = solver.unsatCore()
331-
println("unsat core = $core") // [track!fresh!0, (not c)]
331+
println("unsat core = $core") // [(not (and b a)), (not c)]
332332

333333
// simply asserted expression cannot be in unsat core
334334
println("e1 in core = ${e1 in core}") // false
335-
/**
336-
* An expression added with assertAndTrack cannot be in unsat core.
337-
* The corresponding track variable is used instead of the expression itself.
338-
*/
339-
println("e2 in core = ${e2 in core}") // false
340-
println("e2Track in core = ${e2Track in core}") // true
341335

342-
//the assumed expression appears in unsat core as is
336+
// an expression added with assertAndTrack appears in unsat core as is.
337+
println("e2 in core = ${e2 in core}") // true
338+
339+
// the assumed expression appears in unsat core as is
343340
println("e3 in core = ${e3 in core}") // true
344341
}
345342
}

examples/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ repositories {
1010

1111
dependencies {
1212
// core
13-
implementation("com.github.UnitTestBot.ksmt:ksmt-core:0.5.0")
13+
implementation("com.github.UnitTestBot.ksmt:ksmt-core:0.5.1")
1414
// z3 solver
15-
implementation("com.github.UnitTestBot.ksmt:ksmt-z3:0.5.0")
15+
implementation("com.github.UnitTestBot.ksmt:ksmt-z3:0.5.1")
1616
}
1717

1818
java {

examples/src/main/kotlin/CustomExpressions.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import org.ksmt.KContext
22
import org.ksmt.cache.hash
33
import org.ksmt.cache.structurallyEqual
4-
import org.ksmt.decl.KConstDecl
54
import org.ksmt.decl.KDecl
65
import org.ksmt.expr.KExpr
76
import org.ksmt.expr.KUninterpretedSortValue
@@ -13,6 +12,7 @@ import org.ksmt.solver.KModel
1312
import org.ksmt.solver.KSolver
1413
import org.ksmt.solver.KSolverConfiguration
1514
import org.ksmt.solver.KSolverStatus
15+
import org.ksmt.solver.model.KFuncInterp
1616
import org.ksmt.sort.KBoolSort
1717
import org.ksmt.sort.KBvSort
1818
import org.ksmt.sort.KSort
@@ -174,8 +174,8 @@ class CustomSolver<C : KSolverConfiguration>(
174174
solver.assert(transformer.apply(expr))
175175

176176
// expr can contain custom expressions -> rewrite
177-
override fun assertAndTrack(expr: KExpr<KBoolSort>, trackVar: KConstDecl<KBoolSort>) =
178-
solver.assertAndTrack(transformer.apply(expr), trackVar)
177+
override fun assertAndTrack(expr: KExpr<KBoolSort>) =
178+
solver.assertAndTrack(transformer.apply(expr))
179179

180180
// assumptions can contain custom expressions -> rewrite
181181
override fun checkWithAssumptions(assumptions: List<KExpr<KBoolSort>>, timeout: Duration): KSolverStatus =
@@ -219,7 +219,7 @@ class CustomModel(
219219
override val uninterpretedSorts: Set<KUninterpretedSort>
220220
get() = model.uninterpretedSorts
221221

222-
override fun <T : KSort> interpretation(decl: KDecl<T>): KModel.KFuncInterp<T>? =
222+
override fun <T : KSort> interpretation(decl: KDecl<T>): KFuncInterp<T>? =
223223
model.interpretation(decl)
224224

225225
override fun uninterpretedSortUniverse(sort: KUninterpretedSort): Set<KUninterpretedSortValue>? =

examples/src/main/kotlin/GettingStartedExample.kt

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -231,9 +231,9 @@ private fun unsatCoreGenerationExample(ctx: KContext) =
231231

232232
/**
233233
* Assert and track e2
234-
* Track variable e2Track will appear in unsat core
234+
* e2 will appear in unsat core
235235
* */
236-
val e2Track = solver.assertAndTrack(e2)
236+
solver.assertAndTrack(e2)
237237

238238
/**
239239
* Check satisfiability with e3 assumed.
@@ -244,18 +244,15 @@ private fun unsatCoreGenerationExample(ctx: KContext) =
244244

245245
// retrieve unsat core
246246
val core = solver.unsatCore()
247-
println("unsat core = $core") // [track!fresh!0, (not c)]
247+
println("unsat core = $core") // [(not (and b a)), (not c)]
248248

249249
// simply asserted expression cannot be in unsat core
250250
println("e1 in core = ${e1 in core}") // false
251-
/**
252-
* An expression added with assertAndTrack cannot be in unsat core.
253-
* The corresponding track variable is used instead of the expression itself.
254-
*/
255-
println("e2 in core = ${e2 in core}") // false
256-
println("e2Track in core = ${e2Track in core}") // true
257251

258-
//the assumed expression appears in unsat core as is
252+
// an expression added with assertAndTrack appears in unsat core as is.
253+
println("e2 in core = ${e2 in core}") // true
254+
255+
// the assumed expression appears in unsat core as is
259256
println("e3 in core = ${e3 in core}") // true
260257
}
261258
}

ksmt-bitwuzla/src/main/kotlin/org/ksmt/solver/bitwuzla/KBitwuzlaSolver.kt

Lines changed: 65 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
package org.ksmt.solver.bitwuzla
22

3+
import it.unimi.dsi.fastutil.longs.LongOpenHashSet
34
import org.ksmt.KContext
4-
import org.ksmt.decl.KConstDecl
55
import org.ksmt.expr.KExpr
66
import org.ksmt.solver.KModel
77
import org.ksmt.solver.KSolver
88
import org.ksmt.solver.KSolverStatus
9+
import org.ksmt.solver.bitwuzla.bindings.BitwuzlaNativeException
910
import org.ksmt.solver.bitwuzla.bindings.BitwuzlaOption
1011
import org.ksmt.solver.bitwuzla.bindings.BitwuzlaResult
1112
import org.ksmt.solver.bitwuzla.bindings.BitwuzlaTerm
13+
import org.ksmt.solver.bitwuzla.bindings.BitwuzlaTermArray
1214
import org.ksmt.solver.bitwuzla.bindings.Native
1315
import org.ksmt.sort.KBoolSort
1416
import kotlin.time.Duration
@@ -21,15 +23,19 @@ open class KBitwuzlaSolver(private val ctx: KContext) : KSolver<KBitwuzlaSolverC
2123
open val exprConverter: KBitwuzlaExprConverter by lazy {
2224
KBitwuzlaExprConverter(ctx, bitwuzlaCtx)
2325
}
26+
2427
private var lastCheckStatus = KSolverStatus.UNKNOWN
28+
private var lastReasonOfUnknown: String? = null
29+
private var lastAssumptions: TrackedAssumptions? = null
30+
private var lastModel: KBitwuzlaModel? = null
2531

2632
init {
2733
Native.bitwuzlaSetOption(bitwuzlaCtx.bitwuzla, BitwuzlaOption.BITWUZLA_OPT_INCREMENTAL, value = 1)
2834
Native.bitwuzlaSetOption(bitwuzlaCtx.bitwuzla, BitwuzlaOption.BITWUZLA_OPT_PRODUCE_MODELS, value = 1)
2935
}
3036

31-
private var trackVars = mutableListOf<Pair<KExpr<KBoolSort>, BitwuzlaTerm>>()
32-
private val trackVarsAssertionFrames = arrayListOf(trackVars)
37+
private var trackedAssertions = mutableListOf<Pair<KExpr<KBoolSort>, BitwuzlaTerm>>()
38+
private val trackVarsAssertionFrames = arrayListOf(trackedAssertions)
3339

3440
override fun configure(configurator: KBitwuzlaSolverConfiguration.() -> Unit) {
3541
KBitwuzlaSolverConfigurationImpl(bitwuzlaCtx.bitwuzla).configurator()
@@ -46,23 +52,23 @@ open class KBitwuzlaSolver(private val ctx: KContext) : KSolver<KBitwuzlaSolverC
4652
Native.bitwuzlaAssert(bitwuzlaCtx.bitwuzla, assertionWithAxioms.assertion)
4753
}
4854

49-
override fun assertAndTrack(expr: KExpr<KBoolSort>, trackVar: KConstDecl<KBoolSort>) = bitwuzlaCtx.bitwuzlaTry {
50-
ctx.ensureContextMatch(expr, trackVar)
55+
override fun assertAndTrack(expr: KExpr<KBoolSort>) = bitwuzlaCtx.bitwuzlaTry {
56+
ctx.ensureContextMatch(expr)
5157

52-
val trackVarExpr = ctx.mkConstApp(trackVar)
58+
val trackVarExpr = ctx.mkFreshConst("track", ctx.boolSort)
5359
val trackedExpr = with(ctx) { !trackVarExpr or expr }
5460

5561
assert(trackedExpr)
5662

5763
val trackVarTerm = with(exprInternalizer) { trackVarExpr.internalize() }
58-
trackVars += trackVarExpr to trackVarTerm
64+
trackedAssertions += expr to trackVarTerm
5965
}
6066

6167
override fun push(): Unit = bitwuzlaCtx.bitwuzlaTry {
6268
Native.bitwuzlaPush(bitwuzlaCtx.bitwuzla, nlevels = 1)
6369

64-
trackVars = trackVars.toMutableList()
65-
trackVarsAssertionFrames.add(trackVars)
70+
trackedAssertions = trackedAssertions.toMutableList()
71+
trackVarsAssertionFrames.add(trackedAssertions)
6672

6773
bitwuzlaCtx.createNestedDeclarationScope()
6874
}
@@ -80,35 +86,28 @@ open class KBitwuzlaSolver(private val ctx: KContext) : KSolver<KBitwuzlaSolverC
8086
bitwuzlaCtx.popDeclarationScope()
8187
}
8288

83-
trackVars = trackVarsAssertionFrames.last()
89+
trackedAssertions = trackVarsAssertionFrames.last()
8490

8591
Native.bitwuzlaPop(bitwuzlaCtx.bitwuzla, n.toInt())
8692
}
8793

8894
override fun check(timeout: Duration): KSolverStatus =
8995
checkWithAssumptions(emptyList(), timeout)
9096

91-
private val lastAssumptions = arrayListOf<Pair<KExpr<KBoolSort>, BitwuzlaTerm>>()
92-
93-
private fun assumeExpr(expr: KExpr<KBoolSort>, term: BitwuzlaTerm) {
94-
lastAssumptions += expr to term
95-
Native.bitwuzlaAssume(bitwuzlaCtx.bitwuzla, term)
96-
}
97-
9897
override fun checkWithAssumptions(assumptions: List<KExpr<KBoolSort>>, timeout: Duration): KSolverStatus =
99-
bitwuzlaCtx.bitwuzlaTry {
98+
bitwuzlaTryCheck {
10099
ctx.ensureContextMatch(assumptions)
101100

102-
invalidatePreviousModel()
103-
lastAssumptions.clear()
101+
val currentAssumptions = TrackedAssumptions().also { lastAssumptions = it }
104102

105-
trackVars.forEach {
106-
assumeExpr(it.first, it.second)
103+
trackedAssertions.forEach {
104+
currentAssumptions.assumeTrackedAssertion(it)
107105
}
108106

109-
assumptions.forEach {
110-
val assumptionTerm = with(exprInternalizer) { it.internalize() }
111-
assumeExpr(it, assumptionTerm)
107+
with(exprInternalizer) {
108+
assumptions.forEach {
109+
currentAssumptions.assumeAssumption(it, it.internalize())
110+
}
112111
}
113112

114113
checkWithTimeout(timeout).processCheckResult()
@@ -120,16 +119,6 @@ open class KBitwuzlaSolver(private val ctx: KContext) : KSolver<KBitwuzlaSolverC
120119
Native.bitwuzlaCheckSatTimeoutResult(bitwuzlaCtx.bitwuzla, timeout.inWholeMilliseconds)
121120
}
122121

123-
private var lastModel: KBitwuzlaModel? = null
124-
125-
/**
126-
* Bitwuzla model is only valid until the next check-sat call.
127-
* */
128-
private fun invalidatePreviousModel() {
129-
lastModel?.markInvalid()
130-
lastModel = null
131-
}
132-
133122
override fun model(): KModel = bitwuzlaCtx.bitwuzlaTry {
134123
require(lastCheckStatus == KSolverStatus.SAT) { "Model are only available after SAT checks" }
135124
val model = lastModel ?: KBitwuzlaModel(
@@ -143,9 +132,8 @@ open class KBitwuzlaSolver(private val ctx: KContext) : KSolver<KBitwuzlaSolverC
143132

144133
override fun unsatCore(): List<KExpr<KBoolSort>> = bitwuzlaCtx.bitwuzlaTry {
145134
require(lastCheckStatus == KSolverStatus.UNSAT) { "Unsat cores are only available after UNSAT checks" }
146-
val unsatCore = Native.bitwuzlaGetUnsatAssumptions(bitwuzlaCtx.bitwuzla).toSet()
147-
148-
return lastAssumptions.filter { it.second in unsatCore }.map { it.first }
135+
val unsatAssumptions = Native.bitwuzlaGetUnsatAssumptions(bitwuzlaCtx.bitwuzla)
136+
lastAssumptions?.resolveUnsatCore(unsatAssumptions) ?: emptyList()
149137
}
150138

151139
override fun reasonOfUnknown(): String = bitwuzlaCtx.bitwuzlaTry {
@@ -154,7 +142,7 @@ open class KBitwuzlaSolver(private val ctx: KContext) : KSolver<KBitwuzlaSolverC
154142
}
155143

156144
// There is no way to retrieve reason of unknown from Bitwuzla in general case.
157-
return "unknown"
145+
return lastReasonOfUnknown ?: "unknown"
158146
}
159147

160148
override fun interrupt() = bitwuzlaCtx.bitwuzlaTry {
@@ -170,4 +158,42 @@ open class KBitwuzlaSolver(private val ctx: KContext) : KSolver<KBitwuzlaSolverC
170158
BitwuzlaResult.BITWUZLA_UNSAT -> KSolverStatus.UNSAT
171159
BitwuzlaResult.BITWUZLA_UNKNOWN -> KSolverStatus.UNKNOWN
172160
}.also { lastCheckStatus = it }
161+
162+
private fun invalidateSolverState() {
163+
/**
164+
* Bitwuzla model is only valid until the next check-sat call.
165+
* */
166+
lastModel?.markInvalid()
167+
lastModel = null
168+
169+
lastCheckStatus = KSolverStatus.UNKNOWN
170+
lastReasonOfUnknown = null
171+
172+
lastAssumptions = null
173+
}
174+
175+
private inline fun bitwuzlaTryCheck(body: () -> KSolverStatus): KSolverStatus = try {
176+
invalidateSolverState()
177+
body()
178+
} catch (ex: BitwuzlaNativeException) {
179+
lastReasonOfUnknown = ex.message
180+
KSolverStatus.UNKNOWN.also { lastCheckStatus = it }
181+
}
182+
183+
private inner class TrackedAssumptions {
184+
private val assumedExprs = arrayListOf<Pair<KExpr<KBoolSort>, BitwuzlaTerm>>()
185+
186+
fun assumeTrackedAssertion(trackedAssertion: Pair<KExpr<KBoolSort>, BitwuzlaTerm>) {
187+
assumedExprs.add(trackedAssertion)
188+
Native.bitwuzlaAssume(bitwuzlaCtx.bitwuzla, trackedAssertion.second)
189+
}
190+
191+
fun assumeAssumption(expr: KExpr<KBoolSort>, term: BitwuzlaTerm) =
192+
assumeTrackedAssertion(expr to term)
193+
194+
fun resolveUnsatCore(unsatAssumptions: BitwuzlaTermArray): List<KExpr<KBoolSort>> {
195+
val unsatCoreTerms = LongOpenHashSet(unsatAssumptions)
196+
return assumedExprs.mapNotNull { (expr, term) -> expr.takeIf { unsatCoreTerms.contains(term) } }
197+
}
198+
}
173199
}

0 commit comments

Comments
 (0)