Skip to content

Commit b0d1389

Browse files
authored
Better usage documentation and examples (#30)
* Better usage documentation and examples * Java examples * Upgrade KSMT version * Version 0.2.1
1 parent c8c010a commit b0d1389

File tree

9 files changed

+849
-11
lines changed

9 files changed

+849
-11
lines changed

README.md

+14-7
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,30 @@
11
# KSMT
2-
Kotlin API for various SMT solvers
2+
Kotlin API for various SMT solvers.
33

4-
[![KSMT: build](https://github.com/UnitTestBot/ksmt/actions/workflows/build-and-run-tests.yml/badge.svg)](https://github.com/UnitTestBot/UTBotJava/ksmt/workflows/build-and-run-tests.yml)
4+
[![KSMT: build](https://github.com/UnitTestBot/ksmt/actions/workflows/build-and-run-tests.yml/badge.svg)](https://github.com/UnitTestBot/ksmt/workflows/build-and-run-tests.yml)
5+
[![JitPack](https://jitpack.io/v/UnitTestBot/ksmt.svg)](https://jitpack.io/#UnitTestBot/ksmt)
56

67
# Getting started
7-
Install via JitPack and Gradle
8+
Install via [JitPack](https://jitpack.io/) and [Gradle](https://gradle.org/).
89

910
```kotlin
11+
// JitPack repository
12+
repositories {
13+
maven { url = uri("https://jitpack.io") }
14+
}
15+
1016
// core
11-
implementation("com.github.UnitTestBot.ksmt:ksmt-core:0.1.0")
17+
implementation("com.github.UnitTestBot.ksmt:ksmt-core:0.2.1")
1218
// z3 solver
13-
implementation("com.github.UnitTestBot.ksmt:ksmt-z3:0.1.0")
19+
implementation("com.github.UnitTestBot.ksmt:ksmt-z3:0.2.1")
1420
```
1521

1622
## Usage
17-
See [examples](examples)
23+
Check out our [Getting started guide](docs/getting-started.md) and the [example project](examples).
24+
Also, check out the [Java examples](examples/src/main/java).
1825

1926
# Features
20-
Currently KSMT supports the following SMT solvers:
27+
Currently, KSMT supports the following SMT solvers:
2128

2229
| SMT Solver | Linux-x64 | Windows-x64 | MacOS-x64 |
2330
|--------------------------------------------------|:------------------:|:------------------:|:------------------:|

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ plugins {
99
}
1010

1111
group = "org.ksmt"
12-
version = "0.0.1"
12+
version = "0.2.1"
1313

1414
repositories {
1515
mavenCentral()

docs/getting-started.md

+341
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
# Getting started
2+
3+
For code examples, please check out our [project](/examples).
4+
5+
## Installation
6+
7+
Installation via [JitPack](https://jitpack.io/) and [Gradle](https://gradle.org/).
8+
9+
#### 1. Enable JitPack repository in your build configuration:
10+
```kotlin
11+
// JitPack repository
12+
repositories {
13+
maven { url = uri("https://jitpack.io") }
14+
}
15+
```
16+
17+
#### 2. Add KSMT core dependency:
18+
```kotlin
19+
dependencies {
20+
// core
21+
implementation("com.github.UnitTestBot.ksmt:ksmt-core:0.2.1")
22+
}
23+
```
24+
25+
#### 3. Add one or more SMT solver dependencies:
26+
```kotlin
27+
dependencies {
28+
// z3
29+
implementation("com.github.UnitTestBot.ksmt:ksmt-z3:0.2.1")
30+
// bitwuzla
31+
implementation("com.github.UnitTestBot.ksmt:ksmt-bitwuzla:0.2.1")
32+
}
33+
```
34+
SMT solver specific packages are provided with solver native binaries.
35+
Check OS compatibility [here](https://github.com/UnitTestBot/ksmt/tree/docs#features).
36+
37+
## KSMT usage
38+
39+
First, create a KSMT context that manages all expressions and solvers.
40+
```kotlin
41+
val ctx = KContext()
42+
```
43+
44+
### Working with SMT formulas
45+
46+
Once the context is initialized, we can create expressions.
47+
In this example, we want to create an expression
48+
49+
`a && (b >= c + 3)`
50+
51+
over Boolean variable `a` and integer variables `b` and `c`.
52+
53+
```kotlin
54+
with(ctx) {
55+
// create symbolic variables
56+
val a by boolSort
57+
val b by intSort
58+
val c by intSort
59+
60+
// create expression
61+
val constraint = a and (b ge c + 3.intExpr)
62+
}
63+
```
64+
All KSMT expressions are typed and incorrect terms (e.g. `and` with integer arguments)
65+
result in a compile-time error.
66+
67+
### Working with SMT solvers
68+
69+
To check SMT formula satisfiability we need to instantiate an SMT solver.
70+
In this example, we use `constraint` from a previous step as an SMT formula
71+
and we use Z3 as an SMT solver.
72+
73+
```kotlin
74+
with(ctx) {
75+
KZ3Solver(this).use { solver -> // create s Z3 SMT solver instance
76+
// assert expression
77+
solver.assert(constraint)
78+
79+
// check assertions satisfiability with timeout
80+
val satisfiability = solver.check(timeout = 1.seconds)
81+
println(satisfiability) // SAT
82+
83+
// obtain model
84+
val model = solver.model()
85+
86+
println("$a = ${model.eval(a)}") // a = true
87+
println("$b = ${model.eval(b)}") // b = 0
88+
println("$c = ${model.eval(c)}") // c = -3
89+
}
90+
}
91+
```
92+
The formula in the example above is satisfiable and we can obtain a model.
93+
The model contains concrete values of our symbolic variables `a`, `b` and `c`
94+
which evaluate our formula to `true`.
95+
96+
Note the use of kotlin `.use { }` construction
97+
which is useful for releasing all native resources acquired by the solver.
98+
99+
### Solver incremental API
100+
101+
KSMT solver API provides two approaches to incremental formula solving: push/pop
102+
and assumptions.
103+
104+
#### Incremental solving with push/pop
105+
106+
The push and pop operations in the solver allow us to work with assertions in the same way as with a stack.
107+
The push operation saves the currently asserted expressions onto the stack,
108+
while the pop operation removes previously pushed assertions.
109+
110+
```kotlin
111+
with(ctx) {
112+
// create symbolic variables
113+
val cond1 by boolSort
114+
val cond2 by boolSort
115+
val a by mkBv32Sort()
116+
val b by mkBv32Sort()
117+
val c by mkBv32Sort()
118+
val goal by mkBv32Sort()
119+
120+
KZ3Solver(this).use { solver ->
121+
// a == 0
122+
solver.assert(a eq mkBv(value = 0))
123+
// goal == 2
124+
solver.assert(goal eq mkBv(value = 2))
125+
126+
// push assertions stack
127+
solver.push()
128+
129+
// a == goal
130+
solver.assert(a eq goal)
131+
132+
/**
133+
* Formula is unsatisfiable because we have
134+
* a == 0 && goal == 2 && a == goal
135+
*/
136+
val check0 = solver.check(timeout = 1.seconds)
137+
println("check0 = $check0") // UNSAT
138+
139+
// pop assertions stack. a == goal is removed
140+
solver.pop()
141+
142+
/**
143+
* Formula is satisfiable now because we have
144+
* a == 0 && goal == 2
145+
*/
146+
val check1 = solver.check(timeout = 1.seconds)
147+
println("check1 = $check1") // SAT
148+
149+
// b == if (cond1) a + 1 else a
150+
solver.assert(b eq mkIte(cond1, mkBvAddExpr(a, mkBv(value = 1)), a))
151+
152+
// push assertions stack
153+
solver.push()
154+
155+
// b == goal
156+
solver.assert(b eq goal)
157+
158+
/**
159+
* Formula is unsatisfiable because we have
160+
* a == 0 && goal == 2
161+
* && b == if (cond1) a + 1 else a
162+
* && goal == b
163+
* where all possible values for b are only 0 and 1
164+
*/
165+
val check2 = solver.check(timeout = 1.seconds)
166+
println("check2 = $check2") // UNSAT
167+
168+
// pop assertions stack. b == goal is removed
169+
solver.pop()
170+
171+
/**
172+
* Formula is satisfiable now because we have
173+
* a == 0 && goal == 2
174+
* && b == if (cond1) a + 1 else a
175+
*/
176+
val check3 = solver.check(timeout = 1.seconds)
177+
println("check3 = $check3") // SAT
178+
179+
// c == if (cond2) b + 1 else b
180+
solver.assert(c eq mkIte(cond2, mkBvAddExpr(b, mkBv(value = 1)), b))
181+
182+
// push assertions stack
183+
solver.push()
184+
185+
// c == goal
186+
solver.assert(c eq goal)
187+
188+
/**
189+
* Formula is satisfiable because we have
190+
* a == 0 && goal == 2
191+
* && b == if (cond1) a + 1 else a
192+
* && c == if (cond2) b + 1 else b
193+
* && goal == c
194+
* where all possible values for b are 0 and 1
195+
* and for c we have 0, 1 and 2
196+
*/
197+
val check4 = solver.check(timeout = 1.seconds)
198+
println("check4 = $check4") // SAT
199+
}
200+
}
201+
```
202+
203+
#### Incremental solving with assumptions
204+
205+
Assumption mechanism allows us to assert an expression for a single check
206+
without actually adding it to the assertions.
207+
The following example shows how to implement the previous example using assumptions
208+
instead of push and pop operations.
209+
210+
```kotlin
211+
with(ctx) {
212+
// create symbolic variables
213+
val cond1 by boolSort
214+
val cond2 by boolSort
215+
val a by mkBv32Sort()
216+
val b by mkBv32Sort()
217+
val c by mkBv32Sort()
218+
val goal by mkBv32Sort()
219+
220+
KZ3Solver(this).use { solver ->
221+
// a == 0
222+
solver.assert(a eq mkBv(value = 0))
223+
// goal == 2
224+
solver.assert(goal eq mkBv(value = 2))
225+
226+
/**
227+
* Formula is unsatisfiable because we have
228+
* a == 0 && goal == 2 && a == goal
229+
* Expression a == goal is assumed for current check
230+
*/
231+
val check0 = solver.checkWithAssumptions(
232+
assumptions = listOf(a eq goal),
233+
timeout = 1.seconds
234+
)
235+
println("check0 = $check0") // UNSAT
236+
237+
/**
238+
* Formula is satisfiable because we have
239+
* a == 0 && goal == 2
240+
*/
241+
val check1 = solver.check(timeout = 1.seconds)
242+
println("check1 = $check1") // SAT
243+
244+
// b == if (cond1) a + 1 else a
245+
solver.assert(b eq mkIte(cond1, mkBvAddExpr(a, mkBv(value = 1)), a))
246+
247+
/**
248+
* Formula is unsatisfiable because we have
249+
* a == 0 && goal == 2
250+
* && b == if (cond1) a + 1 else a
251+
* && goal == b
252+
* where all possible values for b are only 0 and 1
253+
* Expression goal == b is assumed for current check
254+
*/
255+
val check2 = solver.checkWithAssumptions(
256+
assumptions = listOf(b eq goal),
257+
timeout = 1.seconds
258+
)
259+
println("check2 = $check2") // UNSAT
260+
261+
/**
262+
* Formula is satisfiable now because we have
263+
* a == 0 && goal == 2
264+
* && b == if (cond1) a + 1 else a
265+
*/
266+
val check3 = solver.check(timeout = 1.seconds)
267+
println("check3 = $check3") // SAT
268+
269+
// c == if (cond2) b + 1 else b
270+
solver.assert(c eq mkIte(cond2, mkBvAddExpr(b, mkBv(value = 1)), b))
271+
272+
/**
273+
* Formula is satisfiable because we have
274+
* a == 0 && goal == 2
275+
* && b == if (cond1) a + 1 else a
276+
* && c == if (cond2) b + 1 else b
277+
* && goal == c
278+
* where all possible values for b are 0 and 1
279+
* and for c we have 0, 1 and 2
280+
* Expression goal == c is assumed for current check
281+
*/
282+
val check4 = solver.checkWithAssumptions(
283+
assumptions = listOf(c eq goal),
284+
timeout = 1.seconds
285+
)
286+
println("check4 = $check4") // SAT
287+
}
288+
}
289+
```
290+
291+
### Solver unsat cores
292+
293+
If the asserted SMT formula is unsatisfiable, we can extract the [unsat core](https://en.wikipedia.org/wiki/Unsatisfiable_core).
294+
The unsat core is a subset of inconsistent assertions and assumptions.
295+
296+
```kotlin
297+
with(ctx) {
298+
// create symbolic variables
299+
val a by boolSort
300+
val b by boolSort
301+
val c by boolSort
302+
303+
val e1 = (a and b) or c
304+
val e2 = !(a and b)
305+
val e3 = !c
306+
307+
KZ3Solver(this).use { solver ->
308+
// simply assert e1
309+
solver.assert(e1)
310+
311+
/**
312+
* Assert and track e2
313+
* Track variable e2Track will appear in unsat core
314+
* */
315+
val e2Track = solver.assertAndTrack(e2)
316+
317+
/**
318+
* Check satisfiability with e3 assumed.
319+
* Formula is unsatisfiable because e1 is inconsistent with e2 and e3
320+
* */
321+
val check = solver.checkWithAssumptions(assumptions = listOf(e3))
322+
println("check = $check")
323+
324+
// retrieve unsat core
325+
val core = solver.unsatCore()
326+
println("unsat core = $core") // [track!fresh!0, (not c)]
327+
328+
// simply asserted expression cannot be in unsat core
329+
println("e1 in core = ${e1 in core}") // false
330+
/**
331+
* An expression added with assertAndTrack cannot be in unsat core.
332+
* The corresponding track variable is used instead of the expression itself.
333+
*/
334+
println("e2 in core = ${e2 in core}") // false
335+
println("e2Track in core = ${e2Track in core}") // true
336+
337+
//the assumed expression appears in unsat core as is
338+
println("e3 in core = ${e3 in core}") // true
339+
}
340+
}
341+
```

examples/README.md

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
## KSMT example project
2+
3+
### Gradle configuration
4+
5+
Try our example [Gradle build configuration](build.gradle.kts).
6+
7+
### Usage examples
8+
9+
To learn more about usage, check out the [examples](src/main/kotlin).
10+
Also, try the [Java examples](src/main/java).

0 commit comments

Comments
 (0)