Graph.Markov­Equiv

The Verma–Pearl characterization of Markov equivalence (Verma & Pearl, *Equivalence and synthesis of causal models*, 1990): two directed acyclic graphs on the same finite vertex set imply exactly the same d-separation st

Covered­Reversal 12 core · 37 supporting · 4 submodules Reversing a covered edge of a directed acyclic graph does not change its d-separations.
Markov­Equiv 1 core · 0 supporting The Verma–Pearl characterization of Markov equivalence (Verma & Pearl, *Equivalence and synthesis of causal models*, 1990): two directed acyclic graphs on the same finite vertex set imply exactly the same d-separation st ★ markovEquiv_iff_sameSkeleton_sameImmoralities

Markov equivalence of DAGs (Verma–Pearl)

The Verma–Pearl characterization of Markov equivalence (Verma & Pearl, Equivalence and synthesis of causal models, 1990): two directed acyclic graphs on the same finite vertex set imply exactly the same d-separation statements, and hence the same conditional-independence constraints, if and only if they have the same skeleton (the same adjacent pairs) and the same v-structures (colliders a → b ← c with a and c non-adjacent). The forward direction reads the skeleton and v-structures off the d-separation relation; the converse shows that two such graphs are linked by reversals of covered edges, each of which preserves every d-separation. The development also proves the moralization criterion: for pairwise-disjoint sets, d-separation is the same as separation in the moral graph of the ancestral set.

Main results

* MarkovEquiv (Defs.lean) — two DAGs declare the same d-separations. * markovEquiv_iff_sameSkeleton_sameImmoralities (this file) — the flagship. Two DAGs are Markov equivalent iff they have the same skeleton (SameSkeleton, undirected adjacency) and the same v-structures (SameImmoralities, colliders with non-adjacent parents). The constraint-based characterization underlying PC/FCI/GES and the CPDAG. * DAG.dSep_iff_moralSep (Moralization.lean) — the Lauritzen moralization criterion: d-separation in a DAG is equivalent to graph separation in the moral graph of the ancestral closure.

Supporting machinery

Readoff.lean (easy direction — skeleton and v-structures are read off the d-separation relation), Transfer.lean (hard direction — same skeleton + v-structures transfer every d-separation), and Moralization.lean (the ancestral moral graph criterion for d-separation). The development reuses the existing d-separation engine (Causalean.Graph.DSep).

The higher-layer companion Causalean.SCM.Do.MarkovEquivDistributional connects this graph notion to distributions and proves SCM.distMarkovEquiv_of_markovEquiv; it is not imported by this file.

theorem markovEquiv_iff_sameSkeleton_sameImmoralities reviewed
Causalean.Graph

Verma–Pearl (1990). For two DAGs G₁, G₂ on the same vertex set, they are Markov equivalent — they declare exactly the same d-separations, hence impose the same conditional-independence constraints — if and only if they have the same skeleton and the same v-structures (immoralities).

Formal statement
V :
Type u_1
shared
G₁ G₂ :
DAG V
MarkovEquiv G₁ G₂ ↔ SameSkeleton G₁ G₂ ∧ SameImmoralities G₁ G₂
Proof (Lean source)
theorem markovEquiv_iff_sameSkeleton_sameImmoralities (G₁ G₂ : DAG V) : MarkovEquiv G₁ G₂ ↔ SameSkeleton G₁ G₂ ∧ SameImmoralities G₁ G₂ := ⟨sameSkeleton_sameImmoralities_of_markovEquiv, fun ⟨hskel, himm⟩ => markovEquiv_of_sameSkeleton_sameImmoralities hskel himm⟩
Causalean.Graph.markovEquiv_iff_sameSkeleton_sameImmoralities · Causalean/Graph/MarkovEquiv.lean:58 · uses DAG , MarkovEquiv , SameImmoralities , SameSkeleton
Defs 5 core · 5 supporting This file introduces the combinatorial vocabulary for the Verma–Pearl characterization of Markov equivalence. ★ MarkovEquiv★ refl

Markov equivalence of DAGs — basic definitions

This file introduces the combinatorial vocabulary for the Verma–Pearl characterization of Markov equivalence. Two directed acyclic graphs on the same vertices are Markov equivalent when they encode exactly the same d-separation statements, i.e. the same conditional-independence constraints. The Verma–Pearl theorem (proved in Causalean.Graph.MarkovEquiv) says this happens precisely when the graphs share a skeleton and the same v-structures (immoralities).

The definitions here are:

* DAG.IsImmorality G a b c — there is a v-structure (immorality) a → b ← c whose two parents a, c are non-adjacent and distinct; * SameSkeleton G₁ G₂ — the two graphs have the same undirected adjacency; * SameImmoralities G₁ G₂ — the two graphs have the same v-structures; * MarkovEquiv G₁ G₂ — the two graphs declare the same d-separations.

The first three relations have decidable instances on a finite vertex type. MarkovEquiv is instead the semantic relation quantified over all finite vertex sets. The underlying undirected adjacency is the existing DAG.UAdj (a and b joined by an edge in either direction), and a collider a → b ← c is DAG.IsCollider.

def IsImmorality reviewed
Causalean.Graph.DAG

For a finite directed acyclic graph on a vertex population and three ordered vertices, the v-structure, or immorality, condition holds exactly when the first and third vertices are distinct, each has a directed edge into the second, and no directed edge joins the first and third in either direction.

Definition (Lean source)
V :
Type u_1
shared
G :
DAG V
shared
a b c :
V
IsImmorality G a b c :
Prop
clause 1
G.edge a b
clause 2
G.edge c b
clause 3
¬ G.UAdj a c
clause 4
a ≠ c
Causalean.Graph.DAG.IsImmorality · Causalean/Graph/MarkovEquiv/Defs.lean:43 · uses DAG
def SameSkeleton reviewed
Causalean.Graph.MarkovEquiv

For a finite vertex population and two directed acyclic graphs on it, the same-skeleton condition holds exactly when, for every pair of vertices, a directed edge joins the pair in either direction in the first graph if and only if one does in the second graph.

Definition (Lean source)
V :
Type u_1
shared
G₁ G₂ :
DAG V
a b :
SameSkeleton G₁ G₂ :
Prop
G₁.UAdj a b ↔ G₂.UAdj a b
Causalean.Graph.MarkovEquiv.SameSkeleton · Causalean/Graph/MarkovEquiv/Defs.lean:65 · uses DAG
def SameImmoralities reviewed
Causalean.Graph.MarkovEquiv

For a finite vertex population and two directed acyclic graphs on it, the same-immoralities condition holds exactly when every ordered triple of vertices is an immorality in the first graph if and only if it is an immorality in the second graph.

Definition (Lean source)
V :
Type u_1
shared
G₁ G₂ :
DAG V
a b c :
SameImmoralities G₁ G₂ :
Prop
G₁.IsImmorality a b c ↔ G₂.IsImmorality a b c
Causalean.Graph.MarkovEquiv.SameImmoralities · Causalean/Graph/MarkovEquiv/Defs.lean:71 · uses DAG
def MarkovEquiv reviewed
Causalean.Graph.MarkovEquiv

For a finite vertex population and two directed acyclic graphs on it, Markov equivalence holds exactly when, for every three finite vertex sets, the first and second sets are d-separated by the third in the first graph if and only if they are d-separated in the second graph.

Definition (Lean source)
V :
Type u_1
shared
G₁ G₂ :
DAG V
X Y Z :
MarkovEquiv G₁ G₂ :
Prop
G₁.dSep X Y Z ↔ G₂.dSep X Y Z
Causalean.Graph.MarkovEquiv.MarkovEquiv · Causalean/Graph/MarkovEquiv/Defs.lean:78 · uses DAG
theorem refl reviewed
Causalean.Graph.MarkovEquiv.MarkovEquiv

For any DAG G, G is Markov equivalent to itself.

Formal statement
V :
Type u_1
shared
G :
DAG V
Proof (Lean source)
@[refl] theorem MarkovEquiv.refl (G : DAG V) : MarkovEquiv G G := fun _ _ _ => Iff.rfl
Causalean.Graph.MarkovEquiv.MarkovEquiv.refl · Causalean/Graph/MarkovEquiv/Defs.lean:103 · uses DAG , MarkovEquiv
5 supporting declarations (lemmas, instances)
  • instDecidableIsImmorality instance — For a finite vertex population with decidable equality, a directed acyclic graph on that population, and three ordered vertices, the decision procedure for the v-structure condition determines whether the first and third vertices are distinct non-adjacent parents of the second.
    V :
    Type u_1
    shared
    G :
    DAG V
    shared
    a b c :
    V
    instDecidableIsImmorality G a b c :
    Decidable (G.IsImmorality a b c)
    by unfold IsImmorality; infer_instance
    Causalean.Graph.DAG.instDecidableIsImmorality · Causalean/Graph/MarkovEquiv/Defs.lean:53
  • instDecidableSameSkeleton instance — For a finite vertex population with decidable equality and two directed acyclic graphs on that population, the decision procedure for the same-skeleton condition determines whether the graphs have identical undirected adjacencies for every pair of vertices.
    V :
    Type u_1
    shared
    G₁ G₂ :
    DAG V
    instDecidableSameSkeleton G₁ G₂ :
    by unfold SameSkeleton; infer_instance
    Causalean.Graph.MarkovEquiv.instDecidableSameSkeleton · Causalean/Graph/MarkovEquiv/Defs.lean:89
  • instDecidableSameImmoralities instance — For a finite vertex population with decidable equality and two directed acyclic graphs on that population, the decision procedure for the same-immoralities condition determines whether the graphs have identical v-structures for every ordered triple of vertices.
    V :
    Type u_1
    shared
    G₁ G₂ :
    DAG V
    instDecidableSameImmoralities G₁ G₂ :
    by unfold SameImmoralities; infer_instance
    Causalean.Graph.MarkovEquiv.instDecidableSameImmoralities · Causalean/Graph/MarkovEquiv/Defs.lean:96
  • symm theorem — Markov equivalence is symmetric.
    V :
    Type u_1
    DAG V
    shared
    h :
    MarkovEquiv G₁ G₂
    MarkovEquiv G₂ G₁
    Proof (Lean source)
    theorem MarkovEquiv.symm {G₁ G₂ : DAG V} (h : MarkovEquiv G₁ G₂) : MarkovEquiv G₂ G₁ := fun X Y Z => (h X Y Z).symm
    Causalean.Graph.MarkovEquiv.MarkovEquiv.symm · Causalean/Graph/MarkovEquiv/Defs.lean:107
  • trans theorem — Markov equivalence is transitive.
    V :
    Type u_1
    DAG V
    shared
    h₁ :
    MarkovEquiv G₁ G₂
    h₂ :
    MarkovEquiv G₂ G₃
    MarkovEquiv G₁ G₃
    Proof (Lean source)
    theorem MarkovEquiv.trans {G₁ G₂ G₃ : DAG V} (h₁ : MarkovEquiv G₁ G₂) (h₂ : MarkovEquiv G₂ G₃) : MarkovEquiv G₁ G₃ := fun X Y Z => (h₁ X Y Z).trans (h₂ X Y Z)
    Causalean.Graph.MarkovEquiv.MarkovEquiv.trans · Causalean/Graph/MarkovEquiv/Defs.lean:111
Decompose 4 core · 7 supporting This file assembles the covered-edge route to the hard direction of Verma–Pearl: DAGs with the same skeleton and the same immoralities are Markov equivalent. ★ exists_covered_reversed_edge★ markovEquiv_of_sameSkeleton_sameImmoralities

Covered-edge induction for the Verma–Pearl hard direction

This file assembles the covered-edge route to the hard direction of Verma–Pearl: DAGs with the same skeleton and the same immoralities are Markov equivalent. The public helper exists_covered_reversed_edge gives the key one-step progress result underlying Andersson–Madigan–Perlman (1997), Lemma 3.2. A private strong induction on the number of oppositely oriented edges (edgeDiffCount) combines each step with markovEquiv_flipEdge and MarkovEquiv.trans to prove the semantic-equivalence theorem. This module does not expose a finite reversal sequence or a finite-chain certificate.

def edgeDiff reviewed
Causalean.Graph.MarkovEquiv

For a finite vertex set with decidable equality and two directed acyclic graphs on that vertex set, the directed-edge difference is the finite set of all ordered pairs of vertices that form a directed edge in the first graph but not in the second graph.

Definition (Lean source)
V :
Type u_1
shared
G₁ G₂ :
DAG V
edgeDiff G₁ G₂ :
Finset (V × V)
Finset.univ.filter (fun p => G₁.edge p.1 p.2 ∧ ¬ G₂.edge p.1 p.2)
Causalean.Graph.MarkovEquiv.edgeDiff · Causalean/Graph/MarkovEquiv/Decompose.lean:64 · uses DAG
def edgeDiffCount reviewed
Causalean.Graph.MarkovEquiv

For a finite vertex set with decidable equality and two directed acyclic graphs on that vertex set, the directed-edge difference count is the number of directed edges in the first graph that are absent from the second graph.

Definition (Lean source)
V :
Type u_1
shared
G₁ G₂ :
DAG V
edgeDiffCount G₁ G₂ :
ℕ
(edgeDiff G₁ G₂).card
Causalean.Graph.MarkovEquiv.edgeDiffCount · Causalean/Graph/MarkovEquiv/Decompose.lean:71 · uses DAG
theorem exists_covered_reversed_edge reviewed
Causalean.Graph.MarkovEquiv

Key induction step underlying AMP Lemma 3.2. For DAGs G₁ and G₂ on the same vertex set, suppose G₁ and G₂ have the same skeleton, have the same immoralities, and G₁ contains a₀ → b₀ while G₂ contains b₀ → a₀. Then a covered G₁ edge that is reversed in G₂ exists. The proof chooses a head of a reversed edge that is minimal in the first graph's topological order, then a tail into that head that is maximal among reversed tails; the skeleton and immorality hypotheses force this edge to have the same non-tail parents at both endpoints.

Formal statement
V :
Type u_1
DAG V
shared
hskel :
SameSkeleton G₁ G₂
himm :
a₀ b₀ :
V
h₀ :
G₁.edge a₀ b₀
h₀' :
G₂.edge b₀ a₀
∃ a b,
conclusion 1
G₁.edge a b
conclusion 2
G₂.edge b a
conclusion 3
G₁.IsCoveredEdge a b
Proof (Lean source)
theorem exists_covered_reversed_edge {G₁ G₂ : DAG V} (hskel : SameSkeleton G₁ G₂) (himm : SameImmoralities G₁ G₂) {a₀ b₀ : V} (h₀ : G₁.edge a₀ b₀) (h₀' : G₂.edge b₀ a₀) : ∃ a b, G₁.edge a b ∧ G₂.edge b a ∧ G₁.IsCoveredEdge a b := by let Heads : Finset V := Finset.univ.filter (fun y => ∃ x, G₁.edge x y ∧ G₂.edge y x) have hHead0 : b₀ ∈ Heads := by change b₀ ∈ Finset.univ.filter (fun y => ∃ x, G₁.edge x y ∧ G₂.edge y x) exact Finset.mem_filter.mpr ⟨Finset.mem_univ _, ⟨a₀, h₀, h₀'⟩⟩ have hHeads_ne : Heads.Nonempty := ⟨b₀, hHead0⟩ obtain ⟨b, hbHead, hbmin⟩ := Finset.exists_min_image Heads G₁.topoOrder hHeads_ne have hbWitness : ∃ x, G₁.edge x b ∧ G₂.edge b x := by have hbHead' : b ∈ Finset.univ.filter (fun y => ∃ x, G₁.edge x y ∧ G₂.edge y x) := by simpa [Heads] using hbHead exact (Finset.mem_filter.mp hbHead').2 obtain ⟨x, hxb₁, hbx₂⟩ := hbWitness let Tails : Finset V := Finset.univ.filter (fun x => G₁.edge x b ∧ G₂.edge b x) have hxTail : x ∈ Tails := by change x ∈ Finset.univ.filter (fun x => G₁.edge x b ∧ G₂.edge b x) exact Finset.mem_filter.mpr ⟨Finset.mem_univ _, hxb₁, hbx₂⟩ have hTails_ne : Tails.Nonempty := ⟨x, hxTail⟩ obtain ⟨a, haTail, hamax⟩ := Finset.exists_max_image Tails G₁.topoOrder hTails_ne have haRev : G₁.edge a b ∧ G₂.edge b a := by have haTail' : a ∈ Finset.univ.filter (fun x => G₁.edge x b ∧ G₂.edge b x) := by simpa [Tails] using haTail exact (Finset.mem_filter.mp haTail').2 refine ⟨a, b, haRev.1, haRev.2, haRev.1, ?_⟩ intro c hca_ne by_contra hiff by_cases hca : G₁.edge c a · by_cases hcb : G₁.edge c b · exact hiff ⟨fun _ => hcb, fun _ => hca⟩ · have hnotG1Ucb : ¬ G₁.UAdj c b := by intro hU rcases hU with hcb' | hbc · exact hcb hcb' · have hcbTop : G₁.topoOrder c < G₁.topoOrder b := lt_trans (G₁.topoOrder_lt c a hca) (G₁.topoOrder_lt a b haRev.1) exact (Nat.lt_irrefl _) (lt_trans hcbTop (G₁.topoOrder_lt b c hbc)) have hnotG2Ucb : ¬ G₂.UAdj c b := by intro hU exact hnotG1Ucb ((hskel c b).mpr hU) have hcb_ne : c ≠ b := by intro heq subst c exact (G₁.asymm haRev.1) hca have hnotG2ca : ¬ G₂.edge c a := by intro hca₂ have him₂ : G₂.IsImmorality c a b := ⟨hca₂, haRev.2, hnotG2Ucb, hcb_ne⟩ have him₁ : G₁.IsImmorality c a b := (himm c a b).mpr him₂ exact (G₁.asymm haRev.1) him₁.2.1 have hU2ca : G₂.UAdj c a := (hskel c a).mp (Or.inl hca) have hac₂ : G₂.edge a c := hU2ca.resolve_left hnotG2ca have haHead : a ∈ Heads := by change a ∈ Finset.univ.filter (fun y => ∃ x, G₁.edge x y ∧ G₂.edge y x) exact Finset.mem_filter.mpr ⟨Finset.mem_univ _, ⟨c, hca, hac₂⟩⟩ have hle : G₁.topoOrder b ≤ G₁.topoOrder a := hbmin a haHead exact (not_lt_of_ge hle) (G₁.topoOrder_lt a b haRev.1) · by_cases hcb : G₁.edge c b · have hac_ne : a ≠ c := fun heq => hca_ne heq.symm have hUac : G₁.UAdj a c := by by_contra hnotU have him₁ : G₁.IsImmorality a b c := ⟨haRev.1, hcb, hnotU, hac_ne⟩ have him₂ : G₂.IsImmorality a b c := (himm a b c).mp him₁ exact (G₂.asymm haRev.2) him₂.1 have hac : G₁.edge a c := hUac.resolve_right hca have hrevNext : G₂.edge b c ∨ G₂.edge c a := by by_cases hbc₂ : G₂.edge b c · exact Or.inl hbc₂ · by_cases hca₂ : G₂.edge c a · exact Or.inr hca₂ · have hU2cb : G₂.UAdj c b := (hskel c b).mp (Or.inl hcb) have hcb₂ : G₂.edge c b := hU2cb.resolve_right hbc₂ have hU2ac : G₂.UAdj a c := (hskel a c).mp (Or.inl hac) have hac₂ : G₂.edge a c := hU2ac.resolve_right hca₂ have hcycle : G₂.topoOrder b < G₂.topoOrder b := lt_trans (G₂.topoOrder_lt b a haRev.2) (lt_trans (G₂.topoOrder_lt a c hac₂) (G₂.topoOrder_lt c b hcb₂)) exact False.elim ((Nat.lt_irrefl _) hcycle) rcases hrevNext with hbc₂ | hca₂ · have hcTail : c ∈ Tails := by change c ∈ Finset.univ.filter (fun x => G₁.edge x b ∧ G₂.edge b x) exact Finset.mem_filter.mpr ⟨Finset.mem_univ _, hcb, hbc₂⟩ have hle : G₁.topoOrder c ≤ G₁.topoOrder a := hamax c hcTail exact (not_lt_of_ge hle) (G₁.topoOrder_lt a c hac) · have hcHead : c ∈ Heads := by change c ∈ Finset.univ.filter (fun y => ∃ x, G₁.edge x y ∧ G₂.edge y x) exact Finset.mem_filter.mpr ⟨Finset.mem_univ _, ⟨a, hac, hca₂⟩⟩ have hle : G₁.topoOrder b ≤ G₁.topoOrder c := hbmin c hcHead exact (not_lt_of_ge hle) (G₁.topoOrder_lt c b hcb) · exact hiff ⟨fun h => False.elim (hca h), fun h => False.elim (hcb h)⟩
Causalean.Graph.MarkovEquiv.exists_covered_reversed_edge · Causalean/Graph/MarkovEquiv/Decompose.lean:93 · uses DAG , IsCoveredEdge , SameImmoralities , SameSkeleton
theorem markovEquiv_of_sameSkeleton_sameImmoralities reviewed
Causalean.Graph.MarkovEquiv

Verma–Pearl hard direction (covered-edge route). For DAGs G₁ and G₂ on the same vertex set, if G₁ and G₂ have the same skeleton and the same immoralities, then G₁ and G₂ are Markov equivalent: they license exactly the same d-separation statements — proven via AMP Lemma 3.2 (covered-edge reversals), independent of the moralization/ancestral kernel.

Formal statement
V :
Type u_1
DAG V
shared
hskel :
SameSkeleton G₁ G₂
himm :
MarkovEquiv G₁ G₂
Proof (Lean source)
theorem markovEquiv_of_sameSkeleton_sameImmoralities {G₁ G₂ : DAG V} (hskel : SameSkeleton G₁ G₂) (himm : SameImmoralities G₁ G₂) : MarkovEquiv G₁ G₂ := markovEquiv_covered_aux G₂ (edgeDiffCount G₁ G₂) G₁ hskel himm rfl
Causalean.Graph.MarkovEquiv.markovEquiv_of_sameSkeleton_sameImmoralities · Causalean/Graph/MarkovEquiv/Decompose.lean:242 · uses DAG , MarkovEquiv , SameImmoralities , SameSkeleton
7 supporting declarations (lemmas, instances)
  • symm theorem — SameSkeleton is symmetric.
    V :
    Type u_1
    DAG V
    shared
    h :
    SameSkeleton G₁ G₂
    SameSkeleton G₂ G₁
    Proof (Lean source)
    theorem SameSkeleton.symm {G₁ G₂ : DAG V} (h : SameSkeleton G₁ G₂) : SameSkeleton G₂ G₁ := fun a b => (h a b).symm
    Causalean.Graph.MarkovEquiv.SameSkeleton.symm · Causalean/Graph/MarkovEquiv/Decompose.lean:31
  • trans theorem — SameSkeleton is transitive.
    V :
    Type u_1
    DAG V
    shared
    h₁ :
    SameSkeleton G₁ G₂
    h₂ :
    SameSkeleton G₂ G₃
    SameSkeleton G₁ G₃
    Proof (Lean source)
    theorem SameSkeleton.trans {G₁ G₂ G₃ : DAG V} (h₁ : SameSkeleton G₁ G₂) (h₂ : SameSkeleton G₂ G₃) : SameSkeleton G₁ G₃ := fun a b => (h₁ a b).trans (h₂ a b)
    Causalean.Graph.MarkovEquiv.SameSkeleton.trans · Causalean/Graph/MarkovEquiv/Decompose.lean:35
  • symm theorem — SameImmoralities is symmetric.
    V :
    Type u_1
    DAG V
    shared
    h :
    Proof (Lean source)
    theorem SameImmoralities.symm {G₁ G₂ : DAG V} (h : SameImmoralities G₁ G₂) : SameImmoralities G₂ G₁ := fun a b c => (h a b c).symm
    Causalean.Graph.MarkovEquiv.SameImmoralities.symm · Causalean/Graph/MarkovEquiv/Decompose.lean:39
  • trans theorem — SameImmoralities is transitive.
    V :
    Type u_1
    DAG V
    shared
    h₁ :
    h₂ :
    Proof (Lean source)
    theorem SameImmoralities.trans {G₁ G₂ G₃ : DAG V} (h₁ : SameImmoralities G₁ G₂) (h₂ : SameImmoralities G₂ G₃) : SameImmoralities G₁ G₃ := fun a b c => (h₁ a b c).trans (h₂ a b c)
    Causalean.Graph.MarkovEquiv.SameImmoralities.trans · Causalean/Graph/MarkovEquiv/Decompose.lean:43
  • markovEquiv_of_same_edge theorem — Edge-congruence for Markov equivalence. DAGs with the same directed-edge relation are Markov equivalent (d-separation depends only on the edge relation).
    V :
    Type u_1
    DAG V
    shared
    he :
    ∀ u w, G₁.edge u w ↔ G₂.edge u w
    MarkovEquiv G₁ G₂
    Proof (Lean source)
    theorem markovEquiv_of_same_edge {G₁ G₂ : DAG V} (he : ∀ u w, G₁.edge u w ↔ G₂.edge u w) : MarkovEquiv G₁ G₂ := by intro X Y Z by_cases hXY : Disjoint X Y · by_cases hXZ : Disjoint X Z · by_cases hYZ : Disjoint Y Z · have h := DAG.hasActiveWalk_edge_congr he X Y Z rw [← DAG.not_dSep_iff_hasActiveWalk G₁ X Y Z hXY hXZ hYZ, ← DAG.not_dSep_iff_hasActiveWalk G₂ X Y Z hXY hXZ hYZ] at h exact not_iff_not.mp h · 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)
    Causalean.Graph.MarkovEquiv.markovEquiv_of_same_edge · Causalean/Graph/MarkovEquiv/Decompose.lean:48
  • same_edge_of_edgeDiff_empty theorem — With a one-way skeleton inclusion, an empty edge-difference forces equal edge relations.
    V :
    Type u_1
    DAG V
    shared
    hskel :
    ∀ u w
    if
    G₂.UAdj u w
    then
    G₁.UAdj u w
    h :
    edgeDiff G₁ G₂ = ∅
    u w :
    G₁.edge u w ↔ G₂.edge u w
    Proof (Lean source)
    theorem same_edge_of_edgeDiff_empty {G₁ G₂ : DAG V} (hskel : ∀ u w, G₂.UAdj u w → G₁.UAdj u w) (h : edgeDiff G₁ G₂ = ∅) : ∀ u w, G₁.edge u w ↔ G₂.edge u w := by have hsub : ∀ u w, G₁.edge u w → G₂.edge u w := by intro u w he by_contra hne have hmem : (u, w) ∈ edgeDiff G₁ G₂ := Finset.mem_filter.mpr ⟨Finset.mem_univ _, he, hne⟩ rw [h] at hmem; simp at hmem intro u w refine ⟨hsub u w, fun he2 => ?_⟩ rcases hskel u w (inl he2) with h1 | h1 · exact h1 · exact absurd (hsub w u h1) (G₂.asymm he2)
    Causalean.Graph.MarkovEquiv.same_edge_of_edgeDiff_empty · Causalean/Graph/MarkovEquiv/Decompose.lean:77
  • edgeDiffCount_flipEdge_lt theorem — Flipping a covered, oppositely oriented edge strictly decreases the edge-difference count (it fixes exactly the pair (a,b) and changes nothing else).
    V :
    Type u_1
    DAG V
    V
    shared
    hcov :
    G₁.IsCoveredEdge a b
    hba :
    G₂.edge b a
    edgeDiffCount (DAG.flipEdge hcov) G₂ < edgeDiffCount G₁ G₂
    Proof (Lean source)
    theorem edgeDiffCount_flipEdge_lt {G₁ G₂ : DAG V} {a b : V} (hcov : G₁.IsCoveredEdge a b) (hba : G₂.edge b a) : edgeDiffCount (DAG.flipEdge hcov) G₂ < edgeDiffCount G₁ G₂ := by have hsub : edgeDiff (DAG.flipEdge hcov) G₂ ⊆ edgeDiff G₁ G₂ := by intro p hp rw [edgeDiff, mem_filter] at hp ⊢ obtain ⟨_, hedge, hnot⟩ := hp rw [DAG.flipEdge_edge] at hedge rcases hedge with ⟨h1, _⟩ | ⟨hb, ha⟩ · exact ⟨Finset.mem_univ _, h1, hnot⟩ · exact absurd (by rw [hb, ha]; exact hba) hnot unfold edgeDiffCount apply Finset.card_lt_card rw [Finset.ssubset_iff_of_subset hsub] refine ⟨(a, b), ?_, ?_⟩ · rw [edgeDiff, mem_filter] exact ⟨Finset.mem_univ _, hcov.1, G₂.asymm hba⟩ · rw [edgeDiff, mem_filter] rintro ⟨_, hedge, _⟩ rw [DAG.flipEdge_edge] at hedge rcases hedge with ⟨_, hnotab⟩ | ⟨hb, _⟩ · exact hnotab ⟨rfl, rfl⟩ · exact hcov.ne hb
    Causalean.Graph.MarkovEquiv.edgeDiffCount_flipEdge_lt · Causalean/Graph/MarkovEquiv/Decompose.lean:192
Moralization 5 core · 13 supporting This file develops the moral graph of a DAG and the classical *moralization criterion* for d-separation. ★ dSep_iff_moralSep

Markov equivalence — the moralization criterion

This file develops the moral graph of a DAG and the classical moralization criterion for d-separation. It proves that, for pairwise-disjoint source, target, and conditioning sets, d-separation is equivalent to separation in the moral graph of the ancestral set.

* DAG.MoralAdj G S u v — u and v are adjacent in the moral graph restricted to a ground set S: they are skeleton-adjacent (UAdj) or "married", i.e. they have a common child inside S. * DAG.MoralConn G S Z u v — u reaches v by a moral path inside S all of whose vertices avoid Z. * DAG.MoralSep G X Y Z — no x ∈ X is moral-connected to any y ∈ Y inside the ancestral set An(X ∪ Y ∪ Z) while avoiding Z. * DAG.dSep_iff_moralSep — the criterion: for pairwise-disjoint sets, d-separation is exactly moral separation in the ancestral set.

The file also proves that moral adjacency, moral steps, and moral connectivity are invariants of a graph's skeleton together with its v-structures, for a fixed ground set. The main Verma–Pearl hard direction used by the Verma–Pearl equivalence theorem is assembled through the covered-edge route in Transfer.lean and Decompose.lean.

def MoralAdj reviewed
Causalean.Graph.DAG

For a finite directed acyclic graph on a vertex population, a ground set of vertices, and two vertices, moral adjacency holds exactly when the vertices are distinct members of the ground set and either a directed edge joins them in one direction or the other, or they have a common child in the ground set.

Definition (Lean source)
V :
Type u_1
shared
G :
DAG V
shared
S :
u v :
V
MoralAdj G S u v :
Prop
clause 1
u ≠ v
clause 2
u ∈ S
clause 3
v ∈ S
clause 4
G.UAdj u v ∨ ∃ c ∈ S, G.edge u c ∧ G.edge v c
Causalean.Graph.DAG.MoralAdj · Causalean/Graph/MarkovEquiv/Moralization.lean:45 · uses DAG
def MoralStep reviewed
Causalean.Graph.DAG

For a finite directed acyclic graph on a vertex population, a ground set, a conditioning set, and two vertices, a moral step holds exactly when the vertices are morally adjacent in the ground set and neither belongs to the conditioning set.

Definition (Lean source)
V :
Type u_1
shared
G :
DAG V
shared
S Z :
u v :
V
MoralStep G S Z u v :
Prop
clause 1
G.MoralAdj S u v
clause 2
u ∉ Z
clause 3
v ∉ Z
Causalean.Graph.DAG.MoralStep · Causalean/Graph/MarkovEquiv/Moralization.lean:54 · uses DAG
def MoralConn reviewed
Causalean.Graph.DAG

For a finite directed acyclic graph on a vertex population, a ground set, a conditioning set, and two vertices, moral connectivity holds exactly when the first vertex reaches the second by a possibly empty sequence of moral-adjacent steps within the ground set, each of whose endpoints lies outside the conditioning set.

Definition (Lean source)
V :
Type u_1
shared
G :
DAG V
shared
S Z :
u v :
V
MoralConn G S Z u v :
Prop
Relation.ReflTransGen (G.MoralStep S Z) u v
Causalean.Graph.DAG.MoralConn · Causalean/Graph/MarkovEquiv/Moralization.lean:61 · uses DAG
def MoralSep reviewed
Causalean.Graph.DAG

For a finite directed acyclic graph on a vertex population, a first vertex set, a second vertex set, and a conditioning set, moral separation holds exactly when no member of the first set is morally connected to any member of the second within the ancestral closure of the union of all three sets, while avoiding the conditioning set.

Definition (Lean source)
V :
Type u_1
shared
G :
DAG V
shared
X Y Z :
x ∈ X :
y ∈ Y :
MoralSep G X Y Z :
Prop
¬ G.MoralConn (G.ancestralSet (X ∪ Y ∪ Z)) Z x y
Causalean.Graph.DAG.MoralSep · Causalean/Graph/MarkovEquiv/Moralization.lean:69 · uses DAG
theorem dSep_iff_moralSep reviewed
Causalean.Graph.DAG

The moralization criterion. For pairwise-disjoint X, Y, Z, X and Y are d-separated by Z exactly when they are moral-separated: no moral path inside the ancestral set An(X ∪ Y ∪ Z) connects them while avoiding Z.

Formal statement
V :
Type u_1
shared
G :
DAG V
shared
X Y Z :
hXY :
hXZ :
hYZ :
G.dSep X Y Z ↔ G.MoralSep X Y Z
Proof (Lean source)
theorem dSep_iff_moralSep {X Y Z : Finset V} (hXY : Disjoint X Y) (hXZ : Disjoint X Z) (hYZ : Disjoint Y Z) : G.dSep X Y Z ↔ G.MoralSep X Y Z := by constructor · -- `dSep → MoralSep` via Direction 2: a moral connection would build a d-connection. intro hdSep x hxX y hyY hconn have hxZ : x ∉ Z := Finset.disjoint_left.mp hXZ hxX obtain ⟨w, hwY, hwReach⟩ := G.dconn_of_moralConn hXY hconn (inl hxX) hxZ hyY exact (Finset.disjoint_left.mp hdSep.2.2.2 hwReach) hwY · -- `MoralSep → dSep` via the contrapositive Direction 1. intro hsep by_contra hdSep exact G.not_moralSep_of_not_dSep hXY hXZ hYZ hdSep hsep
Causalean.Graph.DAG.dSep_iff_moralSep · Causalean/Graph/MarkovEquiv/Moralization.lean:521 · uses DAG , MoralSep , dSep
13 supporting declarations (lemmas, instances)
  • moralAdj_symm theorem — Moral adjacency is symmetric: an undirected moral edge from u to v is also one from v to u.
    V :
    Type u_1
    shared
    G :
    DAG V
    shared
    S :
    u v :
    V
    h :
    G.MoralAdj S u v
    G.MoralAdj S v u
    Proof (Lean source)
    theorem moralAdj_symm {S : Finset V} {u v : V} (h : G.MoralAdj S u v) : G.MoralAdj S v u := by obtain ⟨hne, hu, hv, hdisj⟩ := h refine ⟨hne.symm, hv, hu, ?_⟩ rcases hdisj with hadj | ⟨c, hc, huc, hvc⟩ · exact inl (G.UAdj_symm hadj) · exact inr ⟨c, hc, hvc, huc⟩
    Causalean.Graph.DAG.moralAdj_symm · Causalean/Graph/MarkovEquiv/Moralization.lean:77
  • moralConn_of_step theorem — A single moral step yields moral connectivity.
    V :
    Type u_1
    shared
    G :
    DAG V
    shared
    S Z :
    u v :
    V
    h :
    G.MoralStep S Z u v
    G.MoralConn S Z u v
    Proof (Lean source)
    theorem moralConn_of_step {S Z : Finset V} {u v : V} (h : G.MoralStep S Z u v) : G.MoralConn S Z u v := Relation.ReflTransGen.single h
    Causalean.Graph.DAG.moralConn_of_step · Causalean/Graph/MarkovEquiv/Moralization.lean:91
  • moralConn_trans theorem — Moral connectivity is transitive.
    V :
    Type u_1
    shared
    G :
    DAG V
    shared
    S Z :
    u v w :
    V
    h₁ :
    G.MoralConn S Z u v
    h₂ :
    G.MoralConn S Z v w
    G.MoralConn S Z u w
    Proof (Lean source)
    theorem moralConn_trans {S Z : Finset V} {u v w : V} (h₁ : G.MoralConn S Z u v) (h₂ : G.MoralConn S Z v w) : G.MoralConn S Z u w := Relation.ReflTransGen.trans h₁ h₂
    Causalean.Graph.DAG.moralConn_trans · Causalean/Graph/MarkovEquiv/Moralization.lean:96
  • moralStep_of_uAdj theorem — A skeleton edge between two non-Z vertices of S is a moral step.
    V :
    Type u_1
    shared
    G :
    DAG V
    shared
    S Z :
    u v :
    V
    hne :
    u ≠ v
    hu :
    u ∈ S
    hv :
    v ∈ S
    hadj :
    G.UAdj u v
    huZ :
    u ∉ Z
    hvZ :
    v ∉ Z
    G.MoralStep S Z u v
    Proof (Lean source)
    theorem moralStep_of_uAdj {S Z : Finset V} {u v : V} (hne : u ≠ v) (hu : u ∈ S) (hv : v ∈ S) (hadj : G.UAdj u v) (huZ : u ∉ Z) (hvZ : v ∉ Z) : G.MoralStep S Z u v := ⟨⟨hne, hu, hv, inl hadj⟩, huZ, hvZ⟩
    Causalean.Graph.DAG.moralStep_of_uAdj · Causalean/Graph/MarkovEquiv/Moralization.lean:101
  • moralStep_of_married theorem — A married pair (common child c ∈ S) of distinct non-Z vertices of S is a moral step.
    V :
    Type u_1
    shared
    G :
    DAG V
    shared
    S Z :
    u v c :
    V
    hne :
    u ≠ v
    hu :
    u ∈ S
    hv :
    v ∈ S
    hc :
    c ∈ S
    huc :
    G.edge u c
    hvc :
    G.edge v c
    huZ :
    u ∉ Z
    hvZ :
    v ∉ Z
    G.MoralStep S Z u v
    Proof (Lean source)
    theorem moralStep_of_married {S Z : Finset V} {u v c : V} (hne : u ≠ v) (hu : u ∈ S) (hv : v ∈ S) (hc : c ∈ S) (huc : G.edge u c) (hvc : G.edge v c) (huZ : u ∉ Z) (hvZ : v ∉ Z) : G.MoralStep S Z u v := ⟨⟨hne, hu, hv, inr ⟨c, hc, huc, hvc⟩⟩, huZ, hvZ⟩
    Causalean.Graph.DAG.moralStep_of_married · Causalean/Graph/MarkovEquiv/Moralization.lean:107
  • activeWalk_head_uAdj theorem — In an active walk whose first two vertices are a and b, those vertices are adjacent in the underlying undirected graph.
    V :
    Type u_1
    shared
    G :
    DAG V
    shared
    Z :
    a b :
    V
    r :
    h :
    G.IsActiveWalk Z (a :: b :: r)
    G.UAdj a b
    Proof (Lean source)
    theorem activeWalk_head_uAdj {Z : Finset V} {a b : V} {r : List V} (h : G.IsActiveWalk Z (a :: b :: r)) : G.UAdj a b := by have := h.1 0 (by simp) simpa using this
    Causalean.Graph.DAG.activeWalk_head_uAdj · Causalean/Graph/MarkovEquiv/Moralization.lean:119
  • activeWalk_head_triple theorem — In an active walk whose first three vertices are a, b, and c, the middle vertex obeys the active-walk condition: a collider belongs to the ancestral closure of the conditioning set, and a non-collider is not conditioned on.
    V :
    Type u_1
    shared
    G :
    DAG V
    shared
    Z :
    a b c :
    V
    r :
    h :
    G.IsActiveWalk Z (a :: b :: c :: r)
    if G.IsCollider a b c then b ∈ G.bbZAncestors Z else b ∉ Z
    Proof (Lean source)
    theorem activeWalk_head_triple {Z : Finset V} {a b c : V} {r : List V} (h : G.IsActiveWalk Z (a :: b :: c :: r)) : if G.IsCollider a b c then b ∈ G.bbZAncestors Z else b ∉ Z := by have := h.2 0 (by simp) simpa using this
    Causalean.Graph.DAG.activeWalk_head_triple · Causalean/Graph/MarkovEquiv/Moralization.lean:126
  • activeWalk_drop2 theorem — Removing the first two vertices of an active walk with at least three vertices leaves an active walk.
    V :
    Type u_1
    shared
    G :
    DAG V
    shared
    Z :
    a b c :
    V
    r :
    h :
    G.IsActiveWalk Z (a :: b :: c :: r)
    G.IsActiveWalk Z (c :: r)
    Proof (Lean source)
    theorem activeWalk_drop2 {Z : Finset V} {a b c : V} {r : List V} (h : G.IsActiveWalk Z (a :: b :: c :: r)) : G.IsActiveWalk Z (c :: r) := G.isActiveWalk_cons_tail (G.isActiveWalk_cons_tail h)
    Causalean.Graph.DAG.activeWalk_drop2 · Causalean/Graph/MarkovEquiv/Moralization.lean:135
  • moralConn_of_activeWalk theorem — An active walk inside a ground set, with endpoints outside the conditioning set, induces a connection in the corresponding moral graph.
    V :
    Type u_1
    shared
    G :
    DAG V
    shared
    S Z :
    n :
    ℕ
    x y :
    V
    p :
    p.length ≤ n
    G.IsActiveWalk Z p
    p.length ≥ 2
    (∀ v ∈ p, v ∈ S)
    p.head? = some x
    p.getLast? = some y
    x ∉ Z
    y ∉ Z
    G.MoralConn S Z x y
    Proof (Lean source)
    theorem moralConn_of_activeWalk {S Z : Finset V} : ∀ (n : ℕ) {x y : V} {p : List V}, p.length ≤ n → G.IsActiveWalk Z p → p.length ≥ 2 → (∀ v ∈ p, v ∈ S) → p.head? = some x → p.getLast? = some y → x ∉ Z → y ∉ Z → G.MoralConn S Z x y := by intro n induction n with | zero => intro x y p hn _ hlen _ _ _ _ _; omega | succ n ih => intro x y p hn hp hlen hnodes hhead hlast hxZ hyZ -- `p = a :: b :: r` with `a = x`. obtain ⟨a, b, r, rfl⟩ : ∃ a b r, p = a :: b :: r := by match p, hlen with | a :: b :: r, _ => exact ⟨a, b, r, rfl⟩ have hax : a = x := by simpa using hhead subst hax clear hhead have haS : a ∈ S := hnodes a (by simp) have hbS : b ∈ S := hnodes b (by simp) have hadj_ab : G.UAdj a b := G.activeWalk_head_uAdj hp -- Branch on the remainder. match r, hp, hn, hnodes, hlast with | [], hp, hn, hnodes, hlast => -- `p = [a, b]`: single skeleton step `a — b`, with `b = y`. have hby : b = y := by simpa using hlast subst hby have hne : a ≠ b := by rintro rfl rcases hadj_ab with h | h <;> exact G.irrefl a h exact G.moralConn_of_step (G.moralStep_of_uAdj hne haS hbS hadj_ab hxZ hyZ) | c :: r', hp, hn, hnodes, hlast => have hcS : c ∈ S := hnodes c (by simp) have htri := G.activeWalk_head_triple hp by_cases hcoll : G.IsCollider a b c · -- Married step `a — c` across the collider apex `b`. rw [if_pos hcoll] at htri obtain ⟨hab_edge, hcb_edge⟩ := hcoll -- The married step (or reflexivity if `a = c`), then continue from `c`. have hstep : ∀ (hcZ : c ∉ Z), G.MoralConn S Z a c := by intro hcZ by_cases hne : a = c · subst hne; exact Relation.ReflTransGen.refl · exact G.moralConn_of_step (G.moralStep_of_married hne haS hcS hbS hab_edge hcb_edge hxZ hcZ) match r', hp, hn, hnodes, hlast with | [], hp, hn, hnodes, hlast => -- `c` is the last node, so `c = y`; the married step is the whole path. have hcy : c = y := by simpa using hlast subst hcy exact hstep hyZ | d :: r'', hp, hn, hnodes, hlast => -- triple `(b, c, d)`; `c` cannot be a collider (would clash with `b`). have htri2 := G.activeWalk_head_triple (G.isActiveWalk_cons_tail hp) have hncoll2 : ¬ G.IsCollider b c d := by rintro ⟨hbc, _⟩ exact G.asymm hcb_edge hbc rw [if_neg hncoll2] at htri2 have hcZ : c ∉ Z := htri2 have hrest : G.MoralConn S Z c y := by refine ih (p := c :: d :: r'') ?_ (G.activeWalk_drop2 hp) (by simp) (fun v hv => hnodes v (by simp [hv])) rfl (by simpa using hlast) hcZ hyZ simp only [List.length_cons] at hn ⊢; omega exact G.moralConn_trans (hstep hcZ) hrest · -- Skeleton step `a — b`, then recurse on `b :: c :: r'`. rw [if_neg hcoll] at htri have hbZ : b ∉ Z := htri have hne : a ≠ b := by rintro rfl rcases hadj_ab with h | h <;> exact G.irrefl a h have hstep : G.MoralConn S Z a b := G.moralConn_of_step (G.moralStep_of_uAdj hne haS hbS hadj_ab hxZ hbZ) have hrest : G.MoralConn S Z b y := by apply ih (p := b :: c :: r') (by simp at hn ⊢; omega) (G.isActiveWalk_cons_tail hp) (by simp) (fun v hv => hnodes v (by simp [hv])) rfl (by simpa using hlast) hbZ hyZ exact G.moralConn_trans hstep hrest
    Causalean.Graph.DAG.moralConn_of_activeWalk · Causalean/Graph/MarkovEquiv/Moralization.lean:145
  • ancestralSet_cases theorem — A vertex in the ancestral closure of a set either belongs to that set itself or is a strict ancestor of one of its elements.
    V :
    Type u_1
    shared
    G :
    DAG V
    shared
    A :
    a :
    V
    h :
    a ∈ G.ancestralSet A
    a ∈ A ∨ ∃ w ∈ A, G.isAncestor a w
    Proof (Lean source)
    theorem ancestralSet_cases {A : Finset V} {a : V} (h : a ∈ G.ancestralSet A) : a ∈ A ∨ ∃ w ∈ A, G.isAncestor a w := by rcases Finset.mem_union.mp h with hA | hAnc · exact inl hA · simp only [ancestorsSet, mem_filter, Finset.mem_univ, true_and] at hAnc exact inr hAnc
    Causalean.Graph.DAG.ancestralSet_cases · Causalean/Graph/MarkovEquiv/Moralization.lean:347
  • moralAdj_congr theorem — Moral adjacency is a skeleton + v-structure invariant. Two DAGs with the same skeleton and the same v-structures induce the same moral adjacency on any ground set: a shielded pair is moral-adjacent via the shared skeleton edge, and an unshielded married pair is exactly the apex of a shared immorality.
    V :
    Type u_1
    DAG V
    shared
    hskel :
    SameSkeleton G₁ G₂
    himm :
    S :
    u v :
    V
    G₁.MoralAdj S u v ↔ G₂.MoralAdj S u v
    Proof (Lean source)
    theorem moralAdj_congr {G₁ G₂ : DAG V} (hskel : SameSkeleton G₁ G₂) (himm : SameImmoralities G₁ G₂) (S : Finset V) (u v : V) : G₁.MoralAdj S u v ↔ G₂.MoralAdj S u v := by unfold DAG.MoralAdj by_cases hne : u = v · simp [hne] refine and_congr_right (fun _ => ?_) refine and_congr_right (fun hu => ?_) refine and_congr_right (fun hv => ?_) by_cases hUA : G₁.UAdj u v · simp only [hUA, (hskel u v).mp hUA, true_or] · have hUA₂ : ¬ G₂.UAdj u v := fun h => hUA ((hskel u v).mpr h) simp only [hUA, hUA₂, false_or] refine exists_congr (fun c => ?_) refine and_congr_right (fun _ => ?_) -- common child `u → c ← v` (with `u,v` non-adjacent, distinct) is an immorality. constructor · rintro ⟨huc, hvc⟩ have him₁ : G₁.IsImmorality u c v := ⟨huc, hvc, hUA, hne⟩ have him₂ : G₂.IsImmorality u c v := (himm u c v).mp him₁ exact ⟨him₂.1, him₂.2.1⟩ · rintro ⟨huc, hvc⟩ have him₂ : G₂.IsImmorality u c v := ⟨huc, hvc, hUA₂, hne⟩ have him₁ : G₁.IsImmorality u c v := (himm u c v).mpr him₂ exact ⟨him₁.1, him₁.2.1⟩
    Causalean.Graph.MarkovEquiv.moralAdj_congr · Causalean/Graph/MarkovEquiv/Moralization.lean:546
  • moralStep_congr theorem — Moral steps agree across DAGs with the same skeleton and v-structures (same ground set, same conditioning set).
    V :
    Type u_1
    DAG V
    shared
    hskel :
    SameSkeleton G₁ G₂
    himm :
    S Z :
    u v :
    V
    G₁.MoralStep S Z u v ↔ G₂.MoralStep S Z u v
    Proof (Lean source)
    theorem moralStep_congr {G₁ G₂ : DAG V} (hskel : SameSkeleton G₁ G₂) (himm : SameImmoralities G₁ G₂) (S Z : Finset V) (u v : V) : G₁.MoralStep S Z u v ↔ G₂.MoralStep S Z u v := by unfold DAG.MoralStep rw [moralAdj_congr hskel himm]
    Causalean.Graph.MarkovEquiv.moralStep_congr · Causalean/Graph/MarkovEquiv/Moralization.lean:576
  • moralConn_congr theorem — For two DAGs on a finite vertex population that have the same skeleton and the same v-structures, and for fixed ground and conditioning sets and two vertices, moral connectivity in one DAG holds exactly when it holds in the other.
    V :
    Type u_1
    DAG V
    shared
    hskel :
    SameSkeleton G₁ G₂
    himm :
    S Z :
    u v :
    V
    G₁.MoralConn S Z u v ↔ G₂.MoralConn S Z u v
    Proof (Lean source)
    theorem moralConn_congr {G₁ G₂ : DAG V} (hskel : SameSkeleton G₁ G₂) (himm : SameImmoralities G₁ G₂) (S Z : Finset V) (u v : V) : G₁.MoralConn S Z u v ↔ G₂.MoralConn S Z u v := by unfold DAG.MoralConn constructor <;> intro h <;> refine Relation.ReflTransGen.mono (fun a b hab => ?_) u v h · exact (moralStep_congr hskel himm S Z a b).mp hab · exact (moralStep_congr hskel himm S Z a b).mpr hab
    Causalean.Graph.MarkovEquiv.moralConn_congr · Causalean/Graph/MarkovEquiv/Moralization.lean:584
Readoff 3 core · 9 supporting If two DAGs are Markov equivalent (declare the same d-separations) then they have the same skeleton and the same v-structures. ★ adjacent_iff_not_dSeparable★ immorality_iff_colliderSep★ sameSkeleton_sameImmoralities_of_markovEquiv

Markov equivalence — the easy direction (reading skeleton and v-structures off d-sep)

If two DAGs are Markov equivalent (declare the same d-separations) then they have the same skeleton and the same v-structures. The point is that both the skeleton and the v-structures are determined by the d-separation relation:

* Skeleton. Two distinct vertices are adjacent iff they cannot be d-separated by any set (adjacent_iff_not_dSeparable): an edge is an always-active walk, and conversely two non-adjacent vertices are separated by the parents of the topologically later one. * V-structures. For an unshielded triple a — b — c (with a, c non-adjacent), the middle vertex b is a collider a → b ← c iff b lies in no separating set of a and c (immorality_iff_colliderSep) — the rule the PC algorithm uses to orient colliders.

Both characterizations are phrased purely in terms of dSep, so Markov equivalence transports them, giving sameSkeleton_of_markovEquiv and sameImmoralities_of_markovEquiv, hence the easy direction sameSkeleton_sameImmoralities_of_markovEquiv.

theorem adjacent_iff_not_dSeparable reviewed
Causalean.Graph.DAG

Skeleton read-off. For two distinct vertices a and b, a and b are adjacent (in either direction) exactly when no conditioning set d-separates a from b.

Formal statement
V :
Type u_1
shared
G :
DAG V
shared
a b :
V
hne :
a ≠ b
G.UAdj a b ↔ ¬ ∃ Z : Finset V, G.dSep {a} {b} Z
Proof (Lean source)
theorem adjacent_iff_not_dSeparable {a b : V} (hne : a ≠ b) : G.UAdj a b ↔ ¬ ∃ Z : Finset V, G.dSep {a} {b} Z := by constructor · intro h ⟨Z, hZ⟩ exact G.not_dSeparable_of_uAdj h Z hZ · intro h by_contra hadj exact h (G.dSeparable_of_not_uAdj hne hadj)
Causalean.Graph.DAG.adjacent_iff_not_dSeparable · Causalean/Graph/MarkovEquiv/Readoff.lean:183 · uses DAG , UAdj , dSep
theorem immorality_iff_colliderSep reviewed
Causalean.Graph.DAG

Collider read-off. For vertices a, b, c forming an unshielded triple: a and b adjacent, c and b adjacent, and a, c non-adjacent and distinct, the middle vertex b is a collider a → b ← c — equivalently a → b ← c is a v-structure — exactly when b belongs to no conditioning set that d-separates a and c.

Formal statement
V :
Type u_1
shared
G :
DAG V
shared
a b c :
V
hab :
G.UAdj a b
hcb :
G.UAdj c b
hac :
¬ G.UAdj a c
hne :
a ≠ c
G.IsImmorality a b c ↔ ∀ Z : Finset V, b ∈ Z → ¬ G.dSep {a} {c} Z
Proof (Lean source)
theorem immorality_iff_colliderSep {a b c : V} (hab : G.UAdj a b) (hcb : G.UAdj c b) (hac : ¬ G.UAdj a c) (hne : a ≠ c) : G.IsImmorality a b c ↔ ∀ Z : Finset V, b ∈ Z → ¬ G.dSep {a} {c} Z := by constructor · -- Forward. From the immorality, `(a, b, c)` is a collider; the length-3 walk `[a, b, c]` -- is active given any `Z ∋ b` (the collider `b` is activated since `b ∈ Z ⊆ ancestralSet Z`), -- so `c` is reachable from `{a}`, contradicting d-separation. rintro ⟨heab, hecb, _, _⟩ Z hbZ hsep have hcoll : G.IsCollider a b c := ⟨heab, hecb⟩ have hcReach : c ∈ G.bbReachableVertices Z {a} := by rw [G.bbReachableVertices_iff_activeWalk] refine ⟨a, mem_singleton_self a, [a, b, c], by simp, ⟨?_, ?_⟩, rfl, rfl⟩ · -- Adjacency along `[a, b, c]`. intro i hi match i, hi with | 0, _ => exact hab | 1, _ => exact inr hecb · -- The single interior triple `(a, b, c)` is the collider `b`, activated by `b ∈ Z`. intro i hi have hi0 : i = 0 := by simp only [List.length_cons, List.length_nil] at hi; omega subst hi0 simp only [get] have hbAnc : b ∈ G.bbZAncestors Z := by unfold bbZAncestors; exact G.subset_ancestralSet Z hbZ rw [if_pos hcoll] exact hbAnc simp only [DAG.dSep] at hsep exact (Finset.disjoint_left.mp hsep.2.2.2 hcReach) (mem_singleton_self c) · -- Backward. Suppose `b` is in no separator of `a, c`. We must show `IsImmorality a b c`, -- i.e. `edge a b ∧ edge c b` (the remaining conjuncts are `hac`, `hne`). By contradiction: -- if `b` is not the collider `a → b ← c`, then `b` is a non-collider, hence a parent of -- the topologically-later of `a, c`; so `parents (later)` separates `a` from `c` *and* -- contains `b`, contradicting the hypothesis. intro H refine ⟨?_, ?_, hac, hne⟩ <;> · by_contra hcontra -- Reduce both subgoals to: `¬ (edge a b ∧ edge c b)`, i.e. `¬ IsCollider a b c`. have hnc : ¬ G.IsCollider a b c := by rintro ⟨h1, h2⟩ first | exact hcontra h1 | exact hcontra h2 -- Build a separator `Z ∋ b` with `dSep {a} {c} Z`, contradicting `H`. have htop_ne : G.topoOrder a ≠ G.topoOrder c := fun htop => hne (G.topoOrder_injective htop) rcases lt_or_gt_of_ne htop_ne with hlt | hlt · -- `a` no later than `c`: separator `parents c`; `b → c` makes `b ∈ parents c`. have heby : G.edge b c := G.edge_to_later_of_nonCollider (le_of_lt hlt) hab hcb hnc have hsep : G.dSep {a} {c} (G.parents c) := G.dSep_parents_of_topoOrder_lt hlt hac exact H (G.parents c) (G.mem_parents.mpr heby) hsep · -- `c` no later than `a`: separator `parents a`; `b → a` makes `b ∈ parents a`. have hac' : ¬ G.UAdj c a := fun h' => hac (G.UAdj_symm h') have hnc' : ¬ G.IsCollider c b a := by rintro ⟨h1, h2⟩; exact hnc ⟨h2, h1⟩ have heby : G.edge b a := G.edge_to_later_of_nonCollider (le_of_lt hlt) hcb hab hnc' have hsep : G.dSep {a} {c} (G.parents a) := G.dSep_symm _ _ _ (G.dSep_parents_of_topoOrder_lt hlt hac') exact H (G.parents a) (G.mem_parents.mpr heby) hsep
Causalean.Graph.DAG.immorality_iff_colliderSep · Causalean/Graph/MarkovEquiv/Readoff.lean:217 · uses DAG , IsImmorality , UAdj , dSep
theorem sameSkeleton_sameImmoralities_of_markovEquiv reviewed
Causalean.Graph.MarkovEquiv

Easy direction of Verma–Pearl. For two DAGs G₁, G₂, if they are Markov equivalent then they share the same skeleton and the same v-structures (immoralities).

Formal statement
V :
Type u_1
DAG V
shared
h :
MarkovEquiv G₁ G₂
conclusion 1
SameSkeleton G₁ G₂
conclusion 2
Proof (Lean source)
theorem sameSkeleton_sameImmoralities_of_markovEquiv {G₁ G₂ : DAG V} (h : MarkovEquiv G₁ G₂) : SameSkeleton G₁ G₂ ∧ SameImmoralities G₁ G₂ := ⟨sameSkeleton_of_markovEquiv h, sameImmoralities_of_markovEquiv h⟩
Causalean.Graph.MarkovEquiv.sameSkeleton_sameImmoralities_of_markovEquiv · Causalean/Graph/MarkovEquiv/Readoff.lean:387 · uses DAG , MarkovEquiv , SameImmoralities , SameSkeleton
9 supporting declarations (lemmas, instances)
  • not_dSeparable_of_uAdj theorem — An edge makes its endpoints inseparable: if a and b are adjacent then no conditioning set d-separates them, because the single edge [a, b] is an active walk regardless of the conditioning set.
    V :
    Type u_1
    shared
    G :
    DAG V
    shared
    a b :
    V
    h :
    G.UAdj a b
    Z :
    ¬ G.dSep {a} {b} Z
    Proof (Lean source)
    theorem not_dSeparable_of_uAdj {a b : V} (h : G.UAdj a b) (Z : Finset V) : ¬ G.dSep {a} {b} Z := by intro hsep have hb : b ∈ G.bbReachableVertices Z {a} := by rw [G.bbReachableVertices_iff_activeWalk] refine ⟨a, mem_singleton_self a, [a, b], ?_, ⟨?_, ?_⟩, rfl, rfl⟩ · simp · intro i hi have hi0 : i = 0 := by simp only [List.length_cons, List.length_nil] at hi; omega subst hi0 exact h · intro i hi simp only [List.length_cons, List.length_nil] at hi omega simp only [DAG.dSep] at hsep exact (Finset.disjoint_left.mp hsep.2.2.2 hb) (mem_singleton_self b)
    Causalean.Graph.DAG.not_dSeparable_of_uAdj · Causalean/Graph/MarkovEquiv/Readoff.lean:42
  • dSep_parents_of_topoOrder_lt theorem — If one endpoint precedes a nonadjacent later endpoint topologically, conditioning on the later endpoint's parents d-separates the pair.
    V :
    Type u_1
    shared
    G :
    DAG V
    shared
    x y :
    V
    hlt :
    G.topoOrder x < G.topoOrder y
    hxy :
    ¬ G.UAdj x y
    G.dSep {x} {y} (G.parents y)
    Proof (Lean source)
    theorem dSep_parents_of_topoOrder_lt {x y : V} (hlt : G.topoOrder x < G.topoOrder y) (hxy : ¬ G.UAdj x y) : G.dSep {x} {y} (G.parents y) := by have hne : x ≠ y := by intro h subst h exact (Nat.lt_irrefl _) hlt -- Suppose not: then `y` is reachable from `{x}` given `parents y`. Subst eliminates the -- bound names `x, y`, leaving the source `s₀` and target `t` introduced by the witness. rw [DAG.dSep] refine ⟨Finset.disjoint_singleton.mpr hne, Finset.disjoint_singleton_left.mpr (fun hmem => hxy (inl (G.mem_parents.mp hmem))), Finset.disjoint_singleton_left.mpr (fun hmem => G.irrefl y (G.mem_parents.mp hmem)), ?_⟩ rw [Finset.disjoint_left] intro t hv hvy rw [Finset.mem_singleton] at hvy subst hvy -- Extract an active walk `p` from `s₀` to `t` given `Z := parents t`. rw [G.bbReachableVertices_iff_activeWalk] at hv obtain ⟨s₀, hx', p, hlen, hact, hhead, hlast⟩ := hv rw [Finset.mem_singleton] at hx' subst hx' -- The walk has length ≥ 3: a length-2 walk `[s₀, t]` would force `UAdj s₀ t`. have hp_ne : p ≠ [] := by intro hnil; rw [hnil] at hlen; simp at hlen have hlast_get : p.get ⟨p.length - 1, by omega⟩ = t := by have h := getLast?_eq_some_getLast hp_ne rw [hlast] at h have hy_eq : p.getLast hp_ne = t := Option.some_inj.mp h.symm rw [← hy_eq]; exact (List.getLast_eq_getElem hp_ne).symm have hhead_get : p.get ⟨0, by omega⟩ = s₀ := by have h := List.head?_eq_some_head hp_ne rw [hhead] at h have hx_eq : p.head hp_ne = s₀ := Option.some_inj.mp h.symm rw [← hx_eq]; simp [List.head_eq_getElem hp_ne] have hlen3 : p.length ≥ 3 := by by_contra hlt2 -- p.length = 2, so p = [s₀, t]; then UAdj s₀ t from the adjacency at index 0. have hp2 : p.length = 2 := by omega have hadj0 := hact.1 0 (by omega) have h1 : (⟨0 + 1, by omega⟩ : Fin p.length) = ⟨p.length - 1, by omega⟩ := Fin.ext (by simp; omega) rw [h1] at hadj0 rw [hlast_get] at hadj0 rw [hhead_get] at hadj0 exact hxy hadj0 -- Name the second-to-last vertex `m` and the triple `(m', m, t)`. have hm_idx : p.length - 2 + 1 < p.length := by omega have hadj_my := hact.1 (p.length - 2) hm_idx have hm1_eq : (⟨p.length - 2 + 1, hm_idx⟩ : Fin p.length) = ⟨p.length - 1, by omega⟩ := Fin.ext (by simp; omega) rw [hm1_eq, hlast_get] at hadj_my set m := p.get ⟨p.length - 2, by omega⟩ with hm_def -- `hadj_my : G.UAdj m t`. Split on the orientation of that last edge. rcases hadj_my with hmy | hym · -- Case `edge m t`: then `m ∈ parents t = Z`, but the active-walk non-collider -- condition at the triple `(m', m, t)` forces `m ∉ Z`. have htri : p.length - 3 + 2 < p.length := by omega have hcoll := hact.2 (p.length - 3) htri simp only at hcoll have e1 : (⟨p.length - 3 + 1, by omega⟩ : Fin p.length) = ⟨p.length - 2, by omega⟩ := Fin.ext (by simp; omega) have e2 : (⟨p.length - 3 + 2, htri⟩ : Fin p.length) = ⟨p.length - 1, by omega⟩ := Fin.ext (by simp; omega) rw [e1, e2, hlast_get] at hcoll -- The middle vertex is `m`. It is not a collider: `edge m t` rules out `edge t m`. set l := p.get ⟨p.length - 3, by omega⟩ with hl_def have hnotColl : ¬ G.IsCollider l m t := by rintro ⟨_, hym⟩; exact G.asymm hmy hym rw [if_neg hnotColl] at hcoll -- But `m ∈ parents t` since `edge m t`. exact hcoll (G.mem_parents.mpr hmy) · -- Case `edge t m`: then `m` is a child of `t`, hence `topoOrder m > topoOrder t`. have htop_m : G.topoOrder t < G.topoOrder m := G.topoOrder_lt t m hym -- But `m` lies on the active walk, hence in `ancestralSet ({s₀} ∪ {t} ∪ parents t)`. have hm_mem : m ∈ p := by rw [hm_def]; exact List.get_mem _ _ have hm_anc := G.activeWalk_nodes_are_ancestors (mem_singleton_self s₀) (mem_singleton_self t) hact hhead hlast m hm_mem -- Every member of `{s₀} ∪ {t} ∪ parents t` has `topoOrder ≤ topoOrder t`. have hS_le : ∀ s ∈ ({s₀} ∪ {t} ∪ G.parents t : Finset V), G.topoOrder s ≤ G.topoOrder t := by intro s hs simp only [mem_union, Finset.mem_singleton] at hs rcases hs with (hsx | hsy) | hsp · subst hsx; exact le_of_lt hlt · subst hsy; exact le_refl _ · exact le_of_lt (G.topoOrder_lt s t (G.mem_parents.mp hsp)) -- `m ∈ ancestralSet S`: either `m ∈ S` or `m` is a strict ancestor of some `s ∈ S`. rcases Finset.mem_union.mp hm_anc with hmS | hmAnc · -- `m ∈ S` ⇒ `topoOrder m ≤ topoOrder t`, contradicting `topoOrder t < topoOrder m`. have := hS_le m hmS; omega · -- `m` strict ancestor of some `s ∈ S` ⇒ `topoOrder m < topoOrder s ≤ topoOrder t`. simp only [ancestorsSet, mem_filter, Finset.mem_univ, true_and] at hmAnc obtain ⟨s, hsS, hms⟩ := hmAnc have h1 := G.isAncestor_topoOrder_lt hms have h2 := hS_le s hsS omega
    Causalean.Graph.DAG.dSep_parents_of_topoOrder_lt · Causalean/Graph/MarkovEquiv/Readoff.lean:62
  • dSeparable_of_not_uAdj theorem — Two distinct non-adjacent vertices can always be d-separated: conditioning on the parents of the topologically later vertex blocks every active walk between them.
    V :
    Type u_1
    shared
    G :
    DAG V
    shared
    a b :
    V
    hne :
    a ≠ b
    h :
    ¬ G.UAdj a b
    ∃ Z : Finset V, G.dSep {a} {b} Z
    Proof (Lean source)
    theorem dSeparable_of_not_uAdj {a b : V} (hne : a ≠ b) (h : ¬ G.UAdj a b) : ∃ Z : Finset V, G.dSep {a} {b} Z := by -- Pick the topologically later endpoint; condition on its parents. The helper only needs -- `topoOrder (earlier) < topoOrder (later)`, so `<` in either direction discharges the case. have htop_ne : G.topoOrder a ≠ G.topoOrder b := fun htop => hne (G.topoOrder_injective htop) rcases lt_or_gt_of_ne htop_ne with hlt | hlt · -- `a` no later than `b`: separate by `parents b`, already in the right orientation. exact ⟨G.parents b, G.dSep_parents_of_topoOrder_lt hlt h⟩ · -- `b` no later than `a`: separate by `parents a`, then flip with `dSep_symm`. have hba : ¬ G.UAdj b a := fun h' => h (G.UAdj_symm h') exact ⟨G.parents a, G.dSep_symm _ _ _ (G.dSep_parents_of_topoOrder_lt hlt hba)⟩
    Causalean.Graph.DAG.dSeparable_of_not_uAdj · Causalean/Graph/MarkovEquiv/Readoff.lean:169
  • edge_to_later_of_nonCollider theorem — On an unshielded two-edge triple, a non-collider middle vertex points into the topologically later endpoint.
    V :
    Type u_1
    shared
    G :
    DAG V
    shared
    x y b :
    V
    hxy_le :
    G.topoOrder x ≤ G.topoOrder y
    hxb :
    G.UAdj x b
    hyb :
    G.UAdj y b
    hnc :
    ¬ G.IsCollider x b y
    G.edge b y
    Proof (Lean source)
    theorem edge_to_later_of_nonCollider {x y b : V} (hxy_le : G.topoOrder x ≤ G.topoOrder y) (hxb : G.UAdj x b) (hyb : G.UAdj y b) (hnc : ¬ G.IsCollider x b y) : G.edge b y := by have hby : G.UAdj b y := G.UAdj_symm hyb rcases G.nonCollider_has_outgoing hxb hby hnc with hbx | hby' · -- `edge b x`: then `topoOrder b < topoOrder x ≤ topoOrder y`. The adjacency `y — b` -- cannot be `edge y b` (that gives `topoOrder y < topoOrder b`), so it is `edge b y`. have htb_x : G.topoOrder b < G.topoOrder x := G.topoOrder_lt b x hbx rcases hyb with hyb_e | hby_e · exact absurd (G.topoOrder_lt y b hyb_e) (by omega) · exact hby_e · exact hby'
    Causalean.Graph.DAG.edge_to_later_of_nonCollider · Causalean/Graph/MarkovEquiv/Readoff.lean:195
  • not_uAdj_self theorem — No self-adjacency in a DAG.
    V :
    Type u_1
    shared
    G :
    DAG V
    shared
    a :
    V
    ¬ G.UAdj a a
    Proof (Lean source)
    theorem not_uAdj_self (a : V) : ¬ G.UAdj a a := fun h => h.elim (G.irrefl a) (G.irrefl a)
    Causalean.Graph.DAG.not_uAdj_self · Causalean/Graph/MarkovEquiv/Readoff.lean:283
  • dSeparable_disjoint_of_not_uAdj theorem — Disjoint skeleton read-off witness. Two distinct non-adjacent vertices are d-separated by a conditioning set disjoint from both endpoints (the parents of the topologically later one, which contain neither a nor b).
    V :
    Type u_1
    shared
    G :
    DAG V
    shared
    a b :
    V
    hne :
    a ≠ b
    h :
    ¬ G.UAdj a b
    ∃ Z : Finset V,
    conclusion 1
    a ∉ Z
    conclusion 2
    b ∉ Z
    conclusion 3
    G.dSep {a} {b} Z
    Proof (Lean source)
    theorem dSeparable_disjoint_of_not_uAdj {a b : V} (hne : a ≠ b) (h : ¬ G.UAdj a b) : ∃ Z : Finset V, a ∉ Z ∧ b ∉ Z ∧ G.dSep {a} {b} Z := by have htop_ne : G.topoOrder a ≠ G.topoOrder b := fun htop => hne (G.topoOrder_injective htop) rcases lt_or_gt_of_ne htop_ne with hlt | hlt · refine ⟨G.parents b, fun hmem => h (inl (G.mem_parents.mp hmem)), fun hmem => G.irrefl b (G.mem_parents.mp hmem), G.dSep_parents_of_topoOrder_lt hlt h⟩ · have hba : ¬ G.UAdj b a := fun h' => h (G.UAdj_symm h') refine ⟨G.parents a, fun hmem => G.irrefl a (G.mem_parents.mp hmem), fun hmem => h (inr (G.mem_parents.mp hmem)), G.dSep_symm _ _ _ (G.dSep_parents_of_topoOrder_lt hlt hba)⟩
    Causalean.Graph.DAG.dSeparable_disjoint_of_not_uAdj · Causalean/Graph/MarkovEquiv/Readoff.lean:287
  • immorality_iff_colliderSep_disjoint theorem — Disjoint collider read-off. For an unshielded triple a — b — c, b is the collider a → b ← c iff every separator of a, c that contains b and excludes a, c fails to d-separate — equivalently, b lies in no such separator. (The endpoint-disjoint form used to transport immoralities across Markov-equivalent graphs.)
    V :
    Type u_1
    shared
    G :
    DAG V
    shared
    a b c :
    V
    hab :
    G.UAdj a b
    hcb :
    G.UAdj c b
    hac :
    ¬ G.UAdj a c
    hne :
    a ≠ c
    G.IsImmorality a b c ↔ ∀ Z : Finset V, b ∈ Z → a ∉ Z → c ∉ Z → ¬ G.dSep {a} {c} Z
    Proof (Lean source)
    theorem immorality_iff_colliderSep_disjoint {a b c : V} (hab : G.UAdj a b) (hcb : G.UAdj c b) (hac : ¬ G.UAdj a c) (hne : a ≠ c) : G.IsImmorality a b c ↔ ∀ Z : Finset V, b ∈ Z → a ∉ Z → c ∉ Z → ¬ G.dSep {a} {c} Z := by constructor · intro him Z hbZ _ _ exact (G.immorality_iff_colliderSep hab hcb hac hne).mp him Z hbZ · intro H refine ⟨?_, ?_, hac, hne⟩ <;> · by_contra hcontra have hnc : ¬ G.IsCollider a b c := by rintro ⟨h1, h2⟩; first | exact hcontra h1 | exact hcontra h2 have htop_ne : G.topoOrder a ≠ G.topoOrder c := fun htop => hne (G.topoOrder_injective htop) rcases lt_or_gt_of_ne htop_ne with hlt | hlt · have heby : G.edge b c := G.edge_to_later_of_nonCollider (le_of_lt hlt) hab hcb hnc exact H (G.parents c) (G.mem_parents.mpr heby) (fun hmem => hac (inl (G.mem_parents.mp hmem))) (fun hmem => G.irrefl c (G.mem_parents.mp hmem)) (G.dSep_parents_of_topoOrder_lt hlt hac) · have hac' : ¬ G.UAdj c a := fun h' => hac (G.UAdj_symm h') have hnc' : ¬ G.IsCollider c b a := by rintro ⟨h1, h2⟩; exact hnc ⟨h2, h1⟩ have heby : G.edge b a := G.edge_to_later_of_nonCollider (le_of_lt hlt) hcb hab hnc' exact H (G.parents a) (G.mem_parents.mpr heby) (fun hmem => G.irrefl a (G.mem_parents.mp hmem)) (fun hmem => hac (inr (G.mem_parents.mp hmem))) (G.dSep_symm _ _ _ (G.dSep_parents_of_topoOrder_lt hlt hac'))
    Causalean.Graph.DAG.immorality_iff_colliderSep_disjoint · Causalean/Graph/MarkovEquiv/Readoff.lean:302
  • sameSkeleton_of_markovEquiv theorem — Markov equivalence ⇒ same skeleton.
    V :
    Type u_1
    DAG V
    shared
    h :
    MarkovEquiv G₁ G₂
    SameSkeleton G₁ G₂
    Proof (Lean source)
    theorem sameSkeleton_of_markovEquiv {G₁ G₂ : DAG V} (h : MarkovEquiv G₁ G₂) : SameSkeleton G₁ G₂ := by -- One-directional transfer: a disjoint separator of a non-adjacent pair transports. have key : ∀ {Ga Gb : DAG V}, MarkovEquiv Ga Gb → ∀ a b, a ≠ b → ¬ Ga.UAdj a b → ¬ Gb.UAdj a b := by intro Ga Gb hab a b hne hna hUAdj obtain ⟨Z, haZ, hbZ, hsep⟩ := Ga.dSeparable_disjoint_of_not_uAdj hne hna exact Gb.not_dSeparable_of_uAdj hUAdj Z ((hab {a} {b} Z).mp hsep) intro a b by_cases hne : a = b · subst hne; exact iff_of_false (G₁.not_uAdj_self a) (G₂.not_uAdj_self a) · constructor · intro h1; by_contra h2; exact key h.symm a b hne h2 h1 · intro h1; by_contra h2; exact key h a b hne h2 h1
    Causalean.Graph.MarkovEquiv.sameSkeleton_of_markovEquiv · Causalean/Graph/MarkovEquiv/Readoff.lean:337
  • sameImmoralities_of_markovEquiv theorem — Markov equivalence ⇒ same v-structures.
    V :
    Type u_1
    DAG V
    shared
    h :
    MarkovEquiv G₁ G₂
    Proof (Lean source)
    theorem sameImmoralities_of_markovEquiv {G₁ G₂ : DAG V} (h : MarkovEquiv G₁ G₂) : SameImmoralities G₁ G₂ := by have hskel : SameSkeleton G₁ G₂ := sameSkeleton_of_markovEquiv h intro a b c -- Dispose of the cases where the triple is not unshielded; then use the collider read-off. by_cases hne : a = c · exact iff_of_false (fun him => him.2.2.2 hne) (fun him => him.2.2.2 hne) · by_cases hac : G₁.UAdj a c · have hac₂ : G₂.UAdj a c := (hskel a c).mp hac exact iff_of_false (fun him => him.2.2.1 hac) (fun him => him.2.2.1 hac₂) · have hac₂ : ¬ G₂.UAdj a c := fun h' => hac ((hskel a c).mpr h') by_cases hab : G₁.UAdj a b · by_cases hcb : G₁.UAdj c b · -- the unshielded triple case in both graphs have hab₂ : G₂.UAdj a b := (hskel a b).mp hab have hcb₂ : G₂.UAdj c b := (hskel c b).mp hcb rw [G₁.immorality_iff_colliderSep_disjoint hab hcb hac hne, G₂.immorality_iff_colliderSep_disjoint hab₂ hcb₂ hac₂ hne] refine forall_congr' (fun Z => ?_) refine imp_congr_right (fun _ => ?_) refine imp_congr_right (fun haZ => ?_) refine imp_congr_right (fun hcZ => ?_) exact not_congr (h {a} {c} Z) · -- c, b non-adjacent in both: both sides false (needs `edge c b`, hence `UAdj c b`) have hcb₂ : ¬ G₂.UAdj c b := fun h' => hcb ((hskel c b).mpr h') exact iff_of_false (fun him => hcb (inl him.2.1)) (fun him => hcb₂ (inl him.2.1)) · -- a, b non-adjacent in both: both sides false (needs `edge a b`, hence `UAdj a b`) have hab₂ : ¬ G₂.UAdj a b := fun h' => hab ((hskel a b).mpr h') exact iff_of_false (fun him => hab (inl him.1)) (fun him => hab₂ (inl him.1))
    Causalean.Graph.MarkovEquiv.sameImmoralities_of_markovEquiv · Causalean/Graph/MarkovEquiv/Readoff.lean:354