It would be nice to have some theory for taking inverse images of finite maps.
For example, if I have a map m : gmap nat string, then I want to obtain an inverse m^-1 : gmap string (gset nat) such that
m^-1 !! s = Some X ↔ (∀ x, x ∈ X ↔ m !! x = Some s)
Designs
Child items
...
Show closed items
Linked items
0
Link issues together to show that they're related.
Learn more.
This would indeed be cool! I've written a preimage function for functions from finite types in https://gist.github.com/Blaisorblade/e57f263840cd8ff2263a4a493b17e2b5, and it has the property in general. Without Finite A you can probably iterate over the codomain of m instead of enum A to get the same properties. Most relevant section:
Sectionfinite_preimage.Context`{FiniteA}`{EqDecisionB}.ImplicitTypes(a:A)(b:B)(f:A→B).Definitionfinite_preimagefb:listA:=filter(λa,fa=b)(enumA).Lemmaelem_of_finite_preimagefab:a∈finite_preimagefb↔fa=b.Proof.apply:elem_of_filter_enum.Qed.(** Teach [set_solver] to use [elem_of_finite_preimage]! *)#[global]Instanceset_unfold_finite_preimagefabP:SetUnfold(fa=b)P→SetUnfoldElemOfa(finite_preimagefb)P.Proof.split.rewriteelem_of_finite_preimage.set_solver.Qed.Lemmafinite_preimage_inj_singleton`{!Injeqeqf}a:finite_preimagef(fa)=[a].Proof.applylist_singleton_eq_ext.{applyNoDup_filter,NoDup_enum.}set_solver.Qed.Definitionfinite_inversefb:optionA:=head$finite_preimagefb.Lemmafinite_inverse_Some_invfab:finite_inversefb=Somea→fa=b.Proof.introsHof%head_Some_elem_of.set_solver.Qed.Lemmafinite_inverse_is_Somefab:fa=b→is_Some(finite_inversefb).Proof.introsHeq%elem_of_finite_preimage%elem_of_not_nil.exact/head_is_Some.Qed.Lemmafinite_inverse_None_equivfb:finite_inversefb=None↔¬(∃a,fa=b).Proof.rewriteeq_None_not_Some.f_equiv.split.{intros[a?%finite_inverse_Some_inv].byexistsa.}byintros[a?%finite_inverse_is_Some].Qed.#[global]Instanceset_unfold_finite_inverse_NonefbP:(∀a,SetUnfold(fa=b)(Pa))→SetUnfold(finite_inversefb=None)(¬(∃a,Pa)).Proof.constructor.rewritefinite_inverse_None_equiv.set_solver.Qed.Sectionfinite_preimage_inj.Context`{Hinj:!Injeqeqf}.#[local]SetDefaultProofUsing"Type*".Lemmafinite_inverse_inj_cancela:finite_inversef(fa)=Somea.Proof.byrewrite/finite_inversefinite_preimage_inj_singleton.Qed.Lemmafinite_inverse_inj_Some_equivab:finite_inversefb=Somea↔fa=b.Proof.naive_solvereautousingfinite_inverse_Some_inv,finite_inverse_inj_cancel.Qed.#[global]Instanceset_unfold_finite_inverse_inj_SomeabP:SetUnfold(fa=b)P→SetUnfold(finite_inversefb=Somea)P.Proof.constructor.rewritefinite_inverse_inj_Some_equiv.set_solver.Qed.Endfinite_preimage_inj.Endfinite_preimage.
The gist has more code, and https://github.com/bedrocksystems/BRiCk is the underlying source but it's probably outdated. Note the above is not BSD-licensed for reasons, but we always relax the license when we upstream things.
I forgot to post the functions for the set -> set preimage:
Definition set_concat_map (f : A → list B) (xs : C) : D := list_to_set (elements xs ≫= f). Definition finite_preimage_set (f : A → B) (bs : D) : C := set_concat_map (finite_preimage f) bs.
I wrote it this way, avoiding set_fold/map_fold, because IME proofs about bind are nice and proofs about fold are very annoying. Proofs on set_concat_map proceed by set_solver with a few extra instances :-)
A possible common abstraction: you can invert a (partial) mapping f from A to B, with the constructions I use above, if the domain of f is finite:
ClassFiniteDomain{ABM}`{LookupAB(MB)}(m:MB):={enum_domain:listA;finite_mapping(a:A)(b:B):m!!a=Someb->a∈enum_domain;}.Argumentsenum_domain{ABM_}m{_}:assert.Sectionfin_domain.Context`{EqDecisionB}.Context{AM}(m:MB)`{FiniteDomainABMm}.Definitionpreimage(b:B):listA:=filter(λa,m!!a=Someb)(enum_domainm).Context`{FinSetAC}`{FinSetBD}.Definitionpreimage_set(bs:D):C:=set_concat_mappreimagebs.Endfin_domain.Sectionfinite_preimage.Context`{FiniteA}{B:Type}(f:A->B).#[local]SetDefaultProofUsing"Type*".RequireImportstdpp.functions.(* Instance fn_lookup_total : LookupTotal A B (A -> B) := λ a f, f a. *)Instancefn_lookup:LookupAB(A->B):=λaf,Some(fa).#[refine]Instancefinite_finitedomain:FiniteDomain(M:=λB,A->B)f:={enum_domain:=enum_}.Proof.set_solver.Qed.Endfinite_preimage.Sectionmap_preimage.Context{KA:Type}`{FinMapKM}(m:MA).#[local]SetDefaultProofUsing"Type*".#[refine]Instancegmap_finitedomain:FiniteDomainm:={enum_domain:=fst<$>map_to_listm}.(* XXX fix set_solver. *)Proof.move=>ka/elem_of_map_to_list.apply:elem_of_list_fmap_1.Qed.Endmap_preimage.
Those proofs are good, and a bit more work can probably reduce them all to set_solver; for that, I should probably fix finite_mapping — right now it allows enum_domain to contain too many elements. FinMapDom gets that right, but it bundles a lot more things.
@dfrumin regarding "ease of proof" — did you also run into trouble with folds? That's my experience and the lemmas look harder, but I'm curious if others have different experiences.
Sorry, I am not sure I understand -- what folds are you talking about? If you mean the use of set_fold in my original proposal, then yeah it was painful to work with.
This interface seems to complicate any code sharing with function inverses, compared to the version in #140. The proofs also look more complex due to the use of folds, but maybe there’s a reason for it? The remaining question there is that FinMapDom cannot be “just” instantiated for functions currently, so one would need to add more instances to use functions as maps, or to generalize FinMapDom to domains of non-maps.
The main reason why the code of this MR is a bit more complex is that our preimage is actually a finite map, not just an ordinary function. Finite maps are quite a bit harder to construct but are "nicer" since they have a better equality and can more easily be used as ghost state in Iris.
(I personally have no skin in this game, I just hope y'all can reach consensus. :) I would be fine with reverting the MR for now to avoid having a "default" solution that some people are unhappy with.)
The main reason why the code of this MR is a bit more complex is that our preimage is actually a finite map, not just an ordinary function. Finite maps are quite a bit harder to construct but are "nicer" since they have a better equality and can more easily be used as ghost state in Iris.
I think I didn't explain properly which code I'm discussing, and maybe I should send an MR — above I have a definition that works for maps just fine — let me highlight the core:
That is, the preimage of b in map m is computed by taking the domain of m, and filter the elements that map to b. Since the theory of filter is much better than the theory of map_fold (and folds in general), proofs become much easier assuming FinMapDom. However, does map_fold have better constant factors than filter somehow?
Good point... avoiding folds might be possible but is not as easy, and the advantage is less clear.
And my overall goal is to reuse this preimage to invert functions on finite domains — bedrock.prelude supports that but with a different setup.
One option is to actually prove Finite A -> FinMap A (fun B => A -> B), or at least a large part of it (basically, dom (f : A -> B) := enum A). But would Coq infer such instances robustly? (I am tempted to wrap functions in a newtype). With my alternative definitions, fewer "map" instances for finite functions were required, and everything worked fine.