Rewriting with non-equality equivalent relations using Isabelle simplification
I would like to use simplification to replace subterms that are not equalities. Instead of a general definition of my problem, I will illustrate this with an example:
Let's say that I have a simple programming language and Hoare logic. Let's say that if, while and sequence operation. Moreover, we have denotation
which gives the designation of the program and hoare P c Q
. Below is an example of a signature in Isabelle / HOL:
(* A simple language and Hoare logic *)
typedecl program
typedecl memory
consts
seq :: "program β program β program" (infixl ";" 10) (* c;d: run c, then run d *)
ifthen :: "(memory β bool) β program β program" (* ifthen e c: run c if e(current_mem)=true *)
while :: "(memory β bool) β program β program" (* while e c: run c while e(current_mem)=true *)
denotation :: "program β memory β memory" (* denotation c m: memory after running c, when starting with memory m *)
hoare :: "(memory β bool) β program β (memory β bool) β bool"
(* hoare P c Q: if P(current_mem), then after running c, we have Q(current_mem) *)
Now it is not true that (a;b);c = a;(b;c)
(these are different programs), but he believes that they are denotationally equivalent, i.e. denotation ((a;b);c)) = denotation (a;(b;c))
...
This means that I have to rewrite a;(b;c)
in the (a;b);c
Hoare triplets inside. For example, I would like to be able to prove
lemma "hoare P (while e (a;b;c)) Q ==> hoare P (while e (a;(b;c))) Q"
just using the simplifier ( by simp
), given the appropriate simplification rules.
Logically, the relevant rules would be the following:
lemma "denotation (a;(b;c)) = denotation ((a;b);c)"
lemma "denotation a = denotation b ==> hoare P a Q = hoare P b Q"
lemma "denotation a = denotation b ==> denotation (while e a) = denotation (while e b)"
lemma "denotation a = denotation b ==> denotation (ifthen e a) = denotation (ifthen e b)"
lemma "denotation a = denotation a' ==> denotation b = denotation b' ==> denotation (a;b) = denotation (a';b')"
Unfortunately, there seems to be no easy way to communicate these rules to the simplifier. (More generally, we would like to say a simplifier in the congruence rule that the rewriting below must have some equivalence relation, denotational equivalence in this example.)
I found a partial solution to this problem (see my own answer below), but the solution seems to be a hack (and I don't know how stable it is) and I'm wondering if there is a good way to do this.
I don't mind using any ML code in this process (like writing simproc), but I would like to avoid re-completing the whole overwrite simplification inside Hoare tuples.
source to share
Isabella Simplifier does not support rewriting with respect to arbitrary equivalence relations. Luckily, your rewrites look pretty straightforward, so it might make sense to implement the rewrites in simproc. Here's the idea:
Write simproc that runs on form conditions hoare P c Q
. When called, it sets the purpose of the form hoare P c Q == ?rhs
and enforces the rule that it %c. hoare P c Q
only cares about the equivalence class of its argument, not a specific element. Then apply the rewrite rules as introduction rules until the stated goal is achieved. This was to create an instance ?rhs
on the form hoare P c' Q
. Check if c
and c'
alpha beta eta ... are equivalent. If so, simproc fails with NONE
, otherwise it returns the validated equation.
Here are a bunch of lemmas that I would use as a start:
definition fun_equiv :: "('a β 'b) β 'a β 'a β bool"
where "fun_equiv f x y β· f x = f y"
lemma fun_equiv_refl: "fun_equiv f x x" by(simp add: fun_equiv_def)
lemma hoare_cong_start: (* start rule *)
"fun_equiv denotation c c' βΉ hoare P c Q == hoare P' c' Q'"
sorry
lemma while_cong: "fun_equiv denotation c c' βΉ fun_equiv denotation (while b c) (while b c')" sorry
lemma seq_cong: "β¦ fun_equiv denotation a a'; fun_equiv denotation b b' β§ βΉ fun_equiv denotation (a ; b) (a' ; b')" sorry
lemma if_cong: "fun_equiv denotation c c' βΉ fun_equiv denotation (ifthen b c) (ifthen b c')" sorry
lemma seq_assoc: "fun_equiv denotation (a ; (b ; c)) (a; b; c)" sorry
lemma ifthen_true: "fun_equiv denotation (ifthen (Ξ»m. True) c) c" sorry
lemmas hoare_intros =
-- βΉrewrites come first, congruences later, reflexivity lastβΊ
ifthen_true seq_assoc
while_cong if_cong seq_cong
fun_equiv_refl
Since this is a simproc inside a simplification, you can assume that the command in the call is already in wrt simset normal form. In your example, the test is %m. m = m
already simplified to %_. True
. So simproc can only focus on implementing rewrites for hoare rules.
One step of calling simproc is to do something like the following Isar snippet:
schematic_lemma "hoare (Ξ»m. P x) (while P (c;(d;e);ifthen (Ξ»m. True) (f;g;c))) (Ξ»m. True) == ?c"
by(rule hoare_cong_start)(rule hoare_intros)+
Since the simplifier iterates over simproc until it runs no longer, you should really get a normal form.
If you want to support the conditional rules for rewriting wrt denotational equivalence, you rule hoare_intros
should replace with something that checks the format of the subcell. If it is not a form fun_equiv denotation _ _
, then simproc should invoke simplification recursively (or any other proof method of your choice) rather than trying to apply a different rule hoare_intros
.
source to share
For a way that seems to work fine see the attached Isabel / HOL theory. The idea is to use conditional sim rules to simulate cong rules.
For example, the congruence rule
lemma l1 [cong]: "f a = f b ==> g a = g b"
is also a valid simp conditional rule
lemma l2 [simp]: "f a = f b ==> g a = g b"
and when Isabelle applies the simp rule, b will be replaced with a conditional variable, and the simplifier will rewrite "fa = f? b", which instantiates b to simplify b.
However, the lemma l2
will make a simplification loop because it can be applied to it with its own rhs. So instead we can write the rule
lemma l3 [simp]: "f_simp a = f_done b ==> g_simp a = g_done b"
where g_simp
and are g_done
defined as equal g
, but will not allow the simplifier to apply the rules in a loop.
A complete working example of this idea is contained in the theory file below.
Problems: This is a hack. I don't know in what situations it might break or be incompatible with other things - it apply simp
might overwrite partially, so you need to call it again to complete the rewrite. (See the last lemma in theory.)
theory Test
imports Main
begin
section "Minimal Hoare logic"
(* A simple language and Hoare logic *)
typedecl program
typedecl memory
consts
seq :: "program β program β program" (infixl ";" 10) (* c;d: run c, then run d *)
ifthen :: "(memory β bool) β program β program" (* ifthen e c: run c if e(current_mem)=true *)
while :: "(memory β bool) β program β program" (* while e c: run c while e(current_mem)=true *)
denotation :: "program β memory β memory" (* denotation c m: memory after running c, when starting with memory m *)
hoare :: "(memory β bool) β program β (memory β bool) β bool"
(* hoare P c Q: if P(current_mem), then after running c, we have Q(current_mem) *)
(* seq is associative modulo denotational equivalence.
Thus we should be able to rewrite "a;(b;c)" to "a;b;c"
within a hoare triple. E.g., the following should be solved with simp: *)
lemma
assumes "hoare P (while e (a;b;c)) Q"
shows "hoare P (while e (a;(b;c))) Q"
using assms
oops (* by simp *)
section "A failed attempt"
experiment begin
(* Here a natural approach first which, however, fails. *)
(* A congruence rule for the simplifier.
To rewrite a hoare triple "hoare P c Q",
we need to rewrite "denotation c". *)
lemma enter [cong]:
assumes "P==P'" and "Q==Q'"
assumes "denotation c == denotation c'"
shows "hoare P c Q == hoare P' c' Q'"
sorry
(* To descend further into subterms, we need a congruence-rule for while. *)
lemma while [cong]:
assumes "e=e'"
and "denotation c == denotation c'"
shows "denotation (while e c) β‘ denotation (while e' c')"
sorry
(* And we give a simplification rule for the associativity of seq *)
lemma assoc [simp]:
"denotation (a;(b;c)) = denotation (a;b;c)"
sorry
(* Now we can simplify the lemma from above *)
lemma
assumes "hoare P (while e (a;b;c)) Q"
shows "hoare P (while e (a;(b;c))) Q"
using assms by simp
(* Unfortunately, this does not work any more once we add more congruence rules. *)
(* To descend further into subterms, we need a congruence-rule for while. *)
lemma ifthen [cong]:
assumes "e=e'"
and "denotation c == denotation c'"
shows "denotation (ifthen e c) β‘ denotation (ifthen e' c')"
sorry
(* Warning: Overwriting congruence rule for "Test.denotation" *)
(* Simplifier doesn't work any more because the congruence rule for while was overwritten *)
lemma
assumes "hoare P (while e (a;b;c)) Q"
shows "hoare P (while e (a;(b;c))) Q"
using assms
oops (* by simp *)
end
section {* A working attempt *}
(* Define copies of the denotation-constant, to control the simplifier *)
definition "denotation_simp == denotation"
definition "denotation_done == denotation"
(* A congruence rule for the simplifier.
To rewrite a hoare triple "hoare P c Q",
we need to rewrite "denotation c".
This means, the congruence rule should have an assumption
"denotation c == denotation c'"
However, to avoid infinite loops with the rewrite rules below,
we use the logically equivalent assumption
"denotation_simp c == denotation_done c'"
This means that we need to configure the rules below rewrite
"denotation_simp c" into "denotation_done c'" where c' is
the simplication of c (module denotational equivalence).
*)
lemma enter [cong]:
assumes "P==P'" and "Q==Q'"
assumes "denotation_simp c == denotation_done c'"
shows "hoare P c Q == hoare P' c' Q'"
sorry
(* A similar congruence rule for simplifying expressions
of the form "denotation c". *)
lemma denot [cong]:
assumes "denotation_simp c == denotation_done c'"
shows "denotation c == denotation c'"
sorry
(* Now we add a congruence-rule for while.
Since we saw above that we cannot use several congruence-rules
with "denotation" as their head,
we simulate a congruence rule using a simp-rule.
To rewrite "denotation_simp (while e c)", we need to rewrite
"denotation_simp c".
We put denotation_done on the rhs instead of denotation_simp
to avoid infinite loops.
*)
lemma while [simp]:
assumes "e=e'"
and "denotation_simp c == denotation_done c'"
shows "denotation_simp (while e c) β‘ denotation_done (while e' c')"
sorry
(* A pseudo-congruence rule for ifthen *)
lemma ifthen [simp]:
assumes "e=e'"
and "denotation_simp c == denotation_done c'"
shows "denotation_simp (ifthen e c) == denotation_done (ifthen e' c')"
sorry
(* A pseudo-congruence rule for seq *)
lemma seq [simp]:
assumes "denotation_simp c == denotation_done c'"
and "denotation_simp d == denotation_done d'"
shows "denotation_simp (c;d) == denotation_done (c';d')"
sorry
(* Finally, we can state associativity of seq. *)
lemma assoc [simp]:
"denotation_simp (a;(b;c)) = denotation_simp (a;b;c)"
sorry
(* Finally, since our congruence-rules expect the rewriting to rewrite
"denotation_simp c" into "denotation_done c'",
we need to translate any non-rewritten "denotation_simp" into
"denotation_done".
However, a rule "denotation_simp c == denotation_done c" does not work,
because it could be triggered too early, and block the pseudo-congruence rules above.
So we only trigger the rule when the current term would not match
any of the pseudo congruence rules *)
lemma finish [simp]:
assumes "NO_MATCH (while e1 c1) a"
assumes "NO_MATCH (ifthen e2 c2) a"
assumes "NO_MATCH (c3;d3) a"
shows "denotation_simp a = denotation_done a"
sorry
(* Testing the simplifier rules *)
lemma
assumes "hoare P (while e (a;b;c)) Q"
shows "hoare P (while e (a;(b;c))) Q"
using assms
by simp
(* Some more complex test *)
lemma iftrue [simp]: "denotation_simp (ifthen (Ξ»m. True) c) == denotation_simp c" sorry
lemma
assumes "βx. Q x"
assumes "hoare (Ξ»m. P x β§ Q x) (while P (c;(d;e);(f;g);c)) (Ξ»m. m=m)"
shows "hoare (Ξ»m. P x) (while P (c;(d;e);ifthen (Ξ»m. m=m) (f;g;c))) (Ξ»m. True)"
using assms
apply simp (* Hmm. Only partially simplified... The assoc rule is not applied to the result of the iftrue rule *)
by simp (* Rerunning simp solves the goal *)
(* By the way: "using assms by auto" solves the goal in one go *)
end
source to share
Below is my implementation of Andreas Lochbeeler's suggestions . The first part is a generic implementation (suitable for other equivalents than denotational equivalence), and below it is created for my example of Hoare logic.
(* Written by Dominique Unruh *)
theory Test2
imports Main
begin
section "Implementation of modulo-simplifier"
definition fun_equiv :: "('a β 'b) β 'a β 'a β bool" where "fun_equiv f x y == f x = f y"
lemma fun_equiv_refl: "fun_equiv f x x" by(simp add: fun_equiv_def)
ML {*
(* Call as: hoare_simproc_tac (simplifications@congruences) context/simpset *)
fun fun_equiv_simproc_tac intros ctxt =
SUBGOAL (fn (goal,i) =>
case goal of
Const(@{const_name Trueprop},_) $ (Const(@{const_name fun_equiv},_)$_$_$_) =>
(resolve0_tac intros THEN_ALL_NEW fun_equiv_simproc_tac intros ctxt) i
ORELSE (rtac @{thm fun_equiv_refl} i)
| _ =>
SOLVED' (simp_tac ctxt) i)
fun fun_equiv_simproc start intros _ ctxt (t:cterm) =
let val fresh_var = Var(("x",Term.maxidx_of_term (Thm.term_of t)+1),Thm.typ_of_cterm t)
val goal = Logic.mk_equals (Thm.term_of t,fresh_var)
val thm = Goal.prove ctxt [] [] goal
(fn {context,...} => resolve0_tac start 1 THEN ALLGOALS (fun_equiv_simproc_tac intros context))
in
if (Thm.rhs_of thm) aconvc t then NONE else SOME thm
end
handle ERROR msg => (warning ("fun_equiv_simproc failed\nTerm was:\n"^(@{make_string} t)^"\nError: "^msg); NONE)
fun fun_equiv_simproc_named start cong simp morph ctxt =
fun_equiv_simproc (Named_Theorems.get ctxt start) (Named_Theorems.get ctxt simp @ Named_Theorems.get ctxt cong) morph ctxt
*}
section "Minimal Hoare logic"
(* A simple language and Hoare logic *)
typedecl program
typedecl memory
consts
seq :: "program β program β program" (infixl ";" 100) (* c;d: run c, then run d *)
ifthen :: "(memory β bool) β program β program" (* ifthen e c: run c if e(current_mem)=true *)
while :: "(memory β bool) β program β program" (* while e c: run c while e(current_mem)=true *)
denotation :: "program β memory β memory" (* denotation c m: memory after running c, when starting with memory m *)
hoare :: "(memory β bool) β program β (memory β bool) β bool"
(* hoare P c Q: if P(current_mem), then after running c, we have Q(current_mem) *)
section "Instantiating the simplifier"
named_theorems denot_simp_start
named_theorems denot_simp
named_theorems denot_cong
lemma hoare_cong_start [denot_simp_start]: "fun_equiv denotation c c' βΉ hoare P c Q == hoare P c' Q" sorry
lemma while_cong [denot_cong]: "fun_equiv denotation c c' βΉ fun_equiv denotation (while b c) (while b c')" sorry
lemma seq_cong [denot_cong]: "fun_equiv denotation a a' βΉ fun_equiv denotation b b' βΉ fun_equiv denotation (a ; b) (a' ; b')" sorry
lemma if_cong [denot_cong]: "fun_equiv denotation c c' βΉ fun_equiv denotation (ifthen b c) (ifthen b c')" sorry
lemma seq_assoc [denot_simp]: "fun_equiv denotation (a ; (b ; c)) (a; b; c)" sorry
lemma ifthen_true [denot_simp]: "(βm. e m) βΉ fun_equiv denotation (ifthen e c) c" sorry
lemma double_while [denot_simp]: "(βm. e m = e' m) βΉ fun_equiv denotation c d βΉ fun_equiv denotation (seq (while e c) (while e' d)) (while e c)" sorry
(* -- "If the set of simplification theorems is fixed, we can use the following setup"
lemmas hoare_congruences = while_cong if_cong seq_cong
lemmas hoare_simps = ifthen_true seq_assoc double_while
simproc_setup hoare_simproc ("hoare P c Q") = {* fun_equiv_simproc @{thms hoare_cong_start} @{thms hoare_simps hoare_congruences} *}
*)
(* To make use of dynamic lists of theorems, we use the following setup *)
simproc_setup hoare_simproc ("hoare P c Q") = {*
fun_equiv_simproc_named @{named_theorems denot_simp_start}
@{named_theorems denot_cong}
@{named_theorems denot_simp}
*}
section "Tests"
(* Testing the simplifier rules *)
lemma
assumes "hoare P (while e (a;b;c)) Q"
shows "hoare P (while e (a;(b;c))) Q β§ True"
using assms
by simp
lemma
assumes "hoare P (while P (c;d;e)) Q"
assumes "βx. P x = R x"
shows "hoare P ((while P (c;(d;e))); (while R ((c;d);e))) Q"
using assms by simp
lemma
assumes "βx. Q x"
assumes "hoare (Ξ»m. P x β§ Q x) (while P (c;(d;e);(f;g);c)) (Ξ»m. m=m)"
shows "hoare (Ξ»m. P x) (while P (c;(d;e);ifthen (Ξ»m. m=m) (f;g;c))) (Ξ»m. True)"
using assms by simp
(* Test: Disabling the simproc *)
lemma
assumes "hoare P (a;(b;c)) Q"
shows "hoare P (a;(b;c)) Q β§ True"
using[[simproc del: hoare_simproc]] -- "Without this, proof fails"
apply simp
by (fact assms)
end
source to share