Chapter 29 — Linear Programming
CLRS, fourth edition · Lean 4 formalization
The proofs below use the models and assumptions described in the scope and implementation notes.
Imports
import CLRSLean.FourthEdition.Chapter_29.Section_29_1_Standard_And_Slack_Forms.Definitions
import CLRSLean.FourthEdition.Chapter_29.Section_29_1_Standard_And_Slack_Forms.SlackVariables
import CLRSLean.FourthEdition.Chapter_29.Section_29_1_Standard_And_Slack_Forms.Equivalence
import CLRSLean.FourthEdition.Chapter_29.Section_29_1_Standard_And_Slack_Forms.Normalization29.1. Standard and Slack Forms
The represented foundation defines standard-form maximization programs over finite real matrices, and converts the general linear-programming form to standard form while preserving feasibility and the objective. Subsequent child modules add slack variables and prove their exact feasibility equivalence.
The canonical Chapter 29 guide additionally imports SolverWrapper,
which connects this normalization to the initialized SIMPLEX development in
the supplementary online material. Its basic/nonbasic dictionary model and
GeneralLP.solve_complete certify infeasible, optimal or unbounded
outcomes. This section is the formulation layer of that larger development;
it does not claim a polynomial SIMPLEX bound or executable exact-real choices.
Implementation details
The split proof layers remain available outside the main sidebar:
namespace CLRSnamespace Chapter29end Chapter29end CLRSDefinitions and proofs
CLRSLean.FourthEdition.Chapter_29.Section_29_1_Standard_And_Slack_Forms.Definitions
29.1 Standard-form linear programs
This module defines the finite real matrix model used by Chapter 29. A
standard-form program maximizes cᵀx subject to Ax ≤ b and
0 ≤ x.
Main declarations:
-
IsNonnegative: pointwise nonnegativity of a finite vector. -
StandardLP: coefficients, bounds, and objective coefficients. -
StandardLP.IsFeasible: primal standard-form feasibility. -
StandardLP.objective: the valuecᵀx.
Downstream layers:
-
Slack-variable equivalence is proved in later Section 29.1 modules.
-
Basic/nonbasic dictionaries and SIMPLEX are developed in Sections 29.3--29.5.
namespace CLRSnamespace Chapter29open MatrixA finite real vector is nonnegative when every coordinate is nonnegative.
def IsNonnegative {n : ℕ} (x : Fin n → ℝ) : Prop :=
∀ j, 0 ≤ x j
A maximization linear program in CLRS standard form:
maximize cᵀx subject to Ax ≤ b and 0 ≤ x.
The constraint coefficient matrix.
The constraint right-hand side.
The objective coefficient vector.
structure StandardLP (m n : ℕ) where A : Matrix (Fin m) (Fin n) ℝ b : Fin m → ℝ c : Fin n → ℝnamespace StandardLPA vector is primal feasible when it is nonnegative and satisfies every row inequality of the standard-form program.
def IsFeasible {m n : ℕ} (P : StandardLP m n) (x : Fin n → ℝ) : Prop :=
IsNonnegative x ∧ ∀ i, (P.A *ᵥ x) i ≤ P.b i
The objective value cᵀx of a standard-form assignment.
def objective {m n : ℕ} (P : StandardLP m n) (x : Fin n → ℝ) : ℝ :=
P.c ⬝ᵥ xend StandardLPend Chapter29end CLRSCLRSLean.FourthEdition.Chapter_29.Section_29_1_Standard_And_Slack_Forms.SlackVariables
29.1 Slack-variable construction
The canonical slack vector for an assignment x is b - Ax.
Primal feasibility makes this vector nonnegative and converts every row
inequality into an equality.
Main results:
-
slack_nonnegative_of_feasible. -
slack_equation. -
slackExtension_of_feasible.
namespace CLRSnamespace Chapter29open Matrixnamespace StandardLP
The canonical slack vector b - Ax.
def slack {m n : ℕ} (P : StandardLP m n) (x : Fin n → ℝ) : Fin m → ℝ :=
fun i => P.b i - (P.A *ᵥ x) i
A nonnegative slack extension satisfies Ax + s = b coordinatewise.
def IsSlackExtension {m n : ℕ} (P : StandardLP m n)
(x : Fin n → ℝ) (s : Fin m → ℝ) : Prop :=
IsNonnegative x ∧ IsNonnegative s ∧
∀ i, (P.A *ᵥ x) i + s i = P.b iA feasible assignment has a nonnegative canonical slack vector.
theorem slack_nonnegative_of_feasible {m n : ℕ} {P : StandardLP m n}
{x : Fin n → ℝ} (hx : P.IsFeasible x) :
IsNonnegative (P.slack x) := by
intro i
exact sub_nonneg.mpr (hx.2 i)
The canonical slack vector satisfies Ax + slack(x) = b.
theorem slack_equation {m n : ℕ} (P : StandardLP m n) (x : Fin n → ℝ) :
∀ i, (P.A *ᵥ x) i + P.slack x i = P.b i := by
intro i
simp [slack]Every primal-feasible assignment extends canonically to a nonnegative equality-form assignment.
theorem slackExtension_of_feasible {m n : ℕ} {P : StandardLP m n}
{x : Fin n → ℝ} (hx : P.IsFeasible x) :
P.IsSlackExtension x (P.slack x) := by
exact ⟨hx.1, slack_nonnegative_of_feasible hx, P.slack_equation x⟩end StandardLPend Chapter29end CLRSCLRSLean.FourthEdition.Chapter_29.Section_29_1_Standard_And_Slack_Forms.Equivalence
29.1 Standard/slack feasibility equivalence
This module proves the exact semantic bridge
Ax ≤ b ↔ ∃ s ≥ 0, Ax + s = b for nonnegative decision variables.
The slack vector is uniquely determined by the decision assignment.
Main results:
-
isFeasible_iff_exists_slackExtension. -
slackExtension_eq_slack. -
existsUnique_slackExtension_iff.
namespace CLRSnamespace Chapter29namespace StandardLPEliminating nonnegative slack variables recovers primal feasibility.
theorem feasible_of_slackExtension {m n : ℕ} {P : StandardLP m n}
{x : Fin n → ℝ} {s : Fin m → ℝ} (hxs : P.IsSlackExtension x s) :
P.IsFeasible x := by
refine ⟨hxs.1, ?_⟩
intro i
have hs : 0 ≤ s i := hxs.2.1 i
have heq := hxs.2.2 i
linarithStandard-form feasibility is equivalent to the existence of a nonnegative slack vector satisfying the equality system.
theorem isFeasible_iff_exists_slackExtension {m n : ℕ}
(P : StandardLP m n) {x : Fin n → ℝ} :
P.IsFeasible x ↔ ∃ s, P.IsSlackExtension x s := by
constructor
· intro hx
exact ⟨P.slack x, slackExtension_of_feasible hx⟩
· rintro ⟨s, hxs⟩
exact feasible_of_slackExtension hxs
Every slack extension equals the canonical vector b - Ax.
theorem slackExtension_eq_slack {m n : ℕ} {P : StandardLP m n}
{x : Fin n → ℝ} {s : Fin m → ℝ} (hxs : P.IsSlackExtension x s) :
s = P.slack x := by
funext i
have heq := hxs.2.2 i
simp only [slack]
linarithA standard-form assignment is feasible exactly when it has a unique nonnegative slack extension.
theorem existsUnique_slackExtension_iff {m n : ℕ}
(P : StandardLP m n) {x : Fin n → ℝ} :
P.IsFeasible x ↔ ∃! s, P.IsSlackExtension x s := by
constructor
· intro hx
refine ⟨P.slack x, slackExtension_of_feasible hx, ?_⟩
intro s hxs
exact slackExtension_eq_slack hxs
· rintro ⟨s, hxs, _⟩
exact feasible_of_slackExtension hxsend StandardLPend Chapter29end CLRSCLRSLean.FourthEdition.Chapter_29.Section_29_1_Standard_And_Slack_Forms.Normalization
29.1 General-form normalization
CLRS §29.1 lists the general form of a linear program (equations (29.16)--
(29.19)): the objective may be maximized or minimized, each constraint may be
=, ≥, or ≤, and each variable is either nonnegative or
free. This module converts any such program into a finite standard-form
StandardLP (maximize a linear objective subject to Ax ≤ b,
x ≥ 0) while preserving feasibility and the objective value up to the
maximization/minimization sign.
Main results:
-
GeneralLP: the general-form program representation. -
GeneralLP.toStandardLP: the normalization. -
GeneralLP.lift/GeneralLP.proj: the variable expansion and its inverse. -
GeneralLP.feasible_iff_lift: feasibility is exactly preserved. -
GeneralLP.objective_lift: the objective is preserved up to sign. -
GeneralLP.solve/GeneralLP.solve_complete: a canonical main-text solver wrapper around the initialized SIMPLEX.
Each original variable x j is expanded to two nonnegative variables
x j⁺ and x j⁻ with x j = x j⁺ - x j⁻; for a nonnegative
variable an extra constraint forces x j⁻ = 0.
namespace CLRSnamespace Chapter29open Matrixopen scoped BigOperatorsThe relation of a general-form constraint row.
inductive ConstraintRel where
| le | eq | ge
deriving DecidableEq, Repr
A general-form linear program: optimize cᵀx over constraint rows of
mixed relation and nonnegative or free variables (CLRS (29.16)--(29.19)).
Number of variables.
Number of constraints.
true when the objective is maximized, false when minimized.
Objective coefficients.
The relation of each constraint row.
Constraint coefficient matrix.
Constraint right-hand side.
true when variable j is free (unrestricted).
structure GeneralLP where n : ℕ m : ℕ maximize : Bool c : Fin n → ℝ rel : Fin m → ConstraintRel A : Matrix (Fin m) (Fin n) ℝ b : Fin m → ℝ free : Fin n → Boolnamespace GeneralLPvariable (G : GeneralLP)An assignment is feasible when every nonnegative variable is nonnegative and every constraint holds under its relation.
def IsFeasible (x : Fin G.n → ℝ) : Prop :=
(∀ j, ¬ G.free j → 0 ≤ x j) ∧
∀ i, match G.rel i with
| ConstraintRel.le => (G.A *ᵥ x) i ≤ G.b i
| ConstraintRel.eq => (G.A *ᵥ x) i = G.b i
| ConstraintRel.ge => G.b i ≤ (G.A *ᵥ x) i
The objective cᵀx (before the maximization/minimization sign).
The sign +1 for maximization and -1 for minimization, so that
objectiveSign · objective is always the quantity being maximized.
def objectiveSign : ℝ :=
if G.maximize then 1 else -1An assignment is optimal when it is feasible and its signed objective dominates every other feasible assignment.
def IsOptimal (x : Fin G.n → ℝ) : Prop :=
G.IsFeasible x ∧
∀ z, G.IsFeasible z → G.objectiveSign * G.objective z ≤ G.objectiveSign * G.objective xThe signed objective is unbounded above on feasible assignments.
def IsUnbounded : Prop :=
∀ M : ℝ, ∃ x, G.IsFeasible x ∧ M < G.objectiveSign * G.objective x
Positive part of the expanded variable j.
Negative part of the expanded variable j.
Upper form of constraint row i.
Lower form of constraint row i.
The nonnegativity-lock row for variable j.
The normalized constraint matrix: each equality contributes an upper and
lower inequality, each ≥ contributes a lower inequality, and each
nonnegative variable contributes a lock row forcing its negative part to zero.
def normalizedA : Matrix (Fin ((G.m + G.m) + G.n)) (Fin (G.n + G.n)) ℝ :=
fun i =>
Fin.addCases (motive := fun _ => Fin (G.n + G.n) → ℝ)
(fun ij =>
Fin.addCases (motive := fun _ => Fin (G.n + G.n) → ℝ)
(fun iU =>
fun j =>
Fin.addCases (motive := fun _ => ℝ)
(fun jP => if G.rel iU = ConstraintRel.ge then 0 else G.A iU jP)
(fun jN => if G.rel iU = ConstraintRel.ge then 0 else -(G.A iU jN))
j)
(fun iL =>
fun j =>
Fin.addCases (motive := fun _ => ℝ)
(fun jP => if G.rel iL = ConstraintRel.le then 0 else -(G.A iL jP))
(fun jN => if G.rel iL = ConstraintRel.le then 0 else G.A iL jN)
j)
ij)
(fun jL =>
fun j =>
Fin.addCases (motive := fun _ => ℝ)
(fun _ => 0)
(fun jN => if G.free jL then 0 else (if jN = jL then 1 else 0))
j)
i
The normalized right-hand side, mirroring normalizedA.
def normalizedB : Fin ((G.m + G.m) + G.n) → ℝ :=
fun i =>
Fin.addCases (motive := fun _ => ℝ)
(fun ij =>
Fin.addCases (motive := fun _ => ℝ)
(fun iU => if G.rel iU = ConstraintRel.ge then 0 else G.b iU)
(fun iL => if G.rel iL = ConstraintRel.le then 0 else -(G.b iL))
ij)
(fun _ => 0)
i
The normalized objective: sign · c on positive parts and
-sign · c on negative parts.
def normalizedC : Fin (G.n + G.n) → ℝ :=
fun j =>
Fin.addCases (motive := fun _ => ℝ)
(fun jP => G.objectiveSign * G.c jP)
(fun jN => -(G.objectiveSign * G.c jN))
jThe normalized standard-form program.
abbrev toStandardLP : StandardLP ((G.m + G.m) + G.n) (G.n + G.n) :=
{ A := G.normalizedA, b := G.normalizedB, c := G.normalizedC }Expand an assignment to the nonnegative positive/negative parts.
def lift (x : Fin G.n → ℝ) : Fin (G.n + G.n) → ℝ :=
fun j =>
Fin.addCases (motive := fun _ => ℝ)
(fun jP => max (x jP) 0)
(fun jN => max (-(x jN)) 0)
jCollapse an expanded assignment back to the signed difference.
max a 0 - max (-a) 0 = a.
lemma max_sub_max_neg (a : ℝ) : max a 0 - max (-a) 0 = a := by
rcases le_total 0 a with h | h
· have hneg : -a ≤ 0 := by linarith
simp [max_eq_left h, max_eq_right hneg]
· have hneg : 0 ≤ -a := by linarith
have ha : a ≤ 0 := by linarith
simp [max_eq_right ha, max_eq_left hneg]Index reductions
lemma lift_pos (x : Fin G.n → ℝ) (j : Fin G.n) :
G.lift x (G.posIndex j) = max (x j) 0 := by
simp [lift, posIndex, Fin.addCases, Fin.castAdd, Fin.castLT]lemma lift_neg (x : Fin G.n → ℝ) (j : Fin G.n) :
G.lift x (G.negIndex j) = max (-(x j)) 0 := by
simp [lift, negIndex, Fin.addCases, Fin.natAdd]
lemma normalizedA_upper_pos (iU : Fin G.m) (jP : Fin G.n) :
G.normalizedA (G.upperIndex iU) (G.posIndex jP) =
if G.rel iU = ConstraintRel.ge then 0 else G.A iU jP := by
have hlt : ↑iU < G.m + G.m := by omega
simp [normalizedA, upperIndex, posIndex, Fin.addCases, Fin.castAdd, Fin.castLT, hlt]
lemma normalizedA_upper_neg (iU : Fin G.m) (jN : Fin G.n) :
G.normalizedA (G.upperIndex iU) (G.negIndex jN) =
if G.rel iU = ConstraintRel.ge then 0 else -(G.A iU jN) := by
have hlt : ↑iU < G.m + G.m := by omega
simp [normalizedA, upperIndex, negIndex, Fin.addCases, Fin.castAdd, Fin.castLT, Fin.natAdd, hlt]lemma normalizedA_lower_pos (iL : Fin G.m) (jP : Fin G.n) :
G.normalizedA (G.lowerIndex iL) (G.posIndex jP) =
if G.rel iL = ConstraintRel.le then 0 else -(G.A iL jP) := by
simp [normalizedA, lowerIndex, posIndex, Fin.addCases, Fin.castAdd, Fin.castLT, Fin.natAdd]lemma normalizedA_lower_neg (iL : Fin G.m) (jN : Fin G.n) :
G.normalizedA (G.lowerIndex iL) (G.negIndex jN) =
if G.rel iL = ConstraintRel.le then 0 else G.A iL jN := by
simp [normalizedA, lowerIndex, negIndex, Fin.addCases, Fin.castAdd, Fin.castLT, Fin.natAdd]
lemma normalizedA_lock_pos (jL jP : Fin G.n) :
G.normalizedA (G.lockIndex jL) (G.posIndex jP) = 0 := by
simp [normalizedA, lockIndex, posIndex, Fin.addCases, Fin.castAdd, Fin.castLT, Fin.natAdd]
lemma normalizedA_lock_neg (jL jN : Fin G.n) :
G.normalizedA (G.lockIndex jL) (G.negIndex jN) =
if G.free jL then 0 else (if jN = jL then 1 else 0) := by
simp [normalizedA, lockIndex, negIndex, Fin.addCases, Fin.castAdd, Fin.castLT, Fin.natAdd]
lemma normalizedB_upper (iU : Fin G.m) :
G.normalizedB (G.upperIndex iU) = if G.rel iU = ConstraintRel.ge then 0 else G.b iU := by
have hlt : ↑iU < G.m + G.m := by omega
simp [normalizedB, upperIndex, Fin.addCases, Fin.castAdd, Fin.castLT, hlt]lemma normalizedB_lower (iL : Fin G.m) :
G.normalizedB (G.lowerIndex iL) = if G.rel iL = ConstraintRel.le then 0 else -(G.b iL) := by
simp [normalizedB, lowerIndex, Fin.addCases, Fin.castAdd, Fin.castLT, Fin.natAdd]lemma normalizedB_lock (jL : Fin G.n) : G.normalizedB (G.lockIndex jL) = 0 := by
simp [normalizedB, lockIndex, Fin.addCases, Fin.natAdd]lemma normalizedC_pos (jP : Fin G.n) :
G.normalizedC (G.posIndex jP) = G.objectiveSign * G.c jP := by
simp [normalizedC, posIndex, Fin.addCases, Fin.castAdd, Fin.castLT]lemma normalizedC_neg (jN : Fin G.n) :
G.normalizedC (G.negIndex jN) = -(G.objectiveSign * G.c jN) := by
simp [normalizedC, negIndex, Fin.addCases, Fin.natAdd]Row-vector identities
lemma normalizedA_mulVec_upper (x' : Fin (G.n + G.n) → ℝ) (iU : Fin G.m)
(hge : G.rel iU ≠ ConstraintRel.ge) :
(G.normalizedA *ᵥ x') (G.upperIndex iU) =
∑ j : Fin G.n, G.A iU j * (x' (G.posIndex j) - x' (G.negIndex j)) := by
simp only [Matrix.mulVec, dotProduct]
rw [Fin.sum_univ_add]
have h1 : (∑ j : Fin G.n, G.normalizedA (G.upperIndex iU) (G.posIndex j) * x' (G.posIndex j)) =
∑ j : Fin G.n, G.A iU j * x' (G.posIndex j) := by
apply Finset.sum_congr rfl
intro j hj
rw [normalizedA_upper_pos, if_neg hge]
have h2 : (∑ j : Fin G.n, G.normalizedA (G.upperIndex iU) (G.negIndex j) * x' (G.negIndex j)) =
∑ j : Fin G.n, (-(G.A iU j)) * x' (G.negIndex j) := by
apply Finset.sum_congr rfl
intro j hj
rw [normalizedA_upper_neg, if_neg hge]
rw [h1, h2]
rw [← Finset.sum_add_distrib]
apply Finset.sum_congr rfl
intro j hj
ring
lemma normalizedA_mulVec_upper_ge (x' : Fin (G.n + G.n) → ℝ) (iU : Fin G.m)
(hge : G.rel iU = ConstraintRel.ge) :
(G.normalizedA *ᵥ x') (G.upperIndex iU) = 0 := by
simp only [Matrix.mulVec, dotProduct]
rw [Fin.sum_univ_add]
have h1 : (∑ j : Fin G.n, G.normalizedA (G.upperIndex iU) (G.posIndex j) * x' (G.posIndex j)) = 0 := by
apply Finset.sum_eq_zero
intro j hj
rw [normalizedA_upper_pos, if_pos hge, zero_mul]
have h2 : (∑ j : Fin G.n, G.normalizedA (G.upperIndex iU) (G.negIndex j) * x' (G.negIndex j)) = 0 := by
apply Finset.sum_eq_zero
intro j hj
rw [normalizedA_upper_neg, if_pos hge, zero_mul]
rw [h1, h2]
simp
lemma normalizedA_mulVec_lower (x' : Fin (G.n + G.n) → ℝ) (iL : Fin G.m)
(hle : G.rel iL ≠ ConstraintRel.le) :
(G.normalizedA *ᵥ x') (G.lowerIndex iL) =
-(∑ j : Fin G.n, G.A iL j * (x' (G.posIndex j) - x' (G.negIndex j))) := by
simp only [Matrix.mulVec, dotProduct]
rw [Fin.sum_univ_add]
have h1 : (∑ j : Fin G.n, G.normalizedA (G.lowerIndex iL) (G.posIndex j) * x' (G.posIndex j)) =
∑ j : Fin G.n, -(G.A iL j * x' (G.posIndex j)) := by
apply Finset.sum_congr rfl
intro j hj
rw [normalizedA_lower_pos, if_neg hle, neg_mul]
have h2 : (∑ j : Fin G.n, G.normalizedA (G.lowerIndex iL) (G.negIndex j) * x' (G.negIndex j)) =
∑ j : Fin G.n, G.A iL j * x' (G.negIndex j) := by
apply Finset.sum_congr rfl
intro j hj
rw [normalizedA_lower_neg, if_neg hle]
rw [h1, h2]
rw [Finset.sum_neg_distrib]
simp only [mul_sub]
rw [Finset.sum_sub_distrib]
ring
lemma normalizedA_mulVec_lower_le (x' : Fin (G.n + G.n) → ℝ) (iL : Fin G.m)
(hle : G.rel iL = ConstraintRel.le) :
(G.normalizedA *ᵥ x') (G.lowerIndex iL) = 0 := by
simp only [Matrix.mulVec, dotProduct]
rw [Fin.sum_univ_add]
have h1 : (∑ j : Fin G.n, G.normalizedA (G.lowerIndex iL) (G.posIndex j) * x' (G.posIndex j)) = 0 := by
apply Finset.sum_eq_zero
intro j hj
rw [normalizedA_lower_pos, if_pos hle, zero_mul]
have h2 : (∑ j : Fin G.n, G.normalizedA (G.lowerIndex iL) (G.negIndex j) * x' (G.negIndex j)) = 0 := by
apply Finset.sum_eq_zero
intro j hj
rw [normalizedA_lower_neg, if_pos hle, zero_mul]
rw [h1, h2]
simp
lemma normalizedA_mulVec_lock (x' : Fin (G.n + G.n) → ℝ) (jL : Fin G.n) :
(G.normalizedA *ᵥ x') (G.lockIndex jL) = if G.free jL then 0 else x' (G.negIndex jL) := by
simp only [Matrix.mulVec, dotProduct]
rw [Fin.sum_univ_add]
have h1 : (∑ j : Fin G.n, G.normalizedA (G.lockIndex jL) (G.posIndex j) * x' (G.posIndex j)) = 0 := by
apply Finset.sum_eq_zero
intro j hj
rw [normalizedA_lock_pos, zero_mul]
rw [h1]
simp only [zero_add]
by_cases hfree : G.free jL
· rw [if_pos hfree]
apply Finset.sum_eq_zero
intro j hj
rw [normalizedA_lock_neg, if_pos hfree, zero_mul]
· rw [if_neg hfree]
have hsum : (∑ j : Fin G.n, (if j = jL then 1 else 0) * x' (G.negIndex j)) = x' (G.negIndex jL) := by
rw [Finset.sum_eq_single jL]
· simp
· intro j hj hne
simp [hne]
· intro h
exact False.elim (h (Finset.mem_univ jL))
calc
(∑ j : Fin G.n, G.normalizedA (G.lockIndex jL) (G.negIndex j) * x' (G.negIndex j))
= ∑ j : Fin G.n, (if G.free jL then 0 else (if j = jL then 1 else 0)) * x' (G.negIndex j) := by
apply Finset.sum_congr rfl
intro j hj
rw [normalizedA_lock_neg]
_ = ∑ j : Fin G.n, (if j = jL then 1 else 0) * x' (G.negIndex j) := by
simp [hfree]
_ = x' (G.negIndex jL) := hsumFeasibility
An expanded assignment is coordinatewise nonnegative.
theorem lift_nonnegative (x : Fin G.n → ℝ) : IsNonnegative (G.lift x) := by
intro j
exact Fin.addCases (motive := fun j => 0 ≤ G.lift x j)
(fun jP => by rw [G.lift_pos]; exact le_max_right _ _)
(fun jN => by rw [G.lift_neg]; exact le_max_right _ _)
jThe upper form of a constraint holds for the expanded assignment.
lemma feasible_constraint_upper {x : Fin G.n → ℝ} (hx : G.IsFeasible x) (iU : Fin G.m) :
(G.normalizedA *ᵥ G.lift x) (G.upperIndex iU) ≤ G.normalizedB (G.upperIndex iU) := by
by_cases hge : G.rel iU = ConstraintRel.ge
· simp [G.normalizedA_mulVec_upper_ge (G.lift x) iU hge, G.normalizedB_upper, hge]
· rw [G.normalizedA_mulVec_upper (G.lift x) iU hge]
rw [G.normalizedB_upper]
simp only [hge, if_false]
have hsum : (∑ j : Fin G.n, G.A iU j * (G.lift x (G.posIndex j) - G.lift x (G.negIndex j))) =
(G.A *ᵥ x) iU := by
simp only [Matrix.mulVec, dotProduct]
apply Finset.sum_congr rfl
intro j hj
rw [G.lift_pos, G.lift_neg, max_sub_max_neg]
rw [hsum]
cases hrel : G.rel iU with
| le => exact by simpa [hrel] using hx.2 iU
| eq => exact le_of_eq (by simpa [hrel] using hx.2 iU)
| ge => exact False.elim (hge hrel)The lower form of a constraint holds for the expanded assignment.
lemma feasible_constraint_lower {x : Fin G.n → ℝ} (hx : G.IsFeasible x) (iL : Fin G.m) :
(G.normalizedA *ᵥ G.lift x) (G.lowerIndex iL) ≤ G.normalizedB (G.lowerIndex iL) := by
by_cases hle : G.rel iL = ConstraintRel.le
· simp [G.normalizedA_mulVec_lower_le (G.lift x) iL hle, G.normalizedB_lower, hle]
· rw [G.normalizedA_mulVec_lower (G.lift x) iL hle]
rw [G.normalizedB_lower]
simp only [hle, if_false]
have hsum : (∑ j : Fin G.n, G.A iL j * (G.lift x (G.posIndex j) - G.lift x (G.negIndex j))) =
(G.A *ᵥ x) iL := by
simp only [Matrix.mulVec, dotProduct]
apply Finset.sum_congr rfl
intro j hj
rw [G.lift_pos, G.lift_neg, max_sub_max_neg]
rw [hsum]
cases hrel : G.rel iL with
| le => exact False.elim (hle hrel)
| eq => exact le_of_eq (by simpa [hrel] using hx.2 iL)
| ge => exact neg_le_neg (by simpa [hrel] using hx.2 iL)The nonnegativity-lock row holds for the expanded assignment.
lemma feasible_constraint_lock {x : Fin G.n → ℝ} (hx : G.IsFeasible x) (jL : Fin G.n) :
(G.normalizedA *ᵥ G.lift x) (G.lockIndex jL) ≤ G.normalizedB (G.lockIndex jL) := by
rw [G.normalizedA_mulVec_lock, G.normalizedB_lock]
by_cases hfree : G.free jL
· simp [hfree]
· rw [if_neg hfree]
have hnn : 0 ≤ x jL := hx.1 jL hfree
rw [G.lift_neg]
have hneg : -(x jL) ≤ 0 := by linarith
rw [max_eq_right hneg]A feasible assignment expands to a feasible standard-form assignment.
theorem normalized_feasible_of_feasible {x : Fin G.n → ℝ} (hx : G.IsFeasible x) :
(G.toStandardLP).IsFeasible (G.lift x) := by
refine ⟨G.lift_nonnegative x, ?_⟩
intro i
exact Fin.addCases (motive := fun i => (G.normalizedA *ᵥ G.lift x) i ≤ G.normalizedB i)
(fun ij =>
Fin.addCases (motive := fun ij => (G.normalizedA *ᵥ G.lift x) (Fin.castAdd G.n ij) ≤ G.normalizedB (Fin.castAdd G.n ij))
(fun iU => G.feasible_constraint_upper hx iU)
(fun iL => G.feasible_constraint_lower hx iL)
ij)
(fun jL => G.feasible_constraint_lock hx jL)
iEvery standard-form-feasible expanded assignment collapses to a feasible general-form assignment.
theorem feasible_of_normalized_feasible {x' : Fin (G.n + G.n) → ℝ}
(hx' : (G.toStandardLP).IsFeasible x') : G.IsFeasible (G.proj x') := by
refine ⟨?_, ?_⟩
· intro j hjfree
have hlock : (G.normalizedA *ᵥ x') (G.lockIndex j) ≤ G.normalizedB (G.lockIndex j) :=
hx'.2 (G.lockIndex j)
rw [G.normalizedA_mulVec_lock, G.normalizedB_lock] at hlock
by_cases hfree : G.free j
· exfalso
exact hjfree hfree
· rw [if_neg hfree] at hlock
have hneg_nonneg := hx'.1 (G.negIndex j)
have hneg_zero : x' (G.negIndex j) = 0 := le_antisymm hlock hneg_nonneg
unfold proj
rw [hneg_zero]
simpa using hx'.1 (G.posIndex j)
· intro i
cases hrel : G.rel i with
| le =>
have hne : G.rel i ≠ ConstraintRel.ge := by
intro hc
rw [hrel] at hc
cases hc
have hupper : (G.normalizedA *ᵥ x') (G.upperIndex i) ≤ G.normalizedB (G.upperIndex i) :=
hx'.2 (G.upperIndex i)
rw [G.normalizedA_mulVec_upper x' i hne, G.normalizedB_upper] at hupper
simp only [hrel, if_false] at hupper
simpa [Matrix.mulVec, dotProduct, proj] using hupper
| eq =>
have hne_ge : G.rel i ≠ ConstraintRel.ge := by
intro hc
rw [hrel] at hc
cases hc
have hne_le : G.rel i ≠ ConstraintRel.le := by
intro hc
rw [hrel] at hc
cases hc
have hupper : (G.normalizedA *ᵥ x') (G.upperIndex i) ≤ G.normalizedB (G.upperIndex i) :=
hx'.2 (G.upperIndex i)
have hlower : (G.normalizedA *ᵥ x') (G.lowerIndex i) ≤ G.normalizedB (G.lowerIndex i) :=
hx'.2 (G.lowerIndex i)
rw [G.normalizedA_mulVec_upper x' i hne_ge, G.normalizedB_upper] at hupper
rw [G.normalizedA_mulVec_lower x' i hne_le, G.normalizedB_lower] at hlower
simp only [hrel, if_false] at hupper hlower
have hle : (G.A *ᵥ G.proj x') i ≤ G.b i := by
simpa [Matrix.mulVec, dotProduct, proj] using hupper
have hge : G.b i ≤ (G.A *ᵥ G.proj x') i := by
have h := neg_le_neg_iff.mp hlower
simpa [Matrix.mulVec, dotProduct, proj] using h
exact le_antisymm hle hge
| ge =>
have hne : G.rel i ≠ ConstraintRel.le := by
intro hc
rw [hrel] at hc
cases hc
have hlower : (G.normalizedA *ᵥ x') (G.lowerIndex i) ≤ G.normalizedB (G.lowerIndex i) :=
hx'.2 (G.lowerIndex i)
rw [G.normalizedA_mulVec_lower x' i hne, G.normalizedB_lower] at hlower
simp only [hrel, if_false] at hlower
have h := neg_le_neg_iff.mp hlower
simpa [Matrix.mulVec, dotProduct, proj] using hThe expansion collapses to the original assignment.
theorem proj_lift (x : Fin G.n → ℝ) : G.proj (G.lift x) = x := by
funext j
unfold proj
rw [G.lift_pos, G.lift_neg]
exact max_sub_max_neg (x j)Feasibility is exactly preserved under the expansion.
theorem feasible_iff_lift (x : Fin G.n → ℝ) :
G.IsFeasible x ↔ (G.toStandardLP).IsFeasible (G.lift x) := by
constructor
· exact G.normalized_feasible_of_feasible
· intro h
simpa [G.proj_lift x] using G.feasible_of_normalized_feasible hExistence of a feasible assignment is preserved under the normalization.
theorem feasible_iff_exists :
(∃ x : Fin G.n → ℝ, G.IsFeasible x) ↔
(∃ x' : Fin (G.n + G.n) → ℝ, (G.toStandardLP).IsFeasible x') := by
constructor
· rintro ⟨x, hx⟩
exact ⟨G.lift x, G.normalized_feasible_of_feasible hx⟩
· rintro ⟨x', hx'⟩
exact ⟨G.proj x', G.feasible_of_normalized_feasible hx'⟩Objective preservation
The normalized objective of an arbitrary expanded assignment is the signed objective of its collapse.
theorem objective_eq_sign_objective_proj (x' : Fin (G.n + G.n) → ℝ) :
(G.toStandardLP).objective x' = G.objectiveSign * G.objective (G.proj x') := by
simp only [StandardLP.objective, objective, proj, dotProduct]
rw [Fin.sum_univ_add]
simp only [normalizedC_pos, normalizedC_neg]
calc
(∑ jP : Fin G.n, (G.objectiveSign * G.c jP) * x' (G.posIndex jP))
+ ∑ jN : Fin G.n, (-(G.objectiveSign * G.c jN)) * x' (G.negIndex jN)
= (∑ jP : Fin G.n, G.objectiveSign * (G.c jP * x' (G.posIndex jP)))
+ ∑ jN : Fin G.n, (-(G.objectiveSign)) * (G.c jN * x' (G.negIndex jN)) := by
congr 1
· apply Finset.sum_congr rfl
intro j hj
ring
· apply Finset.sum_congr rfl
intro j hj
ring
_ = G.objectiveSign * (∑ jP : Fin G.n, G.c jP * x' (G.posIndex jP))
+ (-(G.objectiveSign)) * (∑ jN : Fin G.n, G.c jN * x' (G.negIndex jN)) := by
rw [← Finset.mul_sum, ← Finset.mul_sum]
_ = G.objectiveSign * (∑ j : Fin G.n, G.c j * (x' (G.posIndex j) - x' (G.negIndex j))) := by
simp only [mul_sub]
rw [Finset.sum_sub_distrib]
ringThe normalized objective of an expanded assignment is the signed general objective.
theorem objective_lift (x : Fin G.n → ℝ) :
(G.toStandardLP).objective (G.lift x) = G.objectiveSign * G.objective x := by
rw [G.objective_eq_sign_objective_proj]
congr 1
rw [G.proj_lift x]end GeneralLPend Chapter29end CLRSCLRSLean.FourthEdition.Chapter_29.Section_29_1_Standard_And_Slack_Forms.SolverWrapper
29.1 Canonical main-text solver wrapper
This module combines the general-form normalization of the previous module with the initialized SIMPLEX solver (online material) to obtain a canonical main-text solver for general-form programs. The result type exposes exactly the three textbook outcomes.
Main results:
-
GeneralLP.Result: infeasible, optimal, or unbounded. -
GeneralLP.solve: normalize, solve, and translate back. -
GeneralLP.solve_complete: the wrapper always certifies one outcome.
Certified outcomes of the normalized solver, mapped back to the general program.
inductive Result where
| infeasible : (¬ ∃ x : Fin G.n → ℝ, G.IsFeasible x) → Result
| optimal : (x : Fin G.n → ℝ) → G.IsOptimal x → Result
| unbounded : G.IsUnbounded → ResultA canonical main-text solver wrapper: normalize, run the initialized SIMPLEX, and translate the outcome back to the general program.
noncomputable def solve : Result G := by
classical
match G.toStandardLP.initializedSimplex with
| StandardLP.InitializedSimplexResult.infeasible h =>
exact .infeasible (by
intro hx
exact h (G.feasible_iff_exists.mp hx))
| StandardLP.InitializedSimplexResult.optimal x' hx' =>
exact .optimal (G.proj x') (by
refine ⟨G.feasible_of_normalized_feasible hx'.1, ?_⟩
intro z hz
have hz' : (G.toStandardLP).IsFeasible (G.lift z) := G.normalized_feasible_of_feasible hz
have hbound := hx'.2 (G.lift z) hz'
rw [G.objective_lift z, G.objective_eq_sign_objective_proj] at hbound
exact hbound)
| StandardLP.InitializedSimplexResult.unbounded h =>
exact .unbounded (by
intro M
obtain ⟨x', hx', hM⟩ := h M
refine ⟨G.proj x', G.feasible_of_normalized_feasible hx', ?_⟩
rw [G.objective_eq_sign_objective_proj] at hM
exact hM)The wrapper always certifies infeasibility, an optimum, or unboundedness.
theorem solve_complete :
(¬ ∃ x : Fin G.n → ℝ, G.IsFeasible x) ∨
(∃ x : Fin G.n → ℝ, G.IsOptimal x) ∨ G.IsUnbounded := by
cases G.solve with
| infeasible h => exact Or.inl h
| optimal x hx => exact Or.inr (Or.inl ⟨x, hx⟩)
| unbounded h => exact Or.inr (Or.inr h)end GeneralLPend Chapter29end CLRSImports
import CLRSLean.FourthEdition.Chapter_29.Section_29_2_Formulating_Problems_As_Linear_Programs.NetworkFlow
import CLRSLean.FourthEdition.Chapter_29.Section_29_2_Formulating_Problems_As_Linear_Programs.ShortestPath
import CLRSLean.FourthEdition.Chapter_29.Section_29_2_Formulating_Problems_As_Linear_Programs.MaximumFlow
import CLRSLean.FourthEdition.Chapter_29.Section_29_2_Formulating_Problems_As_Linear_Programs.MinimumCostFlow
import CLRSLean.FourthEdition.Chapter_29.Section_29_2_Formulating_Problems_As_Linear_Programs.MulticommodityFlow29.2. Formulating Problems as Linear Programs
This section contains the four textbook formulations: shortest path, maximum flow, minimum-cost flow, and multicommodity flow.
Implementation details
namespace CLRSnamespace Chapter29end Chapter29end CLRSDefinitions and proofs
CLRSLean.FourthEdition.Chapter_29.Section_29_2_Formulating_Problems_As_Linear_Programs.NetworkFlow
29.2: Common finite-network definitions
CLRS writes its flow linear programs with one nonnegative variable f u v
for every ordered pair of vertices. A missing edge is represented by capacity
zero. This file records that shared finite-network vocabulary, together with
the finite-standard-form encoding helpers used by every §29.2 formulation.
namespace CLRSnamespace Chapter29open Finsetopen scoped BigOperatorsA finite directed capacitated network. Capacities are total; assigning capacity zero to nonedges gives exactly the convention used in CLRS §29.2.
structure FlowNetwork (V : Type*) [Fintype V] where
source : V
sink : V
source_ne_sink : source ≠ sink
capacity : V → V → ℝ
capacity_nonnegative : ∀ u v, 0 ≤ capacity u vnamespace FlowNetworkvariable {V : Type*} [Fintype V] (N : FlowNetwork V)Total flow leaving a vertex.
def outflow (f : V → V → ℝ) (u : V) : ℝ := ∑ v, f u vTotal flow entering a vertex.
def inflow (f : V → V → ℝ) (u : V) : ℝ := ∑ v, f v uNet flow leaving a vertex.
Flow conservation at one vertex.
The nonnegativity and capacity inequalities shared by the flow LPs.
def IsCapacityFeasible (f : V → V → ℝ) : Prop :=
(∀ u v, 0 ≤ f u v) ∧ ∀ u v, f u v ≤ N.capacity u vA feasible single-commodity flow: capacity constraints everywhere and conservation away from the source and sink.
def IsFlow (f : V → V → ℝ) : Prop :=
N.IsCapacityFeasible f ∧
∀ u, u ≠ N.source → u ≠ N.sink → ConservesAt f uend FlowNetworkFinite standard-form encoding helpers
These helpers reindex semantic vectors over a finite type into the Fin-indexed
vectors used by StandardLP, and prove the sum identities that the
assignment bridges rely on.
namespace FinEncodingvariable {ι : Type*} [Fintype ι]
Lift a semantic vector over a finite type to the Fin-indexed vector of the
same coordinates.
noncomputable def lift (x : ι → ℝ) : Fin (Fintype.card ι) → ℝ :=
x ∘ (Fintype.equivFin ι).symm
Project a Fin-indexed vector back to a semantic vector.
noncomputable def proj (x : Fin (Fintype.card ι) → ℝ) : ι → ℝ :=
x ∘ (Fintype.equivFin ι)Lifting then projecting recovers the original vector.
A finite sum over Fin (card ι) reindexes to a sum over ι.
theorem sum_reindex {M : Type*} [AddCommMonoid M] (f : Fin (Fintype.card ι) → M) :
(∑ i : Fin (Fintype.card ι), f i) = ∑ a : ι, f (Fintype.equivFin ι a) := by
exact (Equiv.sum_comp (Fintype.equivFin ι) f).symm
The indicator sum that collapses an ι-indicator to its selected coordinate.
theorem sum_indicator [DecidableEq ι] (a : ι) (x : ι → ℝ) :
(∑ j : Fin (Fintype.card ι),
(if (Fintype.equivFin ι).symm j = a then 1 else 0) * lift x j) = x a := by
calc
(∑ j : Fin (Fintype.card ι),
(if (Fintype.equivFin ι).symm j = a then 1 else 0) * lift x j)
= ∑ b : ι, (if b = a then 1 else 0) * x b := by
rw [sum_reindex]
simp [lift]
_ = x a := by
rw [Finset.sum_eq_single a]
· simp
· intro b _ hb; simp [hb]
· intro h; exact False.elim (h (Finset.mem_univ a))
A direct Fin-indicator sum (no reindexing): the coefficient is nonzero
exactly at the selected coordinate.
theorem sum_indicator_fin {n : ℕ} (e : Fin n) (x : Fin n → ℝ) :
(∑ j : Fin n, (if j = e then 1 else 0) * x j) = x e := by
rw [Finset.sum_eq_single e]
· simp
· intro b _ hb; simp [hb]
· intro h; exact False.elim (h (Finset.mem_univ e))
Fin.addCases reduces on a left (castAdd) index.
theorem addCases_castAdd {m n : ℕ} {C : Type u} (left : Fin m → C) (right : Fin n → C) (e : Fin m) :
Fin.addCases left right (Fin.castAdd n e) = left e := by
simp [Fin.addCases]
Fin.addCases reduces on a right (natAdd) index.
theorem addCases_natAdd {m n : ℕ} {C : Type u} (left : Fin m → C) (right : Fin n → C) (v : Fin n) :
Fin.addCases left right (Fin.natAdd m v) = right v := by
simp [Fin.addCases]
Lift a binary vector to the Fin-indexed vector over ordered pairs.
noncomputable def lift₂ {α β : Type*} [Fintype α] [Fintype β] (x : α → β → ℝ) :
Fin (Fintype.card (α × β)) → ℝ :=
lift (Function.uncurry x)
Project a Fin-indexed vector over ordered pairs back to a binary vector.
noncomputable def proj₂ {α β : Type*} [Fintype α] [Fintype β]
(x : Fin (Fintype.card (α × β)) → ℝ) : α → β → ℝ :=
Function.curry (proj x)Sum of an indicator on the first coordinate over all ordered pairs.
theorem sum_indicator_fst {α β : Type*} [Fintype α] [Fintype β] [DecidableEq α]
(a : α) (x : α → β → ℝ) :
(∑ j : Fin (Fintype.card (α × β)),
(if ((Fintype.equivFin (α × β)).symm j).1 = a then 1 else 0) * lift₂ x j) =
∑ b : β, x a b := by
calc
(∑ j : Fin (Fintype.card (α × β)),
(if ((Fintype.equivFin (α × β)).symm j).1 = a then 1 else 0) * lift₂ x j)
= ∑ p : α × β, (if p.1 = a then 1 else 0) * x p.1 p.2 := by
rw [sum_reindex]
simp [lift₂, lift, Function.uncurry]
_ = ∑ b : β, x a b := by
rw [Fintype.sum_prod_type]
rw [Finset.sum_eq_single a]
· simp
· intro c _ hc; simp [hc]
· intro h; exact False.elim (h (Finset.mem_univ a))Sum of an indicator on the second coordinate over all ordered pairs.
theorem sum_indicator_snd {α β : Type*} [Fintype α] [Fintype β] [DecidableEq β]
(a : β) (x : α → β → ℝ) :
(∑ j : Fin (Fintype.card (α × β)),
(if ((Fintype.equivFin (α × β)).symm j).2 = a then 1 else 0) * lift₂ x j) =
∑ b : α, x b a := by
calc
(∑ j : Fin (Fintype.card (α × β)),
(if ((Fintype.equivFin (α × β)).symm j).2 = a then 1 else 0) * lift₂ x j)
= ∑ p : α × β, (if p.2 = a then 1 else 0) * x p.1 p.2 := by
rw [sum_reindex]
simp [lift₂, lift, Function.uncurry]
_ = ∑ b : α, x b a := by
rw [Fintype.sum_prod_type]
rw [Finset.sum_comm]
rw [Finset.sum_eq_single a]
· simp
· intro c _ hc; simp [hc]
· intro h; exact False.elim (h (Finset.mem_univ a))end FinEncodingend Chapter29end CLRSCLRSLean.FourthEdition.Chapter_29.Section_29_2_Formulating_Problems_As_Linear_Programs.ShortestPath
29.2: Shortest paths as a linear program
For a source s and target t, CLRS maximizes d t, subject to
d s = 0 and d v ≤ d u + w u v on every edge. Thus every feasible
d t is a lower bound on every s-to-t walk; an attained
bound is optimal.
This module also records the finite standard-form encoding: the general-form
program is reindexed into a concrete StandardLP (one variable per
vertex), and feasibility and the objective value are proved to be preserved.
namespace CLRSnamespace Chapter29namespace ShortestPathLPopen Chapter24open Matrixopen scoped BigOperatorsvariable {V : Type*} [Fintype V] [DecidableEq V]Feasibility constraints of the CLRS shortest-path LP.
def IsFeasible (G : WeightedGraph V) (s : V) (d : V → ℝ) : Prop :=
d s = 0 ∧ ∀ u v, (u, v) ∈ G.edges → d v ≤ d u + G.w u v
Optimality for the maximization objective d t.
def IsOptimal (G : WeightedGraph V) (s t : V) (d : V → ℝ) : Prop :=
IsFeasible G s d ∧ ∀ e, IsFeasible G s e → e t ≤ d tEvery feasible potential is a lower bound on the weight of every walk from the source. This is the core correctness property of the formulation.
theorem feasible_le_walkWeight {G : WeightedGraph V} {s t : V} {d : V → ℝ}
(hd : IsFeasible G s d) (p : List V) (hp : G.IsWalkFrom s t p) :
d t ≤ Chapter24.WeightedGraph.walkWeight G.w p := by
have h := Chapter24.WeightedGraph.le_add_walkWeight_of_potential G d hd.2 s t p hp
simpa [hd.1] using hIf a feasible potential is attained by an actual source-to-target walk, then it solves the shortest-path LP.
theorem optimal_of_attained_walk {G : WeightedGraph V} {s t : V} {d : V → ℝ}
(hd : IsFeasible G s d) (p : List V) (hp : G.IsWalkFrom s t p)
(hweight : Chapter24.WeightedGraph.walkWeight G.w p = d t) : IsOptimal G s t d := by
refine ⟨hd, ?_⟩
intro e he
calc
e t ≤ Chapter24.WeightedGraph.walkWeight G.w p := feasible_le_walkWeight he p hp
_ = d t := hweightFinite standard-form encoding
The type of directed edges that are actually present in the graph.
abbrev Edge (G : WeightedGraph V) := {e : V × V // e ∈ G.edges}
The general-form program for the shortest-path LP: maximize d t
subject to d s = 0 and d v - d u ≤ w u v on every edge. All
vertices are free variables.
noncomputable def toGeneralLP (G : WeightedGraph V) (s t : V) : GeneralLP where
n := Fintype.card V
m := Fintype.card (Edge G) + 1
maximize := true
c := fun j => if (Fintype.equivFin V).symm j = t then 1 else 0
rel := fun i =>
Fin.cases
(ConstraintRel.eq)
(fun _ => ConstraintRel.le)
i
A := fun i =>
Fin.cases
(fun j => if (Fintype.equivFin V).symm j = s then 1 else 0)
(fun e j =>
let uv := (Fintype.equivFin (Edge G)).symm e
(if (Fintype.equivFin V).symm j = uv.1.2 then 1 else 0) -
(if (Fintype.equivFin V).symm j = uv.1.1 then 1 else 0))
i
b := fun i =>
Fin.cases
(0 : ℝ)
(fun e =>
let uv := (Fintype.equivFin (Edge G)).symm e
G.w uv.1.1 uv.1.2)
i
free := fun _ => trueThe index of the source-equality constraint.
abbrev sourceIndex (G : WeightedGraph V) : Fin (Fintype.card (Edge G) + 1) := 0The index of the constraint attached to a present edge.
abbrev edgeIndex (G : WeightedGraph V) (e : Fin (Fintype.card (Edge G))) :
Fin (Fintype.card (Edge G) + 1) :=
e.succlemma A_source (G : WeightedGraph V) (s t : V) (j : Fin (Fintype.card V)) :
(toGeneralLP G s t).A (sourceIndex G) j =
if (Fintype.equivFin V).symm j = s then 1 else 0 := by
simp [toGeneralLP, sourceIndex]lemma b_source (G : WeightedGraph V) (s t : V) :
(toGeneralLP G s t).b (sourceIndex G) = 0 := by
simp [toGeneralLP, sourceIndex]lemma rel_source (G : WeightedGraph V) (s t : V) :
(toGeneralLP G s t).rel (sourceIndex G) = ConstraintRel.eq := by
simp [toGeneralLP, sourceIndex]lemma A_edge (G : WeightedGraph V) (s t : V) (e : Fin (Fintype.card (Edge G)))
(j : Fin (Fintype.card V)) :
(toGeneralLP G s t).A (edgeIndex G e) j =
(if (Fintype.equivFin V).symm j = ((Fintype.equivFin (Edge G)).symm e).1.2 then 1 else 0) -
(if (Fintype.equivFin V).symm j = ((Fintype.equivFin (Edge G)).symm e).1.1 then 1 else 0) := by
simp [toGeneralLP, edgeIndex]lemma b_edge (G : WeightedGraph V) (s t : V) (e : Fin (Fintype.card (Edge G))) :
(toGeneralLP G s t).b (edgeIndex G e) =
G.w ((Fintype.equivFin (Edge G)).symm e).1.1 ((Fintype.equivFin (Edge G)).symm e).1.2 := by
simp [toGeneralLP, edgeIndex]lemma rel_edge (G : WeightedGraph V) (s t : V) (e : Fin (Fintype.card (Edge G))) :
(toGeneralLP G s t).rel (edgeIndex G e) = ConstraintRel.le := by
simp [toGeneralLP, edgeIndex]
The source row of the encoded constraint matrix reads out d s.
lemma mulVec_source (G : WeightedGraph V) (s t : V) (d : V → ℝ) :
((toGeneralLP G s t).A *ᵥ FinEncoding.lift d) (sourceIndex G) = d s := by
calc
((toGeneralLP G s t).A *ᵥ FinEncoding.lift d) (sourceIndex G)
= ∑ j : Fin (Fintype.card V), (toGeneralLP G s t).A (sourceIndex G) j * FinEncoding.lift d j := by rfl
_ = ∑ j : Fin (Fintype.card V),
(if (Fintype.equivFin V).symm j = s then 1 else 0) * FinEncoding.lift d j := by
simp only [A_source]
_ = d s := FinEncoding.sum_indicator s d
An edge row of the encoded constraint matrix reads out d v - d u.
lemma mulVec_edge (G : WeightedGraph V) (s t : V) (e : Fin (Fintype.card (Edge G)))
(d : V → ℝ) :
((toGeneralLP G s t).A *ᵥ FinEncoding.lift d) (edgeIndex G e) =
d ((Fintype.equivFin (Edge G)).symm e).1.2 - d ((Fintype.equivFin (Edge G)).symm e).1.1 := by
calc
((toGeneralLP G s t).A *ᵥ FinEncoding.lift d) (edgeIndex G e)
= ∑ j : Fin (Fintype.card V), (toGeneralLP G s t).A (edgeIndex G e) j * FinEncoding.lift d j := by rfl
_ = ∑ j : Fin (Fintype.card V),
((if (Fintype.equivFin V).symm j = ((Fintype.equivFin (Edge G)).symm e).1.2 then 1 else 0) -
(if (Fintype.equivFin V).symm j = ((Fintype.equivFin (Edge G)).symm e).1.1 then 1 else 0)) *
FinEncoding.lift d j := by
simp only [A_edge]
_ = (∑ j : Fin (Fintype.card V),
(if (Fintype.equivFin V).symm j = ((Fintype.equivFin (Edge G)).symm e).1.2 then 1 else 0) * FinEncoding.lift d j) -
(∑ j : Fin (Fintype.card V),
(if (Fintype.equivFin V).symm j = ((Fintype.equivFin (Edge G)).symm e).1.1 then 1 else 0) * FinEncoding.lift d j) := by
simp only [sub_mul, Finset.sum_sub_distrib]
_ = d ((Fintype.equivFin (Edge G)).symm e).1.2 - d ((Fintype.equivFin (Edge G)).symm e).1.1 := by
rw [FinEncoding.sum_indicator, FinEncoding.sum_indicator]Semantic feasibility is exactly encoded general-form feasibility.
theorem feasible_iff_generalLP (G : WeightedGraph V) (s t : V) (d : V → ℝ) :
IsFeasible G s d ↔ (toGeneralLP G s t).IsFeasible (FinEncoding.lift d) := by
unfold IsFeasible GeneralLP.IsFeasible
constructor
· intro hd
refine ⟨?_, ?_⟩
· intro j hfree
exfalso
simpa [toGeneralLP] using hfree
· intro i
exact Fin.cases (motive := fun i => match (toGeneralLP G s t).rel i with
| ConstraintRel.le => ((toGeneralLP G s t).A *ᵥ FinEncoding.lift d) i ≤ (toGeneralLP G s t).b i
| ConstraintRel.eq => ((toGeneralLP G s t).A *ᵥ FinEncoding.lift d) i = (toGeneralLP G s t).b i
| ConstraintRel.ge => (toGeneralLP G s t).b i ≤ ((toGeneralLP G s t).A *ᵥ FinEncoding.lift d) i)
(by
rw [rel_source, mulVec_source, b_source]
exact hd.1)
(fun e => by
rw [rel_edge, mulVec_edge, b_edge]
have h := hd.2 ((Fintype.equivFin (Edge G)).symm e).1.1 ((Fintype.equivFin (Edge G)).symm e).1.2 ((Fintype.equivFin (Edge G)).symm e).2
linarith)
i
· intro h
refine ⟨?_, ?_⟩
· have hsource := h.2 (sourceIndex G)
rw [rel_source] at hsource
rw [mulVec_source, b_source] at hsource
exact hsource
· intro u v huv
let e : Edge G := ⟨(u, v), huv⟩
have hedge := h.2 (edgeIndex G (Fintype.equivFin (Edge G) e))
rw [rel_edge] at hedge
rw [mulVec_edge, b_edge] at hedge
have hedge' : d v - d u ≤ G.w u v := by simpa [e] using hedge
linarith
The general-form objective reads out d t.
lemma objective_generalLP (G : WeightedGraph V) (s t : V) (d : V → ℝ) :
(toGeneralLP G s t).objective (FinEncoding.lift d) = d t := by
dsimp [GeneralLP.objective, toGeneralLP]
simp only [dotProduct]
rw [FinEncoding.sum_reindex]
simp only [FinEncoding.lift]
rw [Finset.sum_eq_single t]
· simp
· intro v _ hv; simp [hv]
· intro h; exact False.elim (h (Finset.mem_univ t))The finite standard-form program obtained by normalizing the shortest-path general program.
noncomputable def toStandardLP (G : WeightedGraph V) (s t : V) :=
(toGeneralLP G s t).toStandardLPThe expansion of a potential to the normalized standard-form variable vector.
noncomputable def fullLift (G : WeightedGraph V) (s t : V) (d : V → ℝ) :
Fin (Fintype.card V + Fintype.card V) → ℝ :=
(toGeneralLP G s t).lift (FinEncoding.lift d)
The normalized standard-form objective is exactly the shortest-path
objective d t.
theorem objective_toStandardLP (G : WeightedGraph V) (s t : V) (d : V → ℝ) :
(toStandardLP G s t).objective (fullLift G s t d) = d t := by
unfold toStandardLP fullLift
rw [GeneralLP.objective_lift]
rw [objective_generalLP]
simp [toGeneralLP, GeneralLP.objectiveSign]Feasibility is exactly preserved under the finite standard-form encoding.
theorem feasible_iff_toStandardLP (G : WeightedGraph V) (s t : V) (d : V → ℝ) :
IsFeasible G s d ↔ (toStandardLP G s t).IsFeasible (fullLift G s t d) := by
unfold toStandardLP fullLift
rw [← GeneralLP.feasible_iff_lift]
exact feasible_iff_generalLP G s t dend ShortestPathLPend Chapter29end CLRSCLRSLean.FourthEdition.Chapter_29.Section_29_2_Formulating_Problems_As_Linear_Programs.MaximumFlow
29.2: Maximum flow as a linear program
The variables are the gross directed flows f u v. The constraints are
nonnegativity, capacity, and conservation at every vertex other than
s,t; the objective is the net flow leaving s, exactly as in CLRS
§29.2.
This module also records the finite standard-form encoding: one nonnegative
variable per ordered pair, a capacity inequality per ordered pair, and a
conservation equality per internal vertex, reindexed into a concrete
StandardLP.
namespace CLRSnamespace Chapter29namespace MaximumFlowLPopen Finsetopen Matrixopen scoped BigOperatorsvariable {V : Type*} [Fintype V] (N : FlowNetwork V)The constraints of the maximum-flow LP.
def IsFeasible (f : V → V → ℝ) : Prop :=
(∀ u v, 0 ≤ f u v) ∧
(∀ u v, f u v ≤ N.capacity u v) ∧
∀ u, u ≠ N.source → u ≠ N.sink → FlowNetwork.ConservesAt f uThe textbook maximum-flow objective: source outflow minus source inflow.
def objective (f : V → V → ℝ) : ℝ := FlowNetwork.netOutflow f N.sourceA feasible flow maximizing the source outflow.
def IsOptimal (f : V → V → ℝ) : Prop :=
IsFeasible N f ∧ ∀ g, IsFeasible N g → objective N g ≤ objective N fThe displayed LP constraints are precisely the usual finite-network flow constraints.
theorem isFeasible_iff (f : V → V → ℝ) : IsFeasible N f ↔ N.IsFlow f := by
simp only [IsFeasible, FlowNetwork.IsFlow, FlowNetwork.IsCapacityFeasible]
tautoThe LP optimum is precisely a maximum flow under the same objective.
theorem isOptimal_iff (f : V → V → ℝ) :
IsOptimal N f ↔
N.IsFlow f ∧ ∀ g, N.IsFlow g →
FlowNetwork.netOutflow g N.source ≤ FlowNetwork.netOutflow f N.source := by
simp only [IsOptimal, objective, isFeasible_iff]Finite standard-form encoding
section Encodingvariable [DecidableEq V]The internal vertices at which flow is conserved.
abbrev Internal (N : FlowNetwork V) := {u : V // u ≠ N.source ∧ u ≠ N.sink}The general-form program for the maximum-flow LP: one nonnegative variable per ordered pair, a capacity inequality per ordered pair, and a conservation equality per internal vertex.
noncomputable def toGeneralLP (N : FlowNetwork V) : GeneralLP where
n := Fintype.card (V × V)
m := Fintype.card (V × V) + Fintype.card (Internal N)
maximize := true
c := fun j =>
(if ((Fintype.equivFin (V × V)).symm j).1 = N.source then 1 else 0) -
(if ((Fintype.equivFin (V × V)).symm j).2 = N.source then 1 else 0)
rel := fun i =>
Fin.addCases (motive := fun _ => ConstraintRel)
(fun _ => ConstraintRel.le)
(fun _ => ConstraintRel.eq)
i
A := fun i =>
Fin.addCases (motive := fun _ => Fin (Fintype.card (V × V)) → ℝ)
(fun e j => if j = e then 1 else 0)
(fun u j =>
let v := ((Fintype.equivFin (Internal N)).symm u).1
(if ((Fintype.equivFin (V × V)).symm j).1 = v then 1 else 0) -
(if ((Fintype.equivFin (V × V)).symm j).2 = v then 1 else 0))
i
b := fun i =>
Fin.addCases (motive := fun _ => ℝ)
(fun e => N.capacity ((Fintype.equivFin (V × V)).symm e).1 ((Fintype.equivFin (V × V)).symm e).2)
(fun _ => 0)
i
free := fun _ => falseThe index of the capacity constraint for a directed pair.
abbrev capIndex (N : FlowNetwork V) (e : Fin (Fintype.card (V × V))) :
Fin (Fintype.card (V × V) + Fintype.card (Internal N)) :=
Fin.castAdd (Fintype.card (Internal N)) eThe index of the conservation constraint for an internal vertex.
abbrev consIndex (N : FlowNetwork V) (u : Fin (Fintype.card (Internal N))) :
Fin (Fintype.card (V × V) + Fintype.card (Internal N)) :=
Fin.natAdd (Fintype.card (V × V)) u
lemma A_cap (N : FlowNetwork V) (e : Fin (Fintype.card (V × V))) (j : Fin (Fintype.card (V × V))) :
(toGeneralLP N).A (capIndex N e) j = if j = e then 1 else 0 := by
dsimp [toGeneralLP, capIndex]
rw [FinEncoding.addCases_castAdd]
lemma b_cap (N : FlowNetwork V) (e : Fin (Fintype.card (V × V))) :
(toGeneralLP N).b (capIndex N e) =
N.capacity ((Fintype.equivFin (V × V)).symm e).1 ((Fintype.equivFin (V × V)).symm e).2 := by
dsimp [toGeneralLP, capIndex]
rw [FinEncoding.addCases_castAdd]
lemma rel_cap (N : FlowNetwork V) (e : Fin (Fintype.card (V × V))) :
(toGeneralLP N).rel (capIndex N e) = ConstraintRel.le := by
dsimp [toGeneralLP, capIndex]
rw [FinEncoding.addCases_castAdd]
lemma A_cons (N : FlowNetwork V) (u : Fin (Fintype.card (Internal N))) (j : Fin (Fintype.card (V × V))) :
(toGeneralLP N).A (consIndex N u) j =
(if ((Fintype.equivFin (V × V)).symm j).1 = ((Fintype.equivFin (Internal N)).symm u).1 then 1 else 0) -
(if ((Fintype.equivFin (V × V)).symm j).2 = ((Fintype.equivFin (Internal N)).symm u).1 then 1 else 0) := by
dsimp [toGeneralLP, consIndex]
rw [FinEncoding.addCases_natAdd]
lemma b_cons (N : FlowNetwork V) (u : Fin (Fintype.card (Internal N))) :
(toGeneralLP N).b (consIndex N u) = 0 := by
dsimp [toGeneralLP, consIndex]
rw [FinEncoding.addCases_natAdd]
lemma rel_cons (N : FlowNetwork V) (u : Fin (Fintype.card (Internal N))) :
(toGeneralLP N).rel (consIndex N u) = ConstraintRel.eq := by
dsimp [toGeneralLP, consIndex]
rw [FinEncoding.addCases_natAdd]A capacity row reads out the gross flow on its directed pair.
lemma mulVec_cap (N : FlowNetwork V) (e : Fin (Fintype.card (V × V))) (f : V → V → ℝ) :
((toGeneralLP N).A *ᵥ FinEncoding.lift₂ f) (capIndex N e) = FinEncoding.lift₂ f e := by
calc
((toGeneralLP N).A *ᵥ FinEncoding.lift₂ f) (capIndex N e)
= ∑ j : Fin (Fintype.card (V × V)), (toGeneralLP N).A (capIndex N e) j * FinEncoding.lift₂ f j := by rfl
_ = ∑ j : Fin (Fintype.card (V × V)), (if j = e then 1 else 0) * FinEncoding.lift₂ f j := by
simp only [A_cap]
_ = FinEncoding.lift₂ f e := FinEncoding.sum_indicator_fin e (FinEncoding.lift₂ f)A conservation row reads out the net outflow at its vertex.
lemma mulVec_cons (N : FlowNetwork V) (u : Fin (Fintype.card (Internal N))) (f : V → V → ℝ) :
((toGeneralLP N).A *ᵥ FinEncoding.lift₂ f) (consIndex N u) =
FlowNetwork.outflow f ((Fintype.equivFin (Internal N)).symm u).1 -
FlowNetwork.inflow f ((Fintype.equivFin (Internal N)).symm u).1 := by
calc
((toGeneralLP N).A *ᵥ FinEncoding.lift₂ f) (consIndex N u)
= ∑ j : Fin (Fintype.card (V × V)), (toGeneralLP N).A (consIndex N u) j * FinEncoding.lift₂ f j := by rfl
_ = ∑ j : Fin (Fintype.card (V × V)),
((if ((Fintype.equivFin (V × V)).symm j).1 = ((Fintype.equivFin (Internal N)).symm u).1 then 1 else 0) -
(if ((Fintype.equivFin (V × V)).symm j).2 = ((Fintype.equivFin (Internal N)).symm u).1 then 1 else 0)) *
FinEncoding.lift₂ f j := by
simp only [A_cons]
_ = (∑ j : Fin (Fintype.card (V × V)),
(if ((Fintype.equivFin (V × V)).symm j).1 = ((Fintype.equivFin (Internal N)).symm u).1 then 1 else 0) * FinEncoding.lift₂ f j) -
(∑ j : Fin (Fintype.card (V × V)),
(if ((Fintype.equivFin (V × V)).symm j).2 = ((Fintype.equivFin (Internal N)).symm u).1 then 1 else 0) * FinEncoding.lift₂ f j) := by
simp only [sub_mul, Finset.sum_sub_distrib]
_ = (∑ b : V, f ((Fintype.equivFin (Internal N)).symm u).1 b) -
(∑ b : V, f b ((Fintype.equivFin (Internal N)).symm u).1) := by
rw [FinEncoding.sum_indicator_fst, FinEncoding.sum_indicator_snd]
_ = FlowNetwork.outflow f ((Fintype.equivFin (Internal N)).symm u).1 -
FlowNetwork.inflow f ((Fintype.equivFin (Internal N)).symm u).1 := rflSemantic feasibility is exactly encoded general-form feasibility.
theorem feasible_iff_generalLP (N : FlowNetwork V) (f : V → V → ℝ) :
IsFeasible N f ↔ (toGeneralLP N).IsFeasible (FinEncoding.lift₂ f) := by
unfold IsFeasible GeneralLP.IsFeasible
constructor
· intro hd
refine ⟨?_, ?_⟩
· intro j hfree
simpa [FinEncoding.lift₂, FinEncoding.lift, Function.uncurry] using
(hd.1 ((Fintype.equivFin (V × V)).symm j).1 ((Fintype.equivFin (V × V)).symm j).2)
· intro i
exact Fin.addCases (motive := fun i => match (toGeneralLP N).rel i with
| ConstraintRel.le => ((toGeneralLP N).A *ᵥ FinEncoding.lift₂ f) i ≤ (toGeneralLP N).b i
| ConstraintRel.eq => ((toGeneralLP N).A *ᵥ FinEncoding.lift₂ f) i = (toGeneralLP N).b i
| ConstraintRel.ge => (toGeneralLP N).b i ≤ ((toGeneralLP N).A *ᵥ FinEncoding.lift₂ f) i)
(fun e => by
rw [rel_cap, mulVec_cap, b_cap]
simpa [FinEncoding.lift₂, FinEncoding.lift, Function.uncurry] using
(hd.2.1 ((Fintype.equivFin (V × V)).symm e).1 ((Fintype.equivFin (V × V)).symm e).2))
(fun u => by
rw [rel_cons, mulVec_cons, b_cons]
have hc := hd.2.2 ((Fintype.equivFin (Internal N)).symm u).1
((Fintype.equivFin (Internal N)).symm u).2.1
((Fintype.equivFin (Internal N)).symm u).2.2
unfold FlowNetwork.ConservesAt at hc
linarith)
i
· intro h
refine ⟨?_, ?_, ?_⟩
· intro u v
have hn := h.1 (Fintype.equivFin (V × V) (u, v))
simpa [FinEncoding.lift₂, FinEncoding.lift, Function.uncurry] using (hn (by simp [toGeneralLP]))
· intro u v
have hc := h.2 (capIndex N (Fintype.equivFin (V × V) (u, v)))
rw [rel_cap] at hc
rw [mulVec_cap, b_cap] at hc
simpa [FinEncoding.lift₂, FinEncoding.lift, Function.uncurry] using hc
· intro u hu hw
let e : Internal N := ⟨u, ⟨hu, hw⟩⟩
have hcons := h.2 (consIndex N (Fintype.equivFin (Internal N) e))
rw [rel_cons] at hcons
rw [mulVec_cons, b_cons] at hcons
have hcons' : FlowNetwork.outflow f u - FlowNetwork.inflow f u = 0 := by
simpa [e] using hcons
unfold FlowNetwork.ConservesAt
linarithThe general-form objective is the textbook maximum-flow objective.
lemma objective_generalLP (N : FlowNetwork V) (f : V → V → ℝ) :
(toGeneralLP N).objective (FinEncoding.lift₂ f) = objective N f := by
dsimp [GeneralLP.objective, toGeneralLP, objective, FlowNetwork.netOutflow, FlowNetwork.outflow, FlowNetwork.inflow]
simp only [dotProduct, sub_mul, Finset.sum_sub_distrib]
rw [FinEncoding.sum_indicator_fst, FinEncoding.sum_indicator_snd]The finite standard-form program obtained by normalizing the maximum-flow general program.
noncomputable def toStandardLP (N : FlowNetwork V) :=
(toGeneralLP N).toStandardLPThe expansion of a gross-flow vector to the normalized standard-form variable vector.
noncomputable def fullLift (N : FlowNetwork V) (f : V → V → ℝ) :
Fin (Fintype.card (V × V) + Fintype.card (V × V)) → ℝ :=
(toGeneralLP N).lift (FinEncoding.lift₂ f)The normalized standard-form objective is the maximum-flow objective.
theorem objective_toStandardLP (N : FlowNetwork V) (f : V → V → ℝ) :
(toStandardLP N).objective (fullLift N f) = objective N f := by
unfold toStandardLP fullLift
rw [GeneralLP.objective_lift]
rw [objective_generalLP]
simp [toGeneralLP, GeneralLP.objectiveSign]Feasibility is exactly preserved under the finite standard-form encoding.
theorem feasible_iff_toStandardLP (N : FlowNetwork V) (f : V → V → ℝ) :
IsFeasible N f ↔ (toStandardLP N).IsFeasible (fullLift N f) := by
unfold toStandardLP fullLift
rw [← GeneralLP.feasible_iff_lift]
exact feasible_iff_generalLP N fend Encodingend MaximumFlowLPend Chapter29end CLRSCLRSLean.FourthEdition.Chapter_29.Section_29_2_Formulating_Problems_As_Linear_Programs.MinimumCostFlow
29.2: Minimum-cost flow as a linear program
In addition to capacities, a network assigns a unit cost to every directed
pair. A demand-d flow has net source outflow d; minimizing the
sum of unit cost times flow gives the CLRS minimum-cost-flow formulation.
This module also records the finite standard-form encoding: one nonnegative
variable per ordered pair, a capacity inequality per ordered pair, a
conservation equality per internal vertex, and one source-demand equality,
reindexed into a concrete StandardLP.
namespace CLRSnamespace Chapter29open Finsetopen Matrixopen scoped BigOperatorsA capacitated network with a real unit cost on each directed pair.
structure CostedFlowNetwork (V : Type*) [Fintype V] extends FlowNetwork V where
cost : V → V → ℝnamespace MinimumCostFlowLPvariable {V : Type*} [Fintype V] (N : CostedFlowNetwork V) (demand : ℝ)The capacity, conservation, and exact-demand constraints.
def IsFeasible (f : V → V → ℝ) : Prop :=
N.toFlowNetwork.IsFlow f ∧
FlowNetwork.netOutflow f N.source = demand
Total cost Σ_u Σ_v a_uv f_uv.
def objective (f : V → V → ℝ) : ℝ :=
∑ u, ∑ v, N.cost u v * f u vA demand flow of minimum total cost.
def IsOptimal (f : V → V → ℝ) : Prop :=
IsFeasible N demand f ∧
∀ g, IsFeasible N demand g → objective N f ≤ objective N gThe LP constraints are exactly capacity-feasible flow plus the prescribed source demand.
theorem isFeasible_iff (f : V → V → ℝ) :
IsFeasible N demand f ↔
N.toFlowNetwork.IsFlow f ∧
FlowNetwork.netOutflow f N.source = demand := by
rflThe minimization specification agrees exactly with minimum-cost demand flow.
theorem isOptimal_iff (f : V → V → ℝ) :
IsOptimal N demand f ↔
(N.toFlowNetwork.IsFlow f ∧
FlowNetwork.netOutflow f N.source = demand) ∧
∀ g, (N.toFlowNetwork.IsFlow g ∧
FlowNetwork.netOutflow g N.source = demand) →
(∑ u, ∑ v, N.cost u v * f u v) ≤
∑ u, ∑ v, N.cost u v * g u v := by
rflFinite standard-form encoding
section Encodingvariable [DecidableEq V]The internal vertices at which flow is conserved.
abbrev Internal (N : CostedFlowNetwork V) := {u : V // u ≠ N.source ∧ u ≠ N.sink}The general-form program for the minimum-cost-flow LP: one nonnegative variable per ordered pair, a capacity inequality per ordered pair, a conservation equality per internal vertex, and a source-demand equality. The objective is minimized (cost is the sum of unit cost times flow).
noncomputable def toGeneralLP (N : CostedFlowNetwork V) (demand : ℝ) : GeneralLP where
n := Fintype.card (V × V)
m := Fintype.card (V × V) + Fintype.card (Internal N) + 1
maximize := false
c := fun j => N.cost ((Fintype.equivFin (V × V)).symm j).1 ((Fintype.equivFin (V × V)).symm j).2
rel := fun i =>
Fin.cases
(ConstraintRel.eq)
(fun i => Fin.addCases (motive := fun _ => ConstraintRel)
(fun _ => ConstraintRel.le)
(fun _ => ConstraintRel.eq)
i)
i
A := fun i =>
Fin.cases
(fun j =>
(if ((Fintype.equivFin (V × V)).symm j).1 = N.source then 1 else 0) -
(if ((Fintype.equivFin (V × V)).symm j).2 = N.source then 1 else 0))
(fun i j => Fin.addCases (motive := fun _ => ℝ)
(fun e => if j = e then 1 else 0)
(fun u =>
let v := ((Fintype.equivFin (Internal N)).symm u).1
(if ((Fintype.equivFin (V × V)).symm j).1 = v then 1 else 0) -
(if ((Fintype.equivFin (V × V)).symm j).2 = v then 1 else 0))
i)
i
b := fun i =>
Fin.cases
(demand)
(fun i => Fin.addCases (motive := fun _ => ℝ)
(fun e => N.capacity ((Fintype.equivFin (V × V)).symm e).1 ((Fintype.equivFin (V × V)).symm e).2)
(fun _ => 0)
i)
i
free := fun _ => falseThe index of the source-demand equality constraint.
abbrev demandIndex (N : CostedFlowNetwork V) :
Fin (Fintype.card (V × V) + Fintype.card (Internal N) + 1) := 0The index of the capacity constraint for a directed pair.
abbrev capIndex (N : CostedFlowNetwork V) (e : Fin (Fintype.card (V × V))) :
Fin (Fintype.card (V × V) + Fintype.card (Internal N) + 1) :=
(Fin.castAdd (Fintype.card (Internal N)) e).succThe index of the conservation constraint for an internal vertex.
abbrev consIndex (N : CostedFlowNetwork V) (u : Fin (Fintype.card (Internal N))) :
Fin (Fintype.card (V × V) + Fintype.card (Internal N) + 1) :=
(Fin.natAdd (Fintype.card (V × V)) u).succlemma A_demand (N : CostedFlowNetwork V) (demand : ℝ) (j : Fin (Fintype.card (V × V))) :
(toGeneralLP N demand).A (demandIndex N) j =
(if ((Fintype.equivFin (V × V)).symm j).1 = N.source then 1 else 0) -
(if ((Fintype.equivFin (V × V)).symm j).2 = N.source then 1 else 0) := by
simp [toGeneralLP, demandIndex]lemma b_demand (N : CostedFlowNetwork V) (demand : ℝ) :
(toGeneralLP N demand).b (demandIndex N) = demand := by
simp [toGeneralLP, demandIndex]lemma rel_demand (N : CostedFlowNetwork V) (demand : ℝ) :
(toGeneralLP N demand).rel (demandIndex N) = ConstraintRel.eq := by
simp [toGeneralLP, demandIndex]
lemma A_cap (N : CostedFlowNetwork V) (demand : ℝ) (e : Fin (Fintype.card (V × V))) (j : Fin (Fintype.card (V × V))) :
(toGeneralLP N demand).A (capIndex N e) j = if j = e then 1 else 0 := by
dsimp [toGeneralLP, capIndex]
rw [FinEncoding.addCases_castAdd]
lemma b_cap (N : CostedFlowNetwork V) (demand : ℝ) (e : Fin (Fintype.card (V × V))) :
(toGeneralLP N demand).b (capIndex N e) =
N.capacity ((Fintype.equivFin (V × V)).symm e).1 ((Fintype.equivFin (V × V)).symm e).2 := by
dsimp [toGeneralLP, capIndex]
rw [FinEncoding.addCases_castAdd]
lemma rel_cap (N : CostedFlowNetwork V) (demand : ℝ) (e : Fin (Fintype.card (V × V))) :
(toGeneralLP N demand).rel (capIndex N e) = ConstraintRel.le := by
dsimp [toGeneralLP, capIndex]
rw [FinEncoding.addCases_castAdd]
lemma A_cons (N : CostedFlowNetwork V) (demand : ℝ) (u : Fin (Fintype.card (Internal N))) (j : Fin (Fintype.card (V × V))) :
(toGeneralLP N demand).A (consIndex N u) j =
(if ((Fintype.equivFin (V × V)).symm j).1 = ((Fintype.equivFin (Internal N)).symm u).1 then 1 else 0) -
(if ((Fintype.equivFin (V × V)).symm j).2 = ((Fintype.equivFin (Internal N)).symm u).1 then 1 else 0) := by
dsimp [toGeneralLP, consIndex]
rw [FinEncoding.addCases_natAdd]
lemma b_cons (N : CostedFlowNetwork V) (demand : ℝ) (u : Fin (Fintype.card (Internal N))) :
(toGeneralLP N demand).b (consIndex N u) = 0 := by
dsimp [toGeneralLP, consIndex]
rw [FinEncoding.addCases_natAdd]
lemma rel_cons (N : CostedFlowNetwork V) (demand : ℝ) (u : Fin (Fintype.card (Internal N))) :
(toGeneralLP N demand).rel (consIndex N u) = ConstraintRel.eq := by
dsimp [toGeneralLP, consIndex]
rw [FinEncoding.addCases_natAdd]The demand row reads out the source net outflow.
lemma mulVec_demand (N : CostedFlowNetwork V) (demand : ℝ) (f : V → V → ℝ) :
((toGeneralLP N demand).A *ᵥ FinEncoding.lift₂ f) (demandIndex N) =
FlowNetwork.netOutflow f N.source := by
calc
((toGeneralLP N demand).A *ᵥ FinEncoding.lift₂ f) (demandIndex N)
= ∑ j : Fin (Fintype.card (V × V)), (toGeneralLP N demand).A (demandIndex N) j * FinEncoding.lift₂ f j := by rfl
_ = ∑ j : Fin (Fintype.card (V × V)),
((if ((Fintype.equivFin (V × V)).symm j).1 = N.source then 1 else 0) -
(if ((Fintype.equivFin (V × V)).symm j).2 = N.source then 1 else 0)) *
FinEncoding.lift₂ f j := by
simp only [A_demand]
_ = (∑ j : Fin (Fintype.card (V × V)),
(if ((Fintype.equivFin (V × V)).symm j).1 = N.source then 1 else 0) * FinEncoding.lift₂ f j) -
(∑ j : Fin (Fintype.card (V × V)),
(if ((Fintype.equivFin (V × V)).symm j).2 = N.source then 1 else 0) * FinEncoding.lift₂ f j) := by
simp only [sub_mul, Finset.sum_sub_distrib]
_ = (∑ b : V, f N.source b) - (∑ b : V, f b N.source) := by
rw [FinEncoding.sum_indicator_fst, FinEncoding.sum_indicator_snd]
_ = FlowNetwork.netOutflow f N.source := rflA capacity row reads out the gross flow on its directed pair.
lemma mulVec_cap (N : CostedFlowNetwork V) (demand : ℝ) (e : Fin (Fintype.card (V × V))) (f : V → V → ℝ) :
((toGeneralLP N demand).A *ᵥ FinEncoding.lift₂ f) (capIndex N e) = FinEncoding.lift₂ f e := by
calc
((toGeneralLP N demand).A *ᵥ FinEncoding.lift₂ f) (capIndex N e)
= ∑ j : Fin (Fintype.card (V × V)), (toGeneralLP N demand).A (capIndex N e) j * FinEncoding.lift₂ f j := by rfl
_ = ∑ j : Fin (Fintype.card (V × V)), (if j = e then 1 else 0) * FinEncoding.lift₂ f j := by
simp only [A_cap]
_ = FinEncoding.lift₂ f e := FinEncoding.sum_indicator_fin e (FinEncoding.lift₂ f)A conservation row reads out the net outflow at its vertex.
lemma mulVec_cons (N : CostedFlowNetwork V) (demand : ℝ) (u : Fin (Fintype.card (Internal N))) (f : V → V → ℝ) :
((toGeneralLP N demand).A *ᵥ FinEncoding.lift₂ f) (consIndex N u) =
FlowNetwork.outflow f ((Fintype.equivFin (Internal N)).symm u).1 -
FlowNetwork.inflow f ((Fintype.equivFin (Internal N)).symm u).1 := by
calc
((toGeneralLP N demand).A *ᵥ FinEncoding.lift₂ f) (consIndex N u)
= ∑ j : Fin (Fintype.card (V × V)), (toGeneralLP N demand).A (consIndex N u) j * FinEncoding.lift₂ f j := by rfl
_ = ∑ j : Fin (Fintype.card (V × V)),
((if ((Fintype.equivFin (V × V)).symm j).1 = ((Fintype.equivFin (Internal N)).symm u).1 then 1 else 0) -
(if ((Fintype.equivFin (V × V)).symm j).2 = ((Fintype.equivFin (Internal N)).symm u).1 then 1 else 0)) *
FinEncoding.lift₂ f j := by
simp only [A_cons]
_ = (∑ j : Fin (Fintype.card (V × V)),
(if ((Fintype.equivFin (V × V)).symm j).1 = ((Fintype.equivFin (Internal N)).symm u).1 then 1 else 0) * FinEncoding.lift₂ f j) -
(∑ j : Fin (Fintype.card (V × V)),
(if ((Fintype.equivFin (V × V)).symm j).2 = ((Fintype.equivFin (Internal N)).symm u).1 then 1 else 0) * FinEncoding.lift₂ f j) := by
simp only [sub_mul, Finset.sum_sub_distrib]
_ = (∑ b : V, f ((Fintype.equivFin (Internal N)).symm u).1 b) -
(∑ b : V, f b ((Fintype.equivFin (Internal N)).symm u).1) := by
rw [FinEncoding.sum_indicator_fst, FinEncoding.sum_indicator_snd]
_ = FlowNetwork.outflow f ((Fintype.equivFin (Internal N)).symm u).1 -
FlowNetwork.inflow f ((Fintype.equivFin (Internal N)).symm u).1 := rflSemantic feasibility is exactly encoded general-form feasibility.
theorem feasible_iff_generalLP (N : CostedFlowNetwork V) (demand : ℝ) (f : V → V → ℝ) :
IsFeasible N demand f ↔ (toGeneralLP N demand).IsFeasible (FinEncoding.lift₂ f) := by
unfold IsFeasible GeneralLP.IsFeasible FlowNetwork.IsFlow FlowNetwork.IsCapacityFeasible
constructor
· intro hd
refine ⟨?_, ?_⟩
· intro j hfree
simpa [FinEncoding.lift₂, FinEncoding.lift, Function.uncurry] using
(hd.1.1.1 ((Fintype.equivFin (V × V)).symm j).1 ((Fintype.equivFin (V × V)).symm j).2)
· intro i
exact Fin.cases (motive := fun i => match (toGeneralLP N demand).rel i with
| ConstraintRel.le => ((toGeneralLP N demand).A *ᵥ FinEncoding.lift₂ f) i ≤ (toGeneralLP N demand).b i
| ConstraintRel.eq => ((toGeneralLP N demand).A *ᵥ FinEncoding.lift₂ f) i = (toGeneralLP N demand).b i
| ConstraintRel.ge => (toGeneralLP N demand).b i ≤ ((toGeneralLP N demand).A *ᵥ FinEncoding.lift₂ f) i)
(by
rw [rel_demand, mulVec_demand, b_demand]
exact hd.2)
(fun i => by
exact Fin.addCases (motive := fun i => match (toGeneralLP N demand).rel i.succ with
| ConstraintRel.le => ((toGeneralLP N demand).A *ᵥ FinEncoding.lift₂ f) i.succ ≤ (toGeneralLP N demand).b i.succ
| ConstraintRel.eq => ((toGeneralLP N demand).A *ᵥ FinEncoding.lift₂ f) i.succ = (toGeneralLP N demand).b i.succ
| ConstraintRel.ge => (toGeneralLP N demand).b i.succ ≤ ((toGeneralLP N demand).A *ᵥ FinEncoding.lift₂ f) i.succ)
(fun e => by
rw [rel_cap, mulVec_cap, b_cap]
simpa [FinEncoding.lift₂, FinEncoding.lift, Function.uncurry] using
(hd.1.1.2 ((Fintype.equivFin (V × V)).symm e).1 ((Fintype.equivFin (V × V)).symm e).2))
(fun u => by
rw [rel_cons, mulVec_cons, b_cons]
have hc := hd.1.2 ((Fintype.equivFin (Internal N)).symm u).1
((Fintype.equivFin (Internal N)).symm u).2.1
((Fintype.equivFin (Internal N)).symm u).2.2
unfold FlowNetwork.ConservesAt at hc
linarith)
i)
i
· intro h
refine ⟨⟨⟨?_, ?_⟩, ?_⟩, ?_⟩
· intro u v
have hn := h.1 (Fintype.equivFin (V × V) (u, v))
simpa [FinEncoding.lift₂, FinEncoding.lift, Function.uncurry] using (hn (by simp [toGeneralLP]))
· intro u v
have hc := h.2 (capIndex N (Fintype.equivFin (V × V) (u, v)))
rw [rel_cap] at hc
rw [mulVec_cap, b_cap] at hc
simpa [FinEncoding.lift₂, FinEncoding.lift, Function.uncurry] using hc
· intro u hu hw
let e : Internal N := ⟨u, ⟨hu, hw⟩⟩
have hcons := h.2 (consIndex N (Fintype.equivFin (Internal N) e))
rw [rel_cons] at hcons
rw [mulVec_cons, b_cons] at hcons
have hcons' : FlowNetwork.outflow f u - FlowNetwork.inflow f u = 0 := by
simpa [e] using hcons
unfold FlowNetwork.ConservesAt
linarith
· have hdemand := h.2 (demandIndex N)
rw [rel_demand] at hdemand
rw [mulVec_demand, b_demand] at hdemand
exact hdemandThe general-form objective is the total cost (minimized).
lemma objective_generalLP (N : CostedFlowNetwork V) (demand : ℝ) (f : V → V → ℝ) :
(toGeneralLP N demand).objective (FinEncoding.lift₂ f) = objective N f := by
dsimp [GeneralLP.objective, toGeneralLP, objective]
simp only [dotProduct]
rw [FinEncoding.sum_reindex]
simp [FinEncoding.lift₂, FinEncoding.lift, Function.uncurry]
rw [Fintype.sum_prod_type]The finite standard-form program obtained by normalizing the minimum-cost general program.
noncomputable def toStandardLP (N : CostedFlowNetwork V) (demand : ℝ) :=
(toGeneralLP N demand).toStandardLPThe expansion of a gross-flow vector to the normalized standard-form variable vector.
noncomputable def fullLift (N : CostedFlowNetwork V) (demand : ℝ) (f : V → V → ℝ) :
Fin (Fintype.card (V × V) + Fintype.card (V × V)) → ℝ :=
(toGeneralLP N demand).lift (FinEncoding.lift₂ f)The normalized standard-form objective equals the signed total cost.
theorem objective_toStandardLP (N : CostedFlowNetwork V) (demand : ℝ) (f : V → V → ℝ) :
(toStandardLP N demand).objective (fullLift N demand f) = -objective N f := by
unfold toStandardLP fullLift
rw [GeneralLP.objective_lift]
rw [objective_generalLP]
simp [toGeneralLP, GeneralLP.objectiveSign]Feasibility is exactly preserved under the finite standard-form encoding.
theorem feasible_iff_toStandardLP (N : CostedFlowNetwork V) (demand : ℝ) (f : V → V → ℝ) :
IsFeasible N demand f ↔ (toStandardLP N demand).IsFeasible (fullLift N demand f) := by
unfold toStandardLP fullLift
rw [← GeneralLP.feasible_iff_lift]
exact feasible_iff_generalLP N demand fend Encodingend MinimumCostFlowLPend Chapter29end CLRSCLRSLean.FourthEdition.Chapter_29.Section_29_2_Formulating_Problems_As_Linear_Programs.MulticommodityFlow
29.2: Multicommodity flow as a linear program
Each commodity has its own source, sink, demand, and nonnegative gross flow. All commodities share the edge capacities. The optional cost objective also records Exercise 29.2-7's minimum-cost multicommodity formulation.
This module also records the finite standard-form encoding: one nonnegative
variable per commodity/ordered-pair, a shared capacity inequality per ordered
pair, a conservation equality per internal commodity/vertex, and a demand
equality per commodity, reindexed into a concrete StandardLP.
namespace CLRSnamespace Chapter29open Finsetopen Matrixopen scoped BigOperatorsSource, sink, and nonnegative demand of one commodity.
structure Commodity (V : Type*) where
source : V
sink : V
source_ne_sink : source ≠ sink
demand : ℝ
demand_nonnegative : 0 ≤ demandnamespace MulticommodityFlowLPvariable {V K : Type*} [Fintype V] [Fintype K]Total flow of all commodities on one directed pair.
def aggregate (f : K → V → V → ℝ) (u v : V) : ℝ := ∑ i, f i u vThe CLRS multicommodity feasibility constraints.
def IsFeasible (N : FlowNetwork V) (commodity : K → Commodity V)
(f : K → V → V → ℝ) : Prop :=
(∀ i u v, 0 ≤ f i u v) ∧
(∀ u v, aggregate f u v ≤ N.capacity u v) ∧
(∀ i u, u ≠ (commodity i).source → u ≠ (commodity i).sink →
FlowNetwork.ConservesAt (f i) u) ∧
∀ i, FlowNetwork.netOutflow (f i) (commodity i).source = (commodity i).demandAn expanded statement of the displayed multicommodity LP.
theorem isFeasible_iff (N : FlowNetwork V) (commodity : K → Commodity V)
(f : K → V → V → ℝ) :
IsFeasible N commodity f ↔
(∀ i u v, 0 ≤ f i u v) ∧
(∀ u v, (∑ i, f i u v) ≤ N.capacity u v) ∧
(∀ i u, u ≠ (commodity i).source → u ≠ (commodity i).sink →
FlowNetwork.inflow (f i) u = FlowNetwork.outflow (f i) u) ∧
∀ i, FlowNetwork.outflow (f i) (commodity i).source -
FlowNetwork.inflow (f i) (commodity i).source = (commodity i).demand := by
rflTotal cost of the aggregate multicommodity flow.
def cost (unitCost : V → V → ℝ) (f : K → V → V → ℝ) : ℝ :=
∑ u, ∑ v, unitCost u v * aggregate f u vMinimum-cost feasible multicommodity routing.
def IsMinimumCost (N : FlowNetwork V) (commodity : K → Commodity V)
(unitCost : V → V → ℝ) (f : K → V → V → ℝ) : Prop :=
IsFeasible N commodity f ∧
∀ g, IsFeasible N commodity g → cost unitCost f ≤ cost unitCost gExpanded optimality statement for minimum-cost multicommodity flow.
theorem isMinimumCost_iff (N : FlowNetwork V) (commodity : K → Commodity V)
(unitCost : V → V → ℝ) (f : K → V → V → ℝ) :
IsMinimumCost N commodity unitCost f ↔
IsFeasible N commodity f ∧
∀ g, IsFeasible N commodity g →
(∑ u, ∑ v, unitCost u v * (∑ i, f i u v)) ≤
∑ u, ∑ v, unitCost u v * (∑ i, g i u v) := by
rflFinite standard-form encoding
section Encodingvariable [DecidableEq V] [DecidableEq K]
Lift a commodity-indexed flow to the Fin-indexed vector over
K × (V × V).
noncomputable def lift₃ (f : K → V → V → ℝ) : Fin (Fintype.card (K × (V × V))) → ℝ :=
FinEncoding.lift (fun p : K × (V × V) => f p.1 p.2.1 p.2.2)The internal commodity/vertex pairs at which flow is conserved.
abbrev Internal (N : FlowNetwork V) (commodity : K → Commodity V) :=
{p : K × V // p.2 ≠ (commodity p.1).source ∧ p.2 ≠ (commodity p.1).sink}Sum of an indicator on the commodity and the source coordinate (outflow).
lemma sum_indicator_fst_fst (i : K) (u : V) (f : K → V → V → ℝ) :
(∑ j : Fin (Fintype.card (K × (V × V))),
(if ((Fintype.equivFin (K × (V × V))).symm j).1 = i ∧
((Fintype.equivFin (K × (V × V))).symm j).2.1 = u then 1 else 0) * lift₃ f j) =
∑ b : V, f i u b := by
rw [FinEncoding.sum_reindex]
simp [lift₃, FinEncoding.lift]
rw [Fintype.sum_prod_type]
rw [Finset.sum_eq_single i]
· rw [Fintype.sum_prod_type]
rw [Finset.sum_eq_single u]
· simp
· intro a _ ha; simp [ha]
· intro h; exact False.elim (h (Finset.mem_univ u))
· intro k _ hk; simp [hk]
· intro h; exact False.elim (h (Finset.mem_univ i))Sum of an indicator on the commodity and the target coordinate (inflow).
lemma sum_indicator_fst_snd (i : K) (u : V) (f : K → V → V → ℝ) :
(∑ j : Fin (Fintype.card (K × (V × V))),
(if ((Fintype.equivFin (K × (V × V))).symm j).1 = i ∧
((Fintype.equivFin (K × (V × V))).symm j).2.2 = u then 1 else 0) * lift₃ f j) =
∑ a : V, f i a u := by
rw [FinEncoding.sum_reindex]
simp [lift₃, FinEncoding.lift]
rw [Fintype.sum_prod_type]
rw [Finset.sum_eq_single i]
· rw [Fintype.sum_prod_type]
rw [Finset.sum_comm]
rw [Finset.sum_eq_single u]
· simp
· intro a _ ha; simp [ha]
· intro h; exact False.elim (h (Finset.mem_univ u))
· intro k _ hk; simp [hk]
· intro h; exact False.elim (h (Finset.mem_univ i))Sum of an indicator on the directed pair (shared capacity).
lemma sum_indicator_pair (u v : V) (f : K → V → V → ℝ) :
(∑ j : Fin (Fintype.card (K × (V × V))),
(if ((Fintype.equivFin (K × (V × V))).symm j).2 = (u, v) then 1 else 0) * lift₃ f j) =
∑ i : K, f i u v := by
rw [FinEncoding.sum_reindex]
simp [lift₃, FinEncoding.lift]
rw [Fintype.sum_prod_type]
simp [Finset.sum_ite_eq']The general-form program for the multicommodity-flow LP.
noncomputable def toGeneralLP (N : FlowNetwork V) (commodity : K → Commodity V)
(unitCost : V → V → ℝ) : GeneralLP where
n := Fintype.card (K × (V × V))
m := Fintype.card (V × V) + Fintype.card (Internal N commodity) + Fintype.card K
maximize := false
c := fun j => unitCost ((Fintype.equivFin (K × (V × V))).symm j).2.1 ((Fintype.equivFin (K × (V × V))).symm j).2.2
rel := fun i =>
Fin.addCases (motive := fun _ => ConstraintRel)
(fun ij => Fin.addCases (motive := fun _ => ConstraintRel)
(fun _ => ConstraintRel.le)
(fun _ => ConstraintRel.eq)
ij)
(fun _ => ConstraintRel.eq)
i
A := fun i =>
Fin.addCases (motive := fun _ => Fin (Fintype.card (K × (V × V))) → ℝ)
(fun ij => Fin.addCases (motive := fun _ => Fin (Fintype.card (K × (V × V))) → ℝ)
(fun e j => if ((Fintype.equivFin (K × (V × V))).symm j).2 = (Fintype.equivFin (V × V)).symm e then 1 else 0)
(fun p j =>
let iu := (Fintype.equivFin (Internal N commodity)).symm p
(if ((Fintype.equivFin (K × (V × V))).symm j).1 = iu.1.1 ∧
((Fintype.equivFin (K × (V × V))).symm j).2.1 = iu.1.2 then 1 else 0) -
(if ((Fintype.equivFin (K × (V × V))).symm j).1 = iu.1.1 ∧
((Fintype.equivFin (K × (V × V))).symm j).2.2 = iu.1.2 then 1 else 0))
ij)
(fun i j =>
let ci := (Fintype.equivFin K).symm i
(if ((Fintype.equivFin (K × (V × V))).symm j).1 = ci ∧
((Fintype.equivFin (K × (V × V))).symm j).2.1 = (commodity ci).source then 1 else 0) -
(if ((Fintype.equivFin (K × (V × V))).symm j).1 = ci ∧
((Fintype.equivFin (K × (V × V))).symm j).2.2 = (commodity ci).source then 1 else 0))
i
b := fun i =>
Fin.addCases (motive := fun _ => ℝ)
(fun ij => Fin.addCases (motive := fun _ => ℝ)
(fun e => N.capacity ((Fintype.equivFin (V × V)).symm e).1 ((Fintype.equivFin (V × V)).symm e).2)
(fun _ => 0)
ij)
(fun i => (commodity ((Fintype.equivFin K).symm i)).demand)
i
free := fun _ => falseThe index of the shared-capacity constraint for a directed pair.
abbrev capIndex (N : FlowNetwork V) (commodity : K → Commodity V) (e : Fin (Fintype.card (V × V))) :
Fin (Fintype.card (V × V) + Fintype.card (Internal N commodity) + Fintype.card K) :=
Fin.castAdd (Fintype.card K) (Fin.castAdd (Fintype.card (Internal N commodity)) e)The index of the conservation constraint for an internal commodity/vertex.
abbrev consIndex (N : FlowNetwork V) (commodity : K → Commodity V) (p : Fin (Fintype.card (Internal N commodity))) :
Fin (Fintype.card (V × V) + Fintype.card (Internal N commodity) + Fintype.card K) :=
Fin.castAdd (Fintype.card K) (Fin.natAdd (Fintype.card (V × V)) p)The index of the demand constraint for a commodity.
abbrev demandIndex (N : FlowNetwork V) (commodity : K → Commodity V) (i : Fin (Fintype.card K)) :
Fin (Fintype.card (V × V) + Fintype.card (Internal N commodity) + Fintype.card K) :=
Fin.natAdd (Fintype.card (V × V) + Fintype.card (Internal N commodity)) i
lemma rel_cap (N : FlowNetwork V) (commodity : K → Commodity V) (e : Fin (Fintype.card (V × V))) :
(toGeneralLP N commodity (fun _ _ => 0)).rel (capIndex N commodity e) = ConstraintRel.le := by
dsimp [toGeneralLP, capIndex]
rw [FinEncoding.addCases_castAdd]
rw [FinEncoding.addCases_castAdd]
lemma b_cap (N : FlowNetwork V) (commodity : K → Commodity V) (e : Fin (Fintype.card (V × V))) :
(toGeneralLP N commodity (fun _ _ => 0)).b (capIndex N commodity e) =
N.capacity ((Fintype.equivFin (V × V)).symm e).1 ((Fintype.equivFin (V × V)).symm e).2 := by
dsimp [toGeneralLP, capIndex]
rw [FinEncoding.addCases_castAdd]
rw [FinEncoding.addCases_castAdd]
lemma A_cap (N : FlowNetwork V) (commodity : K → Commodity V) (e : Fin (Fintype.card (V × V)))
(j : Fin (Fintype.card (K × (V × V)))) :
(toGeneralLP N commodity (fun _ _ => 0)).A (capIndex N commodity e) j =
if ((Fintype.equivFin (K × (V × V))).symm j).2 = (Fintype.equivFin (V × V)).symm e then 1 else 0 := by
dsimp [toGeneralLP, capIndex]
rw [FinEncoding.addCases_castAdd]
rw [FinEncoding.addCases_castAdd]
lemma rel_cons (N : FlowNetwork V) (commodity : K → Commodity V) (p : Fin (Fintype.card (Internal N commodity))) :
(toGeneralLP N commodity (fun _ _ => 0)).rel (consIndex N commodity p) = ConstraintRel.eq := by
dsimp [toGeneralLP, consIndex]
rw [FinEncoding.addCases_castAdd]
rw [FinEncoding.addCases_natAdd]
lemma b_cons (N : FlowNetwork V) (commodity : K → Commodity V) (p : Fin (Fintype.card (Internal N commodity))) :
(toGeneralLP N commodity (fun _ _ => 0)).b (consIndex N commodity p) = 0 := by
dsimp [toGeneralLP, consIndex]
rw [FinEncoding.addCases_castAdd]
rw [FinEncoding.addCases_natAdd]
lemma A_cons (N : FlowNetwork V) (commodity : K → Commodity V) (p : Fin (Fintype.card (Internal N commodity)))
(j : Fin (Fintype.card (K × (V × V)))) :
(toGeneralLP N commodity (fun _ _ => 0)).A (consIndex N commodity p) j =
(if ((Fintype.equivFin (K × (V × V))).symm j).1 = ((Fintype.equivFin (Internal N commodity)).symm p).1.1 ∧
((Fintype.equivFin (K × (V × V))).symm j).2.1 = ((Fintype.equivFin (Internal N commodity)).symm p).1.2 then 1 else 0) -
(if ((Fintype.equivFin (K × (V × V))).symm j).1 = ((Fintype.equivFin (Internal N commodity)).symm p).1.1 ∧
((Fintype.equivFin (K × (V × V))).symm j).2.2 = ((Fintype.equivFin (Internal N commodity)).symm p).1.2 then 1 else 0) := by
dsimp [toGeneralLP, consIndex]
rw [FinEncoding.addCases_castAdd]
rw [FinEncoding.addCases_natAdd]
lemma rel_demand (N : FlowNetwork V) (commodity : K → Commodity V) (i : Fin (Fintype.card K)) :
(toGeneralLP N commodity (fun _ _ => 0)).rel (demandIndex N commodity i) = ConstraintRel.eq := by
dsimp [toGeneralLP, demandIndex]
rw [FinEncoding.addCases_natAdd]
lemma b_demand (N : FlowNetwork V) (commodity : K → Commodity V) (i : Fin (Fintype.card K)) :
(toGeneralLP N commodity (fun _ _ => 0)).b (demandIndex N commodity i) =
(commodity ((Fintype.equivFin K).symm i)).demand := by
dsimp [toGeneralLP, demandIndex]
rw [FinEncoding.addCases_natAdd]
lemma A_demand (N : FlowNetwork V) (commodity : K → Commodity V) (i : Fin (Fintype.card K))
(j : Fin (Fintype.card (K × (V × V)))) :
(toGeneralLP N commodity (fun _ _ => 0)).A (demandIndex N commodity i) j =
(if ((Fintype.equivFin (K × (V × V))).symm j).1 = (Fintype.equivFin K).symm i ∧
((Fintype.equivFin (K × (V × V))).symm j).2.1 = (commodity ((Fintype.equivFin K).symm i)).source then 1 else 0) -
(if ((Fintype.equivFin (K × (V × V))).symm j).1 = (Fintype.equivFin K).symm i ∧
((Fintype.equivFin (K × (V × V))).symm j).2.2 = (commodity ((Fintype.equivFin K).symm i)).source then 1 else 0) := by
dsimp [toGeneralLP, demandIndex]
rw [FinEncoding.addCases_natAdd]A capacity row reads out the aggregate flow on its directed pair.
lemma mulVec_cap (N : FlowNetwork V) (commodity : K → Commodity V) (e : Fin (Fintype.card (V × V)))
(f : K → V → V → ℝ) :
((toGeneralLP N commodity (fun _ _ => 0)).A *ᵥ lift₃ f) (capIndex N commodity e) =
aggregate f ((Fintype.equivFin (V × V)).symm e).1 ((Fintype.equivFin (V × V)).symm e).2 := by
calc
((toGeneralLP N commodity (fun _ _ => 0)).A *ᵥ lift₃ f) (capIndex N commodity e)
= ∑ j : Fin (Fintype.card (K × (V × V))), (toGeneralLP N commodity (fun _ _ => 0)).A (capIndex N commodity e) j * lift₃ f j := by rfl
_ = ∑ j : Fin (Fintype.card (K × (V × V))),
(if ((Fintype.equivFin (K × (V × V))).symm j).2 = (Fintype.equivFin (V × V)).symm e then 1 else 0) * lift₃ f j := by
simp only [A_cap]
_ = ∑ i : K, f i ((Fintype.equivFin (V × V)).symm e).1 ((Fintype.equivFin (V × V)).symm e).2 := by
simpa using (sum_indicator_pair ((Fintype.equivFin (V × V)).symm e).1 ((Fintype.equivFin (V × V)).symm e).2 f)
_ = aggregate f ((Fintype.equivFin (V × V)).symm e).1 ((Fintype.equivFin (V × V)).symm e).2 := rflA conservation row reads out the net outflow of one commodity at its internal vertex.
lemma mulVec_cons (N : FlowNetwork V) (commodity : K → Commodity V) (p : Fin (Fintype.card (Internal N commodity)))
(f : K → V → V → ℝ) :
((toGeneralLP N commodity (fun _ _ => 0)).A *ᵥ lift₃ f) (consIndex N commodity p) =
FlowNetwork.outflow (f ((Fintype.equivFin (Internal N commodity)).symm p).1.1) ((Fintype.equivFin (Internal N commodity)).symm p).1.2 -
FlowNetwork.inflow (f ((Fintype.equivFin (Internal N commodity)).symm p).1.1) ((Fintype.equivFin (Internal N commodity)).symm p).1.2 := by
calc
((toGeneralLP N commodity (fun _ _ => 0)).A *ᵥ lift₃ f) (consIndex N commodity p)
= ∑ j : Fin (Fintype.card (K × (V × V))), (toGeneralLP N commodity (fun _ _ => 0)).A (consIndex N commodity p) j * lift₃ f j := by rfl
_ = ∑ j : Fin (Fintype.card (K × (V × V))),
((if ((Fintype.equivFin (K × (V × V))).symm j).1 = ((Fintype.equivFin (Internal N commodity)).symm p).1.1 ∧
((Fintype.equivFin (K × (V × V))).symm j).2.1 = ((Fintype.equivFin (Internal N commodity)).symm p).1.2 then 1 else 0) -
(if ((Fintype.equivFin (K × (V × V))).symm j).1 = ((Fintype.equivFin (Internal N commodity)).symm p).1.1 ∧
((Fintype.equivFin (K × (V × V))).symm j).2.2 = ((Fintype.equivFin (Internal N commodity)).symm p).1.2 then 1 else 0)) *
lift₃ f j := by
simp only [A_cons]
_ = (∑ j : Fin (Fintype.card (K × (V × V))),
(if ((Fintype.equivFin (K × (V × V))).symm j).1 = ((Fintype.equivFin (Internal N commodity)).symm p).1.1 ∧
((Fintype.equivFin (K × (V × V))).symm j).2.1 = ((Fintype.equivFin (Internal N commodity)).symm p).1.2 then 1 else 0) * lift₃ f j) -
(∑ j : Fin (Fintype.card (K × (V × V))),
(if ((Fintype.equivFin (K × (V × V))).symm j).1 = ((Fintype.equivFin (Internal N commodity)).symm p).1.1 ∧
((Fintype.equivFin (K × (V × V))).symm j).2.2 = ((Fintype.equivFin (Internal N commodity)).symm p).1.2 then 1 else 0) * lift₃ f j) := by
simp only [sub_mul, Finset.sum_sub_distrib]
_ = (∑ b : V, f ((Fintype.equivFin (Internal N commodity)).symm p).1.1 ((Fintype.equivFin (Internal N commodity)).symm p).1.2 b) -
(∑ a : V, f ((Fintype.equivFin (Internal N commodity)).symm p).1.1 a ((Fintype.equivFin (Internal N commodity)).symm p).1.2) := by
rw [sum_indicator_fst_fst, sum_indicator_fst_snd]
_ = FlowNetwork.outflow (f ((Fintype.equivFin (Internal N commodity)).symm p).1.1) ((Fintype.equivFin (Internal N commodity)).symm p).1.2 -
FlowNetwork.inflow (f ((Fintype.equivFin (Internal N commodity)).symm p).1.1) ((Fintype.equivFin (Internal N commodity)).symm p).1.2 := rflA demand row reads out the net outflow of one commodity at its source.
lemma mulVec_demand (N : FlowNetwork V) (commodity : K → Commodity V) (i : Fin (Fintype.card K))
(f : K → V → V → ℝ) :
((toGeneralLP N commodity (fun _ _ => 0)).A *ᵥ lift₃ f) (demandIndex N commodity i) =
FlowNetwork.netOutflow (f ((Fintype.equivFin K).symm i)) (commodity ((Fintype.equivFin K).symm i)).source := by
calc
((toGeneralLP N commodity (fun _ _ => 0)).A *ᵥ lift₃ f) (demandIndex N commodity i)
= ∑ j : Fin (Fintype.card (K × (V × V))), (toGeneralLP N commodity (fun _ _ => 0)).A (demandIndex N commodity i) j * lift₃ f j := by rfl
_ = ∑ j : Fin (Fintype.card (K × (V × V))),
((if ((Fintype.equivFin (K × (V × V))).symm j).1 = (Fintype.equivFin K).symm i ∧
((Fintype.equivFin (K × (V × V))).symm j).2.1 = (commodity ((Fintype.equivFin K).symm i)).source then 1 else 0) -
(if ((Fintype.equivFin (K × (V × V))).symm j).1 = (Fintype.equivFin K).symm i ∧
((Fintype.equivFin (K × (V × V))).symm j).2.2 = (commodity ((Fintype.equivFin K).symm i)).source then 1 else 0)) *
lift₃ f j := by
simp only [A_demand]
_ = (∑ j : Fin (Fintype.card (K × (V × V))),
(if ((Fintype.equivFin (K × (V × V))).symm j).1 = (Fintype.equivFin K).symm i ∧
((Fintype.equivFin (K × (V × V))).symm j).2.1 = (commodity ((Fintype.equivFin K).symm i)).source then 1 else 0) * lift₃ f j) -
(∑ j : Fin (Fintype.card (K × (V × V))),
(if ((Fintype.equivFin (K × (V × V))).symm j).1 = (Fintype.equivFin K).symm i ∧
((Fintype.equivFin (K × (V × V))).symm j).2.2 = (commodity ((Fintype.equivFin K).symm i)).source then 1 else 0) * lift₃ f j) := by
simp only [sub_mul, Finset.sum_sub_distrib]
_ = (∑ b : V, f ((Fintype.equivFin K).symm i) (commodity ((Fintype.equivFin K).symm i)).source b) -
(∑ a : V, f ((Fintype.equivFin K).symm i) a (commodity ((Fintype.equivFin K).symm i)).source) := by
rw [sum_indicator_fst_fst, sum_indicator_fst_snd]
_ = FlowNetwork.netOutflow (f ((Fintype.equivFin K).symm i)) (commodity ((Fintype.equivFin K).symm i)).source := rflSemantic feasibility is exactly encoded general-form feasibility.
theorem feasible_iff_generalLP (N : FlowNetwork V) (commodity : K → Commodity V)
(f : K → V → V → ℝ) :
IsFeasible N commodity f ↔ (toGeneralLP N commodity (fun _ _ => 0)).IsFeasible (lift₃ f) := by
unfold IsFeasible GeneralLP.IsFeasible
constructor
· intro hd
refine ⟨?_, ?_⟩
· intro j hfree
simpa [lift₃, FinEncoding.lift] using
(hd.1 ((Fintype.equivFin (K × (V × V))).symm j).1
((Fintype.equivFin (K × (V × V))).symm j).2.1
((Fintype.equivFin (K × (V × V))).symm j).2.2)
· intro i
exact Fin.addCases (motive := fun i => match (toGeneralLP N commodity (fun _ _ => 0)).rel i with
| ConstraintRel.le => ((toGeneralLP N commodity (fun _ _ => 0)).A *ᵥ lift₃ f) i ≤ (toGeneralLP N commodity (fun _ _ => 0)).b i
| ConstraintRel.eq => ((toGeneralLP N commodity (fun _ _ => 0)).A *ᵥ lift₃ f) i = (toGeneralLP N commodity (fun _ _ => 0)).b i
| ConstraintRel.ge => (toGeneralLP N commodity (fun _ _ => 0)).b i ≤ ((toGeneralLP N commodity (fun _ _ => 0)).A *ᵥ lift₃ f) i)
(fun ij =>
Fin.addCases (motive := fun ij => match (toGeneralLP N commodity (fun _ _ => 0)).rel (Fin.castAdd (Fintype.card K) ij) with
| ConstraintRel.le => ((toGeneralLP N commodity (fun _ _ => 0)).A *ᵥ lift₃ f) (Fin.castAdd (Fintype.card K) ij) ≤ (toGeneralLP N commodity (fun _ _ => 0)).b (Fin.castAdd (Fintype.card K) ij)
| ConstraintRel.eq => ((toGeneralLP N commodity (fun _ _ => 0)).A *ᵥ lift₃ f) (Fin.castAdd (Fintype.card K) ij) = (toGeneralLP N commodity (fun _ _ => 0)).b (Fin.castAdd (Fintype.card K) ij)
| ConstraintRel.ge => (toGeneralLP N commodity (fun _ _ => 0)).b (Fin.castAdd (Fintype.card K) ij) ≤ ((toGeneralLP N commodity (fun _ _ => 0)).A *ᵥ lift₃ f) (Fin.castAdd (Fintype.card K) ij))
(fun e => by
rw [rel_cap, mulVec_cap, b_cap]
simpa [aggregate] using
(hd.2.1 ((Fintype.equivFin (V × V)).symm e).1 ((Fintype.equivFin (V × V)).symm e).2))
(fun p => by
rw [rel_cons, mulVec_cons, b_cons]
have hc := hd.2.2.1 ((Fintype.equivFin (Internal N commodity)).symm p).1.1
((Fintype.equivFin (Internal N commodity)).symm p).1.2
((Fintype.equivFin (Internal N commodity)).symm p).2.1
((Fintype.equivFin (Internal N commodity)).symm p).2.2
unfold FlowNetwork.ConservesAt at hc
linarith)
ij)
(fun i => by
rw [rel_demand, mulVec_demand, b_demand]
exact hd.2.2.2 ((Fintype.equivFin K).symm i))
i
· intro h
refine ⟨?_, ?_, ?_, ?_⟩
· intro i u v
have hn := h.1 (Fintype.equivFin (K × (V × V)) (i, (u, v)))
simpa [lift₃, FinEncoding.lift] using (hn (by simp [toGeneralLP]))
· intro u v
have hc := h.2 (capIndex N commodity (Fintype.equivFin (V × V) (u, v)))
rw [rel_cap] at hc
rw [mulVec_cap, b_cap] at hc
simpa [aggregate] using hc
· intro i u hu hw
let e : Internal N commodity := ⟨(i, u), ⟨hu, hw⟩⟩
have hcons := h.2 (consIndex N commodity (Fintype.equivFin (Internal N commodity) e))
rw [rel_cons] at hcons
rw [mulVec_cons, b_cons] at hcons
have hcons' : FlowNetwork.outflow (f i) u - FlowNetwork.inflow (f i) u = 0 := by
simpa [e] using hcons
unfold FlowNetwork.ConservesAt
linarith
· intro i
have hdemand := h.2 (demandIndex N commodity (Fintype.equivFin K i))
rw [rel_demand] at hdemand
rw [mulVec_demand, b_demand] at hdemand
simpa using hdemandThe general-form objective is the aggregate minimum cost.
lemma objective_generalLP (N : FlowNetwork V) (commodity : K → Commodity V)
(unitCost : V → V → ℝ) (f : K → V → V → ℝ) :
(toGeneralLP N commodity unitCost).objective (lift₃ f) = cost unitCost f := by
dsimp [GeneralLP.objective, toGeneralLP, cost, aggregate]
simp only [dotProduct]
rw [FinEncoding.sum_reindex]
simp [lift₃, FinEncoding.lift]
rw [Fintype.sum_prod_type]
rw [Finset.sum_comm]
rw [Fintype.sum_prod_type]
simp [Finset.mul_sum]The finite standard-form program obtained by normalizing the multicommodity general program.
noncomputable def toStandardLP (N : FlowNetwork V) (commodity : K → Commodity V)
(unitCost : V → V → ℝ) :=
(toGeneralLP N commodity unitCost).toStandardLPThe expansion of a commodity flow to the normalized standard-form variable vector.
noncomputable def fullLift (N : FlowNetwork V) (commodity : K → Commodity V)
(unitCost : V → V → ℝ) (f : K → V → V → ℝ) :
Fin (Fintype.card (K × (V × V)) + Fintype.card (K × (V × V))) → ℝ :=
(toGeneralLP N commodity unitCost).lift (lift₃ f)The normalized standard-form objective equals the signed aggregate cost.
theorem objective_toStandardLP (N : FlowNetwork V) (commodity : K → Commodity V)
(unitCost : V → V → ℝ) (f : K → V → V → ℝ) :
(toStandardLP N commodity unitCost).objective (fullLift N commodity unitCost f) = -cost unitCost f := by
unfold toStandardLP fullLift
rw [GeneralLP.objective_lift]
rw [objective_generalLP]
simp [toGeneralLP, GeneralLP.objectiveSign]Feasibility is exactly preserved under the finite standard-form encoding.
theorem feasible_iff_toStandardLP (N : FlowNetwork V) (commodity : K → Commodity V)
(f : K → V → V → ℝ) :
IsFeasible N commodity f ↔ (toStandardLP N commodity (fun _ _ => 0)).IsFeasible (fullLift N commodity (fun _ _ => 0) f) := by
unfold toStandardLP fullLift
rw [← GeneralLP.feasible_iff_lift]
exact feasible_iff_generalLP N commodity fend Encodingend MulticommodityFlowLPend Chapter29end CLRSImports
import CLRSLean.FourthEdition.Chapter_29.Section_29_3_Duality.Definitions
import CLRSLean.FourthEdition.Chapter_29.Section_29_3_Duality.WeakDuality
import CLRSLean.FourthEdition.Chapter_29.Section_29_3_Duality.Optimality
import CLRSLean.FourthEdition.Chapter_29.Section_29_3_Duality.ComplementarySlackness
import CLRSLean.FourthEdition.Chapter_29.Section_29_3_Duality.TerminalCertificate
import CLRSLean.FourthEdition.Chapter_29.Section_29_3_Duality.DictionaryBridge
import CLRSLean.FourthEdition.Chapter_29.Section_29_3_Duality.StrongDuality
import CLRSLean.FourthEdition.Chapter_29.Section_29_3_Duality.ComplementarySlacknessTheorem29.3. Duality
The represented layer proves weak duality, extracts a dual certificate from a terminal SIMPLEX dictionary, derives strong duality, and proves both directions of complementary slackness.
Implementation details
The split proof layers remain available outside the main sidebar:
namespace CLRSnamespace Chapter29end Chapter29end CLRSDefinitions and proofs
CLRSLean.FourthEdition.Chapter_29.Section_29_3_Duality.Definitions
29.3 Dual linear programs
For a primal maximization program max cᵀx with Ax ≤ b and
x ≥ 0, a dual assignment satisfies y ≥ 0 and
Aᵀy ≥ c; its objective is bᵀy.
Main declarations:
-
StandardLP.IsDualFeasible. -
StandardLP.dualObjective.
Downstream layers:
-
Weak duality is proved in the next module.
-
Strong duality and complementary slackness are proved by the later Section 29.3 modules together with the initialized solver (online material).
namespace CLRSnamespace Chapter29open Matrixnamespace StandardLP
A nonnegative vector satisfying c ≤ Aᵀy is dual feasible.
def IsDualFeasible {m n : ℕ} (P : StandardLP m n) (y : Fin m → ℝ) : Prop :=
IsNonnegative y ∧ ∀ j, P.c j ≤ (P.A.transpose *ᵥ y) j
The dual objective value bᵀy.
def dualObjective {m n : ℕ} (P : StandardLP m n) (y : Fin m → ℝ) : ℝ :=
P.b ⬝ᵥ ynamespace IsDualFeasibleA dual-feasible assignment is coordinatewise nonnegative.
theorem nonnegative {m n : ℕ} {P : StandardLP m n} {y : Fin m → ℝ}
(hy : P.IsDualFeasible y) : IsNonnegative y :=
hy.1
A dual-feasible assignment bounds each primal objective coefficient by
the corresponding coordinate of Aᵀy.
theorem coefficient_le {m n : ℕ} {P : StandardLP m n} {y : Fin m → ℝ}
(hy : P.IsDualFeasible y) :
∀ j, P.c j ≤ (P.A.transpose *ᵥ y) j :=
hy.2end IsDualFeasibleend StandardLPend Chapter29end CLRSCLRSLean.FourthEdition.Chapter_29.Section_29_3_Duality.WeakDuality
29.3 Weak duality
This module proves CLRS Theorem 29.8. For every primal-feasible x and
dual-feasible y, the primal objective is bounded by the dual objective:
cᵀx ≤ bᵀy.
The proof follows the CLRS calculation
cᵀx ≤ (Aᵀy)ᵀx = yᵀAx ≤ yᵀb.
Main result:
-
StandardLP.weak_duality: CLRS Theorem 29.8.
Downstream layers:
-
Later Section 29.3 modules prove strong duality (Theorem 29.9) and complementary slackness (Theorem 29.10).
namespace CLRSnamespace Chapter29open Matrixnamespace StandardLPIncreasing the left factor of a dot product preserves order when the right factor is coordinatewise nonnegative.
theorem dotProduct_mono_right_of_nonnegative {n : ℕ}
{a b x : Fin n → ℝ} (hab : ∀ i, a i ≤ b i) (hx : IsNonnegative x) :
a ⬝ᵥ x ≤ b ⬝ᵥ x := by
simp only [dotProduct]
exact Finset.sum_le_sum fun i _ =>
mul_le_mul_of_nonneg_right (hab i) (hx i)Increasing the right factor of a dot product preserves order when the left factor is coordinatewise nonnegative.
theorem dotProduct_mono_left_of_nonnegative {n : ℕ}
{a b y : Fin n → ℝ} (hab : ∀ i, a i ≤ b i) (hy : IsNonnegative y) :
y ⬝ᵥ a ≤ y ⬝ᵥ b := by
simp only [dotProduct]
exact Finset.sum_le_sum fun i _ =>
mul_le_mul_of_nonneg_left (hab i) (hy i)
Moving a matrix transpose across a finite dot product exchanges the order
of summation: (Aᵀy)ᵀx = yᵀ(Ax).
theorem transpose_mulVec_dotProduct {m n : ℕ}
(A : Matrix (Fin m) (Fin n) ℝ) (y : Fin m → ℝ) (x : Fin n → ℝ) :
(A.transpose *ᵥ y) ⬝ᵥ x = y ⬝ᵥ (A *ᵥ x) := by
simp only [dotProduct, Matrix.mulVec, Matrix.transpose_apply]
simp_rw [Finset.sum_mul, Finset.mul_sum]
conv_lhs => rw [Finset.sum_comm]
apply Finset.sum_congr rfl
intro i _
apply Finset.sum_congr rfl
intro j _
ringCLRS Theorem 29.8 (weak duality). Every primal-feasible objective value is at most every dual-feasible objective value.
theorem weak_duality {m n : ℕ} (P : StandardLP m n)
{x : Fin n → ℝ} {y : Fin m → ℝ}
(hx : P.IsFeasible x) (hy : P.IsDualFeasible y) :
P.objective x ≤ P.dualObjective y := by
calc
P.objective x = P.c ⬝ᵥ x := rfl
_ ≤ (P.A.transpose *ᵥ y) ⬝ᵥ x :=
dotProduct_mono_right_of_nonnegative hy.coefficient_le hx.1
_ = y ⬝ᵥ (P.A *ᵥ x) := transpose_mulVec_dotProduct P.A y x
_ ≤ y ⬝ᵥ P.b :=
dotProduct_mono_left_of_nonnegative hx.2 hy.nonnegative
_ = P.dualObjective y := by
simp only [dualObjective, dotProduct]
apply Finset.sum_congr rfl
intro i _
ringend StandardLPend Chapter29end CLRSCLRSLean.FourthEdition.Chapter_29.Section_29_3_Duality.Optimality
29.3 Primal and dual optimality specifications
These predicates state the terminal contracts used by strong duality, complementary slackness, and the full initialized SIMPLEX solver.
namespace CLRSnamespace Chapter29namespace StandardLPA feasible primal assignment dominating every other feasible assignment.
def IsOptimal (P : StandardLP m n) (x : Fin n → ℝ) : Prop :=
P.IsFeasible x ∧ ∀ z, P.IsFeasible z → P.objective z ≤ P.objective xA feasible dual assignment no worse than every other dual assignment.
def IsDualOptimal (P : StandardLP m n) (y : Fin m → ℝ) : Prop :=
P.IsDualFeasible y ∧
∀ z, P.IsDualFeasible z → P.dualObjective y ≤ P.dualObjective zThe primal objective exceeds every real bound on feasible assignments.
def IsUnbounded (P : StandardLP m n) : Prop :=
∀ M : ℝ, ∃ x, P.IsFeasible x ∧ M < P.objective xend StandardLPend Chapter29end CLRSCLRSLean.FourthEdition.Chapter_29.Section_29_3_Duality.ComplementarySlackness
29.3 Complementary slackness
The duality gap splits exactly into primal-slack and dual-slack products. For feasible assignments all products are nonnegative, so equality of the two objectives is equivalent to the textbook complementary-slackness equations.
namespace CLRSnamespace Chapter29open Matrixopen scoped BigOperatorsnamespace StandardLP
Slack in primal constraint row i.
def primalSlack (P : StandardLP m n) (x : Fin n → ℝ) (i : Fin m) : ℝ :=
P.b i - (P.A *ᵥ x) i
Slack in dual constraint column j.
def dualSlack (P : StandardLP m n) (y : Fin m → ℝ) (j : Fin n) : ℝ :=
(P.A.transpose *ᵥ y) j - P.c jThe textbook complementary-slackness equations.
def ComplementarySlackness (P : StandardLP m n)
(x : Fin n → ℝ) (y : Fin m → ℝ) : Prop :=
(∀ i, y i * P.primalSlack x i = 0) ∧
∀ j, x j * P.dualSlack y j = 0Exact decomposition of the duality gap into complementary-slackness products.
theorem dualityGap_eq_slackSums (P : StandardLP m n)
(x : Fin n → ℝ) (y : Fin m → ℝ) :
P.dualObjective y - P.objective x =
(∑ i, y i * P.primalSlack x i) +
∑ j, x j * P.dualSlack y j := by
simp only [dualObjective, objective, primalSlack, dualSlack]
simp_rw [mul_sub]
rw [Finset.sum_sub_distrib, Finset.sum_sub_distrib]
change P.b ⬝ᵥ y - P.c ⬝ᵥ x =
(y ⬝ᵥ P.b - y ⬝ᵥ (P.A *ᵥ x)) +
(x ⬝ᵥ (P.A.transpose *ᵥ y) - x ⬝ᵥ P.c)
rw [dotProduct_comm P.b y, dotProduct_comm x (P.A.transpose *ᵥ y),
dotProduct_comm x P.c, transpose_mulVec_dotProduct]
ringFor feasible primal and dual assignments, complementary slackness holds exactly when their objective values agree.
theorem complementarySlackness_iff_objective_eq (P : StandardLP m n)
{x : Fin n → ℝ} {y : Fin m → ℝ}
(hx : P.IsFeasible x) (hy : P.IsDualFeasible y) :
P.ComplementarySlackness x y ↔
P.objective x = P.dualObjective y := by
have hpnonneg : ∀ i, 0 ≤ y i * P.primalSlack x i := by
intro i
exact mul_nonneg (hy.1 i) (sub_nonneg.mpr (hx.2 i))
have hdnonneg : ∀ j, 0 ≤ x j * P.dualSlack y j := by
intro j
exact mul_nonneg (hx.1 j) (sub_nonneg.mpr (hy.2 j))
constructor
· rintro ⟨hp, hd⟩
have hpsum : (∑ i, y i * P.primalSlack x i) = 0 := by
apply Finset.sum_eq_zero
intro i _
exact hp i
have hdsum : (∑ j, x j * P.dualSlack y j) = 0 := by
apply Finset.sum_eq_zero
intro j _
exact hd j
have hgap := P.dualityGap_eq_slackSums x y
rw [hpsum, hdsum, add_zero] at hgap
linarith
· intro hobj
have hgap := P.dualityGap_eq_slackSums x y
have htotal :
(∑ i, y i * P.primalSlack x i) +
∑ j, x j * P.dualSlack y j = 0 := by
linarith
have hpsum_nonneg : 0 ≤ ∑ i, y i * P.primalSlack x i :=
Finset.sum_nonneg fun i _ => hpnonneg i
have hdsum_nonneg : 0 ≤ ∑ j, x j * P.dualSlack y j :=
Finset.sum_nonneg fun j _ => hdnonneg j
have hpsum : (∑ i, y i * P.primalSlack x i) = 0 := by
linarith
have hdsum : (∑ j, x j * P.dualSlack y j) = 0 := by
linarith
have hpzero : (fun i => y i * P.primalSlack x i) = 0 :=
(Fintype.sum_eq_zero_iff_of_nonneg hpnonneg).mp hpsum
have hdzero : (fun j => x j * P.dualSlack y j) = 0 :=
(Fintype.sum_eq_zero_iff_of_nonneg hdnonneg).mp hdsum
exact ⟨fun i => congrFun hpzero i, fun j => congrFun hdzero j⟩Complementary slackness certifies primal optimality.
theorem optimal_of_complementarySlackness (P : StandardLP m n)
{x : Fin n → ℝ} {y : Fin m → ℝ}
(hx : P.IsFeasible x) (hy : P.IsDualFeasible y)
(hcs : P.ComplementarySlackness x y) : P.IsOptimal x := by
have heq := (P.complementarySlackness_iff_objective_eq hx hy).1 hcs
refine ⟨hx, ?_⟩
intro z hz
calc
P.objective z ≤ P.dualObjective y := P.weak_duality hz hy
_ = P.objective x := heq.symmComplementary slackness certifies dual optimality.
theorem dualOptimal_of_complementarySlackness (P : StandardLP m n)
{x : Fin n → ℝ} {y : Fin m → ℝ}
(hx : P.IsFeasible x) (hy : P.IsDualFeasible y)
(hcs : P.ComplementarySlackness x y) : P.IsDualOptimal y := by
have heq := (P.complementarySlackness_iff_objective_eq hx hy).1 hcs
refine ⟨hy, ?_⟩
intro z hz
calc
P.dualObjective y = P.objective x := heq.symm
_ ≤ P.dualObjective z := P.weak_duality hx hzend StandardLPend Chapter29end CLRSCLRSLean.FourthEdition.Chapter_29.Section_29_3_Duality.TerminalCertificate
29.3 Dual certificates from terminal dictionaries
When every reduced cost is nonpositive, the negated objective coefficients of the original slack variables are the textbook dual variables. Dictionary equivalence proves both dual feasibility and equality of objective values.
namespace CLRSnamespace Chapter29open Matrixopen scoped BigOperatorsnamespace DictionaryNonpositive reduced costs make the stable objective coefficient of every variable nonpositive; basic variables have coefficient zero.
theorem objectiveCoeff_nonpos_of_reducedCosts (D : Dictionary m n)
(hc : ∀ j, D.c j ≤ 0) (q : LPVar m n) :
D.objectiveCoeff q ≤ 0 := by
rcases D.exists_basic_or_nonbasic q with ⟨i, rfl⟩ | ⟨j, rfl⟩
· simp
· simpa using hc jThe shadow-price vector read from a terminal dictionary.
def dualCertificate (D : Dictionary m n) : Fin m → ℝ :=
fun i => -D.objectiveCoeff (.inr i)The shadow prices of a terminal dictionary form a feasible solution of the dual of the original standard-form program.
theorem dualCertificate_isDualFeasible (P : StandardLP m n)
(D : Dictionary m n) (hEq : P.initialDictionary.Equivalent D)
(hc : ∀ j, D.c j ≤ 0) :
P.IsDualFeasible D.dualCertificate := by
constructor
· intro i
exact neg_nonneg.mpr
(D.objectiveCoeff_nonpos_of_reducedCosts hc (.inr i))
· intro j
have hid := hEq.entering_coefficient_identity j
have horiginal : D.objectiveCoeff (.inl j) ≤ 0 :=
D.objectiveCoeff_nonpos_of_reducedCosts hc (.inl j)
have hsum :
(P.A.transpose *ᵥ D.dualCertificate) j =
-(∑ i, D.objectiveCoeff (.inr i) * P.A i j) := by
change (∑ i, P.A i j * (-D.objectiveCoeff (.inr i))) = _
rw [← Finset.sum_neg_distrib]
apply Finset.sum_congr rfl
intro i _
ring
change P.c j = D.objectiveCoeff (.inl j) -
∑ i, D.objectiveCoeff (.inr i) * P.A i j at hid
rw [hsum]
linarithThe objective of the terminal dual certificate equals the terminal dictionary's objective constant.
theorem dualCertificate_objective_eq_v (P : StandardLP m n)
(D : Dictionary m n) (hEq : P.initialDictionary.Equivalent D) :
P.dualObjective D.dualCertificate = D.v := by
let D₀ := P.initialDictionary
have hobj := hEq.2 D₀.basicAssignment D₀.basicAssignment_satisfies
have hsum :
(∑ q, D.objectiveCoeff q * D₀.basicAssignment q) =
∑ i, D.objectiveCoeff (.inr i) * P.b i := by
rw [D₀.sum_eq_sum_labels, Fintype.sum_sum_type]
change
(∑ i, D.objectiveCoeff (D₀.basicVar i) *
D₀.basicAssignment (D₀.basicVar i)) +
(∑ j, D.objectiveCoeff (D₀.nonbasicVar j) *
D₀.basicAssignment (D₀.nonbasicVar j)) = _
simp only [Dictionary.basicAssignment_basicVar,
Dictionary.basicAssignment_nonbasicVar, mul_zero,
Finset.sum_const_zero, add_zero]
change (∑ i, D.objectiveCoeff (.inr i) * P.b i) = _
rfl
have hzero :
0 = D.v + ∑ i, D.objectiveCoeff (.inr i) * P.b i := by
calc
0 = D₀.objectiveRhs D₀.basicAssignment := by
rw [D₀.objectiveRhs_basicAssignment]
rfl
_ = D.objectiveRhs D₀.basicAssignment := hobj
_ = D.v + ∑ q, D.objectiveCoeff q * D₀.basicAssignment q :=
D.objectiveRhs_eq_fullSum D₀.basicAssignment
_ = D.v + ∑ i, D.objectiveCoeff (.inr i) * P.b i := by
rw [hsum]
change (∑ i, P.b i * (-D.objectiveCoeff (.inr i))) = D.v
have hneg :
(∑ i, P.b i * (-D.objectiveCoeff (.inr i))) =
-(∑ i, D.objectiveCoeff (.inr i) * P.b i) := by
rw [← Finset.sum_neg_distrib]
apply Finset.sum_congr rfl
intro i _
ring
rw [hneg]
linarithend Dictionaryend Chapter29end CLRSCLRSLean.FourthEdition.Chapter_29.Section_29_3_Duality.DictionaryBridge
29.3 Bridge from dictionary assignments to the primal program
The initial dictionary uses one stable assignment containing both original and slack variables. This module projects that assignment back to the standard-form primal variables and transports optimality and unboundedness.
namespace CLRSnamespace Chapter29namespace DictionaryOriginal-variable coordinates of a complete dictionary assignment.
def assignmentOriginal (z : LPVar m n → ℝ) : Fin n → ℝ :=
fun j => z (.inl j)Slack-variable coordinates of a complete dictionary assignment.
def assignmentSlack (z : LPVar m n → ℝ) : Fin m → ℝ :=
fun i => z (.inr i)@[simp] theorem assignmentOriginal_combinedAssignment
(x : Fin n → ℝ) (s : Fin m → ℝ) :
assignmentOriginal (StandardLP.combinedAssignment x s) = x := by
funext j
rfl@[simp] theorem assignmentSlack_combinedAssignment
(x : Fin n → ℝ) (s : Fin m → ℝ) :
assignmentSlack (StandardLP.combinedAssignment x s) = s := by
funext i
rflSplitting and recombining a stable assignment is the identity.
theorem combinedAssignment_parts (z : LPVar m n → ℝ) :
StandardLP.combinedAssignment (assignmentOriginal z)
(assignmentSlack z) = z := by
funext q
cases q <;> rflA nonnegative assignment satisfying the initial dictionary projects to a feasible assignment of the original standard-form program.
theorem original_feasible_of_initialDictionary (P : StandardLP m n)
{z : LPVar m n → ℝ} (hznonneg : IsNonnegativeAssignment z)
(hzsat : P.initialDictionary.Satisfies z) :
P.IsFeasible (assignmentOriginal z) := by
apply StandardLP.feasible_of_slackExtension
refine ⟨fun j => hznonneg (.inl j), fun i => hznonneg (.inr i), ?_⟩
apply (P.initialDictionary_satisfies_iff
(assignmentOriginal z) (assignmentSlack z)).1
rw [combinedAssignment_parts]
exact hzsatOn every complete assignment, the initial dictionary objective is the standard-form objective of its original-variable projection.
theorem initialDictionary_objectiveRhs_eq_objective_original
(P : StandardLP m n) (z : LPVar m n → ℝ) :
P.initialDictionary.objectiveRhs z =
P.objective (assignmentOriginal z) := by
rw [← combinedAssignment_parts z]
exact P.initialDictionary_objectiveRhs _ _Dictionary optimality for the initial representation yields primal optimality for the original standard-form program.
theorem initialDictionary_optimal_to_standardLP (P : StandardLP m n)
{z : LPVar m n → ℝ}
(hz : P.initialDictionary.IsOptimalAssignment z) :
P.IsOptimal (assignmentOriginal z) := by
refine ⟨original_feasible_of_initialDictionary P hz.1 hz.2.1, ?_⟩
intro x hx
let w := StandardLP.combinedAssignment x (P.slack x)
have hwext := P.slackExtension_of_feasible hx
have hwnonneg : IsNonnegativeAssignment w :=
(StandardLP.combinedAssignment_nonnegative_iff x (P.slack x)).2
⟨hwext.1, hwext.2.1⟩
have hwsat : P.initialDictionary.Satisfies w :=
P.initialDictionary_satisfies_of_slackExtension hwext
have hle := hz.2.2 w hwnonneg hwsat
calc
P.objective x = P.initialDictionary.objectiveRhs w := by
exact (P.initialDictionary_objectiveRhs x (P.slack x)).symm
_ ≤ P.initialDictionary.objectiveRhs z := hle
_ = P.objective (assignmentOriginal z) :=
initialDictionary_objectiveRhs_eq_objective_original P zDictionary unboundedness for the initial representation yields unboundedness of the original standard-form program.
theorem initialDictionary_unbounded_to_standardLP (P : StandardLP m n)
(h : P.initialDictionary.IsUnbounded) : P.IsUnbounded := by
intro M
obtain ⟨z, hznonneg, hzsat, hzobj⟩ := h M
refine ⟨assignmentOriginal z,
original_feasible_of_initialDictionary P hznonneg hzsat, ?_⟩
rw [← initialDictionary_objectiveRhs_eq_objective_original P z]
exact hzobjend Dictionaryend Chapter29end CLRSCLRSLean.FourthEdition.Chapter_29.Section_29_3_Duality.StrongDuality
29.3 Strong duality from finite SIMPLEX
For an initially basic-feasible standard-form program, finite Bland-SIMPLEX either produces an unbounded primal ray or a terminal dictionary. In the terminal case its basic assignment and shadow prices are primal/dual optimal and have the same objective value.
namespace CLRSnamespace Chapter29namespace StandardLPStrong duality from any basic-feasible dictionary equivalent to the program's initial dictionary.
theorem strongDuality_or_unbounded_of_equivalent_isBasicFeasible
(P : StandardLP m n) (D : Dictionary m n)
(hEq₀ : P.initialDictionary.Equivalent D)
(hD : D.IsBasicFeasible) :
P.IsUnbounded ∨
∃ x y, P.IsOptimal x ∧ P.IsDualOptimal y ∧
P.objective x = P.dualObjective y := by
let fuel := Dictionary.basisCount m n
cases hrun : D.simplexRun fuel with
| optimal terminal hc =>
have hEq : P.initialDictionary.Equivalent terminal :=
hEq₀.trans (by
simpa [hrun, Dictionary.SimplexRunResult.terminalDictionary] using
D.simplexRun_equivalent fuel)
have hterminal : terminal.IsBasicFeasible := by
simpa [hrun, Dictionary.SimplexRunResult.terminalDictionary] using
D.simplexRun_isBasicFeasible fuel hD
have hdictOptimal :
P.initialDictionary.IsOptimalAssignment terminal.basicAssignment :=
hEq.isOptimalAssignment
(terminal.basicAssignment_optimal_of_reducedCosts_nonpos
hterminal hc)
let x := Dictionary.assignmentOriginal terminal.basicAssignment
let y := terminal.dualCertificate
have hx : P.IsOptimal x := by
exact Dictionary.initialDictionary_optimal_to_standardLP P
hdictOptimal
have hyfeasible : P.IsDualFeasible y := by
exact terminal.dualCertificate_isDualFeasible P hEq hc
have hterminalSatD₀ :
P.initialDictionary.Satisfies terminal.basicAssignment :=
(hEq.1 terminal.basicAssignment).2
terminal.basicAssignment_satisfies
have hprimalValue : P.objective x = terminal.v := by
calc
P.objective x =
P.initialDictionary.objectiveRhs terminal.basicAssignment :=
(Dictionary.initialDictionary_objectiveRhs_eq_objective_original
P terminal.basicAssignment).symm
_ = terminal.objectiveRhs terminal.basicAssignment :=
hEq.2 terminal.basicAssignment hterminalSatD₀
_ = terminal.v := terminal.objectiveRhs_basicAssignment
have hdualValue : P.dualObjective y = terminal.v :=
terminal.dualCertificate_objective_eq_v P hEq
have hvalue : P.objective x = P.dualObjective y :=
hprimalValue.trans hdualValue.symm
have hcs : P.ComplementarySlackness x y :=
(P.complementarySlackness_iff_objective_eq hx.1 hyfeasible).2 hvalue
have hy : P.IsDualOptimal y :=
P.dualOptimal_of_complementarySlackness hx.1 hyfeasible hcs
exact Or.inr ⟨x, y, hx, hy, hvalue⟩
| unbounded terminal entering he ha =>
have hEq : P.initialDictionary.Equivalent terminal :=
hEq₀.trans (by
simpa [hrun, Dictionary.SimplexRunResult.terminalDictionary] using
D.simplexRun_equivalent fuel)
have hterminal : terminal.IsBasicFeasible := by
simpa [hrun, Dictionary.SimplexRunResult.terminalDictionary] using
D.simplexRun_isBasicFeasible fuel hD
have hunbounded : P.initialDictionary.IsUnbounded :=
hEq.isUnbounded
(terminal.unbounded_of_entering_column hterminal entering he.1 ha)
exact Or.inl
(Dictionary.initialDictionary_unbounded_to_standardLP P hunbounded)
| exhausted terminal =>
exact False.elim (D.simplexRun_basisCount_not_exhausted hD (by
simp [fuel, hrun, Dictionary.SimplexRunResult.IsExhausted]))Strong duality, with the alternative unbounded outcome made explicit, for programs whose initial slack dictionary is basic feasible.
theorem strongDuality_or_unbounded_of_initialDictionary_isBasicFeasible
(P : StandardLP m n) (hP : P.initialDictionary.IsBasicFeasible) :
P.IsUnbounded ∨
∃ x y, P.IsOptimal x ∧ P.IsDualOptimal y ∧
P.objective x = P.dualObjective y :=
P.strongDuality_or_unbounded_of_equivalent_isBasicFeasible
P.initialDictionary (Dictionary.Equivalent.refl _) hPIf the primal is not unbounded, finite SIMPLEX yields primal and dual optima with equal objective values.
theorem strongDuality_of_initialDictionary_isBasicFeasible
(P : StandardLP m n) (hP : P.initialDictionary.IsBasicFeasible)
(hbounded : ¬P.IsUnbounded) :
∃ x y, P.IsOptimal x ∧ P.IsDualOptimal y ∧
P.objective x = P.dualObjective y := by
rcases P.strongDuality_or_unbounded_of_initialDictionary_isBasicFeasible hP
with hunbounded | hopt
· exact False.elim (hbounded hunbounded)
· exact hoptend StandardLPend Chapter29end CLRSCLRSLean.FourthEdition.Chapter_29.Section_29_3_Duality.ComplementarySlacknessTheorem
29.3 The complementary-slackness theorem
This closes the converse direction of the textbook theorem: for feasible primal and dual assignments, simultaneous optimality is equivalent to the complementary-slackness equations.
namespace CLRSnamespace Chapter29namespace StandardLPAny dual-feasible assignment gives a finite upper bound on all primal feasible objective values.
theorem not_isUnbounded_of_isDualFeasible (P : StandardLP m n)
{y : Fin m → ℝ} (hy : P.IsDualFeasible y) : ¬P.IsUnbounded := by
intro hunbounded
obtain ⟨x, hx, hlarge⟩ := hunbounded (P.dualObjective y)
have hweak := P.weak_duality hx hy
linarithCLRS complementary-slackness theorem for an initially basic-feasible program: feasible primal and dual assignments are both optimal exactly when their complementary products vanish.
theorem complementarySlackness_iff_optimal_of_initialDictionary_isBasicFeasible
(P : StandardLP m n) (hP : P.initialDictionary.IsBasicFeasible)
{x : Fin n → ℝ} {y : Fin m → ℝ}
(hx : P.IsFeasible x) (hy : P.IsDualFeasible y) :
P.ComplementarySlackness x y ↔
P.IsOptimal x ∧ P.IsDualOptimal y := by
constructor
· intro hcs
exact ⟨P.optimal_of_complementarySlackness hx hy hcs,
P.dualOptimal_of_complementarySlackness hx hy hcs⟩
· rintro ⟨hxoptimal, hyoptimal⟩
obtain ⟨x₀, y₀, hx₀, hy₀, hvalue₀⟩ :=
P.strongDuality_of_initialDictionary_isBasicFeasible hP
(P.not_isUnbounded_of_isDualFeasible hy)
have hprimal : P.objective x = P.objective x₀ :=
le_antisymm (hx₀.2 x hx) (hxoptimal.2 x₀ hx₀.1)
have hdual : P.dualObjective y = P.dualObjective y₀ :=
le_antisymm (hyoptimal.2 y₀ hy₀.1) (hy₀.2 y hy)
have hvalue : P.objective x = P.dualObjective y := by
calc
P.objective x = P.objective x₀ := hprimal
_ = P.dualObjective y₀ := hvalue₀
_ = P.dualObjective y := hdual.symm
exact (P.complementarySlackness_iff_objective_eq hx hy).2 hvalueend StandardLPend Chapter29end CLRSCLRSLean.FourthEdition.Chapter_29.Section_29_3_Duality.GeneralStrongDuality
29.3 General strong duality
Phase II supplies the equivalent basic-feasible dictionary required by the finite-SIMPLEX strong-duality proof. Projecting both primal and dual locked coordinates yields the unrestricted textbook theorems for the original LP.
This module is the canonical fourth-edition owner of the general (no
initial-basis hypothesis) strong-duality and complementary-slackness theorems.
The phase-I/II initialization machinery it builds on remains online material
(reachable through CLRSLean.OnlineMaterial); only the resulting duality
declarations live here.
namespace CLRSnamespace Chapter29namespace StandardLPEvery feasible standard-form program is either unbounded or has primal and dual optima with equal values.
theorem strongDuality_or_unbounded_of_feasible (P : StandardLP m n)
(hfeasible : ∃ x, P.IsFeasible x) :
P.IsUnbounded ∨
∃ x y, P.IsOptimal x ∧ P.IsDualOptimal y ∧
P.objective x = P.dualObjective y := by
have hlocked :=
P.lockedAuxiliary.strongDuality_or_unbounded_of_equivalent_isBasicFeasible
P.phaseTwoStart P.phaseTwoStart_equivalent_lockedAuxiliary
(P.phaseTwoStart_isBasicFeasible hfeasible)
rcases hlocked with hunbounded | ⟨z, y, hz, hy, hvalue⟩
· exact Or.inl (P.lockedAuxiliary_unbounded_to_original hunbounded)
· let x₀ := auxiliaryTail z
let y₀ := lockedDualTail y
have hx₀ : P.IsOptimal x₀ :=
P.lockedAuxiliary_optimal_to_original hz
have hy₀feasible : P.IsDualFeasible y₀ :=
P.lockedAuxiliary_dualFeasible_to_original hy.1
have hvalue₀ : P.objective x₀ = P.dualObjective y₀ := by
calc
P.objective x₀ = P.lockedAuxiliary.objective z :=
(P.lockedAuxiliary_objective z).symm
_ = P.lockedAuxiliary.dualObjective y := hvalue
_ = P.dualObjective y₀ := P.lockedAuxiliary_dualObjective y
have hy₀ : P.IsDualOptimal y₀ := by
refine ⟨hy₀feasible, ?_⟩
intro y' hy'
calc
P.dualObjective y₀ = P.objective x₀ := hvalue₀.symm
_ ≤ P.dualObjective y' := P.weak_duality hx₀.1 hy'
exact Or.inr ⟨x₀, y₀, hx₀, hy₀, hvalue₀⟩General strong duality: a feasible, bounded primal program has primal and dual optima with equal objective values.
theorem strongDuality (P : StandardLP m n)
(hfeasible : ∃ x, P.IsFeasible x) (hbounded : ¬P.IsUnbounded) :
∃ x y, P.IsOptimal x ∧ P.IsDualOptimal y ∧
P.objective x = P.dualObjective y := by
rcases P.strongDuality_or_unbounded_of_feasible hfeasible with
hunbounded | hopt
· exact False.elim (hbounded hunbounded)
· exact hoptAn attained primal optimum rules out primal unboundedness.
theorem not_isUnbounded_of_isOptimal (P : StandardLP m n)
{x : Fin n → ℝ} (hx : P.IsOptimal x) : ¬P.IsUnbounded := by
intro hunbounded
obtain ⟨z, hz, hlarge⟩ := hunbounded (P.objective x)
exact (not_lt_of_ge (hx.2 z hz)) hlargeTextbook strong-duality form for a given attained primal optimum.
theorem strongDuality_of_isOptimal (P : StandardLP m n)
{x : Fin n → ℝ} (hx : P.IsOptimal x) :
∃ y, P.IsDualOptimal y ∧ P.objective x = P.dualObjective y := by
obtain ⟨x₀, y, hx₀, hy, hvalue₀⟩ :=
P.strongDuality ⟨x, hx.1⟩ (P.not_isUnbounded_of_isOptimal hx)
have hprimal : P.objective x = P.objective x₀ :=
le_antisymm (hx₀.2 x hx.1) (hx.2 x₀ hx₀.1)
exact ⟨y, hy, hprimal.trans hvalue₀⟩CLRS complementary-slackness theorem without an initial-basis hypothesis: feasible primal and dual assignments are simultaneously optimal exactly when all complementary products vanish.
theorem complementarySlackness_iff_optimal (P : StandardLP m n)
{x : Fin n → ℝ} {y : Fin m → ℝ}
(hx : P.IsFeasible x) (hy : P.IsDualFeasible y) :
P.ComplementarySlackness x y ↔
P.IsOptimal x ∧ P.IsDualOptimal y := by
constructor
· intro hcs
exact ⟨P.optimal_of_complementarySlackness hx hy hcs,
P.dualOptimal_of_complementarySlackness hx hy hcs⟩
· rintro ⟨hxoptimal, hyoptimal⟩
obtain ⟨y₀, hy₀, hvalue₀⟩ := P.strongDuality_of_isOptimal hxoptimal
have hdual : P.dualObjective y = P.dualObjective y₀ :=
le_antisymm (hyoptimal.2 y₀ hy₀.1) (hy₀.2 y hy)
have hvalue : P.objective x = P.dualObjective y :=
hvalue₀.trans hdual.symm
exact (P.complementarySlackness_iff_objective_eq hx hy).2 hvalueend StandardLPend Chapter29end CLRSScope and implementation notes
Imports
import CLRSLean.Chapter_29
import CLRSLean.FourthEdition.Chapter_29.Section_29_1_Standard_And_Slack_Forms
import CLRSLean.FourthEdition.Chapter_29.Section_29_2_Formulating_Problems_As_Linear_Programs
import CLRSLean.FourthEdition.Chapter_29.Section_29_3_Duality
import CLRSLean.FourthEdition.Chapter_29.Section_29_3_Duality.GeneralStrongDuality
import CLRSLean.FourthEdition.Chapter_29.Section_29_1_Standard_And_Slack_Forms.SolverWrapperCurrent source
Sections 29.1--29.3 are native fourth-edition sections (standard and slack
forms, formulating problems as linear programs, and duality), imported
directly from
Section 29.1,
Section 29.2,
and
Section 29.3.
The duality development builds on the online simplex machinery (legacy
Section 29.3), and the difference-constraints bridge imports the
fourth-edition single-source-shortest-paths sources (Chapter 22).
Declarations keep their current namespaces; the third-edition-numbered
imports CLRSLean.Chapter_29 and
CLRSLean.Chapter_29.Section_29_* forward to these sources.
Implementation details
The supporting implementation pages remain available outside the main sidebar:
Coverage boundary
The native sections supply the represented fourth-edition linear-programming
sections (§29.1 formulations and algorithms, §29.2 formulating problems,
§29.3 duality with Theorems 29.8--29.10). The detailed simplex algorithm
(legacy Section 29.3) and the initial basic feasible solution (legacy
Section 29.5) are retained as supplementary online material (reachable
through CLRSLean.OnlineMaterial). The canonical guide already imports
SolverWrapper, whose normalization/initialized-solver composition
certifies all three outcomes for general-form programs. The construction uses
classical real-valued choices; no polynomial SIMPLEX or machine-runtime bound
is claimed.
See docs/clrs-fourth-edition-map.csv for the section-level mapping and
docs/migrations/clrs4.md for compatibility and deprecation policy.
CLRS, fourth edition · Chapter 29 of 35