├── .gitignore ├── README.md ├── assignment6 ├── line.png ├── points.png ├── p08_w.txt ├── p08_p.txt ├── linear_regression_ml.py ├── linear_programming.py ├── subset_sum.py ├── queen.py ├── linear_regression.py ├── knapsack.py └── la-theory.py ├── assignment1 ├── main.py └── exp │ ├── __pycache__ │ ├── ast.cpython-38.pyc │ └── lexer.cpython-38.pyc │ ├── test.py │ ├── ast.py │ ├── parse.py │ └── lexer.py ├── assignment8 ├── victim.db ├── pymp.py ├── sql_injection_sym.py ├── mini_py.py ├── concrete.py ├── concolic.py └── symbolic.py ├── assignment7 ├── int_average.jar ├── arrary.py ├── bit_blast.py ├── array_elim.py ├── pointer.py └── bit_vectors.py ├── assignment2 ├── exercise1.v ├── exercise3.v ├── exercise2.v ├── exercise9.v ├── exercise6.v ├── exercise4.v ├── exercise7.v ├── exercise8.v └── exercise5.v ├── assignment5 ├── counter.py ├── equiv.py ├── exercise1.py ├── compiler.py ├── calc.py └── tac.py ├── assignment4 ├── exercise3.v ├── exercise9.v ├── exercise1.v ├── challenge1.v ├── exercise4.v ├── exercise11.v ├── exercise10.v ├── exercise7.v ├── exercise5.v ├── exercise12.v ├── exercise6.v ├── challenge2.v ├── exercise8.v └── exercise2.v └── assignment3 ├── monster.py ├── exercise4.py ├── exercise2.py ├── exercise5.py ├── queen.py ├── exercise1.py └── dpll.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.zip -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langyaMK/USTC_SSE_FMF/HEAD/README.md -------------------------------------------------------------------------------- /assignment6/line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langyaMK/USTC_SSE_FMF/HEAD/assignment6/line.png -------------------------------------------------------------------------------- /assignment1/main.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | if __name__ == '__main__': 4 | unittest.main() 5 | -------------------------------------------------------------------------------- /assignment6/points.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langyaMK/USTC_SSE_FMF/HEAD/assignment6/points.png -------------------------------------------------------------------------------- /assignment8/victim.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langyaMK/USTC_SSE_FMF/HEAD/assignment8/victim.db -------------------------------------------------------------------------------- /assignment7/int_average.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langyaMK/USTC_SSE_FMF/HEAD/assignment7/int_average.jar -------------------------------------------------------------------------------- /assignment2/exercise1.v: -------------------------------------------------------------------------------- 1 | Theorem exercise1: forall P Q:Prop, 2 | P -> (Q -> P). 3 | Proof. 4 | intros. 5 | apply H. 6 | Qed. 7 | -------------------------------------------------------------------------------- /assignment1/exp/__pycache__/ast.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langyaMK/USTC_SSE_FMF/HEAD/assignment1/exp/__pycache__/ast.cpython-38.pyc -------------------------------------------------------------------------------- /assignment1/exp/__pycache__/lexer.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langyaMK/USTC_SSE_FMF/HEAD/assignment1/exp/__pycache__/lexer.cpython-38.pyc -------------------------------------------------------------------------------- /assignment5/counter.py: -------------------------------------------------------------------------------- 1 | def counter(prefix, start=0, step=1): 2 | n = start 3 | while True: 4 | yield f"_{prefix}_{n}" 5 | n += step 6 | -------------------------------------------------------------------------------- /assignment2/exercise3.v: -------------------------------------------------------------------------------- 1 | Theorem exercise3: forall P Q: Prop, 2 | P /\ (P -> Q) -> Q. 3 | Proof. 4 | intros. 5 | inversion H. 6 | apply H1 in H0. 7 | apply H0. 8 | Qed. -------------------------------------------------------------------------------- /assignment2/exercise2.v: -------------------------------------------------------------------------------- 1 | Theorem exercise2: forall P Q H: Prop, 2 | (P->Q) -> (Q->H) -> (P->H). 3 | Proof. 4 | intros. 5 | apply H0 in H2. 6 | apply H1 in H2. 7 | apply H2. 8 | Qed. -------------------------------------------------------------------------------- /assignment2/exercise9.v: -------------------------------------------------------------------------------- 1 | Theorem exercise9: forall P Q R: Prop, 2 | (P -> Q) -> (~Q -> ~P). 3 | Proof. 4 | unfold not. 5 | intros. 6 | apply H0. 7 | apply H. 8 | apply H1. 9 | Qed. -------------------------------------------------------------------------------- /assignment4/exercise3.v: -------------------------------------------------------------------------------- 1 | Variables A: Set. 2 | Variables P Q: A -> Prop. 3 | Theorem exercise3: 4 | (forall x, ~P x /\ Q x) -> (forall x, P x -> Q x). 5 | Proof. 6 | intros H1 a H2. 7 | apply H1. 8 | Qed. -------------------------------------------------------------------------------- /assignment2/exercise6.v: -------------------------------------------------------------------------------- 1 | Theorem exercise6: forall P Q R: Prop, 2 | ((P -> R) /\ (Q -> R)) -> (P /\ Q -> R). 3 | Proof. 4 | intros. 5 | inversion H. 6 | inversion H0. 7 | apply H1 in H3. 8 | apply H3. 9 | Qed. -------------------------------------------------------------------------------- /assignment4/exercise9.v: -------------------------------------------------------------------------------- 1 | Variables A: Set. 2 | Variables P: A -> Prop. 3 | Theorem exercise9: 4 | (exists x, ~P x ) -> ~(forall x, P x). 5 | Proof. 6 | unfold not. 7 | intros. 8 | destruct H as [a p1]. 9 | apply p1. 10 | apply H0. 11 | Qed. -------------------------------------------------------------------------------- /assignment2/exercise4.v: -------------------------------------------------------------------------------- 1 | Theorem exercise4: forall P Q R:Prop, 2 | (P /\ (Q /\ R)) -> ((P /\ Q) /\ R). 3 | Proof. 4 | intros. 5 | inversion H. 6 | inversion H1. 7 | split. 8 | split. 9 | apply H0. 10 | apply H2. 11 | apply H3. 12 | Qed. -------------------------------------------------------------------------------- /assignment2/exercise7.v: -------------------------------------------------------------------------------- 1 | Theorem exercise7: forall P Q R: Prop, 2 | (P -> Q /\ R) -> ((P -> Q) /\ (P -> R)). 3 | Proof. 4 | intros. 5 | split. 6 | intros. 7 | apply H in H0. 8 | apply H0. 9 | intros. 10 | apply H in H0. 11 | apply H0. 12 | Qed. -------------------------------------------------------------------------------- /assignment3/monster.py: -------------------------------------------------------------------------------- 1 | from z3 import * 2 | 3 | N = 100000 4 | 5 | prop = True 6 | 7 | for i in range(N): 8 | prop = And(prop, Bool('b_{}'.format(i))) 9 | 10 | # feed the large proposition to your solver. you can also 11 | # generate other propositions. 12 | print(prop) 13 | 14 | -------------------------------------------------------------------------------- /assignment2/exercise8.v: -------------------------------------------------------------------------------- 1 | Theorem exercise8: forall P Q R: Prop, 2 | (P /\ Q -> R) <-> (P -> Q -> R). 3 | Proof. 4 | split. 5 | intros. 6 | apply H. 7 | split. 8 | apply H0. 9 | apply H1. 10 | intros. 11 | apply H. 12 | apply H0. 13 | apply H0. 14 | Qed. -------------------------------------------------------------------------------- /assignment4/exercise1.v: -------------------------------------------------------------------------------- 1 | Theorem example1: forall P Q:Prop, 2 | ~(P \/ Q) -> ~P /\ ~Q. 3 | Proof. 4 | unfold not. 5 | intros. 6 | split. 7 | intro H1. 8 | apply H. 9 | left. 10 | apply H1. 11 | intro H2. 12 | apply H. 13 | right. 14 | apply H2. 15 | Qed. -------------------------------------------------------------------------------- /assignment6/p08_w.txt: -------------------------------------------------------------------------------- 1 | 382745 2 | 799601 3 | 909247 4 | 729069 5 | 467902 6 | 44328 7 | 34610 8 | 698150 9 | 823460 10 | 903959 11 | 853665 12 | 551830 13 | 610856 14 | 670702 15 | 488960 16 | 951111 17 | 323046 18 | 446298 19 | 931161 20 | 31385 21 | 496951 22 | 264724 23 | 224916 24 | 169684 25 | -------------------------------------------------------------------------------- /assignment4/challenge1.v: -------------------------------------------------------------------------------- 1 | Variables A B: Set. 2 | Variables P: A -> B -> Prop. 3 | Theorem challenge1: 4 | (exists x, exists y, P x y) -> (exists y, exists x, P x y). 5 | Proof. 6 | intros. 7 | destruct H as [a p1]. 8 | destruct p1 as [b p2]. 9 | exists b, a. 10 | apply p2. 11 | Qed. -------------------------------------------------------------------------------- /assignment4/exercise4.v: -------------------------------------------------------------------------------- 1 | Variables A: Set. 2 | Variables P Q: A -> Prop. 3 | Theorem exercise4: 4 | (forall x, P x -> Q x) -> (forall x, ~Q x) -> (forall x, ~P x). 5 | Proof. 6 | unfold not. 7 | intros H1 H2 a H3. 8 | apply H1 in H3. 9 | apply H2 in H3. 10 | apply H3. 11 | Qed. 12 | -------------------------------------------------------------------------------- /assignment4/exercise11.v: -------------------------------------------------------------------------------- 1 | Variables A: Set. 2 | Variables P Q: A -> Prop. 3 | Theorem exercise11: 4 | (forall x, (P x -> Q x)) /\ (exists x, P x) -> (exists x, Q x). 5 | Proof. 6 | intros. 7 | destruct H as [Ha Hb]. 8 | destruct Hb as [a p1]. 9 | apply Ha in p1. 10 | exists a. 11 | apply p1. 12 | Qed. -------------------------------------------------------------------------------- /assignment2/exercise5.v: -------------------------------------------------------------------------------- 1 | Theorem exercise5: forall P Q R: Prop, 2 | (P \/ (Q \/ R)) -> ((P \/ Q) \/ R). 3 | Proof. 4 | intros. 5 | inversion H. 6 | left. 7 | left. 8 | apply H0. 9 | inversion H0. 10 | left. 11 | right. 12 | apply H1. 13 | right. 14 | apply H1. 15 | Qed. -------------------------------------------------------------------------------- /assignment4/exercise10.v: -------------------------------------------------------------------------------- 1 | Variables A: Set. 2 | Variables P Q: A -> Prop. 3 | Theorem exercise10: 4 | (forall x, (P x -> ~Q x)) -> ~(exists x, (P x /\ Q x)). 5 | Proof. 6 | unfold not. 7 | intros. 8 | destruct H0 as [a p1]. 9 | destruct p1 as [p2 p3]. 10 | apply H in p2. 11 | apply p2. 12 | apply p3. 13 | Qed. -------------------------------------------------------------------------------- /assignment6/p08_p.txt: -------------------------------------------------------------------------------- 1 | 825594 2 | 1677009 3 | 1676628 4 | 1523970 5 | 943972 6 | 97426 7 | 69666 8 | 1296457 9 | 1679693 10 | 1902996 11 | 1844992 12 | 1049289 13 | 1252836 14 | 1319836 15 | 953277 16 | 2067538 17 | 675367 18 | 853655 19 | 1826027 20 | 65731 21 | 901489 22 | 577243 23 | 466257 24 | 369261 25 | -------------------------------------------------------------------------------- /assignment4/exercise7.v: -------------------------------------------------------------------------------- 1 | Variables A: Set. 2 | Variables P Q: A -> Prop. 3 | Theorem exercise7: 4 | (exists x, (~P x /\ ~Q x)) -> (exists x,(~(P x /\ Q x))). 5 | Proof. 6 | unfold not. 7 | intros. 8 | destruct H as [a p]. 9 | exists a. 10 | intros. 11 | destruct H as [H1 H2]. 12 | destruct p as [p1 p2]. 13 | apply p1. 14 | apply H1. 15 | Qed. -------------------------------------------------------------------------------- /assignment4/exercise5.v: -------------------------------------------------------------------------------- 1 | Variables A: Set. 2 | Variables P Q: A -> Prop. 3 | Theorem exercise5: 4 | (forall x, (P x /\ Q x)) <-> ((forall x, P x) /\ (forall x, Q x)). 5 | Proof. 6 | split. 7 | intros. 8 | split. 9 | apply H. 10 | apply H. 11 | intros H a. 12 | inversion H. 13 | split. 14 | apply H0. 15 | apply H1. 16 | Qed. -------------------------------------------------------------------------------- /assignment6/linear_regression_ml.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from sklearn.linear_model import LinearRegression 3 | 4 | 5 | def sklearn_lr(xs, ys): 6 | reg = LinearRegression().fit(np.array(list(zip(xs, [0] * len(xs)))), ys) 7 | return reg.coef_[0], reg.intercept_ 8 | 9 | 10 | if __name__ == '__main__': 11 | sklearn_lr([1.0, 2.0, 3.0, 4.0], [1.0, 3.0, 5.0, 7.0]) 12 | -------------------------------------------------------------------------------- /assignment4/exercise12.v: -------------------------------------------------------------------------------- 1 | Variables A: Set. 2 | Variables P Q R: A -> Prop. 3 | Theorem exercise12: 4 | (exists x, (P x /\ Q x)) /\ (forall x, (P x -> R x)) -> (exists x, (R x /\ Q x)). 5 | Proof. 6 | intros. 7 | destruct H as [Ha Hb]. 8 | destruct Ha as [a p1]. 9 | destruct p1 as [p2 p3]. 10 | apply Hb in p2. 11 | exists a. 12 | split. 13 | apply p2. 14 | apply p3. 15 | Qed. -------------------------------------------------------------------------------- /assignment4/exercise6.v: -------------------------------------------------------------------------------- 1 | Variables A: Set. 2 | Variables P Q: A -> Prop. 3 | Theorem exercise6: 4 | (exists x, (~P x \/ Q x)) -> (exists x,(~(P x /\ ~Q x))). 5 | Proof. 6 | unfold not. 7 | intros. 8 | destruct H as [a p]. 9 | exists a. 10 | intros. 11 | destruct H as [H1 H2]. 12 | destruct p as [p1 | p2]. 13 | apply p1. 14 | apply H1. 15 | apply H2. 16 | apply p2. 17 | Qed. -------------------------------------------------------------------------------- /assignment4/challenge2.v: -------------------------------------------------------------------------------- 1 | Variables A: Set. 2 | Variables P: A -> Prop. 3 | Variables b: A. 4 | Theorem Challenge2: 5 | (P b) /\ (forall (x:A) (y:A), (P x /\ P y -> x = y)) -> (forall (x:A), (P x <-> x = b)). 6 | Proof. 7 | intros. 8 | split. 9 | inversion H. 10 | intros. 11 | apply H1. 12 | split. 13 | apply H2. 14 | apply H0. 15 | intros. 16 | inversion H. 17 | rewrite H0. 18 | apply H1. 19 | Qed. -------------------------------------------------------------------------------- /assignment4/exercise8.v: -------------------------------------------------------------------------------- 1 | Variables A: Set. 2 | Variables P Q: A -> Prop. 3 | Theorem exercise8: 4 | (exists x, P x \/ Q x) <-> (exists x, P x) \/(exists x, Q x). 5 | Proof. 6 | split. 7 | intros. 8 | destruct H as [a p1]. 9 | destruct p1 as [p2|p3]. 10 | left. 11 | exists a. 12 | apply p2. 13 | right. 14 | exists a. 15 | apply p3. 16 | intros. 17 | destruct H as [H1 | H2]. 18 | destruct H1 as [a p1]. 19 | exists a. 20 | left. 21 | apply p1. 22 | destruct H2 as [a p2]. 23 | exists a. 24 | right. 25 | apply p2. 26 | Qed. -------------------------------------------------------------------------------- /assignment4/exercise2.v: -------------------------------------------------------------------------------- 1 | Theorem example3 : forall P Q R: Prop, 2 | P /\ (Q \/ R) <-> (P /\ Q) \/ (P /\ R). 3 | Proof. 4 | split. 5 | intros. 6 | destruct H as [Ha Hb]. 7 | destruct Hb as [Hc|Hd]. 8 | left. 9 | split. 10 | apply Ha. 11 | apply Hc. 12 | right. 13 | split. 14 | apply Ha. 15 | apply Hd. 16 | intros. 17 | destruct H as [He | Hf]. 18 | destruct He as [Hg Hh]. 19 | split. 20 | apply Hg. 21 | left. 22 | apply Hh. 23 | destruct Hf as[Hi Hj]. 24 | split. 25 | apply Hi. 26 | right. 27 | apply Hj. 28 | Qed. -------------------------------------------------------------------------------- /assignment1/exp/test.py: -------------------------------------------------------------------------------- 1 | from exp.ast import * 2 | from exp.lexer import * 3 | from exp.parse import * 4 | import unittest 5 | 6 | test_case_1 = ExpAdd( 7 | ExpMulti(ExpVar(3), ExpVar(4)), 8 | ExpDiv(ExpVar(10), ExpVar(2)) 9 | ) 10 | 11 | test_case_2 = ExpMinus( 12 | ExpMulti( 13 | ExpPar(ExpAdd(ExpVar(12), ExpVar(217))), 14 | ExpVar(3) 15 | ), 16 | ExpVar(621) 17 | ) 18 | 19 | 20 | class TestTableau(unittest.TestCase): 21 | 22 | def test_print_1(self): 23 | self.assertEqual(str(test_case_1), "3 * 4 + 10 / 2") 24 | 25 | def test_print_2(self): 26 | self.assertEqual(str(test_case_2), "(12 + 217) * 3 - 621") 27 | 28 | def test_eval_1(self): 29 | self.assertEqual(eval_value(test_case_1), 17) 30 | 31 | def test_eval_2(self): 32 | self.assertEqual(eval_value(test_case_2), 66) 33 | 34 | def test_eval_3(self): 35 | self.assertEqual(eval_value(parse("25* 2-3*6")), 32) 36 | 37 | def test_eval_4(self): 38 | self.assertEqual(eval_value(parse("(12 + 217) * 3 - 621")), 66) 39 | -------------------------------------------------------------------------------- /assignment5/equiv.py: -------------------------------------------------------------------------------- 1 | from z3 import * 2 | 3 | 4 | class Todo(Exception): 5 | def __init__(self, msg): 6 | self.msg = msg 7 | 8 | def __str__(self): 9 | return self.msg 10 | 11 | def __repr__(self): 12 | return self.__str__() 13 | 14 | 15 | # program equivalence: 16 | # in the class, we present two implementation of the same algorithms, one 17 | # is: 18 | ''' 19 | int power3(int in){ 20 | int i, out_a; 21 | out_a = in; 22 | for(i = 0; i < 2; i++) 23 | out_a = out_a * in; 24 | return out_a; 25 | } 26 | ''' 27 | # and the other one is: 28 | ''' 29 | int power3_new(int in){ 30 | int out_b; 31 | out_b = (in*in)*in; 32 | return out_b; 33 | } 34 | 35 | ''' 36 | # and with EUF, we can prove that these two algorithms are equivalent, 37 | # that is: 38 | # P1/\P2 -> out_a==out_b 39 | 40 | # Please construct, manually, the propositions P1 and P2, and let Z3 41 | # prove the above implication. (Note that you don't need to generate 42 | # P1 or P2 automatically, just write down them manually.) 43 | 44 | # raise Todo("Exercise 2: try to prove the algorithm 'power3' and 'power3_new' " 45 | # "are equivalent by using EUF theory") 46 | S = DeclareSort('S') 47 | 48 | _in, out_a_0, out_a_1, out_a_2, out_b = Consts('_in out_a_0 out_a_1 out_a_2 out_b', S) 49 | 50 | f = Function('f', S, S, S) 51 | 52 | P1 = And(out_a_0 == _in, out_a_1 == f(out_a_0, _in), out_a_2 == f(out_a_1, _in)) 53 | P2 = And(out_b == f(f(_in, _in), _in)) 54 | 55 | F = Implies(And(P1, P2), out_a_2 == out_b) 56 | 57 | solve(Not(F)) 58 | -------------------------------------------------------------------------------- /assignment8/pymp.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import multiprocessing as mp 4 | from collections import namedtuple 5 | from datetime import datetime 6 | 7 | 8 | class Todo(Exception): 9 | def __init__(self, msg): 10 | self.msg = msg 11 | 12 | def __str__(self): 13 | return self.msg 14 | 15 | def __repr__(self): 16 | return self.__str__() 17 | 18 | 19 | Timer = namedtuple('Timer', ('pid', 'time')) 20 | 21 | 22 | # exercise 4: Run the code below, which will start two timers which record the time 23 | # at different time intervals. It use multiprocessing.Queue which allows multiple producers 24 | # to gather the results. You do not need to write any code in this exercise. 25 | 26 | def timer(interval, result_queue): 27 | # a timer sleep interval seconds and record the datetime into result queue for 5 times 28 | for i in range(5): 29 | time.sleep(interval) 30 | result_queue.put(Timer(os.getpid(), str(datetime.now())), block=False) 31 | 32 | 33 | if __name__ == '__main__': 34 | # Start two timers which record time with different 35 | # intervals, use Queue to gather the result. 36 | 37 | # a process shared queue 38 | p = mp.Queue(maxsize=10) 39 | 40 | # start sub-process timer with 1 second interval 41 | p1 = mp.Process(target=timer, args=(1, p)) 42 | 43 | # start sub-process timer with 2 second interval 44 | p2 = mp.Process(target=timer, args=(2, p)) 45 | 46 | # start the two timer 47 | p1.start() 48 | p2.start() 49 | 50 | # wait for the two timer finish 51 | p1.join() 52 | p2.join() 53 | 54 | # get the recorder from result queue 55 | while not p.empty(): 56 | print(p.get()) 57 | -------------------------------------------------------------------------------- /assignment1/exp/ast.py: -------------------------------------------------------------------------------- 1 | class Todo(Exception): 2 | def __init__(self, msg): 3 | self.msg = msg 4 | 5 | def __str__(self): 6 | return self.msg 7 | 8 | def __repr__(self): 9 | return self.__str__() 10 | 11 | 12 | class Exp: 13 | 14 | def __repr__(self): 15 | return self.__str__() 16 | 17 | 18 | class ExpVar(Exp): 19 | def __init__(self, value): 20 | self.value = value 21 | 22 | def __str__(self): 23 | return str(self.value) 24 | 25 | 26 | class ExpAdd(Exp): 27 | def __init__(self, left, right): 28 | self.left = left 29 | self.right = right 30 | 31 | def __str__(self): 32 | return f"{self.left} + {self.right}" 33 | 34 | 35 | class ExpMinus(Exp): 36 | def __init__(self, left, right): 37 | self.left = left 38 | self.right = right 39 | 40 | def __str__(self): 41 | return f"{self.left} - {self.right}" 42 | 43 | 44 | class ExpMulti(Exp): 45 | def __init__(self, left, right): 46 | self.left = left 47 | self.right = right 48 | 49 | def __str__(self): 50 | return f"{self.left} * {self.right}" 51 | 52 | 53 | class ExpDiv(Exp): 54 | def __init__(self, left, right): 55 | self.left = left 56 | self.right = right 57 | 58 | def __str__(self): 59 | return f"{self.left} / {self.right}" 60 | 61 | 62 | class ExpPar(Exp): 63 | def __init__(self, exp): 64 | self.exp = exp 65 | 66 | def __str__(self): 67 | return f"({self.exp})" 68 | 69 | 70 | def eval_value(exp: Exp): 71 | if isinstance(exp, ExpVar): 72 | return exp.value 73 | if isinstance(exp, ExpAdd): 74 | return eval_value(exp.left) + eval_value(exp.right) 75 | if isinstance(exp, ExpMinus): 76 | return eval_value(exp.left) - eval_value(exp.right) 77 | if isinstance(exp, ExpDiv): 78 | return eval_value(exp.left) / eval_value(exp.right) 79 | if isinstance(exp, ExpMulti): 80 | return eval_value(exp.left) * eval_value(exp.right) 81 | if isinstance(exp, ExpPar): 82 | return eval_value(exp.exp) 83 | -------------------------------------------------------------------------------- /assignment1/exp/parse.py: -------------------------------------------------------------------------------- 1 | from exp.lexer import * 2 | from exp.ast import * 3 | 4 | isp = {"#": 0, "(": 1, ")": 6, "+": 3, "-": 3, "*": 5, "/": 5} # 定义栈内优先级 5 | icp = {"#": 0, "(": 6, ")": 1, "+": 2, "-": 2, "*": 4, "/": 4} # 定义栈外优先级 6 | 7 | 8 | # 中序表达式转后序表达式 9 | def expressToPostfix(words): 10 | postfix, stack = [], [] 11 | stack.append(Word("#", 0, Sign.IllSign)) 12 | for word in words: 13 | if word.sign == Sign.Number: 14 | postfix.append(word) 15 | elif isp[stack[-1].char] < icp[word.char]: 16 | stack.append(word) 17 | else: 18 | while isp[stack[-1].char] > icp[word.char]: 19 | postfix.append(stack.pop()) 20 | if word.char == ")": 21 | stack.pop() 22 | else: 23 | stack.append(word) 24 | stack.reverse() 25 | stack.pop() 26 | postfix.extend([i for i in stack]) 27 | return postfix 28 | 29 | 30 | # 通过后序表达式转语法树 31 | def postfixBuildTree(postfix): 32 | treeList = [] 33 | for word in postfix: 34 | if word.sign == Sign.Number: 35 | treeList.append(ExpVar(int(word.number))) 36 | elif word.char == '+': 37 | newTreeNode = ExpAdd(treeList[-2], treeList[-1]) 38 | treeList = treeList[:len(treeList) - 2] 39 | treeList.append(newTreeNode) 40 | elif word.char == '-': 41 | newTreeNode = ExpMinus(treeList[-2], treeList[-1]) 42 | treeList = treeList[:len(treeList) - 2] 43 | treeList.append(newTreeNode) 44 | elif word.char == '*': 45 | newTreeNode = ExpMulti(treeList[-2], treeList[-1]) 46 | treeList = treeList[:len(treeList) - 2] 47 | treeList.append(newTreeNode) 48 | elif word.char == '/': 49 | newTreeNode = ExpDiv(treeList[-2], treeList[-1]) 50 | treeList = treeList[:len(treeList) - 2] 51 | treeList.append(newTreeNode) 52 | return treeList[0] 53 | 54 | 55 | def parse(s): 56 | words = lexer(s) 57 | postfix = expressToPostfix(words) 58 | return postfixBuildTree(postfix) 59 | -------------------------------------------------------------------------------- /assignment5/exercise1.py: -------------------------------------------------------------------------------- 1 | from z3 import * 2 | 3 | # Before we presenting the EUF theory, we first introduce some Z3's 4 | # facilities to define terms and functions. 5 | 6 | ######################################## 7 | # Sort, term, formula 8 | 9 | # In Z3, we can define sorts, we can simply think them as sets. 10 | # This code declares a new sort (although abstract) called S.: 11 | S = DeclareSort('S') 12 | 13 | # The sort can contain constants: 14 | e = Const('e', S) 15 | 16 | # Z3 has some builtin sort: 17 | # Bool, Int, Real, BitVec n, Array, Index, Elem, String, Seq S. 18 | # which can be used directly. 19 | 20 | # We can define functions: 21 | f = Function('f', S, S) 22 | g = Function('g', S, S, S) 23 | # this is a unary function from sort S to sort S. 24 | # we can solve the congruence rule: 25 | # e is a constant from S. 26 | 27 | # And e has some intrinsic property, for example, the reflexivity. 28 | # Hence, the following proposition is unsat: 29 | solve(e != e) 30 | 31 | # the following propositions have solution: 32 | solve(e == f(e)) 33 | solve(e == f(g(e, e))) 34 | 35 | # the example from the lecture: 36 | x1, x2, x3, x4, x5 = Consts('x1 x2 x3 x4 x5', S) 37 | solve(x1 == x2, x2 == x3, x4 == x5, x5 != x1, f(x1) != f(x3)) 38 | # with the result "no solution". 39 | 40 | # If we change the inequality to equality: 41 | solve(x1 == x2, x2 == x3, x4 == x5, x5 != x1, f(x1) == f(x3)) 42 | # this will give the following solution: 43 | ''' 44 | [x3 = S!val!1, 45 | x5 = S!val!0, 46 | x4 = S!val!0, 47 | x1 = S!val!1, 48 | x2 = S!val!1] 49 | ''' 50 | # note that S!val!0, S!val!1 are distinct values. 51 | 52 | # Let's study the translation validation we presented in the lecture: 53 | x1, x2, y1, y2, t1, t2, z = Consts('x1 x2 y1 y2 t1 t2 z', S) 54 | f = Function('f', S, S, S) 55 | g = Function('g', S, S, S) 56 | F1 = And(t1 == f(x1, y1), t2 == f(x2, y2), z == g(t1, t2)) 57 | F2 = And(z == g(f(x1, y1), f(x2, y2))) 58 | F = Implies(F1, F2) 59 | solve(Not(F)) 60 | solve(F) 61 | 62 | # Let's try another example 63 | # This code: 64 | f = Function('f', S, S) 65 | x1, x2, x3, x4, x5 = Consts('x1 x2 x3 x4 x5', S) 66 | solve(x1 == x2, x2 == x3, x4 == x5, x5 != x1, f(x1) != f(x3)) 67 | # what's Z3's output? And why that output? -------------------------------------------------------------------------------- /assignment6/linear_programming.py: -------------------------------------------------------------------------------- 1 | from z3 import * 2 | 3 | 4 | class Todo(Exception): 5 | def __init__(self, msg): 6 | self.msg = msg 7 | 8 | def __str__(self): 9 | return self.msg 10 | 11 | def __repr__(self): 12 | return self.__str__() 13 | 14 | 15 | 16 | # A linear programming problem (shorted as LP in the following) 17 | # consists of two parts: the constraints and the target function. And our goal is 18 | # to get the minimal or the maximal values of under the constraint, 19 | # both the constraints and the target function are of linear forms. 20 | def lp_examples(): 21 | # decal variable in Int or Real type 22 | x, y = Ints("x y") 23 | 24 | # not Solver as we do in LA, 25 | # to solve LP with Z3, we'll be using the Optimizer module 26 | opt_max = Optimize() 27 | 28 | # add the constraints is same with LA 29 | cons = [x < 2, (y - x) < 1] 30 | opt_max.add(cons) 31 | 32 | # use maximize() and minimize() method to add the target function 33 | opt_max.maximize(x + 2 * y) 34 | 35 | # check the Optimizer as we do with Solver 36 | if opt_max.check() == sat: 37 | print(opt_max.model()) 38 | 39 | # LP of real number is also supported by Z3 40 | x, y = Reals("x y") 41 | 42 | opt_min = Optimize() 43 | cons = [x <= 4.3, (y - x) <= 1.1, y >= 2.3] 44 | opt_min.add(cons) 45 | 46 | # minimize() will get minimal value of the target function 47 | opt_min.minimize(x + y) 48 | 49 | if opt_min.check() == sat: 50 | print(opt_min.model()) 51 | 52 | 53 | def lp_exercise(): 54 | opt = Optimize() 55 | # exercise 14: Given the constraints: 56 | # x - y >= 2.1 57 | # x + z <= 5.5 58 | # y - z <= 1.1 59 | # 60 | # try to calculate the maximal value of 61 | # x + y + z 62 | # by using LP in Z3 63 | # Your code here: 64 | # raise Todo("exercise 14: please fill in the missing code.") 65 | x, y, z = Reals("x y z") 66 | cons = [(x - y) > 2.1, (x + z) < 5.5, (y - z) < 1.1] 67 | opt.add(cons) 68 | 69 | # use maximize() and minimize() method to add the target function 70 | opt.maximize(x + y + z) 71 | 72 | # check the Optimizer as we do with Solver 73 | if opt.check() == sat: 74 | print(opt.model()) 75 | 76 | 77 | if __name__ == '__main__': 78 | lp_examples() 79 | 80 | # should output: [z = 23/20, y = 9/4, x = 87/20] 81 | lp_exercise() 82 | -------------------------------------------------------------------------------- /assignment3/exercise4.py: -------------------------------------------------------------------------------- 1 | """Applications of SAT via Z3 2 | 3 | In the previous part we've discussed how to obtain solutions and prove 4 | the validity for propositions. 5 | In this part, we will try to use Z3 to solve some practical problems. 6 | Hints: 7 | You can reuse the `sat_all` function that you've implemented in exercise 1 8 | if you think necessary.""" 9 | 10 | from z3 import * 11 | 12 | 13 | class Todo(Exception): 14 | def __init__(self, msg): 15 | self.msg = msg 16 | 17 | def __str__(self): 18 | return self.msg 19 | 20 | def __repr__(self): 21 | return self.__str__() 22 | 23 | 24 | # TODO: Exercise 4 25 | # Circuit Layout 26 | # Usually When EE-Engineers design a circuit layout, they will verify it to 27 | # make sure that the layout will not only output a single electrical level 28 | # since it's useless. 29 | # Now let's investigate the Circuit Layout we provide you. 30 | # According to the requirement, what we should do is to convert the circuit layout 31 | # into a proposition expression, let's say 'F', and try to obtains the solutions 32 | # for F and Not(F). 33 | # And then make sure that both F and Not(F) can be satisfied. 34 | # First we convert it into proposition 35 | 36 | def sat_all(props, f): 37 | """Get all solutions of given proposition set props that satisfy f 38 | 39 | Arguments: 40 | props {BoolRef} -- Proposition list 41 | f {Boolref} -- logical express that consist of props 42 | """ 43 | solver = Solver() 44 | solver.add(f) 45 | result = [] 46 | while solver.check() == sat: 47 | m = solver.model() 48 | result.append(m) 49 | block = [] 50 | for prop in props: 51 | prop_is_true = m.eval(prop, model_completion=True) 52 | 53 | if prop_is_true: 54 | new_prop = prop 55 | else: 56 | new_prop = Not(prop) 57 | 58 | block.append(new_prop) 59 | 60 | # raise Todo("Exercise 1-6: try to complete the lost part in the function of `set_all`") 61 | # print(block) 62 | solver.add(Not(And(block))) 63 | 64 | print("the given proposition: ", f) 65 | print("the number of solutions: ", len(result)) 66 | 67 | def print_model(m): 68 | print(sorted([(d, m[d]) for d in m], key=lambda x: str(x[0]))) 69 | 70 | for m in result: 71 | print_model(m) 72 | 73 | 74 | def circuit_layout(): 75 | a, b, c, d = Bools('a b c d') 76 | # raise Todo("Exercise 4: try to convert the circuit layout into logical propositions and find solutions") 77 | F_circuit = Or(And(And(a, b), d), And(And(a, b), Not(c))) 78 | sat_all([a, b, c, d], F_circuit) 79 | sat_all([a, b, c, d], Not(F_circuit)) 80 | 81 | 82 | if __name__ == '__main__': 83 | # circuit_layout should have 3 solutions for F and 13 solutions for Not(F) 84 | circuit_layout() 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /assignment3/exercise2.py: -------------------------------------------------------------------------------- 1 | from z3 import * 2 | 3 | 4 | class Todo(Exception): 5 | def __init__(self, msg): 6 | self.msg = msg 7 | print(self.msg) 8 | 9 | def __str__(self): 10 | return self.msg 11 | 12 | def __repr__(self): 13 | return self.__str__() 14 | 15 | # In Exercise 1, we've learned how to use z3 to obtain the solutions that 16 | # satisfy a given proposition. 17 | # We must point out that the validity of Z3 is capable to prove 18 | # lies into the boundary of classical logic: 19 | 20 | 21 | # Besides "And", "Or" and "Not" constructors (functions), there 22 | # is some also the "Implies" and "Xor" constructors, which 23 | # can be used to construct propositions. For instance: 24 | # P->P 25 | P = Bool('P') 26 | F = Implies(P, P) 27 | 28 | # Now think of what we do in previous exercise about using Coq. If we 29 | # use Coq to prove P -> P, we will use tactic apply P. 30 | # Then P -> P is proved. 31 | # Now we use a different way, to prove it via z3. 32 | # This time we don't use z3 to obtain the solution for P->P, we just 33 | # try to find solution for its opposite, aka. Not(F): 34 | solve(Not(F)) 35 | 36 | # Z3 output the following: 37 | # "no solution" 38 | # which indicates that the proposition F is valid. 39 | 40 | # Then the following 41 | # double negation law: 42 | # ~~P -> P 43 | F = Implies(Not(Not(P)), P) 44 | solve(Not(F)) 45 | 46 | # TODO: Exercise 2-1 47 | # Now it's your turn, try to prove the exclusive middle law if also valid: 48 | # P \/ ~P 49 | Todo("Exercise 2-1: try to prove the exclusive middle law if also valid") 50 | P = Bool('P') 51 | F = Or(P, Not(P)) 52 | solve(Not(F)) 53 | 54 | # TODO: Exercise 2-2 55 | # Prove the validity of the Pierce's law: 56 | # ((P->Q)->P)->P) 57 | Todo("Exercise 2-2: try to prove the validity of the Pierce's law") 58 | P, Q = Bools('P Q') 59 | F = Implies(Implies(Implies(P, Q), P), P) 60 | solve(Not(F)) 61 | 62 | 63 | # Note that the Pierce's law only holds in classical logic, but 64 | # not in constructive logic, for 65 | # interested readers, please refer to the background reading: 66 | # https://en.wikipedia.org/wiki/Peirce%27s_law 67 | 68 | 69 | # TODO: Exercise 2-3 70 | # In previous exercise about use Coq, we ever give you an challenge 71 | # (P -> Q) -> (~Q -> ~P). 72 | # Now try to prove it's valid via z3 73 | Todo("Exercise 2-3: try to prove the validity of the proposition: (P -> Q) -> (~Q -> ~P)") 74 | P, Q = Bools('P Q') 75 | F = Implies(Implies(P, Q), Implies(Not(Q), Not(P))) 76 | solve(Not(F)) 77 | 78 | # TODO: Exercise 2-4 79 | # Once more, try to prove that validity of : 80 | # (P -> Q /\ R) -> ((P -> Q) /\ (P -> R)) 81 | # Be carefully when you process the priority of operations cause 82 | # there is no intros. which can process it automatically for you 83 | # to use. 84 | Todo("Exercise 2-4: try to prove the validity of the proposition: (P -> Q /\\ R) -> ((P -> Q) /\\ (P -> R))") 85 | P, Q, R = Bools('P Q R') 86 | F = Implies(Implies(P, And(Q, R)), And(Implies(P, Q), Implies(P, R))) 87 | solve(Not(F)) 88 | 89 | # Well done, you complete Exercise 2, remember to save your code for handing in. 90 | -------------------------------------------------------------------------------- /assignment7/arrary.py: -------------------------------------------------------------------------------- 1 | from z3 import * 2 | 3 | 4 | class Todo(Exception): 5 | def __init__(self, msg): 6 | self.msg = msg 7 | 8 | def __str__(self): 9 | return self.msg 10 | 11 | def __repr__(self): 12 | return self.__str__() 13 | 14 | 15 | def array_test(): 16 | # array is an array from integer to integer 17 | array = Array('A', IntSort(), IntSort()) 18 | x = Int('x') 19 | 20 | print(array[x]) 21 | print(Select(array, x)) 22 | print(Store(array, x, 10)) 23 | print(simplify(Select(Store(array, 2, x+1), 2))) 24 | 25 | # exercise 13: read and run above code, 26 | # try to get familiarize yourself with the basic usage of Array in Z3py: 27 | # you do not need to write any code here. 28 | 29 | 30 | ########################################################## 31 | # exercise 14: This function returns the above formulae: 32 | # Store(A, i, x)[i]>=x 33 | # Now you should fill in the definition of the above formulae: 34 | def array_proof(): 35 | array = Array('a', IntSort(), IntSort()) 36 | value = Int('x') 37 | index = Int('i') 38 | 39 | # your code here 40 | # raise Todo("exercise 14: please fill in the missing code.") 41 | print(simplify(Select(Store(array, index, value), index) >= value)) 42 | 43 | 44 | ########################################################## 45 | # exercise 15: Try to convert the array formulae 46 | # Store(A, i*i - i*i, x)[0] >= x 47 | # into a Z3 constraint and prove it. 48 | # Whether it's "unknown" or not? 49 | # What's the reason? 50 | # 不是未知,因为z3可以处理简单的非线性函数 51 | def array_non_linear_proof(): 52 | array = Array('a', IntSort(), IntSort()) 53 | value = Int('x') 54 | index = Int('i') 55 | 56 | # your code here 57 | # raise Todo("exercise 15: please fill in the missing code.") 58 | print(simplify(Select(Store(array, index*index - index*index, value), 0) >= value)) 59 | 60 | 61 | ########################################################## 62 | # To implement an array interface using lambda (function). 63 | 64 | # In the class, we discussed that the array interfaces can be 65 | # implemented by reducing these interfaces to functions. 66 | # To understand this, you'll implement a small array module 67 | # by using anonymous function---lambdas, in Python. 68 | 69 | # we define 3 APIs on an array: 70 | # array_new(): create a new array 71 | # array_select(): array reading 72 | # array_store(): array writing 73 | def lambda_array(): 74 | def array_new(): 75 | return lambda x: 0 76 | 77 | # array[index] 78 | def array_select(array, index): 79 | return array(index) 80 | 81 | # array[index] = element 82 | def array_store(array, index, element): 83 | # exercise 16: write code to store an "element" into the 84 | # "index" position of "array" by using lambda expression. 85 | # raise Todo("exercise 16: please fill in the missing code.") 86 | return lambda x: element if x == index else array(x) 87 | 88 | # a small test case 89 | arr = array_new() 90 | # print(arr(17)) 91 | print(array_select(array_store(array_store(arr, 5, 88), 92 | 7, 99), 93 | 5)) 94 | assert (array_select(array_store(array_store(arr, 5, 88), 95 | 7, 99), 96 | 5) == 88) 97 | assert (array_select(array_store(array_store(arr, 5, 88), 98 | 7, 99), 99 | 17) == 0) 100 | print("\033[32mSUCCESS!\033[0m") 101 | 102 | 103 | if __name__ == '__main__': 104 | array_test() 105 | array_proof() 106 | array_non_linear_proof() 107 | lambda_array() 108 | 109 | -------------------------------------------------------------------------------- /assignment3/exercise5.py: -------------------------------------------------------------------------------- 1 | """Applications of SAT via Z3 2 | 3 | In the previous part we've discussed how to obtain solutions and prove 4 | the validity for propositions. 5 | In this part, we will try to use Z3 to solve some practical problems. 6 | Hints: 7 | You can reuse the `sat_all` function that you've implemented in exercise 1 8 | if you think necessary.""" 9 | 10 | from z3 import * 11 | 12 | 13 | class Todo(Exception): 14 | def __init__(self, msg): 15 | self.msg = msg 16 | 17 | def __str__(self): 18 | return self.msg 19 | 20 | def __repr__(self): 21 | return self.__str__() 22 | 23 | 24 | # TODO: Exercise 5 25 | # Seat Arrangement Problem 26 | # Alice, Bob, Carol take 3 seats. But they have some requirements: 27 | # 1. Alice can not sit near to Carol; 28 | # 2. Bob can not sit right to Alice. 29 | # Questions: 30 | # 1. Is there any solution? 31 | # 2. How many solutions in total? 32 | 33 | # Now let us investigate the problem 34 | 35 | 36 | def seat_arrangement(): 37 | solver = Solver() 38 | # 1. First we need to modeling the problem 39 | # Let say: 40 | # A_i means Alice takes seat Ai, 41 | # B_i means Bob takes seat Bi, 42 | # C_i means Carol takes seat Ci. 43 | # And since there are only 3 seats, so 1 <= i <= 3 44 | 45 | n_seat = 3 46 | a1, a2, a3 = Bools('a1 a2 a3') 47 | b1, b2, b3 = Bools('b1 b2 b3') 48 | c1, c2, c3 = Bools('c1 c2 c3') 49 | 50 | # alice must take a seat: 51 | alice_take_seat_1 = And(a1, Not(a2), Not(a3), Not(b1), Not(c1)) 52 | alice_take_seat_2 = And(a2, Not(a1), Not(a3), Not(b2), Not(c2)) 53 | alice_take_seat_3 = And(a3, Not(a1), Not(a2), Not(b3), Not(c3)) 54 | solver.add(Or(alice_take_seat_1, alice_take_seat_2, alice_take_seat_3)) 55 | 56 | # bob must take a seat: 57 | # raise Todo("Exercise 5-1: try to add constraints that indicate Bob must take a seat") 58 | bob_take_seat_1 = And(b1, Not(b2), Not(b3), Not(a1), Not(c1)) 59 | bob_take_seat_2 = And(b2, Not(b1), Not(b3), Not(a2), Not(c2)) 60 | bob_take_seat_3 = And(b3, Not(b1), Not(b2), Not(a3), Not(c3)) 61 | solver.add(Or(bob_take_seat_1, bob_take_seat_2, bob_take_seat_3)) 62 | 63 | # carol must take a seat: 64 | # raise Todo("Exercise 5-2: try to add constraints that indicate Carol must take a seat") 65 | carol_take_seat_1 = And(c1, Not(c2), Not(c3), Not(a1), Not(b1)) 66 | carol_take_seat_2 = And(c2, Not(c1), Not(c3), Not(a2), Not(b2)) 67 | carol_take_seat_3 = And(c3, Not(c1), Not(c2), Not(a3), Not(b3)) 68 | solver.add(Or(carol_take_seat_1, carol_take_seat_2, carol_take_seat_3)) 69 | 70 | # alice can not sit near to carol: 71 | alice_not_near_carol = And(Implies(a1, Not(c2)), Implies(a2, Not(Or(c1, c3))), Implies(a3, Not(c2))) 72 | solver.add(alice_not_near_carol) 73 | 74 | # 3. Bob can not sit right to Alice 75 | # raise Todo("Exercise 5-3: try to add constraints that indicate Bob can not sit right to Alice") 76 | bob_not_right_alice = And(Implies(b1, Not(a2)), Implies(b2, Not(c3))) 77 | solver.add(bob_not_right_alice) 78 | 79 | # Hint: here only one solution is printed, you may change this to 80 | # print all the solutions to check your implementation. 81 | solver.check() 82 | model = solver.model() 83 | # print(model) 84 | # fancy printing 85 | if model.evaluate(a1): 86 | print("Alice ", end='') 87 | if model.evaluate(b1): 88 | print("Bob ", end='') 89 | if model.evaluate(c1): 90 | print("Carol ", end='') 91 | if model.evaluate(a2): 92 | print("Alice ", end='') 93 | if model.evaluate(b2): 94 | print("Bob ", end='') 95 | if model.evaluate(c2): 96 | print("Carol ", end='') 97 | if model.evaluate(a3): 98 | print("Alice", end='') 99 | if model.evaluate(b3): 100 | print("Bob", end='') 101 | if model.evaluate(c3): 102 | print("Carol", end='') 103 | 104 | 105 | if __name__ == '__main__': 106 | # seat_arrangement should have 1 solution 107 | seat_arrangement() 108 | -------------------------------------------------------------------------------- /assignment1/exp/lexer.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | # 有限状态自动机实现词法分析 3 | 4 | # 状态 5 | class State(Enum): 6 | IniState = 0 7 | LeftBracketState = 1 8 | RightBracketState = 2 9 | NumState = 3 10 | NumSpaceState = 4 11 | BinSignState = 5 12 | 13 | 14 | # 符号分类 15 | class Sign(Enum): 16 | Number = 0 17 | BinSign = 1 # 二元运算符 18 | Space = 2 19 | LeftBracket = 3 20 | RightBracket = 4 21 | IllSign = 5 22 | 23 | 24 | class Word: 25 | def __init__(self, char, number, sign): 26 | self.char = char 27 | self.number = number 28 | self.sign = sign 29 | 30 | def __str__(self): 31 | if self.sign == Sign.Number: 32 | return str(self.number) 33 | else: 34 | return str(self.char) 35 | 36 | 37 | def JudgeSign(char): 38 | if char == ' ': 39 | return Sign.Space 40 | elif char == '+' or char == '-' or char == '/' or char == '*': 41 | return Sign.BinSign 42 | elif '0' <= char <= '9': 43 | return Sign.Number 44 | elif char == '(': 45 | return Sign.LeftBracket 46 | elif char == ')': 47 | return Sign.RightBracket 48 | else: 49 | return Sign.IllSign 50 | 51 | 52 | # 状态是否为终结状态 53 | def JudgeEndState(state): 54 | if state == State.NumState or state == State.NumSpaceState or state == State.RightBracketState: 55 | return True 56 | return False 57 | 58 | 59 | # 更新数字字符数组 60 | def numTempUpdate(numTemp, words): 61 | if len(numTemp) > 0: 62 | s = "" 63 | for i in numTemp: 64 | s += i 65 | words.append(Word(0, s, Sign.Number)) 66 | return words, True 67 | return words, False 68 | 69 | 70 | def lexer(s): 71 | transfer = { 72 | State.IniState: { 73 | Sign.Space: State.IniState, 74 | Sign.LeftBracket: State.LeftBracketState, 75 | Sign.Number: State.NumState, 76 | }, 77 | State.LeftBracketState: { 78 | Sign.Space: State.LeftBracketState, 79 | Sign.LeftBracket: State.LeftBracketState, 80 | Sign.Number: State.NumState, 81 | }, 82 | State.NumState: { 83 | Sign.Number: State.NumState, 84 | Sign.Space: State.NumSpaceState, 85 | Sign.BinSign: State.BinSignState, 86 | Sign.RightBracket: State.RightBracketState, 87 | }, 88 | State.NumSpaceState: { 89 | Sign.Space: State.NumSpaceState, 90 | Sign.BinSign: State.BinSignState, 91 | Sign.RightBracket: State.RightBracketState, 92 | }, 93 | State.RightBracketState: { 94 | Sign.Space: State.RightBracketState, 95 | Sign.RightBracket: State.RightBracketState, 96 | Sign.BinSign: State.BinSignState, 97 | }, 98 | State.BinSignState: { 99 | Sign.Space: State.BinSignState, 100 | Sign.LeftBracket: State.LeftBracketState, 101 | Sign.Number: State.NumState, 102 | }, 103 | } 104 | state = State.IniState 105 | leftBracketSize = 0 # 当前未匹配的左括号数 106 | numTemp = [] 107 | words = [] 108 | for c in s: 109 | sign = JudgeSign(c) 110 | if sign == Sign.IllSign: 111 | raise print("算术表达式含有非法符号!") 112 | elif sign == Sign.LeftBracket: 113 | leftBracketSize += 1 114 | elif sign == Sign.RightBracket: 115 | leftBracketSize -= 1 116 | 117 | if leftBracketSize < 0: 118 | raise print("算术表达式括号匹配错误!") 119 | if sign != Sign.Number: 120 | words, flag = numTempUpdate(numTemp, words) 121 | if flag: 122 | numTemp = [] 123 | if sign != Sign.Space: 124 | words.append(Word(c, 0, sign)) 125 | else: 126 | numTemp.append(c) 127 | flag = sign in transfer[state] 128 | if not flag: 129 | raise print("算术表达式逻辑错误!") 130 | # 状态转移 131 | state = transfer[state][sign] 132 | if leftBracketSize != 0: 133 | raise print("算术表达式括号匹配错误!") 134 | 135 | if JudgeEndState(state): 136 | words, _ = numTempUpdate(numTemp, words) 137 | return words 138 | else: 139 | raise print("算术表达式逻辑错误!") 140 | -------------------------------------------------------------------------------- /assignment8/sql_injection_sym.py: -------------------------------------------------------------------------------- 1 | import os 2 | import z3 3 | from sqlalchemy import * 4 | 5 | 6 | class Todo(Exception): 7 | def __init__(self, msg): 8 | self.msg = msg 9 | 10 | def __str__(self): 11 | return self.msg 12 | 13 | def __repr__(self): 14 | return self.__str__() 15 | 16 | 17 | ############################################################ 18 | # This is the symbolic execution facilities. 19 | # E ::= s | x | Concat(E, E) 20 | class Exp: 21 | pass 22 | 23 | 24 | class ExpStrConst(Exp): 25 | def __init__(self, s): 26 | self.s = s 27 | 28 | def __str__(self): 29 | return "Const(" + self.s + ")" 30 | 31 | 32 | class ExpStrVar(Exp): 33 | def __init__(self, x): 34 | self.x = x 35 | 36 | def __str__(self): 37 | return "Var(" + self.x + ")" 38 | 39 | 40 | class ExpStrConcat(Exp): 41 | def __init__(self, left, right): 42 | self.left = left 43 | self.right = right 44 | 45 | def __str__(self): 46 | return "Concat(" + (self.left.__str__()) + ", " + (self.right.__str__()) + ")" 47 | 48 | 49 | def exp2z3(e): 50 | if isinstance(e, ExpStrConst): 51 | return z3.StringVal(e.s) 52 | if isinstance(e, ExpStrVar): 53 | return z3.String(e.x) 54 | if isinstance(e, ExpStrConcat): 55 | return z3.Concat(exp2z3(e.left), exp2z3(e.right)) 56 | 57 | 58 | ############################################################ 59 | # This is the symbolic execution facilities. 60 | class SymStr(str): 61 | def __new__(cls, s, sym: Exp): 62 | return str.__new__(cls, s) 63 | 64 | def __init__(self, s, sym: Exp): 65 | super().__init__() 66 | self.sym = sym 67 | 68 | def __add__(self, other): 69 | return SymStr(self.__str__() + other.__str__(), ExpStrConcat(self.sym, other.sym)) 70 | 71 | 72 | def make_sym_str(s): 73 | return SymStr(s, ExpStrVar(s)) 74 | 75 | 76 | ############################################################ 77 | # The Database-related code. 78 | db = None 79 | db_exists = False 80 | 81 | 82 | def db_create_and_init(): 83 | global db, db_exists 84 | db_file = 'victim.db' 85 | if os.path.exists('./%s' % db_file): 86 | db_exists = True 87 | db = create_engine('sqlite:///%s' % db_file, echo=False) 88 | # create a new table 89 | if db_exists: 90 | return 91 | db.execute(""" 92 | create table users( 93 | user_name char(50), 94 | user_age int(32), 95 | user_gender char(10) 96 | )""") 97 | # insert some data 98 | db.execute(""" 99 | insert into users (user_name, user_age, user_gender) values('Bob', 30, 'M') 100 | """) 101 | db.execute(""" 102 | insert into users (user_name, user_age, user_gender) values('Alice', 20, 'F') 103 | """) 104 | db.execute(""" 105 | insert into users (user_name, user_age, user_gender) values('Carol', 40, 'F') 106 | """) 107 | 108 | 109 | # exercise 12: add a possible payload to drop the table "users". 110 | payloads = [] 111 | 112 | 113 | def check_injection(sym): 114 | z3exp = exp2z3(sym) 115 | # print(z3exp) 116 | solver = z3.Solver() 117 | cons = [] 118 | for s in payloads: 119 | cons.append(z3exp == z3.StringVal(s)) 120 | solver.add(z3.Or(cons)) 121 | res = solver.check() 122 | if res == z3.sat: 123 | print("Found a potential SQL injection vulnerability, you may trigger it with:") 124 | print(solver.model()) 125 | else: 126 | print(res) 127 | 128 | 129 | def execute_wrapper(query): 130 | s = query.__str__() 131 | sym = query.sym 132 | check_injection(sym) 133 | result_proxy = db.execute(s) 134 | return result_proxy 135 | 136 | 137 | def db_select(name: SymStr): 138 | global db 139 | s1 = 'select * from users where user_name = \'' 140 | query_str = SymStr(s1, ExpStrConst(s1)) + name + SymStr('\'', ExpStrConst('\'')) 141 | result_proxy = execute_wrapper(query_str) 142 | res = result_proxy.fetchall() 143 | if len(res) == 0: 144 | print("\033[31mNo this user: %s\033[0m" % name) 145 | result_proxy.close() 146 | return 147 | print("\033[32mFound the user: %s\033[0m" % name) 148 | print(res) 149 | result_proxy.close() 150 | 151 | 152 | if __name__ == '__main__': 153 | db_create_and_init() 154 | while True: 155 | print("\nPlease input a user name: ", sep='', end='') 156 | name = input() 157 | sym = make_sym_str(name) 158 | print("Your searched the user: ", name) 159 | db_select(sym) 160 | -------------------------------------------------------------------------------- /assignment6/subset_sum.py: -------------------------------------------------------------------------------- 1 | """ The subset problem 2 | 3 | The subset problem is a well-known satisfiability problem: given 4 | a multiset (a multiset is like a normal set, expect for the 5 | elements can be duplicated) S, whether or not there is a 6 | non-empty subset T of S, such that: 7 | \sum T = 0 8 | 9 | For example, given this set 10 | {-7, -3, -2, 9000, 5, 8} 11 | the answer is yes because the subset 12 | {-3, -2, 5} 13 | sums to zero. 14 | 15 | This problem is NPC, and for more background information of the 16 | subset problem, please refer to: 17 | https://en.wikipedia.org/wiki/Subset_sum_problem 18 | 19 | """ 20 | 21 | from z3 import * 22 | import time 23 | 24 | 25 | class Todo(Exception): 26 | def __init__(self, msg): 27 | self.msg = msg 28 | 29 | def __str__(self): 30 | return self.msg 31 | 32 | def __repr__(self): 33 | return self.__str__() 34 | 35 | 36 | 37 | # LA-based solution 38 | def subset_sum_la(target_set: list): 39 | solver = Solver() 40 | flags = [Int(f"x_{i}") for i in range(len(target_set))] 41 | 42 | # 0-1 ILA 43 | for flag in flags: 44 | solver.add(Or(flag == 0, flag == 1)) 45 | 46 | # the selected set must be non-empty 47 | solver.add(sum(flags) != 0) 48 | 49 | # @exercise 9: please fill in the missing code to add 50 | # the following constraint into the solver. 51 | # \sum_i flags[i]*target_set[i] = 0 52 | # raise Todo("exercise 9: please fill in the missing code.") 53 | i = 0 54 | con = [] 55 | for t in target_set: 56 | con.append(t * flags[i]) 57 | i = i + 1 58 | solver.add(sum(con) == 0) 59 | 60 | start = time.time() 61 | result = solver.check() 62 | print(f"time used in LA: {(time.time() - start):.6f}s") 63 | if result == sat: 64 | return True, [target_set[index] for index, flag in enumerate(flags) if solver.model()[flag] == 1] 65 | return False, result 66 | 67 | 68 | # LA-based optimized solution 69 | def subset_sum_la_opt(target_set: list): 70 | solver = Solver() 71 | 72 | # enable Pseudo-Boolean solver 73 | # to get more information about Pseudo-Boolean constraints 74 | # refer to https://theory.stanford.edu/~nikolaj/programmingz3.html 75 | solver.set("sat.pb.solver", "solver") 76 | 77 | # use Pseudo-Boolean constraints for each flag 78 | flags = [Bool(f"x_{i}") for i in range(len(target_set))] 79 | #solver.add(AtLeast(flags + [1])) 80 | # the selected set must be non-empty 81 | solver.add(PbGe([(flags[i], 1) for i in range(len(target_set))], 1)) 82 | 83 | # selected set must sum to zero 84 | solver.add(PbEq([(flags[i], target_set[i]) for i in range(len(target_set))], 0)) 85 | 86 | start = time.time() 87 | result = solver.check() 88 | print(f"time used in LA optimized: {(time.time() - start):.6f}s") 89 | 90 | if result == sat: 91 | return True, [target_set[index] for index, flag in enumerate(flags) if solver.model()[flag]] 92 | return False, result 93 | 94 | 95 | # dynamic programming-based (DP) solution (don't confuse DP with LP): 96 | def subset_sum_dp(target_set): 97 | def subset_sum_dp_do(the_set, target, index) -> Bool: 98 | if index == 0: 99 | return False 100 | if target == the_set[index - 1]: 101 | return True 102 | if subset_sum_dp_do(the_set, target, index - 1): 103 | return True 104 | return subset_sum_dp_do(the_set, target - the_set[index - 1], index - 1) 105 | 106 | start = time.time() 107 | result = subset_sum_dp_do(target_set, 0, len(target_set)) 108 | print(f"time used in DP: {(time.time() - start):.6f}s") 109 | return result 110 | 111 | 112 | def gen_large_test(n): 113 | nums = [10000] * n 114 | nums[len(nums) - 2] = 1 115 | nums[len(nums) - 1] = -1 116 | # print(nums) 117 | return nums 118 | 119 | 120 | if __name__ == '__main__': 121 | # a small test case 122 | small_set = [-7, -3, -2, 9000, 5, 8] 123 | subset_sum_dp(small_set) 124 | print(subset_sum_la(small_set)) 125 | print(subset_sum_la_opt(small_set)) 126 | 127 | # a large test case 128 | max_nums = 20 129 | large_set = gen_large_test(max_nums) 130 | 131 | # @exercise 10: compare the efficiency of the DP and the 132 | # LP algorithm, by changing the value of "max_nums" to other 133 | # values, say, 200, 2000, 20000, 200000, ... 134 | # what's your observation? What conclusion you can draw from these data? 135 | # raise Todo("exercise 10: please fill in the missing code.") 136 | print(subset_sum_la(large_set)) 137 | print(subset_sum_la_opt(large_set)) 138 | print(subset_sum_dp(large_set)) 139 | -------------------------------------------------------------------------------- /assignment5/compiler.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from z3 import * 4 | 5 | import calc 6 | import tac 7 | from counter import counter 8 | 9 | 10 | class Todo(Exception): 11 | def __init__(self, msg): 12 | self.msg = msg 13 | 14 | def __str__(self): 15 | return self.msg 16 | 17 | def __repr__(self): 18 | return self.__str__() 19 | 20 | 21 | ############################################### 22 | # a compiler from Calc to Tac. 23 | 24 | 25 | def compile_func(func: calc.Function) -> tac.Function: 26 | tac_stmts = [] 27 | fresh_var = counter(f"tmp_{func.name}") 28 | 29 | def compile_expr(expr): 30 | if isinstance(expr, calc.ExprVar): 31 | return expr.var 32 | # raise Todo("Exercise 9: compile Calc expressions to Tac statements") 33 | if isinstance(expr, calc.ExprBop): 34 | new_var_1 = compile_expr(expr.left) 35 | new_var_2 = compile_expr(expr.right) 36 | new_var = next(fresh_var) 37 | if expr.bop is calc.BOp.ADD: 38 | tac_stmts.append(tac.StmtAssignAdd(new_var, new_var_1, new_var_2)) 39 | return new_var 40 | elif expr.bop is calc.BOp.SUB: 41 | tac_stmts.append(tac.StmtAssignSub(new_var, new_var_1, new_var_2)) 42 | return new_var 43 | elif expr.bop is calc.BOp.MUL: 44 | tac_stmts.append(tac.StmtAssignMul(new_var, new_var_1, new_var_2)) 45 | return new_var 46 | elif expr.bop is calc.BOp.DIV: 47 | tac_stmts.append(tac.StmtAssignDiv(new_var, new_var_1, new_var_2)) 48 | return new_var 49 | else: 50 | raise ValueError("unknown binary operator") 51 | 52 | def compile_stmt(stmt): 53 | if isinstance(stmt, calc.StmtAssign): 54 | tac_stmts.append(tac.StmtAssignVar(stmt.var, compile_expr(stmt.expr))) 55 | 56 | for calc_stmt in func.stmts: 57 | compile_stmt(calc_stmt) 58 | 59 | return tac.Function(func.name, func.args, tac_stmts, func.ret) 60 | 61 | 62 | def translation_validation(original_func: calc.Function, result_func: tac.Function): 63 | # TODO: for the compiler to be correct, you should prove this condition: 64 | # TODO: orig_cons /\ result_cons -> x1==x2 65 | # TODO: your code here: 66 | original_func_ssa = calc.to_ssa_func(original_func) 67 | result_func_ssa = tac.to_ssa_func(result_func) 68 | 69 | original_cons = calc.gen_cons_func(original_func_ssa) 70 | result_cons = tac.gen_cons_func(result_func_ssa) 71 | 72 | solver = Solver() 73 | 74 | # raise Todo("Exercise 9: do the translation validation by proving this condition: orig_cons /\ result_cons -> x1==x2") 75 | # note that the z3.And() can accept list of constraints 76 | P1 = And(original_cons) 77 | P2 = And(result_cons) 78 | F = Implies(And(P1, P2), original_cons[-1] == result_cons[-1]) 79 | solver.add(Not(F)) 80 | 81 | return solver 82 | 83 | 84 | ############################################### 85 | # Tests 86 | 87 | 88 | class TestTV(unittest.TestCase): 89 | 90 | tac_func = compile_func(calc.sample_f) 91 | 92 | def test_compile(self): 93 | res = ("f(s1,s2,t1,t2){\n\t_tmp_f_0 = s1 + t1;\n\t_tmp_f_1 = s2 + t2;\n\t" 94 | "_tmp_f_2 = _tmp_f_0 * _tmp_f_1;\n\tz = _tmp_f_2;\n\t_tmp_f_3 = z * s1;\n\t" 95 | "z = _tmp_f_3;\n\treturn z;\n}\n") 96 | 97 | # f(s1, s2, t1, t2){ 98 | # _tac_f_0 = s1 + t1; 99 | # _tac_f_1 = s2 + t2; 100 | # _tac_f_2 = _tac_f_0 * _tac_f_1; 101 | # _tac_f_3 = _tac_f_2; 102 | # _tac_f_4 = _tac_f_3 * s1; 103 | # _tac_f_5 = _tac_f_4; 104 | # return _tac_f_5; 105 | # } 106 | print(tac.to_ssa_func(self.tac_func)) 107 | self.assertEqual(str(self.tac_func), res) 108 | 109 | def test_tv(self): 110 | solver = translation_validation(calc.sample_f, self.tac_func) 111 | 112 | # [Not(Implies(And(_calc_f_0 == 113 | # f_mul(f_add(s1, t1), f_add(s2, t2)), 114 | # _calc_f_1 == f_mul(_calc_f_0, s1), 115 | # _tac_f_0 == f_add(s1, t1), 116 | # _tac_f_1 == f_add(s2, t2), 117 | # _tac_f_2 == f_mul(_tac_f_0, _tac_f_1), 118 | # _tac_f_3 == _tac_f_2, 119 | # _tac_f_4 == f_mul(_tac_f_3, s1), 120 | # _tac_f_5 == _tac_f_4), 121 | # _calc_f_1 == _tac_f_5))] 122 | print(solver) 123 | self.assertEqual(str(solver.check()), "unsat") 124 | 125 | 126 | if __name__ == '__main__': 127 | unittest.main() 128 | -------------------------------------------------------------------------------- /assignment3/queen.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from z3 import * 4 | 5 | 6 | class Todo(Exception): 7 | def __init__(self, msg): 8 | self.msg = msg 9 | 10 | def __str__(self): 11 | return self.msg 12 | 13 | def __repr__(self): 14 | return self.__str__() 15 | 16 | 17 | def four_queen(): 18 | solver = Solver() 19 | # the basic data structure: 20 | N = 100 21 | board = [[Bool('b_{}_{}'.format(i, j)) for i in range(N)] 22 | for j in range(N)] 23 | # print(board[0][1]) 24 | # constraint 1: each row has just one queen: 25 | rows = [] 26 | for i in range(N): 27 | current_row = [] 28 | for j in range(N): 29 | current_row_list = [board[i][j]] 30 | for k in range(N): 31 | if k != j: 32 | current_row_list.append(Not(board[i][k])) 33 | current_row.append(And(current_row_list)) 34 | rows.append(Or(current_row)) 35 | # print(And(rows)) 36 | solver.add(And(rows)) 37 | 38 | # constraint 2: each column has just one queen: 39 | # raise Todo("Challenge: add constraints which describe each column has just one queen") 40 | columns = [] 41 | for i in range(N): 42 | current_column = [] 43 | for j in range(N): 44 | current_column_list = [board[j][i]] 45 | for k in range(N): 46 | if k != j: 47 | current_column_list.append(Not(board[k][i])) 48 | current_column.append(And(current_column_list)) 49 | columns.append(Or(current_column)) 50 | # print(And(columns)) 51 | solver.add(And(columns)) 52 | 53 | # constraint 3: each diagonal has at most one queen: 54 | # raise Todo("Challenge: add constraints which describe each diagonal has at most one queen") 55 | diagonals = [] 56 | for d in range(1-N, N): 57 | current_diagonal = [] 58 | for i in range(N): 59 | j = i - d 60 | # print(i, j, d) 61 | if 0 <= j < N: 62 | current_diagonal_list = [board[i][j]] 63 | for k in range(N): 64 | if k != i: 65 | j = k - d 66 | # print(i, j, d, k) 67 | if 0 <= j < N: 68 | current_diagonal_list.append(Not(board[k][j])) 69 | current_diagonal.append(And(current_diagonal_list)) 70 | 71 | current_diagonal_list = [] 72 | for i in range(N): 73 | j = i - d 74 | # print(i, j, d) 75 | if 0 <= j < N: 76 | current_diagonal_list.append(Not(board[i][j])) 77 | current_diagonal.append(And(current_diagonal_list)) 78 | 79 | diagonals.append(Or(current_diagonal)) 80 | # print(And(diagonals)) 81 | solver.add(And(diagonals)) 82 | 83 | # constraint 4: each anti-diagonal has at most one queen: 84 | # raise Todo("Challenge: add constraints which describe each anti-diagonal has at most one queen") 85 | anti_diagonals = [] 86 | for d in range(0, 2*N-1): 87 | current_anti_diagonal = [] 88 | for i in range(N): 89 | j = d - i 90 | # print(i, j, d) 91 | if 0 <= j < N: 92 | current_anti_diagonal_list = [board[i][j]] 93 | for k in range(N): 94 | if k != i: 95 | j = d - k 96 | # print(i, j, d, k) 97 | if 0 <= j < N: 98 | current_anti_diagonal_list.append(Not(board[k][j])) 99 | current_anti_diagonal.append(And(current_anti_diagonal_list)) 100 | 101 | current_anti_diagonal_list = [] 102 | for i in range(N): 103 | j = d - i 104 | # print(i, j, d) 105 | if 0 <= j < N: 106 | current_anti_diagonal_list.append(Not(board[i][j])) 107 | current_anti_diagonal.append(And(current_anti_diagonal_list)) 108 | 109 | anti_diagonals.append(Or(current_anti_diagonal)) 110 | # print(And(anti_diagonals)) 111 | solver.add(And(anti_diagonals)) 112 | 113 | solution_count = 0 114 | 115 | start = time.time() 116 | while solver.check() == sat: 117 | solution_count += 1 118 | model = solver.model() 119 | 120 | # print the solution 121 | # print([(row_index, col_index) for row_index, row in enumerate(board) 122 | # for col_index, flag in enumerate(row) if model[flag]]) 123 | 124 | # generate constraints from solution 125 | solution_cons = [flag for row in board for flag in row if model[flag]] 126 | 127 | # add solution to the solver to get new solution 128 | solver.add(Not(And(solution_cons))) 129 | 130 | print(f"n_queen_la solve {N}-queens by {(time.time() - start):.6f}s") 131 | return solution_count 132 | 133 | 134 | if __name__ == '__main__': 135 | # Four Queen should have 2 set of solutions 136 | print(four_queen()) 137 | -------------------------------------------------------------------------------- /assignment5/calc.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from typing import List 3 | 4 | from z3 import * 5 | 6 | from counter import counter 7 | 8 | 9 | class Todo(Exception): 10 | def __init__(self, msg): 11 | self.msg = msg 12 | 13 | def __str__(self): 14 | return self.msg 15 | 16 | def __repr__(self): 17 | return self.__str__() 18 | 19 | 20 | ################################## 21 | # The abstract syntax for the Calc language: 22 | ''' 23 | bop ::= + | - | * | / 24 | E ::= x | E bop E 25 | S ::= x=E 26 | F ::= f(x1, …, xn){S;* return E;} 27 | ''' 28 | 29 | 30 | # binary operator 31 | class BOp(Enum): 32 | ADD = "+" 33 | SUB = "-" 34 | MUL = "*" 35 | DIV = "/" 36 | 37 | 38 | # expression 39 | class Expr: 40 | def __repr__(self): 41 | return self.__str__() 42 | 43 | 44 | class ExprVar(Expr): 45 | def __init__(self, var: str): 46 | self.var = var 47 | 48 | def __str__(self): 49 | return f"{self.var}" 50 | 51 | 52 | class ExprBop(Expr): 53 | def __init__(self, left: Expr, right: Expr, bop: BOp): 54 | self.left = left 55 | self.right = right 56 | self.bop = bop 57 | 58 | def __str__(self): 59 | if isinstance(self.left, ExprBop): 60 | left_str = f"({self.left})" 61 | else: 62 | left_str = f"{self.left}" 63 | 64 | if isinstance(self.right, ExprBop): 65 | right_str = f"({self.right})" 66 | else: 67 | right_str = f"{self.right}" 68 | 69 | return f"{left_str} {self.bop.value} {right_str}" 70 | 71 | 72 | # statement 73 | class Stmt: 74 | def __init__(self): 75 | self.level = 0 76 | 77 | def __repr__(self): 78 | return self.__str__() 79 | 80 | 81 | class StmtAssign(Stmt): 82 | def __init__(self, var: str, expr: Expr): 83 | super().__init__() 84 | self.var = var 85 | self.expr = expr 86 | 87 | def __str__(self): 88 | indent_space = self.level * "\t" 89 | return f"{indent_space}{self.var} = {self.expr};\n" 90 | 91 | 92 | # function: 93 | class Function: 94 | def __init__(self, name: str, args: List[str], stmts: List[Stmt], ret: str): 95 | self.name = name 96 | self.args = args 97 | self.stmts = stmts 98 | self.ret = ret 99 | 100 | def __str__(self): 101 | arg_str = ",".join(self.args) 102 | for stm in self.stmts: 103 | stm.level += 1 104 | 105 | stmts_str = "".join([str(stmt) for stmt in self.stmts]) 106 | 107 | return (f"{self.name}({arg_str}){{\n" 108 | f"{stmts_str}" 109 | f"\treturn {self.ret};\n" 110 | f"}}\n") 111 | 112 | 113 | ############################################### 114 | # SSA conversion: 115 | 116 | # take a function 'func', convert it to SSA 117 | def to_ssa_func(func: Function) -> Function: 118 | # a map from variable to new variable: 119 | # init it by putting every argument into the map 120 | var_map = {arg: arg for arg in func.args} 121 | 122 | # fresh variable generator 123 | fresh_var = counter(prefix=f"calc_{func.name}") 124 | 125 | def to_ssa_expr(expr): 126 | if isinstance(expr, ExprVar): 127 | return ExprVar(var_map[expr.var]) 128 | 129 | if isinstance(expr, ExprBop): 130 | return ExprBop(to_ssa_expr(expr.left), to_ssa_expr(expr.right), expr.bop) 131 | 132 | def to_ssa_stmt(stmt): 133 | if isinstance(stmt, StmtAssign): 134 | new_expr = to_ssa_expr(stmt.expr) 135 | new_var = next(fresh_var) 136 | var_map[stmt.var] = new_var 137 | return StmtAssign(new_var, new_expr) 138 | 139 | # to convert each statement one by one: 140 | new_stmts = [to_ssa_stmt(stmt) for stmt in func.stmts] 141 | 142 | return Function(func.name, func.args, new_stmts, var_map[func.ret]) 143 | 144 | 145 | ############################################### 146 | # Generate Z3 constraints: 147 | def gen_cons_exp(expr: Expr): 148 | if isinstance(expr, ExprVar): 149 | return Const(expr.var, DeclareSort('S')) 150 | 151 | if isinstance(expr, ExprBop): 152 | if expr.bop is BOp.ADD: 153 | func_name = "f_add" 154 | elif expr.bop is BOp.SUB: 155 | func_name = "f_sub" 156 | elif expr.bop is BOp.MUL: 157 | func_name = "f_mul" 158 | elif expr.bop is BOp.DIV: 159 | func_name = "f_div" 160 | else: 161 | raise ValueError("unknown binary operator") 162 | 163 | left = gen_cons_exp(expr.left) 164 | right = gen_cons_exp(expr.right) 165 | 166 | return z3.Function(func_name, 167 | DeclareSort('S'), 168 | DeclareSort('S'), 169 | DeclareSort('S')).__call__(left, right) 170 | 171 | 172 | def gen_cons_stm(stmt): 173 | if isinstance(stmt, StmtAssign): 174 | return Const(stmt.var, DeclareSort('S')) == gen_cons_exp(stmt.expr) 175 | 176 | 177 | def gen_cons_func(func): 178 | return [gen_cons_stm(stmt) for stmt in func.stmts] 179 | 180 | 181 | # a sample program: 182 | sample_f = Function('f', 183 | ['s1', 's2', 't1', 't2'], 184 | [StmtAssign('z', ExprBop(ExprBop(ExprVar('s1'), ExprVar('t1'), BOp.ADD), 185 | ExprBop(ExprVar('s2'), ExprVar('t2'), BOp.ADD), 186 | BOp.MUL)), 187 | StmtAssign('z', ExprBop(ExprVar('z'), ExprVar('s1'), BOp.MUL))], 188 | 'z') 189 | 190 | if __name__ == '__main__': 191 | # print the original program 192 | print(sample_f) 193 | # convert it to SSA 194 | new_f = to_ssa_func(sample_f) 195 | # print the converted program 196 | print(new_f) 197 | # generate Z3 constraints 198 | print(gen_cons_func(new_f)) 199 | -------------------------------------------------------------------------------- /assignment8/mini_py.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from typing import List 3 | 4 | 5 | class Todo(Exception): 6 | def __init__(self, msg): 7 | self.msg = msg 8 | 9 | def __str__(self): 10 | return self.msg 11 | 12 | def __repr__(self): 13 | return self.__str__() 14 | 15 | 16 | ######################################## 17 | # This bunch of code declare the syntax for the language MiniPy: 18 | ''' 19 | B ::= + | - | * | / | == | != | > | < | >= | <= 20 | E ::= n | x | E B E 21 | S ::= pass 22 | | x = E 23 | | seq(S, S) 24 | | f(E1, ..., En) 25 | | if(E, S, S) 26 | | while(E, S) 27 | F ::= f((x1, ..., xn), S, E) 28 | ''' 29 | 30 | 31 | ################################## 32 | # bops 33 | class Bop(Enum): 34 | ADD = "+" 35 | MIN = "-" 36 | MUL = "*" 37 | DIV = "/" 38 | EQ = "==" 39 | NE = "!=" 40 | GT = ">" 41 | GE = ">=" 42 | LT = "<" 43 | LE = "<=" 44 | 45 | 46 | ########################################## 47 | # expressions 48 | class Expr: 49 | pass 50 | 51 | 52 | class ExprNum(Expr): 53 | def __init__(self, n: int): 54 | self.num = n 55 | 56 | def __str__(self): 57 | return f"{self.num}" 58 | 59 | 60 | class ExprVar(Expr): 61 | def __init__(self, var: str): 62 | self.var = var 63 | 64 | def __str__(self): 65 | return f"{self.var}" 66 | 67 | 68 | class ExprBop(Expr): 69 | def __init__(self, left: Expr, right: Expr, bop: Bop): 70 | self.left = left 71 | self.right = right 72 | self.bop = bop 73 | 74 | def __str__(self): 75 | if isinstance(self.left, ExprBop): 76 | left_str = f"({self.left})" 77 | else: 78 | left_str = f"{self.left}" 79 | 80 | if isinstance(self.right, ExprBop): 81 | right_str = f"({self.right})" 82 | else: 83 | right_str = f"{self.right}" 84 | 85 | return f"{left_str} {self.bop.value} {right_str}" 86 | 87 | 88 | ############################################### 89 | # statement 90 | class Stmt: 91 | def __init__(self): 92 | self.level = 0 93 | 94 | def __repr__(self): 95 | return str(self) 96 | 97 | 98 | class StmtAssign(Stmt): 99 | def __init__(self, var: str, expr: Expr): 100 | super().__init__() 101 | self.var = var 102 | self.expr = expr 103 | 104 | def __str__(self): 105 | indent_space = self.level * "\t" 106 | return f"{indent_space}{self.var} = {self.expr}\n" 107 | 108 | 109 | class StmtIf(Stmt): 110 | def __init__(self, expr: Expr, then_stmts: List[Stmt], else_stmts: List[Stmt]): 111 | super().__init__() 112 | self.expr = expr 113 | self.then_stmts = then_stmts 114 | self.else_stmts = else_stmts 115 | 116 | def __str__(self): 117 | indent_space = self.level * "\t" 118 | 119 | for stm in self.then_stmts: 120 | stm.level = self.level + 1 121 | 122 | for stm in self.else_stmts: 123 | stm.level = self.level + 1 124 | 125 | then_stmts_str = "".join([str(stmt) for stmt in self.then_stmts]) 126 | else_stmts_str = "".join([str(stmt) for stmt in self.else_stmts]) 127 | 128 | then_str = (f"{indent_space}if {self.expr} :\n" 129 | f"{then_stmts_str}") 130 | 131 | if self.else_stmts: 132 | return (f"{then_str}" 133 | f"{indent_space}else:\n" 134 | f"{else_stmts_str}") 135 | else: 136 | return then_str 137 | 138 | 139 | class StmtWhile(Stmt): 140 | def __init__(self, expr: Expr, stmts: List[Stmt]): 141 | super().__init__() 142 | self.expr = expr 143 | self.stmts = stmts 144 | 145 | def __str__(self): 146 | indent_space = self.level * "\t" 147 | for stmt in self.stmts: 148 | stmt.level = self.level + 1 149 | 150 | stmts_str = "".join([str(stmt) for stmt in self.stmts]) 151 | 152 | return (f"{indent_space}while {self.expr}:\n" 153 | f"{stmts_str}") 154 | 155 | 156 | ############################################### 157 | # function 158 | class Function: 159 | def __init__(self, name: str, args: List[str], stmts: List[Stmt], ret: Expr): 160 | self.name = name 161 | self.args = args 162 | self.stmts = stmts 163 | self.ret = ret 164 | 165 | def __str__(self): 166 | arg_str = ",".join(self.args) 167 | for stmt in self.stmts: 168 | stmt.level += 1 169 | 170 | stmts_str = "".join([str(stmt) for stmt in self.stmts]) 171 | 172 | # exercise 1: Finish the magic methods __str__() method to get the 173 | # desired code-printing result: 174 | # 175 | # Your code here: 176 | 177 | # raise Todo("exercise 1: please fill in the missing code.") 178 | return (f"def {self.name}({arg_str}):\n" 179 | f"{stmts_str}") 180 | 181 | 182 | ############################################### 183 | # test 184 | 185 | test_stmt = [StmtAssign('s', ExprNum(0)), 186 | StmtAssign('i', ExprNum(0)), 187 | StmtWhile(ExprBop(ExprVar('i'), ExprBop(ExprVar('n'), ExprNum(3), Bop.MIN), Bop.LE), 188 | [StmtAssign('s', ExprBop(ExprVar('s'), ExprVar('i'), Bop.ADD)), 189 | StmtAssign('i', ExprBop(ExprVar('i'), ExprNum(1), Bop.ADD)), 190 | StmtIf(ExprBop(ExprVar('s'), ExprVar('i'), Bop.GT), 191 | [StmtAssign("b", ExprBop(ExprVar('s'), ExprNum(1), Bop.MIN))], 192 | []) 193 | ]), 194 | StmtIf(ExprBop(ExprVar('s'), ExprVar('i'), Bop.GT), 195 | [StmtAssign("s", ExprBop(ExprVar('i'), ExprNum(1), Bop.MIN))], 196 | [StmtAssign("s", ExprBop(ExprVar('i'), ExprNum(1), Bop.ADD))]) 197 | ] 198 | 199 | test_func = Function(name='printer_test', args=['n'], stmts=test_stmt, ret=ExprVar('s')) 200 | 201 | 202 | if __name__ == '__main__': 203 | # Your code should print: 204 | # 205 | # def printer_test(n): 206 | # s = 0 207 | # i = 0 208 | # while i <= (n - 3): 209 | # s = s + i 210 | # i = i + 1 211 | # if s > i: 212 | # b = s - 1 213 | # if s > i: 214 | # s = i - 1 215 | # else: 216 | # s = i + 1 217 | # return s 218 | # 219 | print(test_func) 220 | -------------------------------------------------------------------------------- /assignment6/queen.py: -------------------------------------------------------------------------------- 1 | """N-queens puzzle 2 | 3 | The N-queens problem is about placing N chess queens on an N*N chessboard so that 4 | no two queens threaten each other. A solution requires that no two queens share the 5 | same row, column diagonal or anti-diagonal.The problem's target is try to find how 6 | many solutions exist. 7 | 8 | """ 9 | 10 | import time 11 | from z3 import * 12 | 13 | 14 | class Todo(Exception): 15 | def __init__(self, msg): 16 | self.msg = msg 17 | 18 | def __str__(self): 19 | return self.msg 20 | 21 | def __repr__(self): 22 | return self.__str__() 23 | 24 | 25 | def n_queen_la(board_size: int, verbose: bool = False) -> int: 26 | solver = Solver() 27 | n = board_size 28 | 29 | # Each position of the board is represented by a 0-1 integer variable: 30 | # ... ... ... ... 31 | # x_2_0 x_2_1 x_2_2 ... 32 | # x_1_0 x_1_1 x_1_2 ... 33 | # x_0_0 x_0_1 x_0_2 ... 34 | # 35 | board = [[Int(f"x_{row}_{col}") for col in range(n)] for row in range(n)] 36 | # print(board) 37 | # only be 0 or 1 in board 38 | for row in board: 39 | for pos in row: 40 | solver.add(Or(pos == 0, pos == 1)) 41 | 42 | # @exercise 11: please fill in the missing code to add 43 | # the following constraint into the solver: 44 | # each row has just 1 queen, 45 | # each column has just 1 queen, 46 | # each diagonal has at most 1 queen, 47 | # each anti-diagonal has at most 1 queen. 48 | # raise Todo("exercise 11: please fill in the missing code.") 49 | for row in board: 50 | solver.add(sum(row) == 1) # 约束1:一行只有一个皇后 51 | for i in range(n): 52 | col = [] 53 | for j in range(n): 54 | col.append(board[j][i]) 55 | solver.add(sum(col) == 1) # 约束2:一列只有一个皇后 56 | 57 | # 对角线元素放到dia数组里面 58 | for d in range(1 - n, n): 59 | dia = [] 60 | for i in range(n): 61 | j = i - d 62 | # print(i, j, d) 63 | if 0 <= j < n: 64 | dia.append(board[i][j]) 65 | solver.add(sum(dia) <= 1) # 约束3:对角线最多只有一个皇后 66 | # 反对角线元素放到anti_dia数组里面 67 | for d in range(0, 2 * n - 1): 68 | anti_dia = [] 69 | for i in range(n): 70 | j = d - i 71 | if 0 <= j < n: 72 | anti_dia.append(board[i][j]) 73 | # current_anti_diagonal_list = [board[i][j]] 74 | solver.add(sum(anti_dia) <= 1) # 约束4:反对角线最多只有一个皇后 75 | 76 | # count the number of solutions 77 | solution_count = 0 78 | 79 | start = time.time() 80 | while solver.check() == sat: 81 | solution_count += 1 82 | model = solver.model() 83 | 84 | if verbose: 85 | # print the solution 86 | print([(row_index, col_index) for row_index, row in enumerate(board) 87 | for col_index, flag in enumerate(row) if model[flag] == 1]) 88 | 89 | # generate constraints from solution 90 | solution_cons = [(flag == 1) for row in board for flag in row if model[flag] == 1] 91 | 92 | # add solution to the solver to get new solution 93 | solver.add(Not(And(solution_cons))) 94 | 95 | print(f"n_queen_la solve {board_size}-queens by {(time.time() - start):.6f}s") 96 | return solution_count 97 | 98 | 99 | def n_queen_bt(board_size: int, verbose: bool = False) -> int: 100 | n = board_size 101 | solutions = [[]] 102 | 103 | def is_safe(col, solution): 104 | same_col = col in solution 105 | same_diag = any(abs(col - j) == (len(solution) - i) for i, j in enumerate(solution)) 106 | 107 | return not (same_col or same_diag) 108 | 109 | start = time.time() 110 | for row in range(n): 111 | solutions = [solution + [col] for solution in solutions for col in range(n) if is_safe(col, solution)] 112 | print(f"n_queen_bt solve {board_size}-queens by {(time.time() - start):.6f}s") 113 | 114 | if verbose: 115 | # print the solutions 116 | for solution in solutions: 117 | print(list(enumerate(solution))) 118 | 119 | return len(solutions) 120 | 121 | 122 | def n_queen_la_opt(board_size: int, verbose: bool = False) -> int: 123 | solver = Solver() 124 | n = board_size 125 | 126 | # We know each queen must be in a different row. 127 | # So, we represent each queen by a single integer: the column position 128 | # the q_i = j means queen in the row i and column j. 129 | queens = [Int(f"q_{i}") for i in range(n)] 130 | 131 | # each queen is in a column {0, ... 7 } 132 | solver.add([And(0 <= queens[i], queens[i] < n) for i in range(n)]) 133 | 134 | # one queen per column 135 | solver.add([Distinct(queens)]) 136 | 137 | # at most one for per anti-diagonal & diagonal 138 | solver.add([If(i == j, True, And(queens[i] - queens[j] != i - j, queens[i] - queens[j] != j - i)) 139 | for i in range(n) for j in range(i)]) 140 | 141 | # count the number of solutions 142 | solution_count = 0 143 | start = time.time() 144 | 145 | while solver.check() == sat: 146 | solution_count += 1 147 | model = solver.model() 148 | 149 | if verbose: 150 | # print the solutions 151 | print([(index, model[queen]) for index, queen in enumerate(queens)]) 152 | 153 | # generate constraints from solution 154 | solution_cons = [(queen == model[queen]) for queen in queens] 155 | 156 | # add solution to the solver to get new solution 157 | solver.add(Not(And(solution_cons))) 158 | 159 | print(f"n_queen_la_opt solve {board_size}-queens by {(time.time() - start):.6f}s") 160 | 161 | return solution_count 162 | 163 | 164 | if __name__ == '__main__': 165 | # 8-queen problem has 92 solutions 166 | n_queen_la(8) 167 | 168 | # @exercise 12: Try to compare the backtracking with the LA algorithms, 169 | # by changing the value of the chessboard size to other values, 170 | # which one is faster? What conclusion you can draw from the result? 171 | # raise Todo("exercise 12: please fill in the missing code.") 172 | # n_queen_bt(8) 173 | 174 | # @exercise 13: Try to compare the efficiency of n_queen_la_opt() method 175 | # with your n_queen_la() method. 176 | # What's your observation? What conclusion you can draw? 177 | # raise Todo("exercise 13: please fill in the missing code.") 178 | # n_queen_la_opt(8) 179 | 180 | # 三种算法解决N皇后问题效率的比较: 用回溯法最快、LA优化算法其次、LA算法最慢 181 | -------------------------------------------------------------------------------- /assignment8/concrete.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from dataclasses import dataclass 3 | from typing import Dict 4 | 5 | from mini_py import * 6 | 7 | 8 | class Todo(Exception): 9 | def __init__(self, msg): 10 | self.msg = msg 11 | 12 | def __str__(self): 13 | return self.msg 14 | 15 | def __repr__(self): 16 | return self.__str__() 17 | 18 | # a concrete execution engine. 19 | 20 | 21 | # exercise 2: Run the code below, which is the data model 22 | # of concrete memory. Concrete execution memory model will store arguments 23 | # and concrete values. Make sure you understand the model and you don't 24 | # need to write any code here. 25 | 26 | # concrete execution memory model will store arguments and concrete values 27 | @dataclass 28 | class Memory: 29 | args: List[str] 30 | concrete_memory: Dict[str, int] 31 | 32 | def __str__(self): 33 | arg_str = ",".join(self.args) 34 | concrete_str = "\n".join([f"\t{var}: {value}" for var, value in self.concrete_memory.items()]) 35 | return (f"Arguments: {arg_str}\n" 36 | f"Concrete Values: \n" 37 | f"{concrete_str}\n") 38 | 39 | 40 | ##################### 41 | # concrete execution 42 | def interpret_expr(memory, expr): 43 | if isinstance(expr, ExprNum): 44 | return expr.num 45 | 46 | if isinstance(expr, ExprVar): 47 | return memory.concrete_memory[expr.var] 48 | 49 | if isinstance(expr, ExprBop): 50 | if expr.bop is Bop.ADD: 51 | return interpret_expr(memory, expr.left) + interpret_expr(memory, expr.right) 52 | if expr.bop is Bop.MIN: 53 | return interpret_expr(memory, expr.left) - interpret_expr(memory, expr.right) 54 | if expr.bop is Bop.MUL: 55 | return interpret_expr(memory, expr.left) * interpret_expr(memory, expr.right) 56 | if expr.bop is Bop.DIV: 57 | return interpret_expr(memory, expr.left) / interpret_expr(memory, expr.right) 58 | if expr.bop is Bop.EQ: 59 | return interpret_expr(memory, expr.left) == interpret_expr(memory, expr.right) 60 | if expr.bop is Bop.NE: 61 | return interpret_expr(memory, expr.left) != interpret_expr(memory, expr.right) 62 | if expr.bop is Bop.GT: 63 | return interpret_expr(memory, expr.left) > interpret_expr(memory, expr.right) 64 | if expr.bop is Bop.GE: 65 | return interpret_expr(memory, expr.left) >= interpret_expr(memory, expr.right) 66 | if expr.bop is Bop.LT: 67 | return interpret_expr(memory, expr.left) < interpret_expr(memory, expr.right) 68 | if expr.bop is Bop.LE: 69 | return interpret_expr(memory, expr.left) <= interpret_expr(memory, expr.right) 70 | 71 | 72 | def interpret_stm(memory, stmt): 73 | # exercise 3: Complete the code to interpret statement in MiniPy, by 74 | # following the big-step operational semantics rules from the lecture note. 75 | # 76 | # Your code here: 77 | 78 | # raise Todo("exercise 3: please fill in the missing code.") 79 | if isinstance(stmt, StmtAssign): 80 | memory.concrete_memory[stmt.var] = interpret_expr(memory, stmt.expr) 81 | elif isinstance(stmt, StmtIf): 82 | if interpret_expr(memory, stmt.expr): 83 | interpret_stmts(memory, stmt.then_stmts) 84 | else: 85 | interpret_stmts(memory, stmt.else_stmts) 86 | elif isinstance(stmt, StmtWhile): 87 | while interpret_expr(memory, stmt.expr): 88 | interpret_stmts(memory, stmt.stmts) 89 | return memory 90 | 91 | 92 | def interpret_stmts(memory, stmts): 93 | for stmt in stmts: 94 | interpret_stm(memory, stmt) 95 | return memory 96 | 97 | 98 | def interpret_func(func, params): 99 | assert len(func.args) == len(params), "The number of parameters does not match" 100 | memory = Memory(func.args, dict(zip(func.args, params))) 101 | interpret_stmts(memory, func.stmts) 102 | return interpret_expr(memory, func.ret) 103 | 104 | 105 | ####################################### 106 | # test code 107 | func_sum = Function('sum', ['n'], 108 | [StmtAssign('s', ExprNum(0)), 109 | StmtAssign('i', ExprNum(0)), 110 | StmtWhile(ExprBop(ExprVar('i'), ExprVar('n'), Bop.LE), 111 | [StmtAssign('s', ExprBop(ExprVar('s'), ExprVar('i'), Bop.ADD)), 112 | StmtAssign('i', ExprBop(ExprVar('i'), ExprNum(1), Bop.ADD)) 113 | ]) 114 | ], ExprVar('s')) 115 | 116 | func_max = Function("max", ["m", "n"], 117 | [StmtAssign("c", ExprVar("m")), 118 | StmtIf(ExprBop(ExprVar("n"), ExprVar("c"), Bop.GT), 119 | [StmtAssign("c", ExprVar("n"))], 120 | []) 121 | ], ExprVar("c")) 122 | 123 | func_gcd = Function("gcd", ["m", "n"], 124 | [StmtWhile(ExprBop(ExprVar("m"), ExprVar("n"), Bop.NE), 125 | [StmtIf(ExprBop(ExprVar("m"), ExprVar("n"), Bop.GE), 126 | [StmtAssign("m", ExprBop(ExprVar("m"), ExprVar("n"), Bop.MIN))], 127 | [StmtAssign("n", ExprBop(ExprVar("n"), ExprVar("m"), Bop.MIN))]) 128 | ]) 129 | ], ExprVar("m")) 130 | 131 | 132 | class TestConcrete(unittest.TestCase): 133 | def test_interpret_exp(self): 134 | 135 | # 3 + 2 >= 3 * 2 136 | exp1 = ExprBop(ExprBop(ExprNum(3), ExprNum(2), Bop.ADD), 137 | ExprBop(ExprNum(3), ExprNum(2), Bop.MUL), 138 | Bop.GE) 139 | 140 | # 10 / 2 != 8 - 2 141 | exp2 = ExprBop(ExprBop(ExprNum(10), ExprNum(2), Bop.DIV), 142 | ExprBop(ExprNum(8), ExprNum(2), Bop.MIN), 143 | Bop.NE) 144 | 145 | self.assertEqual(interpret_expr({}, exp1), False) 146 | self.assertEqual(interpret_expr({}, exp2), True) 147 | 148 | def test_interpret_func_sum(self): 149 | # print(func_sum) 150 | self.assertEqual(interpret_func(func_sum, [100]), 5050) 151 | 152 | def test_interpret_func_max(self): 153 | # print(func_max) 154 | self.assertEqual(interpret_func(func_max, [10, 20]), 20) 155 | 156 | def test_interpret_func_gcd(self): 157 | # print(func_gcd) 158 | self.assertEqual(interpret_func(func_gcd, [60, 48]), 12) 159 | 160 | 161 | if __name__ == '__main__': 162 | unittest.main() 163 | 164 | -------------------------------------------------------------------------------- /assignment7/bit_blast.py: -------------------------------------------------------------------------------- 1 | from z3 import * 2 | 3 | 4 | class Todo(Exception): 5 | def __init__(self, msg): 6 | self.msg = msg 7 | 8 | def __str__(self): 9 | return self.msg 10 | 11 | def __repr__(self): 12 | return self.__str__() 13 | 14 | # In this part of the assignment, we'll implement the 15 | # bit-blasting algorithm, as described in the class. 16 | 17 | 18 | # First, let's define the basic data structures to represent 19 | # the bit vector syntax: 20 | # E ::= c | x | E&E | E|E | ~E 21 | # R ::= E=E | E!=E 22 | # P ::= R | P/\P 23 | 24 | # we use a functional programming style in this assigment. 25 | 26 | ############################################################### 27 | # Part I: the bit vector syntax. 28 | class Exp: 29 | # vars are the generated bit variables 30 | def __init__(self, gen_vars): 31 | self.gen_vars = gen_vars 32 | 33 | 34 | class ExpConst(Exp): 35 | def __init__(self, c): 36 | super().__init__([]) 37 | self.c = c 38 | 39 | 40 | class ExpVar(Exp): 41 | def __init__(self, x): 42 | super().__init__([]) 43 | self.x = x 44 | 45 | 46 | class ExpBitwiseAnd(Exp): 47 | def __init__(self, left, right): 48 | super().__init__([]) 49 | self.left = left 50 | self.right = right 51 | 52 | 53 | class ExpBitwiseOr(Exp): 54 | def __init__(self, left, right): 55 | super().__init__([]) 56 | self.left = left 57 | self.right = right 58 | 59 | 60 | class ExpBitwiseNot(Exp): 61 | def __init__(self, e): 62 | super().__init__([]) 63 | self.e = e 64 | 65 | 66 | #### 67 | # relations 68 | class Relation: 69 | pass 70 | 71 | 72 | class RelationEq(Relation): 73 | def __init__(self, left, right): 74 | self.left = left 75 | self.right = right 76 | 77 | 78 | class RelationNe(Relation): 79 | def __init__(self, left, right): 80 | self.left = left 81 | self.right = right 82 | 83 | 84 | #### 85 | # propositions 86 | class Prop: 87 | pass 88 | 89 | 90 | class PropSingle(Prop): 91 | def __init__(self, r): 92 | self.r = r 93 | 94 | 95 | class PropAnd(Prop): 96 | def __init__(self, left, right): 97 | self.left = left 98 | self.right = right 99 | 100 | 101 | # printing facilities 102 | # a flag to control whether or not to print the attached boolean variables 103 | should_print_vars = False 104 | 105 | 106 | def print_atomic(s): 107 | print(s, "", end="") 108 | 109 | 110 | def print_exp(e): 111 | if isinstance(e, ExpConst): 112 | print_atomic(e.c) 113 | elif isinstance(e, ExpVar): 114 | print_atomic(e.x) 115 | elif isinstance(e, ExpBitwiseAnd): 116 | print_exp(e.left) 117 | print_atomic("&") 118 | print_exp(e.right) 119 | elif isinstance(e, ExpBitwiseOr): 120 | print_exp(e.left) 121 | print_atomic("|") 122 | print_exp(e.right) 123 | elif isinstance(e, ExpBitwiseAnd): 124 | print_atomic("~") 125 | print_exp(e.e) 126 | 127 | if should_print_vars: 128 | print(e.gen_vars, sep=', ') 129 | 130 | 131 | def print_relation(r): 132 | if isinstance(r, RelationEq): 133 | print_exp(r.left) 134 | print_atomic("=") 135 | print_exp(r.right) 136 | elif isinstance(r, RelationNe): 137 | print_exp(r.left) 138 | print("!=") 139 | print_exp(r.right) 140 | 141 | 142 | def print_prop(p): 143 | if isinstance(p, PropSingle): 144 | print_relation(p.r) 145 | elif isinstance(p, PropAnd): 146 | print_prop(p.left) 147 | print_atomic("/\\") 148 | print_prop(p.right) 149 | 150 | 151 | # x=1 /\ y=2 /\ x&y=1 152 | def gen_sample_prop(): 153 | return PropAnd(PropAnd(PropSingle(RelationEq(ExpVar("x"), 154 | ExpConst(1))), 155 | PropSingle(RelationEq(ExpVar("y"), 156 | ExpConst(2)))), 157 | PropSingle(RelationEq(ExpBitwiseAnd(ExpVar("x"), 158 | ExpVar("y")), 159 | ExpConst(1)))) 160 | 161 | 162 | ############################################################### 163 | # Part II: the bit blasting algorithm. 164 | 165 | # the width, to make debugging simple, use smaller "L". 166 | L = 2 167 | 168 | # the counter, to generate nice variable names. 169 | counter = 0 170 | 171 | 172 | # a dictionary used to remember variables' boolean variable list 173 | var_map = {} 174 | 175 | 176 | def gen_var_list(): 177 | global counter 178 | names = [Bool(f"x_{i}") for i in range(counter, counter+L)] 179 | counter = counter + L 180 | return names 181 | 182 | 183 | # bit blasting a proposition 184 | cons = [] 185 | 186 | 187 | # exercise challenge: implement the array elimination algorithm 188 | def blast_exp(e): 189 | raise Todo("exercise challenge: please fill in the missing code.") 190 | 191 | 192 | def blast_relation(r): 193 | raise Todo("exercise challenge: please fill in the missing code.") 194 | 195 | 196 | def blast_prop(p): 197 | raise Todo("exercise challenge: please fill in the missing code.") 198 | 199 | 200 | # pass #2: generate constraints 201 | def gen_cons_exp_const(n, the_vars): 202 | raise Todo("exercise challenge: please fill in the missing code.") 203 | 204 | 205 | def gen_cons_exp(e): 206 | raise Todo("exercise challenge: please fill in the missing code.") 207 | 208 | 209 | def gen_equal_vars(l, r): 210 | raise Todo("exercise challenge: please fill in the missing code.") 211 | 212 | 213 | def gen_cons_relation(r): 214 | raise Todo("exercise challenge: please fill in the missing code.") 215 | 216 | 217 | def gen_cons_prop(p): 218 | raise Todo("exercise challenge: please fill in the missing code.") 219 | 220 | 221 | # Input: a proposition 222 | # output: a set of constraints, in "cons" 223 | def bit_blast(p): 224 | raise Todo("exercise challenge: please fill in the missing code.") 225 | 226 | 227 | if __name__ == '__main__': 228 | sample_prop = gen_sample_prop() 229 | print("the raw proposition:") 230 | print_prop(sample_prop) 231 | print("\nthe blasted proposition:") 232 | cons = bit_blast(sample_prop) 233 | print("the generated constraints:") 234 | print(cons) 235 | # send constraint to the SAT solver 236 | solver = Solver() 237 | solver.add(cons) 238 | res = solver.check() 239 | if res == sat: 240 | print(solver.model()) 241 | else: 242 | print("\033[31munsat!\033[0m") 243 | 244 | 245 | 246 | -------------------------------------------------------------------------------- /assignment6/linear_regression.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | from z3 import * 3 | 4 | from linear_regression_ml import sklearn_lr 5 | 6 | 7 | class Todo(Exception): 8 | def __init__(self, msg): 9 | self.msg = msg 10 | 11 | def __str__(self): 12 | return self.msg 13 | 14 | def __repr__(self): 15 | return self.__str__() 16 | 17 | 18 | ################################################ 19 | # Linear Regression (from the SMT point of view) 20 | 21 | # In statistics, linear regression is a linear approach to modelling 22 | # the relationship between a scalar response and one or more explanatory 23 | # variables (also known as dependent and independent variables). 24 | # The case of one explanatory variable is called simple linear regression; 25 | # for more than one, the process is called multiple linear regression. 26 | # This term is distinct from multivariate linear regression, where multiple 27 | # correlated dependent variables are predicted, rather than a single scalar variable. 28 | 29 | # In recent years, linear Linear regression plays an important role in the 30 | # field of artificial intelligence such as machine learning. The linear 31 | # regression algorithm is one of the fundamental supervised machine-learning 32 | # algorithms due to its relative simplicity and well-known properties. 33 | # Interested readers can refer to the materials on deep learning, 34 | # for instance, Andrew Ng gives a good introduction to linear regression 35 | # from a deep learning point of view. 36 | 37 | # However, as this is not a deep learning course, so we'll concentrate 38 | # on the mathematical facet. And you should learn the background 39 | # knowledge on linear regression by yourself. 40 | 41 | # We start by studying one concrete example, given the following data 42 | # (in machine learning terminology, these are called the training data): 43 | xs = [1.0, 2.0, 3.0, 4.0] 44 | ys = [1.0, 3.0, 5.0, 7.0] 45 | 46 | # our goal is to produce a linear function: 47 | # y = k*x + b 48 | # such that it fits the above data as close as possible, where 49 | # the variables "k" and "b" are unknown variables. 50 | # By "as close as possible", we use a least square method, that is, we 51 | # want to minimize the following expression: 52 | # min(\sum_i (ys[i] - (k*xs[i]+b)^2) (1) 53 | 54 | # Now the next step is to solve the equation (1) to calculate the values 55 | # for the variables "k" and "b". 56 | # The popular approach used extensively in deep learning is the 57 | # gradient decedent algorithm, if you're interested in this algorithm, 58 | # here is a good introduction from Andrew Ng (up to page 7): 59 | # https://see.stanford.edu/materials/aimlcs229/cs229-notes1.pdf 60 | 61 | # In the following, we'll discuss how to solve this problem using 62 | # SMT technique from this course. 63 | 64 | # Both "draw_points()" and "draw_line()" are drawing utility functions to 65 | # draw points and straight line. 66 | # You don't need to understand these code, and you can skip 67 | # these two functions safely. If you are really interested, 68 | # please refer to the manuals of matplotlib library. 69 | 70 | 71 | # Input: xs and ys are the given data for the coordinates 72 | # Output: draw these points [xs, ys], no explicit return values. 73 | def draw_points(xs, ys): 74 | # print('print') 75 | plt.scatter(xs, ys, marker='x', color='red', s=40, label='Data') 76 | plt.legend(loc='best') 77 | plt.xlim(0, 8) # 设定绘图范围 78 | plt.ylim(0, 8) 79 | plt.savefig("./points.png") 80 | plt.show() 81 | 82 | 83 | # Input: a group of coordinates [xs, ys] 84 | # k and b are coefficients 85 | # Output: draw the coordinates [xs, ys], draw the line y=k*x+b 86 | # no explicit return values 87 | def draw_line(k, b, xs, ys): 88 | new_ys = [(k*xs[i]+b) for i in range(len(xs))] 89 | # print(new_ys) 90 | plt.scatter(xs, ys, marker='x', color='red', s=40, label='Data') 91 | plt.plot(xs, new_ys, 'b') 92 | plt.legend(loc='best') 93 | # plt.xlim(0, 8) # 设定绘图范围 94 | # plt.ylim(0, 8) 95 | plt.savefig("./line.png") 96 | plt.show() 97 | 98 | 99 | # Arguments: xs, ys, the given data for these coordinates 100 | # Return: 101 | # 1. the solver checking result "res"; 102 | # 2. the k, if any; 103 | # 3. the b, if any. 104 | def lr_training(xs, ys): 105 | # create two coefficients 106 | k, b = Ints('k b') 107 | 108 | # exercise 18: Use a least squares method 109 | # (https://en.wikipedia.org/wiki/Least_squares) 110 | # to generate the target expression which will be minimized 111 | # Your code here: 112 | # raise Todo("exercise 18: please fill in the missing code.") 113 | exps = [(ys[i] - (k * xs[i] + b)) * (ys[i] - (k * xs[i] + b)) for i in range(len(xs))] 114 | # i = 0 115 | # for i, x in enumerate(xs): 116 | # exps.append((ys[i] - k * x - b)**2) 117 | # # i = i + 1 118 | # double check the expression is right, 119 | # it should output: 120 | # 121 | # 0 + 122 | # (1 - k*1 - b)*(1 - k*1 - b) + 123 | # (3 - k*2 - b)*(3 - k*2 - b) + 124 | # (5 - k*3 - b)*(5 - k*3 - b) + 125 | # (7 - k*4 - b)*(7 - k*4 - b) 126 | # 127 | print("the target expression is: ") 128 | print(exps) 129 | sumE = 0 130 | for exp in exps: 131 | sumE += exp 132 | print(sumE) 133 | print(sum(exps)) 134 | 135 | # create a solver 136 | solver = Optimize() 137 | 138 | # add some constraints into the solver, these are the feasible values 139 | solver.add([k < 100, k > 0, b > -10, b < 10]) 140 | 141 | # tell the solver which expression to check 142 | solver.minimize(sumE) 143 | 144 | # kick the solver to perform checking 145 | res = solver.check() 146 | 147 | # return the result, if any 148 | if res == sat: 149 | model = solver.model() 150 | kv = model[k] 151 | bv = model[b] 152 | return res, kv.as_long(), bv.as_long() 153 | else: 154 | return res, None, None 155 | 156 | 157 | if __name__ == '__main__': 158 | draw_points(xs, ys) 159 | res, k, b = lr_training(xs, ys) 160 | if res == sat: 161 | print(f"the linear function is:\n y = {k}*x {'+' if b >= 0 else '-'} {abs(b)}") 162 | draw_line(k, b, xs, ys) 163 | else: 164 | print('\033[91m Training failed! \033[0m') 165 | 166 | k, b = sklearn_lr(xs, ys) 167 | print(f"the linear function is:\n y = {k}*x {'+' if b >= 0 else '-'} {abs(b)}") 168 | 169 | # exercise 19: Compare the machine learning approach and the LP approach 170 | # by trying some different training data. Do the two algorithms produce the same 171 | # results? What conclusion you can draw from the result? 172 | # Your code here: 173 | 174 | 175 | -------------------------------------------------------------------------------- /assignment8/concolic.py: -------------------------------------------------------------------------------- 1 | import random 2 | from dataclasses import dataclass 3 | from typing import Dict 4 | 5 | from z3 import * 6 | from mini_py import * 7 | 8 | from symbolic import check_cond, neg_exp, symbolic_expr, f1 9 | from concrete import interpret_expr 10 | 11 | 12 | class Todo(Exception): 13 | def __init__(self, msg): 14 | self.msg = msg 15 | 16 | def __str__(self): 17 | return self.msg 18 | 19 | def __repr__(self): 20 | return self.__str__() 21 | 22 | # a concolic execution engine. 23 | 24 | # concolic memory model will store arguments, concrete values, symbolic values and path condition 25 | @dataclass 26 | class Memory: 27 | args: List[str] 28 | concrete_memory: Dict[str, int] 29 | symbolic_memory: Dict[str, Expr] 30 | path_condition: List[Expr] 31 | 32 | def __str__(self): 33 | arg_str = ",".join(self.args) 34 | actual_str = ",".join([f"{var} = {value}" for var, value in self.concrete_memory.items()]) 35 | expr_str = "\n".join([f"\t{var} = {value}" for var, value in self.symbolic_memory.items()]) 36 | cond_str = ",".join([str(cond) for cond in self.path_condition]) 37 | return (f"Arguments: {arg_str}\n" 38 | f"Path Condition: {cond_str}\n" 39 | f"Actual Table: {actual_str}\n" 40 | f"Symbol Table: \n" 41 | f"{expr_str}\n") 42 | 43 | 44 | ##################### 45 | # concolic execution 46 | def concolic_stmt(memory, stmt): 47 | if isinstance(stmt, StmtAssign): 48 | # exercise 8: Deal with assign-statement, you need to maintain both a symbolic 49 | # memory and a concrete memory. You can directly use the corresponding functions 50 | # symbolic.py and concrete.py file. 51 | # 52 | # Your code here: 53 | 54 | # raise Todo("exercise 8: please fill in the missing code.") 55 | memory.concrete_memory[stmt.var] = interpret_expr(memory, stmt.expr) 56 | memory.symbolic_memory[stmt.var] = symbolic_expr(memory, stmt.expr) 57 | 58 | elif isinstance(stmt, StmtIf): 59 | # exercise 8: Deal with if-statement, recall that concolic execution will do the 60 | # concrete execute on if-statement, but it need to store the path condition to the memory. 61 | # 62 | # Your code here: 63 | 64 | # raise Todo("exercise 8: please fill in the missing code.") 65 | if interpret_expr(memory, stmt.expr): 66 | memory.path_condition.append(symbolic_expr(memory, stmt.expr)) 67 | memory = concolic_stmts(memory, stmt.then_stmts) 68 | else: 69 | memory.path_condition.append(neg_exp(symbolic_expr(memory, stmt.expr))) 70 | memory = concolic_stmts(memory, stmt.else_stmts) 71 | 72 | elif isinstance(stmt, StmtWhile): 73 | # exercise 9: Process the while-statement, what you need to do is execute the 74 | # loop expression by concrete execution to decide whether to continue, 75 | # and for the statements contained by while-statement, do the concolic 76 | # execution. Don't forget to add the loop judgment expression to the 77 | # path condition list for every loop. 78 | # 79 | # Your code here: 80 | 81 | # raise Todo("exercise 9: please fill in the missing code.") 82 | while interpret_expr(memory, stmt.expr): 83 | memory.path_condition.append( 84 | ExprBop( 85 | memory.symbolic_memory[stmt.expr.left.var], 86 | memory.symbolic_memory[stmt.expr.right.var], 87 | stmt.expr.bop)) 88 | concolic_stmts(memory, stmt.stmts) 89 | memory.path_condition.append(neg_exp(symbolic_expr(memory, stmt.expr))) 90 | 91 | return memory 92 | 93 | 94 | def concolic_stmts(memory, stmts): 95 | for stmt in stmts: 96 | concolic_stmt(memory, stmt) 97 | 98 | return memory 99 | 100 | 101 | def concolic_func(func, init_concrete): 102 | # init memory 103 | init_symbolic = dict(zip(func.args, [ExprVar(arg) for arg in func.args])) 104 | memory = Memory(func.args, init_concrete, init_symbolic, []) 105 | 106 | concolic_stmts(memory, func.stmts) 107 | return memory, interpret_expr(memory, func.ret) 108 | 109 | 110 | def concolic_executor(func, init_params, try_times): 111 | init_concrete = dict(zip(func.args, init_params)) 112 | print(f"First Try, Input Value: {init_concrete}") 113 | memory, _ = concolic_func(func, init_concrete.copy()) 114 | print(memory) 115 | 116 | # random select and negate one condition from previous result 117 | # and use z3 to generate a input to do next concolic execution 118 | for try_time in range(2, try_times+1, 1): 119 | random_idx = random.randrange(0, len(memory.path_condition)) 120 | memory.path_condition[random_idx] = neg_exp(memory.path_condition[random_idx]) 121 | ret, solver = check_cond(memory) 122 | 123 | if ret == sat: 124 | # use z3 result update new input concrete values 125 | model = solver.model() 126 | for dec in model.decls(): 127 | if dec.name() in func.args: 128 | init_concrete[dec.name()] = model[dec].as_long() 129 | 130 | print(f"Try times: {try_time}, Input Value: {init_concrete}") 131 | memory, _ = concolic_func(func, init_concrete.copy()) 132 | print(memory) 133 | else: 134 | print(f"Try times: {try_time}, Path conditions got UNSAT/UNKNOWN from z3") 135 | print(f"Conditions try to Solve: {solver}\n") 136 | 137 | 138 | ##################### 139 | # test code 140 | func_loop = Function("loop", ["m", "n"], 141 | [StmtWhile(ExprBop(ExprVar("m"), ExprVar("n"), Bop.LT), 142 | [StmtIf(ExprBop(ExprVar("m"), ExprNum(0), Bop.GT), 143 | [StmtAssign("m", ExprBop(ExprVar("m"), ExprNum(2), Bop.MUL))], 144 | [StmtAssign("m", ExprBop(ExprVar("m"), ExprNum(1), Bop.ADD)) 145 | ]) 146 | ]) 147 | ], ExprVar("m")) 148 | 149 | # example for challenge 150 | hard_stmt_1 = ExprBop(ExprBop(ExprBop(ExprVar("y"), ExprVar("y"), Bop.MUL), ExprVar("x"), Bop.MUL), 151 | ExprBop(ExprVar("y"), ExprNum(23123), Bop.MUL), Bop.ADD) 152 | 153 | func_foo = Function("foo", ["x", "y"], 154 | [StmtAssign("m", hard_stmt_1), 155 | StmtIf(ExprBop(ExprVar("m"), ExprVar("y"), Bop.LE), 156 | [StmtAssign("s", ExprNum(1))], 157 | [StmtAssign("s", ExprNum(2))]) 158 | ], ExprVar("s")) 159 | 160 | 161 | if __name__ == '__main__': 162 | print(f1) 163 | 164 | # sample output(not the exactly output): 165 | # change the try_times as you want 166 | # 167 | # First Try, Input Value: {'a': 0, 'b': 0} 168 | # Arguments: a,b 169 | # Path Condition: a == 0 170 | # Actual Table: a = 0,b = 0,x = 1,y = 0 171 | # Symbol Table: 172 | # a = a 173 | # b = b 174 | # x = 1 175 | # y = 0 176 | # 177 | # Try times: 2, Input Value: {'a': 1, 'b': 0} 178 | # Arguments: a,b 179 | # Path Condition: a != 0,b == 0 180 | # Actual Table: a = 1,b = 0,x = 2,y = 4 181 | # Symbol Table: 182 | # a = a 183 | # b = b 184 | # x = 2 * (a + b) 185 | # y = 1 + 3 186 | # 187 | # Try times: 3, Input Value: {'a': 1, 'b': 1} 188 | # Arguments: a,b 189 | # Path Condition: a != 0,b != 0 190 | # Actual Table: a = 1,b = 1,x = 1,y = 4 191 | # Symbol Table: 192 | # a = a 193 | # b = b 194 | # x = 1 195 | # y = 1 + 3 196 | concolic_executor(f1, [0, 0], try_times=3) 197 | print(func_loop) 198 | 199 | # Should output: 200 | # 201 | # First Try, Input Value: {'m': 0, 'n': 4} 202 | # Arguments: m, n 203 | # Path Condition: m < n, m <= 0, (m + 1) < n, (m + 1) > 0, ((m + 1) * 2) < n, ((m + 1) * 2) > 0, (((m + 1) * 2) * 2) >= n 204 | # Actual Table: m = 4, n = 4 205 | # Symbol Table: 206 | # m = ((m + 1) * 2) * 2 207 | # n = n 208 | concolic_executor(func_loop, [0, 4], 1) 209 | -------------------------------------------------------------------------------- /assignment6/knapsack.py: -------------------------------------------------------------------------------- 1 | """ Knapsack problem 2 | 3 | 4 | The knapsack problem is a typical optimization problem,which has been 5 | studied for hundred of years. The problem is: given a set of items, each 6 | item has a weight and a value, determine the number of items such that the 7 | total weight is less than a given limit and the total value is as large 8 | as possible. There are a number of sub-problems of the knapsack problem: 9 | 0-1 knapsack problem, complete knapsack problem, multiply knapsack problem, 10 | multi-dimensional knapsack problem and so on. 11 | 12 | This problem is NPC, and for more background information of the 13 | knapsack problem, please refer to: 14 | https://en.wikipedia.org/wiki/Knapsack_problem 15 | """ 16 | 17 | import time 18 | from pathlib import Path 19 | 20 | from z3 import * 21 | 22 | 23 | class Todo(Exception): 24 | def __init__(self, msg): 25 | self.msg = msg 26 | 27 | def __str__(self): 28 | return self.msg 29 | 30 | def __repr__(self): 31 | return self.__str__() 32 | 33 | 34 | # 0-1 Knapsack problem 35 | # 36 | # The 0-1 knapsack problem is 37 | # There are n items, with specific weight 38 | # W = {w1, ..., wn} 39 | # and value: 40 | # V = {v1, ..., vn} 41 | # For a given knapsack of maximum capacity C, how to choose some items 42 | # such that: 43 | # wi+...+wk <= C 44 | # and with maximum value 45 | # max(vi+...+vk). 46 | 47 | # Here is a concrete example: 48 | # W = {4, 6, 2, 2, 5, 1} 49 | # V = {8, 10, 6, 3, 7, 2} 50 | # the result is: we should select the first, second, and third items, and 51 | # the total value is: 52 | # 8+10+6 = 24 53 | 54 | # The 0-1 knapsack problem is often solved by the dynamic 55 | # programming, and here is a DP algorithm: 56 | def zero_one_knapsack_dp(weights, values, cap): 57 | def knapsack_dp_do(rest_cap, index): 58 | if rest_cap <= 0 or index <= 0: 59 | return 0 60 | 61 | if weights[index - 1] > rest_cap: 62 | return knapsack_dp_do(rest_cap, index - 1) 63 | 64 | value_1 = knapsack_dp_do(rest_cap, index - 1) 65 | value_2 = knapsack_dp_do(rest_cap - weights[index - 1], index - 1) 66 | 67 | if value_1 >= (value_2 + values[index - 1]): 68 | return value_1 69 | 70 | return value_2 + values[index-1] 71 | 72 | start = time.time() 73 | result = knapsack_dp_do(cap, len(weights)) 74 | print(f"zero_one_knapsack_dp solve {len(weights)} items by time {(time.time() - start):.6f}s") 75 | return result 76 | 77 | 78 | # But it's more natural and much easier to solve knapsack with the 0-1 ILP theory: 79 | def zero_one_knapsack_lp(weights, values, cap, verbose=False): 80 | # create a new solver, but 81 | solver = Optimize() 82 | 83 | # the decision variables 84 | flags = [Int(f"x_{i}") for i in range(len(weights))] 85 | # print(flags) 86 | 87 | # flags are 0-1 88 | for flag in flags: 89 | solver.add(Or(flag == 0, flag == 1)) 90 | 91 | # @exercise 15: solve the 0-1 knapsack problem by using 0-1 ILP 92 | # construct the constraint 93 | # \sum_i weights[i] * flags[i] <= cap 94 | # and the the target 95 | # \sum_i values[i] * flags[i] 96 | # Your code here: 97 | # @begin 98 | # raise Todo("exercise 15: please fill in the missing code.") 99 | w_f = [] 100 | i = 0 101 | for w in weights: 102 | w_f.append(w * flags[i]) 103 | i = i + 1 104 | solver.add(sum(w_f) <= cap) # 约束条件:\sum_i weights[i] * flags[i] <= cap 105 | j = 0 106 | v_f = [] 107 | for v in values: 108 | v_f.append(v * flags[j]) 109 | j = j + 1 110 | solver.maximize(sum(v_f)) 111 | # @end 112 | 113 | start = time.time() 114 | result = solver.check() 115 | print(f"zero_one_knapsack_lp solve {len(weights)} items by time {(time.time() - start):.6f}s") 116 | 117 | if result == sat: 118 | model = solver.model() 119 | 120 | # print the chosen items 121 | if verbose: 122 | print("\n".join([f"Index: {index}, Weight: {weights[index]}, Value: {values[index]}" 123 | for index, flag in enumerate(flags) if model[flag] == 1])) 124 | 125 | return True, sum([values[index] for index, flag in enumerate(flags) if model[flag] == 1]) 126 | 127 | return False, result 128 | 129 | 130 | # The complete knapsack problem assumes that the number of items of all kinds is unlimited, 131 | # your can choose one kind of item any times. 132 | # So we need to declare a variable for each kind of item have chosen by amount 133 | def complete_knapsack_lp(weights, values, cap, verbose=False): 134 | solver = Optimize() 135 | 136 | # @exercise 16: solve the complete knapsack problem by using LP 137 | # note that flags[i] will be a integer and flags[i] >= 0 138 | # construct the constraint 139 | # \sum_i weights[i] * flags[i] <= cap 140 | # and the the target 141 | # \sum_i values[i] * flags[i] 142 | # Your code here: 143 | # @begin 144 | # raise Todo("exercise 16: please fill in the missing code.") 145 | flags = [Int(f"x_{i}") for i in range(len(weights))] 146 | 147 | for flag in flags: 148 | solver.add(flag >= 0) 149 | 150 | w_f = [] 151 | i = 0 152 | for w in weights: 153 | w_f.append(w * flags[i]) 154 | i = i + 1 155 | solver.add(sum(w_f) <= cap) # 约束条件:\sum_i weights[i] * flags[i] <= cap 156 | j = 0 157 | v_f = [] 158 | for v in values: 159 | v_f.append(v * flags[j]) 160 | j = j + 1 161 | solver.maximize(sum(v_f)) 162 | start = time.time() 163 | result = solver.check() 164 | print(f"complete_knapsack_lp solve {len(weights)} items by time {(time.time() - start):.6f}s") 165 | 166 | if result == sat: 167 | model = solver.model() 168 | # print the chosen items 169 | if verbose: 170 | print("\n".join( 171 | [f"Index: {index}, Weight: {weights[index]}, Value: {values[index]},Amount:{model[flag].as_long()}" 172 | for index, flag in enumerate(flags) if model[flag].as_long() > 0])) 173 | 174 | return True, sum( 175 | [values[index] * model[flag].as_long() for index, flag in enumerate(flags) if model[flag].as_long() > 0]) 176 | 177 | return False, result 178 | # @end 179 | 180 | 181 | def get_large_test(): 182 | # this test data is fetched from: 183 | # https://people.sc.fsu.edu/~jburkardt/datasets/knapsack_01/knapsack_01.html 184 | # the expect maximum value should be: 13549094 185 | def read_numbers_from_file(file_path): 186 | with Path(file_path).open(mode="r") as fp: 187 | content = fp.readlines() 188 | return [int(x.strip()) for x in content] 189 | 190 | file_folder = Path(__file__).parent.resolve() 191 | return (read_numbers_from_file(file_folder / "p08_w.txt"), 192 | read_numbers_from_file(file_folder / "p08_p.txt")) 193 | 194 | 195 | if __name__ == '__main__': 196 | # a small test case 197 | W = [4, 6, 2, 2, 5, 1] 198 | V = [8, 10, 6, 3, 7, 2] 199 | C = 12 200 | print(zero_one_knapsack_dp(W, V, C)) 201 | print(zero_one_knapsack_lp(W, V, C)) 202 | 203 | # another test case 204 | W = [23, 26, 20, 18, 32, 27, 29, 26, 30, 27] 205 | V = [505, 352, 458, 220, 354, 414, 498, 545, 473, 543] 206 | C = 67 207 | print(zero_one_knapsack_dp(W, V, C)) 208 | print(zero_one_knapsack_lp(W, V, C)) 209 | 210 | # test case for complete knapsack problem 211 | # it should print: 212 | # 213 | # Index: 0, Weight: 23, Value: 505, Amount: 4 214 | # Index: 2, Weight: 20, Value: 458, Amount: 2 215 | # Maximal Value: 2936 216 | # 217 | C = 133 218 | print("Maximal Value: ", complete_knapsack_lp(W, V, C, verbose=True)) 219 | 220 | # a large test case 221 | W, V = get_large_test() 222 | C = 6404180 223 | 224 | # @exercise 17: compare the efficiency of the DP and the 225 | # LP algorithm, by running your program on a larger data. 226 | # what's your observation? What conclusion you can draw from these data? 227 | # Your code here: 228 | # @begin 229 | # raise Todo("exercise 17: please fill in the missing code.") 230 | print(zero_one_knapsack_dp(W, V, C)) 231 | print(zero_one_knapsack_lp(W, V, C)) 232 | # @end 233 | -------------------------------------------------------------------------------- /assignment5/tac.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from z3 import * 4 | 5 | from counter import counter 6 | 7 | 8 | class Todo(Exception): 9 | def __init__(self, msg): 10 | self.msg = msg 11 | 12 | def __str__(self): 13 | return self.msg 14 | 15 | def __repr__(self): 16 | return self.__str__() 17 | 18 | 19 | ################################## 20 | # The abstract syntax for the Tac (three address code) language: 21 | """ 22 | S ::= x=y | x=y+z | x=y-z | x=y*z | x=y/z 23 | F ::= f(x1, ..., xn){S;* return x;} 24 | """ 25 | 26 | 27 | # statement 28 | class Stmt: 29 | def __repr__(self): 30 | return self.__str__() 31 | 32 | 33 | class StmtAssignVar(Stmt): 34 | def __init__(self, x, y): 35 | self.x = x 36 | self.y = y 37 | 38 | def __str__(self): 39 | # raise Todo("Exercise 6: finish the __str__ method in data structure StmtAssignVar") 40 | return f"\t{self.x} = {self.y};\n" 41 | 42 | 43 | class StmtAssignAdd(Stmt): 44 | def __init__(self, x, y, z): 45 | self.x = x 46 | self.y = y 47 | self.z = z 48 | 49 | def __str__(self): 50 | # raise Todo("Exercise 6: finish the __str__ method in data structure StmtAssignAdd") 51 | return f"\t{self.x} = {self.y} + {self.z};\n" 52 | 53 | 54 | class StmtAssignSub(Stmt): 55 | def __init__(self, x, y, z): 56 | self.x = x 57 | self.y = y 58 | self.z = z 59 | 60 | def __str__(self): 61 | # raise Todo("Exercise 6: finish the __str__ method in data structure StmtAssignSub") 62 | return f"\t{self.x} = {self.y} - {self.z};\n" 63 | 64 | 65 | class StmtAssignMul(Stmt): 66 | def __init__(self, x, y, z): 67 | self.x = x 68 | self.y = y 69 | self.z = z 70 | 71 | def __str__(self): 72 | # raise Todo("Exercise 6: finish the __str__ method in data structure StmtAssignMul") 73 | return f"\t{self.x} = {self.y} * {self.z};\n" 74 | 75 | 76 | class StmtAssignDiv(Stmt): 77 | def __init__(self, x, y, z): 78 | self.x = x 79 | self.y = y 80 | self.z = z 81 | 82 | def __str__(self): 83 | # raise Todo("Exercise 6: finish the __str__ method in data structure StmtAssignDiv") 84 | return f"\t{self.x} = {self.y} / {self.z};\n" 85 | 86 | 87 | # function: 88 | class Function: 89 | def __init__(self, name, args, stmts, ret): 90 | self.name = name 91 | self.args = args 92 | self.stmts = stmts 93 | self.ret = ret 94 | 95 | def __str__(self): 96 | # raise Todo("Exercise 6: finish the __str__ method in data structure Function") 97 | arg_str = ",".join(self.args) 98 | # for stm in self.stmts: 99 | # stm.level += 1 100 | 101 | stmts_str = "".join([str(stmt) for stmt in self.stmts]) 102 | 103 | return (f"{self.name}({arg_str}){{\n" 104 | f"{stmts_str}" 105 | f"\treturn {self.ret};\n" 106 | f"}}\n") 107 | 108 | 109 | ############################################### 110 | # SSA conversion: 111 | 112 | # take a function 'f', convert it to SSA 113 | def to_ssa_func(func: Function) -> Function: 114 | # raise Todo("Exercise 7: do SSA conversion on tac function ") 115 | var_map = {arg: arg for arg in func.args} 116 | # fresh variable generator 117 | fresh_var = counter(prefix=f"tac_{func.name}") 118 | 119 | def to_ssa_stmt(stmt): 120 | # raise Todo("Exercise 7: do SSA conversion on tac statements ") 121 | if isinstance(stmt, StmtAssignVar): 122 | new_var = next(fresh_var) 123 | var_map[stmt.x] = new_var 124 | return StmtAssignVar(new_var, var_map[stmt.y]) 125 | 126 | if isinstance(stmt, StmtAssignAdd): 127 | new_var = next(fresh_var) 128 | var_map[stmt.x] = new_var 129 | return StmtAssignAdd(new_var, var_map[stmt.y], var_map[stmt.z]) 130 | 131 | if isinstance(stmt, StmtAssignSub): 132 | new_var = next(fresh_var) 133 | var_map[stmt.x] = new_var 134 | return StmtAssignSub(new_var, var_map[stmt.y], var_map[stmt.z]) 135 | 136 | if isinstance(stmt, StmtAssignMul): 137 | new_var = next(fresh_var) 138 | var_map[stmt.x] = new_var 139 | return StmtAssignMul(new_var, var_map[stmt.y], var_map[stmt.z]) 140 | 141 | if isinstance(stmt, StmtAssignDiv): 142 | new_var = next(fresh_var) 143 | var_map[stmt.x] = new_var 144 | return StmtAssignDiv(new_var, var_map[stmt.y], var_map[stmt.z]) 145 | 146 | new_stmts = [to_ssa_stmt(stmt) for stmt in func.stmts] 147 | return Function(func.name, func.args, new_stmts, var_map[func.ret]) 148 | 149 | 150 | ############################################### 151 | # Generate Z3 constraints: 152 | def gen_cons_stmt(stmt): 153 | # raise Todo("Exercise 8: generate constraints form TAC statements ") 154 | if isinstance(stmt, StmtAssignVar): 155 | return Const(stmt.x, DeclareSort('S')) == Const(stmt.y, DeclareSort('S')) 156 | 157 | if isinstance(stmt, StmtAssignAdd): 158 | left = Const(stmt.y, DeclareSort('S')) 159 | right = Const(stmt.z, DeclareSort('S')) 160 | return Const(stmt.x, DeclareSort('S')) == z3.Function('f_add', 161 | DeclareSort('S'), 162 | DeclareSort('S'), 163 | DeclareSort('S')).__call__(left, right) 164 | if isinstance(stmt, StmtAssignSub): 165 | left = Const(stmt.y, DeclareSort('S')) 166 | right = Const(stmt.z, DeclareSort('S')) 167 | return Const(stmt.x, DeclareSort('S')) == z3.Function('f_sub', 168 | DeclareSort('S'), 169 | DeclareSort('S'), 170 | DeclareSort('S')).__call__(left, right) 171 | if isinstance(stmt, StmtAssignMul): 172 | left = Const(stmt.y, DeclareSort('S')) 173 | right = Const(stmt.z, DeclareSort('S')) 174 | return Const(stmt.x, DeclareSort('S')) == z3.Function('f_mul', 175 | DeclareSort('S'), 176 | DeclareSort('S'), 177 | DeclareSort('S')).__call__(left, right) 178 | if isinstance(stmt, StmtAssignDiv): 179 | left = Const(stmt.y, DeclareSort('S')) 180 | right = Const(stmt.z, DeclareSort('S')) 181 | return Const(stmt.x, DeclareSort('S')) == z3.Function('f_div', 182 | DeclareSort('S'), 183 | DeclareSort('S'), 184 | DeclareSort('S')).__call__(left, right) 185 | 186 | 187 | def gen_cons_func(func): 188 | # raise Todo("Exercise 8: generate constraints form TAC function ") 189 | return [gen_cons_stmt(stmt) for stmt in func.stmts] 190 | 191 | 192 | ############################################### 193 | # Tests 194 | 195 | test_case = Function('f', 196 | ['s1', 's2', 't1', 't2'], 197 | [StmtAssignAdd('a', 's1', 't1'), 198 | StmtAssignAdd('b', 's2', 't2'), 199 | StmtAssignMul('c', 'a', 'b'), 200 | StmtAssignMul('b', 'c', 's1'), 201 | StmtAssignVar('z', 'b')], 202 | 'z') 203 | 204 | 205 | class TestTac(unittest.TestCase): 206 | ssa = to_ssa_func(test_case) 207 | cons = gen_cons_func(ssa) 208 | 209 | def test_print(self): 210 | res = ("f(s1,s2,t1,t2){\n\ta = s1 + t1;\n\tb = s2 + t2;\n\tc = a * b;\n\t" 211 | "b = c * s1;\n\tz = b;\n\treturn z;\n}\n") 212 | 213 | # f(s1, s2, t1, t2){ 214 | # a = s1 + t1; 215 | # b = s2 + t2; 216 | # c = a * b; 217 | # b = c * s1; 218 | # z = b; 219 | # return z; 220 | # } 221 | print(test_case) 222 | self.assertEqual(str(test_case), res) 223 | 224 | def test_to_ssa(self): 225 | res = ("f(s1,s2,t1,t2){\n\t_tac_f_0 = s1 + t1;\n\t_tac_f_1 = s2 + t2;\n\t_tac_f_2 = _tac_f_0 * _tac_f_1;\n\t" 226 | "_tac_f_3 = _tac_f_2 * s1;\n\t_tac_f_4 = _tac_f_3;\n\treturn _tac_f_4;\n}\n") 227 | 228 | # f(s1, s2, t1, t2){ 229 | # _tac_f_0 = s1 + t1; 230 | # _tac_f_1 = s2 + t2; 231 | # _tac_f_2 = _tac_f_0 * _tac_f_1; 232 | # _tac_f_3 = _tac_f_2 * s1; 233 | # _tac_f_4 = _tac_f_3; 234 | # return _tac_f_4; 235 | # } 236 | 237 | print(self.ssa) 238 | self.assertEqual(str(self.ssa), res) 239 | 240 | def test_gen_cons(self): 241 | res = ("[_tac_f_0 == f_add(s1, t1)," 242 | " _tac_f_1 == f_add(s2, t2)," 243 | " _tac_f_2 == f_mul(_tac_f_0, _tac_f_1)," 244 | " _tac_f_3 == f_mul(_tac_f_2, s1)," 245 | " _tac_f_4 == _tac_f_3]") 246 | print(self.cons) 247 | self.assertEqual(str(self.cons), res) 248 | 249 | 250 | if __name__ == '__main__': 251 | unittest.main() 252 | -------------------------------------------------------------------------------- /assignment7/array_elim.py: -------------------------------------------------------------------------------- 1 | from z3 import * 2 | 3 | 4 | class Todo(Exception): 5 | def __init__(self, msg): 6 | self.msg = msg 7 | 8 | def __str__(self): 9 | return self.msg 10 | 11 | def __repr__(self): 12 | return self.__str__() 13 | 14 | # In this part of the assignment, we'll implement the 15 | # array elimination algorithm, as described in the class. 16 | 17 | 18 | # First, let's define the basic data structures to represent 19 | # the bit vector syntax: 20 | # E ::= c | x | select(E, E) | update(E, E, E) 21 | # R ::= E=E | E!=E 22 | # P ::= R | P/\P 23 | 24 | # we use a functional programming style in this assigment. 25 | 26 | ############################################################### 27 | # Part I: the bit vector syntax. 28 | class Exp: 29 | pass 30 | 31 | 32 | class ExpConst(Exp): 33 | def __init__(self, c: str): 34 | self.c = c 35 | 36 | 37 | class ExpVar(Exp): 38 | def __init__(self, x: str): 39 | self.x = x 40 | 41 | 42 | class ExpSelect(Exp): 43 | def __init__(self, array, index): 44 | self.array = array 45 | self.index = index 46 | 47 | 48 | class ExpUpdate(Exp): 49 | def __init__(self, array, index, element): 50 | self.array = array 51 | self.index = index 52 | self.element = element 53 | 54 | 55 | class ExpCall(Exp): 56 | def __init__(self, f, x): 57 | self.f = f 58 | self.x = x 59 | 60 | 61 | ############################# 62 | # relations, as before 63 | class Relation: 64 | pass 65 | 66 | 67 | class RelationEq(Relation): 68 | def __init__(self, left, right): 69 | self.left = left 70 | self.right = right 71 | 72 | 73 | class RelationNe(Relation): 74 | def __init__(self, left, right): 75 | self.left = left 76 | self.right = right 77 | 78 | 79 | #### 80 | # propositions 81 | class Prop: 82 | pass 83 | 84 | 85 | class PropFalse(Prop): 86 | def __init__(self): 87 | pass 88 | 89 | 90 | class PropTrue(Prop): 91 | def __init__(self): 92 | pass 93 | 94 | 95 | class PropSingle(Prop): 96 | def __init__(self, r): 97 | self.r = r 98 | 99 | 100 | class PropAnd(Prop): 101 | def __init__(self, left: Prop, right: Prop): 102 | self.left = left 103 | self.right = right 104 | 105 | 106 | class PropForall(Prop): 107 | def __init__(self, var: str, prop: Prop): 108 | self.var = var 109 | self.prop = prop 110 | 111 | 112 | # this is only used internally 113 | class PropImply(Prop): 114 | def __init__(self, left: Prop, right: Prop): 115 | self.left = left 116 | self.right = right 117 | 118 | 119 | # printing facilities 120 | def print_atomic(s): 121 | print(s, "", end="") 122 | 123 | 124 | def print_exp(e): 125 | if isinstance(e, ExpConst): 126 | print_atomic(e.c) 127 | elif isinstance(e, ExpVar): 128 | print_atomic(e.x) 129 | elif isinstance(e, ExpSelect): 130 | print_atomic("(") 131 | print_exp(e.array) 132 | print_atomic("[") 133 | print_exp(e.index) 134 | print_atomic("])") 135 | elif isinstance(e, ExpUpdate): 136 | print_atomic("(") 137 | print_exp(e.array) 138 | print_atomic("[") 139 | print_exp(e.index) 140 | print_atomic("] = ") 141 | print_exp(e.element) 142 | print_atomic(")") 143 | elif isinstance(e, ExpCall): 144 | print(e.f) 145 | print_atomic("(") 146 | print_atomic(e.x) 147 | print_atomic(")") 148 | 149 | 150 | def print_relation(r): 151 | if isinstance(r, RelationEq): 152 | print_atomic("(") 153 | print_exp(r.left) 154 | print_atomic("=") 155 | print_exp(r.right) 156 | print_atomic(")") 157 | elif isinstance(r, RelationNe): 158 | print_atomic("(") 159 | print_exp(r.left) 160 | print_atomic("!=") 161 | print_exp(r.right) 162 | print_atomic(")") 163 | 164 | 165 | def print_prop(p): 166 | if isinstance(p, PropFalse): 167 | print_atomic("F") 168 | elif isinstance(p, PropTrue): 169 | print_atomic("T") 170 | elif isinstance(p, PropSingle): 171 | print_relation(p.r) 172 | elif isinstance(p, PropAnd): 173 | print_atomic("(") 174 | print_prop(p.left) 175 | print_atomic("/\\") 176 | print_prop(p.right) 177 | print_atomic(")") 178 | elif isinstance(p, PropImply): 179 | print_prop(p.left) 180 | print_atomic("\n->\n") 181 | print_prop(p.right) 182 | elif isinstance(p, PropForall): 183 | print_atomic("(forall ") 184 | print_atomic(p.var) 185 | print_atomic(".") 186 | print_prop(p.prop) 187 | print_atomic(")") 188 | else: 189 | print(p.__class__) 190 | print_atomic("what???") 191 | exit(999) 192 | 193 | 194 | # select(update(A, i, x), i) == x 195 | def gen_sample_prop(): 196 | return PropSingle(RelationEq(ExpSelect(ExpUpdate(ExpVar("A"), 197 | ExpVar("i"), 198 | ExpConst("x")), 199 | ExpVar("i")), 200 | ExpConst("x"))) 201 | 202 | 203 | ############################################################### 204 | # Part II: the array elimination algorithm. 205 | 206 | # the counter, to generate nice variable names. 207 | counter = 0 208 | 209 | 210 | def fresh_var(): 211 | global counter 212 | name = "x_%d" % counter 213 | counter = counter + 1 214 | return name 215 | 216 | 217 | # newly generated propositions 218 | props = [] 219 | 220 | 221 | # exercise challenge: implement the array elimination algorithm 222 | def elim_update_exp(e): 223 | raise Todo("exercise challenge: please fill in the missing code.") 224 | 225 | 226 | def elim_update_relation(r): 227 | raise Todo("exercise challenge: please fill in the missing code.") 228 | 229 | 230 | def elim_update_prop(p): 231 | raise Todo("exercise challenge: please fill in the missing code.") 232 | 233 | 234 | # this is the first step of pass #3: eliminate "forall" 235 | # we collect all array variables and index variables 236 | array_vars = set() 237 | 238 | 239 | def emit_array(v: ExpVar): 240 | raise Todo("exercise challenge: please fill in the missing code.") 241 | 242 | 243 | index_vars = set() 244 | 245 | 246 | def emit_index(v: ExpVar): 247 | raise Todo("exercise challenge: please fill in the missing code.") 248 | 249 | 250 | def collect_exp(e): 251 | raise Todo("exercise challenge: please fill in the missing code.") 252 | 253 | 254 | def collect_relation(r): 255 | raise Todo("exercise challenge: please fill in the missing code.") 256 | 257 | 258 | def collect_prop(p): 259 | raise Todo("exercise challenge: please fill in the missing code.") 260 | 261 | # pass #3: eliminate forall 262 | # e[x|->y] 263 | def subst_exp(e, x, y): 264 | raise Todo("exercise challenge: please fill in the missing code.") 265 | 266 | 267 | def subst_relation(r, x, y): 268 | raise Todo("exercise challenge: please fill in the missing code.") 269 | 270 | 271 | # p[x|->y] 272 | def subst_prop_0(p, x, y): 273 | raise Todo("exercise challenge: please fill in the missing code.") 274 | 275 | 276 | def subst_prop(p, x: str): 277 | raise Todo("exercise challenge: please fill in the missing code.") 278 | 279 | 280 | def elim_forall_prop_0(p: Prop): 281 | raise Todo("exercise challenge: please fill in the missing code.") 282 | 283 | 284 | def elim_forall_prop(p): 285 | raise Todo("exercise challenge: please fill in the missing code.") 286 | 287 | 288 | # pass #4: eliminate array "select" 289 | def elim_select_exp(e): 290 | raise Todo("exercise challenge: please fill in the missing code.") 291 | 292 | 293 | def elim_select_relation(r): 294 | raise Todo("exercise challenge: please fill in the missing code.") 295 | 296 | 297 | def elim_select_prop(p: Prop): 298 | raise Todo("exercise challenge: please fill in the missing code.") 299 | 300 | 301 | # Input: an array proposition 302 | # output: a EUF proposition 303 | def array_elim(p): 304 | # pass #1: eliminate all array update "A[i] = e" 305 | p = elim_update_prop(p) 306 | print("\nthe proposition after array update elimination:") 307 | print_prop(p) 308 | # pass #2: eliminate "exists" 309 | # pass #3: eliminate "forall" 310 | p = elim_forall_prop(p) 311 | print(array_vars) 312 | print(index_vars) 313 | print_prop(p) 314 | 315 | # pass #4: eliminate array "select" 316 | p = elim_select_prop(p) 317 | print_prop(p) 318 | return p 319 | 320 | 321 | # convert to Z3 format 322 | def to_z3_exp(e): 323 | raise Todo("exercise challenge: please fill in the missing code.") 324 | 325 | 326 | def to_z3_relation(r): 327 | raise Todo("exercise challenge: please fill in the missing code.") 328 | 329 | 330 | def to_z3_prop(p): 331 | raise Todo("exercise challenge: please fill in the missing code.") 332 | 333 | if __name__ == '__main__': 334 | sample_prop = gen_sample_prop() 335 | print("the raw proposition:") 336 | print_prop(sample_prop) 337 | p = array_elim(sample_prop) 338 | 339 | # convert to Z3 friendly format 340 | p = to_z3_prop(p) 341 | print("\n\nthe z3 proposition:") 342 | print(p) 343 | # send constraint to the SAT solver 344 | solver = Solver() 345 | solver.add(Not(p)) 346 | res = solver.check() 347 | if res == sat: 348 | print(solver.model()) 349 | else: 350 | print("\033[31munsat!\033[0m") 351 | -------------------------------------------------------------------------------- /assignment3/exercise1.py: -------------------------------------------------------------------------------- 1 | """Z3 is an SMT solver. 2 | In this lecture, we'll discuss 3 | the basis usage of Z3 through some working example, the 4 | primary goal is to introduce how to use Z3 to solve 5 | the satisfiability problems we've discussed in the past 6 | several lectures. 7 | We must emphasize that Z3 is just one of the many such SMT 8 | solvers, and by introducing Z3, we hope you will have a 9 | general understanding of what such solvers look like, and 10 | what they can do.""" 11 | 12 | 13 | from z3 import * 14 | 15 | 16 | class Todo(Exception): 17 | def __init__(self, msg): 18 | self.msg = msg 19 | print(self.msg) 20 | 21 | def __str__(self): 22 | return self.msg 23 | 24 | def __repr__(self): 25 | return self.__str__() 26 | 27 | 28 | ######################################## 29 | # Basic propositional logic 30 | 31 | # In Z3, we can declare two propositions just as booleans, this 32 | # is rather natural, for propositions can have values true or false. 33 | # To declare two propositions P and Q: 34 | P = Bool('P') 35 | Q = Bool('Q') 36 | # or, we can use a more compact shorthand: 37 | P, Q = Bools('P Q') 38 | 39 | # We can build propositions by writing Lisp-style abstract 40 | # syntax trees, for example, the disjunction: 41 | # P \/ Q 42 | # can be encoded as the following AST: 43 | F = Or(P, Q) 44 | 45 | # Note that the connective '\/' is called 'Or' in Z3, we'll see 46 | # several other in the next. 47 | 48 | # The simplest usage for Z3 is to feed the proposition to Z3 49 | # directly, to check the satisfiability, this can be done by 50 | # calling the # 'solve()' function: 51 | solve(F) 52 | 53 | # Simply speaking, the "solve()" function will create an instance 54 | # of solver, check the satisfiability of the proposition, and 55 | # output a model in which that proposition is satisfiable. 56 | 57 | # For the above call, the Z3 will output something like this: 58 | # [P=True, Q=False] 59 | # which is a model with assignments to proposition P and Q that 60 | # makes the proposition F satisfiable. Obviously, this is just 61 | # one of several possible models. 62 | 63 | # Same as disjunction, the conjunction 64 | # P /\ ~Q 65 | # can be encoded as the following AST 66 | F = And(P, Not(Q)) 67 | 68 | # Note that we use Not(Q) to encode ~Q 69 | 70 | # Then we use solve to find a model that satisfy the proposition 71 | solve(F) 72 | 73 | ########################################################### 74 | # Now you have know the basic usage of using z3 to find the solution that 75 | # will satisfy a proposition. 76 | # 77 | # TODO: Exercise 1-1 78 | # Try to find solution that satisfies proposition: (P /\ Q) \/ R 79 | Todo("Exercise 1-1: try to find solution that satisfies proposition: (P /\\ Q) \\/ R") 80 | P, Q, R = Bools('P Q R') 81 | F = Or(And(P, Q), R) 82 | solve(F) 83 | 84 | # TODO: Exercise 1-2 85 | # Try to find solution that satisfies proposition: P \/ (Q \/ R) 86 | Todo("Exercise 1-2: try to find solution that satisfies proposition: P \\/ (Q \\/ R)") 87 | P, Q, R = Bools('P Q R') 88 | F = Or(P, Or(Q, R)) 89 | solve(F) 90 | 91 | ########################################################### 92 | # In Exercise 1-1 you've see the basic usage of z3 for describing propositions. 93 | # Like And, Or, Not. Also how to use the solve method to get the solution. 94 | 95 | # But keep in mind that not all propositions are satisfiable, consider: 96 | # F = P /\ ~P 97 | P = Bool('P') 98 | F = And(P, Not(P)) 99 | solve(F) 100 | 101 | # Z3 will output: 102 | # "no solution" 103 | # which indicates that the proposition F is not satisfiable, that 104 | # is, the proposition F cannot be true for any possible values 105 | # of P. 106 | 107 | # TODO: Exercise 1-3 108 | # Consider proposition (P \/ Q) /\ (Q /\ R) /\ (P /\ ~R). Complete below code, 109 | # Z3 will show you that no solution can satisfy it. 110 | Todo("Exercise 1-3: try to solve proposition: (P \\/ Q) /\\ (Q /\\ R) /\\ (P /\\ ~R)") 111 | P, Q, R = Bools('P Q R') 112 | F = And(Or(P, Q), And(Q, R), And(P, Not(R))) 113 | solve(F) 114 | 115 | # TODO: Exercise 1-4 116 | # Try to solve proposition 117 | # (P /\ ~S /\ R) /\ (R /\ ( ~P \/ (S /\ ~Q))) 118 | Todo("Exercise 1-4: try to solve proposition: (P /\\ ~S /\\ R) /\\ (R /\\ ( ~P \\/ (S /\\ ~Q)))") 119 | P, Q, R, S = Bools('P Q R S') 120 | F1 = And(P, Not(S), R) 121 | F2 = And(R, Or(Not(P), And(S, Not(Q)))) 122 | F = And(F1, F2) 123 | solve(F) 124 | 125 | ########################################################### 126 | # You may notice that some problems in Exercise 1 has more than one solutions 127 | # that satisfy it, but the solve method only provider you 1 solution. 128 | # To Get another solution that can satisfy it. What you need to do is add 129 | # constraints to solver to get different solutions as below example. 130 | 131 | # Consider again our first example, in exercise 1: 132 | # P \/ Q 133 | # By default, Z3 only outputs the one row in 134 | # the truth table: 135 | """ 136 | P Q P\/Q 137 | t t t 138 | t f t 139 | f t t 140 | f f f 141 | """ 142 | # After all, we're asking Z3 about the satisfiability of the proposition, 143 | # so one row of evidence is enough. 144 | 145 | # What if we want Z3 to output all the assignment of propositions 146 | # that make the proposition satisfiable, not just the first one. 147 | # For the above example, we want the all first 3 rows. 148 | 149 | # Here is the trick: when we get an answer, we negate the 150 | # answer, make a conjunction with the original proposition, 151 | # then check the satisfiability again. For the above example: 152 | P, Q = Bools('P Q') 153 | F = Or(P, Q) 154 | solve(F) 155 | # Z3 outputs: 156 | # [P=True, Q=False] 157 | 158 | # we want to build this: 159 | F = And(F, Not(And(P, Not(Q)))) 160 | solve(F) 161 | # Z3 output this: 162 | # [P = False, Q = True] 163 | 164 | # continue the building process: 165 | F = And(F, Not(And(Not(P), Q))) 166 | solve(F) 167 | # Z3 output this: 168 | # [P = True, Q = True] 169 | 170 | # continue the building process: 171 | F = And(F, Not(And(P, Q))) 172 | solve(F) 173 | # Z3 output this: 174 | # "no solution" 175 | 176 | # In fact, what we Add to F can be treated as constraints, by add negation of 177 | # each solution given by solve method, we can get a new solution which is 178 | # different from solutions we get before. We doing this until no new solution 179 | # can be found, aka. "no solution" by solve, that means we get all solutions. 180 | 181 | 182 | # TODO: Exercise 1-5 183 | # Now you have know how to add constraint to solver to get different solutions 184 | # from z3. Try to get **all solutions** that satisfy the proposition in 185 | # Exercise 1-1: (P /\ Q) \/ R 186 | Todo("Exercise 1-5: try to get all solutions of proposition: (P /\\ Q) \\/ R") 187 | P, Q, R = Bools('P Q R') 188 | F = Or(And(P, Q), R) 189 | solve(F) 190 | F = And(F, Not(And(P, Q, Not(R)))) 191 | solve(F) 192 | F = And(F, Not(And(P, Q, R))) 193 | solve(F) 194 | F = And(F, Not(And(Not(P), Not(Q), R))) 195 | solve(F) 196 | F = And(F, Not(And(P, Not(Q), R))) 197 | solve(F) 198 | F = And(F, Not(And(Not(P), Q, R))) 199 | solve(F) 200 | 201 | # We can automate the above process, for this, we should expose 202 | # more internal capability of Z3. Consider our first example again: 203 | # P /\ Q 204 | 205 | # To create a new SMT solver: 206 | solver = Solver() 207 | 208 | P, Q = Bools('P Q') 209 | F = And(P, Q) 210 | # add the proposition F into the solver: 211 | solver.add(F) 212 | # you can add more propositions into the solver, and the solver 213 | # will try to check the conjunction of these propositions. 214 | 215 | # To check whether or not the current solver is satisfiable: 216 | print(solver.check()) 217 | # meanwhile, also store other results, say, the concrete values 218 | # for the propositions, etc.. 219 | 220 | # To get the model: 221 | print(solver.model()) 222 | # this is effective, only if the solver is "sat". To be specific, 223 | # the model contains all the values for propositions (a map): 224 | model = solver.model() 225 | print(model[P], model[Q]) 226 | 227 | # To wrap these together, we have this code: 228 | solver = Solver() 229 | # to add the proposition F again: 230 | P, Q = Bools('P Q') 231 | props = [P, Q] 232 | F = And(P, Q) 233 | solver.add(F) 234 | print(solver.check()) 235 | print(solver.model()) 236 | 237 | 238 | # TODO: Exercise 1-6 239 | # Now it's your turn, let's wrap all these facility into a nice function: 240 | # Read and understand the code, then complete the lost part. 241 | 242 | def sat_all(props, f): 243 | """Get all solutions of given proposition set props that satisfy f 244 | 245 | Arguments: 246 | props {BoolRef} -- Proposition list 247 | f {Boolref} -- logical express that consist of props 248 | """ 249 | solver = Solver() 250 | solver.add(f) 251 | result = [] 252 | while solver.check() == sat: 253 | m = solver.model() 254 | result.append(m) 255 | block = [] 256 | for prop in props: 257 | prop_is_true = m.eval(prop, model_completion=True) 258 | 259 | if prop_is_true: 260 | new_prop = prop 261 | else: 262 | new_prop = Not(prop) 263 | 264 | block.append(new_prop) 265 | 266 | # raise Todo("Exercise 1-6: try to complete the lost part in the function of `set_all`") 267 | # print(block) 268 | solver.add(Not(And(block))) 269 | 270 | print("the given proposition: ", f) 271 | print("the number of solutions: ", len(result)) 272 | 273 | def print_model(m): 274 | print(sorted([(d, m[d]) for d in m], key=lambda x: str(x[0]))) 275 | 276 | for m in result: 277 | print_model(m) 278 | 279 | 280 | # If you complete the function. Try to use it for below props. 281 | sat_all([P, Q], Or(P, Q)) 282 | sat_all([P], Implies(P, P)) 283 | R = Bool('R') 284 | sat_all([P, Q, R], Or(P, Q, R)) 285 | # Well done, you've complete exercise 1. Remember to save it for later hands in. 286 | -------------------------------------------------------------------------------- /assignment8/symbolic.py: -------------------------------------------------------------------------------- 1 | import multiprocessing as mp 2 | from dataclasses import dataclass 3 | from typing import Dict 4 | 5 | from z3 import * 6 | from mini_py import * 7 | 8 | 9 | class Todo(Exception): 10 | def __init__(self, msg): 11 | self.msg = msg 12 | 13 | def __str__(self): 14 | return self.msg 15 | 16 | def __repr__(self): 17 | return self.__str__() 18 | 19 | # a symbolic execution engine. 20 | 21 | 22 | # exercise 5: Run the code below, which is the data model 23 | # of symbolic memory. Symbolic execution memory model will store arguments, 24 | # symbolic values and path condition and concrete values. 25 | # Make sure you understand the model and you don't need to write any code here. 26 | 27 | # symbolic execution memory model will store arguments, symbolic values and path condition 28 | @dataclass 29 | class Memory: 30 | args: List[str] 31 | symbolic_memory: Dict[str, Expr] 32 | path_condition: List[Expr] 33 | 34 | def __str__(self): 35 | arg_str = ",".join(self.args) 36 | expr_str = "\n".join([f"\t{var} = {value}" for var, value in self.symbolic_memory.items()]) 37 | cond_str = ",".join([str(cond) for cond in self.path_condition]) 38 | return (f"Arguments: {arg_str}\n" 39 | f"Path Condition: {cond_str}\n" 40 | f"Symbol Table: \n" 41 | f"{expr_str}\n") 42 | 43 | 44 | ##################### 45 | # symbolic execution 46 | def symbolic_expr(memory, expr): 47 | def find(expr, var): 48 | if not isinstance(expr, ExprBop): 49 | return False 50 | if str(expr.left) == str(var) or str(expr.right) == str(var): 51 | return True 52 | return find(expr.left, var) or find(expr.right, var) 53 | 54 | if isinstance(expr, ExprNum): 55 | return expr 56 | if isinstance(expr, ExprVar): 57 | 58 | # exercise 6: get variable's symbolic value from symbolic memory 59 | # be careful when deal with parameter variables. the result should 60 | # only contain parameters or constants 61 | # 62 | # Your code here: 63 | 64 | # raise Todo("exercise 6: please fill in the missing code.") 65 | expr_value = memory.symbolic_memory[expr.var] 66 | if isinstance(expr_value, ExprVar): 67 | return expr_value 68 | elif isinstance(expr_value, ExprNum): 69 | return symbolic_expr(memory, expr_value) 70 | elif find(expr_value, expr): 71 | return ExprBop(expr_value.left, expr_value.right, expr_value.bop) 72 | return ExprBop(symbolic_expr(memory, expr_value.left), symbolic_expr(memory, expr_value.right), expr_value.bop) 73 | 74 | if isinstance(expr, ExprBop): 75 | left = symbolic_expr(memory, expr.left) 76 | right = symbolic_expr(memory, expr.right) 77 | return ExprBop(left, right, expr.bop) 78 | 79 | 80 | def symbolic_stmt(memory, stmt, rest_stmts, results): 81 | if isinstance(stmt, StmtAssign): 82 | memory.symbolic_memory[stmt.var] = symbolic_expr(memory, stmt.expr) 83 | return symbolic_stmts(memory, rest_stmts, results) 84 | 85 | if isinstance(stmt, StmtIf): 86 | # exercise 6: process the if-statement by split the symbolic memory, 87 | # use the python multiprocessing module to do this work. the target function 88 | # wil be the symbolic_stmts, read it carefully. 89 | # 90 | # Your code here: 91 | 92 | # raise Todo("exercise 6: please fill in the missing code.") 93 | rest_stmts_then = [i for i in stmt.then_stmts] 94 | rest_stmts_else = [i for i in stmt.else_stmts] 95 | for i in rest_stmts: 96 | rest_stmts_then.append(i) 97 | rest_stmts_else.append(i) 98 | p1 = mp.Process(target=symbolic_stmts, args=(memory, rest_stmts_then, results, stmt.expr)) 99 | p2 = mp.Process(target=symbolic_stmts, args=(memory, rest_stmts_else, results, neg_exp(stmt.expr))) 100 | p1.start() 101 | p2.start() 102 | p1.join() 103 | p2.join() 104 | 105 | 106 | def symbolic_stmts(memory, stmts, results, condition=None): 107 | if condition: 108 | memory.path_condition.append(symbolic_expr(memory, condition)) 109 | 110 | if not stmts: 111 | results.put(memory) 112 | else: 113 | symbolic_stmt(memory, stmts[0], stmts[1:], results) 114 | 115 | return results 116 | 117 | 118 | def symbolic_function(func): 119 | # init memory 120 | init_params = [ExprVar(arg) for arg in func.args] 121 | memory = Memory(func.args, dict(zip(func.args, init_params)), []) 122 | 123 | results = symbolic_stmts(memory, func.stmts, mp.SimpleQueue()) 124 | result_list = [] 125 | 126 | while not results.empty(): 127 | result = results.get() 128 | print(result) 129 | result_list.append(result) 130 | 131 | return result_list 132 | 133 | 134 | ##################### 135 | # compile AST expression to Z3 136 | def expr_2_z3(expr): 137 | # exercise 7: converts path conditions (AST nodes) to equivalent 138 | # Z3 constraints. it will used by check_cond function which you 139 | # need to read. 140 | # 141 | # Your code here: 142 | 143 | # raise Todo("exercise 7: please fill in the missing code.") 144 | if isinstance(expr, ExprNum): 145 | return expr.num 146 | if isinstance(expr, ExprVar): 147 | return Int(expr.var) 148 | if isinstance(expr, ExprBop): 149 | if expr.bop == Bop.ADD: 150 | return expr_2_z3(expr.left) + expr_2_z3(expr.right) 151 | elif expr.bop == Bop.MIN: 152 | return expr_2_z3(expr.left) - expr_2_z3(expr.right) 153 | elif expr.bop == Bop.MUL: 154 | return expr_2_z3(expr.left) * expr_2_z3(expr.right) 155 | elif expr.bop == Bop.DIV: 156 | return expr_2_z3(expr.left) / expr_2_z3(expr.right) 157 | elif expr.bop == Bop.EQ: 158 | return expr_2_z3(expr.left) == expr_2_z3(expr.right) 159 | elif expr.bop == Bop.NE: 160 | return expr_2_z3(expr.left) != expr_2_z3(expr.right) 161 | elif expr.bop == Bop.GT: 162 | return expr_2_z3(expr.left) > expr_2_z3(expr.right) 163 | elif expr.bop == Bop.GE: 164 | return expr_2_z3(expr.left) >= expr_2_z3(expr.right) 165 | elif expr.bop == Bop.LT: 166 | return expr_2_z3(expr.left) < expr_2_z3(expr.right) 167 | elif expr.bop == Bop.LE: 168 | return expr_2_z3(expr.left) <= expr_2_z3(expr.right) 169 | 170 | 171 | # negate the condition 172 | def neg_exp(expr: Expr): 173 | assert isinstance(expr, ExprBop), "negate the bop expression" 174 | if expr.bop is Bop.EQ: 175 | return ExprBop(expr.left, expr.right, Bop.NE) 176 | if expr.bop is Bop.NE: 177 | return ExprBop(expr.left, expr.right, Bop.EQ) 178 | if expr.bop is Bop.GT: 179 | return ExprBop(expr.left, expr.right, Bop.LE) 180 | if expr.bop is Bop.GE: 181 | return ExprBop(expr.left, expr.right, Bop.LT) 182 | if expr.bop is Bop.LT: 183 | return ExprBop(expr.left, expr.right, Bop.GE) 184 | if expr.bop is Bop.LE: 185 | return ExprBop(expr.left, expr.right, Bop.GT) 186 | 187 | 188 | # use Z3 to solve conditions 189 | def check_cond(memory, add_cond=None): 190 | solver = Solver() 191 | 192 | # add path condition 193 | for cond in memory.path_condition: 194 | solver.add(expr_2_z3(cond)) 195 | 196 | # add additional condition 197 | if add_cond: 198 | for cond in add_cond: 199 | solver.add(expr_2_z3(symbolic_expr(memory, cond))) 200 | 201 | check_result = solver.check() 202 | 203 | return check_result, solver 204 | 205 | 206 | ############################### 207 | # test function: 208 | # 209 | # def f2(a,b): 210 | # x = 1 211 | # y = 0 212 | # if a != 0 : 213 | # y = x + 3 214 | # if b == 0 : 215 | # x = 2 * a + b 216 | # return x 217 | # 218 | f1 = Function('f1', ['a', "b"], 219 | [StmtAssign('x', ExprNum(1)), 220 | StmtAssign('y', ExprNum(0)), 221 | StmtIf(ExprBop(ExprVar('a'), ExprNum(0), Bop.NE), 222 | [StmtAssign('y', ExprBop(ExprVar('x'), ExprNum(3), Bop.ADD)), 223 | StmtIf(ExprBop(ExprVar('b'), ExprNum(0), Bop.EQ), 224 | [StmtAssign('x', ExprBop(ExprNum(2), ExprBop(ExprVar('a'), ExprVar('b'), Bop.ADD), Bop.MUL))], 225 | [])], 226 | []) 227 | ], ExprVar('x')) 228 | 229 | 230 | if __name__ == '__main__': 231 | example_memory = Memory(args=["a", "b", "c"], 232 | symbolic_memory={"a": ExprVar("a"), 233 | "b": ExprBop(ExprNum(42), ExprVar("a"), Bop.MIN), 234 | "c": ExprBop(ExprVar("c"), ExprNum(20), Bop.ADD), 235 | "m": ExprBop(ExprVar("b"), ExprNum(5), Bop.MUL), 236 | "n": ExprBop(ExprVar("c"), ExprNum(2), Bop.MUL)}, 237 | path_condition=[]) 238 | 239 | example_exp = ExprBop(ExprVar("m"), ExprVar("n"), Bop.EQ) 240 | 241 | # Should output: 242 | # 243 | # [m == n] ===> [((42 - a) * 5) == ((c + 20) * 2)] 244 | # 245 | print(f"[{example_exp}] ===> [{symbolic_expr(example_memory, example_exp)}]\n") 246 | 247 | # Should output(order can be different): 248 | # 249 | # Arguments: a,b 250 | # Path Condition: a == 0 251 | # Symbol Table: 252 | # a = a 253 | # b = b 254 | # x = 1 255 | # y = 0 256 | # 257 | # Arguments: a,b 258 | # Path Condition: a != 0, b != 0 259 | # Symbol Table: 260 | # a = a 261 | # b = b 262 | # x = 1 263 | # y = 1 + 3 264 | # 265 | # Arguments: a,b 266 | # Path Condition: a != 0, b == 0 267 | # Symbol Table: 268 | # a = a 269 | # b = b 270 | # x = 2 * (a + b) 271 | # y = 1 + 3 272 | # 273 | result_memories = symbolic_function(f1) 274 | 275 | # Should output: 276 | # 277 | # Conditions: [a != 0, b == 0, 2*(a + b) - 4 == 0] 278 | # SAT Input: a = 2, b = 0 279 | # 280 | 281 | # check: x - y = 0 282 | check_conditions = [ExprBop(ExprBop(ExprVar('x'), ExprVar('y'), Bop.MIN), ExprNum(0), Bop.EQ)] 283 | 284 | for result_memory in result_memories: 285 | ret, s = check_cond(result_memory, check_conditions) 286 | if ret == sat: 287 | print(f"Conditions: {s}") 288 | print(f"SAT Input: {s.model()}") 289 | -------------------------------------------------------------------------------- /assignment7/pointer.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from z3 import * 4 | 5 | 6 | class Todo(Exception): 7 | def __init__(self, msg): 8 | self.msg = msg 9 | 10 | def __str__(self): 11 | return self.msg 12 | 13 | def __repr__(self): 14 | return self.__str__() 15 | 16 | 17 | # uninterrupted functions 18 | S = Function("S", IntSort(), IntSort()) 19 | H = Function("H", IntSort(), IntSort()) 20 | 21 | 22 | # syntax of pointers 23 | # 24 | # T ::= x | T + E | &x | &*T | *T | NULL 25 | # E ::= x | n | E + E | E - E | *T 26 | # R ::= T = T | T < T | E = E | E < E 27 | # P ::= R | ~R | P ∧ P 28 | # 29 | # 30 | # Term based 31 | class Term: 32 | def __repr__(self): 33 | return self.__str__() 34 | 35 | 36 | # Expression based 37 | class Expr: 38 | def __repr__(self): 39 | return self.__str__() 40 | 41 | 42 | # Terms 43 | class TVar(Term): 44 | def __init__(self, name: str): 45 | self.name = name 46 | 47 | def __str__(self): 48 | return self.name 49 | 50 | 51 | class TAddE(Term): 52 | def __init__(self, term: Term, expr: Expr): 53 | self.term = term 54 | self.expr = expr 55 | 56 | def __str__(self): 57 | return f"{self.term} + ({self.expr})" 58 | 59 | 60 | class TAddr(Term): 61 | def __init__(self, name: str): 62 | self.name = name 63 | 64 | def __str__(self): 65 | return f"&{self.name}" 66 | 67 | 68 | class TAddrStar(Term): 69 | def __init__(self, term: Term): 70 | self.term = term 71 | 72 | def __str__(self): 73 | return f"&*{self.term}" 74 | 75 | 76 | class TStar(Term): 77 | def __init__(self, term: Term): 78 | self.term = term 79 | 80 | def __str__(self): 81 | return f"*{self.term}" 82 | 83 | 84 | class TNull(Term): 85 | def __str__(self): 86 | return "NULL" 87 | 88 | 89 | # Expressions 90 | class EVar(Expr): 91 | def __init__(self, name): 92 | self.name = name 93 | 94 | def __str__(self): 95 | return self.name 96 | 97 | 98 | class EConst(Expr): 99 | def __init__(self, value): 100 | self.value = value 101 | 102 | def __str__(self): 103 | return f"{self.value}" 104 | 105 | 106 | class EAdd(Expr): 107 | def __init__(self, left: Expr, right: Expr): 108 | self.left = left 109 | self.right = right 110 | 111 | def __str__(self): 112 | return f"({self.left} + {self.right})" 113 | 114 | 115 | class EMinus(Expr): 116 | def __init__(self, left: Expr, right: Expr): 117 | self.left = left 118 | self.right = right 119 | 120 | def __str__(self): 121 | return f"({self.left} - {self.right})" 122 | 123 | 124 | class EStar(Expr): 125 | def __init__(self, term: Term): 126 | self.term = term 127 | 128 | def __str__(self): 129 | return f"*{self.term}" 130 | 131 | 132 | # Relations 133 | class Relation: 134 | def __repr__(self): 135 | return self.__str__() 136 | 137 | 138 | class RTEq(Relation): 139 | def __init__(self, left: Term, right: Term): 140 | self.left = left 141 | self.right = right 142 | 143 | def __str__(self): 144 | return f"({self.left} = {self.right})" 145 | 146 | 147 | class RTLe(Relation): 148 | def __init__(self, left: Term, right: Term): 149 | self.left = left 150 | self.right = right 151 | 152 | def __str__(self): 153 | return f"({self.left} < {self.right})" 154 | 155 | 156 | class REEq(Relation): 157 | def __init__(self, left: Expr, right: Expr): 158 | self.left = left 159 | self.right = right 160 | 161 | def __str__(self): 162 | return f"({self.left} = {self.right})" 163 | 164 | 165 | class RELe(Relation): 166 | def __init__(self, left: Expr, right: Expr): 167 | self.left = left 168 | self.right = right 169 | 170 | def __str__(self): 171 | return f"({self.left} < {self.right})" 172 | 173 | 174 | # Formula 175 | class Prop: 176 | def __repr__(self): 177 | return self.__str__() 178 | 179 | 180 | class PRel(Prop): 181 | def __init__(self, rel: Relation): 182 | self.rel = rel 183 | 184 | def __str__(self): 185 | return f"{self.rel}" 186 | 187 | 188 | class PNot(Prop): 189 | def __init__(self, rel: Relation): 190 | self.rel = rel 191 | 192 | def __str__(self): 193 | return f"~{self.rel}" 194 | 195 | 196 | class PAnd(Prop): 197 | def __init__(self, left: Prop, right: Prop): 198 | self.left = left 199 | self.right = right 200 | 201 | def __str__(self): 202 | return f"({self.left} /\\ {self.right})" 203 | 204 | 205 | def count_stars(prop: Prop): 206 | # exercise 17: finish the missing code in `count_stars()` methods, 207 | # make it can count amount of star symbols (*) in our pointer logic . 208 | 209 | def term_count_stars(term: Term): 210 | # your code here 211 | # raise Todo("exercise 17: please fill in the missing code.") 212 | if isinstance(term, TStar) or isinstance(term, TAddrStar): 213 | return 1 + term_count_stars(term.term) 214 | if isinstance(term, TAddE): 215 | return term_count_stars(term.term) + expr_count_stars(term.expr) 216 | return 0 217 | 218 | def expr_count_stars(expr: Expr): 219 | # your code here 220 | # raise Todo("exercise 17: please fill in the missing code.") 221 | if isinstance(expr, EAdd) or isinstance(expr, EMinus): 222 | return expr_count_stars(expr.left) + expr_count_stars(expr.right) 223 | if isinstance(expr, EStar): 224 | return 1 + term_count_stars(expr.term) 225 | return 0 226 | 227 | def rel_count_stars(rel: Relation): 228 | # your code here 229 | # raise Todo("exercise 17: please fill in the missing code.") 230 | if isinstance(rel, RTEq) or isinstance(rel, RTLe): 231 | return term_count_stars(rel.left) + term_count_stars(rel.right) 232 | if isinstance(rel, REEq) or isinstance(rel, RELe): 233 | return expr_count_stars(rel.left) + expr_count_stars(rel.right) 234 | 235 | if isinstance(prop, PRel) or isinstance(prop, PNot): 236 | return rel_count_stars(prop.rel) 237 | if isinstance(prop, PAnd): 238 | return count_stars(prop.left) + count_stars(prop.right) 239 | 240 | 241 | def to_z3(prop: Prop): 242 | # exercise 18: finish the missing code in `to_z3()` methods, 243 | # make it can translates pointer logic to z3's constraints. 244 | 245 | def term_to_z3(term: Term): 246 | # rules to eliminate a pointer T 247 | # 248 | # ⟦x⟧ = H(S(x)) 249 | # ⟦T + E⟧ = ⟦T⟧ + ⟦E⟧ 250 | # ⟦&x⟧ = S(x) 251 | # ⟦&*T⟧ = ⟦T⟧ 252 | # ⟦*T⟧ = H(⟦T⟧) 253 | # ⟦NULL⟧ = 0 254 | # 255 | # your code here 256 | # raise Todo("exercise 18: please fill in the missing code.") 257 | if isinstance(term, TVar): 258 | return H(S(Int(term.name))) 259 | if isinstance(term, TAddE): 260 | return term_to_z3(term.term) + expr_to_z3(term.expr) 261 | if isinstance(term, TAddr): 262 | return S(Int(term.name)) 263 | if isinstance(term, TAddrStar): 264 | return term_to_z3(term.term) 265 | if isinstance(term, TStar): 266 | return H(term_to_z3(term.term)) 267 | if isinstance(term, TNull): 268 | return 0 269 | 270 | def expr_to_z3(expr: Expr): 271 | # rules to eliminate an expression E 272 | # 273 | # ⟦n⟧ = n 274 | # ⟦x⟧ = H(S(x)) 275 | # ⟦E + E⟧ = ⟦E⟧ + ⟦E⟧ 276 | # ⟦E − E⟧ = ⟦E⟧ − ⟦E⟧ 277 | # ⟦*T⟧ = H(⟦T⟧) 278 | # 279 | # your code here 280 | # raise Todo("exercise 18: please fill in the missing code.") 281 | if isinstance(expr, EAdd): 282 | return expr_to_z3(expr.left) + expr_to_z3(expr.right) 283 | if isinstance(expr, EMinus): 284 | return expr_to_z3(expr.left) - expr_to_z3(expr.right) 285 | if isinstance(expr, EStar): 286 | return H(term_to_z3(expr.term)) 287 | if isinstance(expr, EConst): 288 | return expr.value 289 | if isinstance(expr, EVar): 290 | return H(S(Int(expr.name))) 291 | 292 | def relation_to_z3(rel: Relation): 293 | # rules to eliminate a relation R 294 | # 295 | # ⟦T = T⟧ = ⟦T⟧ = ⟦T⟧ 296 | # ⟦T < T⟧ = ⟦T⟧ < ⟦T⟧ 297 | # ⟦E = E⟧ = ⟦E⟧ = ⟦E⟧ 298 | # ⟦E < E⟧ = ⟦E⟧ < ⟦E⟧ 299 | # 300 | # your code here 301 | # raise Todo("exercise 18: please fill in the missing code.") 302 | if isinstance(rel, RTEq): 303 | return term_to_z3(rel.left) == term_to_z3(rel.right) 304 | if isinstance(rel, RTLe): 305 | return term_to_z3(rel.left) < term_to_z3(rel.right) 306 | if isinstance(rel, REEq): 307 | return expr_to_z3(rel.left) == expr_to_z3(rel.right) 308 | if isinstance(rel, RELe): 309 | return expr_to_z3(rel.left) < expr_to_z3(rel.right) 310 | 311 | # rules to eliminate a proposition P 312 | # 313 | # ⟦R⟧ = ⟦R⟧ 314 | # ⟦~R⟧ = ~⟦R⟧ 315 | # ⟦P /\Q⟧ = ⟦P⟧ / \ ⟦Q⟧ 316 | # 317 | if isinstance(prop, PRel): 318 | return relation_to_z3(prop.rel) 319 | if isinstance(prop, PNot): 320 | return Not(relation_to_z3(prop.rel)) 321 | if isinstance(prop, PAnd): 322 | return And(to_z3(prop.left), to_z3(prop.right)) 323 | 324 | 325 | ###################### 326 | # unit test 327 | p1 = PAnd(PRel(RTEq(TVar("p"), 328 | TAddr("q")) 329 | ), 330 | PRel(REEq(EVar("q"), 331 | EConst(1)) 332 | ) 333 | ) 334 | 335 | p2 = PRel(REEq(EStar(TVar("p")), EConst(1))) 336 | 337 | 338 | p3 = PAnd(PRel(RTEq(TStar(TAddrStar(TVar("p"))), 339 | TAddrStar(TStar(TVar("q")))) 340 | ), 341 | PRel(REEq(EStar(TStar(TStar(TVar("p")))), 342 | EStar(TAddrStar(TAddE(TStar(TVar("q")), EConst(1))))) 343 | ) 344 | ) 345 | 346 | # ((p = &q) /\ (q = 1)) -> (*p = 1) 347 | print(f"{p1} -> {p2}") 348 | 349 | # ((*&*p = &**q) /\ (***p = *&**q + (1))) 350 | print(p3) 351 | 352 | 353 | class TestPointer(unittest.TestCase): 354 | def test_count_starts(self): 355 | self.assertEqual((count_stars(p1)), 0) 356 | self.assertEqual((count_stars(p2)), 1) 357 | self.assertEqual((count_stars(p3)), 10) 358 | 359 | def test_to_z3(self): 360 | p = Implies(to_z3(p1), to_z3(p2)) 361 | self.assertEqual(str(p), "Implies(And(H(S(p)) == S(q), H(S(q)) == 1), H(H(S(p))) == 1)") 362 | 363 | solver = Solver() 364 | solver.add(Not(p)) 365 | self.assertEqual(solver.check(), unsat) 366 | 367 | 368 | if __name__ == '__main__': 369 | unittest.main() 370 | -------------------------------------------------------------------------------- /assignment6/la-theory.py: -------------------------------------------------------------------------------- 1 | from z3 import * 2 | 3 | 4 | class Todo(Exception): 5 | def __init__(self, msg): 6 | self.msg = msg 7 | 8 | def __str__(self): 9 | return self.msg 10 | 11 | def __repr__(self): 12 | return self.__str__() 13 | 14 | 15 | ######################################### 16 | # Introduction 17 | 18 | # Welcome to this assignment: the linear arithmetic (LA) and linear programming (LP). 19 | # In this assignment, you'll be getting familiarized yourself with basic linear 20 | # arithmetic and linear programming, by using the Z3 theorem prover/solver. 21 | 22 | # This assignment consists of three parts: 23 | # 1. the basic LA and LP theories; 24 | # 2. the important applications of the LA and LP theories; 25 | # 3. the algorithms behind the modern LA/LP theories. 26 | # 27 | # this is the first one. 28 | 29 | 30 | ######################################### 31 | # Basic Linear Arithmetics 32 | 33 | # Roughly speaking, Z3 can solve a group of equalities/inequalities, suppose 34 | # we can have two numbers x and y on R domain, we can declare them in Z3 as: 35 | x, y = Reals('x y') 36 | # here, "Reals" is a class, representing the R domain. 37 | # Now, suppose we want to check that whether or not the following constraints (a group of 38 | # equalities) are satisfiable: 39 | # { x + y = 0.8 40 | # { x - y = 0.2 41 | # then we can create a new solver: 42 | solver = Solver() 43 | # and add these constraints into this "solver": 44 | solver.add(x + y == 0.8, x - y == 0.2) 45 | # please note that: comparing with the above mathematical notation, there is only 46 | # one minor difference, in math, we use the symbol "=", but in Z3, we use "==". 47 | # We then drive Z3 to check these two constraints: 48 | res = solver.check() 49 | # and if Z3 return "sat" on these constraints, we can print the concrete values 50 | # for variables "x" and "y". 51 | print("result for problem 1:") 52 | if res == sat: 53 | model = solver.model() 54 | print(model) 55 | else: 56 | print(res) 57 | 58 | # exercise 1: run the above code and check the result, is the result correct? 59 | # you don't need to write any code, just make sure you understand the output. 60 | 61 | 62 | # Of course, not all constraints are satisfiable, given two variables "x" and "y" on 63 | # domain R, consider the following constraints: 64 | # { x + y = 0.8 65 | # { x + y = 0.2 66 | # obviously, these constraints are unsatisfiable, and we let Z3 to check this fact: 67 | x, y = Reals('x y') 68 | solver.add(x + y == 0.8, x + y == 0.2) 69 | res = solver.check() 70 | print("result for problem 2:") 71 | if res == sat: 72 | model = solver.model() 73 | print(model) 74 | else: 75 | print(res) 76 | # exercise 2: For the above constraints, please write down code that creating the 77 | # solver and checking these constraints. Make sure your code outputs "unsat". 78 | # raise Todo("exercise 2: please fill in the missing code.") 79 | 80 | 81 | # Besides the domain real ("Reals"), Z3 also has decent support for the 82 | # Integer domain. For instance, this code 83 | x, y = Ints('x y') 84 | solver = Solver() 85 | solver.add(x + y == 8, x - y == 2) 86 | res = solver.check() 87 | print("result for problem 3:") 88 | if res == sat: 89 | model = solver.model() 90 | print(model) 91 | else: 92 | print(res) 93 | # declares two integers "x" and "y". 94 | # Similarly, we can ask Z3 to check constraints on Integers, for instance: 95 | # { x + y = 8 96 | # { x - y = 2 97 | # exercise 3: please write code to check the above constraints, make sure 98 | # your code should output some like "[x = 5, y = 3]'. 99 | # raise Todo("exercise 3: please fill in the missing code.") 100 | 101 | 102 | # To this point, as you can observe, the code fragment we already wrote 103 | # has many duplications, so it's a good point to clean them by refactoring 104 | # the common part. 105 | # For this purpose, we write the following function "check_cons()", which 106 | # takes a list of constraints, and output the checking result. 107 | def check_cons(constraints): 108 | solver = Solver() 109 | solver.add(constraints) 110 | res = solver.check() 111 | if res == sat: 112 | model = solver.model() 113 | # print(model) 114 | return res, model 115 | else: 116 | # print(res) 117 | return res, None 118 | 119 | 120 | def print_model(res, model): 121 | if res == sat: 122 | print(model) 123 | return 124 | print(res) 125 | return 126 | 127 | 128 | # thus, we can simplify our previous implementation by just calling 129 | # this function, as in: 130 | x, y = Ints('x y') 131 | res, model = check_cons([x + y == 20, x - y == 10]) 132 | print_model(res, model) 133 | 134 | # One key point to note here is that: when we talk about the satisfiability of 135 | # constraints, we must make it clear what domains (R, Z, or Q?) we are 136 | # working on, because the satisfiability of constraints is dependent on 137 | # the underlying domain. 138 | # To understand this, let's consider the following example, given these 139 | # constraints: 140 | # { x + y = 8 141 | # { x - y = 1 142 | # we can conclude that these constraints are satisfiable on domain R, but not 143 | # on domain Z. 144 | # exercise 4: write code to check the above constraints are satisfiable 145 | # domain R, but not on domain Z. You should finish your code, by calling 146 | # the above "check_cons()" function. 147 | x, y = Reals('x, y') 148 | cons = [x + y == 8, x - y == 1] 149 | # raise Todo("exercise 4: please fill in the missing code.") 150 | print('result for problem 4:') 151 | res, model = check_cons(cons) 152 | print_model(res, model) 153 | 154 | ######################################### 155 | # 0-1 Linear Arithmetic 156 | 157 | # There is one important special case for the LA on domain Z, which 158 | # is called the 0-1 integer linear arithmetics. In this case, all 159 | # integer variables can only be of two values: 0 or 1, in this sense, 160 | # they corresponds, perfectly, to boolean values (true or false). 161 | # Let's study one concrete example, given the following constraints 162 | # on integers Z: 163 | x, y = Ints('x, y') 164 | cons = [x + y == 8, x - y == 2] 165 | # but we also require that both "x" and "y" can only be "0" or "1", 166 | # this is given by the following two constraints: 167 | cons_x = [Or(x == 1, x == 0)] 168 | cons_y = [Or(y == 1, y == 0)] 169 | # thus, we only need to check the union of these constraints, as in: 170 | print("0-1 la result:") 171 | res, model = check_cons(cons + cons_x + cons_y) 172 | print_model(res, model) 173 | 174 | # The 0-1 Integer LA (and also the 0-1 Integer LP) has many important 175 | # applications, we'll discuss some in the future. But for now, let's 176 | # consider one interesting problem, to let us appreciate its simplicity 177 | # and power: suppose we are given a list of integers, and we want to 178 | # write a program, to check that whether or not there is at least one 179 | # zero (0) in this list. 180 | # Here are some sample inputs and the expected outputs: 181 | # Input: Output: 182 | l1 = [1, 2, 4, 5] # false 183 | l2 = [3, 0, 8, 2] # true 184 | l3 = [4, 0, 3, 0] # true 185 | 186 | 187 | # First, let's warm up, by writing a normal version: 188 | def check_zero_normal(l): 189 | for x in l: 190 | if x == 0: 191 | return True 192 | return False 193 | 194 | 195 | # unit test the above function: 196 | assert (not check_zero_normal(l1)) 197 | assert (check_zero_normal(l2)) 198 | assert (check_zero_normal(l3)) 199 | 200 | 201 | # Now, let's consider how to solve this problem using 0-1 integer LA, 202 | # for this, let's first declare a list of auxiliary 0-1 integer variables 203 | # [x_1, x_2, ..., x_n] 204 | # which satisfy: 205 | # x_1 + x_2 + ... + x_n = 1 206 | # that is, there is just one 1 in this list. 207 | # For the given input integer list (which is being searched for 0s): 208 | # [e_1, e_2, ..., e_n] 209 | # we require that: 210 | # x_1*e_1 + x_2*e_2 + ... + x_n*e_n = 0 211 | # exercise 5: why the above constraints can guarantee that there is 212 | # one 0 in the input list of integers? 213 | 214 | # Let's continue to turn the above constraints into code; 215 | def check_zero_la(l): 216 | # create a list of auxiliary variables: 217 | # [x_0, ..., x_{n-1}] 218 | vars = [Int('x_%d' % i) for i in range(len(l))] 219 | # create the first group of constraints: 220 | # all "vars" are 0-1 integers: 221 | cons_0_or_1 = [] 222 | for i in range(len(vars)): 223 | cons_0_or_1.append(Or(vars[i] == 0, vars[i] == 1)) 224 | # create the second group of constraints: 225 | # x_1 + x2 + ... + xn = 1 226 | cons_sum = [sum(vars) == 1] 227 | # create the second group of constraints: 228 | # x_1*e_1 + x_2*e_2 + ... + x_n*e_n = 0 229 | cons_exp = [] 230 | # exercise 6: fill in the missing code to generate the above constraint, 231 | # and store it in the "cons_exp" variable. 232 | # Make sure your code passes all the following unit test. 233 | # raise Todo("exercise 6: please fill in the missing code.") 234 | i = 0 235 | con_e = [] 236 | for e in l: 237 | con_e.append(vars[i] * e) 238 | i = i + 1 239 | 240 | cons_exp = [sum(con_e) == 0] 241 | # check these constraints: 242 | res, model = check_cons(cons_0_or_1 + cons_sum + cons_exp) 243 | if res == sat: 244 | print(model) 245 | return True 246 | return False 247 | 248 | 249 | # unit test the above function: 250 | assert (not check_zero_la(l1)) 251 | assert (check_zero_la(l2)) 252 | assert (check_zero_la(l3)) 253 | 254 | # challenge: please write a program to count the number of 0s in 255 | # a given list of integers. 256 | # Sample inputs and outputs: 257 | # Input: Output: 258 | l1 = [1, 2, 4, 5] # 0 259 | l2 = [3, 0, 8, 2] # 1 260 | l3 = [4, 0, 3, 0] # 2 261 | 262 | ######################################### 263 | # None-Linear arithmetic. 264 | 265 | # In previous sections, we've always been talking about the linear 266 | # arithmetics, but Z3 also support the satisfiability solving on 267 | # non-linear constraints. 268 | # Suppose we want to check the following constraints: 269 | # x * x = 2 270 | # we can write (as we can expect): 271 | x = Real('x') 272 | res, model = check_cons([x * x == 2]) 273 | if res == sat: 274 | print(model) 275 | else: 276 | print(res) 277 | 278 | # exercise 7: write some code to check that this non-linear constraint, 279 | # on two real numbers x and y, is unsat: 280 | # x*x + y*y < 0 281 | # raise Todo("exercise 7: please fill in the missing code.") 282 | x, y = Reals('x y') 283 | res, model = check_cons([x * x + y * y < 0]) 284 | if res == sat: 285 | print(model) 286 | else: 287 | print(res) 288 | 289 | ######################################### 290 | # Constraints on rational domain Q. 291 | 292 | # One important application of the non-linear linear arithmetic 293 | # is to reason constraints on rational domain. Recall that we 294 | # have studied linear arithmetic both on the real domain R and 295 | # the integer domain Z, Z3 has built-in support for these two domains. 296 | # However, the bad news is that Z3 does not support rational domain directly. 297 | # To overcome this difficulty, we can use a very important 298 | # property of rational numbers: suppose x is a rational number, then 299 | # we have: 300 | # x = p/q (1) 301 | # where both p and q are integers, and q!=0. The formulae (1) can 302 | # be rewritten into: 303 | # x*q = p (2) 304 | # please notice that the equation (2)is also non-linear. 305 | 306 | # Let's study one example to see how this strategy works in practice, 307 | # consider our previous example again, if we want to check the constraint 308 | # x*x = 2 (3) 309 | # on rational domain (of course, it's unsatisfiable). 310 | # exercise 8: please fill in the missing code to check the 311 | # satisfiability of the equation (3). 312 | x, p, q = Ints('x p q') 313 | # raise Todo("exercise 8: please fill in the missing code.") 314 | res, model = check_cons([x * x == 2, x*q == p]) 315 | if res == sat: 316 | print(model) 317 | else: 318 | print(res) 319 | 320 | # this completes the part of the assignment. 321 | 322 | -------------------------------------------------------------------------------- /assignment7/bit_vectors.py: -------------------------------------------------------------------------------- 1 | import re 2 | import time 3 | 4 | from z3 import * 5 | 6 | 7 | class Todo(Exception): 8 | def __init__(self, msg): 9 | self.msg = msg 10 | 11 | def __str__(self): 12 | return self.msg 13 | 14 | def __repr__(self): 15 | return self.__str__() 16 | 17 | # In this part of the assignment, you'll be familiarizing 18 | # yourself with the theory to reason about bit vectors. 19 | 20 | # Z3 has built-in full support for bit vectors. 21 | # To declare two bit vectors "x" and "y", we use the 22 | # "BitVecs" class, as in: 23 | x, y = BitVecs('x y', 2) 24 | # this code specifies that the bit vectors are of width 2. 25 | # We then create a solver: 26 | solver = Solver() 27 | # and ask whether or not the following constraint is satisfiable: 28 | solver.add(x + y == 9) 29 | res = solver.check() 30 | if res == sat: 31 | print(solver.model()) 32 | else: 33 | print(res) 34 | 35 | 36 | # exercise 1: Run the code above, what output do you get? 37 | # Is the number "9" representable using just 2 bits? And explain 38 | # to yourself how the modulo semantics work here. You don't 39 | # need to write any code here. 40 | 41 | # 9 的后两位是01 原式等价于 x+y=1 42 | 43 | 44 | # exercise 2: In this exercise, you will write a function to 45 | # count the number of 1 in the bit-vector. 46 | # 47 | # BitVecVal(1, 32) : 1 48 | # BitVecVal(-1, 32) : 2 49 | def count_one_in_bit_vector(x): 50 | # raise Todo("exercise 2: please fill in the missing code.") 51 | n = x.size() 52 | time = 0 53 | # print(type(simplify(x & 1).as_long())) 54 | for i in range(n): 55 | time += simplify(x & 1).as_long() 56 | x >>= 1 57 | return time 58 | 59 | 60 | def check_count(): 61 | a = BitVecVal(5, 2) 62 | if count_one_in_bit_vector(a) != 1: 63 | print("Wrong!") 64 | 65 | b = BitVecVal(5, 3) 66 | if count_one_in_bit_vector(b) != 2: 67 | print("Wrong!") 68 | 69 | c = BitVecVal(-1, 16) 70 | if count_one_in_bit_vector(c) != 16: 71 | print("Wrong!") 72 | 73 | d = BitVecVal(1, 16) 74 | if count_one_in_bit_vector(d) != 1: 75 | print("Wrong!") 76 | 77 | 78 | check_count() 79 | 80 | 81 | # Recall that in the class, we discussed a buggy 82 | # version of the binary search algorithm, as the 83 | # following Java code shows: 84 | 85 | # // To search the value "target", in the input array "arr". 86 | # // Return the index when searching succeeded, or return -1 when failed. 87 | # int binarySearch(int[] arr, int target){ 88 | # int low = 0; 89 | # int high = arr.length - 1; 90 | # 91 | # while(low <= high){ 92 | # int middle = (low + high)/2; 93 | # 94 | # if(arr[middle] == target) 95 | # return middle; 96 | # 97 | # if(arr[middle] > target) 98 | # high = middle - 1; 99 | # else 100 | # low = middle + 1; 101 | # } 102 | # return -1; 103 | # } 104 | 105 | # exercise 3: In this exercise, you're required to find 106 | # the bug about the integer overflow in the 107 | # Java code showing above and using z3's bit-vector to 108 | # reproduce the bug. 109 | 110 | # Given two bit vectors, to compute their average: 111 | def int_average_v1(x, y): 112 | # raise Todo("exercise 3: please fill in the missing code.") 113 | return (x + y) / 2 114 | 115 | 116 | # To compute the correct result of integer average, we've 117 | # given this correct implementation for you. 118 | # Make sure you understand this code before continuing. 119 | # Warned: this is a special hack, you should not copy 120 | # this code as your implementation. 121 | def int_average_correct(x, y): 122 | ex = SignExt(1, x) 123 | ey = SignExt(1, y) 124 | result_correct = (ex + ey) / 2 125 | return Extract(31, 0, result_correct) 126 | 127 | 128 | # To check whether or not a given input function "f" is correct. 129 | # Input: "f": the given function 130 | # "is_non_negative": whether or not the arguments should be non-negative 131 | # that is x>=0 && y>=0 132 | 133 | # Declare a variable "result" to store z3's output. 134 | result = '' 135 | 136 | 137 | def check_average(f, is_non_negative): 138 | x, y = BitVecs('x y', 32) 139 | result_correct = int_average_correct(x, y) 140 | result1 = f(x, y) 141 | solver = Solver() 142 | solver.add(result_correct != result1) 143 | 144 | # add extra constraints, for non-negative arguments 145 | if is_non_negative: 146 | solver.add(x >= 0, y >= 0) 147 | res = solver.check() 148 | 149 | if is_non_negative: 150 | sign_str = "non-negative" 151 | else: 152 | sign_str = "negative" 153 | 154 | if res == sat: 155 | print(f"\033[31mFAILED!\033[0m Found a bug with {sign_str} input in the function: {f.__name__}") 156 | print(solver.model()) 157 | global result 158 | result = solver.model() 159 | else: 160 | print(f"\033[32mSUCCESS!\033[0m There is NO bug with {sign_str} input in the function: {f.__name__}") 161 | 162 | 163 | # exercise 4: To check whether or not the above function is correct. 164 | # Does Z3 complain? Why or why not? 165 | check_average(int_average_v1, True) 166 | 167 | 168 | # Given a Java source code which accepts two parameters provided by z3, 169 | # that is provided by z3 after running the function check_func(): 170 | # 171 | # public class IntAverage { 172 | # public static void main(String[] args) { 173 | # String x = args[0]; 174 | # String y = args[1]; 175 | # 176 | # int_average(Integer.parseInt(x), Integer.parseInt(y)); 177 | # } 178 | # 179 | # private static int int_average(int x, int y) { 180 | # int z = (x + y) / 2; 181 | # assert z >= 0 : "Generating a integer overflow bug!"; 182 | # return z; 183 | # } 184 | # } 185 | 186 | # Exercise 5: We build the Java source code into a Jar and provide 187 | # a python function to automatically get the z3's output and invoke 188 | # this Jar. Run the code, and what's your observation? What conclusion 189 | # you can draw from this code. 190 | 191 | def invoke_java_int_average(): 192 | arr = re.findall("\d+", str(result)) 193 | if not arr: 194 | return 195 | x = arr[0] 196 | y = arr[1] 197 | os.system("java -jar -ea int_average.jar " + x + " " + y) 198 | 199 | 200 | invoke_java_int_average() 201 | 202 | 203 | # Joshua J. Bloch proposed the following solution to solve integer overflow problem: 204 | def int_average_v2(x, y): 205 | return x + (y - x) / 2 206 | 207 | 208 | # exercise 6: To check whether or not the above function is correct. 209 | # Does Z3 complain? Why or why not? 210 | check_average(int_average_v2, False) 211 | # 当x和y异号时,可能发生溢出 212 | 213 | 214 | # Joshua J. Bloch proposed a second solution to solve integer 215 | # overflow problem: 216 | # (x+y) >>> 1 217 | # 218 | # exercise 7: Complete the missing part to implement it. 219 | def int_average_v3(x, y): 220 | # raise Todo("exercise 7: please fill in the missing code.") 221 | return LShR(x+y, 1) 222 | 223 | 224 | check_average(int_average_v3, False) 225 | 226 | 227 | # exercise 8: To check whether or not the above function is correct. 228 | # Does Z3 complain? Why or why not? 229 | check_average(int_average_v3, True) 230 | 231 | 232 | # exercise 9: To compute the average of two arbitrary 233 | # integers (negative or non-negative). We've give you a correct 234 | # implementation of the integer average, you can read it 235 | # for some idea, but you must write you own code, 236 | # and don't copy the version we provide. 237 | 238 | # Hint 1: this exercise is harder than you may imagine, you may want to 239 | # search online for the correct implementation of average on fix-width 240 | # integers. For instance, you can refer to the 241 | # Hacker's Delight book (page 9, section 2.5) by Henry S. Warren 242 | # (this is a very good book containing many delighting programming tricks). 243 | def int_average(x, y): 244 | # raise Todo("exercise 9: please fill in the missing code.") 245 | t = (x & y) + ((x ^ y) >> 1) 246 | return t + (LShR(t, 31) & (x ^ y)) 247 | 248 | 249 | check_average(int_average, False) 250 | check_average(int_average, True) 251 | 252 | 253 | # Integer overflows are a notorious source of bugs and are very difficult to 254 | # debug. Read the following C code: 255 | # 256 | # int myfunction(int * array, int len){ 257 | # int * myarray, i; 258 | # 259 | # myarray = malloc(len * sizeof(int)); / *[1] * / 260 | # if (myarray == NULL) 261 | # { 262 | # return -1; 263 | # } 264 | # 265 | # for (i = 0; i < len; i++){ / * [2] * / 266 | # myarray[i] = array[i]; 267 | # } 268 | # 269 | # return myarray; 270 | # } 271 | 272 | 273 | # exercise 10: Read the assigned code, to understand how we can use 274 | # Z3 to justify the existence of overflows. You don't need 275 | # to write any code in this exercise. 276 | def multi_overflow(): 277 | x, y = BitVecs('x y', 32) 278 | solver = Solver() 279 | solver.add(x >= 1, y == 4, x * y < 0) 280 | res = solver.check() 281 | if res == sat: 282 | print('found an poc for integer overflow: ', solver.model()) 283 | else: 284 | print('success!') 285 | 286 | 287 | multi_overflow() 288 | # 可能发生两个整数相乘结果溢出为负的情况 289 | 290 | # exercise 11: Given two bit vectors, computer their multiplication, 291 | # return two value: an overflow flag, and the result (after rounding). 292 | # For instance, for x=1, y=2, return (False, 2). 293 | # for x=0x80000000, y=2, return (True, 0) 294 | 295 | def detect_multi_overflow(x, y): 296 | # raise Todo("exercise 11: please fill in the missing code.") 297 | # print(type(simplify(BVMulNoOverflow(x, y, signed=True)))) 298 | # print(not simplify(BVMulNoOverflow(x, y, signed=True))) 299 | # print(simplify(x * y)) 300 | return not simplify(BVMulNoOverflow(x, y, signed=True)), simplify(x * y) 301 | 302 | 303 | def check_multi(): 304 | # some unit tests 305 | x = BitVecVal(1, 32) 306 | y = BitVecVal(2, 32) 307 | is_overflow, res = detect_multi_overflow(x, y) 308 | if not ((not is_overflow) and res == 2): 309 | print("Wrong!") 310 | else: 311 | print("multi_with_overflow pass test case 1") 312 | 313 | x = BitVecVal(0x80000000, 32) 314 | y = BitVecVal(2, 32) 315 | is_overflow, res = detect_multi_overflow(x, y) 316 | if not (is_overflow and res == 0): 317 | print("Wrong!") 318 | else: 319 | print("multi_with_overflow pass test case 2") 320 | 321 | 322 | check_multi() 323 | 324 | 325 | # We given an example for the special case of Fermat's Last Theorem when n==2, 326 | # that is, we ask Z3 to show that x*x+y*y=z*z does have solutions. 327 | def fermat_2(x, y, z): 328 | cons = [] 329 | cons.append(x & 0xffffff00 == 0) 330 | cons.append(y & 0xffffff00 == 0) 331 | cons.append(z & 0xffffff00 == 0) 332 | cons.append(x != 0) 333 | cons.append(y != 0) 334 | cons.append(z != 0) 335 | cons.append(x * x + y * y == z * z) 336 | return cons 337 | 338 | 339 | # Fermat's last theorem: 340 | # exercise 12: write a function for arbitrary n: 341 | def fermat(x, y, z, n): 342 | # raise Todo("exercise 12: please fill in the missing code.") 343 | cons = [] 344 | cons.append(x & 0xfffffc00 == 0) 345 | cons.append(y & 0xfffffc00 == 0) 346 | cons.append(z & 0xfffffc00 == 0) 347 | cons.append(x != 0) 348 | cons.append(y != 0) 349 | cons.append(z != 0) 350 | xx , yy, zz = x, y, z 351 | for i in range(n-1): 352 | xx *= x 353 | yy *= y 354 | zz *= z 355 | cons.append(xx + yy == zz) 356 | return cons 357 | 358 | def check_fermat(): 359 | # check for n = 2 360 | x, y, z = BitVecs('x y z', 32) 361 | solver = Solver() 362 | solver.add(fermat_2(x, y, z)) 363 | res = solver.check() 364 | if res == sat: 365 | print(f"When n = 2, Fermat's Last Theorem does NOT held: {solver.model()}") 366 | else: 367 | print("When n = 2, Fermat's Last Theorem does held") 368 | 369 | # check for n == 3 370 | n = 3 371 | solver = Solver() 372 | solver.add(fermat(x, y, z, n)) 373 | start = time.time() 374 | res = solver.check() 375 | end = time.time() 376 | print('时间:', end-start, 's') 377 | if res == sat: 378 | print(f"When n = {n}, found a counter example, Fermat's Last Theorem does NOT held: {solver.model()}") 379 | else: 380 | print("We are more confident that Fermat's Last Theorem does held, although we don't have a rigorous proof yet") 381 | 382 | 383 | check_fermat() 384 | -------------------------------------------------------------------------------- /assignment3/dpll.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import unittest 3 | from typing import List 4 | 5 | from z3 import * 6 | 7 | # In this problem, you will implement the DPLL algorithm as discussed 8 | # in the class. 9 | 10 | 11 | # a utility class to represent the code you should fill in. 12 | class Todo(Exception): 13 | def __init__(self, msg): 14 | self.msg = msg 15 | 16 | def __str__(self): 17 | return self.msg 18 | 19 | def __repr__(self): 20 | return self.__str__() 21 | 22 | 23 | ######################################## 24 | # This bunch of code declare the syntax for the propositional logic, we 25 | # repeat here: 26 | ''' 27 | P ::= p 28 | | T 29 | | F 30 | | P /\ P 31 | | P \/ P 32 | | P -> P 33 | | ~P 34 | ''' 35 | 36 | 37 | class Prop: 38 | def __repr__(self): 39 | return self.__str__() 40 | 41 | 42 | class PropVar(Prop): 43 | def __init__(self, var): 44 | self.var = var 45 | 46 | def __str__(self): 47 | return self.var 48 | 49 | def __hash__(self): 50 | return hash(self.var) 51 | 52 | def __eq__(self, other): 53 | return other.__class__ == self.__class__ and self.var == other.var 54 | 55 | 56 | class PropTrue(Prop): 57 | def __init__(self): 58 | pass 59 | 60 | def __str__(self): 61 | return "True" 62 | 63 | def __eq__(self, other): 64 | return other.__class__ == self.__class__ 65 | 66 | 67 | class PropFalse(Prop): 68 | def __init__(self): 69 | pass 70 | 71 | def __str__(self): 72 | return "False" 73 | 74 | def __eq__(self, other): 75 | return other.__class__ == self.__class__ 76 | 77 | 78 | class PropAnd(Prop): 79 | def __init__(self, left, right): 80 | self.left = left 81 | self.right = right 82 | 83 | def __str__(self): 84 | return f"({self.left} /\\ {self.right})" 85 | 86 | 87 | class PropOr(Prop): 88 | def __init__(self, left, right): 89 | self.left = left 90 | self.right = right 91 | 92 | def __str__(self): 93 | return f"({self.left} \\/ {self.right})" 94 | 95 | 96 | class PropImplies(Prop): 97 | def __init__(self, left, right): 98 | self.left = left 99 | self.right = right 100 | 101 | def __str__(self): 102 | return f"({self.left} -> {self.right})" 103 | 104 | 105 | class PropNot(Prop): 106 | def __init__(self, p): 107 | self.p = p 108 | 109 | def __str__(self): 110 | return f"~{self.p}" 111 | 112 | 113 | # we can convert the above defined syntax into Z3's representation, so 114 | # that we can check it's validity easily: 115 | def to_z3(prop): 116 | if isinstance(prop, PropVar): 117 | return Bool(prop.var) 118 | if isinstance(prop, PropImplies): 119 | return Implies(to_z3(prop.left), to_z3(prop.right)) 120 | 121 | # raise Todo("Exercise 3-1: try to complete the `to_z3` method") 122 | if isinstance(prop, PropOr): 123 | return Or(to_z3(prop.left), to_z3(prop.right)) 124 | if isinstance(prop, PropAnd): 125 | return And(to_z3(prop.left), to_z3(prop.right)) 126 | if isinstance(prop, PropNot): 127 | return Not(to_z3(prop.p)) 128 | if isinstance(prop, PropTrue): 129 | return True 130 | if isinstance(prop, PropFalse): 131 | return False 132 | 133 | 134 | ##################### 135 | # TODO: please implement the nnf(), cnf() and dpll() algorithm, as discussed 136 | # in the class. 137 | def nnf(prop: Prop) -> Prop: 138 | # raise Todo("Exercise 3-2: try to implement the `nnf` method") 139 | if isinstance(prop, PropAnd): 140 | return PropAnd(nnf(prop.left), nnf(prop.right)) 141 | if isinstance(prop, PropOr): 142 | return PropOr(nnf(prop.left), nnf(prop.right)) 143 | if isinstance(prop, PropImplies): 144 | return PropOr(PropNot(nnf(prop.left)), nnf(prop.right)) 145 | if isinstance(prop, PropNot): 146 | if isinstance(prop.p, PropVar): 147 | return PropNot(nnf(prop.p)) 148 | elif isinstance(prop.p, PropOr): 149 | return PropAnd(nnf(PropNot(prop.p.left)), nnf(PropNot(prop.p.right))) 150 | elif isinstance(prop.p, PropNot): 151 | return nnf(prop.p.p) 152 | elif isinstance(prop.p, PropAnd): 153 | return PropOr(nnf(PropNot(prop.p.left)), nnf(PropNot(prop.p.right))) 154 | elif isinstance(prop.p, PropTrue): 155 | return PropFalse() 156 | elif isinstance(prop.p, PropFalse): 157 | return PropTrue() 158 | elif isinstance(prop.p, PropImplies): 159 | return PropAnd(nnf(prop.p.left), nnf(PropNot(prop.p.right))) 160 | return prop 161 | 162 | 163 | def is_atom(nnf_prop: Prop) -> bool: 164 | if isinstance(nnf_prop, PropOr) or isinstance(nnf_prop, PropAnd): 165 | return False 166 | return True 167 | 168 | 169 | def cnf(nnf_prop: Prop) -> Prop: 170 | def cnf_d(left: Prop, right: Prop) -> Prop: 171 | if isinstance(left, PropAnd): 172 | return PropAnd(cnf_d(left.left, right), cnf_d(left.right, right)) 173 | elif isinstance(right, PropAnd): 174 | return PropAnd(cnf_d(left, right.left), cnf_d(left, right.right)) 175 | return PropOr(left, right) 176 | 177 | # raise Todo("Exercise 3-3: try to implement the `cnf`and `cnf_d` method") 178 | 179 | if isinstance(nnf_prop, PropVar): 180 | return nnf_prop 181 | if isinstance(nnf_prop, PropAnd): 182 | return PropAnd(cnf(nnf_prop.left), cnf(nnf_prop.right)) 183 | if isinstance(nnf_prop, PropOr): 184 | return cnf_d(nnf_prop.left, nnf_prop.right) 185 | if isinstance(nnf_prop, PropNot): 186 | return PropNot(nnf_prop.p) 187 | return nnf_prop 188 | 189 | 190 | def flatten(cnf_prop: Prop) -> List[List[Prop]]: 191 | """Flatten CNF Propositions to nested list structure . 192 | 193 | The CNF Propositions generated by `cnf` method is AST. 194 | This method can flatten the AST to a nested list of Props. 195 | For example: "((~p1 \\/ ~p3) /\\ (~p1 \\/ p4))" can be 196 | transfer to "[[~p1, ~p3], [~p1, p4]]". 197 | 198 | Parameters 199 | ---------- 200 | cnf_prop : Prop 201 | CNF Propositions generated by `cnf` method. 202 | 203 | Returns 204 | ------- 205 | List[List[Prop] 206 | A nested list of Props, first level lists are connected by `And`, 207 | and second level lists is connected by `Or`. 208 | 209 | """ 210 | 211 | def get_atom_from_disjunction(prop: Prop) -> List[Prop]: 212 | if is_atom(prop): 213 | return [prop] 214 | 215 | if isinstance(prop, PropOr): 216 | return get_atom_from_disjunction(prop.left) + get_atom_from_disjunction(prop.right) 217 | 218 | if isinstance(cnf_prop, PropAnd): 219 | return flatten(cnf_prop.left) + flatten(cnf_prop.right) 220 | elif isinstance(cnf_prop, PropOr): 221 | return [get_atom_from_disjunction(cnf_prop)] 222 | elif is_atom(cnf_prop): 223 | return [[cnf_prop]] 224 | 225 | 226 | def unitProp(prop): # unit prop and simplify P 227 | if isinstance(prop, PropTrue) or isinstance(prop, PropFalse) or isinstance(prop, PropVar): 228 | return prop 229 | if isinstance(prop, PropAnd): 230 | if isinstance(prop.left, PropFalse) or isinstance(prop.right, PropFalse): 231 | return PropFalse() 232 | elif isinstance(prop.left, PropTrue): 233 | return unitProp(prop.right) 234 | elif isinstance(prop.right, PropTrue): 235 | return unitProp(prop.left) 236 | else: 237 | return PropAnd(unitProp(prop.left), unitProp(prop.right)) 238 | if isinstance(prop, PropOr): 239 | if isinstance(prop.left, PropTrue) or isinstance(prop.right, PropTrue): 240 | return PropTrue() 241 | elif isinstance(prop.left, PropFalse): 242 | return unitProp(prop.right) 243 | elif isinstance(prop.right, PropFalse): 244 | return unitProp(prop.left) 245 | else: 246 | return PropOr(unitProp(prop.left), unitProp(prop.right)) 247 | if isinstance(prop, PropNot): 248 | if isinstance(prop.p, PropTrue): 249 | return PropFalse() 250 | if isinstance(prop.p, PropFalse): 251 | return PropTrue() 252 | else: 253 | return prop 254 | return prop 255 | 256 | 257 | def select_atomic(prop): 258 | # print(prop.__class__) 259 | # print(prop) 260 | if isinstance(prop, PropVar): 261 | return prop 262 | if isinstance(prop, PropNot): 263 | return select_atomic(prop.p) 264 | if not is_atom(prop): 265 | if not isinstance(select_atomic(prop.left), PropTrue) \ 266 | and not isinstance(select_atomic(prop.left), PropFalse): 267 | return select_atomic(prop.left) 268 | if not isinstance(select_atomic(prop.right), PropTrue) \ 269 | and not isinstance(select_atomic(prop.right), PropFalse): 270 | return select_atomic(prop.right) 271 | return prop 272 | 273 | 274 | def set_var_value(prop, var, value): 275 | if isinstance(prop, PropVar): 276 | if prop.var == var.var: 277 | return value 278 | if isinstance(prop, PropNot): 279 | prop.p = set_var_value(prop.p, var, value) 280 | if isinstance(prop.p, PropTrue): # after set value, maybe is "PropNot(PropTrue)" 281 | return PropFalse() 282 | if isinstance(prop.p, PropFalse): 283 | return PropTrue() 284 | if isinstance(prop, PropOr): 285 | prop.left = set_var_value(prop.left, var, value) 286 | prop.right = set_var_value(prop.right, var, value) 287 | if isinstance(prop.left, PropTrue) or isinstance(prop.right, PropTrue): 288 | return PropTrue() 289 | if isinstance(prop.left, PropFalse): 290 | return prop.right 291 | if isinstance(prop.right, PropFalse): 292 | return prop.left 293 | if isinstance(prop, PropAnd): 294 | prop.left = set_var_value(prop.left, var, value) 295 | prop.right = set_var_value(prop.right, var, value) 296 | if isinstance(prop.left, PropFalse) or isinstance(prop.right, PropFalse): 297 | return PropFalse() 298 | if isinstance(prop.left, PropTrue): 299 | return prop.right 300 | if isinstance(prop.right, PropTrue): 301 | return prop.left 302 | return prop 303 | 304 | 305 | # 对命题进行拷贝,用于回溯 306 | def copy(prop): 307 | if isinstance(prop, PropVar): 308 | return PropVar(prop.var) 309 | if isinstance(prop, PropAnd): 310 | return PropAnd(copy(prop.left), copy(prop.right)) 311 | if isinstance(prop, PropOr): 312 | return PropOr(copy(prop.left), copy(prop.right)) 313 | if isinstance(prop, PropNot): 314 | return PropNot(copy(prop.p)) 315 | 316 | 317 | def dpll(prop: Prop) -> dict: 318 | flatten_cnf = flatten(cnf(nnf(prop))) 319 | print(nnf(prop)) 320 | print(cnf(nnf(prop))) 321 | print(flatten_cnf) 322 | 323 | # implement the dpll algorithm we've discussed in the lecture 324 | # you can deal with flattened cnf which generated by `flatten` method for convenience, 325 | # or, as another choice, you can process the original cnf destructure generated by `cnf` method 326 | # 327 | # your implementation should output the result as dict like : 328 | # {"p1": True, "p2": False, "p3": False, "p4": True} if there is solution; 329 | # output "unsat" if there is no solution 330 | # 331 | # feel free to add new method. 332 | # raise Todo("Exercise 3-4: try to implement the `dpll` algorithm") 333 | 334 | result = dict() 335 | # 初始化字典 336 | for props in flatten_cnf: 337 | for fc_prop in props: 338 | if isinstance(fc_prop, PropVar): 339 | result[fc_prop.var] = False 340 | if isinstance(fc_prop, PropNot): 341 | result[fc_prop.p.var] = False 342 | 343 | def dpll1(prop: Prop, assignment: Prop) -> Prop: 344 | unitProp(prop) 345 | if isinstance(prop, PropTrue): 346 | return prop 347 | elif isinstance(prop, PropFalse): 348 | return prop 349 | p = select_atomic(prop) 350 | if p is None: 351 | return prop 352 | if isinstance(assignment, PropTrue): 353 | result[p.var] = True 354 | elif isinstance(assignment, PropFalse): 355 | result[p.var] = False 356 | 357 | prop = set_var_value(prop, p, assignment) 358 | prop_copy = copy(prop) 359 | # print(prop_copy) 360 | # print(prop) 361 | if isinstance(dpll1(prop, PropTrue()), PropTrue): 362 | return PropTrue() 363 | return dpll1(prop_copy, PropFalse()) 364 | 365 | newProp = cnf(nnf(prop)) 366 | dpll1(newProp, PropTrue()) 367 | print(result) 368 | return result 369 | 370 | 371 | ##################### 372 | # test cases: 373 | 374 | # p -> (q -> ~p) 375 | test_prop_1 = PropImplies(PropVar('p'), PropImplies(PropVar('q'), PropVar('p'))) 376 | 377 | # ~((p1 \/ ~p2) /\ (p3 \/ ~p4)) 378 | test_prop_2 = PropNot(PropAnd( 379 | PropOr(PropVar("p1"), PropNot(PropVar("p2"))), 380 | PropOr(PropVar("p3"), PropNot(PropVar("p4"))) 381 | )) 382 | 383 | N = 10 384 | # monster_prop = PropVar('b_0') 385 | # for i in range(1, N): 386 | # monster_prop = PropAnd(monster_prop, PropVar('b_{}'.format(i))) 387 | # print(select_atomic(monster_prop)) 388 | 389 | monster_prop = PropTrue() 390 | for i in range(N): 391 | monster_prop = PropAnd(monster_prop, PropVar('b_{}'.format(i))) 392 | 393 | 394 | # feed the large proposition to your solver. you can also 395 | # generate other propositions. 396 | test_prop_3 = monster_prop 397 | # print(test_prop_3) 398 | 399 | 400 | # ##################### 401 | class TestDpll(unittest.TestCase): 402 | 403 | def test_to_z3_1(self): 404 | self.assertEqual(str(to_z3(test_prop_1)), "Implies(p, Implies(q, p))") 405 | 406 | def test_to_z3_2(self): 407 | self.assertEqual(str(to_z3(test_prop_2)), "Not(And(Or(p1, Not(p2)), Or(p3, Not(p4))))") 408 | 409 | def test_nnf_1(self): 410 | self.assertEqual(str(nnf(test_prop_1)), "(~p \\/ (~q \\/ p))") 411 | 412 | def test_nnf_2(self): 413 | self.assertEqual(str(nnf(test_prop_2)), "((~p1 /\\ p2) \\/ (~p3 /\\ p4))") 414 | 415 | def test_cnf_1(self): 416 | self.assertEqual(str(cnf(nnf(test_prop_1))), "(~p \\/ (~q \\/ p))") 417 | 418 | def test_cnf_2(self): 419 | self.assertEqual(str(cnf(nnf(test_prop_2))), 420 | "(((~p1 \\/ ~p3) /\\ (~p1 \\/ p4)) /\\ ((p2 \\/ ~p3) /\\ (p2 \\/ p4)))") 421 | 422 | def test_cnf_flatten_1(self): 423 | self.assertEqual(str(flatten(cnf(nnf(test_prop_1)))), "[[~p, ~q, p]]") 424 | 425 | def test_cnf_flatten_2(self): 426 | self.assertEqual(str(flatten(cnf(nnf(test_prop_2)))), 427 | "[[~p1, ~p3], [~p1, p4], [p2, ~p3], [p2, p4]]") 428 | 429 | def test_dpll_1(self): 430 | s = Solver() 431 | res = dpll(test_prop_1) 432 | # print(res) 433 | s.add(Not(Implies(res["p"], Implies(res["q"], res["p"])))) 434 | self.assertEqual(str(s.check()), "unsat") 435 | 436 | def test_dpll_2(self): 437 | s = Solver() 438 | res = dpll(test_prop_2) 439 | s.add(Not(Not(And(Or(res["p1"], Not(res["p2"])), Or(res["p3"], Not(res["p4"])))))) 440 | self.assertEqual(str(s.check()), "unsat") 441 | 442 | def test_dpll_3(self): 443 | s = Solver() 444 | res = dpll(test_prop_3) 445 | 446 | print(res) 447 | # monster = res["b_0"] 448 | # for i in range(1, N): 449 | # x = str('b_{}'.format(i)) 450 | # monster = And(monster, res[x]) 451 | 452 | monster = True 453 | for i in range(N): 454 | x = str('b_{}'.format(i)) 455 | monster = And(monster, res[x]) 456 | 457 | print(monster) 458 | s.add(Not(monster)) 459 | self.assertEqual(str(s.check()), "unsat") 460 | 461 | 462 | if __name__ == '__main__': 463 | unittest.main() 464 | --------------------------------------------------------------------------------