Imports
25.2. The stable-marriage problem
This section formalizes the stable-marriage problem of CLRS §25.2 and the Gale–Shapley proposal algorithm: the preference model (rank functions, pairings, blocking pairs, stability), the functional proposal loop with well-founded termination, and the stability theorems for its output.
Main results:
-
PreferenceProfile/Pairing: the preference and pairing model -
Pairing.Stable: absence of blocking pairs (CLRS eq. (25.10)) -
gs: the Gale–Shapley output pairing -
gs_terminates_le_n_sq: the proposal loop terminates within|M| · |W|proposals -
gs_stable(Theorem 25.5): the Gale–Shapley output is stable -
stable_matching_exists: every preference profile has a stable pairing -
gs_perfect: equal-size sides yield a perfect Gale–Shapley pairing -
gs_man_optimal(Theorem 25.6): every man receives his best valid partner -
gs_woman_pessimal: the same output is woman-pessimal among stable pairings
Current gaps:
-
None for the finite preference model and the textbook optimality theorems.
Notation conventions used in this section:
-
M: men,W: women (finite types,DecidableEq) -
P: preference profile with rank functionsmRank/wRank(smaller ranks are better) -
μ: pairing with partner functionsmPartner/wPartner
Implementation details
The section is split into the following sub-modules:
Definitions and proofs
CLRSLean.FourthEdition.Chapter_25.Section_25_2_Stable_Marriage.S1_Preference_Model
S1. Preference model
The combinatorial model of the stable-marriage problem of CLRS §25.2: a preference profile assigns every man a strict total order over the women and every woman a strict total order over the men, represented by distinct numeric ranks; a pairing records who is matched to whom; and stability is the absence of blocking pairs.
Main results:
-
PreferenceProfile: men's and women's rank functions (with distinct ranks) -
PreferenceProfile.mPrefers/PreferenceProfile.wPrefers: strict preference relations derived from the ranks -
Pairing: a consistent pair of partner functions (CLRS §25.2) -
Pairing.IsPerfect: every man and every woman is matched -
BlockingPair: a pair that would rather be together than with its current partners (CLRS eq. (25.9)) -
Stable: a pairing with no blocking pair (CLRS eq. (25.10))
namespace CLRSnamespace Matchings
A preference profile for the stable-marriage problem (CLRS §25.2): each
man m ranks every woman by mRank m and each woman w ranks every man by
wRank w, where smaller ranks mean higher preference. The rank functions
are injective in the opposite argument, so every agent's preferences form a
strict total order.
The rank man m assigns to woman w: smaller is better.
The rank woman w assigns to man m: smaller is better.
Every man ranks distinct women distinctly.
Every woman ranks distinct men distinctly.
structure PreferenceProfile (M W : Type*) [Fintype M] [DecidableEq M]
[Fintype W] [DecidableEq W] where mRank : M → W → ℕ wRank : W → M → ℕ h_mRank_injective : ∀ m : M, Function.Injective (mRank m) h_wRank_injective : ∀ w : W, Function.Injective (wRank w)
Man m prefers woman w₁ to woman w₂.
def PreferenceProfile.mPrefers {M W : Type*} [Fintype M] [DecidableEq M]
[Fintype W] [DecidableEq W] (P : PreferenceProfile M W) (m : M) (w₁ w₂ : W) : Prop :=
P.mRank m w₁ < P.mRank m w₂
Woman w prefers man m₁ to man m₂.
def PreferenceProfile.wPrefers {M W : Type*} [Fintype M] [DecidableEq M]
[Fintype W] [DecidableEq W] (P : PreferenceProfile M W) (w : W) (m₁ m₂ : M) : Prop :=
P.wRank w m₁ < P.wRank w m₂
A pairing of men and women (CLRS §25.2): mPartner m is the woman man
m is matched to (if any) and wPartner w is the man woman w is matched
to (if any), and the two sides agree: man m is matched to woman w if and
only if woman w is matched to man m.
The woman each man is matched to, if any.
The man each woman is matched to, if any.
Mutual consistency: m is matched to w iff w is matched to m.
structure Pairing (M W : Type*) where mPartner : M → Option W wPartner : W → Option M h_consistency : ∀ m w, mPartner m = some w ↔ wPartner w = some mnamespace PairingA pairing is perfect when every man and every woman is matched (CLRS §25.2).
def IsPerfect {M W : Type*} (μ : Pairing M W) : Prop :=
(∀ m, ∃ w, μ.mPartner m = some w) ∧ (∀ w, ∃ m, μ.wPartner w = some m)
(m, w) is a blocking pair for the pairing μ when m and w are not
matched to each other and each of them either is unmatched or strictly
prefers the other to its current partner (CLRS eq. (25.9)).
def BlockingPair {M W : Type*} [Fintype M] [DecidableEq M] [Fintype W] [DecidableEq W]
(P : PreferenceProfile M W) (μ : Pairing M W) (m : M) (w : W) : Prop :=
(μ.wPartner w = none ∨ ∃ m', μ.wPartner w = some m' ∧ P.wPrefers w m m') ∧
(μ.mPartner m = none ∨ ∃ w', μ.mPartner m = some w' ∧ P.mPrefers m w w')A pairing is stable when it has no blocking pair (CLRS eq. (25.10)).
def Stable {M W : Type*} [Fintype M] [DecidableEq M] [Fintype W] [DecidableEq W]
(P : PreferenceProfile M W) (μ : Pairing M W) : Prop :=
∀ m w, ¬ BlockingPair P μ m wend Pairingend Matchingsend CLRSCLRSLean.FourthEdition.Chapter_25.Section_25_2_Stable_Marriage.S2_Gale_Shapley
S2. The Gale–Shapley proposal algorithm
A functional formulation of the Gale–Shapley algorithm of CLRS §25.2 as a
loop over proposal steps: at each step a free man proposes to the woman he
has not yet proposed to whom he ranks highest, and the woman accepts him
exactly when she is free or prefers him to her current partner (in which case
her current partner is freed). Each man proposes to each woman at most once,
so the loop terminates within |M| · |W| proposals.
Main results:
-
GSState: the algorithm state (a pairing plus per-man proposed sets) -
nextWoman: the most preferred woman a man has not yet proposed to -
proposalStep/step: the one-step proposal/accept transition -
Invariant: the loop invariants (matched women always prefer their current partner to any proposer; proposed sets are rank prefixes; a man's partner is always proposed to) -
step_partner_improves: a woman's partner only improves over time -
pendingCount/gsLoopN/gs: the well-founded loop and its final state -
ProposedAtandproposedAt_before_of_prefers: proposals proceed in decreasing preference order
namespace CLRSnamespace StableMarriageopen Finset Classical Matchingsvariable {M : Type*} [Fintype M] [DecidableEq M]variable {W : Type*} [Fintype W] [DecidableEq W]variable {P : PreferenceProfile M W}The state of the Gale–Shapley proposal algorithm (CLRS §25.2): a pairing, together with the set of women each man has already proposed to.
The woman each man is currently matched to, if any.
The man each woman is currently matched to, if any.
Mutual consistency of the two partner functions.
The set of women each man has already proposed to.
structure GSState (P : PreferenceProfile M W) where mPartner : M → Option W wPartner : W → Option M h_consistency : ∀ m w, mPartner m = some w ↔ wPartner w = some m proposed : M → Finset WA proposal is pending when some free man has not yet proposed to every woman.
Woman w accepts man m when she is free or prefers him to her current
partner.
def accepts (σ : GSState P) (m : M) (w : W) : Prop :=
σ.wPartner w = none ∨ ∃ m₁, σ.wPartner w = some m₁ ∧ P.wPrefers w m m₁A woman who has never been proposed to is free, and a man who has proposed to every woman has no next proposal.
lemma nextWoman_witness (σ : GSState P) (m : M) (hpend : σ.proposed m ≠ Finset.univ) :
∃ w : W, w ∈ Finset.univ \ σ.proposed m ∧
∀ w' ∈ Finset.univ \ σ.proposed m, P.mRank m w ≤ P.mRank m w' := by
classical
let S : Finset ℕ := (Finset.univ \ σ.proposed m).image (P.mRank m)
have hSne : S.Nonempty := by
have hcm : (Finset.univ \ σ.proposed m).Nonempty := by
by_contra hcm
have hEq : Finset.univ \ σ.proposed m = ∅ := Finset.not_nonempty_iff_eq_empty.mp hcm
apply hpend
ext x
by_cases hx : x ∈ σ.proposed m
· simp [hx]
· exfalso
have hx' : x ∈ Finset.univ \ σ.proposed m := by simp [hx]
rw [hEq] at hx'
simp at hx'
rcases hcm with ⟨x, hx⟩
exact ⟨P.mRank m x, Finset.mem_image.mpr ⟨x, hx, rfl⟩⟩
let r : ℕ := S.min' hSne
have hmin : r ∈ S ∧ ∀ b ∈ S, r ≤ b :=
(Finset.min'_eq_iff (s := S) (H := hSne) r).mp rfl
rcases Finset.mem_image.mp hmin.1 with ⟨w, hw, hrank⟩
refine ⟨w, hw, ?_⟩
intro w' hw'
have hw'_rank : P.mRank m w' ∈ S := Finset.mem_image.mpr ⟨w', hw', rfl⟩
have hle : r ≤ P.mRank m w' := hmin.2 (P.mRank m w') hw'_rank
rw [← hrank] at hle
exact hleThe woman a man proposes to next: the woman he has not yet proposed to whom he ranks highest (CLRS §25.2).
noncomputable def nextWoman (σ : GSState P) (m : M) (hpend : σ.proposed m ≠ Finset.univ) : W :=
Classical.choose (nextWoman_witness σ m hpend)The next woman a man proposes to has not been proposed to yet, so each man proposes to each woman at most once.
lemma nextWoman_not_proposed (σ : GSState P) (m : M) (hpend : σ.proposed m ≠ Finset.univ) :
nextWoman σ m hpend ∉ σ.proposed m :=
(Finset.mem_sdiff.mp (Classical.choose_spec (nextWoman_witness σ m hpend)).1).2Every woman a man has not yet proposed to is ranked at least as high (that is, at most as preferred) as his next proposal.
lemma nextWoman_min_rank (σ : GSState P) (m : M) (hpend : σ.proposed m ≠ Finset.univ)
{w' : W} (hw' : w' ∈ Finset.univ \ σ.proposed m) :
P.mRank m (nextWoman σ m hpend) ≤ P.mRank m w' :=
(Classical.choose_spec (nextWoman_witness σ m hpend)).2 w' hw'
The proposal step of a single free man m: he proposes to the woman he
has not yet proposed to whom he ranks highest; she accepts him exactly when
she is free or prefers him to her current partner, in which case her current
partner is freed.
noncomputable def proposalStep (σ : GSState P) (m : M) (hfree : σ.mPartner m = none)
(hpend : σ.proposed m ≠ Finset.univ) : GSState P :=
let w := nextWoman σ m hpend
{ mPartner := fun m' =>
if accepts σ m w ∧ m' = m then some w
else if accepts σ m w ∧ σ.wPartner w = some m' then none
else σ.mPartner m'
wPartner := fun w' =>
if w' = w then (if accepts σ m w then some m else σ.wPartner w) else σ.wPartner w'
h_consistency := by
intro m' w'
by_cases hacc : accepts σ m w
· by_cases hmm : m' = m
· by_cases hww : w' = w
· simp [hacc, hmm, hww]
· have hnot : σ.wPartner w' ≠ some m := by
intro hw
have hm := (σ.h_consistency m w').mpr hw
simp [hfree] at hm
have hwne : w ≠ w' := by
intro h
exact hww h.symm
simp [hacc, hmm, hww, hnot, hwne]
· by_cases hww : w' = w
· subst w'
by_cases hwm : σ.wPartner w = some m'
· have hmne : m ≠ m' := by
intro h
exact hmm h.symm
constructor
· intro h
simpa [hacc, hmm, hwm, hmne] using h
· intro h
simpa [hacc, hmm, hwm, hmne] using h
· have hmw : σ.mPartner m' ≠ some w := by
intro h
exact hwm ((σ.h_consistency m' w).mp h)
have hmne : m ≠ m' := by
intro h
exact hmm h.symm
constructor
· intro h
simpa [hacc, hmm, hwm, hmw, hmne] using h
· intro h
simpa [hacc, hmm, hwm, hmw, hmne] using h
· by_cases hwm : σ.wPartner w = some m'
· have hnotw' : σ.wPartner w' ≠ some m' := by
intro hw'
have hm1 : σ.mPartner m' = some w := (σ.h_consistency m' w).mpr hwm
have hm2 : σ.mPartner m' = some w' := (σ.h_consistency m' w').mpr hw'
have hww' : w = w' := by
rw [hm1] at hm2
simpa using hm2
exact hww hww'.symm
constructor
· intro h
simpa [hacc, hmm, hwm] using h
· intro h
simpa [hacc, hmm, hww, hwm, hnotw'] using h
· simp [hacc, hmm, hww, hwm]
exact σ.h_consistency m' w'
· by_cases hmm : m' = m
· by_cases hww : w' = w
· have hnot : σ.wPartner w ≠ some m := by
intro hw
have hm := (σ.h_consistency m w).mpr hw
simp [hfree] at hm
simp [hacc, hmm, hww, hnot, hfree]
· have hnot : σ.wPartner w' ≠ some m := by
intro hw
have hm := (σ.h_consistency m w').mpr hw
simp [hfree] at hm
have hwne : w ≠ w' := by
intro h
exact hww h.symm
simp [hacc, hmm, hww, hnot, hwne, hfree]
· by_cases hww : w' = w
· subst w'
simp [hacc, hmm]
exact σ.h_consistency m' w
· simp [hacc, hmm, hww]
exact σ.h_consistency m' w'
proposed := fun m' => if m' = m then insert w (σ.proposed m') else σ.proposed m' }In a proposal step, the proposing man's proposed set gains exactly the target woman.
lemma proposalStep_proposed_self {σ : GSState P} {m : M} (hfree : σ.mPartner m = none)
(hpend : σ.proposed m ≠ Finset.univ) :
(proposalStep σ m hfree hpend).proposed m =
insert (nextWoman σ m hpend) (σ.proposed m) := by
simp [proposalStep]Other men's proposed sets are unchanged by a proposal step.
lemma proposalStep_proposed_other {σ : GSState P} {m m' : M} (hfree : σ.mPartner m = none)
(hpend : σ.proposed m ≠ Finset.univ) (hmm : m' ≠ m) :
(proposalStep σ m hfree hpend).proposed m' = σ.proposed m' := by
simp [proposalStep, hmm]The target woman's partner after a proposal step: she accepts exactly when she is free or prefers the proposer.
lemma proposalStep_wPartner_target {σ : GSState P} {m : M} (hfree : σ.mPartner m = none)
(hpend : σ.proposed m ≠ Finset.univ) :
(proposalStep σ m hfree hpend).wPartner (nextWoman σ m hpend) =
if accepts σ m (nextWoman σ m hpend) then some m
else σ.wPartner (nextWoman σ m hpend) := by
simp [proposalStep]Other women's partners are unchanged by a proposal step.
lemma proposalStep_wPartner_other {σ : GSState P} {m : M} (hfree : σ.mPartner m = none)
(hpend : σ.proposed m ≠ Finset.univ) {w' : W} (hww : w' ≠ nextWoman σ m hpend) :
(proposalStep σ m hfree hpend).wPartner w' = σ.wPartner w' := by
simp [proposalStep, hww]The proposing man's partner after a proposal step: the target woman when she accepts, and unmatched otherwise.
lemma proposalStep_mPartner_self {σ : GSState P} {m : M} (hfree : σ.mPartner m = none)
(hpend : σ.proposed m ≠ Finset.univ) :
(proposalStep σ m hfree hpend).mPartner m =
if accepts σ m (nextWoman σ m hpend) then some (nextWoman σ m hpend) else none := by
by_cases hacc : accepts σ m (nextWoman σ m hpend)
· simp [proposalStep, hacc, hfree]
· simp [proposalStep, hacc, hfree]When a woman accepts a new man, her current partner is freed.
lemma proposalStep_mPartner_dump {σ : GSState P} {m m' : M} (hfree : σ.mPartner m = none)
(hpend : σ.proposed m ≠ Finset.univ) (hmm : m' ≠ m)
(hacc : accepts σ m (nextWoman σ m hpend))
(hwm : σ.wPartner (nextWoman σ m hpend) = some m') :
(proposalStep σ m hfree hpend).mPartner m' = none := by
simp [proposalStep, hmm, hacc, hwm]A man who is neither the proposer nor freed keeps his partner.
lemma proposalStep_mPartner_other {σ : GSState P} {m m' : M} (hfree : σ.mPartner m = none)
(hpend : σ.proposed m ≠ Finset.univ) (hmm : m' ≠ m)
(hnot : ¬ (accepts σ m (nextWoman σ m hpend) ∧
σ.wPartner (nextWoman σ m hpend) = some m')) :
(proposalStep σ m hfree hpend).mPartner m' = σ.mPartner m' := by
simp [proposalStep, hmm, hnot]One proposal step of the algorithm: an arbitrary free man with a pending proposal proposes to his most preferred unproposed woman. If no proposal is pending, the state is unchanged.
noncomputable def step (σ : GSState P) : GSState P :=
if h : hasPending σ then
proposalStep σ (Classical.choose h) (Classical.choose_spec h).1 (Classical.choose_spec h).2
else σWithout a pending proposal, a step changes nothing.
lemma step_eq_self_of_no_pending {σ : GSState P} (h : ¬ hasPending σ) : step σ = σ := by
simp [step, h]The loop invariants of the proposal algorithm: every woman who has been proposed to is matched, every matched woman prefers her current partner to every man who has proposed to her, every man's proposed set is a rank prefix (a woman of smaller rank implies all even more preferred women were proposed to), and every man has proposed to his current partner.
def Invariant (σ : GSState P) : Prop :=
(∀ w m, w ∈ σ.proposed m → σ.wPartner w ≠ none) ∧
(∀ w m₀, σ.wPartner w = some m₀ →
∀ m, w ∈ σ.proposed m → P.wRank w m₀ ≤ P.wRank w m) ∧
(∀ m w₁ w₂, w₁ ∈ σ.proposed m → P.mRank m w₂ < P.mRank m w₁ → w₂ ∈ σ.proposed m) ∧
(∀ m w, σ.mPartner m = some w → w ∈ σ.proposed m)Every woman who has been proposed to is matched.
lemma Invariant.w_proposed_matched {σ : GSState P} (hσ : Invariant σ) {w : W} {m : M}
(h : w ∈ σ.proposed m) : σ.wPartner w ≠ none :=
hσ.1 w m hA matched woman prefers her current partner to every man who has proposed to her.
lemma Invariant.best_among_proposers {σ : GSState P} (hσ : Invariant σ) {w : W} {m₀ : M}
(hw : σ.wPartner w = some m₀) {m : M} (hm : w ∈ σ.proposed m) :
P.wRank w m₀ ≤ P.wRank w m :=
hσ.2.1 w m₀ hw m hmEvery man's proposed set is a rank prefix.
lemma Invariant.downward_closed {σ : GSState P} (hσ : Invariant σ) {m : M} {w₁ w₂ : W}
(h₁ : w₁ ∈ σ.proposed m) (h₂ : P.mRank m w₂ < P.mRank m w₁) : w₂ ∈ σ.proposed m :=
hσ.2.2.1 m w₁ w₂ h₁ h₂Every man has proposed to his current partner.
lemma Invariant.partner_proposed {σ : GSState P} (hσ : Invariant σ) {m : M} {w : W}
(h : σ.mPartner m = some w) : w ∈ σ.proposed m :=
hσ.2.2.2 m w hThe initial state: nobody is matched and nobody has proposed.
def init : GSState P :=
{ mPartner := fun _ => none
wPartner := fun _ => none
h_consistency := by simp
proposed := fun _ => ∅ }The initial state satisfies the invariants.
A proposal step preserves the invariants.
lemma step_preserves_invariant {σ : GSState P} (hσ : Invariant σ) : Invariant (step σ) := by
by_cases h : hasPending σ
· have hstep : step σ = proposalStep σ (Classical.choose h) (Classical.choose_spec h).1
(Classical.choose_spec h).2 := by
simp [step, h]
rw [hstep]
let m := Classical.choose h
have hspec := Classical.choose_spec h
let w := nextWoman σ m hspec.2
have hw_not : w ∉ σ.proposed m := nextWoman_not_proposed σ m hspec.2
have hw_min : ∀ w' ∈ Finset.univ \ σ.proposed m, P.mRank m w ≤ P.mRank m w' := by
intro w'' hw''
exact nextWoman_min_rank σ m hspec.2 hw''
unfold Invariant
refine ⟨?_, ?_, ?_, ?_⟩
· intro w' m' hmem
by_cases hmm' : m' = m
· subst m'
have hmem' : w' ∈ insert w (σ.proposed m) := by
rwa [proposalStep_proposed_self hspec.1 hspec.2] at hmem
rcases Finset.mem_insert.mp hmem' with hw' | hw'
· subst w'
rw [proposalStep_wPartner_target hspec.1 hspec.2]
change (if accepts σ m w then some m else σ.wPartner w) ≠ none
by_cases hacc : accepts σ m w
· simp [hacc]
· simp [hacc]
intro hnone
exact hacc (Or.inl hnone)
· have hwn : σ.wPartner w' ≠ none := hσ.w_proposed_matched hw'
by_cases hww' : w' = w
· subst w'
exact False.elim (hw_not hw')
· rw [proposalStep_wPartner_other hspec.1 hspec.2 hww']
exact hwn
· have hmem' : w' ∈ σ.proposed m' := by
rwa [proposalStep_proposed_other hspec.1 hspec.2 hmm'] at hmem
have hwn : σ.wPartner w' ≠ none := hσ.w_proposed_matched hmem'
by_cases hww' : w' = w
· subst w'
rw [proposalStep_wPartner_target hspec.1 hspec.2]
change (if accepts σ m w then some m else σ.wPartner w) ≠ none
by_cases hacc : accepts σ m w
· simp [hacc]
· simp [hacc]
exact hwn
· rw [proposalStep_wPartner_other hspec.1 hspec.2 hww']
exact hwn
· intro w' m₀ hwm₀
by_cases hww' : w' = w
· subst w'
rw [proposalStep_wPartner_target hspec.1 hspec.2] at hwm₀
change (if accepts σ m w then some m else σ.wPartner w) = some m₀ at hwm₀
by_cases hacc : accepts σ m w
· have hm₀ : m = m₀ := by
simp [hacc] at hwm₀
simpa using hwm₀
subst m₀
intro m'' hmem''
by_cases hmm'' : m'' = m
· subst m''
rfl
· have hmemσ : w ∈ σ.proposed m'' := by
rwa [proposalStep_proposed_other hspec.1 hspec.2 hmm''] at hmem''
by_cases hwfree : σ.wPartner w = none
· exact False.elim ((hσ.w_proposed_matched hmemσ) hwfree)
· have hacc' : ∃ m₁, σ.wPartner w = some m₁ ∧ P.wPrefers w m m₁ := by
rcases hacc with hnone | hpref
· exact False.elim (hwfree hnone)
· exact hpref
rcases hacc' with ⟨m₁, hwm₁, hpref⟩
have hle : P.wRank w m₁ ≤ P.wRank w m'' := hσ.best_among_proposers hwm₁ hmemσ
exact le_trans (le_of_lt hpref) hle
· have hwm₀' : σ.wPartner w = some m₀ := by
simp [hacc] at hwm₀
exact hwm₀
have hnotpref : ¬ ∃ m₁, σ.wPartner w = some m₁ ∧ P.wPrefers w m m₁ := by
intro hpref
exact hacc (Or.inr hpref)
have hnotw : ¬ P.wPrefers w m m₀ := by
intro hpref
exact hnotpref ⟨m₀, hwm₀', hpref⟩
intro m'' hmem''
by_cases hmm'' : m'' = m
· subst m''
exact le_of_not_gt hnotw
· have hmemσ : w ∈ σ.proposed m'' := by
rwa [proposalStep_proposed_other hspec.1 hspec.2 hmm''] at hmem''
exact hσ.best_among_proposers hwm₀' hmemσ
· have hwmσ : σ.wPartner w' = some m₀ := by
rwa [proposalStep_wPartner_other hspec.1 hspec.2 hww'] at hwm₀
intro m'' hmem''
by_cases hmm'' : m'' = m
· subst m''
have hmem' : w' ∈ insert w (σ.proposed m) := by
rwa [proposalStep_proposed_self hspec.1 hspec.2] at hmem''
rcases Finset.mem_insert.mp hmem' with hw' | hw'
· subst w'
exact False.elim (hww' rfl)
· exact hσ.best_among_proposers hwmσ hw'
· have hmemσ : w' ∈ σ.proposed m'' := by
rwa [proposalStep_proposed_other hspec.1 hspec.2 hmm''] at hmem''
exact hσ.best_among_proposers hwmσ hmemσ
· intro m' w₁ w₂ hmem₁ hlt
by_cases hmm' : m' = m
· subst m'
have hmem₁' : w₁ ∈ insert w (σ.proposed m) := by
rwa [proposalStep_proposed_self hspec.1 hspec.2] at hmem₁
rcases Finset.mem_insert.mp hmem₁' with hw₁ | hw₁
· subst w₁
by_cases hw₂ : w₂ = w
· subst w₂
rw [proposalStep_proposed_self hspec.1 hspec.2]
exact Finset.mem_insert_self w (σ.proposed m)
· have hw₂' : w₂ ∈ σ.proposed m := by
by_contra hnot
have hw₂c : w₂ ∈ Finset.univ \ σ.proposed m :=
Finset.mem_sdiff.mpr ⟨Finset.mem_univ w₂, hnot⟩
exact (lt_irrefl (P.mRank m w)) (lt_of_le_of_lt (hw_min w₂ hw₂c) hlt)
rw [proposalStep_proposed_self hspec.1 hspec.2]
exact Finset.mem_insert_of_mem hw₂'
· have hw₂' : w₂ ∈ σ.proposed m := hσ.downward_closed hw₁ hlt
rw [proposalStep_proposed_self hspec.1 hspec.2]
exact Finset.mem_insert_of_mem hw₂'
· have hmem₁' : w₁ ∈ σ.proposed m' := by
rwa [proposalStep_proposed_other hspec.1 hspec.2 hmm'] at hmem₁
have hw₂' : w₂ ∈ σ.proposed m' := hσ.downward_closed hmem₁' hlt
rwa [proposalStep_proposed_other hspec.1 hspec.2 hmm']
· intro m' w' hm
by_cases hmm' : m' = m
· subst m'
rw [proposalStep_mPartner_self hspec.1 hspec.2] at hm
change (if accepts σ m w then some w else none) = some w' at hm
by_cases hacc : accepts σ m w
· simp [hacc] at hm
have hEq : w = w' := by
simpa using hm
rw [← hEq]
rw [proposalStep_proposed_self hspec.1 hspec.2]
exact Finset.mem_insert_self w (σ.proposed m)
· simp [hacc] at hm
· by_cases hdump : accepts σ m w ∧ σ.wPartner w = some m'
· rcases hdump with ⟨hacc, hwm⟩
rw [proposalStep_mPartner_dump hspec.1 hspec.2 hmm' hacc hwm] at hm
simp at hm
· have hmσ : σ.mPartner m' = some w' := by
rwa [proposalStep_mPartner_other hspec.1 hspec.2 hmm' hdump] at hm
have hprop : w' ∈ σ.proposed m' := hσ.partner_proposed hmσ
rwa [proposalStep_proposed_other hspec.1 hspec.2 hmm']
· simp [step, h]
exact hσInvariant (women only improve). When a woman changes partner in a proposal step, she strictly prefers the new partner to the old one.
lemma step_partner_improves {σ : GSState P} {w : W} {m₁ m₂ : M}
(hold : σ.wPartner w = some m₁) (hnew : (step σ).wPartner w = some m₂)
(hne : m₁ ≠ m₂) : P.wPrefers w m₂ m₁ := by
by_cases h : hasPending σ
· rw [step, dif_pos h] at hnew
let m := Classical.choose h
have hspec := Classical.choose_spec h
by_cases hww : w = nextWoman σ m hspec.2
· subst w
rw [proposalStep_wPartner_target hspec.1 hspec.2] at hnew
change (if accepts σ m (nextWoman σ m hspec.2) then some m
else σ.wPartner (nextWoman σ m hspec.2)) = some m₂ at hnew
by_cases hacc : accepts σ m (nextWoman σ m hspec.2)
· simp [hacc] at hnew
have hm₂ : m₂ = m := by
exact hnew.symm
subst m₂
have hacc' : ∃ m₃, σ.wPartner (nextWoman σ m hspec.2) = some m₃ ∧
P.wPrefers (nextWoman σ m hspec.2) m m₃ := by
rcases hacc with hnone | hpref
· exfalso
rw [hold] at hnone
simp at hnone
· exact hpref
rcases hacc' with ⟨m₃, hw₃, hpref⟩
have hm₃ : m₃ = m₁ := by
rw [hold] at hw₃
exact Option.some.inj hw₃.symm
simpa [hm₃] using hpref
· simp [hacc] at hnew
rw [hold] at hnew
exact False.elim (hne (by simpa using hnew))
· rw [proposalStep_wPartner_other hspec.1 hspec.2 hww] at hnew
rw [hold] at hnew
exact False.elim (hne (by simpa using hnew))
· simp [step, h] at hnew
rw [hold] at hnew
exact False.elim (hne (by simpa using hnew))Forget the proposal bookkeeping: a GS state is a pairing.
def GSState.toPairing (σ : GSState P) : Pairing M W :=
{ mPartner := σ.mPartner, wPartner := σ.wPartner, h_consistency := σ.h_consistency }The number of proposals still to be made: the total count of women each man has not yet proposed to.
noncomputable def pendingCount (σ : GSState P) : ℕ :=
∑ m : M, (Fintype.card W - (σ.proposed m).card)The proposed sets of a proposal step: the proposing man gains exactly the target woman, everyone else is unchanged.
lemma proposalStep_proposed (σ : GSState P) (m : M) (hfree : σ.mPartner m = none)
(hpend : σ.proposed m ≠ Finset.univ) (m' : M) :
(proposalStep σ m hfree hpend).proposed m' =
if m' = m then insert (nextWoman σ m hpend) (σ.proposed m') else σ.proposed m' := by
by_cases hmm' : m' = m
· subst m'
rw [proposalStep_proposed_self hfree hpend]
simp
· rw [proposalStep_proposed_other hfree hpend hmm']
simp [hmm']A proposal step strictly decreases the number of pending proposals.
lemma pendingCount_step_lt (σ : GSState P) (h : hasPending σ) :
pendingCount (step σ) < pendingCount σ := by
let m := Classical.choose h
have hspec := Classical.choose_spec h
have hstep : step σ = proposalStep σ m hspec.1 hspec.2 := by
simp [step, h, m, hspec]
rw [hstep]
unfold pendingCount
have hdecrease : ∀ m' : M,
(Fintype.card W - ((proposalStep σ m hspec.1 hspec.2).proposed m').card) ≤
(Fintype.card W - (σ.proposed m').card) := by
intro m'
by_cases hmm' : m' = m
· subst m'
rw [proposalStep_proposed_self hspec.1 hspec.2]
have hcard : (insert (nextWoman σ m hspec.2) (σ.proposed m)).card =
(σ.proposed m).card + 1 :=
Finset.card_insert_of_notMem (nextWoman_not_proposed σ m hspec.2)
rw [hcard]
omega
· rw [proposalStep_proposed_other hspec.1 hspec.2 hmm']
have hstrict : (Fintype.card W - ((proposalStep σ m hspec.1 hspec.2).proposed m).card) <
(Fintype.card W - (σ.proposed m).card) := by
rw [proposalStep_proposed_self hspec.1 hspec.2]
have hcard : (insert (nextWoman σ m hspec.2) (σ.proposed m)).card =
(σ.proposed m).card + 1 :=
Finset.card_insert_of_notMem (nextWoman_not_proposed σ m hspec.2)
rw [hcard]
have hlt : (σ.proposed m).card < Fintype.card W := by
exact Finset.card_lt_card
(Finset.ssubset_iff_subset_ne.mpr ⟨Finset.subset_univ _, hspec.2⟩)
omega
exact Finset.sum_lt_sum (fun m' hm' => hdecrease m') ⟨m, Finset.mem_univ m, hstrict⟩
Iterating the proposal step n times.
The loop preserves the invariants.
lemma gsLoopN_invariant (σ : GSState P) (hσ : Invariant σ) (n : ℕ) :
Invariant (gsLoopN n σ) := by
induction n generalizing σ with
| zero => simpa [gsLoopN]
| succ n ih => simpa [gsLoopN] using ih (step σ) (step_preserves_invariant hσ)Once no proposal is pending, further steps change nothing.
lemma gsLoopN_eq_self_of_no_pending {σ : GSState P} (h : ¬ hasPending σ) (n : ℕ) :
gsLoopN n σ = σ := by
induction n with
| zero => rfl
| succ n ih =>
simp [gsLoopN, step_eq_self_of_no_pending h, ih]The Gale–Shapley proposal loop (CLRS §25.2): repeat the proposal step until no proposal is pending. The recursion is well-founded on the pending count, which strictly decreases at every pending step, so the loop is guaranteed to halt.
noncomputable def gsLoop (σ : GSState P) : GSState P :=
if h : hasPending σ then gsLoop (step σ) else σ
termination_by pendingCount σ
decreasing_by
exact pendingCount_step_lt σ hThe proposal loop halts: its output has no pending proposal.
lemma gsLoop_no_pending (σ : GSState P) : ¬ hasPending (gsLoop σ) := by
rw [gsLoop]
by_cases h : hasPending σ
· simpa [h] using gsLoop_no_pending (step σ)
· simp [h]
termination_by pendingCount σ
decreasing_by
exact pendingCount_step_lt σ hThe proposal loop preserves the invariants.
lemma gsLoop_invariant (σ : GSState P) (hσ : Invariant σ) :
Invariant (gsLoop σ) := by
rw [gsLoop]
by_cases h : hasPending σ
· simpa [h] using gsLoop_invariant (step σ) (step_preserves_invariant hσ)
· simpa [h]
termination_by pendingCount σ
decreasing_by
exact pendingCount_step_lt σ hThe Gale–Shapley output pairing (CLRS §25.2): run the proposal loop from the empty state.
noncomputable def gs (P : PreferenceProfile M W) : Pairing M W :=
(gsLoop (init : GSState P)).toPairingend StableMarriageend CLRSCLRSLean.FourthEdition.Chapter_25.Section_25_2_Stable_Marriage.S3_Optimality
S3. Optimality of the Gale–Shapley output
The stable-marriage theorems of CLRS §25.2: the Gale–Shapley output is a stable pairing, every preference profile admits a stable pairing, the output is a perfect pairing when the two sides have equal size, and the output is man-optimal and woman-pessimal.
Main results:
-
gs_stable: the Gale–Shapley output is stable (CLRS Theorem 25.5) -
stable_matching_exists: every preference profile has a stable pairing -
gs_perfect: with equal numbers of men and women, the Gale–Shapley output is a perfect pairing -
gs_man_optimal: every man is matched to his best valid partner (CLRS Theorem 25.6) -
gs_man_optimal_perfect: with equal numbers, every man is matched to his best valid partner -
gs_woman_pessimal: every woman is matched to her worst valid partner -
unmatched_man_proposes_to_all: at the final state an unmatched man has proposed to every woman
The man-optimality proof is time-indexed: runAt n is the state after n
steps, proposesAt n m w detects that m proposes to w at step n+1
(w leaves his unproposed set), and rejectedAt n m w detects that w
rejects m at step n+1. no_rejection_of_valid shows that no man is ever
rejected by a valid partner: a rejection at step n+1 forces an earlier
rejection of a valid partner (rejected_valid_earlier), contradicting
minimality of the first such rejection. The proposal loop is placed on the
timeline by gsLoop_eq_gsLoopN, which identifies the final state with the
indexed run at its pending count.
namespace CLRSnamespace StableMarriageopen Finset Classical Matchingsvariable {M : Type*} [Fintype M] [DecidableEq M]variable {W : Type*} [Fintype W] [DecidableEq W]variable (P : PreferenceProfile M W)At the final state of the proposal loop, an unmatched man has proposed to every woman.
lemma unmatched_man_proposes_to_all (P : PreferenceProfile M W) {m : M}
(hm : (gsLoop (init : GSState P)).mPartner m = none) :
(gsLoop (init : GSState P)).proposed m = Finset.univ := by
by_contra hnot
exact gsLoop_no_pending (init : GSState P) ⟨m, hm, hnot⟩Stability of the Gale–Shapley output (CLRS Theorem 25.5): the pairing produced by the proposal algorithm is stable.
theorem gs_stable (P : PreferenceProfile M W) : Pairing.Stable P (gs P) := by
let σ : GSState P := gsLoop (init : GSState P)
have hσ : Invariant σ := by
unfold σ
exact gsLoop_invariant (init : GSState P) init_invariant
have hhalt : ¬ hasPending σ := by
unfold σ
exact gsLoop_no_pending (init : GSState P)
intro m w hblock
rcases hblock with ⟨hwside, hmside⟩
have hwprop : w ∈ σ.proposed m := by
by_cases hmfree : σ.mPartner m = none
· have hpropall : σ.proposed m = Finset.univ := by
by_contra hnot
exact hhalt ⟨m, hmfree, hnot⟩
rw [hpropall]
exact Finset.mem_univ w
· rcases hmside with hmun | ⟨w₀, hw₀, hpref⟩
· exfalso
exact hmfree (by simpa [σ, gs, GSState.toPairing] using hmun)
· have hw₀prop : w₀ ∈ σ.proposed m :=
hσ.partner_proposed (by simpa [σ, gs, GSState.toPairing] using hw₀)
exact hσ.downward_closed hw₀prop hpref
have hwmatched : σ.wPartner w ≠ none := hσ.w_proposed_matched hwprop
rcases hwside with hwun | ⟨m₀, hwm₀, hpref_w⟩
· exact False.elim (hwmatched (by simpa [σ, gs, GSState.toPairing] using hwun))
· have hbest : P.wRank w m₀ ≤ P.wRank w m :=
hσ.best_among_proposers (by simpa [σ, gs, GSState.toPairing] using hwm₀) hwprop
exact (lt_irrefl (P.wRank w m₀)) (lt_of_le_of_lt hbest hpref_w)Existence of stable matchings (CLRS §25.2): every preference profile admits a stable pairing — the Gale–Shapley output is one.
theorem stable_matching_exists (P : PreferenceProfile M W) :
∃ μ : Pairing M W, Pairing.Stable P μ :=
⟨gs P, gs_stable P⟩
A non-none option is some its value.
lemma ne_none_iff_exists {α : Type*} {a : Option α} (h : a ≠ none) :
∃ x, a = some x := by
cases a with
| none => exact False.elim (h (by rfl))
| some x => exact ⟨x, rfl⟩
The matched women and the matched men of a consistent pairing have the
same cardinality: the partner map w ↦ wPartner w is a bijection between
them.
lemma matched_card_eq {σ : GSState P} (hM : Nonempty M) :
(Finset.univ.filter fun w : W => σ.wPartner w ≠ none).card =
(Finset.univ.filter fun m : M => σ.mPartner m ≠ none).card := by
let f : W → M := fun w =>
if hw : σ.wPartner w ≠ none then
Classical.choose (ne_none_iff_exists hw)
else Classical.choice hM
refine Finset.card_bij (fun w hw => f w) ?_ ?_ ?_
· intro a ha
simp only [Finset.mem_filter, Finset.mem_univ, true_and] at ha ⊢
have hf : f a = Classical.choose (ne_none_iff_exists ha) := by simp [f, ha]
rw [hf]
have hs : σ.wPartner a = some (Classical.choose (ne_none_iff_exists ha)) :=
Classical.choose_spec (ne_none_iff_exists ha)
intro hnot
have hm := (σ.h_consistency (Classical.choose (ne_none_iff_exists ha)) a).mpr hs
simp [hnot] at hm
· intro a ha b hb hf
simp only [Finset.mem_filter, Finset.mem_univ, true_and] at ha hb
have hfa : f a = Classical.choose (ne_none_iff_exists ha) := by simp [f, ha]
have hfb : f b = Classical.choose (ne_none_iff_exists hb) := by simp [f, hb]
rw [hfa, hfb] at hf
have hsa : σ.wPartner a = some (Classical.choose (ne_none_iff_exists ha)) :=
Classical.choose_spec (ne_none_iff_exists ha)
have hsb : σ.wPartner b = some (Classical.choose (ne_none_iff_exists hb)) :=
Classical.choose_spec (ne_none_iff_exists hb)
have hma : σ.mPartner (Classical.choose (ne_none_iff_exists ha)) = some a :=
(σ.h_consistency (Classical.choose (ne_none_iff_exists ha)) a).mpr hsa
have hmb : σ.mPartner (Classical.choose (ne_none_iff_exists hb)) = some b :=
(σ.h_consistency (Classical.choose (ne_none_iff_exists hb)) b).mpr hsb
have : some a = some b := by
rw [← hma]
rw [hf]
rw [hmb]
exact Option.some.inj this
· intro b hb
simp only [Finset.mem_filter, Finset.mem_univ, true_and] at hb
rcases ne_none_iff_exists hb with ⟨w, hw⟩
have hwm : σ.wPartner w = some b := (σ.h_consistency b w).mp hw
have hw' : σ.wPartner w ≠ none := by
rw [hwm]
simp
refine ⟨w, ?_, ?_⟩
· simp only [Finset.mem_filter, Finset.mem_univ, true_and]
intro hnot
have hmw := (σ.h_consistency b w).mp hw
simp [hnot] at hmw
· have hf : f w = Classical.choose (ne_none_iff_exists hw') := by simp [f, hw']
rw [hf]
have hs : σ.wPartner w = some (Classical.choose (ne_none_iff_exists hw')) :=
Classical.choose_spec (ne_none_iff_exists hw')
have hs' : some b = some (Classical.choose (ne_none_iff_exists hw')) := by
simpa [hwm] using hs
have hEq : Classical.choose (ne_none_iff_exists hw') = b := by
exact Option.some.inj hs'.symm
exact hEqThe matched set of a side is everything when its cardinality forces it.
lemma filter_eq_univ_of_card_eq {α : Type*} [Fintype α] [DecidableEq α]
(p : α → Prop) [DecidablePred p] (h : (Finset.univ.filter p).card = Fintype.card α) :
∀ a : α, p a := by
intro a
by_contra hnot
have hlt : (Finset.univ.filter p).card < Fintype.card α := by
exact Finset.card_lt_card
(Finset.ssubset_iff_subset_ne.mpr ⟨Finset.subset_univ _, by
intro heq
have hmem : a ∈ Finset.univ.filter p := by
rw [heq]
exact Finset.mem_univ a
exact hnot (Finset.mem_filter.mp hmem).2⟩)
omegaPerfectness of the Gale–Shapley output (CLRS §25.2): with equally many men and women, the algorithm terminates with a perfect pairing.
theorem gs_perfect (P : PreferenceProfile M W) (hcard : Fintype.card M = Fintype.card W) :
(gs P).IsPerfect := by
by_cases hM : Nonempty M
· let σ : GSState P := gsLoop (init : GSState P)
have hσ : Invariant σ := by
unfold σ
exact gsLoop_invariant (init : GSState P) init_invariant
have hhalt : ¬ hasPending σ := by
unfold σ
exact gsLoop_no_pending (init : GSState P)
have hbij : (Finset.univ.filter fun w : W => σ.wPartner w ≠ none).card =
(Finset.univ.filter fun m : M => σ.mPartner m ≠ none).card :=
matched_card_eq P hM
have hmmatched : ∀ m : M, ∃ w : W, σ.mPartner m = some w := by
intro m
by_contra hnot
have hmfree : σ.mPartner m = none := by
by_contra hnotnone
exact hnot (ne_none_iff_exists hnotnone)
have hpropall : σ.proposed m = Finset.univ := by
by_contra h
exact hhalt ⟨m, hmfree, h⟩
have hwmatched : ∀ w : W, σ.wPartner w ≠ none := by
intro w
have hwprop : w ∈ σ.proposed m := by
rw [hpropall]
exact Finset.mem_univ w
exact hσ.w_proposed_matched hwprop
have hcardW : (Finset.univ.filter fun w : W => σ.wPartner w ≠ none).card =
Fintype.card W := by
rw [show (Finset.univ.filter fun w : W => σ.wPartner w ≠ none) = Finset.univ by
apply Finset.eq_univ_iff_forall.mpr
intro w
simp [hwmatched w]]
simp
have hcardM : (Finset.univ.filter fun m : M => σ.mPartner m ≠ none).card =
Fintype.card M := by
rw [← hbij, hcardW, hcard]
have hall : ∀ m' : M, σ.mPartner m' ≠ none :=
filter_eq_univ_of_card_eq (fun m : M => σ.mPartner m ≠ none) hcardM
exact hnot (ne_none_iff_exists (hall m))
have hwmatched : ∀ w : W, ∃ m : M, σ.wPartner w = some m := by
intro w
have hcardM : (Finset.univ.filter fun m : M => σ.mPartner m ≠ none).card =
Fintype.card M := by
rw [show (Finset.univ.filter fun m : M => σ.mPartner m ≠ none) = Finset.univ by
apply Finset.eq_univ_iff_forall.mpr
intro m
rcases hmmatched m with ⟨w, hw⟩
simp [hw]]
simp
have hcardW : (Finset.univ.filter fun w : W => σ.wPartner w ≠ none).card =
Fintype.card W := by
rw [hbij, hcardM, hcard]
have hall : ∀ w' : W, σ.wPartner w' ≠ none :=
filter_eq_univ_of_card_eq (fun w : W => σ.wPartner w ≠ none) hcardW
exact ne_none_iff_exists (hall w)
constructor
· intro m
exact hmmatched m
· intro w
exact hwmatched w
· constructor
· intro m
exfalso
exact hM ⟨m⟩
· intro w
exfalso
have hW : ¬ Nonempty W := by
intro hW
have hcardW : Fintype.card W = 0 := by
rw [← hcard]
letI : IsEmpty M := not_nonempty_iff.mp hM
exact Fintype.card_eq_zero (α := M)
have hpos : 0 < Fintype.card W := (Fintype.card_pos_iff).mpr hW
omega
exact hW ⟨w⟩
A woman w is a valid partner of man m when some stable pairing
matches m with w (CLRS §25.2).
def validPartner (P : PreferenceProfile M W) (m : M) (w : W) : Prop :=
∃ μ : Pairing M W, Pairing.Stable P μ ∧ μ.mPartner m = some w
The state of the proposal loop after exactly n steps from the initial
state.
noncomputable def runAt (P : PreferenceProfile M W) (n : ℕ) : GSState P :=
gsLoopN n (init : GSState P)
Running the loop n+1 times from σ is stepping the n-step run.
lemma gsLoopN_step_comm (σ : GSState P) (n : ℕ) :
gsLoopN (n+1) σ = step (gsLoopN n σ) := by
induction n generalizing σ with
| zero => rfl
| succ n ih =>
calc
gsLoopN (n+2) σ = gsLoopN (n+1) (step σ) := rfl
_ = step (gsLoopN n (step σ)) := ih (step σ)
_ = step (gsLoopN (n+1) σ) := by rflOne more step of the indexed run steps the current state.
lemma runAt_succ (n : ℕ) : runAt P (n+1) = step (runAt P n) := by
unfold runAt
exact gsLoopN_step_comm P (init : GSState P) nFrom a state with pending proposals, the pending count is positive.
lemma pendingCount_pos_of_hasPending (σ : GSState P) (h : hasPending σ) :
1 ≤ pendingCount σ := by
classical
rcases h with ⟨m, hmfree, hpropne⟩
unfold pendingCount
have hlt : (σ.proposed m).card < Fintype.card W := by
exact Finset.card_lt_card
(Finset.ssubset_iff_subset_ne.mpr ⟨Finset.subset_univ _, hpropne⟩)
have hterm : 1 ≤ Fintype.card W - (σ.proposed m).card := by omega
have hsum : Fintype.card W - (σ.proposed m).card ≤
∑ m : M, (Fintype.card W - (σ.proposed m).card) := by
exact Finset.single_le_sum
(fun m' hm' => Nat.zero_le (Fintype.card W - (σ.proposed m').card))
(Finset.mem_univ m)
omega
The proposal loop is the indexed run at its pending count: after
pendingCount σ steps the loop has halted.
lemma gsLoopN_eq_gsLoop_of_ge (σ : GSState P) (k : ℕ) (hk : pendingCount σ ≤ k) :
gsLoopN k σ = gsLoop σ := by
rw [gsLoop]
by_cases h : hasPending σ
· rw [dif_pos h]
have hk1 : 1 ≤ k := le_trans (pendingCount_pos_of_hasPending P σ h) hk
have hlt : pendingCount (step σ) < pendingCount σ := pendingCount_step_lt σ h
have hk' : k = (k - 1) + 1 := by omega
calc
gsLoopN k σ = gsLoopN ((k - 1) + 1) σ := by rw [← hk']
_ = gsLoopN (k - 1) (step σ) := rfl
_ = gsLoop (step σ) := by
exact gsLoopN_eq_gsLoop_of_ge (step σ) (k - 1) (by omega)
· rw [dif_neg h]
exact gsLoopN_eq_self_of_no_pending h k
termination_by pendingCount σ
decreasing_by
exact pendingCount_step_lt σ h
The Gale–Shapley loop from σ equals the indexed run of length
pendingCount σ.
lemma gsLoop_eq_gsLoopN (σ : GSState P) : gsLoop σ = gsLoopN (pendingCount σ) σ := by
exact (gsLoopN_eq_gsLoop_of_ge P σ (pendingCount σ) le_rfl).symm
Man m proposes to woman w at step n+1: w leaves the unproposed set
between the n-th and (n+1)-st states.
def proposesAt (n : ℕ) (m : M) (w : W) : Prop :=
w ∉ (runAt P n).proposed m ∧ w ∈ (runAt P (n+1)).proposed m
Man m is rejected by woman w at step n+1: w either drops m
between the two states, or rejects his proposal.
def rejectedAt (n : ℕ) (m : M) (w : W) : Prop :=
((runAt P n).mPartner m = some w ∧ (runAt P (n+1)).mPartner m ≠ some w) ∨
(proposesAt P n m w ∧ ¬ accepts (runAt P n) m w)
A proposal at step n+1 is exactly the proposal step of m to his next
woman.
lemma proposesAt_extract (n : ℕ) (m : M) (w : W) (hp : proposesAt P n m w) :
∃ hfree : (runAt P n).mPartner m = none,
∃ hpend : (runAt P n).proposed m ≠ Finset.univ,
w = nextWoman (runAt P n) m hpend ∧ runAt P (n+1) = proposalStep (runAt P n) m hfree hpend := by
classical
unfold proposesAt at hp
rw [runAt_succ P n] at hp ⊢
by_cases h : hasPending (runAt P n)
· let c := Classical.choose h
have hspec := Classical.choose_spec h
have hstep : step (runAt P n) = proposalStep (runAt P n) c hspec.1 hspec.2 := by
simp [step, h, c]
have hmem : w ∈ (step (runAt P n)).proposed m := hp.2
have hmc : m = c := by
by_contra hmc
have h' : (proposalStep (runAt P n) c hspec.1 hspec.2).proposed m =
(runAt P n).proposed m := by
exact proposalStep_proposed_other hspec.1 hspec.2 hmc
exact hp.1 (by simpa [hstep, h'] using hmem)
subst m
have hmem' : w ∈ insert (nextWoman (runAt P n) c hspec.2) ((runAt P n).proposed c) := by
rw [← proposalStep_proposed_self hspec.1 hspec.2]
rw [← hstep]
exact hmem
have hw : w = nextWoman (runAt P n) c hspec.2 := by
rcases Finset.mem_insert.mp hmem' with hw | hmem''
· exact hw
· exact False.elim (hp.1 (by simpa using hmem''))
refine ⟨hspec.1, hspec.2, hw, ?_⟩
exact hstep
· have h' : step (runAt P n) = runAt P n := step_eq_self_of_no_pending h
rw [h'] at hp
exact False.elim (hp.1 hp.2)
A man proposing at step n+1 is free at time n.
lemma proposesAt_free (n : ℕ) (m : M) (w : W) (hp : proposesAt P n m w) :
(runAt P n).mPartner m = none := by
rcases proposesAt_extract P n m w hp with ⟨hfree, hpend, hw, hstep⟩
exact hfreeThe proposer's partner after a proposal step: the target woman when she accepts, and unmatched otherwise.
lemma proposesAt_self (n : ℕ) (m : M) (w : W) (hp : proposesAt P n m w) :
(runAt P (n+1)).mPartner m = if accepts (runAt P n) m w then some w else none := by
rcases proposesAt_extract P n m w hp with ⟨hfree, hpend, hw, hstep⟩
rw [hstep]
simpa [← hw] using proposalStep_mPartner_self hfree hpendThe target woman's partner after a proposal step: the proposer when she accepts, and unchanged otherwise.
lemma proposesAt_target (n : ℕ) (m : M) (w : W) (hp : proposesAt P n m w) :
(runAt P (n+1)).wPartner w = if accepts (runAt P n) m w then some m else (runAt P n).wPartner w := by
rcases proposesAt_extract P n m w hp with ⟨hfree, hpend, hw, hstep⟩
rw [hstep]
simpa [← hw] using proposalStep_wPartner_target hfree hpend
If w is in a man's proposed set at time b ≥ 1, he proposed to her at
some earlier step.
lemma proposesAt_exists (b : ℕ) (m : M) (w : W) (hb : 1 ≤ b)
(h : w ∈ (runAt P b).proposed m) : ∃ n < b, proposesAt P n m w := by
classical
have hnonempty : ∃ t : ℕ, w ∈ (runAt P t).proposed m := ⟨b, h⟩
let t₀ : ℕ := Nat.find hnonempty
have ht₀ : w ∈ (runAt P t₀).proposed m := Nat.find_spec hnonempty
have htmin : ∀ t < t₀, w ∉ (runAt P t).proposed m := by
intro t ht
exact Nat.find_min hnonempty ht
have ht₀ge : 1 ≤ t₀ := by
by_contra ht0
have ht₀' : t₀ = 0 := by omega
rw [ht₀'] at ht₀
have hfalse : False := by
simp [runAt, gsLoopN, init] at ht₀
exact hfalse
have ht₀le : t₀ ≤ b := by
by_contra htle
exact htmin b (by omega) h
refine ⟨t₀ - 1, by omega, ?_⟩
constructor
· exact htmin (t₀ - 1) (by omega)
· have ht₀' : t₀ - 1 + 1 = t₀ := by omega
rw [ht₀']
exact ht₀At a rejection step, the rejecting woman is matched to a man she strictly prefers to the rejected man.
lemma rejectedAt_new_partner (n : ℕ) (m : M) (w : W) (hrej : rejectedAt P n m w) :
∃ m₀, (runAt P (n+1)).wPartner w = some m₀ ∧ P.wPrefers w m₀ m := by
rcases hrej with hB | hA
· rcases hB with ⟨hwm, hnotm⟩
by_cases h : hasPending (runAt P n)
· let c := Classical.choose h
have hspec := Classical.choose_spec h
have hstep : step (runAt P n) = proposalStep (runAt P n) c hspec.1 hspec.2 := by
simp [step, h, c]
let w₀ : W := nextWoman (runAt P n) c hspec.2
have hmc : m ≠ c := by
intro hmc
have hc : (runAt P n).mPartner m = none := by simpa [hmc] using hspec.1
exact (by simpa [hc] using hwm)
have hacc : accepts (runAt P n) c w₀ ∧ (runAt P n).wPartner w₀ = some m := by
by_contra hnot'
have hm' : (proposalStep (runAt P n) c hspec.1 hspec.2).mPartner m =
(runAt P n).mPartner m := by
exact proposalStep_mPartner_other hspec.1 hspec.2 hmc hnot'
have hm'w : (runAt P (n+1)).mPartner m = some w := by
rw [runAt_succ P n, hstep, hm', hwm]
exact hnotm hm'w
have hw₀ : w₀ = w := by
have h1 : (runAt P n).mPartner m = some w₀ :=
((runAt P n).h_consistency m w₀).mpr hacc.2
exact Option.some.inj (by rw [← h1, hwm])
have hwnew : (runAt P (n+1)).wPartner w = some c := by
rw [runAt_succ P n, hstep]
have htarget : (proposalStep (runAt P n) c hspec.1 hspec.2).wPartner
(nextWoman (runAt P n) c hspec.2) = some c := by
simpa [w₀, hacc.1] using proposalStep_wPartner_target (σ := runAt P n) (m := c)
hspec.1 hspec.2
rw [← hw₀]
exact htarget
have hwmn : (runAt P n).wPartner w = some m := ((runAt P n).h_consistency m w).mp hwm
have hpref : P.wPrefers w c m := by
rcases hacc.1 with hfree' | hpref'
· exact False.elim (by simp [← hw₀, hfree'] at hwmn)
· rcases hpref' with ⟨m₁, hwm₁, hp'⟩
have hm₁ : m₁ = m := Option.some.inj (by rw [← hwm₁, hw₀, hwmn])
simpa [hw₀, hm₁] using hp'
exact ⟨c, hwnew, hpref⟩
· have hstep : step (runAt P n) = runAt P n := step_eq_self_of_no_pending h
have hm' : (runAt P (n+1)).mPartner m = (runAt P n).mPartner m := by
rw [runAt_succ P n, hstep]
exact False.elim (hnotm (by simpa [hm'] using hwm))
· rcases hA with ⟨hp, hnotacc⟩
rcases proposesAt_extract P n m w hp with ⟨hfree, hpend, hw, hstep⟩
have hwne : (runAt P n).wPartner w ≠ none := by
intro hnone
exact hnotacc (Or.inl hnone)
rcases ne_none_iff_exists hwne with ⟨m₀, hwm₀⟩
refine ⟨m₀, ?_, ?_⟩
· rw [hstep, hw]
have hnotacc' : ¬ accepts (runAt P n) m (nextWoman (runAt P n) m hpend) := by
simpa [hw] using hnotacc
have htarget : (proposalStep (runAt P n) m hfree hpend).wPartner
(nextWoman (runAt P n) m hpend) =
(runAt P n).wPartner (nextWoman (runAt P n) m hpend) := by
rw [proposalStep_wPartner_target hfree hpend]
rw [if_neg hnotacc']
rw [htarget, hw.symm]
exact hwm₀
· have hnotpref : ¬ P.wPrefers w m m₀ := by
intro hp'
exact hnotacc (Or.inr ⟨m₀, hwm₀, hp'⟩)
have hm₀ne : m₀ ≠ m := by
intro hm₀
have h : (runAt P n).mPartner m = some w := ((runAt P n).h_consistency m w).mpr
(by simpa [hm₀] using hwm₀)
exact False.elim (by simpa [hfree] using h)
have hle : P.wRank w m₀ ≤ P.wRank w m := le_of_not_gt hnotpref
have hne : P.wRank w m₀ ≠ P.wRank w m := Function.Injective.ne (P.h_wRank_injective w) hm₀ne
exact lt_of_le_of_ne hle hne
After a rejection step the new partner m₀ of the rejecting woman has
already proposed to every woman w₀ he strictly prefers to her by time n:
his proposed set does not gain w₀ at the rejection step, and he is not
matched to w₀ at time n.
lemma rejection_partner_facts (n : ℕ) (m : M) (w : W) (m₀ : M) (w₀ : W)
(hrej : rejectedAt P n m w) (hwm₀new : (runAt P (n+1)).wPartner w = some m₀)
(hm₀ne : m₀ ≠ m) (hw₀ne : w₀ ≠ w) (hw₀part : w₀ ∈ (runAt P (n+1)).proposed m₀) :
w₀ ∈ (runAt P n).proposed m₀ ∧ (runAt P n).mPartner m₀ ≠ some w₀ := by
rcases hrej with hB | hA
· rcases hB with ⟨hwm, hnotm⟩
by_cases h : hasPending (runAt P n)
· let c := Classical.choose h
have hspec := Classical.choose_spec h
have hstep : step (runAt P n) = proposalStep (runAt P n) c hspec.1 hspec.2 := by
simp [step, h, c]
let w₀' : W := nextWoman (runAt P n) c hspec.2
have hmc : m ≠ c := by
intro hmc
have hc : (runAt P n).mPartner m = none := by simpa [hmc] using hspec.1
exact (by simpa [hc] using hwm)
have hacc : accepts (runAt P n) c w₀' ∧ (runAt P n).wPartner w₀' = some m := by
by_contra hnot'
have hm' : (proposalStep (runAt P n) c hspec.1 hspec.2).mPartner m =
(runAt P n).mPartner m := by
exact proposalStep_mPartner_other hspec.1 hspec.2 hmc hnot'
have hm'w : (runAt P (n+1)).mPartner m = some w := by
rw [runAt_succ P n, hstep, hm', hwm]
exact hnotm hm'w
have hw₀' : w₀' = w := by
have h1 : (runAt P n).mPartner m = some w₀' :=
((runAt P n).h_consistency m w₀').mpr hacc.2
exact Option.some.inj (by rw [← h1, hwm])
have hm₀c : m₀ = c := by
have hwnew : (runAt P (n+1)).wPartner w = some c := by
rw [runAt_succ P n, hstep]
have htarget : (proposalStep (runAt P n) c hspec.1 hspec.2).wPartner
(nextWoman (runAt P n) c hspec.2) = some c := by
simpa [w₀', hacc.1] using proposalStep_wPartner_target (σ := runAt P n) (m := c)
hspec.1 hspec.2
rw [← hw₀']
exact htarget
exact Option.some.inj (by rw [← hwnew, hwm₀new])
have hw₀partn : w₀ ∈ (runAt P n).proposed m₀ := by
have hprop : (runAt P (n+1)).proposed m₀ = insert w ((runAt P n).proposed m₀) := by
rw [runAt_succ P n, hstep]
rw [hm₀c]
rw [proposalStep_proposed_self hspec.1 hspec.2]
change insert (w₀' : W) ((runAt P n).proposed c) = insert w ((runAt P n).proposed c)
rw [hw₀']
rw [hprop] at hw₀part
rcases Finset.mem_insert.mp hw₀part with hw₀eq | hw₀in
· exact False.elim (hw₀ne hw₀eq)
· exact hw₀in
have hmn₀ : (runAt P n).mPartner m₀ ≠ some w₀ := by
have hm₀none : (runAt P n).mPartner m₀ = none := by simpa [hm₀c] using hspec.1
intro h
exact (by simpa [hm₀none] using h)
exact ⟨hw₀partn, hmn₀⟩
· have hstep : step (runAt P n) = runAt P n := step_eq_self_of_no_pending h
have hm' : (runAt P (n+1)).mPartner m = (runAt P n).mPartner m := by
rw [runAt_succ P n, hstep]
exact False.elim (hnotm (by simpa [hm'] using hwm))
· rcases hA with ⟨hp, hnotacc⟩
rcases proposesAt_extract P n m w hp with ⟨hfree, hpend, hw, hstep⟩
have hw₀partn : w₀ ∈ (runAt P n).proposed m₀ := by
have hprop : (runAt P (n+1)).proposed m₀ = (runAt P n).proposed m₀ := by
rw [hstep]
exact proposalStep_proposed_other hfree hpend hm₀ne
rwa [← hprop]
have hmn₀ : (runAt P n).mPartner m₀ ≠ some w₀ := by
have hnot' : ¬ (accepts (runAt P n) m (nextWoman (runAt P n) m hpend) ∧
(runAt P n).wPartner (nextWoman (runAt P n) m hpend) = some m₀) := by
intro hc
have hwm' : (runAt P (n+1)).wPartner w = some m := by
rw [hstep, hw]
have htarget : (proposalStep (runAt P n) m hfree hpend).wPartner
(nextWoman (runAt P n) m hpend) = some m := by
simpa [hc.1] using proposalStep_wPartner_target (σ := runAt P n) (m := m) hfree hpend
exact htarget
exact hm₀ne (Option.some.inj (by rw [← hwm', hwm₀new]))
have hm' : (runAt P n).mPartner m₀ = some w := by
have hmm' : (proposalStep (runAt P n) m hfree hpend).mPartner m₀ =
(runAt P n).mPartner m₀ :=
proposalStep_mPartner_other hfree hpend hm₀ne hnot'
have hcons : (proposalStep (runAt P n) m hfree hpend).mPartner m₀ = some w :=
((proposalStep (runAt P n) m hfree hpend).h_consistency m₀ w).mpr
(by simpa [hstep] using hwm₀new)
rw [← hmm', hcons]
intro h
have hw₀eq : w = w₀ := Option.some.inj (by rw [← h, hm'])
exact hw₀ne hw₀eq.symm
exact ⟨hw₀partn, hmn₀⟩
A rejection of a valid partner implies an earlier rejection of a valid
partner: from a rejection at step n+1 one can construct a rejection at an
earlier step.
lemma rejected_valid_earlier (n : ℕ) (m : M) (w : W) (hrej : rejectedAt P n m w)
(hval : validPartner P m w) : ∃ n' < n, ∃ m' w', rejectedAt P n' m' w' ∧ validPartner P m' w' := by
rcases hval with ⟨μ, hstable, hμm⟩
rcases rejectedAt_new_partner P n m w hrej with ⟨m₀, hwm₀new, hpref₀⟩
have hm₀ne : m₀ ≠ m := by
intro hm₀
have h : P.wRank w m < P.wRank w m := by
rw [hm₀] at hpref₀
exact hpref₀
exact (lt_irrefl (P.wRank w m)) h
have hμm₀ne : μ.mPartner m₀ ≠ none := by
intro hm₀none
have hblock : Pairing.BlockingPair P μ m₀ w := by
constructor
· exact Or.inr ⟨m, (μ.h_consistency m w).mp hμm, hpref₀⟩
· exact Or.inl hm₀none
exact hstable m₀ w hblock
rcases ne_none_iff_exists hμm₀ne with ⟨w₀, hμm₀⟩
have hw₀ne : w₀ ≠ w := by
intro hw₀
have h1 : μ.wPartner w = some m₀ := (μ.h_consistency m₀ w).mp (by simpa [hw₀] using hμm₀)
have h2 : μ.wPartner w = some m := (μ.h_consistency m w).mp hμm
exact hm₀ne (Option.some.inj (by rw [← h1, h2]))
have hpref₀' : P.mPrefers m₀ w₀ w := by
have hnotm : ¬ (μ.mPartner m₀ = none ∨ ∃ w₁, μ.mPartner m₀ = some w₁ ∧ P.mPrefers m₀ w w₁) := by
intro hm
exact hstable m₀ w ⟨Or.inr ⟨m, (μ.h_consistency m w).mp hμm, hpref₀⟩, hm⟩
have hnotpref : ¬ P.mPrefers m₀ w w₀ := by
intro hp
exact hnotm (Or.inr ⟨w₀, hμm₀, hp⟩)
have hle : P.mRank m₀ w₀ ≤ P.mRank m₀ w := le_of_not_gt hnotpref
have hne : P.mRank m₀ w₀ ≠ P.mRank m₀ w := Function.Injective.ne (P.h_mRank_injective m₀) hw₀ne
exact lt_of_le_of_ne hle hne
have hσ₁ : Invariant (runAt P (n+1)) := by
unfold runAt
exact gsLoopN_invariant (init : GSState P) init_invariant (n+1)
have hwm₀part : w ∈ (runAt P (n+1)).proposed m₀ := by
have hm₀w : (runAt P (n+1)).mPartner m₀ = some w :=
((runAt P (n+1)).h_consistency m₀ w).mpr hwm₀new
exact hσ₁.partner_proposed hm₀w
have hw₀part : w₀ ∈ (runAt P (n+1)).proposed m₀ := hσ₁.downward_closed hwm₀part hpref₀'
have hpair : w₀ ∈ (runAt P n).proposed m₀ ∧ (runAt P n).mPartner m₀ ≠ some w₀ :=
rejection_partner_facts P n m w m₀ w₀ hrej hwm₀new hm₀ne hw₀ne hw₀part
rcases hpair with ⟨hw₀partn, hmn₀⟩
have hn1 : 1 ≤ n := by
by_contra hn
have hn' : n = 0 := by omega
rw [hn'] at hw₀partn
simp [runAt, gsLoopN, init] at hw₀partn
rcases proposesAt_exists P n m₀ w₀ hn1 hw₀partn with ⟨n', hn'lt, hprop'⟩
by_cases hacc : accepts (runAt P n') m₀ w₀
· have hself : (runAt P (n'+1)).mPartner m₀ = some w₀ := by
simpa [hacc] using proposesAt_self P n' m₀ w₀ hprop'
let p : ℕ → Prop := fun t => n' + 1 < t ∧ (runAt P t).mPartner m₀ ≠ some w₀
have hwitness : p (n+1) := by
constructor
· omega
· have hcons : (runAt P (n+1)).mPartner m₀ = some w :=
((runAt P (n+1)).h_consistency m₀ w).mpr hwm₀new
intro h
exact hw₀ne (Option.some.inj (by rw [← h, hcons]))
have hT : ∃ t, p t := ⟨n+1, hwitness⟩
let T : ℕ := Nat.find hT
have hTspec : p T := Nat.find_spec hT
have hTmin : ∀ t < T, ¬ p t := by
intro t ht
exact Nat.find_min hT ht
have hTle : T ≤ n + 1 := by
by_contra hlt
exact hTmin (n+1) (by omega) hwitness
have hTne : T ≠ n + 1 := by
intro hT'
by_cases hn'lt : n' + 1 < n
· exact hTmin n (by omega) ⟨hn'lt, hmn₀⟩
· have hn'eq : n' + 1 = n := by omega
exact hmn₀ (by simpa [hn'eq] using hself)
have hT1 : T - 1 < n := by omega
have hrej' : rejectedAt P (T-1) m₀ w₀ := by
have hm₁ : (runAt P (T-1)).mPartner m₀ = some w₀ := by
by_cases hT1eq : T - 1 = n' + 1
· rwa [hT1eq]
· have hT1gt : n' + 1 < T - 1 := by omega
have hnotp : ¬ p (T-1) := hTmin (T-1) (by omega)
by_cases hm' : (runAt P (T-1)).mPartner m₀ = some w₀
· exact hm'
· exact False.elim (hnotp ⟨hT1gt, hm'⟩)
have hT' : (runAt P (T - 1 + 1)).mPartner m₀ ≠ some w₀ := by
simpa [show T - 1 + 1 = T by omega] using hTspec.2
exact Or.inl ⟨hm₁, hT'⟩
exact ⟨T - 1, hT1, m₀, w₀, hrej', ⟨μ, hstable, hμm₀⟩⟩
· have hrej' : rejectedAt P n' m₀ w₀ := Or.inr ⟨hprop', hacc⟩
exact ⟨n', hn'lt, m₀, w₀, hrej', ⟨μ, hstable, hμm₀⟩⟩No man is ever rejected by a valid partner during the proposal run: rejections of valid partners would descend to strictly earlier steps.
theorem no_rejection_of_valid : ¬ ∃ n m w, rejectedAt P n m w ∧ validPartner P m w := by
classical
intro h
let p : ℕ → Prop := fun n => ∃ m w, rejectedAt P n m w ∧ validPartner P m w
have hnonempty : ∃ n, p n := h
let n₀ : ℕ := Nat.find hnonempty
have h₀ : p n₀ := Nat.find_spec hnonempty
have hmin : ∀ n < n₀, ¬ p n := by
intro n hn
exact Nat.find_min hnonempty hn
rcases h₀ with ⟨m, w, hrej, hval⟩
rcases rejected_valid_earlier P n₀ m w hrej hval with ⟨n', hn'lt, m', w', hrej', hval'⟩
exact hmin n' hn'lt ⟨m', w', hrej', hval'⟩A man matched at the end of the run cannot strictly prefer a valid partner to his final partner.
lemma better_valid_contradiction (k : ℕ) (m : M) (w : W) (hm : (runAt P k).mPartner m = some w)
{w' : W} (hval : validPartner P m w') (hpref : P.mPrefers m w' w) : False := by
have hk1 : 1 ≤ k := by
by_contra hk
have hk' : k = 0 := by omega
rw [hk'] at hm
simp [runAt, gsLoopN, init] at hm
have hσ : Invariant (runAt P k) := by
unfold runAt
exact gsLoopN_invariant (init : GSState P) init_invariant k
have hwprop : w ∈ (runAt P k).proposed m := hσ.partner_proposed hm
have hw'prop : w' ∈ (runAt P k).proposed m := hσ.downward_closed hwprop hpref
rcases proposesAt_exists P k m w' hk1 hw'prop with ⟨n, hnlt, hp⟩
by_cases hacc : accepts (runAt P n) m w'
· have hself : (runAt P (n+1)).mPartner m = some w' := by
simpa [hacc] using proposesAt_self P n m w' hp
have hww' : w' ≠ w := by
intro h
have h₁ : P.mRank m w < P.mRank m w := by
rw [h] at hpref
exact hpref
exact (lt_irrefl (P.mRank m w)) h₁
let p : ℕ → Prop := fun t => n + 1 < t ∧ (runAt P t).mPartner m ≠ some w'
have hwitness : p k := by
constructor
· by_contra hnk
have hkeq : k = n + 1 := by omega
have hm' : (runAt P (n+1)).mPartner m = some w := by simpa [hkeq] using hm
exact hww' (Option.some.inj (by rw [← hself, hm']))
· intro h
exact hww' (Option.some.inj (by rw [← h, hm]))
have hT : ∃ t, p t := ⟨k, hwitness⟩
let T : ℕ := Nat.find hT
have hTspec : p T := Nat.find_spec hT
have hTmin : ∀ t < T, ¬ p t := by
intro t ht
exact Nat.find_min hT ht
have hT1 : T - 1 < k := by
have hTle : T ≤ k := by
by_contra hlt
exact hTmin k (by omega) hwitness
have hTpos : 1 ≤ T := by
have h : n + 1 < T := hTspec.1
omega
omega
have hrej : rejectedAt P (T-1) m w' := by
have hm₁ : (runAt P (T-1)).mPartner m = some w' := by
by_cases hT1eq : T - 1 = n + 1
· rwa [hT1eq]
· have hT1gt : n + 1 < T - 1 := by omega
have hnotp : ¬ p (T-1) := hTmin (T-1) (by omega)
by_cases hm' : (runAt P (T-1)).mPartner m = some w'
· exact hm'
· exact False.elim (hnotp ⟨hT1gt, hm'⟩)
have hT' : (runAt P (T - 1 + 1)).mPartner m ≠ some w' := by
simpa [show T - 1 + 1 = T by omega] using hTspec.2
exact Or.inl ⟨hm₁, hT'⟩
exact no_rejection_of_valid P ⟨T - 1, m, w', hrej, hval⟩
· have hrej : rejectedAt P n m w' := Or.inr ⟨hp, hacc⟩
exact no_rejection_of_valid P ⟨n, m, w', hrej, hval⟩Man-optimality of the Gale–Shapley output (CLRS Theorem 25.6): every man is matched to his best valid partner — no stable matching pairs him with a woman he strictly prefers to his Gale–Shapley partner.
theorem gs_man_optimal (P : PreferenceProfile M W) :
∀ m w, (gs P).mPartner m = some w →
∀ w', validPartner P m w' → ¬ P.mPrefers m w' w := by
intro m w hm w' hval
by_contra hpref
have hσeq : gsLoop (init : GSState P) = runAt P (pendingCount (init : GSState P)) := by
unfold runAt
exact gsLoop_eq_gsLoopN P (init : GSState P)
have hm' : (runAt P (pendingCount (init : GSState P))).mPartner m = some w := by
simp [gs, GSState.toPairing, hσeq] at hm
exact hm
exact better_valid_contradiction P (pendingCount (init : GSState P)) m w hm' hval hprefMan-optimality with equal cardinality (CLRS Theorem 25.6): with equally many men and women, every man is matched to his best valid partner.
theorem gs_man_optimal_perfect (P : PreferenceProfile M W)
(hcard : Fintype.card M = Fintype.card W) :
∀ m, ∃ w, (gs P).mPartner m = some w ∧
∀ w', validPartner P m w' → ¬ P.mPrefers m w' w := by
intro m
rcases (gs_perfect P hcard).1 m with ⟨w, hw⟩
exact ⟨w, hw, gs_man_optimal P m w hw⟩Woman-pessimality of the Gale–Shapley output (CLRS §25.2): each woman who receives a partner does not strictly prefer that Gale–Shapley partner to any partner she receives in another stable pairing. Thus her Gale–Shapley partner is her worst valid partner. With equal cardinalities, perfectness discharges the matched-woman premise.
theorem gs_woman_pessimal (P : PreferenceProfile M W) :
∀ w m₀, (gs P).wPartner w = some m₀ →
∀ μ : Pairing M W, Pairing.Stable P μ →
∀ m', μ.wPartner w = some m' → ¬ P.wPrefers w m₀ m' := by
intro w m₀ hwm₀ μ hstable m' hμm'
by_contra hpref
have hnot : ¬ Pairing.BlockingPair P μ m₀ w := hstable m₀ w
have hwside : μ.wPartner w = none ∨ ∃ m₁, μ.wPartner w = some m₁ ∧ P.wPrefers w m₀ m₁ :=
Or.inr ⟨m', hμm', hpref⟩
have hnotm : ¬ (μ.mPartner m₀ = none ∨ ∃ w', μ.mPartner m₀ = some w' ∧ P.mPrefers m₀ w w') := by
intro hm
exact hnot ⟨hwside, hm⟩
have hm₀ne : μ.mPartner m₀ ≠ none := by
intro hn
exact hnotm (Or.inl hn)
rcases ne_none_iff_exists hm₀ne with ⟨w₀, hm₀w₀⟩
have hnotpref : ¬ P.mPrefers m₀ w w₀ := by
intro hp
exact hnotm (Or.inr ⟨w₀, hm₀w₀, hp⟩)
have hw₀ne : w₀ ≠ w := by
intro h
have h1 : μ.wPartner w = some m₀ := (μ.h_consistency m₀ w).mp (by simpa [h] using hm₀w₀)
have hmm' : m₀ = m' := Option.some.inj (by rw [← h1, hμm'])
have h : P.wRank w m₀ < P.wRank w m₀ := by
rw [← hmm'] at hpref
exact hpref
exact (lt_irrefl (P.wRank w m₀)) h
have hpref₀ : P.mPrefers m₀ w₀ w := by
have hle : P.mRank m₀ w₀ ≤ P.mRank m₀ w := le_of_not_gt hnotpref
have hne : P.mRank m₀ w₀ ≠ P.mRank m₀ w := Function.Injective.ne (P.h_mRank_injective m₀) hw₀ne
exact lt_of_le_of_ne hle hne
have hσm₀ : (gs P).mPartner m₀ = some w := ((gs P).h_consistency m₀ w).mpr hwm₀
have hvalidw₀ : validPartner P m₀ w₀ := ⟨μ, hstable, hm₀w₀⟩
exact gs_man_optimal P m₀ w hσm₀ w₀ hvalidw₀ hpref₀end StableMarriageend CLRS