Skip to content

Conversation

@michaelmcinerney
Copy link
Contributor

This work should be rebased over the work Gerwin has adding the declaring method, and then the automation should be updated to support the del: feature. I will do that once that work is merged. But I thought I would make this PR now so that people could comment on it, just in case it generated a lot of thoughts/comments.

@lsf37
Copy link
Member

lsf37 commented Feb 2, 2026

The declaring method from #950 is now merged.

@michaelmcinerney michaelmcinerney force-pushed the michaelm-rules_for_Lib_Jan26 branch from 70cd9c9 to c03b79f Compare February 3, 2026 02:48
Signed-off-by: Michael McInerney <michael.mcinerney@proofcraft.systems>
This adds rcorres, an approach to correspondence with general
relations. Many lemmas to use this concept are included, as
well as a lightweight method that is very similar to wpsimp.

Signed-off-by: Michael McInerney <michael.mcinerney@proofcraft.systems>
@michaelmcinerney michaelmcinerney changed the base branch from michaelm-rules_for_Lib_Jan26 to master February 4, 2026 23:26
@michaelmcinerney
Copy link
Contributor Author

Now including the rcorres_del feature, as well as a method I have used to solve or make progress on rcorres goals via lifting.

Instead of targeting the other PR with Lib rules, this now targets master, and I have included from that PR only one commit that was necessary.

lib/RCorres.thy Outdated
Corres_UL DetWPLib SubMonadLib
begin

text \<open>An approach to correspondence with general relations\<close>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit terse, which is the Isabelle tradition, but we're trying not to do that :-)

As a heading (eg. section) it is fine, but it should be followed by a bit more text that describes what it does and what it is intended for (can be a forward reference to examples after the defs/lemmas are there).

lib/RCorres.thy Outdated
Comment on lines 45 to 46
assumes det: "\<And>s'. det_wp (\<lambda>s. R s s') f"
assumes valid: "\<And>rv s t. (rv, t) \<in> fst (f s) \<Longrightarrow> \<lbrace>\<lambda>s'. R s s'\<rbrace> f' \<lbrace>\<lambda>rv' t'. R' rv rv' t t'\<rbrace>"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're doing using assms you don't need to name them:

Suggested change
assumes det: "\<And>s'. det_wp (\<lambda>s. R s s') f"
assumes valid: "\<And>rv s t. (rv, t) \<in> fst (f s) \<Longrightarrow> \<lbrace>\<lambda>s'. R s s'\<rbrace> f' \<lbrace>\<lambda>rv' t'. R' rv rv' t t'\<rbrace>"
assumes "\<And>s'. det_wp (\<lambda>s. R s s') f"
assumes "\<And>rv s t. (rv, t) \<in> fst (f s) \<Longrightarrow> \<lbrace>\<lambda>s'. R s s'\<rbrace> f' \<lbrace>\<lambda>rv' t'. R' rv rv' t t'\<rbrace>"

lib/RCorres.thy Outdated
Comment on lines 48 to 54
using assms
apply (clarsimp simp: rcorres_def det_wp_def)
apply (rename_tac s s' rv' t')
apply (drule_tac x=s' in meta_spec)
apply (drule_tac x=s in spec)
apply (force simp: det_wp_def rcorres_def valid_def)
done
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consideration:

Suggested change
using assms
apply (clarsimp simp: rcorres_def det_wp_def)
apply (rename_tac s s' rv' t')
apply (drule_tac x=s' in meta_spec)
apply (drule_tac x=s in spec)
apply (force simp: det_wp_def rcorres_def valid_def)
done
using assms
by (force simp: rcorres_def det_wp_def valid_def)

works, but is slow. The by means it doesn't matter much that it is slow, but it's still a trade-off. Personally I like to avoid instantiations when I can, but it is mostly a matter of preference, I guess.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like avoiding instantiations, too, and if you're happy with slow proofs in Lib, I'll go with your suggestion. I sometimes try to imagine how much an extra 2 seconds in Lib that's run all the time will affect the overall time these proofs will run in the future. But I guess that's already a lost cause.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It won't affect the wall time of a run of Lib -- because of the by the force will run in parallel with the rest and we rarely actually use all cores. This one only affects CPU time and potential impatience :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I’ll go with the by, thanks

Comment on lines +76 to +77
lemma rcorres_req:
"\<lbrakk>\<And>s s'. R s s' \<Longrightarrow> F; F \<Longrightarrow> rcorres R f f' R'\<rbrakk> \<Longrightarrow> rcorres R f f' R'"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does req stand for? Not that I have a better name. The normal corres rules have something similar with corres_gen_asm. Not quite the same, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm actually not sure what req stands for. I'm just copying the format of corres_req, which also has just a plain schematic in the guard and then produces a new assumption. I would only use this rule when the precondition is schematic and I'm doing a bit of forwards reasoning, and it works nicely for that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that's where it comes from. I don't think I have used that rule yet. The req is a bit obscure, I'm not sure we should proliferate it, but I also don't have a good suggestion. @Xaphiosis or @corlewis do you have any ideas on this one?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do also have monadic_rewrite_req in the other PR, so yeah we should definitely decide on that before we go futher.

\<Longrightarrow> rcorres R f f' R'"
by (fastforce simp: rcorres_def)

lemma rcorres_req:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest adding the following rule for when you don't want to spell out F:

lemma rcorres_assume_pre:
  "⟦⋀s s'. R s s' ⟹ rcorres R f f' R'⟧ ⟹ rcorres R f f' R'"
  by (rule rcorres_req)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to put an and (%_ _. F) or something in there?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no F at all in this rule (this is not a replacement of the _req rule, but an additional rule)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh OK. I think I have seen rules like this before, but they're not something I've used. I'll add this one in, thanks.

Comment on lines 129 to 131
lemma rcorres_assert_l:
"\<lbrakk>\<And>s s'. R s s' \<Longrightarrow> P; rcorres (\<lambda>s s'. R s s' \<and> P) (b ()) c R'\<rbrakk>
\<Longrightarrow> rcorres R (assert P >>= b) c R'"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this behaves nicely in backward reasoning -- requiring R s s' ==> P means that you already need to have a fully instantiated R that is also strong enough for P.

I'd have expected something like

lemma rcorres_assert_l:
  "⟦P ⟹ rcorres R (b ()) c R'⟧ ⟹ rcorres (λs s'. R s s' ∧ P) (assert P >>= b) c R'"
  by (fastforce intro: rcorres_assume_pre)

Maybe we do want both, though. Your formulation is nice at the beginning for sp-style reasoning when you have a bunch of asserts at the beginning of a function that you want to remove.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be using this one for forwards reasoning. I could definitely look to add backwards versions of many of the rules I have here.

Early on when we were discussing rcorres, you said that I shouldn't try to write a complete rule set for it, so the rules I have here are all actually used. But I can look to flesh things out some more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you have a backwards rule, you can immediately use it after corres_pre even if you are going forward, so I would usually write the more general rule.

Comment on lines 138 to 140
lemma rcorres_assert_r:
"\<lbrakk>\<And>s s'. R s s' \<Longrightarrow> P; rcorres (\<lambda>s s'. R s s' \<and> P) a (c ()) R'\<rbrakk>
\<Longrightarrow> rcorres R a (assert P >>= c) R'"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(same as for assert_l)

Comment on lines +152 to +153
lemma rcorres_return_stateAssert:
"rcorres (\<lambda>s s'. R' s s' \<and> P s') (return ()) (stateAssert P []) (\<lambda>_ _. R')"
Copy link
Member

@lsf37 lsf37 Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works (and I would keep it), but do we also want an assert_l/assert_r style rule for stateAssert? It's a bit more awkward in backwards style, because of the state dependency, so you might only do something like:

lemma rcorres_stateAssert_l:
  "⟦P ⟹ rcorres R (b ()) c R'⟧ ⟹ rcorres (λs s'. R s s' ∧ P s) (stateAssert P xs >>= b) c R'"
  by (fastforce intro: rcorres_assume_pre)

That seems roughly equivalent to what we get in the return_stateAssert rule.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the P depends on the abstract state, the P in the assumptions for this rule doesn't type check. If you just remove the P, it works, it would then be exactly like corres_stateAssert_r, so I guess that's nice and maybe what we'd like. I think that if we want to add the asserted property to the guard, then we'd need to use the forwards style rule, which has the analogue in corres world of corres_stateAssert_add_assertion. Or maybe my brain isn't sufficiently backwards(-style) and I'm wrong.

lib/RCorres.thy Outdated
Comment on lines 344 to 346
method rcorres uses wp simp declares rcorres rcorres_lift =
wp_pre,
((rcorres_step rcorres: rcorres rcorres_lift: rcorres_lift wp: wp simp: simp)+)[1]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also add rcorres_del here as a parameter and pass it through to rcorres_step, since rcorres will be the main method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yep, that was a bad oversight. Thanks.

rule rcorres_weaken_pre,
(rule rcorres_lift, (solves \<open>rule rule\<close> | solves \<open>wpsimp wp: wp simp: simp\<close>)+)[1],
solves solve

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The methods look good, we should add a few examples in an experiment sections for showing intended use. You can also add an RCorres_Test file instead if you don't want to clutter up the theory here, but I think it's probably small enough.

lib/RCorres.thy Outdated
Comment on lines 159 to 162
lemma rcorres_gets_the:
"\<lbrakk>\<forall>rv rv' s s'. P s s' \<and> f s = Some rv \<and> f' s' = Some rv' \<longrightarrow> rrel rv rv';
\<And>s'. no_ofail (\<lambda>s. P s s') f \<rbrakk>
\<Longrightarrow> rcorres P (gets_the f) (gets_the f') (\<lambda>rv rv' _ _. rrel rv rv')"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also an sp-style rule. Again, I would keep it, because it is useful at the beginning of functions, but I'd also provide a wp rule where the post condition is just Q and the precondition is some transformation of Q. (Probably just Q rv rv' s s' with some conjunction about rrel rv rv', but I haven't investigated further).

lib/RCorres.thy Outdated
done

lemma rcorres_gets_the:
"\<lbrakk>\<forall>rv rv' s s'. P s s' \<and> f s = Some rv \<and> f' s' = Some rv' \<longrightarrow> rrel rv rv';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"\<lbrakk>\<forall>rv rv' s s'. P s s' \<and> f s = Some rv \<and> f' s' = Some rv' \<longrightarrow> rrel rv rv';
"\<lbrakk>\<forall>rv rv' s s'. P s s' --> f s = Some rv --> f' s' = Some rv' \<longrightarrow> rrel rv rv';

We should almost always write implications as nested implications, not as conjunctions. The difference is that nested implications can be solved step-wise, whereas if it is a conjunction, you have to do all in one go. Automation deals better with the first kind.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would in this case probably make them all meta implications anyway, i.e.

[| !!rv rv' s s'. [| P s s'; f s = Some rv; f' s' = Some rv' |] ==> rrel rv rv'; ...

Comment on lines 167 to 170
lemma rcorres_rrel:
"\<lbrakk>rcorres P (gets_the f) (gets_the g) (\<lambda>rv rv' _ _. rrel rv rv'); P s s';
f s = Some rv; g s' = Some rv'\<rbrakk>
\<Longrightarrow> rrel rv rv'"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting rule. I have the feeling there is something more general lurking here, some equivalent of use_valid for normal wp. Probably something like:

  "⟦rcorres P f g Q; P s s'; (rv, t) : f s; (rv', t') : g s'⟧ ⟹ Q rv rv' s s'"

You could then use that to prove the gets_the variant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would require determinacy for the function f. This will still give us the gets_the variant, so I guess it's OK. Shall I go with that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stating det_wp in the assumptions for f is not quite what we want, instead it should be fst (f s) = { (rv, t) }, so that's what I'd put, if we wanted this.

lib/RCorres.thy Outdated
Comment on lines 188 to 195
lemma rcorres_split_gets_the:
"\<lbrakk>rcorres R (gets_the f) (gets_the g) (\<lambda>rv rv' _ _. rrel rv rv');
\<And>s'. no_ofail (\<lambda>s. R s s') f;
\<And>rv rv'.
rcorres
(\<lambda>s s'. R s s' \<and> f s = Some rv \<and> g s' = Some rv' \<and> rrel rv rv' )
(b rv) (d rv') R'\<rbrakk>
\<Longrightarrow> rcorres R (gets_the f >>= b) (gets_the g >>= d) R'"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The shared R between the conclusion and the rcorres and no_ofail assumption means you can only use this in forward reasoning at the beginning. If you write it as

Suggested change
lemma rcorres_split_gets_the:
"\<lbrakk>rcorres R (gets_the f) (gets_the g) (\<lambda>rv rv' _ _. rrel rv rv');
\<And>s'. no_ofail (\<lambda>s. R s s') f;
\<And>rv rv'.
rcorres
(\<lambda>s s'. R s s' \<and> f s = Some rv \<and> g s' = Some rv' \<and> rrel rv rv' )
(b rv) (d rv') R'\<rbrakk>
\<Longrightarrow> rcorres R (gets_the f >>= b) (gets_the g >>= d) R'"
lemma rcorres_split_gets_the:
"\<lbrakk>rcorres R1 (gets_the f) (gets_the g) (\<lambda>rv rv' _ _. rrel rv rv');
\<And>s'. no_ofail P f;
\<And>rv rv'.
rrel rv rv' ==> rcorres
R2
(b rv) (d rv') R'\<rbrakk>
\<Longrightarrow> rcorres (R1 and (%s _. P s) and (%s s'. f s = Some rv --> g s' = Some rv' --> rrel rv rv' --> R2 s s') (gets_the f >>= b) (gets_the g >>= d) R'"

you can also use it in the middle of a proof (might be slightly messed up, GitHub is not the best place to type formulas).

We might also a variant of this, where the gets_the rule is already applied to the first assumption, not sure.

lib/RCorres.thy Outdated
Comment on lines 210 to 211
lemma rcorres_if_r:
"\<lbrakk>b \<Longrightarrow> rcorres R f f' R'; \<not> b \<Longrightarrow> rcorres R f g' R'\<rbrakk> \<Longrightarrow> rcorres R f (if b then f' else g') R'"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These also only work for instantiated preconditions. You'd generally want the preconditions of the two branches to be independent and the precondition of the entire if the conjunction of the two.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(We should also add an _l rule)

Comment on lines +217 to +221
lemma rcorres_conj_lift:
"\<lbrakk>\<And>s'. det_wp (\<lambda>s. P s s') f; rcorres Q f f' R'; rcorres R f f' Q'\<rbrakk>
\<Longrightarrow> rcorres
(\<lambda>s s'. P s s' \<and> Q s s' \<and> R s s')
f f' (\<lambda>rv rv' s s'. R' rv rv' s s' \<and> Q' rv rv' s s')"
Copy link
Member

@lsf37 lsf37 Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The det_wp in this one is surprising, I'd have thought it would work without. Is there a similar rule that would work without it? Not saying we should get rid of this one, just trying to get an intuition. (I see it's a theme in the other rules as well)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was the argument about not having a conj lift rule for exs_valid, or not being able to go from (Ex x. P x) /\ (Ex x. Q x) to Ex x. P x /\ Q x. We may be able to have one state that satisfies R' and then perhaps another state that satisfies Q', but not one state that satisfies both. Forcing the function to be deterministic means that the unique state satisfies both.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, of course, that's what it is and that is also why it doesn't behave like any of the other forms of Hoare triples -- the post condition is existential in one and universal in the other state parameter, so you get effects of both. I think that would be worth a comment somewhere, either at this rule or at the definition of rcorres. Because it's really something slightly new.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing. I could even explain there why we might not want to introduce anything about failure into rcorres itself. It might be a bit of an essay, but I could try if you'd like.

lib/RCorres.thy Outdated
Comment on lines 21 to 23
lemma rcorres_return:
"(\<And>s s'. R s s' \<Longrightarrow> R' v v' s s') \<Longrightarrow> rcorres R (return v) (return v') R'"
by (clarsimp simp: rcorres_def return_def)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one should move down to where the bind rule is (unless it's needed before)

Copy link
Member

@lsf37 lsf37 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some suggestions for tweaks and a request for more documentation (mostly examples). But I think this is looking pretty nice!

It's interesting to see where it behaves exactly like Hoare triples and where it deviates -- mostly around failure. Maybe it would correspond more closely to total correctness, but I don't think it quite does that either.

Signed-off-by: Michael McInerney <michael.mcinerney@proofcraft.systems>
@michaelmcinerney
Copy link
Contributor Author

I've updated this, addressing all the comments so far, except the experiment section for the lifting method. I'll do that early next week.

I'm expecting that we will want to still tweak things quite a lot, so please let me know if there are any further changes I should make to these changes.

@michaelmcinerney
Copy link
Contributor Author

Actually, I'm not sure if chapter is the right keyword, or if it's section or subsection that we want. I guess it's chapter to begin with, and then section. But I can change this later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants