CubicPathwidth.Alg.Proofs.G5Components

Source file CubicPathwidth/Alg/Proofs/G5Components.lean of the Lean project (how it is built).

import CubicPathwidth.Alg.Spec

/-!
# G5: `components` satisfies `ComponentsSpec`

Proof of `ComponentsSpec` (Spec.lean, section 3) for `Graph.components`.
Helpers live in `CubicPathwidth.Alg.G5`.

1. `fold_spec`: the neighbour fold of `reachAux`, for an arbitrary initial accumulator.
2. `reachAux_spec`: invariant of the depth-first search, giving a reachable and closed
   result.
3. `go_good`: invariant of `components.go` (disjoint closed nonempty parts, covering
   the processed nodes), including the fallback branch.
-/

namespace CubicPathwidth.Alg

open Graph

namespace G5

/-- The neighbour-fold step of `Graph.reachAux` (same function as the lambda there). -/
abbrev stepF (acc : Std.HashSet Nat × List Nat) (y : Nat) : Std.HashSet Nat × List Nat :=
  if acc.1.contains y then acc else (acc.1.insert y, y :: acc.2)

/-- Behaviour of the neighbour fold, for an arbitrary initial accumulator. -/
theorem fold_spec (L : List Nat) : ∀ (acc : Std.HashSet Nat × List Nat),
    (∀ y ∈ L, (L.foldl stepF acc).1.contains y = true) ∧
    (∀ z, acc.1.contains z = true → (L.foldl stepF acc).1.contains z = true) ∧
    (∀ z ∈ acc.2, z ∈ (L.foldl stepF acc).2) ∧
    (∀ z, (L.foldl stepF acc).1.contains z = true →
        acc.1.contains z = true ∨ (z ∈ L ∧ z ∈ (L.foldl stepF acc).2)) ∧
    (∀ z ∈ (L.foldl stepF acc).2, z ∈ acc.2 ∨ z ∈ L) := by
  induction L with
  | nil =>
    intro acc
    simp only [List.foldl_nil]
    exact ⟨fun y hy => absurd hy (by simp), fun _ hz => hz, fun _ hz => hz,
      fun _ hz => Or.inl hz, fun _ hz => Or.inl hz⟩
  | cons y L ih =>
    intro acc
    rw [List.foldl_cons]
    by_cases hc : acc.1.contains y = true
    · have hstep : stepF acc y = acc := by simp [stepF, hc]
      rw [hstep]
      obtain ⟨h1, h2, h3, h4, h5⟩ := ih acc
      refine ⟨?_, h2, h3, ?_, ?_⟩
      · intro y' hy'
        rcases List.mem_cons.1 hy' with h | h
        · subst h; exact h2 _ hc
        · exact h1 y' h
      · intro z hz
        rcases h4 z hz with h | ⟨hzL, hzr⟩
        · exact Or.inl h
        · exact Or.inr ⟨List.mem_cons_of_mem _ hzL, hzr⟩
      · intro z hz
        rcases h5 z hz with h | h
        · exact Or.inl h
        · exact Or.inr (List.mem_cons_of_mem _ h)
    · have hc' : acc.1.contains y = false := by simpa using hc
      have hstep : stepF acc y = (acc.1.insert y, y :: acc.2) := by simp [stepF, hc']
      rw [hstep]
      obtain ⟨h1, h2, h3, h4, h5⟩ := ih (acc.1.insert y, y :: acc.2)
      refine ⟨?_, ?_, ?_, ?_, ?_⟩
      · intro y' hy'
        rcases List.mem_cons.1 hy' with h | h
        · subst h; exact h2 _ (by simp)
        · exact h1 y' h
      · intro z hz
        exact h2 z (by simp [hz])
      · intro z hz
        exact h3 z (List.mem_cons_of_mem _ hz)
      · intro z hz
        rcases h4 z hz with h | ⟨hzL, hzr⟩
        · simp only [Std.HashSet.contains_insert, Bool.or_eq_true, beq_iff_eq] at h
          rcases h with h | h
          · subst h
            exact Or.inr ⟨List.mem_cons_self, h3 _ List.mem_cons_self⟩
          · exact Or.inl h
        · exact Or.inr ⟨List.mem_cons_of_mem _ hzL, hzr⟩
      · intro z hz
        rcases h5 z hz with h | h
        · rcases List.mem_cons.1 h with h | h
          · subst h; exact Or.inr List.mem_cons_self
          · exact Or.inl h
        · exact Or.inr (List.mem_cons_of_mem _ h)

/-- Invariant of the depth-first search `reachAux` from the start `s`: every seen vertex
is reachable from `s`; every stacked vertex is seen; every seen vertex is on the stack or
has all its neighbours seen. -/
def SearchInv (G : Graph) (s : Nat) (stack : List Nat) (seen : Std.HashSet Nat) : Prop :=
  (∀ z, seen.contains z = true → Relation.ReflTransGen G.Adj s z) ∧
  (∀ z ∈ stack, seen.contains z = true) ∧
  (∀ z, seen.contains z = true → z ∈ stack ∨ ∀ y, G.Adj z y → seen.contains y = true)

/-- One pop of the search preserves the invariant. -/
theorem searchInv_step (G : Graph) (s x : Nat) (rest : List Nat) (seen : Std.HashSet Nat)
    (hInv : SearchInv G s (x :: rest) seen) :
    SearchInv G s ((G.nbrs x).foldl stepF (seen, rest)).2
      ((G.nbrs x).foldl stepF (seen, rest)).1 := by
  obtain ⟨h1, h2, h3⟩ := hInv
  obtain ⟨f1, f2, f3, f4, f5⟩ := fold_spec (G.nbrs x) (seen, rest)
  have hx : seen.contains x = true := h2 x List.mem_cons_self
  refine ⟨?_, ?_, ?_⟩
  · intro z hz
    rcases f4 z hz with h | ⟨hzL, _⟩
    · exact h1 z h
    · exact Relation.ReflTransGen.tail (h1 x hx) hzL
  · intro z hz
    rcases f5 z hz with h | h
    · exact f2 z (h2 z (List.mem_cons_of_mem _ h))
    · exact f1 z h
  · intro z hz
    rcases f4 z hz with h | ⟨_, hzr⟩
    · rcases h3 z h with h' | h'
      · rcases List.mem_cons.1 h' with h'' | h''
        · subst h''
          exact Or.inr (fun y hy => f1 y hy)
        · exact Or.inl (f3 z h'')
      · exact Or.inr (fun y hy => f2 y (h' y hy))
    · exact Or.inl hzr

theorem contains_single (s z : Nat) :
    ((∅ : Std.HashSet Nat).insert s).contains z = true ↔ s = z := by
  rw [Std.HashSet.contains_insert, Std.HashSet.contains_empty, Bool.or_false, beq_iff_eq]

theorem searchInv_init (G : Graph) (s : Nat) :
    SearchInv G s [s] ((∅ : Std.HashSet Nat).insert s) := by
  refine ⟨?_, ?_, ?_⟩
  · intro z hz
    rw [contains_single] at hz
    subst hz
    exact Relation.ReflTransGen.refl
  · intro z hz
    rw [List.mem_singleton] at hz
    subst hz
    exact (contains_single _ _).2 rfl
  · intro z hz
    rw [contains_single] at hz
    subst hz
    exact Or.inl List.mem_cons_self

theorem reachAux_nil (G : Graph) (fuel : Nat) (seen : Std.HashSet Nat) :
    G.reachAux fuel [] seen = some seen := by
  cases fuel <;> rfl

/-- Correctness of `reachAux`: a returned set consists of reachable vertices, contains the
initial seen set, and is closed under adjacency. -/
theorem reachAux_spec (G : Graph) (s : Nat) (fuel : Nat) :
    ∀ (stack : List Nat) (seen r : Std.HashSet Nat), SearchInv G s stack seen →
      G.reachAux fuel stack seen = some r →
      (∀ z, r.contains z = true → Relation.ReflTransGen G.Adj s z) ∧
      (∀ z, seen.contains z = true → r.contains z = true) ∧
      (∀ z, r.contains z = true → ∀ y, G.Adj z y → r.contains y = true) := by
  induction fuel with
  | zero =>
    intro stack seen r hInv hr
    cases stack with
    | nil =>
      rw [reachAux_nil] at hr
      cases hr
      refine ⟨hInv.1, fun _ hz => hz, fun z hz y hy => ?_⟩
      rcases hInv.2.2 z hz with h | h
      · exact absurd h (by simp)
      · exact h y hy
    | cons x rest =>
      rw [reachAux] at hr
      cases hr
  | succ n ih =>
    intro stack seen r hInv hr
    cases stack with
    | nil =>
      rw [reachAux_nil] at hr
      cases hr
      refine ⟨hInv.1, fun _ hz => hz, fun z hz y hy => ?_⟩
      rcases hInv.2.2 z hz with h | h
      · exact absurd h (by simp)
      · exact h y hy
    | cons x rest =>
      rw [reachAux] at hr
      have hI := searchInv_step G s x rest seen hInv
      obtain ⟨f1, f2, f3, f4, f5⟩ := fold_spec (G.nbrs x) (seen, rest)
      obtain ⟨i1, i2, i3⟩ := ih _ _ r hI hr
      refine ⟨i1, fun z hz => i2 z (f2 z hz), i3⟩

/-! ## Part 2: closure, the accumulator invariant of `components.go`, the result -/

/-- `c` is closed under adjacency. -/
def Closed (G : Graph) (c : List Nat) : Prop := ∀ x ∈ c, ∀ y, G.Adj x y → y ∈ c

theorem closed_rtg (G : Graph) (c : List Nat) (hc : Closed G c) {a b : Nat} (ha : a ∈ c)
    (h : Relation.ReflTransGen G.Adj a b) : b ∈ c := by
  induction h with
  | refl => exact ha
  | tail _ hbc ih => exact hc _ ih _ hbc

theorem rtg_symm (G : Graph) (hs : ∀ u v, G.Adj u v → G.Adj v u) {a b : Nat}
    (h : Relation.ReflTransGen G.Adj a b) : Relation.ReflTransGen G.Adj b a := by
  induction h with
  | refl => exact Relation.ReflTransGen.refl
  | tail _ hbc ih => exact Relation.ReflTransGen.head (hs _ _ hbc) ih

theorem foldInsert_contains (l : List Nat) : ∀ (s : Std.HashSet Nat) (x : Nat),
    (l.foldl (fun s y => s.insert y) s).contains x = true ↔ x ∈ l ∨ s.contains x = true := by
  induction l with
  | nil => intro s x; simp
  | cons y l ih =>
    intro s x
    rw [List.foldl_cons, ih, Std.HashSet.contains_insert, Bool.or_eq_true, beq_iff_eq,
      List.mem_cons]
    constructor
    · rintro (h | h | h)
      · exact Or.inl (Or.inr h)
      · exact Or.inl (Or.inl h.symm)
      · exact Or.inr h
    · rintro ((h | h) | h)
      · exact Or.inr (Or.inl h.symm)
      · exact Or.inl h
      · exact Or.inr (Or.inr h)

/-- The shape of the output of `components`: disjoint, nonempty, closed parts; the parts
together contain exactly the nodes (flatten nodup). -/
def Good (G : Graph) (res : List (List Nat)) : Prop :=
  (∀ c ∈ res, c ≠ [] ∧ (∀ x ∈ c, x ∈ G.nodes) ∧ Closed G c) ∧
  res.flatten.Nodup ∧ (∀ x, x ∈ res.flatten ↔ x ∈ G.nodes)

theorem good_reverse (G : Graph) {res : List (List Nat)} (h : Good G res) :
    Good G res.reverse := by
  obtain ⟨h1, h2, h3⟩ := h
  have hp := (List.reverse_perm res).flatten
  exact ⟨fun c hc => h1 c (List.mem_reverse.1 hc), hp.nodup_iff.2 h2,
    fun x => by rw [hp.mem_iff]; exact h3 x⟩

/-- Invariant of `components.go G vs assigned acc` (with `acc` the components found so far,
in reverse order). -/
structure GoInv (G : Graph) (vs : List Nat) (assigned : Std.HashSet Nat)
    (acc : List (List Nat)) : Prop where
  comp_ne : ∀ c ∈ acc, c ≠ []
  comp_sub : ∀ c ∈ acc, ∀ x ∈ c, x ∈ G.nodes
  comp_closed : ∀ c ∈ acc, Closed G c
  flat_nodup : acc.flatten.Nodup
  assigned_iff : ∀ x, assigned.contains x = true ↔ x ∈ acc.flatten
  cover : ∀ x ∈ G.nodes, x ∉ vs → x ∈ acc.flatten
  vs_sub : ∀ x ∈ vs, x ∈ G.nodes

theorem final_good (G : Graph) {acc : List (List Nat)} (hI : GoInv G [] assigned acc) :
    Good G acc := by
  refine ⟨fun c hc => ⟨hI.comp_ne c hc, hI.comp_sub c hc, hI.comp_closed c hc⟩,
    hI.flat_nodup, fun x => ⟨fun h => ?_, fun h => ?_⟩⟩
  · obtain ⟨c, hc, hxc⟩ := List.mem_flatten.1 h
    exact hI.comp_sub c hc x hxc
  · exact hI.cover x h (List.not_mem_nil)

theorem fallback_good (G : Graph) (hG : G.WF) (assigned : Std.HashSet Nat)
    (acc : List (List Nat)) (vs : List Nat) (v : Nat)
    (hI : GoInv G (v :: vs) assigned acc) (hv : v ∈ G.nodes)
    (hA : ¬ assigned.contains v = true) :
    Good G ((G.nodes.filter (fun x => !assigned.contains x)) :: acc).reverse := by
  have hmem : ∀ x, x ∈ G.nodes.filter (fun x => !assigned.contains x) ↔
      x ∈ G.nodes ∧ assigned.contains x = false := by
    intro x
    rw [List.mem_filter]
    simp
  have hsymm : ∀ u w, G.Adj u w → G.Adj w u := fun u w h => hG.symm u w h
  have hclosed : Closed G (G.nodes.filter (fun x => !assigned.contains x)) := by
    intro x hx y hxy
    rw [hmem] at hx ⊢
    refine ⟨hG.nbrs_sub x y hxy, ?_⟩
    by_cases hy : assigned.contains y = true
    · exfalso
      have hyf := (hI.assigned_iff y).1 hy
      obtain ⟨c, hc, hyc⟩ := List.mem_flatten.1 hyf
      have hxc : x ∈ c := hI.comp_closed c hc y hyc x (hsymm x y hxy)
      have hxa : assigned.contains x = true :=
        (hI.assigned_iff x).2 (List.mem_flatten.2 ⟨c, hc, hxc⟩)
      rw [hx.2] at hxa
      exact Bool.false_ne_true hxa
    · exact Bool.eq_false_iff.2 hy
  have hG' : Good G ((G.nodes.filter (fun x => !assigned.contains x)) :: acc) := by
    refine ⟨?_, ?_, ?_⟩
    · intro c hc
      rcases List.mem_cons.1 hc with h | h
      · subst h
        refine ⟨List.ne_nil_of_mem ((hmem v).2 ⟨hv, by simpa using hA⟩), ?_, hclosed⟩
        intro x hx
        exact ((hmem x).1 hx).1
      · exact ⟨hI.comp_ne c h, hI.comp_sub c h, hI.comp_closed c h⟩
    · rw [List.flatten_cons, List.nodup_append]
      refine ⟨hG.nodup.filter _, hI.flat_nodup, ?_⟩
      intro a ha b hb hab
      subst hab
      have hain := (hI.assigned_iff a).2 hb
      rw [((hmem a).1 ha).2] at hain
      exact Bool.false_ne_true hain
    · intro x
      rw [List.flatten_cons, List.mem_append]
      constructor
      · rintro (h | h)
        · exact ((hmem x).1 h).1
        · obtain ⟨c, hc, hxc⟩ := List.mem_flatten.1 h
          exact hI.comp_sub c hc x hxc
      · intro hx
        by_cases hxa : assigned.contains x = true
        · exact Or.inr ((hI.assigned_iff x).1 hxa)
        · exact Or.inl ((hmem x).2 ⟨hx, Bool.eq_false_iff.2 hxa⟩)
  exact good_reverse G hG'

/-- Invariant of `components.go` and its result: for every state satisfying `GoInv`, the
output is `Good`. Induction on the remaining nodes `vs`. -/
theorem go_good (G : Graph) (hG : G.WF) :
    ∀ (vs : List Nat) (assigned : Std.HashSet Nat) (acc : List (List Nat)),
      GoInv G vs assigned acc → Good G (components.go G vs assigned acc) := by
  intro vs
  induction vs with
  | nil =>
    intro assigned acc hI
    rw [components.go]
    exact good_reverse G (final_good G hI)
  | cons v vs ih =>
    intro assigned acc hI
    have hv : v ∈ G.nodes := hI.vs_sub v List.mem_cons_self
    by_cases hA : assigned.contains v = true
    · rw [components.go, ite_eq_left hA]
      apply ih
      refine GoInv.mk hI.comp_ne hI.comp_sub hI.comp_closed hI.flat_nodup hI.assigned_iff ?_ ?_
      · intro x hx hxn
        by_cases hxv : x = v
        · rw [hxv]
          exact (hI.assigned_iff v).1 hA
        · exact hI.cover x hx (fun h => by
            rcases List.mem_cons.1 h with h' | h'
            · exact hxv h'
            · exact hxn h')
      · intro x hx
        exact hI.vs_sub x (List.mem_cons_of_mem _ hx)
    · rw [components.go, ite_eq_right hA]
      split
      · rename_i r hr
        obtain ⟨hr1, hr2, hr3⟩ :=
          reachAux_spec G v (G.numNodes + 1) [v] _ r (searchInv_init G v) hr
        have hsv : ((∅ : Std.HashSet Nat).insert v).contains v = true :=
          (contains_single v v).2 rfl
        have hmemc : ∀ x, x ∈ G.nodes.filter r.contains ↔
            x ∈ G.nodes ∧ r.contains x = true := by
          intro x
          rw [List.mem_filter]
        have hvc : v ∈ G.nodes.filter r.contains := (hmemc v).2 ⟨hv, hr2 v hsv⟩
        have hsymm : ∀ u w, G.Adj u w → G.Adj w u := fun u w h => hG.symm u w h
        apply ih
        refine GoInv.mk ?_ ?_ ?_ ?_ ?_ ?_ ?_
        · intro c hc
          rcases List.mem_cons.1 hc with h | h
          · rw [h]
            exact List.ne_nil_of_mem hvc
          · exact hI.comp_ne c h
        · intro c hc x hx
          rcases List.mem_cons.1 hc with h | h
          · rw [h] at hx
            exact ((hmemc x).1 hx).1
          · exact hI.comp_sub c h x hx
        · intro c hc
          rcases List.mem_cons.1 hc with h | h
          · rw [h]
            intro x hx y hxy
            have hxr := ((hmemc x).1 hx).2
            exact (hmemc y).2 ⟨hG.nbrs_sub x y hxy, hr3 x hxr y hxy⟩
          · exact hI.comp_closed c h
        · rw [List.flatten_cons, List.nodup_append]
          refine ⟨hG.nodup.filter _, hI.flat_nodup, ?_⟩
          intro a ha b hb hab
          subst hab
          obtain ⟨c, hc, hac⟩ := List.mem_flatten.1 hb
          have hav : Relation.ReflTransGen G.Adj a v :=
            rtg_symm G hsymm (hr1 a ((hmemc a).1 ha).2)
          have hvc' : v ∈ c := closed_rtg G c (hI.comp_closed c hc) hac hav
          exact hA ((hI.assigned_iff v).2 (List.mem_flatten.2 ⟨c, hc, hvc'⟩))
        · intro x
          rw [foldInsert_contains, List.flatten_cons, List.mem_append, hI.assigned_iff x]
        · intro x hx hxv
          rw [List.flatten_cons, List.mem_append]
          by_cases hxe : x = v
          · left
            rw [hxe]
            exact hvc
          · right
            exact hI.cover x hx (fun h => by
              rcases List.mem_cons.1 h with h' | h'
              · exact hxe h'
              · exact hxv h')
        · intro x hx
          exact hI.vs_sub x (List.mem_cons_of_mem _ hx)
      · rename_i hr
        exact fallback_good G hG assigned acc vs v hI hv hA

end G5

/-- `components` satisfies `ComponentsSpec` (G5). The hypothesis `WFBasics` is not needed. -/
theorem componentsSpec_of (hWF : WFBasics) : ComponentsSpec := by
  intro G hG
  have hI0 : G5.GoInv G G.nodes ∅ [] :=
    { comp_ne := by simp
      comp_sub := by simp
      comp_closed := by simp
      flat_nodup := by simp
      assigned_iff := by intro x; simp
      cover := fun x hx hn => absurd hx hn
      vs_sub := fun x hx => hx }
  obtain ⟨h1, h2, h3⟩ := G5.go_good G hG G.nodes ∅ [] hI0
  have hc : G.components = components.go G G.nodes ∅ [] := rfl
  rw [hc]
  exact ⟨(List.perm_ext_iff_of_nodup h2 hG.nodup).2 h3,
    fun c hc' => (h1 c hc').1,
    fun c hc' x hx y hxy => (h1 c hc').2.2 x hx y hxy⟩

end CubicPathwidth.Alg

#print axioms CubicPathwidth.Alg.componentsSpec_of
Back to top