Solutions to Exercises

Solution to Exercise 1

solutions/Exercise1.smt2

1(set-logic QF_LIA)
2
3(declare-const a Int)
4(declare-const b Int)
5
6(assert (= (+ a 10) (* 2 b)))
7(assert (= (+ b 20) (* 2 a)))
8
9(check-sat)

The output is as follows.

The SMT solver says unsat (or “no solution” for the Python version) because there are no integer values that satisfy the constraints.

Solution to Exercise 2

solutions/Exercise2.smt2

1(set-logic QF_BV)
2(set-option :produce-models true)
3
4(declare-const x (_ BitVec 8))
5
6(assert (= (bvmul x (_ bv5 8)) (_ bv1 8)))
7
8(check-sat)
9(get-model)

The output is as follows.

solutions/Exercise2.out.smt2

1sat
2(
3(define-fun x () (_ BitVec 8) #b11001101)
4)

The SMT solver gives a binary value of x as #b11001101. This equals 205 in decimal. And, indeed, \(5 \times 205 = 1025\), and \(1025 \equiv 1\ \mathsf{mod}\ 256\).

Solution to Exercise 3

One way to modify Example 4 to make it satisfiable is to change the hypothesis so that f is applied 2 and 4 times instead of 3 and 5 times. In fact, any two number of applications that are not relatively prime will work.

solutions/Exercise3a.smt2

 1(set-logic QF_UF)
 2(set-option :produce-models true)
 3
 4(declare-sort U 0)
 5
 6(declare-fun f (U) U)
 7(declare-const x U) 
 8
 9(assert (and (= (f (f x)) x) (= (f (f (f (f x)))) x)))
10(assert (distinct (f x) x))
11
12(check-sat)
13(get-model)

The output from cvc5 is as follows.

solutions/Exercise3a.out.smt2

1sat
2(
3; cardinality of U is 2
4; rep: (as @U_0 U)
5; rep: (as @U_1 U)
6(define-fun f ((_arg_1 U)) U (ite (= (as @U_1 U) _arg_1) (as @U_0 U) (as @U_1 U)))
7(define-fun x () U (as @U_1 U))
8)

Notice that for the SMT-LIB example, cvc5 uses a custom output format to describe how it has interpreted the sort U: as a set with two elements, @U_0 and @U_1. Furthermore, cvc5 uses the define-fun command to describe the value assigned to f. In this case, the interpretation of f maps @U_1 to @U_0 and everything else (i.e., @U_0) to @U_1. Then, interpreting x as @U_1 satisfies the constraints. The Python output gives the same model, but it uses Lambda to define the function.

We next consider how to modify Example 5. One way to do this is to additionally require that y and z: are equal.

solutions/Exercise3b.smt2

 1(set-logic QF_UF)
 2
 3(declare-sort U 0)
 4
 5(declare-const b Bool)
 6(declare-const x U) 
 7(declare-const y U) 
 8(declare-const z U) 
 9(declare-const w U) 
10
11(assert (= x (ite b y z)))
12(assert (or (= w y) (= w z)))
13(assert (= y z))
14(assert (distinct x w))
15  
16(check-sat)

The output from cvc5 is as follows.

Solution to Exercise 4

In the SMT-LIB solution, we make use of the incremental option, which enables the commands push and pop. These commands allow us to temporarily add one or more assertions, check for satisfiability, and then return to the previous state. The Python example demonstrates the power of having the solver embedded in a general-purpose programming language: we can use a loop to iterate until no better solution is found. We first show that for the original problem, 30 minutes is the best possible time.

solutions/Exercise4a.smt2

 1(set-logic QF_IDL)
 2(set-option :produce-models true)
 3(set-option :incremental true)
 4
 5(declare-const j11 Int)
 6(declare-const j12 Int) 
 7(declare-const j21 Int)
 8(declare-const j22 Int)
 9(declare-const j31 Int)
10(declare-const j32 Int)
11
12(assert (and (>= j11 0) (>= j12 0) (>= j21 0) (>= j22 0) (>= j31 0) (>= j32 0)))
13(assert (and (>= (- j12 j11) 10) (>= (- j22 j21) 20) (>= (- j32 j31) 5)))
14(assert (and (or (>= (- j22 j11) 10) (>= (- j11 j22) 5))
15             (or (>= (- j31 j11) 10) (>= (- j11 j31) 5))
16             (or (>= (- j31 j22) 5) (>= (- j22 j31) 5))))
17(assert (and (or (>= (- j21 j12) 5) (>= (- j12 j21) 20))
18             (or (>= (- j32 j12) 5) (>= (- j12 j32) 5))
19             (or (>= (- j32 j21) 20) (>= (- j21 j32) 5))))
20
21(define-fun max3 ((i Int) (j Int) (k Int)) Int
22                 (ite (< i j) (ite (< j k) k j) (ite (< i k) k i)))
23
24(push)
25(assert (<= (max3 j12 j22 j32) 25))
26(check-sat)
27(get-model)
28(pop)
29
30(assert (<= (max3 j12 j22 j32) 24))
31(check-sat)

The output from cvc5 is as follows.

solutions/Exercise4a.out.smt2

 1sat
 2(
 3(define-fun j11 () Int 0)
 4(define-fun j12 () Int 25)
 5(define-fun j21 () Int 0)
 6(define-fun j22 () Int 21)
 7(define-fun j31 () Int 15)
 8(define-fun j32 () Int 20)
 9)
10unsat

We now modify the example so that Job 2 only uses machine 2 for 15 minutes. The best solution is now 25 minutes.

solutions/Exercise4b.smt2

 1(set-logic QF_IDL)
 2(set-option :produce-models true)
 3(set-option :incremental true)
 4
 5(declare-const j11 Int)
 6(declare-const j12 Int) 
 7(declare-const j21 Int)
 8(declare-const j22 Int)
 9(declare-const j31 Int)
10(declare-const j32 Int)
11
12(assert (and (>= j11 0) (>= j12 0) (>= j21 0) (>= j22 0) (>= j31 0) (>= j32 0)))
13(assert (and (>= (- j12 j11) 10) (>= (- j22 j21) 15) (>= (- j32 j31) 5)))
14(assert (and (or (>= (- j22 j11) 10) (>= (- j11 j22) 5))
15             (or (>= (- j31 j11) 10) (>= (- j11 j31) 5))
16             (or (>= (- j31 j22) 5) (>= (- j22 j31) 5))))
17(assert (and (or (>= (- j21 j12) 5) (>= (- j12 j21) 15))
18             (or (>= (- j32 j12) 5) (>= (- j12 j32) 5))
19             (or (>= (- j32 j21) 15) (>= (- j21 j32) 5))))
20
21(define-fun max3 ((i Int) (j Int) (k Int)) Int
22                 (ite (< i j) (ite (< j k) k j) (ite (< i k) k i)))
23
24(push)
25(assert (<= (max3 j12 j22 j32) 25))
26(check-sat)
27(pop)
28
29(push)
30(assert (<= (max3 j12 j22 j32) 24))
31(check-sat)
32(pop)
33
34(push)
35(assert (<= (max3 j12 j22 j32) 23))
36(check-sat)
37(pop)
38
39(push)
40(assert (<= (max3 j12 j22 j32) 23))
41(check-sat)
42(pop)
43
44(push)
45(assert (<= (max3 j12 j22 j32) 21))
46(check-sat)
47(pop)
48
49(push)
50(assert (<= (max3 j12 j22 j32) 20))
51(check-sat)
52(get-model)
53(pop)
54
55(assert (< (max3 j12 j22 j32) 20))
56(check-sat)

The output from cvc5 is as follows.

solutions/Exercise4b.out.smt2

 1sat
 2sat
 3sat
 4sat
 5sat
 6sat
 7(
 8(define-fun j11 () Int 10)
 9(define-fun j12 () Int 20)
10(define-fun j21 () Int 0)
11(define-fun j22 () Int 20)
12(define-fun j31 () Int 5)
13(define-fun j32 () Int 15)
14)
15unsat

Solution to Exercise 5

solutions/Exercise5.smt2

 1(set-logic QF_LRA)
 2(set-option :produce-models true)
 3
 4(declare-const a Real)
 5(declare-const b Real)
 6
 7(assert (= (+ a 10.0) (* 2.0 b)))
 8(assert (= (+ b 20.0) (* 2.0 a)))
 9
10(check-sat)
11(get-model)

The output is as follows.

solutions/Exercise5.out.smt2

1sat
2(
3(define-fun a () Real (/ 50 3))
4(define-fun b () Real (/ 40 3))
5)

Solution to Exercise 6

The changes are pretty simple. In Example 4, we proved that if the elements in indexes i and j are the same, then the arrays are the same. The way to do that was to assert that the elements are the same while the arrays are different, and expect to get an unsat answer, which indeed we got.

Now, we want to show that if the elements are different, then so are the arrays. Hence, we would like to assert that the elements are different while the arrays are the same. Hence, we interchange the equality and dis-equality in the last two assertions.

solutions/Exercise6.smt2

 1(set-logic QF_AX)
 2(declare-sort I 0)
 3(declare-sort E 0)
 4(declare-fun i () I)
 5(declare-fun j () I)
 6(declare-fun tmp () E)
 7(declare-fun a_in () (Array I E))
 8(declare-fun a_out () (Array I E))
 9
10(assert (= tmp (select a_in i)))
11(assert (= a_out (store (store a_in i (select a_in j)) j tmp)))
12(assert (distinct (select a_in i) (select a_in j)))
13(assert (= a_in a_out))
14
15(check-sat)

The output is as follows.

Solution to Exercise 7

solutions/Exercise7.smt2

 1(set-logic QF_BV)
 2(set-option :incremental true)
 3
 4(declare-const x (_ BitVec 32))
 5
 6; orignal abs(x) version
 7(define-fun abs ((x (_ BitVec 32))) (_ BitVec 32) (ite (bvslt x (_ bv0 32)) (bvneg x) x))
 8
 9; encode branchless abs(x) alternatives
10(define-fun xrs () (_ BitVec 32) (bvashr x (_ bv31 32)))
11(define-fun abs1 ((x (_ BitVec 32))) (_ BitVec 32) (bvsub (bvxor x xrs) xrs))
12(define-fun abs2 ((x (_ BitVec 32))) (_ BitVec 32) (bvxor (bvadd x xrs) xrs))
13(define-fun abs3 ((x (_ BitVec 32))) (_ BitVec 32) (bvsub x (bvand (bvshl x (_ bv1 32)) xrs)))
14
15; check if abs() and abs1() are equivalent
16(push 1)
17(assert (distinct (abs x) (abs1 x)))
18(check-sat)
19(pop 1)
20
21; check if abs() and abs2() are equivalent
22(push 1)
23(assert (distinct (abs x) (abs2 x)))
24(check-sat)
25(pop 1)
26
27; check if abs() and abs3() are equivalent
28(push 1)
29(assert (distinct (abs x) (abs3 x)))
30(check-sat)
31(pop 1)

The output is as follows.

solutions/Exercise7.out.smt2

1unsat
2unsat
3unsat

Solution to Exercise 8

solutions/Exercise8.smt2

 1(set-logic QF_DTLIA)
 2(set-option :produce-models true)
 3
 4(declare-datatypes ((Tree 0)) (((node (data Int) (left Tree) (right Tree)) (nil))))
 5
 6(declare-fun x () Tree)
 7
 8(assert ((_ is node) x))
 9(assert (= (left x) x))
10
11(check-sat)

The output is as follows.

Solution to Exercise 9

solutions/Exercise9.smt2

 1(set-logic QF_FP)
 2(set-option :produce-models true)
 3(declare-const a Float32)
 4(declare-const b Float32)
 5(declare-const c Float32)
 6(declare-const rm RoundingMode)
 7(assert (distinct (fp.add rm a (fp.add rm b c)) (fp.add rm (fp.add rm a b) c)))
 8(check-sat)
 9(get-model)
10(get-value ((fp.add rm a (fp.add rm b c))))
11(get-value ((fp.add rm (fp.add rm a b) c)))

The output is as follows.

solutions/Exercise9.out.smt2

1sat
2(
3(define-fun a () (_ FloatingPoint 8 24) (fp #b1 #b11000111 #b00011111111111111111111))
4(define-fun b () (_ FloatingPoint 8 24) (fp #b0 #b11000111 #b00000000000000000000000))
5(define-fun c () (_ FloatingPoint 8 24) (fp #b1 #b10001101 #b11111111000000000000000))
6(define-fun rm () RoundingMode roundTowardNegative)
7)
8(((fp.add rm a (fp.add rm b c)) (fp #b1 #b11000011 #b11111111111111111111000)))
9(((fp.add rm (fp.add rm a b) c) (fp #b1 #b11000011 #b11111111111111111110001)))

Solution to Exercise 10

We can change the solution to Example 12 to enforce that x1 and x2 have length 1. With this change, the problem becomes unsatisfiable. Thus, to obtain the solution, we add one more layer of concatenations, as shown below.

solutions/Exercise10.smt2

 1(set-logic QF_SLIA)
 2(set-option :produce-models true)
 3
 4(declare-const p1 Bool)
 5(declare-const p2 Bool)
 6(declare-const p3 Bool)
 7(declare-const p4 Bool)
 8(declare-const p5 Bool)
 9(declare-const p6 Bool)
10(declare-const p7 Bool)
11(declare-const p8 Bool)
12(declare-const p9 Bool)
13(declare-const p10 Bool)
14(declare-const p11 Bool)
15(declare-const p12 Bool)
16(declare-const p13 Bool)
17(declare-const p14 Bool)
18(declare-const p15 Bool)
19(declare-const p16 Bool)
20(declare-const p17 Bool)
21(declare-const p18 Bool)
22(declare-const p19 Bool)
23(declare-const p20 Bool)
24
25(declare-const x1 String)
26(declare-const x2 String)
27(declare-const x3 String)
28(declare-const x4 String)
29(declare-const x5 String)
30(declare-const x6 String)
31
32(declare-const i1 String)
33(declare-const i2 String)
34(declare-const i3 String)
35(declare-const i4 String)
36(declare-const i5 String)
37(declare-const i6 String)
38(declare-const i7 String)
39(declare-const i8 String)
40
41(define-const result String "abbaabb")
42
43(assert (and (<= (str.len x1) 1) (<= (str.len x2) 1)))
44
45(assert (= i1 (ite p1 x1 x2)))
46(assert (= i2 (ite p2 x1 x2)))
47(assert (= x3 (str.++ i1 i2)))
48
49(assert (= i3 (ite p3 x1 (ite p4 x2 x3))))
50(assert (= i4 (ite p5 x1 (ite p6 x2 x3))))
51(assert (= x4 (str.++ i3 i4)))
52
53(assert (= i5 (ite p7 x1 (ite p8 x2 (ite p9 x3 x4)))))
54(assert (= i6 (ite p10 x1 (ite p11 x2 (ite p12 x3 x4)))))
55(assert (= x5 (str.++ i5 i6)))
56
57(assert (= i7 (ite p13 x1 (ite p14 x2 (ite p15 x3 (ite p16 x4 x5))))))
58(assert (= i8 (ite p17 x1 (ite p18 x2 (ite p19 x3 (ite p20 x4 x5))))))
59(assert (= x6 (str.++ i7 i8)))
60
61(assert (= x6 result))
62
63(check-sat)
64(get-model)

The output is as follows.

solutions/Exercise10.out.smt2

 1sat
 2(
 3(define-fun p1 () Bool true)
 4(define-fun p2 () Bool true)
 5(define-fun p3 () Bool false)
 6(define-fun p4 () Bool true)
 7(define-fun p5 () Bool false)
 8(define-fun p6 () Bool false)
 9(define-fun p7 () Bool false)
10(define-fun p8 () Bool true)
11(define-fun p9 () Bool true)
12(define-fun p10 () Bool false)
13(define-fun p11 () Bool false)
14(define-fun p12 () Bool false)
15(define-fun p13 () Bool false)
16(define-fun p14 () Bool false)
17(define-fun p15 () Bool false)
18(define-fun p16 () Bool true)
19(define-fun p17 () Bool false)
20(define-fun p18 () Bool false)
21(define-fun p19 () Bool false)
22(define-fun p20 () Bool false)
23(define-fun x1 () String "b")
24(define-fun x2 () String "a")
25(define-fun x3 () String "bb")
26(define-fun x4 () String "abb")
27(define-fun x5 () String "aabb")
28(define-fun x6 () String "abbaabb")
29(define-fun i1 () String "b")
30(define-fun i2 () String "b")
31(define-fun i3 () String "a")
32(define-fun i4 () String "bb")
33(define-fun i5 () String "a")
34(define-fun i6 () String "abb")
35(define-fun i7 () String "abb")
36(define-fun i8 () String "aabb")
37)

Thus, the answer is that four concatentations are required.

Solution to Exercise 11

The most natural solution to this exercise seems to be to use the seq.prefixof operator in SMT-LIB (PrefixOf in python). However, this operator only enforces the property of being a prefix, not a proper prefix. Hence, we also need to add for each pair of variables that they are distinct. The result is indeed unsat as expected.

solutions/Exercise11.smt2

 1(set-logic QF_SLIA)
 2
 3(declare-const x (Seq Int))
 4(declare-const y (Seq Int))
 5(declare-const z (Seq Int))
 6
 7(assert (and (seq.prefixof x y) (distinct x y)))
 8(assert (and (seq.prefixof y z) (distinct y z)))
 9(assert (and (seq.prefixof z x) (distinct z x)))
10
11(check-sat)

The output is as follows.

Solution to Exercise 12

solutions/Exercise12.smt2

 1(set-logic QF_FF)
 2(set-option :produce-models true)
 3
 4(define-sort F () (_ FiniteField 13))  ; F is an alias of sort (_ FiniteField 13)
 5
 6(declare-const x F)
 7(declare-const y F)
 8
 9(assert (= (ff.mul x x) y))
10(assert (= (ff.mul y y) (as ff1 F)))
11
12(check-sat)
13(get-model)

The output is as follows.

solutions/Exercise12.out.smt2

1sat
2(
3(define-fun x () (_ FiniteField 13) #f12m13)
4(define-fun y () (_ FiniteField 13) #f1m13)
5)

Solution to Exercise 13

Set difference does not distribute over intersection. The solver is able to find a counterexample for this property.

solutions/Exercise13.smt2

 1(set-logic QF_FS)
 2(set-option :produce-models true)
 3
 4(declare-sort U 0)
 5
 6(declare-const A (Set U))
 7(declare-const B (Set U))
 8(declare-const C (Set U))
 9
10(assert (not (= (set.minus A (set.inter B C)) (set.inter (set.minus A B) (set.minus A C)))))
11
12(check-sat)
13(get-model)

The output is as follows.

solutions/Exercise13.out.smt2

1sat
2(
3; cardinality of U is 1
4; rep: (as @U_0 U)
5(define-fun A () (Set U) (set.singleton (as @U_0 U)))
6(define-fun B () (Set U) (set.singleton (as @U_0 U)))
7(define-fun C () (Set U) (as set.empty (Set U)))
8)