Imports

24.3. Maximum bipartite matching

This file formalizes the bipartite-to-flow-network reduction of CLRS §24.3 and proves Theorem 24.12: the maximum matching size equals the maximum flow value in the unit-capacity network. It constructs the feasible flow induced by every matching, recovers a matching from every integral flow, iterates augmentation from the zero flow to obtain an integral maximum flow, and combines the two directions.

Main results:

  • BipartiteGraph, Matching, and Matching.size

  • capFunc and toFlowNetwork

  • matchingFlowFun and matchingFlowFunSummand

  • matchingToFlow and matchingToFlow_value: the feasible flow induced by a matching has value |M|

  • Flow.IsIntegral, matchingOfIntegralFlow, and matchingOfIntegralFlow_size: an integral flow of value v yields a matching of size v

  • maxMatching_eq_maxFlow_value (Theorem 24.12): the maximum matching size equals the value of a maximal flow

namespace CLRSnamespace Chapter26open Finset Classical

A bipartite graph with left partition L, right partition R, and edges E that only go from L to R. (CLRS §24.3.)

structure BipartiteGraph (V : Type*) [Fintype V] [DecidableEq V] where L : Finset V R : Finset V h_disjoint : L R = h_cover : L R = Finset.univ E : Finset (V × V) hE_subset : e E, e.1 L e.2 R

A matching in a bipartite graph: a set of edges with no shared endpoints.

structure Matching (V : Type*) [Fintype V] [DecidableEq V] (G : BipartiteGraph V) where edges : Finset (V × V) h_subset : edges G.E h_unique_left : (l r₁ r₂ : V), (l, r₁) edges (l, r₂) edges r₁ = r₂ h_unique_right : (l₁ l₂ r : V), (l₁, r) edges (l₂, r) edges l₁ = l₂

The size (cardinality) of a matching.

def Matching.size {V : Type*} [Fintype V] [DecidableEq V] {G : BipartiteGraph V} (M : Matching V G) : := M.edges.card
lemma Matching.left_mem_L {V : Type*} [Fintype V] [DecidableEq V] {G : BipartiteGraph V} (M : Matching V G) {l r : V} (h : (l, r) M.edges) : l G.L := by have hE : (l, r) G.E := M.h_subset h; exact (G.hE_subset (l, r) hE).1lemma Matching.right_mem_R {V : Type*} [Fintype V] [DecidableEq V] {G : BipartiteGraph V} (M : Matching V G) {l r : V} (h : (l, r) M.edges) : r G.R := by have hE : (l, r) G.E := M.h_subset h; exact (G.hE_subset (l, r) hE).2

Capacity function defined as a standalone def so simp can use it.

def capFunc (V : Type*) [Fintype V] [DecidableEq V] (G : BipartiteGraph V) (u v : V Bool) : := match u, v with | Sum.inr true, Sum.inl l' => if l' G.L then (1 : ) else 0 | Sum.inl l', Sum.inl r' => if (l', r') G.E then (1 : ) else 0 | Sum.inl r', Sum.inr false => if r' G.R then (1 : ) else 0 | _, _ => 0

The flow network constructed from a bipartite graph (CLRS eq. (24.11)).

Used `tac1 <;> tac2` where `(tac1; tac2)` would suffice Note: This linter can be disabled with `set_option linter.unnecessarySeqFocus false` def toFlowNetwork (V : Type*) [Fintype V] [DecidableEq V] (G : BipartiteGraph V) : FlowNetwork (V Bool) := { s := Sum.inr true , t := Sum.inr false , c := capFunc V G , hc_nonneg := λ u v => have h_nonneg : 0 capFunc V G u v := by unfold capFunc cases u with | inl a => cases v with | inl b => simp; split_ifs <;> norm_num | inr b => cases b <;> simp Used `tac1 <;> tac2` where `(tac1; tac2)` would suffice Note: This linter can be disabled with `set_option linter.unnecessarySeqFocus false`<;> try (split_ifs <;> norm_num) | inr a => cases a with | true => cases v with | inl b => simp; split_ifs <;> norm_num | inr b => cases b <;> simp <;> this tactic is never executed Note: This linter can be disabled with `set_option linter.unreachableTactic false`'try (split_ifs <;> norm_num)' tactic does nothing Note: This linter can be disabled with `set_option linter.unusedTactic false`try (split_ifs <;> norm_num) | false => cases v with | inl b => norm_num | inr b => cases b <;> simp <;> this tactic is never executed Note: This linter can be disabled with `set_option linter.unreachableTactic false`'try (split_ifs <;> norm_num)' tactic does nothing Note: This linter can be disabled with `set_option linter.unusedTactic false`try (split_ifs <;> norm_num) h_nonneg , hc_self := λ u => by unfold capFunc match u with | Sum.inl v => by_cases h : (v, v) G.E · have hvL : v G.L := (G.hE_subset (v, v) h).1 have hvR : v G.R := (G.hE_subset (v, v) h).2 have : v G.L G.R := Finset.mem_inter.mpr hvL, hvR rw [G.h_disjoint] at this; simp at this · simp [h] | Sum.inr _ => simp , hs_ne_t := by simp }

The flow induced by a matching

The contribution of a single matched edge e to the matching-induced flow on pair (u,v) (CLRS eq. (24.11)): +1 along s → e.1, e.1 → e.2, e.2 → t, −1 on the reverse edges, and 0 elsewhere.

The (e.1, e.2) direction is written as the sum of two indicators so that the forward and reverse contributions cancel on degenerate pairs, making the summand skew-symmetric for every e.

def matchingFlowFunSummand {V : Type*} [DecidableEq V] (e : V × V) (u v : V Bool) : := match u, v with | Sum.inr true, Sum.inl l => if e.1 = l then (1 : ) else 0 | Sum.inl a, Sum.inl b => (if e = (a, b) then (1 : ) else 0) + (if e = (b, a) then (-1 : ) else 0) | Sum.inl r, Sum.inr false => if e.2 = r then (1 : ) else 0 | Sum.inl l, Sum.inr true => if e.1 = l then (-1 : ) else 0 | Sum.inr false, Sum.inl r => if e.2 = r then (-1 : ) else 0 | _, _ => 0

The flow induced by a matching M in the constructed flow network.

def matchingFlowFun {V : Type*} [Fintype V] [DecidableEq V] {G : BipartiteGraph V} (M : Matching V G) (u v : V Bool) : := Finset.sum M.edges (fun (e : V × V) => matchingFlowFunSummand e u v)

A matching has at most one edge leaving a given left vertex.

lemma count_left_le_one {V : Type*} [Fintype V] [DecidableEq V] {G : BipartiteGraph V} (M : Matching V G) (l : V) : Finset.sum M.edges (fun (e : V × V) => if e.1 = l then (1 : ) else 0) 1 := by have hcard : (M.edges.filter (fun e : V × V => e.1 = l)).card 1 := by refine Finset.card_le_one.mpr ?_ intro e1 he1 e2 he2 have he1' : e1 M.edges := (Finset.mem_filter.mp he1).1 have he2' : e2 M.edges := (Finset.mem_filter.mp he2).1 have h1 : e1.1 = e2.1 := (Finset.mem_filter.mp he1).2.trans (Finset.mem_filter.mp he2).2.symm have h2 : e1.2 = e2.2 := M.h_unique_left e1.1 e1.2 e2.2 he1' (by simpa [h1] using he2') exact Prod.ext h1 h2 have hsum : Finset.sum M.edges (fun (e : V × V) => if e.1 = l then (1 : ) else 0) = ((M.edges.filter (fun e : V × V => e.1 = l)).card : ) := by rw [ Finset.sum_filter] simp rw [hsum] exact_mod_cast hcard

A matching has at most one edge entering a given right vertex.

lemma count_right_le_one {V : Type*} [Fintype V] [DecidableEq V] {G : BipartiteGraph V} (M : Matching V G) (r : V) : Finset.sum M.edges (fun (e : V × V) => if e.2 = r then (1 : ) else 0) 1 := by have hcard : (M.edges.filter (fun e : V × V => e.2 = r)).card 1 := by refine Finset.card_le_one.mpr ?_ intro e1 he1 e2 he2 have he1' : e1 M.edges := (Finset.mem_filter.mp he1).1 have he2' : e2 M.edges := (Finset.mem_filter.mp he2).1 have h1 : e1.2 = e2.2 := (Finset.mem_filter.mp he1).2.trans (Finset.mem_filter.mp he2).2.symm have h2 : e1.1 = e2.1 := M.h_unique_right e1.1 e2.1 e1.2 he1' (by simpa [h1] using he2') exact Prod.ext h2 h1 have hsum : Finset.sum M.edges (fun (e : V × V) => if e.2 = r then (1 : ) else 0) = ((M.edges.filter (fun e : V × V => e.2 = r)).card : ) := by rw [ Finset.sum_filter] simp rw [hsum] exact_mod_cast hcard

The single-edge summand is skew-symmetric: its value on (u,v) is the negation of its value on (v,u).

lemma matchingFlowFunSummand_skew {V : Type*} [DecidableEq V] (e : V × V) (u v : V Bool) : matchingFlowFunSummand e u v = - matchingFlowFunSummand e v u := by unfold matchingFlowFunSummand cases u with | inl a => cases v with | inl b => by_cases h1 : e = (a, b) · by_cases h2 : e = (b, a) · have hab : a = b := by exact (congrArg Prod.fst h1).symm.trans (congrArg Prod.fst h2) simp [h1, hab] · have hab_ne : a b := by intro hab apply h2 simp [h1, hab] simp [h1, hab_ne] · by_cases h2 : e = (b, a) · have hab_ne : a b := by intro hab apply h1 simp [h2, hab] simp [h2, hab_ne] · simp [h1, h2] | inr b => cases b with | true => by_cases h : e.1 = a <;> simp [h] | false => by_cases h : e.2 = a <;> simp [h] | inr b => cases b with | true => cases v with | inl l => by_cases h : e.1 = l <;> simp [h] | inr b' => cases b' <;> simp | false => cases v with | inl r => by_cases h : e.2 = r <;> simp [h] | inr b' => cases b' <;> simp

The flow induced by a matching is skew-symmetric.

lemma matchingFlowFun_skew_symm {V : Type*} [Fintype V] [DecidableEq V] {G : BipartiteGraph V} (M : Matching V G) (u v : V Bool) : matchingFlowFun M u v = - matchingFlowFun M v u := by unfold matchingFlowFun rw [ Finset.sum_neg_distrib] refine Finset.sum_congr rfl (fun e _ => ?_) exact matchingFlowFunSummand_skew e u v

For a fixed edge e, summing the (inl a, inl b) summand over all b counts how many endpoints of e equal a.

lemma matchingFlowFunSummand_inl_inl_sum {V : Type*} [Fintype V] [DecidableEq V] (e : V × V) (a : V) : ( b : V, matchingFlowFunSummand e (Sum.inl a) (Sum.inl b)) = (if e.1 = a then (1 : ) else 0) - (if e.2 = a then (1 : ) else 0) := by have hS1 : ( b : V, if e = (a, b) then (1 : ) else 0) = if e.1 = a then (1 : ) else 0 := by by_cases h : e.1 = a · calc ( b : V, if e = (a, b) then (1 : ) else 0) = if e = (a, e.2) then (1 : ) else 0 := by refine Finset.sum_eq_single e.2 ?_ ?_ · intro b _ hb have hNe : e (a, b) := by intro hEq exact hb (congrArg Prod.snd hEq).symm simp [hNe] · simp _ = if e.1 = a then 1 else 0 := by have hEq : e = (a, e.2) := Prod.ext h rfl rw [hEq] simp · have hsum : ( b : V, if e = (a, b) then (1 : ) else 0) = 0 := by refine Finset.sum_eq_zero (fun b _ => ?_) have hNe : e (a, b) := by intro hEq exact h (congrArg Prod.fst hEq) simp [hNe] simpa [h] using hsum have hS2 : ( b : V, if e = (b, a) then (-1 : ) else 0) = if e.2 = a then (-1 : ) else 0 := by by_cases h : e.2 = a · calc ( b : V, if e = (b, a) then (-1 : ) else 0) = if e = (e.1, a) then (-1 : ) else 0 := by refine Finset.sum_eq_single e.1 ?_ ?_ · intro b _ hb have hNe : e (b, a) := by intro hEq exact hb (congrArg Prod.fst hEq).symm simp [hNe] · simp _ = if e.2 = a then -1 else 0 := by have hEq : e = (e.1, a) := Prod.ext rfl h rw [hEq] simp · have hsum : ( b : V, if e = (b, a) then (-1 : ) else 0) = 0 := by refine Finset.sum_eq_zero (fun b _ => ?_) have hNe : e (b, a) := by intro hEq exact h (congrArg Prod.snd hEq) simp [hNe] simpa [h] using hsum calc ( b : V, matchingFlowFunSummand e (Sum.inl a) (Sum.inl b)) = ( b : V, ((if e = (a, b) then (1 : ) else 0) + (if e = (b, a) then (-1 : ) else 0))) := by rfl _ = ( b : V, if e = (a, b) then (1 : ) else 0) + ( b : V, if e = (b, a) then (-1 : ) else 0) := by rw [Finset.sum_add_distrib] _ = (if e.1 = a then (1 : ) else 0) + (if e.2 = a then (-1 : ) else 0) := by rw [hS1, hS2] _ = (if e.1 = a then (1 : ) else 0) - (if e.2 = a then (1 : ) else 0) := by by_cases h1 : e.1 = a <;> by_cases h2 : e.2 = a <;> simp [h1, h2]

The flow induced by a matching satisfies flow conservation at every non-source non-sink vertex.

lemma matchingFlowFun_conservation {V : Type*} [Fintype V] [DecidableEq V] {G : BipartiteGraph V} (M : Matching V G) (a : V) : ( v : V Bool, matchingFlowFun M (Sum.inl a) v) = 0 := by have h1 : ( b : V, matchingFlowFun M (Sum.inl a) (Sum.inl b)) = Finset.sum M.edges (fun (e : V × V) => (if e.1 = a then (1 : ) else 0) - (if e.2 = a then (1 : ) else 0)) := by unfold matchingFlowFun rw [Finset.sum_comm] refine Finset.sum_congr rfl (fun e _ => ?_) exact matchingFlowFunSummand_inl_inl_sum e a have h2 : matchingFlowFun M (Sum.inl a) (Sum.inr false) = Finset.sum M.edges (fun (e : V × V) => if e.2 = a then (1 : ) else 0) := by unfold matchingFlowFun refine Finset.sum_congr rfl (fun e _ => ?_) rfl have h3 : matchingFlowFun M (Sum.inl a) (Sum.inr true) = -(Finset.sum M.edges (fun (e : V × V) => if e.1 = a then (1 : ) else 0)) := by unfold matchingFlowFun rw [ Finset.sum_neg_distrib] refine Finset.sum_congr rfl (fun e _ => ?_) dsimp [matchingFlowFunSummand] by_cases h : e.1 = a <;> simp [h] calc ( v : V Bool, matchingFlowFun M (Sum.inl a) v) = ( b : V, matchingFlowFun M (Sum.inl a) (Sum.inl b)) + (matchingFlowFun M (Sum.inl a) (Sum.inr false) + matchingFlowFun M (Sum.inl a) (Sum.inr true)) := by rw [ Finset.univ_disjSum_univ (α := V) (β := Bool)] rw [Finset.sum_disjSum (Finset.univ : Finset V) (Finset.univ : Finset Bool) (fun v : V Bool => matchingFlowFun M (Sum.inl a) v)] rw [Fintype.univ_bool] rw [Finset.sum_pair (by decide : true false)] ring _ = 0 := by rw [h1, h2, h3] rw [Finset.sum_sub_distrib] ring

The flow induced by a matching satisfies the capacity constraint f(u,v) ≤ c(u,v).

lemma matchingFlowFun_capacity {V : Type*} [Fintype V] [DecidableEq V] {G : BipartiteGraph V} (M : Matching V G) (u v : V Bool) : matchingFlowFun M u v capFunc V G u v := by cases u with | inl a => cases v with | inr b => cases b with | true => have hflow : matchingFlowFun M (Sum.inl a) (Sum.inr true) 0 := by unfold matchingFlowFun exact Finset.sum_nonpos (fun e he => by dsimp [matchingFlowFunSummand] by_cases h : e.1 = a <;> simp [h]) simpa [capFunc] using hflow | false => by_cases hR : a G.R · have hflow : matchingFlowFun M (Sum.inl a) (Sum.inr false) 1 := by have hsum : matchingFlowFun M (Sum.inl a) (Sum.inr false) = Finset.sum M.edges (fun (e : V × V) => if e.2 = a then (1 : ) else 0) := by unfold matchingFlowFun refine Finset.sum_congr rfl (fun e _ => ?_) rfl rw [hsum] exact count_right_le_one M a simpa [capFunc, hR] using hflow · have hflow : matchingFlowFun M (Sum.inl a) (Sum.inr false) 0 := by unfold matchingFlowFun exact Finset.sum_nonpos (fun e he => by have hNe : e.2 a := by intro hEq exact hR (by simpa [hEq] using M.right_mem_R he) dsimp [matchingFlowFunSummand] simp [hNe]) simpa [capFunc, hR] using hflow | inl b => by_cases hE : (a, b) G.E · have hflow : matchingFlowFun M (Sum.inl a) (Sum.inl b) 1 := by have hsum_le : Finset.sum M.edges (fun (e : V × V) => if e = (a, b) then (1 : ) else 0) 1 := by rw [Finset.sum_ite_eq' M.edges (a, b) (fun _ => (1 : ))] by_cases h : (a, b) M.edges <;> simp [h] have hflow_le : matchingFlowFun M (Sum.inl a) (Sum.inl b) Finset.sum M.edges (fun (e : V × V) => if e = (a, b) then (1 : ) else 0) := by unfold matchingFlowFun exact Finset.sum_le_sum (fun e he => by dsimp [matchingFlowFunSummand] by_cases h1 : e = (a, b) · by_cases h2 : e = (b, a) · have hab : a = b := by exact (congrArg Prod.fst h1).symm.trans (congrArg Prod.fst h2) simp [h1, hab] · have hab_ne : a b := by intro hab apply h2 simp [h1, hab] simp [h1, hab_ne] · by_cases h2 : e = (b, a) · have hab_ne : a b := by intro hab apply h1 simp [h2, hab] simp [h2, hab_ne] · simp [h1, h2]) linarith simpa [capFunc, hE] using hflow · have hflow : matchingFlowFun M (Sum.inl a) (Sum.inl b) 0 := by unfold matchingFlowFun exact Finset.sum_nonpos (fun e he => by have hNe : e (a, b) := by intro hEq exact hE (by simpa [hEq] using M.h_subset he) dsimp [matchingFlowFunSummand] by_cases h2 : e = (b, a) · have hab_ne : a b := by intro hab exact hNe (by simp [h2, hab]) simp [h2, hab_ne] · simp [hNe, h2]) simpa [capFunc, hE] using hflow | inr b => cases b with | true => cases v with | inr b' => cases b' <;> dsimp [matchingFlowFun, matchingFlowFunSummand] <;> simp [capFunc] | inl l => by_cases hL : l G.L · have hflow : matchingFlowFun M (Sum.inr true) (Sum.inl l) 1 := by have hsum : matchingFlowFun M (Sum.inr true) (Sum.inl l) = Finset.sum M.edges (fun (e : V × V) => if e.1 = l then (1 : ) else 0) := by unfold matchingFlowFun refine Finset.sum_congr rfl (fun e _ => ?_) rfl rw [hsum] exact count_left_le_one M l simpa [capFunc, hL] using hflow · have hflow : matchingFlowFun M (Sum.inr true) (Sum.inl l) 0 := by unfold matchingFlowFun exact Finset.sum_nonpos (fun e he => by have hNe : e.1 l := by intro hEq exact hL (by simpa [hEq] using M.left_mem_L he) dsimp [matchingFlowFunSummand] simp [hNe]) simpa [capFunc, hL] using hflow | false => cases v with | inl r => have hflow : matchingFlowFun M (Sum.inr false) (Sum.inl r) 0 := by unfold matchingFlowFun exact Finset.sum_nonpos (fun e he => by dsimp [matchingFlowFunSummand] by_cases h : e.2 = r <;> simp [h]) simpa [capFunc] using hflow | inr b' => cases b' <;> dsimp [matchingFlowFun, matchingFlowFunSummand] <;> simp [capFunc]

The feasible flow induced by a matching (CLRS §24.3). The flow sends one unit along s → l, l → r, r → t for every matched edge (l, r), with skew-symmetric reverse contributions.

noncomputable def matchingToFlow {V : Type*} [Fintype V] [DecidableEq V] {G : BipartiteGraph V} (M : Matching V G) : Flow (V Bool) (toFlowNetwork V G) := { f := matchingFlowFun M , hcapacity := matchingFlowFun_capacity M , hskew_symm := matchingFlowFun_skew_symm M , hconservation := by intro u hu hs cases u with | inl a => exact matchingFlowFun_conservation M a | inr b => cases b with | true => simp [toFlowNetwork] at hu | false => simp [toFlowNetwork] at hs }

The total flow out of the source equals the number of matched edges.

lemma matchingFlowFun_value_sum {V : Type*} [Fintype V] [DecidableEq V] {G : BipartiteGraph V} (M : Matching V G) : Finset.sum (Finset.univ : Finset (V Bool)) (fun v => matchingFlowFun M (Sum.inr true) v) = (M.size : ) := by calc Finset.sum (Finset.univ : Finset (V Bool)) (fun v => matchingFlowFun M (Sum.inr true) v) = Finset.sum (Finset.univ : Finset (V Bool)) (fun v => Finset.sum M.edges (fun (e : V × V) => match v with | Sum.inl l => if e.1 = l then (1 : ) else 0 | Sum.inr _ => 0)) := by refine Finset.sum_congr rfl (fun v hv => ?_) unfold matchingFlowFun cases v with | inl l => rfl | inr b => cases b <;> rfl _ = Finset.sum M.edges (fun (e : V × V) => Finset.sum (Finset.univ : Finset (V Bool)) (fun v => match v with | Sum.inl l => if e.1 = l then (1 : ) else 0 | Sum.inr _ => 0)) := by rw [Finset.sum_comm] _ = Finset.sum M.edges (fun (e : V × V) => (1 : )) := by refine Finset.sum_congr rfl (fun e _ => ?_) simp _ = (M.size : ) := by simp [Matching.size]

Theorem (matching-flow value). The flow induced by a matching M has value equal to |M| (CLRS Theorem 24.12, value direction).

theorem matchingToFlow_value {V : Type*} [Fintype V] [DecidableEq V] {G : BipartiteGraph V} (M : Matching V G) : (matchingToFlow M).value = (M.size : ) := by simp [Flow.value, matchingToFlow, toFlowNetwork, matchingFlowFun_value_sum M]

Integral flows and the converse construction

An integral flow takes only integer values on every edge. Values may be negative (reverse flow); the {0,1} recovery uses the capacity bounds to force nonnegativity on the relevant pairs.

def Flow.IsIntegral {V : Type*} [Fintype V] [DecidableEq V] {G : FlowNetwork V} (φ : Flow V G) : Prop := u v, n : , φ.f u v = (n : )

A real in [0,1] that is an integer is 0 or 1.

lemma integral_of_unit_range {x : } (h01 : 0 x x 1) (hint : n : , x = (n : )) : x = 0 x = 1 := by rcases hint with n, hn have hn_ge : 0 n := by have hx_ge : 0 x := h01.1 rw [hn] at hx_ge exact_mod_cast hx_ge have hn_le : n 1 := by have hx_le : x 1 := h01.2 rw [hn] at hx_le exact_mod_cast hx_le have hn_eq : n = 0 n = 1 := by omega rcases hn_eq with h | h <;> simp [hn, h]

A vertex in R is not in L (the partitions are disjoint).

lemma BipartiteGraph.not_mem_L_of_mem_R {V : Type*} [Fintype V] [DecidableEq V] (G : BipartiteGraph V) {v : V} (h : v G.R) : v G.L := by intro hL have : v G.L G.R := Finset.mem_inter.mpr hL, h rw [G.h_disjoint] at this simp at this

A vertex in L is not in R (the partitions are disjoint).

lemma BipartiteGraph.not_mem_R_of_mem_L {V : Type*} [Fintype V] [DecidableEq V] (G : BipartiteGraph V) {v : V} (h : v G.L) : v G.R := by intro hR have : v G.L G.R := Finset.mem_inter.mpr h, hR rw [G.h_disjoint] at this simp at this

In the unit-capacity matching network, the flow on every L→R pair lies in [0, 1], and is zero when the edge is absent. The reverse capacity is zero because the graph has no anti-parallel edges: (r, l) ∈ E would put l in both partitions.

lemma matchingFlow_lr_bounds {V : Type*} [Fintype V] [DecidableEq V] {G : BipartiteGraph V} (φ : Flow (V Bool) (toFlowNetwork V G)) (l : V) (hl : l G.L) (r : V) : 0 φ.f (Sum.inl l) (Sum.inl r) φ.f (Sum.inl l) (Sum.inl r) (if (l, r) G.E then (1 : ) else 0) := by have hrev : (toFlowNetwork V G).c (Sum.inl r) (Sum.inl l) = 0 := by simp [toFlowNetwork, capFunc] by_cases h : (r, l) G.E · exact False.elim (G.not_mem_R_of_mem_L hl (G.hE_subset (r, l) h).2) · simp [h] simpa [toFlowNetwork, capFunc] using (Flow.range_of_zero_reverse_cap φ (Sum.inl l) (Sum.inl r) hrev)

In the matching network, the flow out of a left vertex equals its inflow from the source (conservation at l ∈ L).

lemma matchingFlow_conservation_left {V : Type*} [Fintype V] [DecidableEq V] {G : BipartiteGraph V} (φ : Flow (V Bool) (toFlowNetwork V G)) (l : V) (hl : l G.L) : φ.f (Sum.inr true) (Sum.inl l) = r : V, φ.f (Sum.inl l) (Sum.inl r) := by have hcons : ( v : V Bool, φ.f (Sum.inl l) v) = 0 := φ.hconservation (Sum.inl l) (by simp [toFlowNetwork]) (by simp [toFlowNetwork]) have hdecomp : ( v : V Bool, φ.f (Sum.inl l) v) = ( r : V, φ.f (Sum.inl l) (Sum.inl r)) + (φ.f (Sum.inl l) (Sum.inr true) + φ.f (Sum.inl l) (Sum.inr false)) := by rw [ Finset.univ_disjSum_univ (α := V) (β := Bool)] rw [Finset.sum_disjSum (Finset.univ : Finset V) (Finset.univ : Finset Bool) (fun v : V Bool => φ.f (Sum.inl l) v)] rw [Fintype.univ_bool] rw [Finset.sum_pair (by decide : true false)] have hlt : φ.f (Sum.inl l) (Sum.inr false) = 0 := by have hrev : (toFlowNetwork V G).c (Sum.inr false) (Sum.inl l) = 0 := by simp [toFlowNetwork, capFunc] have hr0 := Flow.range_of_zero_reverse_cap φ (Sum.inl l) (Sum.inr false) hrev have hcap : (toFlowNetwork V G).c (Sum.inl l) (Sum.inr false) = 0 := by simp [toFlowNetwork, capFunc] by_cases h : l G.R · exact False.elim (G.not_mem_R_of_mem_L hl h) · simp [h] linarith have hls : φ.f (Sum.inl l) (Sum.inr true) = -φ.f (Sum.inr true) (Sum.inl l) := φ.hskew_symm (Sum.inl l) (Sum.inr true) rw [hdecomp, hlt, hls] at hcons linarith

In the matching network, the flow into a right vertex equals its outflow to the sink (conservation at r ∈ R).

lemma matchingFlow_conservation_right {V : Type*} [Fintype V] [DecidableEq V] {G : BipartiteGraph V} (φ : Flow (V Bool) (toFlowNetwork V G)) (r : V) (hr : r G.R) : φ.f (Sum.inl r) (Sum.inr false) = l : V, φ.f (Sum.inl l) (Sum.inl r) := by have hcons : ( v : V Bool, φ.f (Sum.inl r) v) = 0 := φ.hconservation (Sum.inl r) (by simp [toFlowNetwork]) (by simp [toFlowNetwork]) have hdecomp : ( v : V Bool, φ.f (Sum.inl r) v) = ( l : V, φ.f (Sum.inl r) (Sum.inl l)) + (φ.f (Sum.inl r) (Sum.inr true) + φ.f (Sum.inl r) (Sum.inr false)) := by rw [ Finset.univ_disjSum_univ (α := V) (β := Bool)] rw [Finset.sum_disjSum (Finset.univ : Finset V) (Finset.univ : Finset Bool) (fun v : V Bool => φ.f (Sum.inl r) v)] rw [Fintype.univ_bool] rw [Finset.sum_pair (by decide : true false)] have hrs : φ.f (Sum.inl r) (Sum.inr true) = 0 := by have hrev : (toFlowNetwork V G).c (Sum.inr true) (Sum.inl r) = 0 := by simp [toFlowNetwork, capFunc] by_cases h : r G.L · exact False.elim (G.not_mem_L_of_mem_R hr h) · simp [h] have hr0 := Flow.range_of_zero_reverse_cap φ (Sum.inl r) (Sum.inr true) hrev have hcap : (toFlowNetwork V G).c (Sum.inl r) (Sum.inr true) = 0 := by simp [toFlowNetwork, capFunc] linarith have hlr_sum : ( l : V, φ.f (Sum.inl r) (Sum.inl l)) = -( l : V, φ.f (Sum.inl l) (Sum.inl r)) := by calc ( l : V, φ.f (Sum.inl r) (Sum.inl l)) = l : V, -φ.f (Sum.inl l) (Sum.inl r) := by refine Finset.sum_congr rfl (fun l _ => ?_) exact φ.hskew_symm (Sum.inl r) (Sum.inl l) _ = -( l : V, φ.f (Sum.inl l) (Sum.inl r)) := by rw [Finset.sum_neg_distrib] rw [hdecomp, hrs, hlr_sum] at hcons linarith

Flow between two right vertices is zero (both directions have zero capacity).

lemma matchingFlow_rr_zero {V : Type*} [Fintype V] [DecidableEq V] {G : BipartiteGraph V} (φ : Flow (V Bool) (toFlowNetwork V G)) {r₁ r₂ : V} (hr₁ : r₁ G.R) (hr₂ : r₂ G.R) : φ.f (Sum.inl r₁) (Sum.inl r₂) = 0 := by have hcap : (toFlowNetwork V G).c (Sum.inl r₁) (Sum.inl r₂) = 0 := by simp [toFlowNetwork, capFunc] by_cases h : (r₁, r₂) G.E · exact False.elim (G.not_mem_L_of_mem_R hr₁ (G.hE_subset (r₁, r₂) h).1) · simp [h] have hrev : (toFlowNetwork V G).c (Sum.inl r₂) (Sum.inl r₁) = 0 := by simp [toFlowNetwork, capFunc] by_cases h : (r₂, r₁) G.E · exact False.elim (G.not_mem_L_of_mem_R hr₂ (G.hE_subset (r₂, r₁) h).1) · simp [h] have hle := Flow.nonpos_of_zero_cap φ (Sum.inl r₁) (Sum.inl r₂) hcap have hge := Flow.nonneg_of_zero_reverse_cap φ (Sum.inl r₁) (Sum.inl r₂) hrev linarith

On the unit-capacity network an integral flow takes values in {0, 1} on every L→R pair.

lemma integral_lr_unit {V : Type*} [Fintype V] [DecidableEq V] {G : BipartiteGraph V} (φ : Flow (V Bool) (toFlowNetwork V G)) (hint : φ.IsIntegral) (l : V) (hl : l G.L) (r : V) : φ.f (Sum.inl l) (Sum.inl r) = 0 φ.f (Sum.inl l) (Sum.inl r) = 1 := by have hb := matchingFlow_lr_bounds φ l hl r have hle : φ.f (Sum.inl l) (Sum.inl r) 1 := by by_cases hE : (l, r) G.E · simpa [hE] using hb.2 · simp [hE] at hb linarith exact integral_of_unit_range hb.1, hle (hint (Sum.inl l) (Sum.inl r))

On the unit-capacity network an integral flow sends 0 or 1 units out of the source to every left vertex.

lemma integral_source_unit {V : Type*} [Fintype V] [DecidableEq V] {G : BipartiteGraph V} (φ : Flow (V Bool) (toFlowNetwork V G)) (hint : φ.IsIntegral) (l : V) (hl : l G.L) : φ.f (Sum.inr true) (Sum.inl l) = 0 φ.f (Sum.inr true) (Sum.inl l) = 1 := by have hrev : (toFlowNetwork V G).c (Sum.inl l) (Sum.inr true) = 0 := by simp [toFlowNetwork, capFunc] exact integral_of_unit_range (by simpa [toFlowNetwork, capFunc, hl] using (Flow.range_of_zero_reverse_cap φ (Sum.inr true) (Sum.inl l) hrev)) (hint (Sum.inr true) (Sum.inl l))

An edge carrying one unit of flow in the matching network belongs to G.E.

lemma mem_edges_of_flow_one {V : Type*} [Fintype V] [DecidableEq V] {G : BipartiteGraph V} (φ : Flow (V Bool) (toFlowNetwork V G)) {l r : V} (h : φ.f (Sum.inl l) (Sum.inl r) = 1) : (l, r) G.E := by have hle1 : 1 (toFlowNetwork V G).c (Sum.inl l) (Sum.inl r) := by linarith [φ.hcapacity (Sum.inl l) (Sum.inl r), h] by_cases hE : (l, r) G.E · exact hE · simp [toFlowNetwork, capFunc, hE] at hle1 norm_num at hle1

The indicator of a one-unit flow into a right vertex equals the flow value: integral flows take 0 or 1 on L→R pairs and zero on R→R pairs.

lemma flow_one_indicator_eq_of_right {V : Type*} [Fintype V] [DecidableEq V] {G : BipartiteGraph V} (φ : Flow (V Bool) (toFlowNetwork V G)) (hint : φ.IsIntegral) {r : V} (hr : r G.R) (l : V) : (if φ.f (Sum.inl l) (Sum.inl r) = 1 then (1 : ) else 0) = φ.f (Sum.inl l) (Sum.inl r) := by by_cases hl : l G.L · rcases integral_lr_unit φ hint l hl r with h | h <;> simp [h] · have hlR : l G.R := by have : l G.L G.R := by simp [G.h_cover] exact (Finset.mem_union.mp this).resolve_left hl have hz : φ.f (Sum.inl l) (Sum.inl r) = 0 := matchingFlow_rr_zero φ hlR hr simp [hz]

The matching recovered from an integral flow: every L→R pair carrying one unit of flow.

noncomputable def matchingOfIntegralFlow {V : Type*} [Fintype V] [DecidableEq V] {G : BipartiteGraph V} (φ : Flow (V Bool) (toFlowNetwork V G)) (hint : φ.IsIntegral) : Matching V G := { edges := (Finset.univ : Finset (V × V)).filter (fun e : V × V => φ.f (Sum.inl e.1) (Sum.inl e.2) = 1) , h_subset := by intro e he exact mem_edges_of_flow_one φ (Finset.mem_filter.mp he).2 , h_unique_left := by intro l r₁ r₂ h1 h2 have hf1 : φ.f (Sum.inl l) (Sum.inl r₁) = 1 := (Finset.mem_filter.mp h1).2 have hl : l G.L := (G.hE_subset (l, r₁) (mem_edges_of_flow_one φ hf1)).1 have hcount : φ.f (Sum.inr true) (Sum.inl l) = ((Finset.univ.filter (fun r : V => φ.f (Sum.inl l) (Sum.inl r) = 1)).card : ) := by calc φ.f (Sum.inr true) (Sum.inl l) = r : V, φ.f (Sum.inl l) (Sum.inl r) := matchingFlow_conservation_left φ l hl _ = r : V, (if φ.f (Sum.inl l) (Sum.inl r) = 1 then (1 : ) else 0) := by refine Finset.sum_congr rfl (fun r _ => ?_) rcases integral_lr_unit φ hint l hl r with h | h <;> simp [h] _ = ((Finset.univ.filter (fun r : V => φ.f (Sum.inl l) (Sum.inl r) = 1)).card : ) := by rw [ Finset.sum_filter] simp have hle : φ.f (Sum.inr true) (Sum.inl l) 1 := by rcases integral_source_unit φ hint l hl with h | h <;> simp [h] have hcard : (Finset.univ.filter (fun r : V => φ.f (Sum.inl l) (Sum.inl r) = 1)).card 1 := by have hℝ : ((Finset.univ.filter (fun r : V => φ.f (Sum.inl l) (Sum.inl r) = 1)).card : ) 1 := by rw [ hcount] exact hle exact_mod_cast hℝ exact Finset.card_le_one.mp hcard r₁ (by simp [hf1]) r₂ (by simp [(Finset.mem_filter.mp h2).2]) , h_unique_right := by intro l₁ l₂ r h1 h2 have hf1 : φ.f (Sum.inl l₁) (Sum.inl r) = 1 := (Finset.mem_filter.mp h1).2 have hr : r G.R := (G.hE_subset (l₁, r) (mem_edges_of_flow_one φ hf1)).2 have hcount : φ.f (Sum.inl r) (Sum.inr false) = ((Finset.univ.filter (fun l : V => φ.f (Sum.inl l) (Sum.inl r) = 1)).card : ) := by calc φ.f (Sum.inl r) (Sum.inr false) = l : V, φ.f (Sum.inl l) (Sum.inl r) := matchingFlow_conservation_right φ r hr _ = l : V, (if φ.f (Sum.inl l) (Sum.inl r) = 1 then (1 : ) else 0) := by refine Finset.sum_congr rfl (fun l _ => ?_) exact (flow_one_indicator_eq_of_right φ hint hr l).symm _ = ((Finset.univ.filter (fun l : V => φ.f (Sum.inl l) (Sum.inl r) = 1)).card : ) := by rw [ Finset.sum_filter] simp have hr01 : 0 φ.f (Sum.inl r) (Sum.inr false) φ.f (Sum.inl r) (Sum.inr false) 1 := by have hrev : (toFlowNetwork V G).c (Sum.inr false) (Sum.inl r) = 0 := by simp [toFlowNetwork, capFunc] have hr0 := Flow.range_of_zero_reverse_cap φ (Sum.inl r) (Sum.inr false) hrev have hc : (toFlowNetwork V G).c (Sum.inl r) (Sum.inr false) = 1 := by simp [toFlowNetwork, capFunc, hr] exact hr0.1, by simpa [hc] using hr0.2 have hcard : (Finset.univ.filter (fun l : V => φ.f (Sum.inl l) (Sum.inl r) = 1)).card 1 := by have hℝ : ((Finset.univ.filter (fun l : V => φ.f (Sum.inl l) (Sum.inl r) = 1)).card : ) 1 := by rw [ hcount] exact hr01.2 exact_mod_cast hℝ exact Finset.card_le_one.mp hcard l₁ (by simp [hf1]) l₂ (by simp [(Finset.mem_filter.mp h2).2]) }

Theorem (integral-flow converse). An integral flow of value v in the matching network yields a matching of size v (CLRS Theorem 24.12, converse direction).

theorem matchingOfIntegralFlow_size {V : Type*} [Fintype V] [DecidableEq V] {G : BipartiteGraph V} (φ : Flow (V Bool) (toFlowNetwork V G)) (hint : φ.IsIntegral) : ((matchingOfIntegralFlow φ hint).size : ) = φ.value := by have hcard : ((matchingOfIntegralFlow φ hint).edges.card : ) = e : V × V, (if φ.f (Sum.inl e.1) (Sum.inl e.2) = 1 then (1 : ) else 0) := by unfold matchingOfIntegralFlow rw [ Finset.sum_filter] simp have hval : φ.value = l : V, φ.f (Sum.inr true) (Sum.inl l) := by calc φ.value = ( l : V, φ.f (Sum.inr true) (Sum.inl l)) + (φ.f (Sum.inr true) (Sum.inr true) + φ.f (Sum.inr true) (Sum.inr false)) := by simp [Flow.value, toFlowNetwork] _ = l : V, φ.f (Sum.inr true) (Sum.inl l) := by have hss : φ.f (Sum.inr true) (Sum.inr true) = 0 := Flow.self_zero φ (Sum.inr true) have hst : φ.f (Sum.inr true) (Sum.inr false) = 0 := by have hrev : (toFlowNetwork V G).c (Sum.inr false) (Sum.inr true) = 0 := by simp [toFlowNetwork, capFunc] have hr0 := Flow.range_of_zero_reverse_cap φ (Sum.inr true) (Sum.inr false) hrev have hcap : (toFlowNetwork V G).c (Sum.inr true) (Sum.inr false) = 0 := by simp [toFlowNetwork, capFunc] linarith simp [hss, hst] calc ((matchingOfIntegralFlow φ hint).size : ) = e : V × V, (if φ.f (Sum.inl e.1) (Sum.inl e.2) = 1 then (1 : ) else 0) := by simp only [Matching.size] rw [hcard] _ = l : V, r : V, (if φ.f (Sum.inl l) (Sum.inl r) = 1 then (1 : ) else 0) := by have huniv : (Finset.univ : Finset (V × V)) = Finset.univ.product Finset.univ := by ext e; simp rw [huniv] exact (Finset.sum_product Finset.univ Finset.univ (fun e : V × V => if φ.f (Sum.inl e.1) (Sum.inl e.2) = 1 then (1 : ) else 0)) _ = l : V, φ.f (Sum.inr true) (Sum.inl l) := by refine Finset.sum_congr rfl (fun l _ => ?_) by_cases hl : l G.L · calc r : V, (if φ.f (Sum.inl l) (Sum.inl r) = 1 then (1 : ) else 0) = r : V, φ.f (Sum.inl l) (Sum.inl r) := by refine Finset.sum_congr rfl (fun r _ => ?_) rcases integral_lr_unit φ hint l hl r with h | h <;> simp [h] _ = φ.f (Sum.inr true) (Sum.inl l) := (matchingFlow_conservation_left φ l hl).symm · have hlR : l G.R := by have : l G.L G.R := by simp [G.h_cover] exact (Finset.mem_union.mp this).resolve_left hl have hsum : r : V, (if φ.f (Sum.inl l) (Sum.inl r) = 1 then (1 : ) else 0) = 0 := by refine Finset.sum_eq_zero (fun r _ => ?_) have hcap : (toFlowNetwork V G).c (Sum.inl l) (Sum.inl r) = 0 := by simp [toFlowNetwork, capFunc] by_cases h : (l, r) G.E · exact False.elim (hl (G.hE_subset (l, r) h).1) · simp [h] have hf : φ.f (Sum.inl l) (Sum.inl r) 0 := Flow.nonpos_of_zero_cap φ (Sum.inl l) (Sum.inl r) hcap by_cases h : φ.f (Sum.inl l) (Sum.inl r) = 1 · exfalso; linarith · simp [h] have hsrc : φ.f (Sum.inr true) (Sum.inl l) = 0 := by have hcap : (toFlowNetwork V G).c (Sum.inr true) (Sum.inl l) = 0 := by simp [toFlowNetwork, capFunc] by_cases h : l G.L · exact False.elim (hl h) · simp [h] have hrev : (toFlowNetwork V G).c (Sum.inl l) (Sum.inr true) = 0 := by simp [toFlowNetwork, capFunc] have hr0 := Flow.range_of_zero_reverse_cap φ (Sum.inr true) (Sum.inl l) hrev linarith rw [hsum, hsrc] _ = φ.value := hval.symm

Integral maximum flow and Theorem 24.12

The zero flow on a network.

noncomputable def zeroFlow {V : Type*} [Fintype V] [DecidableEq V] (G : FlowNetwork V) : Flow V G := { f := fun _ _ => 0 , hcapacity := by intro u v; exact G.hc_nonneg u v , hskew_symm := by intro u v; simp , hconservation := by intro u hu ht; simp }

The zero flow is integral.

lemma IsIntegral_zero {V : Type*} [Fintype V] [DecidableEq V] {G : FlowNetwork V} : (zeroFlow G).IsIntegral := by intro u v exact 0, by simp [zeroFlow]

The residual capacity of an integral flow on an integral-capacity network is an integer.

lemma residualCapacity_integral {V : Type*} [Fintype V] [DecidableEq V] {G : FlowNetwork V} (φ : Flow V G) (hint : φ.IsIntegral) (hc : u v, n : , G.c u v = (n : )) (u v : V) : n : , φ.residualCapacity u v = (n : ) := by rcases hc u v with m, hm rcases hint u v with n, hn unfold Flow.residualCapacity refine m - n, ?_ rw [hm, hn] push_cast ring

The bottleneck of an augmenting path in an integral network is an integer.

lemma bottleneck_integral {V : Type*} [Fintype V] [DecidableEq V] {G : FlowNetwork V} (φ : Flow V G) (hint : φ.IsIntegral) (hc : u v, n : , G.c u v = (n : )) (p : Flow.AugmentingPath φ) : n : , p.bottleneck = (n : ) := by have hmem : p.bottleneck p.edges.toFinset.image (fun e => φ.residualCapacity e.1 e.2) := by unfold Flow.AugmentingPath.bottleneck exact Finset.min'_mem _ _ rcases Finset.mem_image.mp hmem with e, he, hEq rw [ hEq] exact residualCapacity_integral φ hint hc e.1 e.2

The bottleneck of an augmenting path in an integral network is at least 1.

lemma bottleneck_ge_one {V : Type*} [Fintype V] [DecidableEq V] {G : FlowNetwork V} (φ : Flow V G) (hint : φ.IsIntegral) (hc : u v, n : , G.c u v = (n : )) (p : Flow.AugmentingPath φ) : 1 p.bottleneck := by unfold Flow.AugmentingPath.bottleneck rw [Finset.le_min'_iff] intro x hx rcases Finset.mem_image.mp hx with e, he, rfl have hpos : 0 < φ.residualCapacity e.1 e.2 := p.residualEdge_of_mem_edges (by simpa using he) rcases residualCapacity_integral φ hint hc e.1 e.2 with n, hn rw [hn] have hn_pos : 0 < n := by have h0 : 0 < (n : ) := by simpa [hn] using hpos exact_mod_cast h0 exact_mod_cast (by omega : 1 n)

The single-edge update of an integral delta is integral.

lemma edgeDelta_integral {V : Type*} [DecidableEq V] {delta : } (hdelta : n : , delta = (n : )) (a b u v : V) : n : , Flow.edgeDelta delta a b u v = (n : ) := by rcases hdelta with n, hn by_cases h1 : u = a v = b · by_cases h2 : u = b v = a · have hab : a = b := h1.1.symm.trans h2.1 exact 0, by simp [Flow.edgeDelta, h1, hab, hn] · have hab_ne : a b := by intro hab apply h2 exact by simpa [hab] using h1.1, by simpa [hab] using h1.2 exact n, by simp [Flow.edgeDelta, h1, hab_ne, hn] · by_cases h2 : u = b v = a · have hab_ne : a b := by intro hab apply h1 exact by simpa [hab] using h2.1, by simpa [hab] using h2.2 exact -n, by simp [Flow.edgeDelta, h2, hab_ne, hn] · exact 0, by simp [Flow.edgeDelta, h1, h2]

The path update of an integral delta is integral.

lemma pathDelta_integral {V : Type*} [DecidableEq V] {delta : } (hdelta : n : , delta = (n : )) (xs : List V) (u v : V) : n : , Flow.pathDelta delta xs u v = (n : ) := by induction xs with | nil => exact 0, by simp [Flow.pathDelta] | cons a xs ih => cases xs with | nil => exact 0, by simp [Flow.pathDelta] | cons b xs => rcases edgeDelta_integral hdelta a b u v with m, hm rcases ih with k, hk exact m + k, by simp only [Flow.pathDelta] rw [hm, hk] push_cast ring

Augmentation preserves integrality on an integral-capacity network.

lemma IsIntegral_augment {V : Type*} [Fintype V] [DecidableEq V] {G : FlowNetwork V} (φ : Flow V G) (hint : φ.IsIntegral) (hc : u v, n : , G.c u v = (n : )) (p : Flow.AugmentingPath φ) : (φ.augment p).IsIntegral := by intro u v have hb : n : , p.bottleneck = (n : ) := bottleneck_integral φ hint hc p have hf : n : , φ.f u v = (n : ) := hint u v have hp : n : , Flow.pathDelta p.bottleneck p.vertices u v = (n : ) := pathDelta_integral hb p.vertices u v rcases hf with n, hn rcases hp with k, hk exact n + k, by change φ.f u v + Flow.pathDelta p.bottleneck p.vertices u v = ((n + k : ) : ) rw [hn, hk] push_cast ring

Augmenting along a residual path increases the value by at least one on an integral network.

lemma augment_value_ge_one {V : Type*} [Fintype V] [DecidableEq V] {G : FlowNetwork V} (φ : Flow V G) (hint : φ.IsIntegral) (hc : u v, n : , G.c u v = (n : )) (p : Flow.AugmentingPath φ) : φ.value + 1 (φ.augment p).value := by rw [φ.augment_value p] linarith [bottleneck_ge_one φ hint hc p]

One augmentation step: augment along an arbitrary residual source-to-sink path if one exists.

noncomputable def augmentOnce {V : Type*} [Fintype V] [DecidableEq V] {G : FlowNetwork V} (φ : Flow V G) : Flow V G := if h : φ.hasAugmentingPath then φ.augment (Classical.choice (Flow.hasAugmentingPath_iff_nonempty_augmentingPath.mp h)) else φ

augmentOnce preserves integrality.

lemma IsIntegral_augmentOnce {V : Type*} [Fintype V] [DecidableEq V] {G : FlowNetwork V} (φ : Flow V G) (hint : φ.IsIntegral) (hc : u v, n : , G.c u v = (n : )) : (augmentOnce φ).IsIntegral := by unfold augmentOnce by_cases h : φ.hasAugmentingPath · simp [h] exact IsIntegral_augment φ hint hc (Classical.choice (Flow.hasAugmentingPath_iff_nonempty_augmentingPath.mp h)) · simp [h] exact hint

Repeatedly augment from a starting flow.

noncomputable def iterAugment {V : Type*} [Fintype V] [DecidableEq V] {G : FlowNetwork V} (φ : Flow V G) : Flow V G | 0 => φ | n + 1 => augmentOnce (iterAugment φ n)

Every iterate of iterAugment is integral.

lemma IsIntegral_iterAugment {V : Type*} [Fintype V] [DecidableEq V] {G : FlowNetwork V} (φ : Flow V G) (hint : φ.IsIntegral) (hc : u v, n : , G.c u v = (n : )) : n, (iterAugment φ n).IsIntegral := by intro n induction n with | zero => simpa [iterAugment] using hint | succ n ih => simpa [iterAugment] using (IsIntegral_augmentOnce (iterAugment φ n) ih hc)

Each augmentation step increases the value by at least one, unless the flow is already free of augmenting paths.

lemma iterAugment_step_value {V : Type*} [Fintype V] [DecidableEq V] {G : FlowNetwork V} (φ : Flow V G) (hint : φ.IsIntegral) (hc : u v, n : , G.c u v = (n : )) (n : ) : ¬(iterAugment φ n).hasAugmentingPath (iterAugment φ n).value + 1 (iterAugment φ (n + 1)).value := by by_cases h : (iterAugment φ n).hasAugmentingPath · right simp [iterAugment, augmentOnce, h] exact augment_value_ge_one (iterAugment φ n) (IsIntegral_iterAugment φ hint hc n) hc (Classical.choice (Flow.hasAugmentingPath_iff_nonempty_augmentingPath.mp h)) · left exact h

Flow value is bounded by the total capacity out of the source.

lemma value_le_source_cut {V : Type*} [Fintype V] [DecidableEq V] {G : FlowNetwork V} (φ : Flow V G) : φ.value Finset.sum (Finset.univ : Finset V) (fun v => G.c G.s v) := by unfold Flow.value exact Finset.sum_le_sum (fun v _ => φ.hcapacity G.s v)

The total capacity out of the source is an integer on an integral network.

lemma source_cut_integral {V : Type*} [Fintype V] [DecidableEq V] {G : FlowNetwork V} (hc : u v, n : , G.c u v = (n : )) : n : , Finset.sum (Finset.univ : Finset V) (fun v => G.c G.s v) = (n : ) := by classical choose nv hnv using (fun v => hc G.s v) refine Finset.sum (Finset.univ : Finset V) nv, ?_ rw [show (fun v : V => G.c G.s v) = fun v : V => (nv v : ) by funext v exact hnv v] norm_cast

While every step finds an augmenting path, the value after n steps is at least n.

lemma iterAugment_value_ge {V : Type*} [Fintype V] [DecidableEq V] {G : FlowNetwork V} (φ : Flow V G) (hint : φ.IsIntegral) (hc : u v, n : , G.c u v = (n : )) ( : 0 φ.value) (hsteps : n, (iterAugment φ n).hasAugmentingPath) : n : , (n : ) (iterAugment φ n).value := by intro n induction n with | zero => simpa [iterAugment] using | succ n ih => have hinc := (iterAugment_step_value φ hint hc n).resolve_left (not_not_intro (hsteps n)) push_cast linarith

Repeated augmentation from an integral flow terminates at a flow without augmenting paths: the value strictly increases by at least one each step and is bounded by the (integral) source-side cut capacity.

lemma exists_noAugmentingPath_iter {V : Type*} [Fintype V] [DecidableEq V] {G : FlowNetwork V} (φ : Flow V G) (hint : φ.IsIntegral) (hc : u v, n : , G.c u v = (n : )) ( : 0 φ.value) : n, ¬ (iterAugment φ n).hasAugmentingPath := by by_contra hnot have hsteps : n, (iterAugment φ n).hasAugmentingPath := by intro n by_contra h exact hnot n, h rcases source_cut_integral hc with K, hK have hge : ((K + 1 : ) : ) (iterAugment φ (K + 1)).value := iterAugment_value_ge φ hint hc hsteps (K + 1) have hle : (iterAugment φ (K + 1)).value (K : ) := by rw [ hK] exact value_le_source_cut (iterAugment φ (K + 1)) have hle' : ((K + 1 : ) : ) (K : ) := le_trans hge hle norm_num at hle'

The matching network has integral capacities (in fact 0 or 1).

lemma toFlowNetwork_integral_capacity {V : Type*} [Fintype V] [DecidableEq V] {G : BipartiteGraph V} : u v, n : , (toFlowNetwork V G).c u v = (n : ) := by intro u v cases u with | inl a => cases v with | inl b => change n : , (if (a, b) G.E then (1 : ) else 0) = (n : ) by_cases h : (a, b) G.E · exact 1, by simp [h] · exact 0, by simp [h] | inr b => cases b with | true => change n : , (0 : ) = (n : ) exact 0, by norm_num | false => change n : , (if a G.R then (1 : ) else 0) = (n : ) by_cases h : a G.R · exact 1, by simp [h] · exact 0, by simp [h] | inr a => cases a with | true => cases v with | inl b => change n : , (if b G.L then (1 : ) else 0) = (n : ) by_cases h : b G.L · exact 1, by simp [h] · exact 0, by simp [h] | inr b => cases b <;> (change n : , (0 : ) = (n : ); exact 0, by norm_num) | false => cases v with | inl b => change n : , (0 : ) = (n : ) exact 0, by norm_num | inr b => cases b <;> (change n : , (0 : ) = (n : ); exact 0, by norm_num)

Theorem 24.12 (CLRS). In the unit-capacity network of a bipartite graph, the maximum matching size equals the maximum flow value: there is a matching at least as large as every matching, and a maximal flow whose value is exactly its size.

theorem maxMatching_eq_maxFlow_value {V : Type*} [Fintype V] [DecidableEq V] {G : BipartiteGraph V} : M : Matching V G, ( M' : Matching V G, M'.size M.size) φ : Flow (V Bool) (toFlowNetwork V G), φ.isMaximal φ.value = (M.size : ) := by let N : FlowNetwork (V Bool) := toFlowNetwork V G let zf : Flow (V Bool) N := zeroFlow N have hc : u v, n : , N.c u v = (n : ) := by intro u v exact toFlowNetwork_integral_capacity u v have hz : 0 zf.value := by unfold Flow.value simp [zf, zeroFlow] rcases exists_noAugmentingPath_iter zf (IsIntegral_zero (G := N)) hc hz with n, hn let φ : Flow (V Bool) N := iterAugment zf n have hintφ : φ.IsIntegral := IsIntegral_iterAugment zf (IsIntegral_zero (G := N)) hc n have hmax : φ.isMaximal := Flow.maximal_of_noAugmentingPath φ hn let M : Matching V G := matchingOfIntegralFlow φ hintφ refine M, ?_, φ, hmax, ?_ · intro M' have hvalφ : φ.value = (M.size : ) := (matchingOfIntegralFlow_size φ hintφ).symm have hvalM : (matchingToFlow M').value = (M'.size : ) := matchingToFlow_value M' have hle : (matchingToFlow M').value φ.value := hmax (matchingToFlow M') have hsize : (M'.size : ) (M.size : ) := by linarith exact_mod_cast hsize · exact (matchingOfIntegralFlow_size φ hintφ).symm
end Chapter26end CLRS