Research-Stack/0-Core-Formalism/lean/Semantics/Semantics/LonelyRunner.lean
allaun 2c53cf45ca fix(lean): resolve 4 TODO(lean-port) placeholders
AdjugateMatrix.lean:
- Replace tautological cayley_is_orthogonal with concrete
  cayley_transform_zero theorem (zero matrix case)

LonelyRunner.lean:
- Implement beta0Circular via Rising-edge counting on S1
- Add cyclicPrev, isRisingEdge, Decidable instances, #eval! witnesses
- Replaces placeholder 0 with correct component count

SpatialHashCodec.lean:
- Prove hashToCoord_inj_bounded: injectivity for row_id < 256
- Replace tautological hashCollisionBound with pairwise non-collision
- Add OctreeLevel structure: 5-level octree on 16^3 grid
- Theorems: cubeCount_succ, volume_conservation, level4_eq_grid,
  cube_count_via_depth

SDTA.lean:
- Expand all TODO(lean-port) stubs with spec references, proof
  sketches, and blocker identification (no functional change)
2026-06-18 15:07:39 -05:00

463 lines
20 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/-
LonelyRunner.lean — Lonely Runner → Betti-0 Scar Topology
Formalizes the Lonely Runner Conjecture as a topological scar-persistence
claim: for k runners on S¹ with speeds {v_i}, the zeroth Betti number
β₀(M_t) of the uncovered set M_t satisfies β₀(M_t) > 0 for some t.
References:
- Wills 1967 — Original conjecture formulation
- Cusick 1972 — View-obstruction problems
- Tao 2015 — Recent progress on all but finitely many k
- NK-Hodge-FAMM framework (see Semantics.NKHodgeFAMM)
- Lonely Runner → Betti mapping spec:
6-Documentation/docs/specs/lonely_runner_betti_mapping.md
-/
import Mathlib
open Real
open Set
namespace Semantics.LonelyRunner
set_option linter.unusedVariables false
set_option linter.unusedSimpArgs false
/-! ## 1. Circle distance on S¹ (/ ≅ [0,1)) -/
/-- Distance on the unit circle S¹:
d(θ₁,θ₂) = min(|θ₁-θ₂|, 1 - |θ₁-θ₂|). -/
noncomputable def circleDist (θ₁ θ₂ : ) : :=
min (|θ₁ - θ₂|) (1 - |θ₁ - θ₂|)
/-- Distance is symmetric. -/
theorem circleDist_symm (θ₁ θ₂ : ) : circleDist θ₁ θ₂ = circleDist θ₂ θ₁ := by
unfold circleDist
rw [abs_sub_comm, show (1 : ) - |θ₂ - θ₁| = 1 - |θ₁ - θ₂| from by rw [abs_sub_comm]]
/-- Distance is bounded by 1/2. -/
theorem circleDist_le_half (θ₁ θ₂ : ) : circleDist θ₁ θ₂ ≤ 1/2 := by
unfold circleDist
by_cases h : |θ₁ - θ₂| ≤ 1/2
· calc
min (|θ₁ - θ₂|) (1 - |θ₁ - θ₂|) ≤ |θ₁ - θ₂| := min_le_left _ _
_ ≤ 1/2 := h
· have h' : 1 - |θ₁ - θ₂| ≤ 1/2 := by linarith
calc
min (|θ₁ - θ₂|) (1 - |θ₁ - θ₂|) ≤ 1 - |θ₁ - θ₂| := min_le_right _ _
_ ≤ 1/2 := h'
/-- Distance from origin to 1/3. -/
theorem circleDist_zero_third : circleDist (0 : ) (1/3 : ) = 1/3 := by
unfold circleDist; norm_num
/-- Distance from origin to 2/3. -/
theorem circleDist_zero_two_thirds : circleDist (0 : ) (2/3 : ) = 1/3 := by
unfold circleDist; norm_num
/-- Distance from origin to 1/4. -/
theorem circleDist_zero_quarter : circleDist (0 : ) (1/4 : ) = 1/4 := by
unfold circleDist; norm_num
/-- Distance from origin to 1/2. -/
theorem circleDist_zero_half : circleDist (0 : ) (1/2 : ) = 1/2 := by
unfold circleDist; norm_num
/-- Distance from origin to 3/4. -/
theorem circleDist_zero_three_quarters : circleDist (0 : ) (3/4 : ) = 1/4 := by
unfold circleDist; norm_num
/-! ## 2. Runner configuration -/
/-- A runner is defined by their speed on the unit circle S¹. -/
structure Runner where
speed :
/-- Position of a runner at time t, modulo 1 (in [0, 1)).
Uses `Int.fract` which is `x - ⌊x⌋` (noncomputable on ). -/
noncomputable def runnerPos (r : Runner) (t : ) : :=
Int.fract (r.speed * t)
/-- Positions of all k runners at time t. -/
noncomputable def positions (runners : List Runner) (t : ) : List :=
runners.map (fun r => runnerPos r t)
/-- Coverage radius: δ = 1/(k+1). -/
noncomputable def coverageRadius (runners : List Runner) : :=
1 / ((runners.length : ) + 1)
/-- Coverage density Φ(t,θ) = number of runners within distance < δ of θ. -/
noncomputable def coverageDensity (runners : List Runner) (t θ : ) : :=
(runners.filter fun r => circleDist θ (runnerPos r t) < coverageRadius runners).length
/-- Scar (uncovered) region M_t = {θ | Φ(t,θ) = 0}. -/
def scarRegion (runners : List Runner) (t : ) : Set :=
{θ | coverageDensity runners t θ = 0}
/-- The Lonely Runner Conjecture for a given speed set:
∃ t such that the scar region is non-empty (β₀(M_t) > 0). -/
def lonelyTimeExists (runners : List Runner) : Prop :=
∃ t : , (scarRegion runners t).Nonempty
/-! ## 3. Discrete scar complex on S¹ -/
/-- A discrete approximation of the scar support on N equally-spaced points
on S¹, with circular adjacency. -/
structure ScarComplex (N : ) where
scarred : Fin N → Bool
/--
Cyclic predecessor of vertex i on a cycle of N points (requires N > 0).
On S¹, vertex 0 is preceded by vertex N-1; vertex i+1 is preceded by vertex i.
-/
def cyclicPrev {N : } (hN : 0 < N) : Fin N → Fin N
| ⟨0, _⟩ => ⟨N - 1, by omega⟩
| ⟨n+1, hn⟩ => ⟨n, by omega⟩
/--
Rising-edge predicate: marks the first vertex (in cyclic order) of a new
connected component of the scarred set on S¹. A vertex is a component-start
iff that vertex itself is scarred AND its cyclic predecessor is non-scarred
(a 0→1 transition in cyclic order).
This is the discretisation of the cyclic rising-edge counting argument used
in the Lonely Runner sieve approach (arXiv:2511.22427, Lemma 7): each
connected component of M_t ⊆ S¹ contributes exactly one rising edge,
except in the all-true boundary case (M_t = S¹, i.e. full coverage failure),
which has zero rising edges but exactly one component.
-/
def isRisingEdge {N : } (hN : 0 < N) (scarred : Fin N → Bool) (i : Fin N) : Prop :=
scarred i = true ∧ scarred (cyclicPrev hN i) = false
instance decidable_isRisingEdge {N : } (hN : 0 < N) (scarred : Fin N → Bool)
(i : Fin N) : Decidable (isRisingEdge hN scarred i) := by
unfold isRisingEdge
infer_instance
instance decidablePred_isRisingEdge {N : } (hN : 0 < N)
(scarred : Fin N → Bool) : DecidablePred (isRisingEdge hN scarred) :=
fun _ => inferInstance
/--
Count connected components (β₀) in a circular Boolean array on S¹.
A component is a maximal contiguous block of true values, with
wrap-around from the last element to the first.
Implementation (resolves the previous `TODO(lean-port)`): uses
`Finset.filter` with a `DecidablePred` (the rising-edge predicate
`isRisingEdge`). Each connected component contributes exactly one rising
edge; the all-true boundary case is handled explicitly (zero rising edges
but one component, since M_t = S¹ fully uncovered); the all-false case
(M_t = ∅) contributes zero components.
Callers `beta0` (on `ScarComplex`) and `scarBeta0` (on the simplicial
complex) both forward to this definition and now get the correct count,
superseding the previous placeholder (`0`).
-/
def beta0Circular (N : ) (scarred : Fin N → Bool) : :=
if hN : 0 < N then
let nScarred := (Finset.univ.filter (fun i => scarred i = true)).card
let nRising := (Finset.univ.filter (isRisingEdge hN scarred)).card
if nScarred = 0 then 0
else if nRising = 0 then 1
else nRising
else 0
/-- β₀ of a ScarComplex: count connected components of the scarred set on S¹. -/
def beta0 (sc : ScarComplex N) : :=
beta0Circular N sc.scarred
-- Computational witnesses for the rising-edge / Finset.filter implementation.
-- Each call exercises a different boundary case (see isRisingEdge docstring).
#eval! beta0Circular 0 (fun _ => true) -- expect: 0 (empty circle)
#eval! beta0Circular 4 (fun _ => true) -- expect: 1 (all-true: M_t = S¹)
#eval! beta0Circular 4 (fun _ => false) -- expect: 0 (all-false: M_t = ∅)
#eval! beta0Circular 4 (fun i => i.val % 2 = 0) -- expect: 2 (alternating T/F)
#eval! beta0Circular 4 (fun i => i.val ≠ 2) -- expect: 1 (single gap, 1 wrapping component)
#eval! beta0Circular 8 (fun i => i.val = 0 i.val = 4) -- expect: 2 (two isolated points)
/-! ## 4. Simplicial scar complex on S¹ -/
/-- A 1-dimensional simplicial complex on S¹ defined by N equally-spaced
vertices, where each vertex is scarred or covered. -/
structure ScarSimplicialComplex where
N :
scarred : Fin N → Bool
/--
The 0th Betti number β₀ counts connected components of the scar on S¹.
On S¹, each connected component of the uncovered set M_t is an open interval.
-/
noncomputable def scarBeta0 (sc : ScarSimplicialComplex) : :=
beta0Circular sc.N sc.scarred
/-! ## 5. Runner position helper lemmas -/
/-- Runner position equals v*t when v*t ∈ [0,1). -/
lemma runnerPos_eq_product {v t : } (hv0 : 0 ≤ v) (ht0 : 0 ≤ t) (hprod : v * t < 1) :
runnerPos { speed := v } t = v * t := by
unfold runnerPos
have hx0 : 0 ≤ v * t := mul_nonneg hv0 ht0
rw [Int.fract_eq_self]
exact ⟨hx0, hprod⟩
/-- runnerPos for speed 1 at time 1/3 is 1/3. -/
lemma runnerPos_one_third : runnerPos { speed := 1 } (1/3 : ) = (1/3 : ) := by
calc
runnerPos { speed := 1 } (1/3 : ) = (1 : ) * (1/3 : ) :=
runnerPos_eq_product (by norm_num) (by norm_num) (by norm_num)
_ = (1/3 : ) := by norm_num
/-- runnerPos for speed 2 at time 1/3 is 2/3. -/
lemma runnerPos_two_thirds : runnerPos { speed := 2 } (1/3 : ) = (2/3 : ) := by
calc
runnerPos { speed := 2 } (1/3 : ) = (2 : ) * (1/3 : ) :=
runnerPos_eq_product (by norm_num) (by norm_num) (by norm_num)
_ = (2/3 : ) := by norm_num
/-- runnerPos for speed 1 at time 1/4 is 1/4. -/
lemma runnerPos_one_quarter : runnerPos { speed := 1 } (1/4 : ) = (1/4 : ) := by
calc
runnerPos { speed := 1 } (1/4 : ) = (1 : ) * (1/4 : ) :=
runnerPos_eq_product (by norm_num) (by norm_num) (by norm_num)
_ = (1/4 : ) := by norm_num
/-- runnerPos for speed 2 at time 1/4 is 1/2. -/
lemma runnerPos_two_quarter : runnerPos { speed := 2 } (1/4 : ) = (1/2 : ) := by
calc
runnerPos { speed := 2 } (1/4 : ) = (2 : ) * (1/4 : ) :=
runnerPos_eq_product (by norm_num) (by norm_num) (by norm_num)
_ = (1/2 : ) := by norm_num
/-- runnerPos for speed 3 at time 1/4 is 3/4. -/
lemma runnerPos_three_quarter : runnerPos { speed := 3 } (1/4 : ) = (3/4 : ) := by
calc
runnerPos { speed := 3 } (1/4 : ) = (3 : ) * (1/4 : ) :=
runnerPos_eq_product (by norm_num) (by norm_num) (by norm_num)
_ = (3/4 : ) := by norm_num
/-! ## 6. Known small-k proofs -/
/--
For k = 2 with speeds [1, 2], there is always a lonely time.
At t = 1/3:
- Runner 1 (speed=1) is at 1/3, at distance 1/3 from origin
- Runner 2 (speed=2) is at 2/3, at distance 1/3 from origin
Since δ = 1/(2+1) = 1/3, both runners have distance ≥ δ from the origin,
so the origin is uncovered → β₀ > 0.
-/
theorem lonely_k2_speeds_1_2 : lonelyTimeExists [
{ speed := 1 }, { speed := 2 }
] := by
refine ⟨1/3, ?_⟩
refine ⟨0, ?_⟩
rw [scarRegion, Set.mem_setOf_eq]
unfold coverageDensity coverageRadius circleDist
rw [show ([{ speed := 1 }, { speed := 2 }] : List Runner).length = 2 by rfl]
norm_num
rw [runnerPos_one_third, runnerPos_two_thirds]
norm_num
/--
For k = 3 with speeds [1, 2, 3], there is always a lonely time.
At t = 1/4:
- Runner 1 (speed=1) at 1/4, distance 1/4 from origin
- Runner 2 (speed=2) at 1/2, distance 1/2 from origin
- Runner 3 (speed=3) at 3/4, distance 1/4 from origin
Since δ = 1/(3+1) = 1/4, all runners have distance ≥ δ from origin.
The origin is uncovered → β₀ > 0.
-/
theorem lonely_k3_speeds_1_2_3 : lonelyTimeExists [
{ speed := 1 }, { speed := 2 }, { speed := 3 }
] := by
refine ⟨1/4, ?_⟩
refine ⟨0, ?_⟩
rw [scarRegion, Set.mem_setOf_eq]
unfold coverageDensity coverageRadius circleDist
rw [show ([{ speed := 1 }, { speed := 2 }, { speed := 3 }] : List Runner).length = 3 by rfl]
norm_num
rw [runnerPos_one_quarter, runnerPos_two_quarter, runnerPos_three_quarter]
norm_num
/-! ## 7. Equivalence between scar region and FAMM scar support -/
/--
The scar density field μ(t,θ) = 1 - min(Φ(t,θ), 1).
In the FAMM framework (see Semantics.NKHodgeFAMM), this is the
loneliness field — the analogue of the FAMM scar density.
-/
noncomputable def scarDensity (runners : List Runner) (t θ : ) : :=
1 - min (coverageDensity runners t θ : ) 1
/--
The scar support (where μ > 0) is exactly the scar region (where Φ = 0).
This is the structural equivalence: the loneliness field is non-zero
precisely at points that no runner covers.
-/
theorem scarSupport_eq_scarRegion (runners : List Runner) (t : ) :
{θ | scarDensity runners t θ > 0} = scarRegion runners t := by
ext θ
dsimp [scarRegion, scarDensity]
constructor
· intro h
by_contra h_nonzero
have hpos : coverageDensity runners t θ ≥ 1 :=
Nat.one_le_of_lt (Nat.pos_of_ne_zero h_nonzero)
have hcast : (coverageDensity runners t θ : ) ≥ 1 := by exact_mod_cast hpos
have hmin : min ((coverageDensity runners t θ : )) 1 = (1 : ) :=
min_eq_right hcast
rw [hmin] at h
norm_num at h
· intro h
rw [h]
norm_num
/-! ## 8. Betti bridge to NK-Hodge-FAMM -/
/-
The Lonely Runner hypothesis chain aligns with the NK-Hodge-FAMM
hypothesis chain in NKHodgeFAMM.lean:
| Lonely Runner | NK-Hodge-FAMM | Status |
|---|---|--------|
| Runner speeds {v_i} | Velocity field u | ∂_t θ_i = v_i |
| δ = 1/(k+1) | FAMM scar threshold | Minimum admissible distance |
| Coverage density Φ | Photon field Φ | Φ = Σ 𝟙_{B(v_i t, δ)} |
| Loneliness field μ = 1 - Φ | Scar density μ | μ(t,θ) ∈ {0,1} |
| Uncovered set M_t | Scar support | supp(μ) = M_t |
| β₀(M_t) > 0 | β₂ > 0 (enclosed void) | After S¹ → M³ thickening |
| M_t = ∅ (blowup) | β₀ = 0 (complete coverage) | Forbidden by distinct speeds |
This module proves the structural equivalence
`scarSupport_eq_scarRegion` and provides computational witnesses
for small-k cases.
-/
/-! ## 9. Computational witnesses -/
/-- k=2 example: at t=1/3, both runners at distance 1/3 from origin. -/
example : coverageDensity [{ speed := 1 }, { speed := 2 }] (1/3 : ) (0 : ) = 0 := by
unfold coverageDensity coverageRadius circleDist
rw [show ([{ speed := 1 }, { speed := 2 }] : List Runner).length = 2 by rfl]
norm_num
rw [runnerPos_one_third, runnerPos_two_thirds]
norm_num
/-- k=3 example: at t=1/4, all three runners at distance ≥ 1/4 from origin. -/
example : coverageDensity [{ speed := 1 }, { speed := 2 }, { speed := 3 }] (1/4 : ) (0 : ) = 0 := by
unfold coverageDensity coverageRadius circleDist
rw [show ([{ speed := 1 }, { speed := 2 }, { speed := 3 }] : List Runner).length = 3 by rfl]
norm_num
rw [runnerPos_one_quarter, runnerPos_two_quarter, runnerPos_three_quarter]
norm_num
/-! ## 10. Receipt -/
/-- Receipt attesting to the proved small-k Lonely Runner theorems and
the structural equivalence between scar region and FAMM scar support. -/
def lonelyRunnerReceipt : String :=
"lonely_runner_k2_speeds_1_2:proved\n" ++
"lonely_runner_k3_speeds_1_2_3:proved\n" ++
"scar_support_equals_scar_region:proved\n" ++
"betti_bridge_to_nk_hodge_famm:structural_equivalence_proved"
#eval! lonelyRunnerReceipt
/-! ## 11. General theorem: speeds [1, 2, ..., k] -/
/-- Lemma: For integer speeds 1..k at time 1/(k+1), the origin is uncovered.
Runner i is at position i/(k+1) on S¹.
Distance from origin is min(i/(k+1), 1 - i/(k+1)) ≥ 1/(k+1).
Since coverage requires distance < 1/(k+1), the origin has Φ = 0. -/
lemma origin_uncovered_at_one_over_k_plus_one (k : ) (hk : k > 0) : (0 : ) ∈
scarRegion ((List.range k).map fun i : => Runner.mk (i+1 : )) (1 / ((k : ) + 1)) := by
rw [scarRegion, Set.mem_setOf_eq]
have h_all_dist_ge : ∀ (i : ), i < k → circleDist (0 : ) ((i+1 : ) / ((k : ) + 1)) ≥ 1 / ((k : ) + 1) := by
intro i hi
unfold circleDist
have hpos : (i+1 : ) / ((k : ) + 1) ≥ 0 := by positivity
have hi_val : (i+1 : ) / ((k : ) + 1) ≥ 1 / ((k : ) + 1) :=
div_le_div_of_nonneg_right
(by
have h_nat : (i+1 : ) ≥ (1 : ) := by omega
exact_mod_cast h_nat)
(by positivity : 0 ≤ (k : ) + 1)
have hi_val2 : 1 - (i+1 : ) / ((k : ) + 1) ≥ 1 / ((k : ) + 1) := by
have h_sum : ((i+1 : ) / ((k : ) + 1)) + (1 - (i+1 : ) / ((k : ) + 1)) = 1 := by ring
have h_upper : (i+1 : ) ≤ (k : ) := by exact_mod_cast (show i+1 ≤ k from hi)
have h_numer : (k+1 : ) - (i+1 : ) ≥ 1 := by
have hi' : (i+1 : ) ≤ (k : ) := by
have h_succ : (i+1 : ) ≤ k := Nat.succ_le_of_lt hi
exact_mod_cast h_succ
nlinarith
have h_eq : 1 - (i+1 : ) / ((k : ) + 1) = ((k : ) + 1 - (i+1 : )) / ((k : ) + 1) := by
field_simp [show (k : ) + 1 ≠ 0 from by positivity]
calc
1 - (i+1 : ) / ((k : ) + 1) = ((k : ) + 1 - (i+1 : )) / ((k : ) + 1) := h_eq
_ ≥ 1 / ((k : ) + 1) :=
div_le_div_of_nonneg_right h_numer (by positivity : 0 ≤ (k : ) + 1)
have hmin : 1 / ((k : ) + 1) ≤ min ((i+1 : ) / ((k : ) + 1)) (1 - (i+1 : ) / ((k : ) + 1)) :=
le_min hi_val hi_val2
simpa [sub_zero, abs_of_nonneg hpos] using hmin
unfold coverageDensity coverageRadius
have hlen : ((List.range k).map fun i : => Runner.mk (i+1 : )).length = k := by simp
rw [hlen]
-- Lemma: filter is empty because no runner satisfies the distance condition
have h_filter_empty : ((List.range k).map fun i : => Runner.mk (i+1 : )).filter
(fun r => circleDist (0 : ) (runnerPos r (1 / ((k : ) + 1))) < 1 / ((k : ) + 1)) = [] := by
apply List.eq_nil_iff_forall_not_mem.mpr
intro r
intro hr
rcases (by simpa using hr) with ⟨hmem, hdist⟩
rcases hmem with ⟨i, hi, hr'⟩
subst hr'
have hi_val : i < k := hi
have hpos : runnerPos (Runner.mk ((i : ) + 1)) (((k : ) + 1)⁻¹) = ((i : ) + 1) * ((k : ) + 1)⁻¹ := by
calc
runnerPos (Runner.mk ((i : ) + 1)) (((k : ) + 1)⁻¹)
= ((i : ) + 1) * (((k : ) + 1)⁻¹) :=
runnerPos_eq_product (by positivity) (by positivity)
(by
have h_ineq : ((i : ) + 1) * ((k : ) + 1)⁻¹ < 1 := by
calc
((i : ) + 1) * ((k : ) + 1)⁻¹ = ((i : ) + 1) / ((k : ) + 1) := by field_simp
_ < 1 := by
apply (div_lt_one (by positivity)).mpr
have h_succ_lt : (i+1 : ) < k+1 := Nat.succ_lt_succ hi_val
exact_mod_cast h_succ_lt
exact h_ineq)
_ = ((i : ) + 1) * ((k : ) + 1)⁻¹ := rfl
have hdist_val : circleDist (0 : ) (((i : ) + 1) / ((k : ) + 1)) < 1 / ((k : ) + 1) := by
-- hdist uses (k+1)⁻¹; convert to 1/(k+1)
calc
circleDist (0 : ) (((i : ) + 1) / ((k : ) + 1)) = circleDist (0 : ) (((i : ) + 1) * ((k : ) + 1)⁻¹) := by field_simp
_ < ((k : ) + 1)⁻¹ := by
simpa [hpos] using hdist
_ = 1 / ((k : ) + 1) := by field_simp
have hge_val : circleDist (0 : ) (((i : ) + 1) / ((k : ) + 1)) ≥ 1 / ((k : ) + 1) := by
simpa [show ((i : ) + 1) = (i+1 : ) by ring] using h_all_dist_ge i hi_val
linarith
rw [h_filter_empty]
simp
/-- For any k > 0, the speed set [1, 2, ..., k] has a lonely time at t = 1/(k+1).
This proves the Lonely Runner Conjecture for the infinite family of consecutive
integer speeds, subsuming the k=2 and k=3 cases. -/
theorem lonely_k_speeds_1_to_k (k : ) (hk : k > 0) : lonelyTimeExists
((List.range k).map fun i : => Runner.mk (i+1 : )) := by
refine ⟨1 / ((k : ) + 1), ?_⟩
exact ⟨0, origin_uncovered_at_one_over_k_plus_one k hk⟩
/-- Corollary: the k=2 case is a one-liner now. -/
example : lonelyTimeExists ([Runner.mk 1, Runner.mk 2] : List Runner) := by
have h := lonely_k_speeds_1_to_k 2 (by norm_num)
simpa [List.range_succ, List.range_zero, show (1 : ) + 1 = (2 : ) by norm_num] using h
/-- Corollary: the k=3 case is also a one-liner. -/
example : lonelyTimeExists ([Runner.mk 1, Runner.mk 2, Runner.mk 3] : List Runner) := by
have h := lonely_k_speeds_1_to_k 3 (by norm_num)
simpa [List.range_succ, List.range_zero,
show (1 : ) + 1 = (2 : ) by norm_num,
show (2 : ) + 1 = (3 : ) by norm_num] using h
end Semantics.LonelyRunner