The Three-Gap (Steinhaus) theorem #
A self-contained Lean 4 / Mathlib formalization of the three-gap theorem,
depending only on Mathlib. It works in [0,1) through Int.fract (NOT
AddCircle), via the first-return (Slater / van Ravenstein) route.
Status #
Complete and machine-checked — three_gap_card_le_three (for every a : ℝ
and N : ℕ, the N-point orbit {i·a mod 1 : i < N} partitions [0,1) into at
most three distinct gap lengths) is proved with no sorry, no warnings, and only
the standard axioms propext / Classical.choice / Quot.sound. Uniform in a
(rational and irrational alike).
The development, in order:
orbit,gaps,gaps_sum_eq_one— construction; the gaps sum to1;sortedValand its order theory (sortedVal_strictMono,_zero,_last, …);- the two first returns
etaPos = η⁺,etaNeg = η⁻, with minimality; three_gap_card_le_three_of_subset— the reduction "gap values lie in a three-element set⇒at most three distinct gap lengths";- the multiplier layer (
canMul,mPlus,mMinus) with the index-shift identities (fract_index_shift) and thenoPointBetweenlemmas; - cases A (
gap_eq_etaPos) and B (gap_eq_etaNeg) of the first-return classification, with the index-bridge infrastructure; - the corner case
η⁺ + η⁻(corner_gap), via the M-circle (L1/L3) and an induction-free closure of L2 — see the "corner case" section below; - the full classification
neighbour_gap_in_returnsand the boundthree_gap_card_le_three.
Design #
gaps enumerates the sorted distinct points through Finset.orderEmbOfFin
(Fin k ↪o ℝ, k = (orbit a N).card), extended to a total sortedVal : ℕ → ℝ;
the gaps are the adjacent differences over Finset.range (k - 1) plus the
wrap-around (min + 1) - max, and gaps_sum_eq_one is a Finset telescoping sum.
Note on noncomputable: on ℝ, DecidableEq and the order are noncomputable, so
orbit, orbitCard, orbitEmb, sortedVal, gaps, canMul, … are all
noncomputable.
The (finite, distinct) orbit { Int.fract (i * a) | i < N } in [0,1).
noncomputable, since Finset.image on ℝ uses the (noncomputable)
DecidableEq ℝ instance.
Equations
- SteinhausThreeGap.orbit a N = Finset.image (fun (i : ℕ) => Int.fract (↑i * a)) (Finset.range N)
Instances For
The orbit is nonempty as soon as there is at least one index, i.e. 0 < N.
Ties the hypothesis 0 < N of gaps_sum_eq_one to the orbit.
The number of distinct orbit points. noncomputable, as it depends on the
noncomputable orbit.
Equations
Instances For
The sorted distinct orbit points as a total index function ℕ → ℝ:
sortedVal a N i is the i-th smallest point when i < orbitCard a N,
and a junk value 0 otherwise. Only indices 0, …, orbitCard a N - 1
matter; the telescoping below is purely formal in sortedVal.
Equations
- SteinhausThreeGap.sortedVal a N i = if h : i < SteinhausThreeGap.orbitCard a N then (SteinhausThreeGap.orbitEmb a N) ⟨i, h⟩ else 0
Instances For
The multiset of consecutive gaps of the sorted distinct orbit points, plus
the single wrap-around gap (min + 1) - max.
With k = orbitCard a N and g = sortedVal a N:
- the
k - 1adjacent gapsg (i+1) - g i,i = 0, …, k - 2, by mapping overFinset.range (k - 1); - the wrap-around gap
g 0 + 1 - g (k - 1)(i.e.min + 1 - max), added as a singleton.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The gaps sum to one. Telescoping of the adjacent differences gives
max - min; adding the wrap-around gap (min + 1) - max yields 1.
The proof is a pure Finset telescoping sum (Finset.sum_range_sub) followed
by ring.
Phase 1 — orbit membership and the sorted enumeration #
Phase 2 — gap infrastructure and the distinct-gap-count reduction #
The i-th adjacent gap of the sorted enumeration.
Equations
- SteinhausThreeGap.gapAt a N i = SteinhausThreeGap.sortedVal a N (i + 1) - SteinhausThreeGap.sortedVal a N i
Instances For
Reduction lemma for the three-gap bound. If every distinct gap value lies in
a fixed three-element set, then there are at most three distinct gap lengths.
The Phase 2 core supplies the hypothesis with {η⁺, η⁻, η⁺+η⁻}.
Phase 2 core — the two first returns η⁺, η⁻ (self-contained infrastructure) #
The positive return η⁺ is the smallest positive orbit point; the negative
return η⁻ is 1 - max(orbit). We package the positive orbit points as
posOrbit a N := (orbit a N).erase 0. Everything below is proved unconditionally
(no surjectivity of the sorted enumeration is needed); the genuinely combinatorial
crux and the lemmas that depend on the enumeration-onto bridge are organized in the
Phase 2 core sections below.
The positive orbit points (orbit a N) \ {0}. Each lies in (0,1).
Equations
- SteinhausThreeGap.posOrbit a N = (SteinhausThreeGap.orbit a N).erase 0
Instances For
The positive return η⁺: the smallest positive orbit point. Junk 1 in the
degenerate regime.
Equations
- SteinhausThreeGap.etaPos a N = if h : (SteinhausThreeGap.posOrbit a N).Nonempty then (SteinhausThreeGap.posOrbit a N).min' h else 1
Instances For
The negative return η⁻ = 1 - (largest positive orbit point). Junk 1 in
the degenerate regime.
Equations
- SteinhausThreeGap.etaNeg a N = if h : (SteinhausThreeGap.posOrbit a N).Nonempty then 1 - (SteinhausThreeGap.posOrbit a N).max' h else 1
Instances For
Phase 2 core — enumeration-onto bridge and the two returns as gap values #
The bridge from the sorted enumeration back to the orbit, the no-orbit-point-between
lemma, and the identification of the first internal gap with η⁺ and the wrap-around
gap with η⁻. Together with three_gap_card_le_three_of_subset these reduce the
three-gap bound to the single first-return classification lemma
(neighbour_gap_in_returns, the genuine combinatorial core, proved below).
Phase 2 core — fractional-part rotation helpers (toward the first-return lemma) #
Fractional-part index-shift identity. Reducing the two summands mod 1
first does not change the fractional part of a sum:
Int.fract (Int.fract x + Int.fract y) = Int.fract (x + y). With x = u*a,
y = m*a this is the value form of the multiplier shift u, m ↦ u + m.
Forward rotation closure (multiplier in range). If u + m < N, the
rotated value Int.fract (Int.fract (u*a) + Int.fract (m*a)) is again an orbit
point. This is the half of the Slater closure that does not wrap past index
N; the values that fall off the end (u + m ≥ N) are where the negative
return appears. (API extra; not used by the main chain.)
Multiplier-indexed infrastructure (toward the first-return dichotomy) #
The value-Finset orbit hides the index ("multiplier") behind each point.
We recover a canonical multiplier and the forward value-shift; the discriminant
of the three-gap dichotomy is the Nat condition canMul + mp < N.
The finset of indices < N whose rotation value equals p.
Equations
- SteinhausThreeGap.mulFiber a N p = {k ∈ Finset.range N | Int.fract (↑k * a) = p}
Instances For
Canonical (least) multiplier of an orbit point p.
Equations
- SteinhausThreeGap.canMul a N p hp = (SteinhausThreeGap.mulFiber a N p).min' ⋯
Instances For
Value-shift identity. Rotating an orbit point p (canonical multiplier
u = canMul p) by a return multiplier mp for η⁺ lands on
Int.fract (p + η⁺). (API extra; the main chain uses fract_index_shift.)
Forward shift lands in the orbit when the multiplier stays in range
(canMul p + mp < N) and p + η⁺ < 1 (no wrap). (API extra; unused.)
Forward-neighbour upper bound (API extra — superseded by Case A below) #
Forward-neighbour upper bound. Under the forward hypothesis hfwd, the
gap is at MOST η⁺: the orbit point q := sortedVal i + η⁺ lies strictly above
sortedVal i and < 1, so by no_orbit_strictly_between it cannot sit strictly
below sortedVal (i+1). (API extra; superseded by gap_eq_etaPos.)
Index-bridge infrastructure (multiplier indices and difference extraction) #
Toward the first-return classification, recovering the index structure behind the
orbit values. The decisive discriminant is the index condition j + m⁺ < N, NOT
the value condition xⱼ + η⁺ ∈ orbit: those differ (e.g. α = 4/5, N = 4, point
2/5 has 2/5 + η⁺ = 4/5 ∈ orbit yet gap = η⁻, since its index is off-end).
Forward return multiplier as an index: the canonical (least) index whose
rotation value is η⁺.
Equations
- SteinhausThreeGap.mPlus a N h2 = SteinhausThreeGap.canMul a N (SteinhausThreeGap.etaPos a N) ⋯
Instances For
Difference extraction, w ≥ j. If two orbit points have non-decreasing
indices j ≤ w < N and increasing values, their difference is an orbit point.
Combined with no_orbit_below_etaPos this rules out a between-point reachable
from a higher index.
Difference extraction, w < j (dual). If the larger value {w·a} has the
smaller index w < j, the complement 1 - ({w·a} - {j·a}) is an orbit point
({(j-w)·a}) in the top band (1 - η⁺, 1). Via le_one_sub_etaNeg this gives
{w·a} - {j·a} ≥ η⁻; used in the backward index-bridge toward Case B.
The forward (η⁺) case #
D1 — an in-range index is not in the top band. If d + m⁺ < N then
{d·a} + η⁺ ≤ 1. (If not, {(d+m⁺)·a} = {d·a}+η⁺-1 is a positive orbit value
< η⁺, contradicting minimality.)
No orbit point lies in (xⱼ, xⱼ + η⁺) when j + m⁺ < N. w ≥ j: the
difference is a positive orbit value < η⁺ (minimality). w < j: the
complementary index j - w would be in the top band, contradicting D1.
Backward (η⁻) machinery — mirror of the forward case #
Backward return multiplier: the canonical index of the MAX orbit value 1-η⁻.
Equations
- SteinhausThreeGap.mMinus a N h2 = SteinhausThreeGap.canMul a N (1 - SteinhausThreeGap.etaNeg a N) ⋯
Instances For
No orbit point lies in (xⱼ - η⁻, xⱼ) when j + m⁻ < N (backward mirror
of noPointBetween).
Case B (backward): an index j' of v' = sortedVal (i+1) with j' + m⁻ < N
forces gap = η⁻.
The corner case η⁺ + η⁻ — completing the classification #
The remaining case of the three-gap bound: the corner, where both first returns
fall off the end (canMul v + m⁺ ≥ N and canMul v' + m⁻ ≥ N). Then the gap is
η⁺ + η⁻ (corner_gap). The argument:
corner_canMul_lt_mMinus(hP1 : canMul v < m⁻): ifj ≥ m⁻thenv+η⁻would be the successorv', forcingcanMul v' + m⁻ < N, contradictinghB.- upper bound
v' ≤ v+η⁺+η⁻: realiseq := v+η⁺+η⁻as{(canMul v + m⁺ - m⁻)·a}(index< NbyhP1). - lower bound: no orbit point in
(v, v+η⁺+η⁻), split as L1(v, v+η⁺), L2v+η⁺ ∉ orbit a N, L3(v+η⁺, q).
L1/L3 (corner_noPoint_lo / corner_noPoint_hi) use the M-circle M := m⁺ + m⁻:
orbit a N ⊆ orbit a M, the returns are unchanged (no_new_below_etaPos,
no_new_above_etaNeg, etaPos_eq_extend, etaNeg_eq_extend), and hP1 keeps the
shifted indices < M.
L2 is closed WITHOUT induction (uniform in a, rational or irrational): if
v+η⁺ = {k·a} with k ≥ j then m⁺ ≤ k-j, so k ≥ j+m⁺ ≥ N, impossible; if
k < j then d := j+m⁺-k is a period ({d·a}=0) with m⁺ < d < m⁺+m⁻,
contradicting minimality of m⁻ (the max 1-η⁻ at index m⁻-d) or of m⁺
(η⁺=η⁻, then at index d-m⁻). This replaces van Ravenstein / Mayero's
induction-on-N and avoids their irrational-only restriction.
Returns are preserved when extending the orbit to M = m⁺ + m⁻ indices
(positive return). No new index k ∈ [N, m⁺+m⁻) produces a positive value
below η⁺. (Key step of the M-circle route to the corner bound: a value
s = {k·a} ∈ (0, η⁺) would force {(k-m⁻)·a} = η⁺, hence m⁺ ≤ k-m⁻ by
minimality, i.e. k ≥ m⁺+m⁻, contradiction.)
Returns are preserved when extending the orbit to M = m⁺ + m⁻ indices
(negative return). No new index k ∈ [N, m⁺+m⁻) produces a value in the top
band (1-η⁻, 1). Mirror of no_new_below_etaPos.
M-circle infrastructure (orbit extension N ↦ M = m⁺ + m⁻) #
Monotonicity of the orbit in the index count, and the fact that the two returns
η⁺, η⁻ are unchanged when the orbit is extended from N to M = m⁺ + m⁻
(no new index in [N, M) produces a smaller positive value or a larger value).
These let the corner "no point in the big gap" arguments (corner_noPoint_lo/hi)
borrow the in-range first-return machinery in the larger circle.
Positive return preserved under extension. If every new index k ∈ [N, M)
avoids the band (0, η⁺), then η⁺ is unchanged from N to M.
Negative return preserved under extension. If every new index k ∈ [N, M)
avoids the top band (1 - η⁻, 1), then η⁻ is unchanged from N to M.
Corner discriminant hP1 (independent of the lower bound). In the corner,
the canonical index j = canMul v of the left point satisfies j < m⁻.
Proof: if j ≥ m⁻, then w := v + η⁻ = {(j-m⁻)·a} ∈ orbit a N (a valid index
< N). As w lies in range with canMul w + m⁻ ≤ j < N, case B
(gap_eq_etaNeg) shows the predecessor of w is w - η⁻ = v; hence w is the
successor v' of v, so v' = v + η⁻ and canMul v' ≤ j - m⁻, giving
canMul v' + m⁻ ≤ j < N — contradicting the corner hypothesis hB.
L1 — no orbit point in (v, v + η⁺) (corner). Direct difference
extraction: a point z = {w·a} with v < z < v + η⁺ gives, when w ≥ j, a
positive value z - v < η⁺ in orbit a N (contra η⁺ minimal); and when
w < j, a positive value η⁺ - (z-v) < η⁺ in the extended orbit orbit a M
(M = m⁺ + m⁻) at index (j-w) + m⁺ < M, contradicting minimality of
etaPos a M = etaPos a N. The discriminant hP1 : j < m⁻ makes the shifted
index stay in range < M.
L3 — no orbit point in (v + η⁺, v + η⁺ + η⁻) (corner). Mirror of L1:
the point p₁ = v + η⁺ = {(j+m⁺)·a} ∈ orbit a M. A point z with
p₁ < z < p₁ + η⁻ has every index w < N ≤ j + m⁺, so {((j+m⁺)-w)·a} = 1 - (z - p₁) lies in (1 - η⁻, 1) ∩ orbit a M, exceeding the maximum 1 - η⁻ of
orbit a M — contradiction.
The corner case. When both first returns fall off the end
(canMul v + m⁺ ≥ N and canMul v' + m⁻ ≥ N), the internal gap is η⁺ + η⁻.
The lower bound hLower (no orbit point lies strictly in (v, v + η⁺ + η⁻))
is proved inline below; the proof is self-contained, with no external input.
The first-return classification. Every internal gap is η⁺, η⁻, or
η⁺ + η⁻: case A (gap_eq_etaPos) when canMul v + m⁺ < N, case B
(gap_eq_etaNeg) when canMul v' + m⁻ < N, and the corner (corner_gap)
otherwise. (No i = 0 special case is needed: there canMul v = 0 and
m⁺ < N, so case A always fires.)
The sum relation. When the gaps take exactly three distinct lengths, those
lengths are precisely η⁺, η⁻, and η⁺ + η⁻; in particular the largest,
η⁺ + η⁻, is the sum of the other two (they are positive: etaPos_pos,
etaNeg_pos). The nondegeneracy 2 ≤ orbitCard is derived from h3.