Skip to content

Commit 2fd0701

Browse files
authored
Specialized model function interpretations (#101)
* Initial function interpretation specialization * Move function interpretation to separate files * Invalidate solver models * Initial evaluator optimization * Specialized evaluator * Fix model serialization * Track vars usage in Z3 model * Rebase on Bitwuzla model fix
1 parent 7e88851 commit 2fd0701

File tree

17 files changed

+811
-326
lines changed

17 files changed

+811
-326
lines changed

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

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

33
import org.ksmt.KContext
4-
import org.ksmt.decl.KConstDecl
54
import org.ksmt.decl.KDecl
65
import org.ksmt.expr.KExpr
76
import org.ksmt.expr.KUninterpretedSortValue
7+
import org.ksmt.solver.model.KFuncInterp
8+
import org.ksmt.solver.model.KFuncInterpEntryVarsFree
9+
import org.ksmt.solver.model.KFuncInterpEntryVarsFreeOneAry
10+
import org.ksmt.solver.model.KFuncInterpVarsFree
811
import org.ksmt.solver.KModel
912
import org.ksmt.solver.KSolverUnsupportedFeatureException
1013
import org.ksmt.solver.bitwuzla.bindings.BitwuzlaNativeException
1114
import org.ksmt.solver.bitwuzla.bindings.BitwuzlaTerm
1215
import org.ksmt.solver.bitwuzla.bindings.FunValue
1316
import org.ksmt.solver.bitwuzla.bindings.Native
17+
import org.ksmt.solver.model.KFuncInterpWithVars
1418
import org.ksmt.solver.model.KModelEvaluator
1519
import org.ksmt.solver.model.KModelImpl
1620
import org.ksmt.sort.KArraySort
@@ -36,8 +40,18 @@ open class KBitwuzlaModel(
3640
private val evaluatorWithModelCompletion by lazy { KModelEvaluator(ctx, this, isComplete = true) }
3741
private val evaluatorWithoutModelCompletion by lazy { KModelEvaluator(ctx, this, isComplete = false) }
3842

39-
override fun <T : KSort> eval(expr: KExpr<T>, isComplete: Boolean): KExpr<T> {
43+
private var isValid: Boolean = true
44+
45+
fun markInvalid() {
46+
isValid = false
47+
}
48+
49+
private fun ensureModelValid() {
4050
bitwuzlaCtx.ensureActive()
51+
check(isValid) { "The model is no longer valid" }
52+
}
53+
54+
override fun <T : KSort> eval(expr: KExpr<T>, isComplete: Boolean): KExpr<T> {
4155
ctx.ensureContextMatch(expr)
4256

4357
val evaluator = if (isComplete) evaluatorWithModelCompletion else evaluatorWithoutModelCompletion
@@ -68,11 +82,11 @@ open class KBitwuzlaModel(
6882
uninterpretedSortValueContext.currentSortUniverse(sort)
6983
}
7084

71-
private val interpretations: MutableMap<KDecl<*>, KModel.KFuncInterp<*>> = hashMapOf()
85+
private val interpretations: MutableMap<KDecl<*>, KFuncInterp<*>> = hashMapOf()
7286

73-
override fun <T : KSort> interpretation(decl: KDecl<T>): KModel.KFuncInterp<T>? {
87+
override fun <T : KSort> interpretation(decl: KDecl<T>): KFuncInterp<T>? {
88+
ensureModelValid()
7489
ctx.ensureContextMatch(decl)
75-
bitwuzlaCtx.ensureActive()
7690

7791
if (decl !in modelDeclarations) return null
7892

@@ -90,7 +104,7 @@ open class KBitwuzlaModel(
90104
private fun <T : KSort> getInterpretationSafe(
91105
decl: KDecl<T>,
92106
term: BitwuzlaTerm
93-
): KModel.KFuncInterp<T> = bitwuzlaCtx.bitwuzlaTry {
107+
): KFuncInterp<T> = bitwuzlaCtx.bitwuzlaTry {
94108
handleModelIsUnsupportedWithQuantifiers {
95109
getInterpretation(decl, term)
96110
}
@@ -99,16 +113,15 @@ open class KBitwuzlaModel(
99113
private fun <T : KSort> getInterpretation(
100114
decl: KDecl<T>,
101115
term: BitwuzlaTerm
102-
): KModel.KFuncInterp<T> = converter.withUninterpretedSortValueContext(uninterpretedSortValueContext) {
116+
): KFuncInterp<T> = converter.withUninterpretedSortValueContext(uninterpretedSortValueContext) {
103117
when {
104118
Native.bitwuzlaTermIsArray(term) -> arrayInterpretation(decl, term)
105119
Native.bitwuzlaTermIsFun(term) -> functionInterpretation(decl, term)
106120
else -> {
107121
val value = Native.bitwuzlaGetValue(bitwuzlaCtx.bitwuzla, term)
108122
val convertedValue = with(converter) { value.convertExpr(decl.sort) }
109-
KModel.KFuncInterp(
123+
KFuncInterpVarsFree(
110124
decl = decl,
111-
vars = emptyList(),
112125
entries = emptyList(),
113126
default = convertedValue
114127
)
@@ -119,11 +132,11 @@ open class KBitwuzlaModel(
119132
private fun <T : KSort> functionInterpretation(
120133
decl: KDecl<T>,
121134
term: BitwuzlaTerm
122-
): KModel.KFuncInterp<T> {
135+
): KFuncInterp<T> {
123136
val interp = Native.bitwuzlaGetFunValue(bitwuzlaCtx.bitwuzla, term)
124137
return if (interp.size != 0) {
125-
handleArrayFunctionDecl(decl) { functionDecl, vars ->
126-
functionValueInterpretation(functionDecl, vars, interp)
138+
handleArrayFunctionDecl(decl) { functionDecl ->
139+
functionValueInterpretation(functionDecl, interp)
127140
}
128141
} else {
129142
/**
@@ -136,21 +149,19 @@ open class KBitwuzlaModel(
136149

137150
private fun <T : KSort> KBitwuzlaExprConverter.functionValueInterpretation(
138151
decl: KDecl<T>,
139-
vars: List<KConstDecl<*>>,
140152
interp: FunValue
141-
): KModel.KFuncInterp<T> {
142-
val entries = mutableListOf<KModel.KFuncInterpEntry<T>>()
153+
): KFuncInterpVarsFree<T> {
154+
val entries = mutableListOf<KFuncInterpEntryVarsFree<T>>()
143155

144156
for (i in 0 until interp.size) {
145157
// Don't substitute vars since arguments in Bitwuzla model are always constants
146158
val args = interp.args!![i].zip(decl.argSorts) { arg, sort -> arg.convertExpr(sort) }
147159
val value = interp.values!![i].convertExpr(decl.sort)
148-
entries += KModel.KFuncInterpEntry(args, value)
160+
entries += KFuncInterpEntryVarsFree.create(args, value)
149161
}
150162

151-
return KModel.KFuncInterp(
163+
return KFuncInterpVarsFree(
152164
decl = decl,
153-
vars = vars,
154165
entries = entries,
155166
default = null
156167
)
@@ -159,7 +170,7 @@ open class KBitwuzlaModel(
159170
private fun <T : KSort> KBitwuzlaExprConverter.retrieveFunctionValue(
160171
decl: KDecl<T>,
161172
functionTerm: BitwuzlaTerm
162-
): KModel.KFuncInterp<T> = handleArrayFunctionInterpretation(decl) { arraySort ->
173+
): KFuncInterp<T> = handleArrayFunctionInterpretation(decl) { arraySort ->
163174
// We expect lambda expression here. Therefore, we convert function interpretation as array.
164175
val functionValue = Native.bitwuzlaGetValue(bitwuzlaCtx.bitwuzla, functionTerm)
165176
functionValue.convertExpr(arraySort)
@@ -168,51 +179,45 @@ open class KBitwuzlaModel(
168179
private fun <T : KSort> arrayInterpretation(
169180
decl: KDecl<T>,
170181
term: BitwuzlaTerm
171-
): KModel.KFuncInterp<T> = handleArrayFunctionDecl(decl) { arrayFunctionDecl, vars ->
182+
): KFuncInterp<T> = handleArrayFunctionDecl(decl) { arrayFunctionDecl ->
172183
val sort: KArraySort<KSort, KSort> = decl.sort.uncheckedCast()
173-
val entries = mutableListOf<KModel.KFuncInterpEntry<KSort>>()
184+
val entries = mutableListOf<KFuncInterpEntryVarsFree<KSort>>()
174185
val interp = Native.bitwuzlaGetArrayValue(bitwuzlaCtx.bitwuzla, term)
175186

176187
for (i in 0 until interp.size) {
177188
val index = interp.indices!![i].convertExpr(sort.domain)
178189
val value = interp.values!![i].convertExpr(sort.range)
179-
entries += KModel.KFuncInterpEntry(listOf(index), value)
190+
entries += KFuncInterpEntryVarsFreeOneAry(index, value)
180191
}
181192

182193
val default = interp.defaultValue.takeIf { it != 0L }?.convertExpr(sort.range)
183194

184-
KModel.KFuncInterp(
195+
KFuncInterpVarsFree(
185196
decl = arrayFunctionDecl,
186-
vars = vars,
187197
entries = entries,
188198
default = default
189199
)
190200
}
191201

192202
private inline fun <T : KSort> handleArrayFunctionDecl(
193203
decl: KDecl<T>,
194-
body: KBitwuzlaExprConverter.(KDecl<KSort>, List<KConstDecl<*>>) -> KModel.KFuncInterp<*>
195-
): KModel.KFuncInterp<T> = with(ctx) {
204+
body: KBitwuzlaExprConverter.(KDecl<KSort>) -> KFuncInterp<*>
205+
): KFuncInterp<T> = with(ctx) {
196206
val sort = decl.sort
197207

198208
if (sort !is KArraySortBase<*>) {
199-
val vars = decl.argSorts.mapIndexed { i, s -> s.mkFreshConstDecl("x!$i") }
200-
return converter.body(decl.uncheckedCast(), vars).uncheckedCast()
209+
return converter.body(decl.uncheckedCast()).uncheckedCast()
201210
}
202211

203212
check(decl.argSorts.isEmpty()) { "Unexpected function with array range" }
204213

205214
val arrayInterpDecl = mkFreshFuncDecl("array", sort.range, sort.domainSorts)
206-
val arrayInterpIndicesDecls = sort.domainSorts.mapIndexed { i, s ->
207-
s.mkFreshConstDecl("idx!$i")
208-
}
209215

210216
modelDeclarations += arrayInterpDecl
211-
interpretations[arrayInterpDecl] = converter.body(arrayInterpDecl, arrayInterpIndicesDecls)
217+
interpretations[arrayInterpDecl] = converter.body(arrayInterpDecl)
212218

213-
KModel.KFuncInterp(
219+
KFuncInterpVarsFree(
214220
decl = decl,
215-
vars = emptyList(),
216221
entries = emptyList(),
217222
default = mkFunctionAsArray(sort.uncheckedCast(), arrayInterpDecl).uncheckedCast()
218223
)
@@ -221,14 +226,13 @@ open class KBitwuzlaModel(
221226
private inline fun <T : KSort> KBitwuzlaExprConverter.handleArrayFunctionInterpretation(
222227
decl: KDecl<T>,
223228
convertInterpretation: (KArraySortBase<*>) -> KExpr<KArraySortBase<*>>
224-
): KModel.KFuncInterp<T> {
229+
): KFuncInterp<T> {
225230
val sort = decl.sort
226231

227232
if (sort is KArraySortBase<*> && decl.argSorts.isEmpty()) {
228233
val arrayInterpretation = convertInterpretation(sort)
229-
return KModel.KFuncInterp(
234+
return KFuncInterpVarsFree(
230235
decl = decl,
231-
vars = emptyList(),
232236
entries = emptyList(),
233237
default = arrayInterpretation.uncheckedCast()
234238
)
@@ -242,7 +246,7 @@ open class KBitwuzlaModel(
242246
val functionVars = decl.argSorts.mapIndexed { i, s -> s.mkFreshConstDecl("x!$i") }
243247
val functionValue = ctx.mkAnyArraySelect(arrayInterpretation, functionVars.map { it.apply() })
244248

245-
return KModel.KFuncInterp(
249+
return KFuncInterpWithVars(
246250
decl = decl,
247251
vars = functionVars,
248252
entries = emptyList(),

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ open class KBitwuzlaSolver(private val ctx: KContext) : KSolver<KBitwuzlaSolverC
9999
bitwuzlaCtx.bitwuzlaTry {
100100
ctx.ensureContextMatch(assumptions)
101101

102+
invalidatePreviousModel()
102103
lastAssumptions.clear()
103104

104105
trackVars.forEach {
@@ -119,13 +120,25 @@ open class KBitwuzlaSolver(private val ctx: KContext) : KSolver<KBitwuzlaSolverC
119120
Native.bitwuzlaCheckSatTimeoutResult(bitwuzlaCtx.bitwuzla, timeout.inWholeMilliseconds)
120121
}
121122

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+
122133
override fun model(): KModel = bitwuzlaCtx.bitwuzlaTry {
123134
require(lastCheckStatus == KSolverStatus.SAT) { "Model are only available after SAT checks" }
124-
return KBitwuzlaModel(
135+
val model = lastModel ?: KBitwuzlaModel(
125136
ctx, bitwuzlaCtx, exprConverter,
126137
bitwuzlaCtx.declarations(),
127138
bitwuzlaCtx.uninterpretedSortsWithRelevantDecls()
128139
)
140+
lastModel = model
141+
model
129142
}
130143

131144
override fun unsatCore(): List<KExpr<KBoolSort>> = bitwuzlaCtx.bitwuzlaTry {

ksmt-bitwuzla/src/test/kotlin/org/ksmt/solver/bitwuzla/Example.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class Example {
6363

6464
solver.close()
6565

66-
assertFailsWith(IllegalStateException::class) { model.eval(a) }
66+
assertFailsWith(IllegalStateException::class) { model.interpretation(b) }
6767
assertEquals(aValue, detachedModel.eval(a))
6868
assertEquals(cValue, detachedModel.eval(c))
6969
}
Lines changed: 1 addition & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package org.ksmt.solver
22

33
import org.ksmt.decl.KDecl
4-
import org.ksmt.decl.KFuncDecl
54
import org.ksmt.expr.KExpr
65
import org.ksmt.expr.KUninterpretedSortValue
6+
import org.ksmt.solver.model.KFuncInterp
77
import org.ksmt.sort.KSort
88
import org.ksmt.sort.KUninterpretedSort
99

@@ -22,44 +22,4 @@ interface KModel {
2222
fun uninterpretedSortUniverse(sort: KUninterpretedSort): Set<KUninterpretedSortValue>?
2323

2424
fun detach(): KModel
25-
26-
data class KFuncInterp<T : KSort>(
27-
val decl: KDecl<T>,
28-
val vars: List<KDecl<*>>,
29-
val entries: List<KFuncInterpEntry<T>>,
30-
val default: KExpr<T>?
31-
) {
32-
init {
33-
if (decl is KFuncDecl<T>) {
34-
require(decl.argSorts.size == vars.size) {
35-
"Function $decl has ${decl.argSorts.size} arguments but ${vars.size} were provided"
36-
}
37-
}
38-
require(entries.all { it.args.size == vars.size }) {
39-
"Function interpretation arguments mismatch"
40-
}
41-
}
42-
43-
val sort: T
44-
get() = decl.sort
45-
46-
override fun toString(): String {
47-
if (entries.isEmpty()) return default.toString()
48-
return buildString {
49-
appendLine('{')
50-
entries.forEach { appendLine(it) }
51-
append("else -> ")
52-
appendLine(default)
53-
append('}')
54-
}
55-
}
56-
}
57-
58-
data class KFuncInterpEntry<T : KSort>(
59-
val args: List<KExpr<*>>,
60-
val value: KExpr<T>
61-
) {
62-
override fun toString(): String =
63-
args.joinToString(prefix = "(", postfix = ") -> $value")
64-
}
6525
}

0 commit comments

Comments
 (0)