Graph.Markov­Equiv.Covered­Reversal

Reversing a covered edge of a directed acyclic graph does not change its d-separations.

Flip­Edge 5 core · 6 supporting This file defines covered edges and constructs DAG.flipEdge by reversing one of them. ★ IsCoveredEdge★ flipEdge_sameImmoralities

Constructing a covered-edge reversal

This file defines covered edges and constructs DAG.flipEdge by reversing one of them. It proves that the flipped relation is acyclic and that the resulting DAG has the same skeleton and immoralities as the original graph. Active-walk transport across this construction is developed in ActivePathTransport and PathSurgery; the resulting Markov-equivalence theorem is in MarkovEquivalence.

def IsCoveredEdge reviewed
Causalean.Graph.DAG

For a finite directed acyclic graph on a vertex population and two vertices, the covered-edge condition holds exactly when there is a directed edge from the first vertex to the second and, for every other vertex, that vertex has an edge into the first if and only if it has an edge into the second.

Definition (Lean source)
V :
Type u_1
shared
G :
DAG V
shared
a b :
V
IsCoveredEdge G a b :
Prop
clause 1
G.edge a b
clause 2
c :
c ≠ a
G.edge c a ↔ G.edge c b
def flipMinus reviewed
Causalean.Graph.DAG

For a finite directed acyclic graph on a vertex population and two vertices, the edge relation with one deletion holds for two queried vertices exactly when the original graph has an edge from the first queried vertex to the second and the queried edge is not the edge from the first specified vertex to the second.

Definition (Lean source)
V :
Type u_1
shared
G :
DAG V
shared
a b :
V
flipMinus G a b :
V → V → Prop
fun u w => G.edge u w ∧ ¬ (u = a ∧ w = b)
def flipRel reviewed
Causalean.Graph.DAG

For a finite directed acyclic graph on a vertex population and two vertices, the edge relation with one reversal holds for two queried vertices exactly when the original graph has the queried edge other than the edge from the first specified vertex to the second, or the queried edge is the reversed edge from the second specified vertex to the first.

Definition (Lean source)
V :
Type u_1
shared
G :
DAG V
shared
a b :
V
flipRel G a b :
V → V → Prop
fun u w => G.flipMinus a b u w ∨ (u = b ∧ w = a)
def flipEdge reviewed
Causalean.Graph.DAG

For a finite vertex population, a directed acyclic graph, two vertices, and evidence that their directed edge is covered, the covered-edge reversal graph is the directed acyclic graph obtained by replacing the edge from the first vertex to the second with the edge from the second to the first.

Definition (Lean source)
V :
Type u_1
DAG V
V
shared
hcov :
G.IsCoveredEdge a b
flipEdge hcov :
DAG V
DAG.ofAcyclic (G.flipRel a b) (flipRel_acyclic hcov)
theorem flipEdge_sameImmoralities reviewed
Causalean.Graph.DAG

For a finite directed acyclic graph and two vertices, if the directed edge between them is covered, then reversing that edge preserves every unshielded collider.

Formal statement
V :
Type u_1
DAG V
V
shared
hcov :
G.IsCoveredEdge a b
Proof (Lean source)
theorem flipEdge_sameImmoralities {a b : V} (hcov : G.IsCoveredEdge a b) : SameImmoralities G (flipEdge hcov) := by have hU : ∀ x y, G.UAdj x y ↔ (flipEdge hcov).UAdj x y := flipEdge_sameSkeleton hcov intro p q r constructor · rintro ⟨hpq, hrq, hnadj, hpr⟩ have hpq' : (flipEdge hcov).edge p q := by rw [flipEdge_edge]; left; refine ⟨hpq, ?_⟩ rintro ⟨hpa, hqb⟩ -- collider `a → b ← r`: covered forces `r → a`, contradicting non-adjacency rw [hpa] at hpr hnadj; rw [hqb] at hrq exact hnadj (inr ((hcov.2 r (Ne.symm hpr)).mpr hrq)) have hrq' : (flipEdge hcov).edge r q := by rw [flipEdge_edge]; left; refine ⟨hrq, ?_⟩ rintro ⟨hra, hqb⟩ -- collider `p → b ← a`: covered forces `p → a`, contradicting non-adjacency rw [hra] at hpr hnadj; rw [hqb] at hpq exact hnadj (inl ((hcov.2 p hpr).mpr hpq)) exact ⟨hpq', hrq', fun h => hnadj ((hU p r).mpr h), hpr⟩ · rintro ⟨hpq, hrq, hnadj, hpr⟩ rw [flipEdge_edge] at hpq hrq have hnadjG : ¬ G.UAdj p r := fun h => hnadj ((hU p r).mp h) have hpqG : G.edge p q := by rcases hpq with ⟨h, _⟩ | ⟨hpb, hqa⟩ · exact h · exfalso rcases hrq with ⟨hra, _⟩ | ⟨hrb, _⟩ · rw [hqa] at hra have hrne : r ≠ a := fun heq => G.irrefl _ (heq ▸ hra) apply hnadjG; rw [hpb]; exact inr ((hcov.2 r hrne).mp hra) · exact hpr (hpb.trans hrb.symm) have hrqG : G.edge r q := by rcases hrq with ⟨h, _⟩ | ⟨hrb, hqa⟩ · exact h · exfalso rcases hpq with ⟨hpa, _⟩ | ⟨hpb, _⟩ · rw [hqa] at hpa have hpne : p ≠ a := fun heq => G.irrefl _ (heq ▸ hpa) apply hnadjG; rw [hrb]; exact inl ((hcov.2 p hpne).mp hpa) · exact hpr (hpb.trans hrb.symm) exact ⟨hpqG, hrqG, hnadjG, hpr⟩
6 supporting declarations (lemmas, instances)
  • flipMinus_le theorem — A flipMinus-edge is in particular a G-edge.
    V :
    Type u_1
    DAG V
    V
    shared
    h :
    G.flipMinus a b u w
    G.edge u w
    Proof (Lean source)
    theorem flipMinus_le {a b u w : V} (h : G.flipMinus a b u w) : G.edge u w := h.1
  • topoOrder_lt_of_flipMinus_transGen theorem — A directed flipMinus-path strictly increases the topological order.
    V :
    Type u_1
    DAG V
    V
    shared
    h :
    TransGen (G.flipMinus a b) u w
    G.topoOrder u < G.topoOrder w
    Proof (Lean source)
    theorem topoOrder_lt_of_flipMinus_transGen {a b u w : V} (h : TransGen (G.flipMinus a b) u w) : G.topoOrder u < G.topoOrder w := by induction h with | single he => exact G.topoOrder_lt _ _ he.1 | tail _ he ih => exact lt_trans ih (G.topoOrder_lt _ _ he.1)
    Causalean.Graph.DAG.topoOrder_lt_of_flipMinus_transGen · Causalean/Graph/MarkovEquiv/CoveredReversal/FlipEdge.lean:61
  • ne theorem — A covered edge is genuinely an edge a → b, so a ≠ b.
    V :
    Type u_1
    DAG V
    V
    shared
    h :
    G.IsCoveredEdge a b
    a ≠ b
    Proof (Lean source)
    theorem IsCoveredEdge.ne {a b : V} (h : G.IsCoveredEdge a b) : a ≠ b := by rintro rfl; exact G.irrefl _ h.1
  • flipRel_acyclic theorem — Reversing a covered edge keeps the graph acyclic. The transitive closure of the flipped relation is irreflexive. Key step (AMP Lemma 3.1): a directed G-path a ⇝ b of length ≥ 2 would end at a parent c ≠ a of b, hence (covered) a parent of a, closing a G-cycle; so no such detour exists and the single reversal introduces no cycle.
    V :
    Type u_1
    DAG V
    V
    shared
    hcov :
    G.IsCoveredEdge a b
    v :
    ¬ TransGen (G.flipRel a b) v v
    Proof (Lean source)
    theorem flipRel_acyclic {a b : V} (hcov : G.IsCoveredEdge a b) : ∀ v, ¬ TransGen (G.flipRel a b) v v := by -- last-step destructor for a transitive-closure path have transGen_last : ∀ {z : V}, TransGen (G.flipMinus a b) a z → G.flipMinus a b a z ∨ ∃ c, TransGen (G.flipMinus a b) a c ∧ G.flipMinus a b c z := by intro z h induction h with | single h => exact inl h | tail h1 h2 _ => exact inr ⟨_, h1, h2⟩ -- No `flipMinus`-detour from `a` to `b`: the last vertex `c` of such a path is a parent of -- `b` other than `a`, hence (covered) a parent of `a`, closing a `G`-cycle. have hnodetour : ¬ TransGen (G.flipMinus a b) a b := by intro h rcases transGen_last h with hab | ⟨c, hac, hcb⟩ · exact hab.2 ⟨rfl, rfl⟩ · have hca' : c ≠ a := fun hc => hcb.2 ⟨hc, rfl⟩ have hca : G.edge c a := (hcov.2 c hca').mpr hcb.1 have l1 : G.topoOrder a < G.topoOrder c := topoOrder_lt_of_flipMinus_transGen hac have l2 : G.topoOrder c < G.topoOrder a := G.topoOrder_lt _ _ hca omega -- Any flipped-walk either avoids `b → a` (so it is a `flipMinus`-walk) or it splits as -- `x ⇝ b` (before the first `b → a`) and `a ⇝ y` (after), both `flipMinus`-reachable. have hP : ∀ {x y}, TransGen (G.flipRel a b) x y → TransGen (G.flipMinus a b) x y ∨ (Relation.ReflTransGen (G.flipMinus a b) x b ∧ Relation.ReflTransGen (G.flipMinus a b) a y) := by intro x y h induction h with | single hxy => rcases hxy with h0 | ⟨hxb, hya⟩ · exact inl (Relation.TransGen.single h0) · subst hxb; subst hya exact inr ⟨Relation.ReflTransGen.refl, Relation.ReflTransGen.refl⟩ | @tail c y _ hcy ih => rcases hcy with h0 | ⟨hcb, hya⟩ · rcases ih with hl | ⟨hr1, hr2⟩ · exact inl (hl.tail h0) · exact inr ⟨hr1, hr2.tail h0⟩ · subst hcb; subst hya rcases ih with hl | ⟨hr1, _hr2⟩ · exact inr ⟨hl.to_reflTransGen, Relation.ReflTransGen.refl⟩ · exact inr ⟨hr1, Relation.ReflTransGen.refl⟩ intro v hv rcases hP hv with hl | ⟨hr1, hr2⟩ · exact absurd (topoOrder_lt_of_flipMinus_transGen hl) (lt_irrefl _) · have hab : Relation.ReflTransGen (G.flipMinus a b) a b := hr2.trans hr1 rcases Relation.reflTransGen_iff_eq_or_transGen.mp hab with heq | htr · exact hcov.ne heq.symm · exact hnodetour htr
  • flipEdge_edge theorem — In the graph obtained by reversing a covered edge, the edges are exactly the old edges except for deleting a → b and adding b → a.
    V :
    Type u_1
    DAG V
    V
    shared
    hcov :
    G.IsCoveredEdge a b
    u w :
    V
    (flipEdge hcov).edge u w ↔ (G.edge u w ∧ ¬ (u = a ∧ w = b)) ∨ (u = b ∧ w = a)
    Proof (Lean source)
    @[simp] theorem flipEdge_edge {a b : V} (hcov : G.IsCoveredEdge a b) (u w : V) : (flipEdge hcov).edge u w ↔ (G.edge u w ∧ ¬ (u = a ∧ w = b)) ∨ (u = b ∧ w = a) := by rfl
  • flipEdge_sameSkeleton theorem — Reversing a covered edge preserves the skeleton. The undirected adjacency is unchanged: only the orientation of the single edge a — b flips.
    V :
    Type u_1
    DAG V
    V
    shared
    hcov :
    G.IsCoveredEdge a b
    Proof (Lean source)
    theorem flipEdge_sameSkeleton {a b : V} (hcov : G.IsCoveredEdge a b) : SameSkeleton G (flipEdge hcov) := by intro u w simp only [DAG.UAdj, flipEdge_edge] constructor · rintro (h | h) · by_cases hab : u = a ∧ w = b · obtain ⟨rfl, rfl⟩ := hab; exact inr (inr ⟨rfl, rfl⟩) · exact inl (inl ⟨h, hab⟩) · by_cases hab : w = a ∧ u = b · obtain ⟨rfl, rfl⟩ := hab; exact inl (inr ⟨rfl, rfl⟩) · exact inr (inl ⟨h, hab⟩) · rintro ((⟨h, _⟩ | ⟨rfl, rfl⟩) | (⟨h, _⟩ | ⟨rfl, rfl⟩)) · exact inl h · exact inr hcov.1 · exact inr h · exact inl hcov.1
    Causalean.Graph.DAG.flipEdge_sameSkeleton · Causalean/Graph/MarkovEquiv/CoveredReversal/FlipEdge.lean:140
Active­Path­Transport 5 core · 26 supporting This file develops the local invariance facts used to transport active walks across DAG.flipEdge. ★ isActiveWalk_flip_of_not_mem

Active-walk transport ingredients for covered-edge reversal

This file develops the local invariance facts used to transport active walks across DAG.flipEdge. It relates d-connection to active walks, proves edge-relation congruence lemmas, tracks ancestry through a covered reversal, and defines swapPath together with the obstruction and list-surgery operations used by PathSurgery. The final transport argument and markovEquiv_flipEdge are in MarkovEquivalence.

theorem isActiveWalk_flip_of_not_mem reviewed
Causalean.Graph.DAG

For a DAG, edge endpoints, conditioning set, and walk, if the edge is covered, the walk is active, and the walk omits the reversed edge's tail, then that walk remains active after the covered reversal.

Formal statement
V :
Type u_1
DAG V
V
shared
hcov :
G.IsCoveredEdge a b
Z :
p :
hact :
G.IsActiveWalk Z p
hna :
a ∉ p
Proof (Lean source)
theorem isActiveWalk_flip_of_not_mem {a b : V} (hcov : G.IsCoveredEdge a b) {Z : Finset V} {p : List V} (hact : G.IsActiveWalk Z p) (hna : a ∉ p) : (flipEdge hcov).IsActiveWalk Z p := by obtain ⟨hadj, htri⟩ := hact refine ⟨fun i hi => ?_, fun i hi => ?_⟩ · exact (flipEdge_sameSkeleton hcov _ _).mp (hadj i hi) · let l := p.get ⟨i, by omega⟩ let m := p.get ⟨i + 1, by omega⟩ let r := p.get ⟨i + 2, hi⟩ have hla : l ≠ a := by intro h apply hna rw [← h] exact List.get_mem p ⟨i, by omega⟩ have hma : m ≠ a := by intro h apply hna rw [← h] exact List.get_mem p ⟨i + 1, by omega⟩ have hra : r ≠ a := by intro h apply hna rw [← h] exact List.get_mem p ⟨i + 2, hi⟩ have hciff : (flipEdge hcov).IsCollider l m r ↔ G.IsCollider l m r := flipEdge_isCollider_iff_of_ne_a hcov hla hma hra have hval := htri i hi change (if G.IsCollider l m r then m ∈ G.bbZAncestors Z else m ∉ Z) at hval change (if (flipEdge hcov).IsCollider l m r then m ∈ (flipEdge hcov).bbZAncestors Z else m ∉ Z) by_cases hC : G.IsCollider l m r · rw [if_pos ((hciff).mpr hC)] exact bbZAncestors_flip_of_ne hcov Z hma (by rwa [if_pos hC] at hval) · have hCF : ¬ (flipEdge hcov).IsCollider l m r := fun h => hC ((hciff).mp h) rw [if_neg hCF] rwa [if_neg hC] at hval
def swapPath reviewed
Causalean.Graph.DAG

The walk p with each interior collider occurrence of a whose activation is lost in the flipped graph (a ∉ (flipEdge).bbZAncestors Z) replaced by b. Same length as p; endpoints are never touched (they are not interior).

Definition (Lean source)
V :
Type u_1
DAG V
V
shared
hcov :
G.IsCoveredEdge a b
Z :
p :
swapPath hcov Z p :
List.ofFn (fun i : Fin p.length => if h : 1 ≤ i.val ∧ i.val + 1 < p.length then if p.get i = a ∧ a ∉ (flipEdge hcov).bbZAncestors Z ∧ G.IsCollider (p.get ⟨i.val - 1, by have := i.isLt; omega⟩) (p.get i) (p.get ⟨i.val + 1, h.2⟩) then b else p.get i else p.get i)
def FlipObstruct reviewed
Causalean.Graph.DAG

The (l, m, r) configurations that the covered flip turns into an un-repairable block. Each is an a/b adjacency whose far arrowhead either makes a an inactive collider after the flip (clauses 1–2) or destroys the active collider at b (clauses 3–4). The two backtracks b — a — b and a — b — a are special cases (the first via clause 1 with r = b, the second via clause 4 with l = a, since a → b). swapPath cannot repair these locally; the assembly removes them by dropping/excising one vertex before swapping.

Definition (Lean source)
V :
Type u_1
DAG V
shared
a b l m r :
V
FlipObstruct a b l m r :
Prop
(m = a ∧ l = b ∧ (G.edge r a ∨ r = b)) ∨ (m = a ∧ r = b ∧ (G.edge l a ∨ l = b)) ∨ (m = b ∧ l = a ∧ G.edge r b) ∨ (m = b ∧ r = a ∧ G.edge l b)
def skipOne reviewed
Causalean.Graph.DAG

A list of values and an index determine the list of length one less whose entries before the index are unchanged and whose later entries are shifted forward by one.

Definition (Lean source)
V :
Type u_1
shared
p :
k :
ℕ
skipOne p k :
List.ofFn (fun i : Fin (p.length - 1) => if h : i.val < k then p.get ⟨i.val, by omega⟩ else p.get ⟨i.val + 1, by omega⟩)
def skipTwo reviewed
Causalean.Graph.DAG

A list of values and an index determine the list of length two less whose entries before the index are unchanged and whose later entries are shifted forward by two.

Definition (Lean source)
V :
Type u_1
shared
p :
k :
ℕ
skipTwo p k :
List.ofFn (fun i : Fin (p.length - 2) => if h : i.val < k then p.get ⟨i.val, by omega⟩ else p.get ⟨i.val + 2, by omega⟩)
26 supporting declarations (lemmas, instances)
  • not_dSep_iff_hasActiveWalk theorem — Bridge: failure of d-separation is exactly an active walk. For pairwise-disjoint query sets, X and Y are *not* d-separated by Z iff there is an active walk from X to Y given Z. Assembled from bbReachableVertices_iff_activeWalk. Reduces the covered-flip invariance to a pure active-walk statement.
    V :
    Type u_1
    shared
    H :
    DAG V
    X Y Z :
    hXY :
    hXZ :
    hYZ :
    ¬ H.dSep X Y Z ↔ H.HasActiveWalk X Y Z
    Proof (Lean source)
    theorem not_dSep_iff_hasActiveWalk (H : DAG V) (X Y Z : Finset V) (hXY : Disjoint X Y) (hXZ : Disjoint X Z) (hYZ : Disjoint Y Z) : ¬ H.dSep X Y Z ↔ H.HasActiveWalk X Y Z := by unfold DAG.dSep DAG.HasActiveWalk constructor · intro hnot have hReach : ¬ Disjoint (H.bbReachableVertices Z X) Y := by intro hReach exact hnot ⟨hXY, hXZ, hYZ, hReach⟩ rw [Finset.not_disjoint_iff] at hReach obtain ⟨v, hvR, hvY⟩ := hReach obtain ⟨x, hxX, p, hlen, hact, hhead, hlast⟩ := (H.bbReachableVertices_iff_activeWalk X Z v).mp hvR exact ⟨p, hlen, hact, by rw [hhead]; exact Finset.mem_image_of_mem _ hxX, by rw [hlast]; exact Finset.mem_image_of_mem _ hvY⟩ · rintro ⟨p, hlen, hact, hhead, hlast⟩ hsep obtain ⟨x, hxX, hx⟩ := Finset.mem_image.mp hhead obtain ⟨v, hvY, hv⟩ := Finset.mem_image.mp hlast have hvReach : v ∈ H.bbReachableVertices Z X := by rw [H.bbReachableVertices_iff_activeWalk] exact ⟨x, hxX, p, hlen, hact, by rw [hx], by rw [hv]⟩ exact (Finset.disjoint_left.mp hsep.2.2.2 hvReach) hvY
    Causalean.Graph.DAG.not_dSep_iff_hasActiveWalk · Causalean/Graph/MarkovEquiv/CoveredReversal/ActivePathTransport.lean:33
  • isAncestor_edge_congr theorem — Graphs with the same directed edges have exactly the same ancestor relations.
    V :
    Type u_1
    DAG V
    shared
    he :
    ∀ u w : V, G₁.edge u w ↔ G₂.edge u w
    u v :
    V
    G₁.isAncestor u v ↔ G₂.isAncestor u v
    Proof (Lean source)
    theorem isAncestor_edge_congr {G₁ G₂ : DAG V} (he : ∀ u w : V, G₁.edge u w ↔ G₂.edge u w) {u v : V} : G₁.isAncestor u v ↔ G₂.isAncestor u v := by constructor · intro h induction h with | edge h => exact isAncestor.edge ((he _ _).mp h) | trans _ h ih => exact isAncestor.trans ih ((he _ _).mp h) · intro h induction h with | edge h => exact isAncestor.edge ((he _ _).mpr h) | trans _ h ih => exact isAncestor.trans ih ((he _ _).mpr h)
  • bbZAncestors_edge_congr theorem — Graphs with the same directed edges have exactly the same sets of vertices that are ancestors of the conditioning set and can activate colliders.
    V :
    Type u_1
    DAG V
    shared
    he :
    ∀ u w : V, G₁.edge u w ↔ G₂.edge u w
    Z :
    v :
    V
    v ∈ G₁.bbZAncestors Z ↔ v ∈ G₂.bbZAncestors Z
    Proof (Lean source)
    theorem bbZAncestors_edge_congr {G₁ G₂ : DAG V} (he : ∀ u w : V, G₁.edge u w ↔ G₂.edge u w) (Z : Finset V) (v : V) : v ∈ G₁.bbZAncestors Z ↔ v ∈ G₂.bbZAncestors Z := by simp only [bbZAncestors, ancestralSet, ancestorsSet, mem_union, mem_filter, Finset.mem_univ, true_and] constructor · rintro (hv | ⟨w, hw, h⟩) · exact inl hv · exact inr ⟨w, hw, (isAncestor_edge_congr he).mp h⟩ · rintro (hv | ⟨w, hw, h⟩) · exact inl hv · exact inr ⟨w, hw, (isAncestor_edge_congr he).mpr h⟩
  • uAdj_edge_congr theorem — Graphs with the same directed edges have exactly the same undirected adjacencies.
    V :
    Type u_1
    DAG V
    shared
    he :
    ∀ u w : V, G₁.edge u w ↔ G₂.edge u w
    u v :
    V
    G₁.UAdj u v ↔ G₂.UAdj u v
    Proof (Lean source)
    theorem uAdj_edge_congr {G₁ G₂ : DAG V} (he : ∀ u w : V, G₁.edge u w ↔ G₂.edge u w) {u v : V} : G₁.UAdj u v ↔ G₂.UAdj u v := by unfold UAdj exact or_congr (he u v) (he v u)
  • isCollider_edge_congr theorem — Graphs with the same directed edges have exactly the same collider triples.
    V :
    Type u_1
    DAG V
    shared
    he :
    ∀ u w : V, G₁.edge u w ↔ G₂.edge u w
    l m r :
    V
    G₁.IsCollider l m r ↔ G₂.IsCollider l m r
    Proof (Lean source)
    theorem isCollider_edge_congr {G₁ G₂ : DAG V} (he : ∀ u w : V, G₁.edge u w ↔ G₂.edge u w) {l m r : V} : G₁.IsCollider l m r ↔ G₂.IsCollider l m r := by unfold IsCollider exact and_congr (he l m) (he r m)
  • isActiveWalk_edge_congr theorem — Graphs with the same directed edges have exactly the same active walks for every conditioning set.
    V :
    Type u_1
    DAG V
    shared
    he :
    ∀ u w : V, G₁.edge u w ↔ G₂.edge u w
    Z :
    p :
    G₁.IsActiveWalk Z p ↔ G₂.IsActiveWalk Z p
    Proof (Lean source)
    theorem isActiveWalk_edge_congr {G₁ G₂ : DAG V} (he : ∀ u w : V, G₁.edge u w ↔ G₂.edge u w) (Z : Finset V) (p : List V) : G₁.IsActiveWalk Z p ↔ G₂.IsActiveWalk Z p := by constructor · intro h obtain ⟨hadj, htri⟩ := h refine ⟨fun i hi => (uAdj_edge_congr he).mp (hadj i hi), fun i hi => ?_⟩ let l := p.get ⟨i, by omega⟩ let m := p.get ⟨i + 1, by omega⟩ let r := p.get ⟨i + 2, hi⟩ have hciff : G₁.IsCollider l m r ↔ G₂.IsCollider l m r := isCollider_edge_congr he have haiff : m ∈ G₁.bbZAncestors Z ↔ m ∈ G₂.bbZAncestors Z := bbZAncestors_edge_congr he Z m have hval := htri i hi change (if G₁.IsCollider l m r then m ∈ G₁.bbZAncestors Z else m ∉ Z) at hval change (if G₂.IsCollider l m r then m ∈ G₂.bbZAncestors Z else m ∉ Z) by_cases hC : G₁.IsCollider l m r · rw [if_pos (hciff.mp hC), ← haiff] rwa [if_pos hC] at hval · have hC₂ : ¬ G₂.IsCollider l m r := fun h => hC (hciff.mpr h) rw [if_neg hC₂] rwa [if_neg hC] at hval · intro h obtain ⟨hadj, htri⟩ := h refine ⟨fun i hi => (uAdj_edge_congr he).mpr (hadj i hi), fun i hi => ?_⟩ let l := p.get ⟨i, by omega⟩ let m := p.get ⟨i + 1, by omega⟩ let r := p.get ⟨i + 2, hi⟩ have hciff : G₁.IsCollider l m r ↔ G₂.IsCollider l m r := isCollider_edge_congr he have haiff : m ∈ G₁.bbZAncestors Z ↔ m ∈ G₂.bbZAncestors Z := bbZAncestors_edge_congr he Z m have hval := htri i hi change (if G₂.IsCollider l m r then m ∈ G₂.bbZAncestors Z else m ∉ Z) at hval change (if G₁.IsCollider l m r then m ∈ G₁.bbZAncestors Z else m ∉ Z) by_cases hC : G₂.IsCollider l m r · rw [if_pos (hciff.mpr hC), haiff] rwa [if_pos hC] at hval · have hC₁ : ¬ G₁.IsCollider l m r := fun h => hC (hciff.mp h) rw [if_neg hC₁] rwa [if_neg hC] at hval
  • hasActiveWalk_edge_congr theorem — Equal directed-edge relations preserve existence of active walks.
    V :
    Type u_1
    DAG V
    shared
    he :
    ∀ u w : V, G₁.edge u w ↔ G₂.edge u w
    X Y Z :
    G₁.HasActiveWalk X Y Z ↔ G₂.HasActiveWalk X Y Z
    Proof (Lean source)
    theorem hasActiveWalk_edge_congr {G₁ G₂ : DAG V} (he : ∀ u w : V, G₁.edge u w ↔ G₂.edge u w) (X Y Z : Finset V) : G₁.HasActiveWalk X Y Z ↔ G₂.HasActiveWalk X Y Z := by constructor · rintro ⟨p, hlen, hact, hhead, hlast⟩ exact ⟨p, hlen, (isActiveWalk_edge_congr he Z p).mp hact, hhead, hlast⟩ · rintro ⟨p, hlen, hact, hhead, hlast⟩ exact ⟨p, hlen, (isActiveWalk_edge_congr he Z p).mpr hact, hhead, hlast⟩
  • flipEdge_isCoveredEdge_back theorem — In the flipped graph, the reversed edge b → a is covered.
    V :
    Type u_1
    DAG V
    V
    shared
    hcov :
    G.IsCoveredEdge a b
    Proof (Lean source)
    theorem flipEdge_isCoveredEdge_back {a b : V} (hcov : G.IsCoveredEdge a b) : (flipEdge hcov).IsCoveredEdge b a := by constructor · rw [flipEdge_edge] exact inr ⟨rfl, rfl⟩ · intro c hcb by_cases hca : c = a · subst c rw [flipEdge_edge, flipEdge_edge] constructor · rintro (⟨_, hnot⟩ | ⟨hab, _⟩) · exact absurd ⟨rfl, rfl⟩ hnot · exact absurd hab hcov.ne · rintro (⟨haa, _⟩ | ⟨hab, _⟩) · exact absurd haa (G.irrefl a) · exact absurd hab hcov.ne · rw [flipEdge_edge, flipEdge_edge] constructor · rintro (⟨hcbG, _⟩ | ⟨hcb', _⟩) · left refine ⟨(hcov.2 c hca).mpr hcbG, ?_⟩ rintro ⟨hca', _⟩ exact hca hca' · exact absurd hcb' hcb · rintro (⟨hcaG, _⟩ | ⟨hcb', _⟩) · left refine ⟨(hcov.2 c hca).mp hcaG, ?_⟩ rintro ⟨hca', _⟩ exact hca hca' · exact absurd hcb' hcb
    Causalean.Graph.DAG.flipEdge_isCoveredEdge_back · Causalean/Graph/MarkovEquiv/CoveredReversal/ActivePathTransport.lean:157
  • flipEdge_flipEdge_edge theorem — Flipping the reversed covered edge restores the original edge relation.
    V :
    Type u_1
    DAG V
    V
    shared
    hcov :
    G.IsCoveredEdge a b
    u w :
    V
    (flipEdge (flipEdge_isCoveredEdge_back hcov)).edge u w ↔ G.edge u w
    Proof (Lean source)
    theorem flipEdge_flipEdge_edge {a b : V} (hcov : G.IsCoveredEdge a b) : ∀ u w : V, (flipEdge (flipEdge_isCoveredEdge_back hcov)).edge u w ↔ G.edge u w := by intro u w rw [flipEdge_edge, flipEdge_edge] constructor · rintro (h | ⟨hub, hwa⟩) · rcases h with ⟨h, hnot⟩ rcases h with ⟨hG, hnot_ab⟩ | ⟨hub, hwa⟩ · exact hG · exact absurd ⟨hub, hwa⟩ hnot · subst hub; subst hwa exact hcov.1 · intro hG by_cases hab : u = a ∧ w = b · obtain ⟨rfl, rfl⟩ := hab exact inr ⟨rfl, rfl⟩ · left refine ⟨Or.inl ⟨hG, hab⟩, ?_⟩ rintro ⟨hub, hwa⟩ subst hub; subst hwa exact G.asymm hcov.1 hG
  • isAncestor_last theorem — If one node is a strict ancestor of another in a directed acyclic graph, then it either has a direct edge to the latter or is a strict ancestor of a node that has a direct edge to the latter.
    V :
    Type u_1
    DAG V
    V
    shared
    h :
    H.isAncestor u v
    H.edge u v ∨ ∃ w, H.isAncestor u w ∧ H.edge w v
    Proof (Lean source)
    theorem isAncestor_last {H : DAG V} {u v : V} (h : H.isAncestor u v) : H.edge u v ∨ ∃ w, H.isAncestor u w ∧ H.edge w v := by induction h with | edge he => exact inl he | trans h₁ he _ => exact inr ⟨_, h₁, he⟩
  • flipEdge_edge_or_deleted theorem — Under a covered reversal of the edge from a to b, every directed edge of the original graph either remains an edge or is the deleted edge from a to b.
    V :
    Type u_1
    DAG V
    V
    shared
    hcov :
    G.IsCoveredEdge a b
    he :
    G.edge u w
    (flipEdge hcov).edge u w ∨ (u = a ∧ w = b)
    Proof (Lean source)
    theorem flipEdge_edge_or_deleted {a b u w : V} (hcov : G.IsCoveredEdge a b) (he : G.edge u w) : (flipEdge hcov).edge u w ∨ (u = a ∧ w = b) := by by_cases hab : u = a ∧ w = b · exact inr hab · exact inl (by rw [flipEdge_edge] exact inl ⟨he, hab⟩)
  • bbZAncestors_flip_of_ne theorem — If a vertex other than the reversed tail belongs to the Bayes-ball ancestor closure of a conditioning set before a covered reversal, it belongs to that closure after the reversal.
    V :
    Type u_1
    DAG V
    V
    shared
    hcov :
    G.IsCoveredEdge a b
    Z :
    hva :
    v ≠ a
    v ∈ G.bbZAncestors Z
    v ∈ (flipEdge hcov).bbZAncestors Z
    Proof (Lean source)
    theorem bbZAncestors_flip_of_ne {a b v : V} (hcov : G.IsCoveredEdge a b) (Z : Finset V) (hva : v ≠ a) : v ∈ G.bbZAncestors Z → v ∈ (flipEdge hcov).bbZAncestors Z := by simp only [bbZAncestors, ancestralSet, ancestorsSet, mem_union, mem_filter, Finset.mem_univ, true_and] rintro (hvZ | ⟨z, hzZ, hvz⟩) · exact inl hvZ · exact inr ⟨z, hzZ, isAncestor_flip hcov hva hvz⟩
  • bbZAncestors_flip_of_b theorem — The vertex b keeps its Bayes-ball ancestor-set membership after flipping a → b.
    V :
    Type u_1
    DAG V
    V
    shared
    hcov :
    G.IsCoveredEdge a b
    Z :
    b ∈ G.bbZAncestors Z
    b ∈ (flipEdge hcov).bbZAncestors Z
    Proof (Lean source)
    theorem bbZAncestors_flip_of_b {a b : V} (hcov : G.IsCoveredEdge a b) (Z : Finset V) : b ∈ G.bbZAncestors Z → b ∈ (flipEdge hcov).bbZAncestors Z := bbZAncestors_flip_of_ne hcov Z hcov.ne.symm
  • bbZAncestors_flip_of_lost theorem — If a reaches Z in G but not after the flip, that route used a → b, so b reaches Z; hence b is activated in the flipped graph.
    V :
    Type u_1
    DAG V
    V
    shared
    hcov :
    G.IsCoveredEdge a b
    Z :
    h1 :
    a ∈ G.bbZAncestors Z
    h2 :
    a ∉ (flipEdge hcov).bbZAncestors Z
    b ∈ (flipEdge hcov).bbZAncestors Z
    Proof (Lean source)
    theorem bbZAncestors_flip_of_lost {a b : V} (hcov : G.IsCoveredEdge a b) (Z : Finset V) (h1 : a ∈ G.bbZAncestors Z) (h2 : a ∉ (flipEdge hcov).bbZAncestors Z) : b ∈ (flipEdge hcov).bbZAncestors Z := by apply bbZAncestors_flip_of_b hcov Z simp only [bbZAncestors, ancestralSet, ancestorsSet, mem_union, mem_filter, Finset.mem_univ, true_and] at h1 ⊢ have hmemflip : ∀ {w : V}, (∃ z ∈ Z, (flipEdge hcov).isAncestor w z) → w ∈ (flipEdge hcov).bbZAncestors Z := by intro w hw simp only [bbZAncestors, ancestralSet, ancestorsSet, mem_union, mem_filter, Finset.mem_univ, true_and] exact Or.inr hw have haZ : a ∉ Z := fun h => h2 (hmemflip ⟨a, h, isAncestor.edge (by rw [flipEdge_edge]; exact Or.inr ⟨rfl, rfl⟩)⟩ |> fun _ => by simp only [bbZAncestors, ancestralSet, ancestorsSet, mem_union, mem_filter, Finset.mem_univ, true_and]; exact inl h) rcases h1 with hZ | ⟨z, hzZ, haz⟩ · exact absurd hZ haZ · rcases G.isAncestor_child haz with hedge | ⟨c, hac, hcz⟩ · by_cases hzb : z = b · exact inl (hzb ▸ hzZ) · exact absurd (hmemflip ⟨z, hzZ, isAncestor.edge (by rw [flipEdge_edge]; exact inl ⟨hedge, fun h => hzb h.2⟩)⟩) h2 · by_cases hcb : c = b · subst hcb; exact Or.inr ⟨z, hzZ, hcz⟩ · have hca : c ≠ a := fun h => G.irrefl a (h ▸ hac) have hacF : (flipEdge hcov).edge a c := by rw [flipEdge_edge]; exact inl ⟨hac, fun h => hcb h.2⟩ exact absurd (hmemflip ⟨z, hzZ, (flipEdge hcov).isAncestor_trans (isAncestor.edge hacF) (isAncestor_flip hcov hca hcz)⟩) h2
  • flipEdge_edge_iff_of_ne_a theorem — Away from a, the covered flip leaves directed edges unchanged.
    V :
    Type u_1
    DAG V
    V
    shared
    hcov :
    G.IsCoveredEdge a b
    hua :
    u ≠ a
    hwa :
    w ≠ a
    (flipEdge hcov).edge u w ↔ G.edge u w
    Proof (Lean source)
    theorem flipEdge_edge_iff_of_ne_a {a b u w : V} (hcov : G.IsCoveredEdge a b) (hua : u ≠ a) (hwa : w ≠ a) : (flipEdge hcov).edge u w ↔ G.edge u w := by rw [flipEdge_edge] constructor · rintro (⟨he, _⟩ | ⟨_, hwa'⟩) · exact he · exact absurd hwa' hwa · intro he exact inl ⟨he, fun hdel => hua hdel.1⟩
  • swapPath_length theorem — For a directed acyclic graph, a covered edge, a conditioning set, and a walk, replacing affected collider occurrences along the walk preserves its length.
    V :
    Type u_1
    DAG V
    V
    shared
    hcov :
    G.IsCoveredEdge a b
    Z :
    p :
    (swapPath hcov Z p).length = p.length
    Proof (Lean source)
    @[simp] theorem swapPath_length {a b : V} (hcov : G.IsCoveredEdge a b) (Z : Finset V) (p : List V) : (swapPath hcov Z p).length = p.length := by simp [swapPath]
  • swapPath_get_eq theorem — Each entry of swapPath is either the original vertex, or b at an interior collider occurrence of a whose activation is lost.
    V :
    Type u_1
    DAG V
    V
    shared
    hcov :
    G.IsCoveredEdge a b
    Z :
    p :
    i :
    ℕ
    hi :
    i < (swapPath hcov Z p).length
    hi' :
    i < p.length
    (swapPath hcov Z p).get ⟨i, hi⟩
    = p.get ⟨i, hi'⟩ ∨ ((swapPath hcov Z p).get ⟨i, hi⟩ = b ∧ p.get ⟨i, hi'⟩ = a ∧ 1 ≤ i ∧ ∃ (h2 : i + 1 < p.length), G.IsCollider (p.get ⟨i - 1, by omega⟩) (p.get ⟨i, hi'⟩) (p.get ⟨i + 1, h2⟩) ∧ a ∉ (flipEdge hcov).bbZAncestors Z)
    Proof (Lean source)
    theorem swapPath_get_eq {a b : V} (hcov : G.IsCoveredEdge a b) (Z : Finset V) (p : List V) (i : ℕ) (hi : i < (swapPath hcov Z p).length) (hi' : i < p.length) : (swapPath hcov Z p).get ⟨i, hi⟩ = p.get ⟨i, hi'⟩ ∨ ((swapPath hcov Z p).get ⟨i, hi⟩ = b ∧ p.get ⟨i, hi'⟩ = a ∧ 1 ≤ i ∧ ∃ (h2 : i + 1 < p.length), G.IsCollider (p.get ⟨i - 1, by omega⟩) (p.get ⟨i, hi'⟩) (p.get ⟨i + 1, h2⟩) ∧ a ∉ (flipEdge hcov).bbZAncestors Z) := by simp only [swapPath, List.get_ofFn, Fin.cast_mk] split_ifs with h hc · exact inr ⟨rfl, hc.1, h.1, h.2, hc.2.2, hc.2.1⟩ · exact inl rfl · exact inl rfl
  • swapPath_get_b_of theorem — When the swap predicate holds at i, the entry is b.
    V :
    Type u_1
    DAG V
    V
    shared
    hcov :
    G.IsCoveredEdge a b
    Z :
    p :
    i :
    ℕ
    hi :
    i < (swapPath hcov Z p).length
    hi' :
    i < p.length
    h1 :
    1 ≤ i
    h2 :
    i + 1 < p.length
    ha :
    p.get ⟨i, hi'⟩ = a
    hanc :
    a ∉ (flipEdge hcov).bbZAncestors Z
    hcol :
    G.IsCollider (p.get ⟨i - 1, by omega⟩) (p.get ⟨i, hi'⟩) (p.get ⟨i + 1, h2⟩)
    (swapPath hcov Z p).get ⟨i, hi⟩ = b
    Proof (Lean source)
    theorem swapPath_get_b_of {a b : V} (hcov : G.IsCoveredEdge a b) (Z : Finset V) (p : List V) (i : ℕ) (hi : i < (swapPath hcov Z p).length) (hi' : i < p.length) (h1 : 1 ≤ i) (h2 : i + 1 < p.length) (ha : p.get ⟨i, hi'⟩ = a) (hanc : a ∉ (flipEdge hcov).bbZAncestors Z) (hcol : G.IsCollider (p.get ⟨i - 1, by omega⟩) (p.get ⟨i, hi'⟩) (p.get ⟨i + 1, h2⟩)) : (swapPath hcov Z p).get ⟨i, hi⟩ = b := by simp only [swapPath, List.get_ofFn, Fin.cast_mk] rw [dif_pos ⟨h1, h2⟩, if_pos ⟨ha, hanc, hcol⟩]
  • flipEdge_edge_into_iff theorem — An edge into a vertex m ∉ {a, b} is unaffected by the flip.
    V :
    Type u_1
    DAG V
    V
    shared
    hcov :
    G.IsCoveredEdge a b
    hma :
    m ≠ a
    hmb :
    m ≠ b
    u :
    V
    (flipEdge hcov).edge u m ↔ G.edge u m
    Proof (Lean source)
    theorem flipEdge_edge_into_iff {a b m : V} (hcov : G.IsCoveredEdge a b) (hma : m ≠ a) (hmb : m ≠ b) (u : V) : (flipEdge hcov).edge u m ↔ G.edge u m := by rw [flipEdge_edge] refine ⟨fun h => h.elim (·.1) (fun hh => absurd hh.2 hma), fun h => inl ⟨h, fun hh => hmb hh.2⟩⟩
  • bbZAncestors_of_edge theorem — Ancestor-set membership moves backwards along a directed edge.
    V :
    Type u_1
    DAG V
    V
    shared
    huw :
    G.edge u w
    hw :
    w ∈ G.bbZAncestors Z
    u ∈ G.bbZAncestors Z
    Proof (Lean source)
    theorem bbZAncestors_of_edge {Z : Finset V} {u w : V} (huw : G.edge u w) (hw : w ∈ G.bbZAncestors Z) : u ∈ G.bbZAncestors Z := by simp only [bbZAncestors, ancestralSet, ancestorsSet, mem_union, mem_filter, Finset.mem_univ, true_and] at hw ⊢ rcases hw with hwZ | ⟨z, hzZ, hwz⟩ · exact inr ⟨w, hwZ, isAncestor.edge huw⟩ · exact inr ⟨z, hzZ, G.isAncestor_trans (isAncestor.edge huw) hwz⟩
  • skipOne_length theorem — For a list of values and an index, skipping one entry reduces the length by one.
    V :
    Type u_1
    shared
    p :
    k :
    ℕ
    (skipOne p k).length = p.length - 1
    Proof (Lean source)
    @[simp] theorem skipOne_length (p : List V) (k : ℕ) : (skipOne p k).length = p.length - 1 := by simp [skipOne]
  • skipTwo_length theorem — For a list of values and an index, skipping two entries reduces the length by two.
    V :
    Type u_1
    shared
    p :
    k :
    ℕ
    (skipTwo p k).length = p.length - 2
    Proof (Lean source)
    @[simp] theorem skipTwo_length (p : List V) (k : ℕ) : (skipTwo p k).length = p.length - 2 := by simp [skipTwo]
  • skipOne_head? theorem — If an index is positive and lies within a list of values, then skipping that entry preserves the first entry.
    V :
    Type u_1
    shared
    k :
    ℕ
    hk0 :
    0 < k
    hk :
    k < p.length
    (skipOne p k).head? = p.head?
    Proof (Lean source)
    theorem skipOne_head? {p : List V} {k : ℕ} (hk0 : 0 < k) (hk : k < p.length) : (skipOne p k).head? = p.head? := by rw [List.head?_eq_getElem?, List.head?_eq_getElem?] have hp0 : 0 < p.length := by omega have hq0 : 0 < (skipOne p k).length := by simp [skipOne_length]; omega rw [List.getElem?_eq_getElem hq0, List.getElem?_eq_getElem hp0] simp [skipOne, hk0]
  • skipOne_getLast? theorem — If an index and its successor lie before the end of a list of values, then skipping that entry preserves the last entry.
    V :
    Type u_1
    shared
    k :
    ℕ
    hk :
    k + 1 < p.length
    (skipOne p k).getLast? = p.getLast?
    Proof (Lean source)
    theorem skipOne_getLast? {p : List V} {k : ℕ} (hk : k + 1 < p.length) : (skipOne p k).getLast? = p.getLast? := by rw [getLast?_eq_getElem?, getLast?_eq_getElem?, skipOne_length] have hp0 : 0 < p.length := by omega rw [List.getElem?_eq_getElem (by rw [skipOne_length]; omega), List.getElem?_eq_getElem (by omega)] have hnot : ¬ (p.length - 1 - 1 < k) := by omega simp [skipOne, hnot, show p.length - 1 - 1 + 1 = p.length - 1 by omega]
  • skipTwo_head? theorem — If an index is positive and its successor lies before the end of a list of values, then skipping the two entries beginning there preserves the first entry.
    V :
    Type u_1
    shared
    k :
    ℕ
    hk0 :
    0 < k
    hk :
    k + 1 < p.length
    (skipTwo p k).head? = p.head?
    Proof (Lean source)
    theorem skipTwo_head? {p : List V} {k : ℕ} (hk0 : 0 < k) (hk : k + 1 < p.length) : (skipTwo p k).head? = p.head? := by rw [List.head?_eq_getElem?, List.head?_eq_getElem?] have hp0 : 0 < p.length := by omega have hq0 : 0 < (skipTwo p k).length := by simp [skipTwo_length]; omega rw [List.getElem?_eq_getElem hq0, List.getElem?_eq_getElem hp0] simp [skipTwo, hk0]
  • skipTwo_getLast? theorem — If a positive index and its successor lie before the end of a list of values and the entries immediately before and after the index agree, then skipping the two entries beginning at the index preserves the last entry.
    V :
    Type u_1
    shared
    k :
    ℕ
    hk0 :
    0 < k
    hk :
    k + 1 < p.length
    hsame :
    p.get ⟨k - 1, by omega⟩ = p.get ⟨k + 1, hk⟩
    (skipTwo p k).getLast? = p.getLast?
    Proof (Lean source)
    theorem skipTwo_getLast? {p : List V} {k : ℕ} (hk0 : 0 < k) (hk : k + 1 < p.length) (hsame : p.get ⟨k - 1, by omega⟩ = p.get ⟨k + 1, hk⟩) : (skipTwo p k).getLast? = p.getLast? := by rw [getLast?_eq_getElem?, getLast?_eq_getElem?, skipTwo_length] have hp0 : 0 < p.length := by omega rw [List.getElem?_eq_getElem (by rw [skipTwo_length]; omega), List.getElem?_eq_getElem (by omega)] by_cases htail : k + 2 < p.length · have hnot : ¬ (p.length - 2 - 1 < k) := by omega simp [skipTwo, hnot, show p.length - 2 - 1 + 2 = p.length - 1 by omega] · have hk_last : k + 1 = p.length - 1 := by omega have hq_last : p.length - 2 - 1 = k - 1 := by omega have htrue : k - 1 < k := by omega simpa [skipTwo, hq_last, hk_last, htrue] using hsame
Markov­Equivalence 1 core · 0 supporting This file assembles the active-walk transport machinery into markovEquiv_flipEdge. ★ markovEquiv_flipEdge

Markov equivalence under one covered-edge reversal

This file assembles the active-walk transport machinery into markovEquiv_flipEdge. Obstructing triples are removed by strictly shortening the walk, while unobstructed walks are handled by swapPath. Applying the argument in both directions shows that reversing one covered edge preserves every d-separation statement. The finite-chain decomposition that uses this result is in Causalean.Graph.MarkovEquiv.Decompose.

theorem markovEquiv_flipEdge reviewed
Causalean.Graph.DAG

The analytic core (AMP, per-step invariance). In a DAG G, if the edge a → b is covered — every other parent of b is also a parent of a, and vice versa, then the DAG obtained by reversing that edge to b → a is Markov equivalent to G: the two graphs license exactly the same d-separation statements. This is the single-edge kernel used by the covered-reversal proof of the Verma--Pearl hard direction.

Formal statement
V :
Type u_1
DAG V
V
shared
hcov :
G.IsCoveredEdge a b
Proof (Lean source)
theorem markovEquiv_flipEdge {a b : V} (hcov : G.IsCoveredEdge a b) : MarkovEquiv G (flipEdge hcov) := by intro X Y Z by_cases hXY : Disjoint X Y · by_cases hXZ : Disjoint X Z · by_cases hYZ : Disjoint Y Z · have hAP : G.HasActiveWalk X Y Z ↔ (flipEdge hcov).HasActiveWalk X Y Z := by constructor · exact hasActiveWalk_flipEdge_of_isCoveredEdge hcov X Y Z hXY · intro hp have hp₂ : (flipEdge (flipEdge_isCoveredEdge_back hcov)).HasActiveWalk X Y Z := @hasActiveWalk_flipEdge_of_isCoveredEdge V _ _ (flipEdge hcov) b a (flipEdge_isCoveredEdge_back hcov) X Y Z hXY hp exact (hasActiveWalk_edge_congr (flipEdge_flipEdge_edge hcov) X Y Z).mp hp₂ apply not_iff_not.mp calc ¬ G.dSep X Y Z ↔ G.HasActiveWalk X Y Z := not_dSep_iff_hasActiveWalk G X Y Z hXY hXZ hYZ _ ↔ (flipEdge hcov).HasActiveWalk X Y Z := hAP _ ↔ ¬ (flipEdge hcov).dSep X Y Z := (not_dSep_iff_hasActiveWalk (flipEdge hcov) X Y Z hXY hXZ hYZ).symm · exact iff_of_false (fun h => hYZ h.2.2.1) (fun h => hYZ h.2.2.1) · exact iff_of_false (fun h => hXZ h.2.1) (fun h => hXZ h.2.1) · exact iff_of_false (fun h => hXY h.1) (fun h => hXY h.1)
Path­Surgery 1 core · 5 supporting This file proves that the list operations from ActivePathTransport preserve active walks under the required splice conditions. ★ isActiveWalk_swapPath_of_no_obstruct

Active-walk surgery for covered-edge reversal

This file proves that the list operations from ActivePathTransport preserve active walks under the required splice conditions. It also shows that swapPath preserves endpoints and yields an active walk when no obstructing triple occurs. MarkovEquivalence uses these results to remove obstructions and transport an arbitrary active walk across a covered-edge reversal.

theorem isActiveWalk_swapPath_of_no_obstruct reviewed
Causalean.Graph.DAG

For a finite directed acyclic graph, the endpoints of a directed edge, a conditioning set, and a walk, if the directed edge is covered, the original walk is active, and none of its consecutive interior triples obstructs the reversal, then replacing the affected collider occurrences yields an active walk after the covered-edge reversal.

Formal statement
V :
Type u_1
DAG V
V
shared
hcov :
G.IsCoveredEdge a b
Z :
p :
hact :
G.IsActiveWalk Z p
hfork :
∀ (j : ℕ) (hj : j + 2 < p.length),
¬ G.FlipObstruct a b (p.get ⟨j, by omega⟩) (p.get ⟨j + 1, by omega⟩) (p.get ⟨j + 2, hj⟩)
(flipEdge hcov).IsActiveWalk Z (swapPath hcov Z p)
Proof (Lean source)
theorem isActiveWalk_swapPath_of_no_obstruct {a b : V} (hcov : G.IsCoveredEdge a b) {Z : Finset V} {p : List V} (hact : G.IsActiveWalk Z p) (hfork : ∀ (j : ℕ) (hj : j + 2 < p.length), ¬ G.FlipObstruct a b (p.get ⟨j, by omega⟩) (p.get ⟨j + 1, by omega⟩) (p.get ⟨j + 2, hj⟩)) : (flipEdge hcov).IsActiveWalk Z (swapPath hcov Z p) := by obtain ⟨hadj, htri⟩ := hact constructor · -- adjacency intro i hi have hi1 : i + 1 < p.length := by simpa using hi have hi0 : i < p.length := by omega have hG : G.UAdj (p.get ⟨i, hi0⟩) (p.get ⟨i + 1, hi1⟩) := hadj i (by simpa using hi) rcases swapPath_get_eq hcov Z p i (by omega) hi0 with hvi | ⟨hvi, hai, _, _, hcoli, _⟩ · rcases swapPath_get_eq hcov Z p (i + 1) (by omega) hi1 with hvi1 | ⟨hvi1, hai1, _, _, hcol1, _⟩ · rw [hvi, hvi1]; exact (flipEdge_sameSkeleton hcov _ _).mp hG · -- i+1 swapped: its left collider arm gives `p i → a`, covered ⟹ `p i → b` rw [hvi, hvi1] have harm : G.edge (p.get ⟨i, hi0⟩) a := by have h := hcol1.1 simp only [Nat.add_sub_cancel] at h rw [hai1] at h; exact h have hne : p.get ⟨i, hi0⟩ ≠ a := fun h => G.irrefl a (h ▸ harm) have hb : G.edge (p.get ⟨i, hi0⟩) b := (hcov.2 _ hne).mp harm exact inl ((flipEdge_edge_iff_of_ne_a hcov hne hcov.ne.symm).mpr hb) · -- i swapped: its right collider arm gives `p (i+1) → a`, covered ⟹ `p (i+1) → b` have harm : G.edge (p.get ⟨i + 1, hi1⟩) a := by have h := hcoli.2; rw [hai] at h; exact h have hne1 : p.get ⟨i + 1, hi1⟩ ≠ a := fun h => G.irrefl a (h ▸ harm) have hb : G.edge (p.get ⟨i + 1, hi1⟩) b := (hcov.2 _ hne1).mp harm rcases swapPath_get_eq hcov Z p (i + 1) (by omega) hi1 with hvi1 | ⟨_, hai1, _, _, _⟩ · rw [hvi, hvi1] exact inr ((flipEdge_edge_iff_of_ne_a hcov hne1 hcov.ne.symm).mpr hb) · exact absurd hai1 hne1 · -- collider / activation conditions intro j hj have hjl : j + 2 < p.length := by simpa using hj have hj0 : j < p.length := by omega have hj1 : j + 1 < p.length := by omega have hjtri := htri j (by simpa using hj) have hob := hfork j (by simpa using hj) rcases swapPath_get_eq hcov Z p (j + 1) (by omega) hj1 with hmid | ⟨hmidb, hmida, _, _, hmcol, hmanc⟩ · -- middle kept by_cases hmida : p.get ⟨j + 1, hj1⟩ = a · -- A1: middle = a (kept). Under `hfork`, `a` is a flip-collider iff it is a G-collider, -- because a `b` neighbour of `a` would otherwise be a flip-obstructing triple. have hjne : p.get ⟨j, hj0⟩ ≠ a := by intro h have hu := hadj j (by omega); rw [h, hmida] at hu rcases hu with he | he <;> exact G.irrefl a he have hj2ne : p.get ⟨j + 2, hjl⟩ ≠ a := by intro h have hu := hadj (j + 1) (by omega); rw [hmida, h] at hu rcases hu with he | he <;> exact G.irrefl a he rcases swapPath_get_eq hcov Z p j (by omega) hj0 with hvj | ⟨_, haj, _⟩ on_goal 2 => exact absurd haj hjne rcases swapPath_get_eq hcov Z p (j + 2) (by omega) hjl with hvj2 | ⟨_, haj2, _⟩ on_goal 2 => exact absurd haj2 hj2ne have hcoll_iff : (flipEdge hcov).IsCollider (p.get ⟨j, hj0⟩) a (p.get ⟨j + 2, hjl⟩) ↔ G.IsCollider (p.get ⟨j, hj0⟩) a (p.get ⟨j + 2, hjl⟩) := by constructor · rintro ⟨hL, hR⟩ rw [flipEdge_edge] at hL hR have hLa : G.edge (p.get ⟨j, hj0⟩) a := by rcases hL with ⟨he, _⟩ | ⟨hlb, _⟩ · exact he · exact (hob (inl ⟨hmida, hlb, hR.elim (fun h => inl h.1) (fun h => inr h.1)⟩)).elim have hRa : G.edge (p.get ⟨j + 2, hjl⟩) a := by rcases hR with ⟨he, _⟩ | ⟨hrb, _⟩ · exact he · exact (hob (inr (inl ⟨hmida, hrb, inl hLa⟩))).elim exact ⟨hLa, hRa⟩ · rintro ⟨hL, hR⟩ exact ⟨by rw [flipEdge_edge]; exact inl ⟨hL, fun h => hjne h.1⟩, by rw [flipEdge_edge]; exact inl ⟨hR, fun h => hj2ne h.1⟩⟩ simp only [hvj, hvj2, hmid, hmida] by_cases hC : G.IsCollider (p.get ⟨j, hj0⟩) a (p.get ⟨j + 2, hjl⟩) · rw [if_pos (hcoll_iff.mpr hC)] by_contra hanc have hb : (swapPath hcov Z p).get ⟨j + 1, by simpa using hj1⟩ = b := swapPath_get_b_of hcov Z p (j + 1) (by simpa using hj1) hj1 (by omega) (by omega) hmida hanc (by simpa only [Nat.add_sub_cancel, hmida] using hC) rw [hmid, hmida] at hb exact hcov.ne hb · rw [if_neg (fun h => hC (hcoll_iff.mp h))] have hcG : ¬ G.IsCollider (p.get ⟨j, hj0⟩) (p.get ⟨j + 1, hj1⟩) (p.get ⟨j + 2, hjl⟩) := by rw [hmida]; exact hC have ht := hjtri; rw [if_neg hcG] at ht; rwa [hmida] at ht · -- A2: middle m = p[j+1] ≠ a (kept) rw [hmid] -- `m ∉ Z` from the original triple, used in every non-collider branch have hmnotZ_of : ¬ G.IsCollider (p.get ⟨j, hj0⟩) (p.get ⟨j + 1, hj1⟩) (p.get ⟨j + 2, hjl⟩) → p.get ⟨j + 1, hj1⟩ ∉ Z := fun hc => by have ht := hjtri; rw [if_neg hc] at ht; exact ht -- helper: a swapped side gives `m → a`, hence the side has no flip-edge into `m`, -- and the original triple is not a collider, so `m ∉ Z`. have swapped_side : G.edge (p.get ⟨j + 1, hj1⟩) a → ¬ (flipEdge hcov).edge b (p.get ⟨j + 1, hj1⟩) := by intro hma hbm have hmb : p.get ⟨j + 1, hj1⟩ ≠ b := by intro h; rw [h] at hma; exact G.asymm hcov.1 hma have hmne : p.get ⟨j + 1, hj1⟩ ≠ a := fun h => G.irrefl a (h ▸ hma) exact absurd ((flipEdge_edge_into_iff hcov hmida hmb b).mp hbm) (G.asymm ((hcov.2 (p.get ⟨j + 1, hj1⟩) hmne).mp hma)) rcases swapPath_get_eq hcov Z p j (by omega) hj0 with hvj | ⟨hvjb, hvja, _, _, hvjcol, _⟩ · rcases swapPath_get_eq hcov Z p (j + 2) (by omega) hjl with hvj2 | ⟨hvj2b, hvj2a, _, _, hvj2col, _⟩ · -- both sides kept: collider status and activation transfer by_cases hmb : p.get ⟨j + 1, hj1⟩ = b · -- middle is `b`. Under `hfork`, flip-collider ↔ G-collider at `b`: an `a` -- neighbour would otherwise destroy the collider (a flip-obstructing triple). have hbiff : (flipEdge hcov).IsCollider (p.get ⟨j, hj0⟩) (p.get ⟨j + 1, hj1⟩) (p.get ⟨j + 2, hjl⟩) ↔ G.IsCollider (p.get ⟨j, hj0⟩) (p.get ⟨j + 1, hj1⟩) (p.get ⟨j + 2, hjl⟩) := by constructor · rintro ⟨hL, hR⟩ rw [flipEdge_edge, hmb] at hL hR refine ⟨?_, ?_⟩ · rcases hL with ⟨he, _⟩ | ⟨_, hba⟩ · rw [hmb]; exact he · exact absurd hba.symm hcov.ne · rcases hR with ⟨he, _⟩ | ⟨_, hba⟩ · rw [hmb]; exact he · exact absurd hba.symm hcov.ne · rintro ⟨hL, hR⟩ rw [hmb] at hL hR have hjna : p.get ⟨j, hj0⟩ ≠ a := fun h => hob (inr (inr (inl ⟨hmb, h, hR⟩))) have hj2na : p.get ⟨j + 2, hjl⟩ ≠ a := fun h => hob (inr (inr (inr ⟨hmb, h, hL⟩))) exact ⟨by rw [flipEdge_edge, hmb]; exact inl ⟨hL, fun h => hjna h.1⟩, by rw [flipEdge_edge, hmb]; exact inl ⟨hR, fun h => hj2na h.1⟩⟩ rw [hvj, hvj2] by_cases hC : G.IsCollider (p.get ⟨j, hj0⟩) (p.get ⟨j + 1, hj1⟩) (p.get ⟨j + 2, hjl⟩) · rw [if_pos (hbiff.mpr hC)] have hmG : p.get ⟨j + 1, hj1⟩ ∈ G.bbZAncestors Z := by have ht := hjtri; rwa [if_pos hC] at ht rw [hmb]; exact bbZAncestors_flip_of_b hcov Z (by rw [← hmb]; exact hmG) · rw [if_neg (fun h => hC (hbiff.mp h))] exact hmnotZ_of hC · -- middle ∉ {a, b}: edges into it are flip-invariant have hiffL := flipEdge_edge_into_iff hcov hmida hmb (p.get ⟨j, hj0⟩) have hiffR := flipEdge_edge_into_iff hcov hmida hmb (p.get ⟨j + 2, hjl⟩) rw [hvj, hvj2] by_cases hC : G.IsCollider (p.get ⟨j, hj0⟩) (p.get ⟨j + 1, hj1⟩) (p.get ⟨j + 2, hjl⟩) · rw [if_pos ⟨hiffL.mpr hC.1, hiffR.mpr hC.2⟩] have hmG : p.get ⟨j + 1, hj1⟩ ∈ G.bbZAncestors Z := by have ht := hjtri; rwa [if_pos hC] at ht exact bbZAncestors_flip_of_ne hcov Z hmida hmG · rw [if_neg (fun h => hC ⟨hiffL.mp h.1, hiffR.mp h.2⟩)] exact hmnotZ_of hC · -- j+2 swapped: p[j+2]=a, collider at j+2 gives m → a have hma : G.edge (p.get ⟨j + 1, hj1⟩) a := by have h := hvj2col.1; rw [hvj2a] at h; exact h have hGc : ¬ G.IsCollider (p.get ⟨j, hj0⟩) (p.get ⟨j + 1, hj1⟩) (p.get ⟨j + 2, hjl⟩) := fun h => G.asymm hma (hvj2a ▸ h.2) rw [hvj2b, if_neg (fun h => swapped_side hma h.2)] exact hmnotZ_of hGc · -- j swapped: p[j]=a, collider at j gives m → a have hma : G.edge (p.get ⟨j + 1, hj1⟩) a := by have h := hvjcol.2; rw [hvja] at h; exact h have hGc : ¬ G.IsCollider (p.get ⟨j, hj0⟩) (p.get ⟨j + 1, hj1⟩) (p.get ⟨j + 2, hjl⟩) := fun h => G.asymm hma (hvja ▸ h.1) rw [hvjb, if_neg (fun h => swapped_side hma h.1)] exact hmnotZ_of hGc · -- middle swapped to b: p[j+1]=a, G-collider at j+1, a ∉ bbZAncestors_flip have harmL : G.edge (p.get ⟨j, hj0⟩) a := by have h := hmcol.1; simp only [Nat.add_sub_cancel] at h; rw [hmida] at h; exact h have harmR : G.edge (p.get ⟨j + 2, hjl⟩) a := by have h := hmcol.2; rw [hmida] at h; exact h have hjne : p.get ⟨j, hj0⟩ ≠ a := fun h => G.irrefl a (h ▸ harmL) have hj2ne : p.get ⟨j + 2, hjl⟩ ≠ a := fun h => G.irrefl a (h ▸ harmR) have hbL : G.edge (p.get ⟨j, hj0⟩) b := (hcov.2 _ hjne).mp harmL have hbR : G.edge (p.get ⟨j + 2, hjl⟩) b := (hcov.2 _ hj2ne).mp harmR rcases swapPath_get_eq hcov Z p j (by omega) hj0 with hvj | ⟨_, haj, _⟩ · rcases swapPath_get_eq hcov Z p (j + 2) (by omega) hjl with hvj2 | ⟨_, haj2, _⟩ · have hcolF : (flipEdge hcov).IsCollider (p.get ⟨j, hj0⟩) b (p.get ⟨j + 2, hjl⟩) := ⟨(flipEdge_edge_iff_of_ne_a hcov hjne hcov.ne.symm).mpr hbL, (flipEdge_edge_iff_of_ne_a hcov hj2ne hcov.ne.symm).mpr hbR⟩ have haG : a ∈ G.bbZAncestors Z := by have hc : G.IsCollider (p.get ⟨j, hj0⟩) (p.get ⟨j + 1, hj1⟩) (p.get ⟨j + 2, hjl⟩) := by rw [hmida]; exact ⟨harmL, harmR⟩ have ht := hjtri; rw [if_pos hc] at ht; rwa [hmida] at ht simp only [hmidb, hvj, hvj2] rw [if_pos hcolF] exact bbZAncestors_flip_of_lost hcov Z haG hmanc · exact absurd haj2 hj2ne · exact absurd haj hjne
5 supporting declarations (lemmas, instances)
  • isActiveWalk_skipOne theorem — Given a directed acyclic graph, conditioning set, walk, and index, if the walk is active, the index is positive, its successor lies inside the walk, the vertices on either side are adjacent, and the newly formed left and right triples satisfy the active-walk criterion, then removing the indexed vertex leaves an active walk.
    V :
    Type u_1
    DAG V
    shared
    k :
    ℕ
    hact :
    G.IsActiveWalk Z p
    hk0 :
    0 < k
    hk :
    k + 1 < p.length
    hadj_splice :
    G.UAdj (p.get ⟨k - 1, by omega⟩) (p.get ⟨k + 1, hk⟩)
    hleft :
    ∀ (hk2 : 2 ≤ k),
    if G.IsCollider (p.get ⟨k - 2, by omega⟩) (p.get ⟨k - 1, by omega⟩) (p.get ⟨k + 1, hk⟩) then p.get ⟨k
    - 1, by omega⟩ ∈ G.bbZAncestors Z else p.get ⟨k
    - 1, by omega⟩ ∉ Z
    hright :
    ∀ (hkR : k + 2 < p.length),
    if G.IsCollider (p.get ⟨k - 1, by omega⟩) (p.get ⟨k + 1, by omega⟩) (p.get ⟨k + 2, hkR⟩) then p.get ⟨k
    + 1, by omega⟩ ∈ G.bbZAncestors Z else p.get ⟨k
    + 1, by omega⟩ ∉ Z
    G.IsActiveWalk Z (skipOne p k)
    Proof (Lean source)
    theorem isActiveWalk_skipOne {Z : Finset V} {p : List V} {k : ℕ} (hact : G.IsActiveWalk Z p) (hk0 : 0 < k) (hk : k + 1 < p.length) (hadj_splice : G.UAdj (p.get ⟨k - 1, by omega⟩) (p.get ⟨k + 1, hk⟩)) (hleft : ∀ (hk2 : 2 ≤ k), if G.IsCollider (p.get ⟨k - 2, by omega⟩) (p.get ⟨k - 1, by omega⟩) (p.get ⟨k + 1, hk⟩) then p.get ⟨k - 1, by omega⟩ ∈ G.bbZAncestors Z else p.get ⟨k - 1, by omega⟩ ∉ Z) (hright : ∀ (hkR : k + 2 < p.length), if G.IsCollider (p.get ⟨k - 1, by omega⟩) (p.get ⟨k + 1, by omega⟩) (p.get ⟨k + 2, hkR⟩) then p.get ⟨k + 1, by omega⟩ ∈ G.bbZAncestors Z else p.get ⟨k + 1, by omega⟩ ∉ Z) : G.IsActiveWalk Z (skipOne p k) := by obtain ⟨hadj, htri⟩ := hact refine ⟨fun i hi => ?_, fun i hi => ?_⟩ · simp only [skipOne_length] at hi simp only [skipOne, List.get_ofFn] by_cases hi0 : i < k · by_cases hi1 : i + 1 < k · have h := hadj i (by omega) simpa [hi0, hi1] using h · have hik : i = k - 1 := by omega subst i have h0 : k - 1 < k := by omega have h1 : ¬ (k - 1 + 1 < k) := by omega simpa [h0, h1, show k - 1 + 1 + 1 = k + 1 by omega] using hadj_splice · have hi1 : ¬ (i + 1 < k) := by omega have h := hadj (i + 1) (by omega) simpa [hi0, hi1, Nat.add_assoc] using h · simp only [skipOne_length] at hi simp only [skipOne, List.get_ofFn] by_cases hi2 : i + 2 < k · have hi0 : i < k := by omega have hi1 : i + 1 < k := by omega have h := htri i (by omega) simpa [hi0, hi1, hi2] using h · by_cases hi1 : i + 1 < k · have hi0 : i < k := by omega have hik : i = k - 2 := by omega subst i have h0 : k - 2 < k := by omega have h1 : k - 2 + 1 < k := by omega have h2 : ¬ (k - 2 + 2 < k) := by omega have hmid : k - 1 < k := by omega simpa [h0, h1, h2, show k - 2 + 1 = k - 1 by omega, show k - 2 + 2 + 1 = k + 1 by omega, hmid] using hleft (by omega) · by_cases hi0 : i < k · have hik : i = k - 1 := by omega subst i have h0 : k - 1 < k := by omega have h1 : ¬ (k - 1 + 1 < k) := by omega have h2 : ¬ (k - 1 + 2 < k) := by omega have hrightNot : ¬ (k + 1 < k) := by omega simpa [h0, h1, h2, show k - 1 + 1 + 1 = k + 1 by omega, show k - 1 + 2 + 1 = k + 2 by omega, hrightNot] using hright (by omega) · have hi1' : ¬ (i + 1 < k) := by omega have hi2' : ¬ (i + 2 < k) := by omega have h := htri (i + 1) (by omega) simpa [hi0, hi1', hi2', Nat.add_assoc] using h
    Causalean.Graph.DAG.isActiveWalk_skipOne · Causalean/Graph/MarkovEquiv/CoveredReversal/PathSurgery.lean:33
  • isActiveWalk_skipTwo theorem — Given a directed acyclic graph, conditioning set, walk, and index, if the walk is active, the index is positive, its successor lies inside the walk, the vertices immediately before and after it agree, and the triple created by deleting two entries satisfies the active-walk criterion, then the shortened walk is active.
    V :
    Type u_1
    DAG V
    shared
    k :
    ℕ
    hact :
    G.IsActiveWalk Z p
    hk0 :
    0 < k
    hk :
    k + 1 < p.length
    hsame :
    p.get ⟨k - 1, by omega⟩ = p.get ⟨k + 1, hk⟩
    hsplice :
    ∀ (hk2 : 2 ≤ k) (hkR : k + 2 < p.length),
    if G.IsCollider (p.get ⟨k - 2, by omega⟩) (p.get ⟨k - 1, by omega⟩) (p.get ⟨k + 2, hkR⟩) then p.get ⟨k
    - 1, by omega⟩ ∈ G.bbZAncestors Z else p.get ⟨k
    - 1, by omega⟩ ∉ Z
    G.IsActiveWalk Z (skipTwo p k)
    Proof (Lean source)
    theorem isActiveWalk_skipTwo {Z : Finset V} {p : List V} {k : ℕ} (hact : G.IsActiveWalk Z p) (hk0 : 0 < k) (hk : k + 1 < p.length) (hsame : p.get ⟨k - 1, by omega⟩ = p.get ⟨k + 1, hk⟩) (hsplice : ∀ (hk2 : 2 ≤ k) (hkR : k + 2 < p.length), if G.IsCollider (p.get ⟨k - 2, by omega⟩) (p.get ⟨k - 1, by omega⟩) (p.get ⟨k + 2, hkR⟩) then p.get ⟨k - 1, by omega⟩ ∈ G.bbZAncestors Z else p.get ⟨k - 1, by omega⟩ ∉ Z) : G.IsActiveWalk Z (skipTwo p k) := by obtain ⟨hadj, htri⟩ := hact refine ⟨fun i hi => ?_, fun i hi => ?_⟩ · simp only [skipTwo_length] at hi simp only [skipTwo, List.get_ofFn] by_cases hi0 : i < k · by_cases hi1 : i + 1 < k · have h := hadj i (by omega) simpa [hi0, hi1] using h · have hik : i = k - 1 := by omega subst i have h0 : k - 1 < k := by omega have h1 : ¬ (k - 1 + 1 < k) := by omega have h := hadj (k + 1) (by omega) rw [← hsame] at h simpa [h0, h1, Nat.add_assoc, show k - 1 + 3 = k + 2 by omega] using h · have hi1 : ¬ (i + 1 < k) := by omega have h := hadj (i + 2) (by omega) simpa [hi0, hi1, Nat.add_assoc] using h · simp only [skipTwo_length] at hi simp only [skipTwo, List.get_ofFn] by_cases hi2 : i + 2 < k · have hi0 : i < k := by omega have hi1 : i + 1 < k := by omega have h := htri i (by omega) simpa [hi0, hi1, hi2] using h · by_cases hi1 : i + 1 < k · have hi0 : i < k := by omega have hik : i = k - 2 := by omega subst i have h0 : k - 2 < k := by omega have h1 : k - 2 + 1 < k := by omega have h2 : ¬ (k - 2 + 2 < k) := by omega have hmid : k - 1 < k := by omega simpa [h0, h1, h2, show k - 2 + 1 = k - 1 by omega, show k - 2 + 2 + 2 = k + 2 by omega, hmid] using hsplice (by omega) (by omega) · by_cases hi0 : i < k · have hik : i = k - 1 := by omega subst i have h0 : k - 1 < k := by omega have h1 : ¬ (k - 1 + 1 < k) := by omega have h2 : ¬ (k - 1 + 2 < k) := by omega have h := htri (k + 1) (by omega) rw [← hsame] at h simpa [h0, h1, h2, Nat.add_assoc, show k - 1 + 3 = k + 2 by omega, show k - 1 + 4 = k + 3 by omega] using h · have hi1' : ¬ (i + 1 < k) := by omega have hi2' : ¬ (i + 2 < k) := by omega have h := htri (i + 2) (by omega) simpa [hi0, hi1', hi2', Nat.add_assoc] using h
    Causalean.Graph.DAG.isActiveWalk_skipTwo · Causalean/Graph/MarkovEquiv/CoveredReversal/PathSurgery.lean:101
  • four_le_length_of_backtrack_endpoints_ne theorem — If a list of values has distinct first and last entries, an index has a second successor in the list, and the entries at the index and second successor agree, then the list has at least four entries.
    V :
    Type u_1
    shared
    j :
    ℕ
    hne :
    p.head? ≠ p.getLast?
    hj :
    j + 2 < p.length
    hback :
    p.get ⟨j, by omega⟩ = p.get ⟨j + 2, hj⟩
    4 ≤ p.length
    Proof (Lean source)
    theorem four_le_length_of_backtrack_endpoints_ne {p : List V} {j : ℕ} (hne : p.head? ≠ p.getLast?) (hj : j + 2 < p.length) (hback : p.get ⟨j, by omega⟩ = p.get ⟨j + 2, hj⟩) : 4 ≤ p.length := by by_contra hlt have hlen : p.length = 3 := by omega have hj0 : j = 0 := by omega apply hne rw [List.head?_eq_getElem?, getLast?_eq_getElem?] rw [List.getElem?_eq_getElem (by omega), List.getElem?_eq_getElem (by omega)] congr 1 simpa [hj0, hlen] using hback
    Causalean.Graph.DAG.four_le_length_of_backtrack_endpoints_ne · Causalean/Graph/MarkovEquiv/CoveredReversal/PathSurgery.lean:167
  • swapPath_head? theorem — swapPath never touches the first vertex, so it preserves the head.
    V :
    Type u_1
    DAG V
    V
    shared
    hcov :
    G.IsCoveredEdge a b
    Z :
    p :
    (swapPath hcov Z p).head? = p.head?
    Proof (Lean source)
    theorem swapPath_head? {a b : V} (hcov : G.IsCoveredEdge a b) (Z : Finset V) (p : List V) : (swapPath hcov Z p).head? = p.head? := by rw [List.head?_eq_getElem?, List.head?_eq_getElem?] rcases Nat.eq_zero_or_pos p.length with h | h · rw [List.getElem?_eq_none (by rw [swapPath_length]; omega), List.getElem?_eq_none (by omega)] · rw [List.getElem?_eq_getElem (by rw [swapPath_length]; exact h), List.getElem?_eq_getElem h] congr 1 rcases swapPath_get_eq hcov Z p 0 (by rw [swapPath_length]; exact h) h with hh | ⟨_, _, h1, _⟩ · simpa using hh · omega
  • swapPath_getLast? theorem — swapPath never touches the last vertex, so it preserves the last element.
    V :
    Type u_1
    DAG V
    V
    shared
    hcov :
    G.IsCoveredEdge a b
    Z :
    p :
    (swapPath hcov Z p).getLast? = p.getLast?
    Proof (Lean source)
    theorem swapPath_getLast? {a b : V} (hcov : G.IsCoveredEdge a b) (Z : Finset V) (p : List V) : (swapPath hcov Z p).getLast? = p.getLast? := by rw [getLast?_eq_getElem?, getLast?_eq_getElem?, swapPath_length] rcases Nat.eq_zero_or_pos p.length with h | h · rw [List.getElem?_eq_none (by rw [swapPath_length]; omega), List.getElem?_eq_none (by omega)] · rw [List.getElem?_eq_getElem (by rw [swapPath_length]; omega), List.getElem?_eq_getElem (by omega)] congr 1 rcases swapPath_get_eq hcov Z p (p.length - 1) (by rw [swapPath_length]; omega) (by omega) with hh | ⟨_, _, _, h2, _⟩ · simpa using hh · omega