Graph.DSep.OrderedLocalSG
This interface exports the ordered-local derivation rules, the active-walk surgery and joining lemmas used by the peel induction, and the theorem that d-separation yields an ordered-local semi-graphoid derivation.
Defs 3 core · 4 supporting This file defines DAG.OrderedLocalSG, the triples generated from a DAG's ordered-local basis by symmetry, decomposition, weak union, and contraction. ★ OrderedLocalSG★ subset_random
Ordered-local semi-graphoid definitions
This file defines DAG.OrderedLocalSG, the triples generated from a DAG's ordered-local
basis by symmetry, decomposition, weak union, and contraction. It also provides the
ambient-set containment theorem and the ancestral-cardinality and topological-height
measures used by the later peel induction.
For a finite vertex population with decidable equality, a directed acyclic graph on that population, and an ambient set of random vertices, the ordered-local semi-graphoid closure contains precisely the triples of finite vertex sets generated by the empty-set rule, the ordered-local basis, symmetry, decomposition, weak union, and contraction.
Definition (Lean source)
For a finite directed acyclic graph, an ambient random set, and three query sets, if the query triple is derivable from the ordered-local semi-graphoid rules, then each query set is contained in the ambient random set.
Formal statement
Proof (Lean source)
For a finite directed acyclic graph and two endpoint sets plus random and fixed conditioning sets, the lexicographic peel measure pairs the size of the query's ancestral closure with the topological height of its random conditioning set.
4 supporting declarations (lemmas, instances)
-
topoMax_mem_seedtheorem — In a finite directed acyclic graph, a vertex in a seed set's ancestral closure that maximizes topological order there belongs to the seed set itself.hypothesesV :sharedType u_1Q :Finset Vn :Vhn :n ∈ G.ancestralSet Qhmax :∀ m ∈ G.ancestralSet Q, G.topoOrder m ≤ G.topoOrder nconclusionn ∈ QProof (Lean source)
theorem topoMax_mem_seed {Q : Finset V} {n : V} (hn : n ∈ G.ancestralSet Q) (hmax : ∀ m ∈ G.ancestralSet Q, G.topoOrder m ≤ G.topoOrder n) : n ∈ Q := by simp only [ancestralSet, mem_union, ancestorsSet, mem_filter, Finset.mem_univ, true_and] at hn rcases hn with hnQ | ⟨w, hwQ, haw⟩ · exact hnQ · -- `n` is a strict ancestor of `w ∈ Q`; but `w ∈ ancestralSet Q`, so -- `topoOrder w ≤ topoOrder n < topoOrder w`, contradiction. exfalso have hwAnc : w ∈ G.ancestralSet Q := G.subset_ancestralSet Q (by exact hwQ) have h1 : G.topoOrder w ≤ G.topoOrder n := hmax w hwAnc have h2 : G.topoOrder n < G.topoOrder w := G.isAncestor_topoOrder_lt haw omega -
ancestralSet_subset_of_subset_ancestralSettheorem — In a finite directed acyclic graph, if one set lies inside another set's ancestral closure, then the first set's ancestral closure remains inside the second set's ancestral closure.hypothesesconclusionG.ancestralSet Q' ⊆ G.ancestralSet QProof (Lean source)
theorem ancestralSet_subset_of_subset_ancestralSet {Q Q' : Finset V} (h : Q' ⊆ G.ancestralSet Q) : G.ancestralSet Q' ⊆ G.ancestralSet Q := by intro u hu simp only [ancestralSet, mem_union, ancestorsSet, mem_filter, Finset.mem_univ, true_and] at hu ⊢ rcases hu with huQ' | ⟨q', hq'Q', haq'⟩ · have := h huQ' simpa only [ancestralSet, mem_union, ancestorsSet, mem_filter, Finset.mem_univ, true_and] using this · have hq' := h hq'Q' simp only [ancestralSet, mem_union, ancestorsSet, mem_filter, Finset.mem_univ, true_and] at hq' rcases hq' with hq'Q | ⟨q, hqQ, haq'q⟩ · exact inr ⟨q', hq'Q, haq'⟩ · exact inr ⟨q, hqQ, G.isAncestor_trans haq' haq'q⟩ -
ancestralSet_card_lt_of_peeltheorem — In a finite directed acyclic graph, let a selected vertex lie in a seed's ancestral closure and maximize topological order there. If a new seed lies in that closure but omits that vertex, then the new seed's ancestral closure has strictly smaller cardinality.hypothesesV :sharedType u_1Q Q' :Finset Vn :VhnAnc :n ∈ G.ancestralSet Qhmax :∀ m ∈ G.ancestralSet Q, G.topoOrder m ≤ G.topoOrder nhQ'sub :Q' ⊆ G.ancestralSet QhnQ' :n ∉ Q'conclusion(G.ancestralSet Q').card < (G.ancestralSet Q).cardProof (Lean source)
theorem ancestralSet_card_lt_of_peel {Q Q' : Finset V} {n : V} (hnAnc : n ∈ G.ancestralSet Q) (hmax : ∀ m ∈ G.ancestralSet Q, G.topoOrder m ≤ G.topoOrder n) (hQ'sub : Q' ⊆ G.ancestralSet Q) (hnQ' : n ∉ Q') : (G.ancestralSet Q').card < (G.ancestralSet Q).card := by apply Finset.card_lt_card rw [Finset.ssubset_iff_of_subset (G.ancestralSet_subset_of_subset_ancestralSet hQ'sub)] refine ⟨n, hnAnc, ?_⟩ intro hnAnc' -- `n ∈ ancestralSet Q'` means either `n ∈ Q'` or `n` is a strict ancestor of some -- `q' ∈ Q' ⊆ ancestralSet Q`; the hypotheses rule out both cases. simp only [ancestralSet, mem_union, ancestorsSet, mem_filter, Finset.mem_univ, true_and] at hnAnc' rcases hnAnc' with hnQ'' | ⟨q', hq'Q', haq'⟩ · exact hnQ' hnQ'' · have hq'Anc : q' ∈ G.ancestralSet Q := hQ'sub hq'Q' have h1 : G.topoOrder q' ≤ G.topoOrder n := hmax q' hq'Anc have h2 : G.topoOrder n < G.topoOrder q' := G.isAncestor_topoOrder_lt haq' omega -
sup_topoOrder_lt_of_peeltheorem — In a finite directed acyclic graph, let a selected vertex maximize topological order in a seed's ancestral closure and belong to a random conditioning set. If a replacement set lies in the closure but omits the selected vertex, then its topological height is strictly smaller than that of the random conditioning set.hypothesesV :sharedType u_1Q Zr S :Finset Vn :Vhmax :∀ m ∈ G.ancestralSet Q, G.topoOrder m ≤ G.topoOrder nhnZr :n ∈ ZrhSsub :S ⊆ G.ancestralSet QhnS :n ∉ Sconclusion(S.sup (fun v => G.topoOrder v + 1)) < (Zr.sup (fun v => G.topoOrder v + 1))Proof (Lean source)
theorem sup_topoOrder_lt_of_peel {Q Zr S : Finset V} {n : V} (hmax : ∀ m ∈ G.ancestralSet Q, G.topoOrder m ≤ G.topoOrder n) (hnZr : n ∈ Zr) (hSsub : S ⊆ G.ancestralSet Q) (hnS : n ∉ S) : (S.sup (fun v => G.topoOrder v + 1)) < (Zr.sup (fun v => G.topoOrder v + 1)) := by have hZr_ge : G.topoOrder n + 1 ≤ Zr.sup (fun v => G.topoOrder v + 1) := Finset.le_sup (f := fun v => G.topoOrder v + 1) hnZr have hS_le : S.sup (fun v => G.topoOrder v + 1) ≤ G.topoOrder n := by rw [Finset.sup_le_iff] intro s hs have hsAnc : s ∈ G.ancestralSet Q := hSsub hs have hle : G.topoOrder s ≤ G.topoOrder n := hmax s hsAnc have hne : s ≠ n := fun h => hnS (h ▸ hs) have : G.topoOrder s ≠ G.topoOrder n := fun h => hne (G.topoOrder_injective h) omega omega
Closure 1 core · 2 supporting This file carries out the well-founded peel induction proving orderedLocalSG_of_dSep_with_fixed: d-separation with random and fixed conditioning sets yields a derivation from the ordered-local basis and the semi-graphoid ★ orderedLocalSG_of_dSep_with_fixed
Closure of d-separation under the ordered-local rules
This file carries out the well-founded peel induction proving
orderedLocalSG_of_dSep_with_fixed: d-separation with random and fixed conditioning
sets yields a derivation from the ordered-local basis and the semi-graphoid rules. It
also exposes a reachability lemma that extends an active walk by a forward directed arm.
d-separation yields an ordered-local derivation. In a DAG G, suppose every vertex of Zf has no parents, i.e. Zf consists of fixed roots, Zf is disjoint from R, and X, Y, and Zr are each contained in R. If X and Y are d-separated by Zr ∪ Zf, then the conditional-independence triple "X ⊥ Y given Zr" is derivable from the ordered-local basis on R via the semi-graphoid axioms.
Formal statement
Proof (Lean source)
2 supporting declarations (lemmas, instances)
-
branch_seed_eqtheorem — For a finite vertex population, four vertex sets and a selected vertex, if the selected vertex belongs to the third set, then removing and reinserting it while forming the union leaves that union unchanged.hypotheseshn :n ∈ Zrconclusiona ∪ {n} ∪ (Zr.erase n ∪ b) ∪ c = a ∪ b ∪ Zr ∪ cProof (Lean source)
theorem branch_seed_eq {a b Zr c : Finset V} {n : V} (hn : n ∈ Zr) : a ∪ {n} ∪ (Zr.erase n ∪ b) ∪ c = a ∪ b ∪ Zr ∪ c := by ext x; simp only [mem_union, Finset.mem_singleton, Finset.mem_erase] constructor · rintro (((h | h) | (h | h)) | h) · exact inl (inl (inl h)) · exact inl (inr (h ▸ hn)) · exact inl (inr h.2) · exact inl (inl (inr h)) · exact inr h · rintro (((h | h) | h) | h) · exact inl (inl (inl h)) · exact inl (inr (inr h)) · by_cases hx : x = n · exact inl (inl (inr hx)) · exact inl (inr (inl ⟨hx, h⟩)) · exact inr h -
bbReachable_extend_directed_armtheorem — Given a directed acyclic graph, conditioning set, endpoints, and two walks, if the first walk has at least two vertices, begins at the first endpoint, ends at the seam vertex, and is active, while the second walk has at least two vertices, begins at the seam, ends at the final endpoint, follows directed edges, has no conditioned interior vertices, and the seam is not conditioned on, then the final endpoint is Bayes-ball reachable from the first.hypothesesV :sharedType u_1Z :Finset Va c b :Vpa q :List Vhpa_len :pa.length ≥ 2hpa_head :pa.head? = some ahpa_last :pa.getLast? = some chpa_act :G.IsActiveWalk Z pahq_len :q.length ≥ 2hq_head :q.head? = some chq_last :q.getLast? = some bhq_edge :∀ (i : ℕ) (hi : i + 1 < q.length), G.edge (q.get ⟨i, by omega⟩) (q.get ⟨i + 1, hi⟩)hq_int :∀ (i : ℕ) (hi : i + 2 < q.length), q.get ⟨i + 1, by omega⟩ ∉ ZhcZ :c ∉ Zconclusionb ∈ G.bbReachableVertices Z ({a} : Finset V)Proof (Lean source)
theorem bbReachable_extend_directed_arm {Z : Finset V} {a c b : V} {pa q : List V} (hpa_len : pa.length ≥ 2) (hpa_head : pa.head? = some a) (hpa_last : pa.getLast? = some c) (hpa_act : G.IsActiveWalk Z pa) (hq_len : q.length ≥ 2) (hq_head : q.head? = some c) (hq_last : q.getLast? = some b) (hq_edge : ∀ (i : ℕ) (hi : i + 1 < q.length), G.edge (q.get ⟨i, by omega⟩) (q.get ⟨i + 1, hi⟩)) (hq_int : ∀ (i : ℕ) (hi : i + 2 < q.length), q.get ⟨i + 1, by omega⟩ ∉ Z) (hcZ : c ∉ Z) : b ∈ G.bbReachableVertices Z ({a} : Finset V) := by have hq_act : G.IsActiveWalk Z q := G.isActiveWalk_of_directed hq_edge hq_int have hqne : q ≠ [] := by intro h; rw [h] at hq_len; simp at hq_len -- First edge of `q` points out of `c`. have hq_head_eq : q.get ⟨0, by omega⟩ = c := by have h := List.head?_eq_some_head hqne rw [hq_head] at h rw [List.get_eq_getElem, List.getElem_zero] exact Option.some_inj.mp h.symm have hseam_out : G.edge c (q.get ⟨1, by omega⟩) := by have he := hq_edge 0 (by omega) rwa [hq_head_eq] at he obtain ⟨_, hjhead, hjlast, hjact⟩ := G.chain_join_active hpa_len hpa_head hpa_last hpa_act hq_len hq_head hq_last hq_act hseam_out hcZ rw [G.bbReachableVertices_iff_activeWalk] exact ⟨a, mem_singleton_self a, pa ++ q.tail, by rw [List.length_append]; have := List.length_tail (l := q); omega, hjact, hjhead, hjlast⟩
PathJoining 1 core · 3 supporting This file develops the walk-composition tools for the ordered-local induction. ★ survive_or_splice
Joining active walks in the ordered-local peel
This file develops the walk-composition tools for the ordered-local induction. It joins active walks across non-collider seams, relates forward directed walks to ancestry, preserves activity under prefix extraction, and proves the survive-or-splice alternative used when a maximal conditioning vertex is removed.
For a finite directed acyclic graph, two endpoint sets, a conditioning set, an auxiliary set, an ambient ancestral query set, a walk, and its endpoints, if all relevant vertices lie in the ambient query set, the terminal vertex is topologically maximal in its ancestral closure, the initial vertex belongs to its endpoint set, the walk has at least two vertices, the walk is active under the cross-conditioning set, the walk begins at the stated initial vertex, and ends at the maximal vertex, then either an active walk can be spliced to the auxiliary endpoint set or an active walk survives to the maximal vertex with its last edge directed inward.
Formal statement
Proof (Lean source)
3 supporting declarations (lemmas, instances)
-
chain_join_activetheorem — In a finite directed acyclic graph, consider a conditioning set, joining vertex, endpoints, and two walks. If the first walk is active from the first endpoint to the join, the second is active from the join to the second endpoint, its first edge points out of the join, and the joining vertex is not conditioned on, then concatenating the walks gives an active walk between the endpoints.hypothesesV :sharedType u_1Z :Finset Vm x y :Vpa pb :List Vhpa_len :pa.length ≥ 2hpa_head :pa.head? = some xhpa_last :pa.getLast? = some mhpa_act :G.IsActiveWalk Z pahpb_len :pb.length ≥ 2hpb_head :pb.head? = some mhpb_last :pb.getLast? = some yhpb_act :G.IsActiveWalk Z pbhseam_out :G.edge m (pb.get ⟨1, by omega⟩)hmZ :m ∉ Zconclusionp :pa ++ pb.tailp.length ≥ 2p.head? = some xp.getLast? = some yG.IsActiveWalk Z pProof (Lean source)
theorem chain_join_active {Z : Finset V} {m x y : V} {pa pb : List V} (hpa_len : pa.length ≥ 2) (hpa_head : pa.head? = some x) (hpa_last : pa.getLast? = some m) (hpa_act : G.IsActiveWalk Z pa) (hpb_len : pb.length ≥ 2) (hpb_head : pb.head? = some m) (hpb_last : pb.getLast? = some y) (hpb_act : G.IsActiveWalk Z pb) (hseam_out : G.edge m (pb.get ⟨1, by omega⟩)) (hmZ : m ∉ Z) : let p := pa ++ pb.tail p.length ≥ 2 ∧ p.head? = some x ∧ p.getLast? = some y ∧ G.IsActiveWalk Z p := by have hpa_ne : pa ≠ [] := by intro h; rw [h] at hpa_len; simp at hpa_len have hpb_ne : pb ≠ [] := by intro h; rw [h] at hpb_len; simp at hpb_len -- pb.head = m have hpb_head_eq : pb.head hpb_ne = m := by have h := List.head?_eq_some_head hpb_ne; rw [hpb_head] at h exact (Option.some_inj.mp h.symm) -- pa.getLast = m have hpa_last_eq : pa.getLast hpa_ne = m := by have h := getLast?_eq_some_getLast hpa_ne; rw [hpa_last] at h exact (Option.some_inj.mp h.symm) -- pa.head = x have hpa_head_eq : pa.head hpa_ne = x := by have h := List.head?_eq_some_head hpa_ne; rw [hpa_head] at h exact (Option.some_inj.mp h.symm) -- pb.getLast = y have hpb_last_eq : pb.getLast hpb_ne = y := by have h := getLast?_eq_some_getLast hpb_ne; rw [hpb_last] at h exact (Option.some_inj.mp h.symm) -- pb = m :: pb.tail have hpb_decomp : pb = m :: pb.tail := by conv_lhs => rw [← List.cons_head_tail hpb_ne]; rw [hpb_head_eq] have hpb_tail_len : pb.tail.length = pb.length - 1 := List.length_tail have hpb_tail_ne : pb.tail ≠ [] := by rw [← List.length_pos_iff, hpb_tail_len]; omega set p := pa ++ pb.tail with hp_def set R := pa.length with hR_def have hp_len_eq : p.length = R + pb.tail.length := by simp only [hp_def, List.length_append, ← hR_def] have hp_len : p.length ≥ 2 := by rw [hp_len_eq]; omega have hp_head : p.head? = some x := by have : p.head? = pa.head? := List.head?_append_of_ne_nil _ hpa_ne rw [this, hpa_head] have hp_last : p.getLast? = some y := by have hlast_tail : pb.tail.getLast? = some y := by have heq : pb = [m] ++ pb.tail := by simpa using hpb_decomp have := getLast?_append_of_ne_nil [m] hpb_tail_ne rw [← heq] at this; rw [← this]; exact hpb_last simp only [hp_def, getLast?_append, hlast_tail]; rfl refine ⟨hp_len, hp_head, hp_last, ?_⟩ -- Index translation: p[k] = pa[k] for k < R; p[k] = pb[k-R+1] for k ≥ R. have hp_L : ∀ (k : ℕ) (hkR : k < R), p.get ⟨k, by rw [hp_len_eq]; omega⟩ = pa.get ⟨k, hkR⟩ := by intro k hkR simp only [hp_def, List.get_eq_getElem, List.getElem_append_left (h := hkR)] have hpb_tail_get : ∀ (j : ℕ) (hj1 : j < pb.tail.length) (hj2 : j + 1 < pb.length), pb.tail[j]'hj1 = pb[j + 1]'hj2 := by intro j hj1 hj2 have : pb[j + 1] = (m :: pb.tail)[j + 1]'(by rw [← hpb_decomp]; exact hj2) := by congr 1 rw [this]; simp [List.getElem_cons_succ] have hp_R : ∀ (k : ℕ) (hkL : R ≤ k) (hk : k < p.length), p.get ⟨k, hk⟩ = pb.get ⟨k - R + 1, by rw [hp_len_eq, hpb_tail_len] at hk; omega⟩ := by intro k hkL hk have hk_app : R ≤ k := hkL have htail_idx_lt : k - R < pb.tail.length := by rw [hp_len_eq] at hk; omega have hpget : p.get ⟨k, hk⟩ = pb.tail[k - R]'htail_idx_lt := by simp only [hp_def, List.get_eq_getElem] rw [List.getElem_append_right (by rw [← hR_def]; exact hk_app)] rw [hpget] rw [hpb_tail_get (k - R) htail_idx_lt (by rw [hpb_tail_len] at htail_idx_lt; omega)] simp [List.get_eq_getElem] -- pb[0] = m, pa[R-1] = m. have hpb_get_0 : pb.get ⟨0, by omega⟩ = m := by rw [List.get_eq_getElem, List.getElem_zero]; exact hpb_head_eq have hpa_get_last : pa.get ⟨R - 1, by omega⟩ = m := by rw [List.get_eq_getElem] have := List.getLast_eq_getElem hpa_ne rw [hpa_last_eq] at this rw [← this] refine ⟨?_, ?_⟩ · -- Adjacency. intro i hi rw [hp_len_eq] at hi by_cases hA1 : i + 1 < R · have hiR : i < R := by omega rw [hp_L i hiR, hp_L (i + 1) hA1] exact hpa_act.1 i hA1 · push_neg at hA1 by_cases hA2 : i + 1 = R · have hi_eq : i = R - 1 := by omega subst hi_eq have hiR : R - 1 < R := by omega rw [hp_L (R - 1) hiR] have hi1L : R ≤ R - 1 + 1 := by omega have hi1_lt : R - 1 + 1 < p.length := by rw [hp_len_eq]; exact hi rw [hp_R (R - 1 + 1) hi1L hi1_lt] rw [hpa_get_last] have h_yidx : R - 1 + 1 - R + 1 = 1 := by omega rw [show (⟨R - 1 + 1 - R + 1, by rw [hp_len_eq, hpb_tail_len] at hi1_lt; omega⟩ : Fin pb.length) = ⟨1, by omega⟩ from Fin.ext h_yidx] exact Or.inl hseam_out · have hiL : R ≤ i := by omega have hi1L : R ≤ i + 1 := by omega have hi_lt : i < p.length := by rw [hp_len_eq]; omega have hi1_lt : i + 1 < p.length := by rw [hp_len_eq]; exact hi rw [hp_R i hiL hi_lt, hp_R (i + 1) hi1L hi1_lt] have hidx : (i - R + 1) + 1 < pb.length := by omega have hadj := hpb_act.1 (i - R + 1) hidx have he : (i - R + 1) + 1 = (i + 1) - R + 1 := by omega rw [show (⟨(i - R + 1) + 1, hidx⟩ : Fin pb.length) = ⟨(i + 1) - R + 1, by omega⟩ from Fin.ext he] at hadj exact hadj · -- Collider condition. intro i hi rw [hp_len_eq] at hi by_cases hC1 : i + 2 < R · have hiR : i < R := by omega have hi1R : i + 1 < R := by omega rw [hp_L i hiR, hp_L (i + 1) hi1R, hp_L (i + 2) hC1] exact hpa_act.2 i hC1 · push_neg at hC1 by_cases hC2 : i + 2 = R · have hi_eq : i = R - 2 := by omega subst hi_eq have hiR : R - 2 < R := by omega have hi1R : R - 2 + 1 < R := by omega have hi2L : R ≤ R - 2 + 2 := by omega have hi2_lt : R - 2 + 2 < p.length := by rw [hp_len_eq]; exact hi rw [hp_L (R - 2) hiR, hp_L (R - 2 + 1) hi1R, hp_R (R - 2 + 2) hi2L hi2_lt] rw [show (⟨R - 2 + 1, by omega⟩ : Fin pa.length) = ⟨R - 1, by omega⟩ from Fin.ext (show R - 2 + 1 = R - 1 by omega)] rw [hpa_get_last] have h_ridx : R - 2 + 2 - R + 1 = 1 := by omega rw [show (⟨R - 2 + 2 - R + 1, by rw [hp_len_eq, hpb_tail_len] at hi2_lt; omega⟩ : Fin pb.length) = ⟨1, by omega⟩ from Fin.ext h_ridx] have hNotColl : ¬ G.IsCollider (pa.get ⟨R - 2, by omega⟩) m (pb.get ⟨1, by omega⟩) := by intro hColl exact (G.asymm hseam_out) hColl.2 rw [if_neg hNotColl] exact hmZ · by_cases hC3 : i + 1 = R · have hi_eq : i = R - 1 := by omega subst hi_eq have hiR : R - 1 < R := by omega have hi1L : R ≤ R - 1 + 1 := by omega have hi2L : R ≤ R - 1 + 2 := by omega have hi1_lt : R - 1 + 1 < p.length := by rw [hp_len_eq]; omega have hi2_lt : R - 1 + 2 < p.length := by rw [hp_len_eq]; exact hi rw [hp_L (R - 1) hiR, hp_R (R - 1 + 1) hi1L hi1_lt, hp_R (R - 1 + 2) hi2L hi2_lt] rw [hpa_get_last] rw [show (⟨R - 1 + 1 - R + 1, by omega⟩ : Fin pb.length) = ⟨1, by omega⟩ from Fin.ext (show R - 1 + 1 - R + 1 = 1 by omega)] rw [show (⟨R - 1 + 2 - R + 1, by omega⟩ : Fin pb.length) = ⟨2, by omega⟩ from Fin.ext (show R - 1 + 2 - R + 1 = 2 by omega)] have hpb0 := hpb_act.2 0 (by omega) rw [show (⟨0 + 1, by omega⟩ : Fin pb.length) = ⟨1, by omega⟩ from rfl, show (⟨0 + 2, by omega⟩ : Fin pb.length) = ⟨2, by omega⟩ from rfl] at hpb0 rw [hpb_get_0] at hpb0 exact hpb0 · have hiL : R ≤ i := by omega have hi1L : R ≤ i + 1 := by omega have hi2L : R ≤ i + 2 := by omega have hi_lt : i < p.length := by rw [hp_len_eq]; omega have hi1_lt : i + 1 < p.length := by rw [hp_len_eq]; omega have hi2_lt : i + 2 < p.length := by rw [hp_len_eq]; exact hi rw [hp_R i hiL hi_lt, hp_R (i + 1) hi1L hi1_lt, hp_R (i + 2) hi2L hi2_lt] have hjlen : (i - R + 1) + 2 < pb.length := by omega have hpbcoll := hpb_act.2 (i - R + 1) hjlen rw [show (⟨(i - R + 1) + 1, by omega⟩ : Fin pb.length) = ⟨(i + 1) - R + 1, by omega⟩ from Fin.ext (show (i-R+1)+1 = (i+1)-R+1 by omega), show (⟨(i - R + 1) + 2, hjlen⟩ : Fin pb.length) = ⟨(i + 2) - R + 1, by omega⟩ from Fin.ext (show (i-R+1)+2 = (i+2)-R+1 by omega)] at hpbcoll exact hpbcoll -
node_isAncestor_last_of_directedtheorem — In a finite directed acyclic graph, consider a walk, its endpoint, and a selected position. If the walk is nontrivial, has the stated endpoint, follows forward directed edges, and the position lies on the walk, then the selected vertex is either the endpoint or its proper ancestor.hypothesesV :sharedType u_1q :List Vw :Vhlen :q.length ≥ 2hlast :q.getLast? = some whedge :∀ (i : ℕ) (hi : i + 1 < q.length), G.edge (q.get ⟨i, by omega⟩) (q.get ⟨i + 1, hi⟩)j :ℕhj :j < q.lengthconclusionG.isAncestor (q.get ⟨j, hj⟩) w ∨ q.get ⟨j, hj⟩ = wProof (Lean source)
theorem node_isAncestor_last_of_directed {q : List V} {w : V} (hlen : q.length ≥ 2) (hlast : q.getLast? = some w) (hedge : ∀ (i : ℕ) (hi : i + 1 < q.length), G.edge (q.get ⟨i, by omega⟩) (q.get ⟨i + 1, hi⟩)) (j : ℕ) (hj : j < q.length) : G.isAncestor (q.get ⟨j, hj⟩) w ∨ q.get ⟨j, hj⟩ = w := by by_cases hjlast : j = q.length - 1 · right have hqne : q ≠ [] := by intro h; rw [h] at hlen; simp at hlen have hh := getLast?_eq_some_getLast hqne rw [hlast] at hh have hw_eq : q.getLast hqne = w := Option.some_inj.mp hh.symm have hidx : (⟨j, hj⟩ : Fin q.length) = ⟨q.length - 1, by omega⟩ := Fin.ext (by omega) rw [hidx, List.get_eq_getElem, ← hw_eq, List.getLast_eq_getElem] · have hj1 : j + 1 < q.length := by omega have he := hedge j hj1 rcases node_isAncestor_last_of_directed hlen hlast hedge (j + 1) hj1 with h | h · exact inl (G.isAncestor_trans (isAncestor.edge he) h) · exact inl (h ▸ isAncestor.edge he) termination_by q.length - j decreasing_by omega -
isActiveWalk_taketheorem — In a finite directed acyclic graph, a conditioning set, walk, and prefix length with an active full walk have an active prefix.hypothesesconclusionG.IsActiveWalk Z (p.take k)Proof (Lean source)
theorem isActiveWalk_take {Z : Finset V} {p : List V} {k : ℕ} (hact : G.IsActiveWalk Z p) : G.IsActiveWalk Z (p.take k) := by obtain ⟨hadj, hcoll⟩ := hact have hle : (p.take k).length ≤ p.length := by rw [List.length_take]; exact min_le_right k p.length have hget : ∀ (j : ℕ) (hj : j < (p.take k).length), (p.take k).get ⟨j, hj⟩ = p.get ⟨j, by omega⟩ := by intro j hj simp only [List.get_eq_getElem, List.getElem_take] refine ⟨fun i hi => ?_, fun i hi => ?_⟩ · rw [hget i (by omega), hget (i + 1) hi] exact hadj i (by omega) · rw [hget i (by omega), hget (i + 1) (by omega), hget (i + 2) hi] exact hcoll i (by omega)
PathSurgery 1 core · 6 supporting This file proves the d-separation and active-walk transformations used when the ordered-local induction removes a topologically maximal vertex. ★ dSep_erase_maximal_random_condition
Walk surgery for the ordered-local peel
This file proves the d-separation and active-walk transformations used when the ordered-local induction removes a topologically maximal vertex. The results cover parent inheritance, erasing a maximal random conditioning vertex, changing a walk's conditioning set, orienting its final edge, and joining walks at a conditioned collider.
For a finite directed acyclic graph, two endpoint sets, random and fixed conditioning sets, and a selected vertex, if the vertex belongs to the random conditioning set, it is topologically maximal in the ancestral closure of the query, and the endpoint sets are d-separated by the full conditioning set, then erasing that vertex from the random conditioning set preserves the d-separation.
Formal statement
Proof (Lean source)
6 supporting declarations (lemmas, instances)
-
parents_disjoint_of_dSep_singletontheorem — In a finite directed acyclic graph, if a vertex, target set, and conditioning set satisfy d-separation of the vertex from the target, then the vertex's parents are disjoint from the target set.hypothesesconclusionDisjoint (G.parents n) YProof (Lean source)
theorem parents_disjoint_of_dSep_singleton {n : V} {Y D : Finset V} (hdSep : G.dSep {n} Y D) : Disjoint (G.parents n) Y := by rw [Finset.disjoint_left] intro a haPar haY -- The walk `[n, a]` is active given `D` (no interior vertex), from `n` to `a ∈ Y`. have hedge : G.edge a n := G.mem_parents.mp haPar have hact : G.IsActiveWalk D [n, a] := by refine ⟨fun i hi => ?_, fun i hi => ?_⟩ · -- length 2 ⟹ i = 0; the only adjacency is `UAdj n a`, from `edge a n`. have hi0 : i = 0 := by simp at hi; omega subst hi0 exact inr (by simpa using hedge) · -- length 2 ⟹ no interior triple simp only [List.length_cons, List.length_nil] at hi omega have hmem : a ∈ G.bbReachableVertices D {n} := by rw [G.bbReachableVertices_iff_activeWalk] exact ⟨n, mem_singleton_self n, [n, a], by simp, hact, rfl, rfl⟩ exact (Finset.disjoint_left.mp hdSep.2.2.2 hmem) haY -
dSep_parents_of_maximal_sourcetheorem — In a finite directed acyclic graph, consider a source vertex, target set, conditioning set, and selected parent set. If the source is d-separated from the target, every selected vertex is a parent of the source, and none is conditioned on, then the selected parents are d-separated from the target under the same conditioning set.hypothesesV :sharedType u_1n :VY D A0 :Finset VhdSep :G.dSep {n} Y DhA0_par :∀ a ∈ A0, G.edge a nhA0_D :∀ a ∈ A0, a ∉ DconclusionG.dSep A0 Y DProof (Lean source)
theorem dSep_parents_of_maximal_source {n : V} {Y D A0 : Finset V} (hdSep : G.dSep {n} Y D) (hA0_par : ∀ a ∈ A0, G.edge a n) (hA0_D : ∀ a ∈ A0, a ∉ D) : G.dSep A0 Y D := by refine ⟨?_, ?_, hdSep.2.2.1, ?_⟩ · exact (G.parents_disjoint_of_dSep_singleton hdSep).mono_left (by intro a ha exact G.mem_parents.mpr (hA0_par a ha)) · exact Finset.disjoint_left.mpr hA0_D rw [Finset.disjoint_left] intro y hyReach hyY -- Extract an active walk `a → … → y` for some `a ∈ A0`. rw [G.bbReachableVertices_iff_activeWalk] at hyReach obtain ⟨a, haA0, p, hlen, hact, hhead, hlast⟩ := hyReach -- `p` has length ≥ 2 and head `a`, so `p = a :: u :: r`. obtain ⟨u, r, hp⟩ : ∃ u r, p = a :: u :: r := by match p, hlen, hhead with | _ :: u :: r, _, hhead => exact ⟨u, r, by simp only [List.head?_cons, Option.some_inj] at hhead; subst hhead; rfl⟩ subst hp -- Prepend `n` via the edge `a → n`; `a` is a non-collider in `(n, a, u)`. have hedge : G.edge a n := hA0_par a haA0 have hnotcoll : ¬ G.IsCollider n a u := fun hC => G.asymm hedge hC.1 have htri : (if G.IsCollider n a u then a ∈ G.bbZAncestors D else a ∉ D) := by rw [if_neg hnotcoll]; exact hA0_D a haA0 have hact' : G.IsActiveWalk D (n :: a :: u :: r) := G.isActiveWalk_cons_of_active_triple (inr hedge) htri hact -- This new active walk witnesses `y ∈ bbReachableVertices D {n}`, contradiction. have hyReach' : y ∈ G.bbReachableVertices D {n} := by rw [G.bbReachableVertices_iff_activeWalk] refine ⟨n, mem_singleton_self n, n :: a :: u :: r, by simp, hact', rfl, ?_⟩ simpa using hlast exact (Finset.disjoint_left.mp hdSep.2.2.2 hyReach') hyY -
isActiveWalk_insert_condtheorem — In a finite directed acyclic graph, consider a conditioning set, walk, and added vertex. If the walk is active and the added vertex can occur internally only as a collider, then the walk remains active after that vertex is added to the conditioning set.hypothesesV :sharedType u_1D :Finset Vp :List Vn :Vhact :G.IsActiveWalk D phno :∀ (i : ℕ) (hi : i + 2 < p.length)ifp.get ⟨i + 1, by omega⟩ = nthenG.IsCollider (p.get ⟨i, by omega⟩) (p.get ⟨i + 1, by omega⟩) (p.get ⟨i + 2, hi⟩)conclusionG.IsActiveWalk (insert n D) pProof (Lean source)
theorem isActiveWalk_insert_cond {D : Finset V} {p : List V} {n : V} (hact : G.IsActiveWalk D p) (hno : ∀ (i : ℕ) (hi : i + 2 < p.length), p.get ⟨i + 1, by omega⟩ = n → G.IsCollider (p.get ⟨i, by omega⟩) (p.get ⟨i + 1, by omega⟩) (p.get ⟨i + 2, hi⟩)) : G.IsActiveWalk (insert n D) p := by obtain ⟨hadj, hcoll⟩ := hact refine ⟨hadj, fun i hi => ?_⟩ have hval := hcoll i hi simp only at hval ⊢ set l := p.get ⟨i, by omega⟩ set m := p.get ⟨i + 1, by omega⟩ set r := p.get ⟨i + 2, hi⟩ by_cases hC : G.IsCollider l m r · rw [if_pos hC] at hval ⊢ exact (G.ancestralSet_mono (Finset.subset_insert n D)) hval · rw [if_neg hC] at hval ⊢ rw [Finset.mem_insert, not_or] refine ⟨fun hmn => hC (hno i hi hmn), hval⟩ -
lift_crosscond_to_inserttheorem — In a finite directed acyclic graph, consider conditioning sets, a walk, and an added vertex. If the walk is active under both sets, every interior collider is activated by the first set, and the added vertex can occur internally only as a collider, then the walk is active when only the first set and added vertex are conditioned on.hypothesesV :sharedType u_1S W :Finset Vp :List Vn :Vhact :G.IsActiveWalk (S ∪ W) phcollS :∀ (i : ℕ) (hi : i + 2 < p.length)ifG.IsCollider (p.get ⟨i, by omega⟩) (p.get ⟨i + 1, by omega⟩) (p.get ⟨i + 2, hi⟩)thenp.get ⟨i + 1, by omega⟩ ∈ G.ancestralSet Shno_n :∀ (i : ℕ) (hi : i + 2 < p.length)ifp.get ⟨i + 1, by omega⟩ = nthenG.IsCollider (p.get ⟨i, by omega⟩) (p.get ⟨i + 1, by omega⟩) (p.get ⟨i + 2, hi⟩)conclusionG.IsActiveWalk (insert n S) pProof (Lean source)
theorem lift_crosscond_to_insert {S W : Finset V} {p : List V} {n : V} (hact : G.IsActiveWalk (S ∪ W) p) (hcollS : ∀ (i : ℕ) (hi : i + 2 < p.length), G.IsCollider (p.get ⟨i, by omega⟩) (p.get ⟨i + 1, by omega⟩) (p.get ⟨i + 2, hi⟩) → p.get ⟨i + 1, by omega⟩ ∈ G.ancestralSet S) (hno_n : ∀ (i : ℕ) (hi : i + 2 < p.length), p.get ⟨i + 1, by omega⟩ = n → G.IsCollider (p.get ⟨i, by omega⟩) (p.get ⟨i + 1, by omega⟩) (p.get ⟨i + 2, hi⟩)) : G.IsActiveWalk (insert n S) p := by obtain ⟨hadj, hcoll⟩ := hact refine ⟨hadj, fun i hi => ?_⟩ have hval := hcoll i hi simp only at hval ⊢ set l := p.get ⟨i, by omega⟩ with hl set m := p.get ⟨i + 1, by omega⟩ with hm set r := p.get ⟨i + 2, hi⟩ with hr by_cases hC : G.IsCollider l m r · rw [if_pos hC] at hval ⊢ show m ∈ G.bbZAncestors (insert n S) exact (G.ancestralSet_mono (Finset.subset_insert n S)) (hcollS i hi hC) · rw [if_neg hC] at hval ⊢ rw [Finset.mem_insert, not_or] refine ⟨fun hmn => hC (hno_n i hi hmn), fun hmS => hval (mem_union_left _ hmS)⟩ -
last_edge_into_maxtheorem — In a finite directed acyclic graph, consider a conditioning set, ancestral seed, active walk, and selected endpoint. If the walk is nontrivial, is active, ends at the selected vertex, lies in the seed's ancestral closure, and the endpoint maximizes topological order there, then the walk's final edge points into that endpoint.hypothesesV :sharedType u_1D bigQ :Finset Vp :List Vn :Vhp_len :p.length ≥ 2hact :G.IsActiveWalk D phlast :p.getLast? = some nhpAnc :∀ v ∈ p, v ∈ G.ancestralSet bigQhmax :∀ m ∈ G.ancestralSet bigQ, G.topoOrder m ≤ G.topoOrder nconclusionG.edge (p.get ⟨p.length - 2, by omega⟩) nProof (Lean source)
theorem last_edge_into_max {D bigQ : Finset V} {p : List V} {n : V} (hp_len : p.length ≥ 2) (hact : G.IsActiveWalk D p) (hlast : p.getLast? = some n) (hpAnc : ∀ v ∈ p, v ∈ G.ancestralSet bigQ) (hmax : ∀ m ∈ G.ancestralSet bigQ, G.topoOrder m ≤ G.topoOrder n) : G.edge (p.get ⟨p.length - 2, by omega⟩) n := by -- The last adjacency is between `p[len-2]` and `p[len-1] = n`. have hadj := hact.1 (p.length - 2) (by omega) have hlast_get : p.get ⟨p.length - 2 + 1, by omega⟩ = n := by have hpne : p ≠ [] := by intro h; rw [h] at hp_len; simp at hp_len have h := getLast?_eq_some_getLast hpne rw [hlast] at h have hn_eq : p.getLast hpne = n := Option.some_inj.mp h.symm have hidx : (⟨p.length - 2 + 1, by omega⟩ : Fin p.length) = ⟨p.length - 1, by omega⟩ := Fin.ext (show p.length - 2 + 1 = p.length - 1 by omega) rw [hidx, List.get_eq_getElem, ← hn_eq, List.getLast_eq_getElem] rw [hlast_get] at hadj -- `hadj : UAdj p[len-2] n`. Rule out `edge n p[len-2]` by topo-maximality. rcases hadj with hin | hout · exact hin · exfalso have hAnc := hpAnc _ (List.get_mem p ⟨p.length - 2, by omega⟩) have h1 : G.topoOrder (p.get ⟨p.length - 2, by omega⟩) ≤ G.topoOrder n := hmax _ hAnc have h2 := G.isAncestor_topoOrder_lt (isAncestor.edge hout) omega -
activeWalk_join_at_collidertheorem — In a finite directed acyclic graph, consider a conditioning set, joining vertex, endpoints, and two walks. If the first walk is active from the first endpoint to the join, the second is active from the join to the second endpoint, both seam edges point into the join, and the joining vertex is conditioned on, then concatenating the walks gives an active walk between the endpoints.hypothesesV :sharedType u_1Z :Finset Vn x y :Vpa pb :List Vhpa_len :pa.length ≥ 2hpa_head :pa.head? = some xhpa_last :pa.getLast? = some nhpa_act :G.IsActiveWalk Z pahpb_len :pb.length ≥ 2hpb_head :pb.head? = some nhpb_last :pb.getLast? = some yhpb_act :G.IsActiveWalk Z pbhpa_in :G.edge (pa.get ⟨pa.length - 2, by omega⟩) nhpb_in :G.edge (pb.get ⟨1, by omega⟩) nhnZ :n ∈ Zconclusionp :pa ++ pb.tailp.length ≥ 2p.head? = some xp.getLast? = some yG.IsActiveWalk Z pProof (Lean source)
theorem activeWalk_join_at_collider {Z : Finset V} {n x y : V} {pa pb : List V} (hpa_len : pa.length ≥ 2) (hpa_head : pa.head? = some x) (hpa_last : pa.getLast? = some n) (hpa_act : G.IsActiveWalk Z pa) (hpb_len : pb.length ≥ 2) (hpb_head : pb.head? = some n) (hpb_last : pb.getLast? = some y) (hpb_act : G.IsActiveWalk Z pb) (hpa_in : G.edge (pa.get ⟨pa.length - 2, by omega⟩) n) (hpb_in : G.edge (pb.get ⟨1, by omega⟩) n) (hnZ : n ∈ Z) : let p := pa ++ pb.tail p.length ≥ 2 ∧ p.head? = some x ∧ p.getLast? = some y ∧ G.IsActiveWalk Z p := by have hpa_ne : pa ≠ [] := by intro h; rw [h] at hpa_len; simp at hpa_len have hpb_ne : pb ≠ [] := by intro h; rw [h] at hpb_len; simp at hpb_len -- pb.head = n have hpb_head_eq : pb.head hpb_ne = n := by have h := List.head?_eq_some_head hpb_ne; rw [hpb_head] at h exact (Option.some_inj.mp h.symm) -- pa.getLast = n have hpa_last_eq : pa.getLast hpa_ne = n := by have h := getLast?_eq_some_getLast hpa_ne; rw [hpa_last] at h exact (Option.some_inj.mp h.symm) -- pa.head = x have hpa_head_eq : pa.head hpa_ne = x := by have h := List.head?_eq_some_head hpa_ne; rw [hpa_head] at h exact (Option.some_inj.mp h.symm) -- pb.getLast = y have hpb_last_eq : pb.getLast hpb_ne = y := by have h := getLast?_eq_some_getLast hpb_ne; rw [hpb_last] at h exact (Option.some_inj.mp h.symm) -- pb = n :: pb.tail have hpb_decomp : pb = n :: pb.tail := by conv_lhs => rw [← List.cons_head_tail hpb_ne]; rw [hpb_head_eq] have hpb_tail_len : pb.tail.length = pb.length - 1 := List.length_tail have hpb_tail_ne : pb.tail ≠ [] := by rw [← List.length_pos_iff, hpb_tail_len]; omega set p := pa ++ pb.tail with hp_def set R := pa.length with hR_def have hp_len_eq : p.length = R + pb.tail.length := by simp only [hp_def, List.length_append, ← hR_def] have hp_len : p.length ≥ 2 := by rw [hp_len_eq]; omega have hp_head : p.head? = some x := by have : p.head? = pa.head? := List.head?_append_of_ne_nil _ hpa_ne rw [this, hpa_head] have hp_last : p.getLast? = some y := by have hlast_tail : pb.tail.getLast? = some y := by have heq : pb = [n] ++ pb.tail := by simpa using hpb_decomp have := getLast?_append_of_ne_nil [n] hpb_tail_ne rw [← heq] at this; rw [← this]; exact hpb_last simp only [hp_def, getLast?_append, hlast_tail]; rfl refine ⟨hp_len, hp_head, hp_last, ?_⟩ -- Index translation: p[k] = pa[k] for k < R; p[k] = pb[k-R+1] for k ≥ R. have hp_L : ∀ (k : ℕ) (hkR : k < R), p.get ⟨k, by rw [hp_len_eq]; omega⟩ = pa.get ⟨k, hkR⟩ := by intro k hkR simp only [hp_def, List.get_eq_getElem, List.getElem_append_left (h := hkR)] have hpb_tail_get : ∀ (j : ℕ) (hj1 : j < pb.tail.length) (hj2 : j + 1 < pb.length), pb.tail[j]'hj1 = pb[j + 1]'hj2 := by intro j hj1 hj2 have : pb[j + 1] = (n :: pb.tail)[j + 1]'(by rw [← hpb_decomp]; exact hj2) := by congr 1 rw [this]; simp [List.getElem_cons_succ] have hp_R : ∀ (k : ℕ) (hkL : R ≤ k) (hk : k < p.length), p.get ⟨k, hk⟩ = pb.get ⟨k - R + 1, by rw [hp_len_eq, hpb_tail_len] at hk; omega⟩ := by intro k hkL hk have hk_app : R ≤ k := hkL have htail_idx_lt : k - R < pb.tail.length := by rw [hp_len_eq] at hk; omega have hpget : p.get ⟨k, hk⟩ = pb.tail[k - R]'htail_idx_lt := by simp only [hp_def, List.get_eq_getElem] rw [List.getElem_append_right (by rw [← hR_def]; exact hk_app)] rw [hpget] rw [hpb_tail_get (k - R) htail_idx_lt (by rw [hpb_tail_len] at htail_idx_lt; omega)] simp [List.get_eq_getElem] -- pb[0] = n, pa[R-1] = n. have hpb_get_0 : pb.get ⟨0, by omega⟩ = n := by rw [List.get_eq_getElem, List.getElem_zero]; exact hpb_head_eq have hpa_get_last : pa.get ⟨R - 1, by omega⟩ = n := by rw [List.get_eq_getElem] have := List.getLast_eq_getElem hpa_ne rw [hpa_last_eq] at this rw [← this] refine ⟨?_, ?_⟩ · -- Adjacency. intro i hi rw [hp_len_eq] at hi by_cases hA1 : i + 1 < R · -- Both in pa. have hiR : i < R := by omega rw [hp_L i hiR, hp_L (i + 1) hA1] exact hpa_act.1 i hA1 · push_neg at hA1 by_cases hA2 : i + 1 = R · -- Seam at i = R-1: UAdj(pa[R-1]=n, pb.tail[0]=pb[1]). have hi_eq : i = R - 1 := by omega subst hi_eq have hiR : R - 1 < R := by omega rw [hp_L (R - 1) hiR] have hi1L : R ≤ R - 1 + 1 := by omega have hi1_lt : R - 1 + 1 < p.length := by rw [hp_len_eq]; exact hi rw [hp_R (R - 1 + 1) hi1L hi1_lt] rw [hpa_get_last] have h_yidx : R - 1 + 1 - R + 1 = 1 := by omega rw [show (⟨R - 1 + 1 - R + 1, by rw [hp_len_eq, hpb_tail_len] at hi1_lt; omega⟩ : Fin pb.length) = ⟨1, by omega⟩ from Fin.ext h_yidx] -- UAdj n pb[1] from edge pb[1] → n. exact inr hpb_in · -- Both in pb.tail. i ≥ R. have hiL : R ≤ i := by omega have hi1L : R ≤ i + 1 := by omega have hi_lt : i < p.length := by rw [hp_len_eq]; omega have hi1_lt : i + 1 < p.length := by rw [hp_len_eq]; exact hi rw [hp_R i hiL hi_lt, hp_R (i + 1) hi1L hi1_lt] have hidx : (i - R + 1) + 1 < pb.length := by omega have hadj := hpb_act.1 (i - R + 1) hidx have he : (i - R + 1) + 1 = (i + 1) - R + 1 := by omega rw [show (⟨(i - R + 1) + 1, hidx⟩ : Fin pb.length) = ⟨(i + 1) - R + 1, by omega⟩ from Fin.ext he] at hadj exact hadj · -- Collider condition. intro i hi rw [hp_len_eq] at hi by_cases hC1 : i + 2 < R · -- All three in pa. have hiR : i < R := by omega have hi1R : i + 1 < R := by omega rw [hp_L i hiR, hp_L (i + 1) hi1R, hp_L (i + 2) hC1] exact hpa_act.2 i hC1 · push_neg at hC1 by_cases hC2 : i + 2 = R · -- Seam collider: middle = pa[R-1] = n. Triple (pa[R-2], n, pb[1]). have hi_eq : i = R - 2 := by omega subst hi_eq have hiR : R - 2 < R := by omega have hi1R : R - 2 + 1 < R := by omega have hi2L : R ≤ R - 2 + 2 := by omega have hi2_lt : R - 2 + 2 < p.length := by rw [hp_len_eq]; exact hi rw [hp_L (R - 2) hiR, hp_L (R - 2 + 1) hi1R, hp_R (R - 2 + 2) hi2L hi2_lt] rw [show (⟨R - 2 + 1, by omega⟩ : Fin pa.length) = ⟨R - 1, by omega⟩ from Fin.ext (show R - 2 + 1 = R - 1 by omega)] rw [hpa_get_last] have h_ridx : R - 2 + 2 - R + 1 = 1 := by omega rw [show (⟨R - 2 + 2 - R + 1, by rw [hp_len_eq, hpb_tail_len] at hi2_lt; omega⟩ : Fin pb.length) = ⟨1, by omega⟩ from Fin.ext h_ridx] -- left = pa[R-2]; edge into n is hpa_in (pa[R-2]→n); right = pb[1], edge pb[1]→n. have hLeq : pa.get ⟨R - 2, by omega⟩ = pa.get ⟨pa.length - 2, by omega⟩ := by congr 1 have hColl : G.IsCollider (pa.get ⟨R - 2, by omega⟩) n (pb.get ⟨1, by omega⟩) := by refine ⟨?_, hpb_in⟩ rw [hLeq]; exact hpa_in rw [if_pos hColl] -- n ∈ bbZAncestors Z since n ∈ Z. show n ∈ G.bbZAncestors Z exact G.subset_ancestralSet Z hnZ · by_cases hC3 : i + 1 = R · -- Straddle: middle = pb.tail[0] = pb[1]. Triple (pa[R-1]=n, pb[1], pb[2]) = pb's index 0. have hi_eq : i = R - 1 := by omega subst hi_eq have hiR : R - 1 < R := by omega have hi1L : R ≤ R - 1 + 1 := by omega have hi2L : R ≤ R - 1 + 2 := by omega have hi1_lt : R - 1 + 1 < p.length := by rw [hp_len_eq]; omega have hi2_lt : R - 1 + 2 < p.length := by rw [hp_len_eq]; exact hi rw [hp_L (R - 1) hiR, hp_R (R - 1 + 1) hi1L hi1_lt, hp_R (R - 1 + 2) hi2L hi2_lt] rw [hpa_get_last] rw [show (⟨R - 1 + 1 - R + 1, by omega⟩ : Fin pb.length) = ⟨1, by omega⟩ from Fin.ext (show R - 1 + 1 - R + 1 = 1 by omega)] rw [show (⟨R - 1 + 2 - R + 1, by omega⟩ : Fin pb.length) = ⟨2, by omega⟩ from Fin.ext (show R - 1 + 2 - R + 1 = 2 by omega)] -- This matches pb's collider clause at index 0 (triple pb[0]=n, pb[1], pb[2]). have hpb0 := hpb_act.2 0 (by omega) rw [show (⟨0 + 1, by omega⟩ : Fin pb.length) = ⟨1, by omega⟩ from rfl, show (⟨0 + 2, by omega⟩ : Fin pb.length) = ⟨2, by omega⟩ from rfl] at hpb0 rw [hpb_get_0] at hpb0 exact hpb0 · -- All three in pb.tail. i ≥ R. have hiL : R ≤ i := by omega have hi1L : R ≤ i + 1 := by omega have hi2L : R ≤ i + 2 := by omega have hi_lt : i < p.length := by rw [hp_len_eq]; omega have hi1_lt : i + 1 < p.length := by rw [hp_len_eq]; omega have hi2_lt : i + 2 < p.length := by rw [hp_len_eq]; exact hi rw [hp_R i hiL hi_lt, hp_R (i + 1) hi1L hi1_lt, hp_R (i + 2) hi2L hi2_lt] have hjlen : (i - R + 1) + 2 < pb.length := by omega have hpbcoll := hpb_act.2 (i - R + 1) hjlen rw [show (⟨(i - R + 1) + 1, by omega⟩ : Fin pb.length) = ⟨(i + 1) - R + 1, by omega⟩ from Fin.ext (show (i-R+1)+1 = (i+1)-R+1 by omega), show (⟨(i - R + 1) + 2, hjlen⟩ : Fin pb.length) = ⟨(i + 2) - R + 1, by omega⟩ from Fin.ext (show (i-R+1)+2 = (i+2)-R+1 by omega)] at hpbcoll exact hpbcoll