main.io.ksmt.solver.cvc5.KCvc5Context.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ksmt-cvc5-core Show documentation
Show all versions of ksmt-cvc5-core Show documentation
Kotlin API for various SMT solvers
The newest version!
package io.ksmt.solver.cvc5
import io.github.cvc5.Kind
import io.github.cvc5.Solver
import io.github.cvc5.Sort
import io.github.cvc5.Term
import io.ksmt.KContext
import io.ksmt.decl.KDecl
import io.ksmt.expr.KArrayLambda
import io.ksmt.expr.KConst
import io.ksmt.expr.KExistentialQuantifier
import io.ksmt.expr.KExpr
import io.ksmt.expr.KFunctionApp
import io.ksmt.expr.KFunctionAsArray
import io.ksmt.expr.KUninterpretedSortValue
import io.ksmt.expr.KUniversalQuantifier
import io.ksmt.expr.rewrite.KExprUninterpretedDeclCollector
import io.ksmt.expr.transformer.KNonRecursiveTransformer
import io.ksmt.sort.KArray2Sort
import io.ksmt.sort.KArray3Sort
import io.ksmt.sort.KArrayNSort
import io.ksmt.sort.KArraySort
import io.ksmt.sort.KArraySortBase
import io.ksmt.sort.KBoolSort
import io.ksmt.sort.KBvSort
import io.ksmt.sort.KFpRoundingModeSort
import io.ksmt.sort.KFpSort
import io.ksmt.sort.KIntSort
import io.ksmt.sort.KRealSort
import io.ksmt.sort.KSort
import io.ksmt.sort.KSortVisitor
import io.ksmt.sort.KUninterpretedSort
class KCvc5Context(
val termManager: KCvc5TermManager,
val ctx: KContext
) : AutoCloseable {
private var isClosed = false
private val uninterpretedSortCollector = KUninterpretedSortCollector(this)
private var exprCurrentLevelCacheRestorer = KCurrentScopeExprCacheRestorer(uninterpretedSortCollector, ctx)
/**
* We use double-scoped expression internalization cache:
* * current (before pop operation) - [currentScopeExpressions]
* * global (after pop operation) - [expressions]
*
* Due to incremental collect of declarations and uninterpreted sorts for the model,
* we collect them during internalizing.
*
* **problem**: However, after asserting the expression that previously was in the cache,
* its uninterpreted sorts / declarations would not be collected repeatedly.
* Then, there are two scenarios:
* 1) we have sorts / decls for the expr on one previous push levels
* 2) we popped the scope, and relevant declarations have been erased
*
* **solution**: Recollect sorts / decls for each expression
* that is in global cache, but whose sorts / decls have been erased after pop()
* (and put this expr to the current scope cache)
*/
private val currentScopeExpressions = HashMap, Term>()
private val expressions = HashMap, Term>()
// we can't use HashMap with Term and Sort (hashcode is not implemented)
private val cvc5Expressions = KCvc5TermMap>()
private val sorts = HashMap()
private val cvc5Sorts = KCvc5SortMap()
private val decls = HashMap, Term>()
private val cvc5Decls = KCvc5TermMap>()
private var currentLevelUninterpretedSorts = hashSetOf()
private val uninterpretedSorts = mutableListOf(currentLevelUninterpretedSorts)
private var currentLevelDeclarations = hashSetOf>()
private val declarations = mutableListOf(currentLevelDeclarations)
fun addUninterpretedSort(sort: KUninterpretedSort) { currentLevelUninterpretedSorts += sort }
/**
* uninterpreted sorts of active push-levels
*/
fun uninterpretedSorts(): List> = uninterpretedSorts
fun addDeclaration(decl: KDecl<*>) {
currentLevelDeclarations += decl
uninterpretedSortCollector.collect(decl)
}
/**
* declarations of active push-levels
*/
fun declarations(): List>> = declarations
val isActive: Boolean
get() = !isClosed
fun push() {
currentLevelDeclarations = hashSetOf()
declarations.add(currentLevelDeclarations)
currentLevelUninterpretedSorts = hashSetOf()
uninterpretedSorts.add(currentLevelUninterpretedSorts)
pushAssertionLevel()
}
fun pop(n: UInt) {
repeat(n.toInt()) {
declarations.removeLast()
uninterpretedSorts.removeLast()
popAssertionLevel()
}
currentLevelDeclarations = declarations.last()
currentLevelUninterpretedSorts = uninterpretedSorts.last()
expressions += currentScopeExpressions
currentScopeExpressions.clear()
// recreate cache restorer to avoid KNonRecursiveTransformer cache
exprCurrentLevelCacheRestorer = KCurrentScopeExprCacheRestorer(uninterpretedSortCollector, ctx)
}
// expr
fun findInternalizedExpr(expr: KExpr<*>): Term? = currentScopeExpressions[expr]
?: expressions[expr]?.also {
/*
* expr is not in current scope cache, but in global cache.
* Recollect declarations and uninterpreted sorts
* and add entire expression tree to the current scope cache from the global
* to avoid re-internalizing with native calls
*/
exprCurrentLevelCacheRestorer.apply(expr)
}
fun findConvertedExpr(expr: Term): KExpr<*>? = cvc5Expressions[expr]
fun saveInternalizedExpr(expr: KExpr<*>, internalized: Term): Term =
internalizeAst(currentScopeExpressions, cvc5Expressions, expr) { internalized }
/**
* save expr, which is in global cache, to the current scope cache
*/
fun savePreviouslyInternalizedExpr(expr: KExpr<*>): Term = saveInternalizedExpr(expr, expressions[expr]!!)
fun saveConvertedExpr(expr: Term, converted: KExpr<*>): KExpr<*> =
convertAst(currentScopeExpressions, cvc5Expressions, expr) { converted }
// sort
fun findInternalizedSort(sort: KSort): Sort? = sorts[sort]
fun findConvertedSort(sort: Sort): KSort? = cvc5Sorts[sort]
fun saveInternalizedSort(sort: KSort, internalized: Sort): Sort =
internalizeAst(sorts, cvc5Sorts, sort) { internalized }
fun saveConvertedSort(sort: Sort, converted: KSort): KSort =
convertAst(sorts, cvc5Sorts, sort) { converted }
inline fun internalizeSort(sort: KSort, internalizer: () -> Sort): Sort =
findOrSave(sort, internalizer, ::findInternalizedSort, ::saveInternalizedSort)
inline fun convertSort(sort: Sort, converter: () -> KSort): KSort =
findOrSave(sort, converter, ::findConvertedSort, ::saveConvertedSort)
// decl
fun findInternalizedDecl(decl: KDecl<*>): Term? = decls[decl]
fun findConvertedDecl(decl: Term): KDecl<*>? = cvc5Decls[decl]
fun saveInternalizedDecl(decl: KDecl<*>, internalized: Term): Term =
internalizeAst(decls, cvc5Decls, decl) { internalized }
fun saveConvertedDecl(decl: Term, converted: KDecl<*>): KDecl<*> =
convertAst(decls, cvc5Decls, decl) { converted }
inline fun internalizeDecl(decl: KDecl<*>, internalizer: () -> Term): Term =
findOrSave(decl, internalizer, ::findInternalizedDecl, ::saveInternalizedDecl)
inline fun convertDecl(decl: Term, converter: () -> KDecl<*>): KDecl<*> =
findOrSave(decl, converter, ::findConvertedDecl, ::saveConvertedDecl)
inline fun findOrSave(
key: K,
computeValue: () -> V,
find: (K) -> V?,
save: (K, V) -> V
): V {
val value = find(key)
if (value != null) return value
return save(key, computeValue())
}
@Suppress("ForbiddenComment")
/**
* Uninterpreted sort values distinct constraints management.
*
* 1. save/register uninterpreted value.
* See [KUninterpretedSortValue] internalization for the details.
* 2. Assert distinct constraints ([assertPendingAxioms]) that may be introduced during internalization.
* Currently, we assert constraints for all the values we have ever internalized.
*
* todo: precise uninterpreted sort values tracking
* */
private data class UninterpretedSortValueDescriptor(
val value: KUninterpretedSortValue,
val nativeUniqueValueDescriptor: Term,
val nativeValueTerm: Term
)
private var currentValueConstraintsLevel = 0
private val assertedConstraintLevels = arrayListOf()
private val uninterpretedSortValueDescriptors = arrayListOf()
private val uninterpretedSortValueInterpreter = hashMapOf()
private val uninterpretedSortValues =
hashMapOf>>()
fun saveUninterpretedSortValue(nativeValue: Term, value: KUninterpretedSortValue): Term {
val sortValues = uninterpretedSortValues.getOrPut(value.sort) { arrayListOf() }
sortValues.add(nativeValue to value)
return nativeValue
}
inline fun registerUninterpretedSortValue(
value: KUninterpretedSortValue,
uniqueValueDescriptorTerm: Term,
uninterpretedValueTerm: Term,
mkInterpreter: () -> Term
) {
val interpreter = getUninterpretedSortValueInterpreter(value.sort)
if (interpreter == null) {
registerUninterpretedSortValueInterpreter(value.sort, mkInterpreter())
}
registerUninterpretedSortValue(value, uniqueValueDescriptorTerm, uninterpretedValueTerm)
}
fun assertPendingAxioms(solver: Solver) {
assertPendingUninterpretedValueConstraints(solver)
}
fun getUninterpretedSortValueInterpreter(sort: KUninterpretedSort): Term? =
uninterpretedSortValueInterpreter[sort]
fun registerUninterpretedSortValueInterpreter(sort: KUninterpretedSort, interpreter: Term) {
uninterpretedSortValueInterpreter[sort] = interpreter
}
fun registerUninterpretedSortValue(
value: KUninterpretedSortValue,
uniqueValueDescriptorTerm: Term,
uninterpretedValueTerm: Term
) {
uninterpretedSortValueDescriptors += UninterpretedSortValueDescriptor(
value = value,
nativeUniqueValueDescriptor = uniqueValueDescriptorTerm,
nativeValueTerm = uninterpretedValueTerm
)
}
fun getRegisteredSortValues(sort: KUninterpretedSort): List> =
uninterpretedSortValues[sort] ?: emptyList()
private fun pushAssertionLevel() {
assertedConstraintLevels.add(currentValueConstraintsLevel)
}
private fun popAssertionLevel() {
currentValueConstraintsLevel = assertedConstraintLevels.removeLast()
}
private fun assertPendingUninterpretedValueConstraints(solver: Solver) {
while (currentValueConstraintsLevel < uninterpretedSortValueDescriptors.size) {
assertUninterpretedSortValueConstraint(
solver,
uninterpretedSortValueDescriptors[currentValueConstraintsLevel]
)
currentValueConstraintsLevel++
}
}
private fun assertUninterpretedSortValueConstraint(solver: Solver, value: UninterpretedSortValueDescriptor) {
val interpreter = uninterpretedSortValueInterpreter[value.value.sort]
?: error("Interpreter was not registered for sort: ${value.value.sort}")
val constraintLhs = termManager.mkTerm(Kind.APPLY_UF, arrayOf(interpreter, value.nativeValueTerm))
val constraint = constraintLhs.eqTerm(value.nativeUniqueValueDescriptor)
solver.assertFormula(constraint)
}
private inline fun internalizeAst(
cache: MutableMap,
reverseCache: MutableMap,
key: K,
internalizer: () -> Term
): Term = cache.getOrPut(key) {
internalizer().also {
reverseCache[it] = key
}
}
private inline fun internalizeAst(
cache: MutableMap,
reverseCache: MutableMap,
key: K,
internalizer: () -> Sort
): Sort = cache.getOrPut(key) {
internalizer().also {
reverseCache[it] = key
}
}
private inline fun convertAst(
cache: MutableMap,
reverseCache: MutableMap,
key: Sort,
converter: () -> K
): K {
val current = reverseCache[key]
if (current != null) return current
val converted = converter()
cache.getOrPut(converted) { key }
reverseCache[key] = converted
return converted
}
private inline fun convertAst(
cache: MutableMap,
reverseCache: MutableMap,
key: Term,
converter: () -> K
): K {
val current = reverseCache[key]
if (current != null) return current
val converted = converter()
cache.getOrPut(converted) { key }
reverseCache[key] = converted
return converted
}
override fun close() {
if (isClosed) return
isClosed = true
currentScopeExpressions.clear()
expressions.clear()
cvc5Expressions.clear()
uninterpretedSorts.clear()
currentLevelUninterpretedSorts.clear()
declarations.clear()
currentLevelDeclarations.clear()
sorts.clear()
cvc5Sorts.clear()
decls.clear()
cvc5Decls.clear()
}
class KUninterpretedSortCollector(private val cvc5Ctx: KCvc5Context) : KSortVisitor {
override fun visit(sort: KBoolSort) = Unit
override fun visit(sort: KIntSort) = Unit
override fun visit(sort: KRealSort) = Unit
override fun visit(sort: S) = Unit
override fun visit(sort: S) = Unit
override fun visit(sort: KArraySort) {
sort.domain.accept(this)
sort.range.accept(this)
}
override fun visit(sort: KArray3Sort) {
sort.domainSorts.forEach { it.accept(this) }
sort.range.accept(this)
}
override fun visit(sort: KArray2Sort) {
sort.domainSorts.forEach { it.accept(this) }
sort.range.accept(this)
}
override fun visit(sort: KArrayNSort) {
sort.domainSorts.forEach { it.accept(this) }
sort.range.accept(this)
}
override fun visit(sort: KFpRoundingModeSort) = Unit
override fun visit(sort: KUninterpretedSort) = cvc5Ctx.addUninterpretedSort(sort)
fun collect(decl: KDecl<*>) {
decl.argSorts.map { it.accept(this) }
decl.sort.accept(this)
}
}
inner class KCurrentScopeExprCacheRestorer(
private val uninterpretedSortCollector: KUninterpretedSortCollector,
ctx: KContext
) : KNonRecursiveTransformer(ctx) {
override fun exprTransformationRequired(expr: KExpr): Boolean = expr !in currentScopeExpressions
override fun transform(expr: KFunctionApp): KExpr = cacheIfNeed(expr) {
[email protected](expr.decl)
uninterpretedSortCollector.collect(expr.decl)
}
override fun transform(expr: KConst): KExpr = cacheIfNeed(expr) {
[email protected](expr.decl)
uninterpretedSortCollector.collect(expr.decl)
[email protected](expr)
}
override fun , R : KSort> transform(expr: KFunctionAsArray): KExpr =
cacheIfNeed(expr) {
[email protected](expr.function)
uninterpretedSortCollector.collect(expr.function)
}
override fun transform(expr: KArrayLambda): KExpr> =
cacheIfNeed(expr) {
transformQuantifier(setOf(expr.indexVarDecl), expr.body)
}
override fun transform(expr: KExistentialQuantifier): KExpr = cacheIfNeed(expr) {
transformQuantifier(expr.bounds.toSet(), expr.body)
}
override fun transform(expr: KUniversalQuantifier): KExpr = cacheIfNeed(expr) {
transformQuantifier(expr.bounds.toSet(), expr.body)
[email protected](expr)
}
private fun transformQuantifier(bounds: Set>, body: KExpr<*>) {
val usedDecls = KExprUninterpretedDeclCollector.collectUninterpretedDeclarations(body) - bounds
usedDecls.forEach(this@KCvc5Context::addDeclaration)
}
private fun > cacheIfNeed(expr: E, transform: E.() -> Unit): KExpr {
if (expr in currentScopeExpressions)
return expr
expr.transform()
[email protected](expr)
return expr
}
}
}