|
| 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 | +``` |
0 commit comments