-
Notifications
You must be signed in to change notification settings - Fork 1k
feat(SimpleGraph): add max-flow/min-cut weak duality #34028
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
floor-licker
wants to merge
12
commits into
leanprover-community:master
Choose a base branch
from
floor-licker:mfmc-wip
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+169
−0
Open
Changes from 1 commit
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
995bcf7
Add Max-Flow/Min-Cut weak duality
floor-licker 9931533
Update Mathlib/Combinatorics/SimpleGraph/Connectivity/MaxFlowMinCut.lean
floor-licker ea051b7
fix(SimpleGraph/Connectivity): make Flow supported on edges
floor-licker 5aec78a
refactor(SimpleGraph/Connectivity): use ℤ capacities for Flow
floor-licker 4963b51
refactor: generalize to α
floor-licker baac82d
lint: lake exe mk_all
floor-licker e854568
fix: replace obsolete LinearOrderedAddCommGroup assumption
floor-licker be482c0
fix(SimpleGraph/Connectivity): update MaxFlowMinCut for new big-opera…
floor-licker 3ef6d54
fix: docs + API cleanup for SimpleGraph max-flow/min-cut weak duality
floor-licker 83d9e1e
Merge branch 'master' into mfmc-wip
floor-licker ee9a2e7
Merge branch 'master' into mfmc-wip
floor-licker 809fefc
Merge branch 'master' into mfmc-wip
floor-licker File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
133 changes: 133 additions & 0 deletions
133
Mathlib/Combinatorics/SimpleGraph/Connectivity/MaxFlowMinCut.lean
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| /- | ||
| Copyright (c) 2026. All rights reserved. | ||
| Released under Apache 2.0 license as described in the file LICENSE. | ||
| Authors: Julius Tranquilli | ||
| -/ | ||
| module | ||
|
|
||
| public import Mathlib.Combinatorics.SimpleGraph.Basic | ||
| public import Mathlib.Data.Int.Order.Lemmas | ||
| public import Mathlib.Algebra.BigOperators.Group.Finset.Basic | ||
|
|
||
| /-! | ||
| # Max-Flow / Min-Cut (basic definitions, weak duality) | ||
|
|
||
| This file sets up a convenient notion of an `s`-`t` flow on an undirected simple graph, modelled as | ||
| a skew-symmetric function `V → V → ℤ` bounded by a (symmetric) capacity on unordered pairs. | ||
|
|
||
| At the moment, we only prove the "easy" inequality: the value of any flow is bounded above by the | ||
| capacity of any `s`-`t` cut. | ||
|
|
||
| The full max-flow/min-cut theorem (existence of a cut achieving equality for a maximal flow) is not | ||
| yet in Mathlib. | ||
| -/ | ||
|
|
||
| @[expose] public section | ||
|
|
||
| namespace SimpleGraph | ||
|
|
||
| open scoped BigOperators | ||
|
|
||
| variable {V : Type*} [Fintype V] [DecidableEq V] | ||
| variable {G : SimpleGraph V} {c : Sym2 V → ℕ} {s t : V} | ||
|
|
||
| /-- An `s`-`t` flow on an undirected graph, expressed as a skew-symmetric function `V → V → ℤ` | ||
| whose absolute value is bounded by a capacity on unordered pairs, together with the usual flow | ||
| conservation law away from `s` and `t`. -/ | ||
| structure Flow (G : SimpleGraph V) (c : Sym2 V → ℕ) (s t : V) where | ||
| val : V → V → ℤ | ||
| skew : ∀ u v, val u v = -val v u | ||
| capacity : ∀ u v, |val u v| ≤ (c s(u, v) : ℤ) | ||
floor-licker marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| conserve : ∀ v, v ≠ s → v ≠ t → (∑ u, val v u) = 0 | ||
floor-licker marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| namespace Flow | ||
|
|
||
| variable (f : Flow G c s t) | ||
|
|
||
| /-- The net outflow at a vertex. -/ | ||
| def netOut (v : V) : ℤ := ∑ u, f.val v u | ||
|
|
||
| /-- The value of an `s`-`t` flow (net outflow at `s`). -/ | ||
| def value : ℤ := f.netOut s | ||
|
|
||
| /-- For a set `S` of vertices, the total flow leaving `S`. -/ | ||
| def cutValue (S : Finset V) : ℤ := ∑ u in S, ∑ v in Sᶜ, f.val u v | ||
|
|
||
| /-- For a set `S` of vertices, the total capacity leaving `S`. -/ | ||
| def cutCapacity (S : Finset V) : ℤ := ∑ u in S, ∑ v in Sᶜ, (c s(u, v) : ℤ) | ||
|
|
||
| lemma value_def : f.value = ∑ v, f.val s v := rfl | ||
|
|
||
| lemma netOut_def (v : V) : f.netOut v = ∑ u, f.val v u := rfl | ||
|
|
||
| private lemma sum_sum_skew_eq_zero (S : Finset V) : | ||
| (∑ u in S, ∑ v in S, f.val u v) = 0 := by | ||
| classical | ||
| set A : ℤ := ∑ u in S, ∑ v in S, f.val u v | ||
| have hA : A = -A := by | ||
| -- Swap the two sums and use skew-symmetry. | ||
| simp [A, Finset.sum_comm, f.skew, Finset.sum_neg_distrib] | ||
| have h2A : (2 : ℤ) * A = 0 := by | ||
| -- From `A = -A`, conclude `A + A = 0`, hence `2*A = 0`. | ||
| have : A + A = 0 := by | ||
| calc | ||
| A + A = A + (-A) := by simpa [hA] | ||
| _ = 0 := by simp | ||
| simpa [two_mul] using this | ||
| -- `2 ≠ 0` in `ℤ`, so `A = 0`. | ||
| have : A = 0 := (mul_eq_zero.mp h2A).resolve_left (by decide) | ||
| simpa [A] using this | ||
|
|
||
| lemma value_eq_cutValue (S : Finset V) (hs : s ∈ S) (ht : t ∉ S) : | ||
| f.value = f.cutValue S := by | ||
| classical | ||
| have h_other : (∑ v in S.erase s, f.netOut v) = 0 := by | ||
| refine Finset.sum_eq_zero ?_ | ||
| intro v hv | ||
| have hv' : v ≠ s ∧ v ∈ S := Finset.mem_erase.mp hv | ||
| have hv_ne_t : v ≠ t := by | ||
| intro hvt | ||
| exact ht (hvt ▸ hv'.2) | ||
| simpa [Flow.netOut, netOut] using f.conserve v hv'.1 hv_ne_t | ||
| have hsum : f.netOut s = ∑ v in S, f.netOut v := by | ||
| have h := (Finset.sum_erase_add (s := S) (a := s) (f := fun v => f.netOut v) hs) | ||
| -- `h : (∑ v in S.erase s, netOut v) + netOut s = ∑ v in S, netOut v`. | ||
| -- The first summand is `0` by conservation away from `s` and `t`. | ||
| simpa [h_other, Flow.netOut, netOut, f.netOut_def] using h | ||
| have hsplit : | ||
| (∑ v in S, f.netOut v) = (∑ u in S, ∑ v in S, f.val u v) + (∑ u in S, ∑ v in Sᶜ, f.val u v) := by | ||
| -- Split the inner sum over `univ` as `S + Sᶜ`. | ||
| simpa [Flow.netOut, netOut, Finset.sum_add_sum_compl, Finset.sum_add_distrib, Finset.sum_sum, | ||
| add_comm, add_left_comm, add_assoc] using congrArg (fun x => (∑ u in S, x u)) | ||
| (funext fun u => (Finset.sum_add_sum_compl (s := S) (f := fun v => f.val u v)).symm) | ||
| -- Internal edges cancel by skew-symmetry. | ||
| calc | ||
| f.value = f.netOut s := rfl | ||
| _ = ∑ v in S, f.netOut v := hsum | ||
| _ = (∑ u in S, ∑ v in S, f.val u v) + (∑ u in S, ∑ v in Sᶜ, f.val u v) := hsplit | ||
| _ = 0 + f.cutValue S := by | ||
| simp [Flow.cutValue, cutValue, f.sum_sum_skew_eq_zero S] | ||
| _ = f.cutValue S := by simp | ||
|
|
||
| lemma cutValue_le_cutCapacity (S : Finset V) : f.cutValue S ≤ f.cutCapacity S := by | ||
| classical | ||
| -- Pointwise bound: `f.val u v ≤ c s(u,v)` since `f.val u v ≤ |f.val u v| ≤ c s(u,v)`. | ||
| have hle : ∀ u ∈ S, ∀ v ∈ Sᶜ, f.val u v ≤ (c s(u, v) : ℤ) := by | ||
| intro u hu v hv | ||
| exact (le_abs_self (f.val u v)).trans (f.capacity u v) | ||
| -- Sum the pointwise bounds. | ||
| unfold Flow.cutValue Flow.cutCapacity cutValue cutCapacity | ||
| refine Finset.sum_le_sum ?_ | ||
| intro u hu | ||
| refine Finset.sum_le_sum ?_ | ||
| intro v hv | ||
| exact hle u hu v hv | ||
|
|
||
| /-- Weak duality: the value of any flow is bounded by the capacity of any `s`-`t` cut. -/ | ||
| theorem value_le_cutCapacity (S : Finset V) (hs : s ∈ S) (ht : t ∉ S) : | ||
| f.value ≤ f.cutCapacity S := by | ||
| simpa [f.value_eq_cutValue S hs ht] using f.cutValue_le_cutCapacity (S := S) | ||
|
|
||
| end Flow | ||
|
|
||
| end SimpleGraph | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To allow non-integral flows we should define flows on more general types.
Perhaps
{α : Type*} [AddGroup α] [LE α]for the definition, and most lemmas would require[LinearOrder α]? I'm not sure what's the best choice here.The capacities can have the same type as the values, and
capacitywill enforce they are non-negative.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed that an α-valued Flow might be nicer API, but for now I would like to keep ℤ/ℕ because the next step is strong MFMC (and Menger), where integrality/termination arguments are way simpler. Happy to follow up with a separate PR generalizing the definition + weak duality once the core theorem lands. I think at this point generalizing would unnecessarily increase the overhead of mechanizing a lot of the core results in graph theory, but let me know what you think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Umm that depends. Do you prefer splitting because it'll be easier to review, or because you did not prove the general case and prefer to leave generalizing as a TODO? Both are good reasons to not generalize now, but "integrality/termination arguments are way simpler" confuses me.
If it's neither of these (e.g. you already proved the general case but want to PR the special case first and then replace it entirely) then I don't see a reason to do this.
Either way I think having both
ℤ/ℕcomplicates things (adds casts and makes this not a special case of the general case), wdyt about using integer capacities? Non-negativity is already enforced.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah sorry, my phrasing was sloppy. The reason I’m not generalizing to α in this PR is that I haven’t proved the α-valued development yet so it would basically be leaving a design-heavy TODO, and my next planned step is strong MFMC via an augmenting-path/Ford–Fulkerson style proof, where working integrally makes the termination/integrality story much cleaner, and also lines up with the Menger corollary later. Agreed about the ℤ/ℕ mix: I’ve updated the code to use integer capacities (c : Sym2 V -> ℤ). Thanks for the suggestion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the problem is strong MFMC, can you generalize the
Flowstuff here, and just in your proof of strong MFMC take in aFlow ℤ?btw wouldn't a proof using LP duality work? I imagine it's much cleaner since we don't need any algorithmic proof + termination, and it should work for the general case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, I’ve generalized the definitions/weak duality so Flow takes values in α and capacities are c : Sym2 V -> α (so no casts and this covers non-integral flows). You're right that can prove strong MFMC by specializing to α := ℤ. Re LP duality, I just saw that
Mathlib/Analysis/Convex/Cone/Basic.leandiscusses Farkas/conic duality and explicitly lists LP duality as a TODO, i.e, it seems like the main underlying infrastructure would be lacking there. But if you think that avenue would lead to overall cleaner code I'm happy to do it that way.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
Yeah that's unfortunate. I think that if you have a good proof that fits Mathlib you shouldn't have to wait for LPs, when they come we can rewrite the proof for the general (non-integral) case.
I think proofs based on algorithms are usually awkward, and LPs can lead to a slick proof which Mathlib prefers, but maybe the algorithmic approach here isn't awkward, idk.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do agree. I believe what's here now should be compatible with the LP approach you described. Let me know if the current PR is up to par.