Skip to content
Open
1 change: 1 addition & 0 deletions Cslib.lean
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public import Cslib.Foundations.Data.DecidableEqZero
public import Cslib.Foundations.Data.FinFun.Basic
public import Cslib.Foundations.Data.FinFun.Update
public import Cslib.Foundations.Data.HasFresh
public import Cslib.Foundations.Data.List.IsChainFromTo
public import Cslib.Foundations.Data.Nat.Segment
public import Cslib.Foundations.Data.OmegaSequence.Defs
public import Cslib.Foundations.Data.OmegaSequence.Flatten
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ def TimeComputable.comp {f g : List Symbol → List Symbol}
(hg.timeBound (f a).length) hg_outputsFun
-- Therefore, the computer reduces a to g (f a) in the sum of those times.
have h_a_reducesTo_g_f_a := RelatesWithinSteps.trans h_a_reducesTo_f_a h_f_a_reducesTo_g_f_a
apply RelatesWithinSteps.of_le h_a_reducesTo_g_f_a
refine RelatesWithinSteps.mono ?_ h_a_reducesTo_g_f_a
refine Nat.add_le_add_left ?_ (hf.timeBound a.length)
· apply h_mono
-- Use the lemma about output length being bounded by input length + time
Expand Down
75 changes: 75 additions & 0 deletions Cslib/Foundations/Data/List/IsChainFromTo.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/-
Copyright (c) 2026 Christian Reitwiessner. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Christian Reitwiessner
-/

module

public import Cslib.Init
public import Mathlib.Data.List.Chain
public import Mathlib.Data.List.Nodup

/-! # Chains with a designated start and end

This file defines `List.IsChainFromTo`, a variant of `List.IsChain` that also fixes the first and
last element of the chain.
Comment thread
crei marked this conversation as resolved.

The lemma `List.IsChainFromTo.exists_length_lt_of_not_nodup` shows that a chain with duplicates can
always be shortened.
-/

@[expose] public section

variable {α : Type*} {r : α → α → Prop} {chain : List α} {a b : α}

/-- 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 List.IsChainFromTo {α : Type*} (r : α → α → Prop) (chain : List α) (a b : α) : Prop where
isChain : chain.IsChain r
ne_nil : chain ≠ []
head : chain.head ne_nil = a
last : chain.getLast ne_nil = b
Comment on lines +31 to +32

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
head : chain.head ne_nil = a
last : chain.getLast ne_nil = b
head_eq : chain.head ne_nil = a
getLast_eq : chain.getLast ne_nil = b
attribute [grind →] List.IsChainFromTo.head_eq List.IsChainFromTo.getLast_eq

and then you can delete the duplicate lemmas


/-- Create a `List.IsChainFromTo` from a non-empty `List.IsChain`. -/
theorem List.IsChainFromTo.of_isChain_ne_nil

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
theorem List.IsChainFromTo.of_isChain_ne_nil
theorem List.IsChain.isChainFromTo_of_ne_nil

gives usable dot notation

(chain : List α) (hc : chain.IsChain r) (h_ne_nil : chain ≠ []) :
List.IsChainFromTo r chain (chain.head h_ne_nil) (chain.getLast h_ne_nil) :=
⟨hc, h_ne_nil, rfl, rfl⟩

Comment thread
crei marked this conversation as resolved.
/-- Restatement of `head_eq`, but tagged with grind. -/
@[grind →]
lemma List.IsChainFromTo.head_eq (hc : chain.IsChainFromTo r a b) :
chain.head hc.ne_nil = a :=
hc.head

/-- Restatement of `getLast_eq`, but tagged with grind. -/
@[grind →]
lemma List.IsChainFromTo.last_eq (hc : chain.IsChainFromTo r a b) :
chain.getLast hc.ne_nil = b :=
hc.last

@[simp, grind ←]
lemma List.IsChainFromTo.singleton {a : α} : List.IsChainFromTo r [a] a a :=
⟨List.IsChain.singleton a, by simp, rfl, rfl⟩

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

i think some further api lemmas for IsChainFromTo would be good — at very least this one can help with the proof of RelatesInSteps.exists_isChainFromTo

lemma List.IsChainFromTo.cons (h : r a b) (hc : chain.IsChainFromTo r b c) :
    (a :: chain).IsChainFromTo r a c where
  isChain := hc.isChain.cons_of_ne_nil hc.ne_nil (hc.head_eq.symm ▸ h)
  ne_nil := cons_ne_nil a chain
  head_eq := head_cons
  getLast_eq := hc.getLast_eq ▸ chain.getLast_cons hc.ne_nil

i think some induction principles (in the style of, say, RelatesInSteps.head_induction_on) would also be helpful, but if this pr is blocking something else maybe that can wait (though they oughtn't be too hard)

/-- If there is an `r`-chain from `a` to `b` with duplicates, then there is a shorter `r`-chain
from `a` to `b` (the one that skips the part between the duplicates).
Note that applying this method iteratively does not necessarily lead to the shortest `r`-chain
from `a` to `b`, since we always keep the initial and final segment. -/
Comment on lines +58 to +59

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I don't understand this sentence. It seems to contradict chain'.length < chain.length in the theorem statement.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Iterating the method is a greedy search that cuts duplicates. Each iteration reduces the length and it will terminate at some point. But the length of that chain is not the minimum over all chains from a to b.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the explanation. But I'm not sure you need this second sentence here. The purpose of the doc-string is to explain the contents of the theorem, not how its application may or may imply something. The first sentence alone suffices.

lemma List.IsChainFromTo.exists_length_lt_of_not_nodup
(hc : chain.IsChainFromTo r a b)
(h_dup : ¬ chain.Nodup) :
∃ chain' : List α, chain'.IsChainFromTo r a b ∧ chain'.length < chain.length := by
rw [nodup_iff_getElem?_ne_getElem?] at h_dup
push Not at h_dup
obtain ⟨i, j, h_ij, h_lt, h_eq⟩ := h_dup
use chain.take i ++ chain.drop j
constructor
· apply IsChainFromTo.mk ((hc.isChain.take _).append (hc.isChain.drop _) ?_)
(by simp; omega) (by grind) (by grind)
intro x hx y hy
rw [List.head?_drop] at hy
have := hc.isChain.getElem (i := i - 1)
grind
· grind
Comment on lines +64 to +75

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

A little golfing:

  simp only [nodup_iff_getElem?_ne_getElem?, not_forall, not_not] at h_dup
  obtain ⟨i, j, h_ij, h_lt, h_eq⟩ := h_dup
  use chain.take i ++ chain.drop j
  split_ands
  · apply IsChainFromTo.mk ..
    · apply (hc.isChain.take _).append (hc.isChain.drop _)
      grind [List.head?_drop, hc.isChain.getElem (i := i - 1)]
    · grind [append_eq_nil_iff, drop_eq_nil_iff]
    · grind
    · grind
  · grind

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

i think this lemma would also be nice

Suggested change
· grind
· grind
lemma List.IsChainFromTo.exists_noDup (hc : chain.IsChainFromTo r a b) :
∃ chain' : List α, chain'.IsChainFromTo r a b ∧ chain'.Nodup := by
induction hn : chain.length using Nat.strong_induction_on generalizing chain with
| h n ih =>
by_cases h_dup : chain.Nodup
· use chain, hc, h_dup
· obtain ⟨chain', hc', hlen⟩ := hc.exists_length_lt_of_not_nodup h_dup
exact ih chain'.length (hn ▸ hlen) hc' rfl

119 changes: 100 additions & 19 deletions Cslib/Foundations/Data/RelatesInSteps.lean
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,27 @@ Authors: Bolton Bailey
module

public import Cslib.Init
public import Cslib.Foundations.Data.List.IsChainFromTo
public import Mathlib.Data.Set.Card
public import Mathlib.Logic.Relation

/-! # Relations Across Steps

This file defines `Relation.RelatesInSteps` (and `Relation.RelatesWithinSteps`).
These are inductively defines propositions that communicate whether a relation forms a
These are inductively defined propositions that communicate whether a relation forms a
chain of length `n` (or at most `n`) between two elements.

The lemma `RelatesInSteps.exists_isChainFromTo` allows to obtain a chain
(`List.IsChainFromTo`) of related elements that witness the reachability, and
`RelatesInSteps.of_isChainFromTo` is the converse direction.

Another result is `Relation.ReflTransGen.relatesInSteps_lt_encard`, which states that any element
reachable from `a` is reachable in fewer steps than there are elements reachable from `a`.
-/

@[expose] public section

variable {α : Type*} {r : α → α → Prop} {a b c : α}
variable {α : Type*} {r : α → α → Prop} {a b c : α} {n m : ℕ}

namespace Relation

Expand All @@ -37,6 +46,9 @@ theorem RelatesInSteps.reflTransGen (h : RelatesInSteps r a b n) : ReflTransGen
| refl => rfl
| tail _ _ _ _ h ih => exact .tail ih h

/-- If `b` is reachable from `a` via `r`, then they relate to each other for some number
of steps.
See `ReflTransGen.relatesInSteps_lt_encard` for a bound on the number of steps. -/

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

imo the second sentence is unnecessary

theorem ReflTransGen.relatesInSteps (h : ReflTransGen r a b) : ∃ n, RelatesInSteps r a b n := by
induction h with
| refl => exact ⟨0, .refl a⟩
Expand Down Expand Up @@ -100,9 +112,8 @@ lemma RelatesInSteps.succ_iff {a b : α} {n : ℕ} :
· rintro ⟨t', h_steps, h_red⟩
exact .tail _ t' b n h_steps h_red

lemma RelatesInSteps.succ' {a b : α} : ∀ {n : ℕ}, RelatesInSteps r a b (n + 1)
lemma RelatesInSteps.succ' {a b : α} {n : ℕ} (h : RelatesInSteps r a b (n + 1)) :
∃ t', r a t' ∧ RelatesInSteps r t' b n := by
intro n h
obtain ⟨t', hsteps, hstep⟩ := succ h
cases n with
| zero =>
Expand Down Expand Up @@ -147,6 +158,49 @@ lemma RelatesInSteps.map {α α' : Type*}
| tail t' t'' m _ hstep ih =>
exact .tail (g _) (g t') (g t'') m ih (hg t' t'' hstep)

/-! ## Lemmas to translate between RelatesInSteps and the existence of a chain (`List.IsChain`) -/

/-- If `b` is related to `a` via `r` in `n` steps, then there is an `r`-chain of `n + 1` elements
starting at `a` and ending at `b`.
This is similar to `List.exists_isChain_ne_nil_of_relationReflTransGen`, but also provides
a length guarantee. -/
lemma RelatesInSteps.exists_isChainFromTo {a b : α} {n : ℕ} (h : RelatesInSteps r a b n) :
∃ chain : List α, chain.IsChainFromTo r a b ∧ chain.length = n + 1 := by
induction h with
| refl => exact ⟨[a], by simp, rfl⟩
| tail t' t'' m _ hstep ih =>
obtain ⟨l, hchain, hlen⟩ := ih
use l ++ [t'']
constructor
· apply List.IsChainFromTo.mk (hchain.isChain.append (by simp) (by grind))
(by simp) (by grind) (by simp)
· simp [hlen]
Comment on lines +167 to +177

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

potentially simpler, using the lemma i suggested above

Suggested change
lemma RelatesInSteps.exists_isChainFromTo {a b : α} {n : ℕ} (h : RelatesInSteps r a b n) :
∃ chain : List α, chain.IsChainFromTo r a b ∧ chain.length = n + 1 := by
induction h with
| refl => exact ⟨[a], by simp, rfl⟩
| tail t' t'' m _ hstep ih =>
obtain ⟨l, hchain, hlen⟩ := ih
use l ++ [t'']
constructor
· apply List.IsChainFromTo.mk (hchain.isChain.append (by simp) (by grind))
(by simp) (by grind) (by simp)
· simp [hlen]
lemma RelatesInSteps.exists_isChainFromTo {a b : α} {n : ℕ} (h : RelatesInSteps r a b n) :
∃ chain : List α, chain.IsChainFromTo r a b ∧ chain.length = n + 1 := by
induction h using RelatesInSteps.head_induction_on with
| hrefl => exact ⟨[b], List.IsChainFromTo.singleton, rfl⟩
| @hhead a c n h' h ih =>
obtain ⟨l, hchain, hlen⟩ := ih
use a :: l, hchain.cons h'
simpa


/-- 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 α}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

maybe (including more useful dot notation):

Suggested change
lemma RelatesInSteps.of_isChain {chain : List α}
lemma _root_.List.IsChain.relatesInSteps_getElem {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 =>
apply RelatesInSteps.tail _ (chain[p + k]) _ k (ih (by lia))
apply List.IsChain.getElem hc

/-- If there is an `r`-chain from `a` to `b`, then `a` and `b` are related to each other with
a number of steps equal to the length of the chain minus one. -/
lemma RelatesInSteps.of_isChainFromTo {chain : List α} (hc : chain.IsChainFromTo r a b) :

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this proof might be easier with the induction principle(s) i mentioned, but maybe that's just my taste
also the same naming idea as above:

Suggested change
lemma RelatesInSteps.of_isChainFromTo {chain : List α} (hc : chain.IsChainFromTo r a b) :
lemma _root_.IsChainFromTo.relatesInSteps {chain : List α} (hc : chain.IsChainFromTo r a b) :

RelatesInSteps r a b (chain.length - 1) := by
have h_ne : chain.length > 0 := by grind
have hrel := RelatesInSteps.of_isChain hc.isChain 0 (chain.length - 1) (by omega)
have h0 : chain[0] = a := by grind
have hl : chain[chain.length - 1] = b := by grind
simpa [h0, hl] using hrel

/-! ## RelatesWithinSteps - only requires an upper bound on the number of steps -/

/--
`RelatesWithinSteps` is a variant of `RelatesInSteps` that allows for a loose bound.
It states that `a` relates to `b` in *at most* `n` steps.
Expand All @@ -166,10 +220,8 @@ lemma RelatesWithinSteps.single {a b : α} (h : r a b) : RelatesWithinSteps r a
RelatesWithinSteps.of_relatesInSteps (RelatesInSteps.single h)

lemma RelatesWithinSteps.zero {a b : α} (h : RelatesWithinSteps r a b 0) : a = b := by
obtain ⟨m, hm, hevals⟩ := h
have : m = 0 := Nat.le_zero.mp hm
subst this
exact RelatesInSteps.zero hevals
obtain ⟨_, hm, hevals⟩ := h
simp_all

@[simp]
lemma RelatesWithinSteps.zero_iff {a b : α} : RelatesWithinSteps r a b 0 ↔ a = b := by
Expand All @@ -186,22 +238,20 @@ lemma RelatesWithinSteps.trans {a b c : α} {n₁ n₂ : ℕ}
RelatesWithinSteps r a c (n₁ + n₂) := by
obtain ⟨m₁, hm₁, hevals₁⟩ := h₁
obtain ⟨m₂, hm₂, hevals₂⟩ := h₂
use m₁ + m₂
constructor
· lia
· exact RelatesInSteps.trans hevals₁ hevals₂
exact ⟨m₁ + m₂, by lia, hevals₁.trans hevals₂⟩

lemma RelatesWithinSteps.of_le {a b : α} {n₁ n₂ : ℕ}
(h : RelatesWithinSteps r a b n₁) (hn : n₁ ≤ n₂) :
RelatesWithinSteps r a b n₂ := by
obtain ⟨m, hm, hevals⟩ := h
/-- If two elements `a` and `b` are related in at most `n₁` steps in the relation `r` and
`n₁ ≤ n₂`, then they are also related in at most `n₂` steps. -/
lemma RelatesWithinSteps.mono {a b : α} : Monotone (RelatesWithinSteps r a b ·) := by
intro n₁ n₂ hn ⟨m, hm, hevals⟩
exact ⟨m, Nat.le_trans hm hn, hevals⟩

/-- If `h : α → ℕ` increases by at most 1 on each step of `r`,
then the value of `h` at the output is at most `h` at the input plus the step bound. -/
lemma RelatesWithinSteps.apply_le_apply_add {a b : α} {m : ℕ} (hevals : RelatesWithinSteps r a b m)
(h : α → ℕ) (h_step : ∀ a b, r a b → h b ≤ h a + 1)
:
lemma RelatesWithinSteps.apply_le_apply_add {a b : α} {m : ℕ}
(hevals : RelatesWithinSteps r a b m)
(h : α → ℕ)
(h_step : ∀ a b, r a b → h b ≤ h a + 1) :
h b ≤ h a + m := by
obtain ⟨m, hm, hevals_m⟩ := hevals
have := RelatesInSteps.apply_le_apply_add hevals_m h h_step
Expand All @@ -218,4 +268,35 @@ lemma RelatesWithinSteps.map {α α' : Type*} {r : α → α → Prop} {r' : α'
obtain ⟨m, hm, hevals⟩ := h
exact ⟨m, hm, RelatesInSteps.map g hg hevals⟩

/-! ## Reachability under a bound on the number of reachable elements -/

/-- A more precise version of `ReflTransGen.relatesInSteps`: if `b` is reachable from `a`, then it
is related to `a` in fewer steps than there are elements reachable from `a`.
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) :

@thomaskwaring thomaskwaring Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

possibly this whole proof could be simplified using List.IsChainFromTo.exists_noDup suggested above, something like:

    ... := by
  obtain ⟨_, hn⟩ := h.relatesInSteps
  obtain ⟨chain, hc, _⟩ := hn.exists_isChainFromTo
  replace ⟨chain, hc, h⟩ := hc.exists_noDup
  use chain.length - 1, RelatesInSteps.of_isChainFromTo hc
  suffices hcard : chain.length ≤ {x | ReflTransGen r a x}.encard by
    by_contra! hcard'
    grind [ENat.natCast_le_natCast, hcard.trans hcard']
  classical
  rw [←List.toFinset_card_of_nodup h, ←Set.encard_coe_eq_coe_finsetCard, List.coe_toFinset]
  apply Set.encard_le_encard
  intro y hy
  obtain ⟨i, hi, rfl⟩ := List.getElem_of_mem hy
  have := RelatesInSteps.of_isChain hc.isChain 0 i
  grind [RelatesInSteps.reflTransGen]

which feels a little conceptually clearer to me (especially with hsub extracted as a lemma), but that could be a matter of taste

∃ n, RelatesInSteps r a b n ∧ (n : ℕ∞) < {x | ReflTransGen r a x}.encard := by

@ctchou ctchou Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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 := by

Additionally, 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?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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)

@ctchou ctchou Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

classical
-- Let us use the shortest chain from `a` to `b`.
have hex : ∃ n, RelatesInSteps r a b n := h.relatesInSteps
use Nat.find hex, Nat.find_spec hex
obtain ⟨chain, hc, hlen⟩ := (Nat.find_spec hex).exists_isChainFromTo
-- All elements in the chain are reachable from `a`.
have hsub : {x | x ∈ chain} ⊆ {x | ReflTransGen r a x} := by

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this could be extracted as a lemma — with signature something like
chain.IsChainFromTo r a b → x ∈ chain → ReflTransGen r a x, plus also the one with conclusion ReflTransGen r x b

intro y hy
obtain ⟨i, hi, rfl⟩ := List.getElem_of_mem hy
have := RelatesInSteps.of_isChain hc.isChain 0 i
grind [RelatesInSteps.reflTransGen]
-- Now assume, for the sake of contradiction, that the minimal chain has at least as many
-- elements as there are reachable elements.
by_contra! hcard
-- Then there is at least one duplicate element.
have h_dup : ¬chain.Nodup := by
have hle := (Set.encard_le_encard hsub).trans hcard
rw [← List.coe_toFinset, Set.encard_coe_eq_coe_finsetCard] at hle
grind [List.toFinset_card_of_nodup, Nat.cast_le]
-- But then we can shorten the chain which contradicts the fact that it is minimal.
obtain ⟨chain', hc', hlt⟩ := hc.exists_length_lt_of_not_nodup h_dup
grind [Nat.find_min hex (m := chain'.length - 1), RelatesInSteps.of_isChainFromTo hc']

end Relation
Loading