Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
cce51a6
Results about reachability with repeated visits.
crei Aug 6, 2026
c462faa
Use isChain.
crei Aug 12, 2026
dea453a
Merge remote-tracking branch 'origin/main' into relates_in_steps_bounded
crei Aug 12, 2026
877f6d5
Extract a lemma and clean up.
crei Aug 12, 2026
ba0c2b5
Undo some golfing.
crei Aug 12, 2026
cd5c63f
Undo some more golfing.
crei Aug 12, 2026
c4c92c1
Extract results related to List.isChain into their own file.
crei Aug 17, 2026
9ec5246
Merge remote-tracking branch 'origin/main' into relates_in_steps_bounded
crei Aug 17, 2026
af70d3b
Minor tweaks.
crei Aug 17, 2026
f03aca6
Remove simp.
crei Aug 17, 2026
a54f681
Merge remote-tracking branch 'origin/main' into relates_in_steps_bounded
crei Aug 19, 2026
f85d8ed
Move file.
crei Aug 20, 2026
bd393f3
Review comments.
crei Aug 20, 2026
6f018c3
Update Cslib/Foundations/Data/List/IsChainFromTo.lean
crei Aug 25, 2026
cdff1ee
Fix and apply suggestions.
crei Aug 25, 2026
ca19e4f
Update Cslib/Foundations/Data/List/IsChainFromTo.lean
crei Aug 25, 2026
9d1ea10
Review comments and many more lemmas.
crei Aug 25, 2026
d66f76a
Cleanup.
crei Aug 25, 2026
4f2cd1b
Add some more grind lemmas and update the module docstring.
crei Aug 25, 2026
a2505e4
Merge remote-tracking branch 'origin/main' into relates_in_steps_bounded
crei Aug 25, 2026
2a892a1
Move into List namespace, review suggestions
crei Aug 26, 2026
6833eb1
Update Cslib/Foundations/Data/List/IsChainFromTo.lean
crei Aug 27, 2026
3ecf3e0
Update Cslib/Foundations/Data/List/IsChainFromTo.lean
crei Aug 27, 2026
da3cec4
Update Cslib/Foundations/Data/List/IsChainFromTo.lean
crei Aug 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
206 changes: 206 additions & 0 deletions Cslib/Foundations/Data/List/IsChainFromTo.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
/-
Copyright (c) 2026 Christian Reitwiessner. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Christian Reitwiessner, Thomas Waring
-/

module

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

/-! # 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. Such a chain is an explicit witness for the fact that its end point is
reachable from its start point, and its length bounds the number of steps that are needed.

## Main definitions

* `List.IsChainFromTo r chain a b`: `chain` is a non-empty list whose adjacent elements are related
by `r`, whose first element is `a` and whose last element is `b`.

## Main results

* `List.IsChainFromTo.reflTransGen`: the start and the end of a chain are related by
`Relation.ReflTransGen`.
* `List.IsChainFromTo.head_induction_on`: induction on a chain, peeling off elements at the start.
* `List.IsChainFromTo.exists_length_lt_of_not_nodup`: a chain with duplicates can always be
shortened.
* `List.IsChainFromTo.exists_nodup`: iterating the above yields a chain without duplicates.
-/

@[expose] public section

namespace List

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

/-- 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 IsChainFromTo {α : Type*} (r : α → α → Prop) (chain : List α) (a b : α) : Prop where
isChain : chain.IsChain r
ne_nil : chain ≠ []
head_eq : chain.head ne_nil = a
getLast_eq : chain.getLast ne_nil = b

Comment thread
crei marked this conversation as resolved.
attribute [scoped grind →] IsChainFromTo.head_eq IsChainFromTo.getLast_eq

/-- A chain has at least one element. -/
@[scoped grind →]
lemma IsChainFromTo.length_pos (hc : chain.IsChainFromTo r a b) : 0 < chain.length :=
List.length_pos_iff.mpr hc.ne_nil

/-- The first element of an `r`-chain from `a` to `b` is `a`. -/
@[scoped grind →]
lemma IsChainFromTo.getElem_zero (hc : chain.IsChainFromTo r a b) :
chain[0]'hc.length_pos = a := by
rw [List.getElem_zero]
exact hc.head_eq

/-- The last element of an `r`-chain from `a` to `b` is `b`. -/
@[scoped grind →]
lemma IsChainFromTo.getElem_length_sub_one (hc : chain.IsChainFromTo r a b) :
chain[chain.length - 1]'(by have := hc.length_pos; lia) = b := by
rw [List.getElem_length_sub_one_eq_getLast]
exact hc.getLast_eq

/-- The start and the end of an `r`-chain are reflexively-transitively related by `r`. -/
theorem IsChainFromTo.reflTransGen (hc : chain.IsChainFromTo r a b) :
Relation.ReflTransGen r a b := by
simpa [hc.head_eq, hc.getLast_eq] using
List.relationReflTransGen_of_exists_isChain chain hc.isChain hc.ne_nil

/-- Create a `List.IsChainFromTo` from a non-empty `List.IsChain`. -/
theorem IsChain.isChainFromTo_of_ne_nil
{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⟩

/-- A one-element list is an `r`-chain from that element to itself. -/
@[simp, scoped grind ←]
lemma isChainFromTo_singleton : List.IsChainFromTo r [a] a a :=
⟨List.IsChain.singleton a, by simp, rfl, rfl⟩

/-- Prepend an `r`-related element to the start of the chain. -/
lemma 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

@[simp, scoped grind =]
lemma isChainFromTo_pair_iff {a a' b b' : α} :
List.IsChainFromTo r [a, b] a' b' ↔ r a b ∧ a = a' ∧ b = b' := by
constructor
· rintro ⟨hc, _, rfl, rfl⟩
simpa using hc
· rintro ⟨h, rfl, rfl⟩
constructor <;> simp_all

/-- Removing the head yields a valid chain. -/
lemma IsChainFromTo.of_cons_cons {x y : α} (hc : (x :: y :: chain).IsChainFromTo r a b) :
(y :: chain).IsChainFromTo r y b :=
⟨hc.isChain.of_cons, cons_ne_nil _ _, head_cons, by grind⟩

/-- Appending a chain and the tail of a second one whose start point equals the end point of the
first yields a valid chain. -/
lemma IsChainFromTo.append_tail (hc : chain.IsChainFromTo r a b) {chain' : List α}
(hc' : chain'.IsChainFromTo r b c) : (chain ++ chain'.tail).IsChainFromTo r a c where
isChain := by
have hb : chain.dropLast ++ [b] = chain :=
hc.getLast_eq ▸ chain.dropLast_append_getLast hc.ne_nil
have hb' : [b] ++ chain'.tail = chain' := by simp [←hc'.head_eq]
rw [←hb] at hc ⊢
exact hc.isChain.append_overlap (l₃ := chain'.tail) (hb'.symm ▸ hc'.isChain) (cons_ne_nil b [])
ne_nil := append_ne_nil_of_left_ne_nil hc.ne_nil _
head_eq := head_append_left hc.ne_nil |>.trans hc.head_eq
getLast_eq := by grind

/-- Add an `r`-related element to the end of the chain. -/
lemma IsChainFromTo.snoc (hc : chain.IsChainFromTo r a b) (h : r b c) :
(chain ++ [c]).IsChainFromTo r a c :=
append_tail hc (chain' := [b, c]) (by simpa)

/-- Appending a chain, dropping its last element and another chain whose start point equals
the end point of the first chain yields a valid chain. -/
lemma IsChainFromTo.append_dropLast (hc : chain.IsChainFromTo r a b) {chain' : List α}
(hc' : chain'.IsChainFromTo r b c) : (chain.dropLast ++ chain').IsChainFromTo r a c := by
convert hc.append_tail hc' using 1
nth_rw 1 [←chain'.cons_head_tail hc'.ne_nil, hc'.head_eq, append_cons, ←hc.getLast_eq,
dropLast_concat_getLast hc.ne_nil]

/-- Taking the first `i + 1` elements of a chain yields a chain from the same start point to
`chain[i]`. -/
lemma IsChainFromTo.take (hc : chain.IsChainFromTo r a b) {i : ℕ} (hi : i < chain.length) :
(chain.take (i + 1)).IsChainFromTo r a chain[i] := by
have : chain.take (i + 1) ≠ [] := by grind only [length_nil, min_def, length_take]
exact ⟨hc.isChain.take _, this, by grind, by grind [chain.getLast_take this]⟩

/-- Dropping the first `i` elements of a chain yields a chain from `chain[i]` to the same end
point. -/
lemma IsChainFromTo.drop (hc : chain.IsChainFromTo r a b) {i : ℕ} (hi : i < chain.length) :
(chain.drop i).IsChainFromTo r chain[i] b := by
have : chain.drop i ≠ [] := ne_nil_iff_length_pos.mpr <| chain.lt_length_drop hi
refine ⟨hc.isChain.drop _, this, chain.head_drop this, hc.getLast_eq ▸ chain.getLast_drop this⟩

@[elab_as_elim]
lemma IsChainFromTo.head_induction_on
{motive : ∀ {chain : List α} {a b : α}, chain.IsChainFromTo r a b → Prop}
(h_refl : ∀ {a : α}, motive (isChainFromTo_singleton (r := r) (a := a)))
(h_head : ∀ {a b c : α} {chain : List α} (hab : r a b) (hc : chain.IsChainFromTo r b c),
motive hc → motive (hc.cons hab))
{chain : List α} {a b : α} (hc : chain.IsChainFromTo r a b) : motive hc := by
induction htail : chain.tail generalizing chain a with
| nil => grind => have : chain = [a]; finish
| cons a' tail ih =>
obtain rfl : chain = a :: a' :: tail := by grind
obtain ⟨hrel, hchain⟩ := isChain_cons_cons.mp hc.isChain
have : (a' :: tail).IsChainFromTo r a' b := hc.of_cons_cons
exact h_head hrel this (ih this rfl)

/-- Any element of an `r`-chain from `a` to `b` is reflexively-transitively related from `a`. -/
lemma IsChainFromTo.reflTransGen_of_mem (hc : chain.IsChainFromTo r a b) {x : α} (mem : x ∈ chain) :
Relation.ReflTransGen r a x := by
obtain ⟨i, hi, rfl⟩ := List.getElem_of_mem mem
exact (hc.take hi).reflTransGen

/-- Any element of an `r`-chain from `a` to `b` is reflexively-transitively related to `b`. -/
lemma IsChainFromTo.reflTransGen_of_mem' (hc : chain.IsChainFromTo r a b) {x : α}
(mem : x ∈ chain) :
Relation.ReflTransGen r x b := by
obtain ⟨i, hi, rfl⟩ := List.getElem_of_mem mem
exact (hc.drop hi).reflTransGen

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). -/
lemma 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
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
Comment thread
crei marked this conversation as resolved.

/-- For any `r`-chain from `a` to `b` there is one without duplicates. -/
lemma 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

end List
Loading
Loading