theory week08B_demo_sol imports Main begin \ \Syntax and State Space\ type_synonym vname = string type_synonym state = "vname \ nat" type_synonym aexp = "state \ nat" type_synonym bexp = "state \ bool" datatype com = SKIP | Assign vname aexp ("_ :== _ " [60,60] 60) | Semi com com ("_;; _" [50, 51] 50) | Cond bexp com com ("IF _ THEN _ ELSE _" [0,0,59] 60) | While bexp com ("WHILE _ DO _ OD" [0,45] 60) \ \Example Program\ definition A :: vname where "A \ ''A''" definition B :: vname where "B \ ''B''" lemma [simp]: "A \ B" by (simp add: A_def B_def) lemma [simp]: "B \ A" by (simp add: A_def B_def) definition factorial :: com where "factorial \ B :== (\s. 1);; WHILE (\s. s A \ 0) DO B :== (\s. s B * s A);; A :== (\s. s A - 1) OD" \ \Execution\ inductive eval :: "com \ state \ state \ bool" ("_/ \ _" [60,60] 61) where Skip [intro!,simp]: "(SKIP, s) \ s" | Assign [intro!,simp]: "(x :== a, s) \ s (x := a s)" | Semi [intro!]: "\ (c\<^sub>1,s) \ s''; (c\<^sub>2,s'') \ s' \ \ (c\<^sub>1;; c\<^sub>2, s) \ s'" | IfTrue: "\ b s; (c\<^sub>1,s) \ s' \ \ (IF b THEN c\<^sub>1 ELSE c\<^sub>2, s) \ s'" | IfFalse: "\ \b s; (c\<^sub>2,s) \ s' \ \ (IF b THEN c\<^sub>1 ELSE c\<^sub>2, s) \ s'" | WhileFalse: "\b s \ (WHILE b DO c OD, s) \ s" | WhileTrue: "\b s; (c,s) \ s''; (WHILE b DO c OD, s'') \ s'\ \ (WHILE b DO c OD, s) \ s'" (* Isabelle provides eval.induct which allows the elimination of terms cs \ t. In practice, we will have cs as a tuple (c,s). To avoid to manually have to split cs every time, we can use split_format as below to define an induction rule that will work on terms of the form (c, s) \ t.*) thm eval.induct lemmas eval_induct = eval.induct [split_format (complete)] thm eval_induct (* In practice, it is also useful to have elimination rules for precisely when e.g. c=SKIP, or c= c1;;c2, etc. (If c=SKIP, eval_induct has many useless cases). The command inductive_cases allows exactly this.*) inductive_cases skip [elim!]: "(SKIP, s) \ s'" and assign [elim!]: "(x :== a, s) \ s'" and semi [elim!]: "(c\<^sub>1;; c\<^sub>2, s) \ s'" and "if" [elim!]: "(IF b THEN c\<^sub>1 ELSE c\<^sub>2, s) \ s'" and while: "(WHILE b DO c OD, s) \ s'" thm skip assign semi "if" while lemma skip_eq [simp]: "(SKIP, s) \ s' = (s' = s)" by blast lemma assign_eq [simp]: "(x :== a, s) \ s' = (s' = s (x := a s))" by blast \ \---------------------------------------------------------------------\ \ \Example Proof\ primrec fac :: "nat \ nat" where "fac 0 = 1" |"fac (Suc n) = (Suc n) * fac n" (* Need to relate the final state of B to values of A and B *at each iteration* of the loop *) lemma factorial_partial_correct_helper: "(prog, s) \ s' \ prog = WHILE \s. 0 < s A DO B :== (\s. s B * s A) ;; A :== (\s. s A - Suc 0) OD \ s' B = s B * fac (s A)" apply (induct rule: eval_induct) apply (simp_all) apply (case_tac "s A") apply (simp) apply clarsimp (*sledgehammer*) by (simp add: distrib_left distrib_right) lemma factorial_partial_correct: "(factorial, s) \ s' \ s' B = fac (s A)" apply (unfold factorial_def) apply clarsimp apply (clarsimp simp: factorial_partial_correct_helper) done \ \---------------------------------------------------------------------\ type_synonym assn = "state => bool" definition hoare_valid :: "assn \ com \ assn \ bool" ("\ {(1_)}/ (_)/ {(1_)}" 50) where "\ {P} c {Q} \ \s s'. P s \ (c,s) \ s' \ Q s'" inductive hoare :: "assn \ com \ assn \ bool" ("\ ({(1_)}/ (_)/ {(1_)})" 50) where h_skip: "\ {P} SKIP {P}" | h_assign: "\ {\s. P (s(x:= a s))} x:==a {P}" | h_semi: "\ \ {P} c\<^sub>1 {R}; \ {R} c\<^sub>2 {Q} \ \ \ {P} c\<^sub>1;;c\<^sub>2 {Q}" | h_if: "\ \ {\s. P s \ b s} c\<^sub>1 {Q}; \ {\s. P s \ \b s} c\<^sub>2 {Q} \ \ \ {P} IF b THEN c\<^sub>1 ELSE c\<^sub>2 {Q}" | h_while: "\ \ {\s. P s \ b s} c {P}; \s. P s \ \b s \ Q s \ \ \ {P} WHILE b DO c OD {Q}" | conseq: "\ \s. P s \ P' s; \ {P'} c {Q'}; \s. Q' s \ Q s \ \ \ {P} c {Q}" \ \Soundness Proof\ (* When a while loop is fully executed to a state s', then the loop condition is false in s' (otherwise there would have been another iteration). Moreover, if the loop satisfies an invariant P, then P is true when the loop exits. *) lemma while_final: "\(prog, s) \ s'; \s. P s \ b s \ (\s'. (c, s) \ s' \ P s'); P s; prog = WHILE b DO c OD\ \ P s' \ \ b s'" apply (induct rule: eval_induct) apply (simp_all) done (* To show that a postcondition Q holds after a While loop, we need to find a predicate P that is an invariant of the loop (i.e. preserved by the loop body at each iteration) and is "enough" to prove the postcondition when the proof exits.*) lemma while_sound: "\\ {\a. P a \ b a} c {P}; \s. P s \ \ b s \ Q s\ \ \ {P} WHILE b DO c OD {Q}" apply (clarsimp simp: hoare_valid_def) apply (subgoal_tac "P s' \ \ b s'") apply blast apply (rule while_final) apply (simp_all) done theorem hoare_sound: "\ {P} c {Q} \ \ {P} c {Q}" proof(induct rule: hoare.induct) case (h_while P b c Q) then show ?case by(rule_tac while_sound) qed (auto simp add: hoare_valid_def) (*OR*) theorem hoare_sound2: "\ {P} c {Q} \ \ {P} c {Q}" apply (induct rule:hoare.induct) by (fastforce elim: while_sound simp: hoare_valid_def)+ \ \--------------------------------------------------------------------------------------\ \ \derived rules\ lemma h_ass: "(\s. P s \ Q (s(x := a s))) \ \ {P} x :== a {Q}" apply (rule_tac Q'=Q in conseq) prefer 2 apply (rule h_assign) apply (blast)+ done \ \example proof\ lemma "\ {\s. True} x :== (\s. 2) {\s. s x = 2}" apply (rule h_ass) apply simp done lemma "\ {\s. s A = n} factorial {\s. s B = fac n}" apply (unfold factorial_def) apply (rule h_semi) prefer 2 apply (rule_tac P="\s. s B * fac (s A) = fac n" in h_while) apply (rule h_semi) prefer 2 apply (rule h_assign) apply (rule h_ass) apply clarsimp apply (case_tac "s A", simp) apply clarsimp apply (simp add: add_mult_distrib2 comm_semiring_class.distrib) apply clarsimp apply (rule h_ass) apply clarsimp done \ \Bonus content: completeness proof\ (* This relation is used to state a loop invariant for the completeness proof. Intuitively, while_rel P c x1 s holds if, starting from a state satisfying P, s is an intermediate state that can be reached by running the loop WHILE x1 DO c OD n times (not necessarily to completion). *) inductive while_rel where while_rel_base: "P s \ while_rel P c x1 s" | while_rel_step: "\while_rel P c x1 s; x1 s; (c,s) \ s'\ \ while_rel P c x1 s'" lemma while_rel_valid: assumes "\ {P} WHILE x1 DO c OD {Q}" shows "\ {while_rel P c x1} WHILE x1 DO c OD {Q}" proof - (* The curly brackets begin what's called an _explicit proof block_, which is a way of proving an auxiliary result within an Isar proof without explicitly stating it. For example, the proof block { fix x y :: nat have "x + y = y + x" by simp } will create the following theorem: ?x + ?y = ?y + ?x note that the arbitrary but fixed (orange) variables in the proof block were exported from the block as schematic variables. *) { fix s s' assume "while_rel P c x1 s" and "(WHILE x1 DO c OD, s) \ s'" hence "Q s'" using assms proof(induct rule: while_rel.induct) case (while_rel_base P s c x1) then show ?case by(auto simp add: hoare_valid_def) next case (while_rel_step P c x1 s s'') then show ?case apply clarsimp apply(erule meta_mp) by(rule WhileTrue) qed } \ \ Put the cursor here to see the theorem that the proof block created. \ thus ?thesis by(force simp add: hoare_valid_def) qed theorem hoare_complete: assumes "\ {P} c {Q}" shows "\ {P} c {Q}" using assms \ \ Structural induction is fine here since the Hoare rules are syntax-directed. Besides, we don't have an inductive relation as a premise, so no sense trying rule induction. \ proof(induct c arbitrary: P Q) case SKIP then show ?case by(auto simp add: hoare_valid_def intro: h_skip conseq) next case (Assign x1 x2) then show ?case by(force intro: h_ass simp add: hoare_valid_def) next case (Semi c1 c2) (* thm Semi contains induction hypotheses + other premises inherited from the chained facts present when the induction started. These can be referred to separately: *) thm Semi thm Semi.prems thm Semi.hyps show ?case using Semi.prems by(force intro: Semi.hyps h_semi[where R="\ s. (\s'. (c2, s) \ s' \ Q s')"] simp add: hoare_valid_def) (* force doesn't find the intermediate pre/post-condition on its own, so we give it a clue by giving it a pre-instantiated version of h_semi as an intro rule. *) next case (Cond x1 c1 c2) show ?case using Cond.prems by(force intro: h_if Cond.hyps IfTrue IfFalse simp add: hoare_valid_def)+ next case (While x1 c) from While.prems have "\ {while_rel P c x1} WHILE x1 DO c OD {Q}" by(rule while_rel_valid) then show ?case apply - apply(rule conseq[where Q'=Q and P'="while_rel P c x1"]) apply(force intro: while_rel.intros) apply(rule h_while) apply(rule While.hyps) apply(force intro: while_rel.intros simp add: hoare_valid_def) apply(force intro: WhileFalse simp add: hoare_valid_def) apply simp done qed end