Skip to content

Comments

Add rcorres definition, lemmas, and automation#959

Open
michaelmcinerney wants to merge 5 commits intomasterfrom
michaelm-rcorres
Open

Add rcorres definition, lemmas, and automation#959
michaelmcinerney wants to merge 5 commits intomasterfrom
michaelm-rcorres

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
@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
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.

Copy link
Member

Choose a reason for hiding this comment

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

@Xaphiosis, @corlewis do you have any naming input on _req?

Copy link
Member

Choose a reason for hiding this comment

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

Ah, I've seen corres_req before but also never had an idea of what the req stood for.

For an alternative name, I think the equivalent valid lemma is hoare_gen_asm_spec, so maybe this one could be rcorres_gen_asm_spec?

Copy link
Member

@lsf37 lsf37 Feb 17, 2026

Choose a reason for hiding this comment

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

I've just ran a grep, and apparently we've been using ccorres_req and ccorres_req quite a bit (>270 occurrences). Maybe we should just leave it as _req after all even though nobody knows what it was supposed to mean. It's probably more confusing if we use the valid convention here than the corres one.

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.

Copy link
Member

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.

You are right, of course. We could do !!s. P s ==> .., but that is generally not so nice (it's sound but will usually just pollute the goal, because the s is not related to anything, and making it related to the precondition will make the precondition not schematic). So just leaving it out gives us the backwards rule, yes.

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.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks for adding the explanation, I think that is good. I would add a summary side sentence on the universal/existential duality, will comment above.

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.

@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.

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

Now with the experiment section, using section instead of chapter, and some adjustments to the comments.

lib/RCorres.thy Outdated
Comment on lines 502 to 503
apply rcorres
by simp
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 change all of these to:

Suggested change
apply rcorres
by simp
apply rcorres
apply simp
done

apply scripts should generally end with done. We use by when it's a one-liner or when there is a good other reason like the last step taking long and therefore benefitting from concurrency.

There are a few in the original PR that I didn't comment on because they looked like they are slow steps (e.g. by force or by (fastforce ..)), but if they are not slow we should also change them to apply/done for consistency.

result of @{term f} such that the desired conjunction holds. The @{term det_wp}
assumption does allow us to make this inference, and so we include this assumption in our rule.

It does not seem possible for us to state an adequate lifting rule for conjunction for
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
It does not seem possible for us to state an adequate lifting rule for conjunction for
Overall, because the precondition is universal, but the postcondition is existential,
it does not seem possible for us to state an adequate lifting rule for conjunction for

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you mean the precondition and the postcondition, or do you mean the concrete state is universal and the abstract state is existential?

Copy link
Member

Choose a reason for hiding this comment

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

I mean the conditions. The definition is forall s s' in P, there exists t t' in execution such that Q t t', so the P behaves universally and the Q existentially.

Thinking more about it, I wonder if we should change it so that that Q is also universal, but that doesn't make sense if we view Q as a state relation instead of a kind of validity. So probably not.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It doesn't make sense to me, but I can add this.

Copy link
Member

Choose a reason for hiding this comment

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

Which bit doesn't make sense? That a condition behaves universally/existentially? I admit that's not exactly a defined expression, but I thought it carries some intuition at least. If it just confuses people we should not add it.

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 that if we were to say the precondition is universal, but the postcondition is existential, then someone would think we're doing something along the lines of "for all preconditions P, there is a postcondition Q, such that...", which is not what we're doing, so might be more confusing than helpful. I think the comment I have there now is way too long, but I can't think of a way to make it both precise and short. I think some will be happy to just go along with the det_wp assumption being there, and for those who are sceptical or interested, the explanation is easy enough to follow.

Copy link
Member

Choose a reason for hiding this comment

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

I had not thought of that reading. Ok, let's not add anything, your explanation is clear enough.

lib/RCorres.thy Outdated
Comment on lines 321 to 323
From our assumptions, we know that there is there is some pair @{term "(rv_1, t_1)"} in the result
of @{term f} such that @{term "R' rv_1 rv' t_1 t'"} holds, and that there is some pair
@{term "(rv_2, t_2)"} in the result of @{term f} such that @{term "Q' rv_2 rv' t_2 t' holds"}.
Copy link
Member

Choose a reason for hiding this comment

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

The Isabelle convention is without the underscore (unlike LaTeX) -- mostly because the underscore is not rendered as subscript. We could use actual Isabelle subscript notation (that is fine), but it's awkward to type, so we usually don't.

I.e. I would propose just

Suggested change
From our assumptions, we know that there is there is some pair @{term "(rv_1, t_1)"} in the result
of @{term f} such that @{term "R' rv_1 rv' t_1 t'"} holds, and that there is some pair
@{term "(rv_2, t_2)"} in the result of @{term f} such that @{term "Q' rv_2 rv' t_2 t' holds"}.
From our assumptions, we know that there is there is some pair @{term "(rv1, t1)"} in the result
of @{term f} such that @{term "R' rv1 rv' t1 t'"} holds, and that there is some pair
@{term "(rv2, t2)"} in the result of @{term f} such that @{term "Q' rv2 rv' t2 t' holds"}.

lib/RCorres.thy Outdated
of @{term f} such that @{term "R' rv_1 rv' t_1 t'"} holds, and that there is some pair
@{term "(rv_2, t_2)"} in the result of @{term f} such that @{term "Q' rv_2 rv' t_2 t' holds"}.
However, without any further assumptions on @{term f}, we cannot infer that
@{term "(rv_1, t_1) = (rv_2, t_2)"} and therefore that there is a pair @{term "(rv, t)"} in the
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
@{term "(rv_1, t_1) = (rv_2, t_2)"} and therefore that there is a pair @{term "(rv, t)"} in the
@{term "(rv1, t1) = (rv2, t2)"} and therefore that there is a pair @{term "(rv, t)"} in the

lib/RCorres.thy Outdated
monadic function @{term f'}, it is occasionally useful for us to be able to state a relation
between all four values from the pair @{term "(rv', t')"} in the set of results of @{term f'}
and the pair @{term "(rv, t)"} in the set of results of @{term f}. This is a partial
generalisation of our usual @{term corres_underlying} framework, which allows for us to state a
Copy link
Member

Choose a reason for hiding this comment

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

Nitpick: @{term } is a good default, but if we're referring to a single constant, we should use @{const } because Isabelle will then check that the constant exists, which means you get an error if someone changes the constant and you can update the documentation.

Suggested change
generalisation of our usual @{term corres_underlying} framework, which allows for us to state a
generalisation of our usual @{const corres_underlying} framework, which allows for us to state a

(For that to work, the constant has to be declared already, so it often can't be used in the explanation of the constant at the definition place, but it is available everywhere after).

lib/RCorres.thy Outdated
relate to the returned states. An example that is used within the proofs is list_queue_relation.

This shares many similarities with Hoare triples, as seen by the split rule for
@{term Nondet_Monad.bind}, as well as the lifting rules, though there are some important
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
@{term Nondet_Monad.bind}, as well as the lifting rules, though there are some important
@{const Nondet_Monad.bind}, as well as the lifting rules, though there are some important

lib/RCorres.thy Outdated
@{term Nondet_Monad.bind}, as well as the lifting rules, though there are some important
differences involving determinacy that are explained below.

Note that rcorres is not a full generalisation of @{term corres_underlying}, in that it does not
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
Note that rcorres is not a full generalisation of @{term corres_underlying}, in that it does not
Note that rcorres is not a full generalisation of @{const corres_underlying}, in that it does not

lib/RCorres.thy Outdated
play a prominent role in the proofs we wish to perform for rcorres goals. We have lifting rules
for the usual Boolean connectives, but will also wish to lift an rcorres goal to a @{term valid}
goal when the postcondition of the rcorres goal mentions only the return value and state from
one of the two monads. These lifting rules often require @{term no_fail} assumptions, and so when
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
one of the two monads. These lifting rules often require @{term no_fail} assumptions, and so when
one of the two monads. These lifting rules often require @{const no_fail} assumptions, and so when

lib/RCorres.thy Outdated
goal when the postcondition of the rcorres goal mentions only the return value and state from
one of the two monads. These lifting rules often require @{term no_fail} assumptions, and so when
lifting an rcorres goal with a concrete function that is a @{term Nondet_Monad.bind} of other
functions, we will immediately be required to show @{term no_fail} for the composite function
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
functions, we will immediately be required to show @{term no_fail} for the composite function
functions, we will immediately be required to show @{const no_fail} for the composite function

lib/RCorres.thy Outdated
lifting an rcorres goal with a concrete function that is a @{term Nondet_Monad.bind} of other
functions, we will immediately be required to show @{term no_fail} for the composite function
anyway. Therefore, we have chosen to keep failure separate. The section below regarding
interactions with @{term no_fail} includes a rule that allows us to show @{term no_fail} for
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
interactions with @{term no_fail} includes a rule that allows us to show @{term no_fail} for
interactions with @{const no_fail} includes a rule that allows us to show @{const no_fail} for

lib/RCorres.thy Outdated
anyway. Therefore, we have chosen to keep failure separate. The section below regarding
interactions with @{term no_fail} includes a rule that allows us to show @{term no_fail} for
composite functions by transforming complex predicates with rcorres. The section below regarding
@{term corres_underlying} shows the relation between rcorres and @{term corres_underlying}.\<close>
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
@{term corres_underlying} shows the relation between rcorres and @{term corres_underlying}.\<close>
@{const corres_underlying} shows the relation between rcorres and @{const corres_underlying}.\<close>

Copy link
Member

Choose a reason for hiding this comment

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

(I've only done this paragraph, because I've just re-read it -- please have a look if there are more instances below. We might as well make it nice while we're here.)

lib/RCorres.thy Outdated
Comment on lines 333 to 335
"\<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')
Copy link
Member

@lsf37 lsf37 Feb 16, 2026

Choose a reason for hiding this comment

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

Now that I've understood the need for the det_wp -- do we really want it to be !!s'?

I think the following would be equivalent

Suggested change
"\<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')
"\<lbrakk>det_wp P f; rcorres Q f f' R'; rcorres R f f' Q'\<rbrakk>
\<Longrightarrow> rcorres
(\<lambda>s s'. P s \<and> Q s s' \<and> R s s')

because P can always be instantiated as %s. Ex s'. P s s' if we want it to. The only time we'd want the forall form is if we want to use this rule as a forward rule where the P already exists in the rcorres goal and instantiates the one for det_wp.

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 can make this change, but then I'm not sure how I would use a lemmas statement to get the forwards version. So I think I'd have to write the forwards version out in full. If that's alright, I'll go with that.

lib/RCorres.thy Outdated
\<open>As with @{thm rcorres_conj_lift}, the @{term det_wp} assumption seems necessary in order
to state an adequate lifting rule for @{term All}.\<close>
lemma rcorres_allI:
"\<lbrakk>\<And>s'. det_wp (\<lambda>s. P s s') f; \<And>x. rcorres (\<lambda>s s'. R x s s') f f' (\<lambda>rv rv' s s'. R' x rv rv' s s')\<rbrakk>
Copy link
Member

Choose a reason for hiding this comment

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

if we decide to change the forall for det_wp above, we should also change it here and below

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.

Thanks for adding the experiments section, I think that works fine. The new explanations prompted me to go back through some of the details -- have a look what you think.

Signed-off-by: Michael McInerney <michael.mcinerney@proofcraft.systems>
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.

3 participants