feat(Data): Results about RelatesInSteps with bounds on the reachable set - #779
feat(Data): Results about RelatesInSteps with bounds on the reachable set#779crei wants to merge 6 commits into
Conversation
9a46a2d to
cce51a6
Compare
| i.e. `r (f i) (f (i + 1))` holds for every `i < n`. The values of `f` beyond index `n` are | ||
| irrelevant. | ||
| -/ | ||
| def IsPath (r : α → α → Prop) (f : ℕ → α) (n : ℕ) : Prop := ∀ i < n, r (f i) (f (i + 1)) |
There was a problem hiding this comment.
Why is this not just a list of elements? This reminds me a lot of Execution in LTS and its omega-counterpart. What you have here looks like the omega-sequence infinite execution concept, but you use only a finite part of it.
@ctchou, what do you think?
There was a problem hiding this comment.
In general I'm otherwise very positive about this. We should just make sure that the API experience between Relation and LTS for these things is similar enough to be familiar to people using both.
There was a problem hiding this comment.
for a list this is exactly List.IsChain — i would advocate not duplicating that definition (EDIT: by which i mean using that definition instead of IsPath — unless there is some reason the indexing over Nat is necessary)
There was a problem hiding this comment.
on this point, here's the equivalent RelatesInSteps.exists_isPath phrased using List.IsChain — to my mind the proof is simpler
theorem RelatesInSteps.exists_isChain {a b : α} {n : ℕ} (h : RelatesInSteps r a b n) :
∃ (l : List α), (a :: l).IsChain r ∧ b ∈ (a :: l).getLast? ∧ l.length = n := by
induction h using RelatesInSteps.head_induction_on with
| hrefl => use (discharger := simp) []
| @hhead a c n h h' ih =>
obtain ⟨l, hchain, hb, hlen⟩ := ih
use c :: l
grind
for other results, note the existing api connected to Relation.ReflTransGen, and List.isChain_ofFn, which is more-or-less the Fin n version of your IsPath
| theorem IsPath.exists_eq_of_ncard_le {f : ℕ → α} {n : ℕ} | ||
| (hf : IsPath r f n) | ||
| (hfin : Set.Finite (ReflTransGen r (f 0))) | ||
| (hn : Set.ncard (ReflTransGen r (f 0)) ≤ n) : |
There was a problem hiding this comment.
i think using Set.ncard on the predicate ReflTransGen r (f 0) should be considered defeq abuse — the preferred spelling would be Set.ncard {x | ReflTransGen r (f 0) x}. also, the assumptions hfin and hn can be collapsed to Set.encard {...} ≤ n
There was a problem hiding this comment.
I wonder if it makes sense to give this a name:
abbrev ReachableFrom (r : α → α → Prop) (a : α) := {x | ReflTransGen r a x}
ctchou
left a comment
There was a problem hiding this comment.
Sorry, I just saw this PR. I agree with @thomaskwaring that List.IsChain probably can do everything that IsPath can and has the advantage of having the enormous set of results about List in mathlib at its disposal.
Another potential problem with IsPath is that only the first n elements of the infinite sequence f matter. So when you need to exhibit an f, you need to supply a dummy value for all elements in f beyond the first n elements. This is not exactly elegant and may require you to add an assumption like [Inhabited α] in some situations.
| a length guarantee. -/ | ||
| lemma RelatesInSteps.exists_isChain {a b : α} {n : ℕ} (h : RelatesInSteps r a b n) : | ||
| ∃ chain : List α, | ||
| chain.IsChain r ∧ ∃ h_len : chain.length = n + 1, chain[0] = a ∧ chain[n] = b := by |
There was a problem hiding this comment.
Since you don't refer to h_len below, you can replace it by _.
| /-- Any two elements along an `r`-chain are related in as many steps as their distance in the | ||
| chain. -/ | ||
| lemma RelatesInSteps.of_isChain {chain : List α} | ||
| (hc : chain.IsChain r) | ||
| (p k : ℕ) | ||
| (hpk : p + k < chain.length) : | ||
| RelatesInSteps r chain[p] chain[p + k] k := by | ||
| induction k with | ||
| | zero => exact .refl _ | ||
| | succ k ih => | ||
| refine .tail _ (chain[p + k]) _ k (ih (by lia)) ?_ | ||
| apply List.IsChain.getElem hc | ||
|
|
||
| /-- A chain that visits the same element at two different positions can be shortened by splicing | ||
| out the loop in between, i.e. the first and last elements are also related to each other | ||
| by fewer steps. -/ | ||
| lemma RelatesInSteps.of_isChain_eq {chain : List α} {i j : ℕ} | ||
| (hc : chain.IsChain r) | ||
| (hij : i < j) | ||
| (hjn : j < chain.length) | ||
| (heq : chain[i] = chain[j]) : | ||
| RelatesInSteps r (chain.head (by grind)) (chain.getLast (by grind)) | ||
| (i + (chain.length - 1 - j)) := by | ||
| rw [List.head_eq_getElem, List.getLast_eq_getElem] | ||
| have h₁ := RelatesInSteps.of_isChain hc 0 i (by omega) | ||
| have h₂ := RelatesInSteps.of_isChain hc j (chain.length - 1 - j) (by omega) | ||
| grind [RelatesInSteps.trans] | ||
|
|
||
| /-- If a chain has duplicates, there is a shorter version with the same start and end point. | ||
| This is a less explicit version of `RelatesInSteps.of_isChain_eq`. -/ | ||
| lemma RelatesInSteps.of_isChain_neg_nodup {chain : List α} | ||
| (hc : chain.IsChain r) | ||
| (hne : chain ≠ []) | ||
| (hdup : ¬ chain.Nodup) : | ||
| ∃ n < chain.length - 1, RelatesInSteps r (chain.head hne) (chain.getLast hne) n := by | ||
| rw [List.nodup_iff_getElem?_ne_getElem?] at hdup | ||
| push Not at hdup | ||
| obtain ⟨i, j, hij, hjn, heq⟩ := hdup | ||
| exact ⟨_, by omega, RelatesInSteps.of_isChain_eq hc hij hjn (by grind)⟩ |
There was a problem hiding this comment.
It seems to me that the comments on these theorems do not accurately describe the contents of the theorems, because the word "chain" in these comments really refers to RelatesInSteps, rather than List.IsChain. One can imagine perfectly reasonable theorems fitting these comments that use List.IsChain instead.
| Note that this cardinality is an `ℕ∞`, and if it is infinite, no bound on the number of steps | ||
| is stated. -/ | ||
| theorem ReflTransGen.relatesInSteps_lt_encard {b : α} (h : ReflTransGen r a b) : | ||
| ∃ n, RelatesInSteps r a b n ∧ (n : ℕ∞) < {x | ReflTransGen r a x}.encard := by |
There was a problem hiding this comment.
Personally I think the last part of the statement would have been clearer if you explicitly require the set being finite for the cardinality comparison. But that's just me and I don't insist on it.
More seriously, it seems to me that the real mathematical content of this theorem is that there is a shortest path from a to b in which there is no duplication of elements. I think you should try to phrase and prove that theorem in terms of List.IsChain and then derive this theorem as a corollary.
There was a problem hiding this comment.
I think both versions (with finiteness and Finset.card / just .encard) have their advantages and disadvantages. I like the current version better because it can be used both for finite and infinite sets and is "sharp" in both versions.
About the "IsChain-only" theorem: I guess I wanted to limit myself to results that directly relate to RelatesInSteps, but you are right, this is the cleaner approach, I'll try.
There was a problem hiding this comment.
I was wondering if it makes sense to introduce a structure here:
/-- A "chain from to" is a list of elements where adjacent elements relate to each other
(cf. `List.IsChain`) and start and end with specific elements. -/
structure _root_.List.IsChainFromTo {α : Type*}
(r : α → α → Prop) (chain : List α) (a b : α) : Prop where
h_chain : chain.IsChain r
h_from : chain.head? = some a
h_to : chain.getLast? = some b
/-- If there is an `r`-chain from `a` to `b` with duplicates, then there is a shorter `r`-chain
from `a` to `b`. -/
lemma _root_.List.IsChainFromTo.exists_length_lt_of_not_nodup {chain : List α}
(hc : chain.IsChainFromTo r a b) (h_dup : ¬ chain.Nodup) :
∃ chain' : List α, chain'.IsChainFromTo r a b ∧ chain'.length < chain.length := byAdditionally, this is now much more general and should probably move to mathlib (I'm a bit surprised that it is not there yet, but maybe I didn't find it) - should I just create a new file for that? Plus, this is probably relevant for the emerging graph theory section as well?
There was a problem hiding this comment.
Our usual procedure for Mathlib upstreaming is to leave them in the same file as any other proof, sometimes leaving a comment or in a section if it's several proofs. (If a comment is prefaced with TODO an issue will automatically open with that as its title)
There was a problem hiding this comment.
Yes, I think List.IsChainFromTo is a good idea. I like putting the new definitions and theorems about List in a new file under Cslib/Foundations/Data/List/. The file can be removed after the mathlib upstreaming happens. Personally I find this approach more modular.
If the number of elements reachable from an element
aalong a relationris at mostk, then any of those elements can be reached in at mostk - 1steps.This is a generalization of a result in #767, which was specific for Turing machine configurations (that PR still needs to be adapted).
This PR adds that result and introduces the notion of "Path": A function
ℕ → αwhere successive values are related, up to a path length (it could be debated to useFin ninstead ofℕ).I believe that going back and forth from relation to path is useful in the future for various computation models.
Note that Mathlib has a notion similar to "Path" called
RelSeries. I did not re-use it because it is defined on top of sets of pairs instead of relations.AI disclosure: LLMs were used in creating this PR but everything was carefully edited and reviewed.