Graph

Causal graphs: DAGs, d-separation via Bayes-Ball, SWIGs and their splits, and c-components.

DSep 25 core · 58 supporting · 7 submodules d-separation on directed acyclic graphs: the Bayes-Ball characterization, ancestral closures, moralization bridges, and equivalences between the criteria. Finite­Density 64 core · 63 supporting · 7 submodules Finite DAG product-density factorizations: coordinate dependence, normalized leaf elimination, and nonancestor-intervention marginal invariance without an SCM representation. Markov­Equiv 28 core · 55 supporting · 6 submodules Entry point for the formalization of the Verma–Pearl characterization of Markov equivalence (Verma & Pearl, *Equivalence and synthesis of causal models*, 1990): two directed acyclic graphs declare the same conditional-in
DAG 18 core · 24 supporting This file provides finite directed acyclic graphs. ★ DAG★ acyclic_of_topoOrder

Directed Acyclic Graphs

This file provides finite directed acyclic graphs. A DAG is represented by a decidable edge relation together with the standard acyclicity condition: no vertex reaches itself along a directed path (∀ v, ¬ Relation.TransGen edge v v). This is the textbook definition, and it is the entire data of the structure — a DAG is determined by its edge relation.

A topological numbering is not part of the definition; it is derived. The strict ancestors of a vertex are computed by a finite backward-reachability fixpoint (ancClosure), which yields both a decidable ancestor relation (decIsAncestor, order-free and computable) and a canonical strict-ancestor count (ancestorRank, computable). The topological order topoOrder v = rank v * |V| + enum v is built on the rank; it satisfies injectivity (topoOrder_injective) and edge-consistency (topoOrder_lt), so downstream constructions that need a topological order use it exactly as before. topoOrder is noncomputable because the tie-breaking enumeration of a bare finite type needs a choice of ordering; it is used purely for its ordering properties, never reduced on concrete values (decidable ancestry goes through ancClosure).

This file also defines immediate neighborhoods (parents, children), strict reachability (isAncestor, isDescendant), finite ancestor/descendant sets, set-level ancestry operations (ancestralSet, descendantsSet), non-descendants, and roots.

References

  • Basic Concepts.tex, Definition 1 (Directed Acyclic Graph)
structure DAG reviewed
Causalean

A Directed Acyclic Graph on a finite vertex type: a decidable edge relation together with the condition that no vertex is connected to itself by a directed path — the transitive closure of the edge relation is irreflexive. Irreflexivity of the transitive closure is exactly the statement that the graph has no directed cycle.

Definition (Lean source)
V :
The edge relation: `edge u v` means there is a directed edge from `u` to `v`.
edge :
V → V → Prop
Decidability of the edge relation.
decEdge :
**Acyclicity.** No vertex reaches itself along a directed path: the transitive closure of `edge` is irreflexive. Equivalently, the graph has no directed cycle. This is the defining property of a DAG.
acyclic :
∀ v, ¬ TransGen edge v v
def parents reviewed
Causalean.DAG

For a finite directed acyclic graph on a vertex population and a vertex, the parent set is the finite set of all vertices having a directed edge into that vertex.

Definition (Lean source)
V :
Type u_2
shared
G :
DAG V
shared
v :
V
parents G v :
Finset.univ.filter (fun u => G.edge u v)
Causalean.DAG.parents · Causalean/Graph/DAG.lean:81 · uses DAG
def children reviewed
Causalean.DAG

For a finite directed acyclic graph on a vertex population and a vertex, the child set is the finite set of all vertices to which that vertex has a directed edge.

Definition (Lean source)
V :
Type u_2
shared
G :
DAG V
shared
v :
V
children G v :
Finset.univ.filter (fun w => G.edge v w)
Causalean.DAG.children · Causalean/Graph/DAG.lean:85 · uses DAG
inductive isAncestor reviewed
Causalean.DAG

For a finite vertex population with decidable equality and a directed acyclic graph on that population, the ancestor relation holds from one vertex to another exactly when there is a directed path from the former to the latter. It is established either by a directed edge from the former vertex to the latter or by an existing ancestor path followed by a directed edge.

Definition (Lean source)
V :
Type u_2
shared
G :
DAG V
shared
isAncestor G :
V → V → Prop
edge :
{u v : V} : G.edge u v → isAncestor u v
trans :
{u w v : V} : isAncestor u w → G.edge w v → isAncestor u v
Causalean.DAG.isAncestor · Causalean/Graph/DAG.lean:101 · uses DAG
def isDescendant reviewed
Causalean.DAG

For a finite directed acyclic graph on a vertex population and two vertices, the first and the second, the descendant relation holds precisely when there is a directed path from the second vertex to the first.

Definition (Lean source)
V :
Type u_2
shared
G :
DAG V
shared
u v :
V
isDescendant G u v :
Prop
G.isAncestor v u
Causalean.DAG.isDescendant · Causalean/Graph/DAG.lean:155 · uses DAG
def ancStep reviewed
Causalean.DAG

For a finite directed acyclic graph on a vertex population and a finite vertex set, one backward ancestor-expansion step returns that set together with every parent of every member of the set.

Definition (Lean source)
V :
Type u_2
shared
G :
DAG V
shared
S :
ancStep G S :
S ∪ S.biUnion G.parents
Causalean.DAG.ancStep · Causalean/Graph/DAG.lean:162 · uses DAG
def ancClosure reviewed
Causalean.DAG

For a finite directed acyclic graph on a vertex population and a vertex, the strict-ancestor set is obtained by starting with the vertex’s parents and applying backward ancestor expansion once for each vertex in the population.

Definition (Lean source)
V :
Type u_2
shared
G :
DAG V
shared
v :
V
ancClosure G v :
(G.ancStep)^[card V] (G.parents v)
Causalean.DAG.ancClosure · Causalean/Graph/DAG.lean:165 · uses DAG
def ancestors reviewed
Causalean.DAG

For a finite directed acyclic graph on a vertex population and a vertex, the ancestor set is the finite set of all vertices from which a directed path reaches that vertex.

Definition (Lean source)
V :
Type u_2
shared
G :
DAG V
shared
v :
V
ancestors G v :
Finset.univ.filter (fun u => G.isAncestor u v)
Causalean.DAG.ancestors · Causalean/Graph/DAG.lean:304 · uses DAG
def descendants reviewed
Causalean.DAG

For a finite directed acyclic graph on a vertex population and a vertex, the descendant set is the finite set of all vertices reachable from that vertex by a directed path.

Definition (Lean source)
V :
Type u_2
shared
G :
DAG V
shared
v :
V
descendants G v :
Finset.univ.filter (fun w => G.isAncestor v w)
Causalean.DAG.descendants · Causalean/Graph/DAG.lean:308 · uses DAG
def ancestorsSet reviewed
Causalean.DAG

For a finite directed acyclic graph on a vertex population and a finite vertex set, the set of its strict ancestors contains exactly the vertices from which a directed path reaches at least one member of the given set.

Definition (Lean source)
V :
Type u_2
shared
G :
DAG V
shared
S :
ancestorsSet G S :
Finset.univ.filter (fun u => ∃ v ∈ S, G.isAncestor u v)
Causalean.DAG.ancestorsSet · Causalean/Graph/DAG.lean:336 · uses DAG
def ancestralSet reviewed
Causalean.DAG

For a finite directed acyclic graph on a vertex population and a finite vertex set, its ancestral closure is that set together with every vertex from which a directed path reaches one of its members.

Definition (Lean source)
V :
Type u_2
shared
G :
DAG V
shared
S :
ancestralSet G S :
S ∪ G.ancestorsSet S
Causalean.DAG.ancestralSet · Causalean/Graph/DAG.lean:340 · uses DAG
def descendantsSet reviewed
Causalean.DAG

For a finite directed acyclic graph on a vertex population and a finite vertex set, the set of its strict descendants contains exactly the vertices reachable by a directed path from at least one member of the given set.

Definition (Lean source)
V :
Type u_2
shared
G :
DAG V
shared
S :
descendantsSet G S :
Finset.univ.filter (fun w => ∃ v ∈ S, G.isAncestor v w)
Causalean.DAG.descendantsSet · Causalean/Graph/DAG.lean:344 · uses DAG
def nonDescendants reviewed
Causalean.DAG

For a finite directed acyclic graph on a vertex population and a vertex, the non-descendant set contains exactly the vertices other than that vertex which cannot be reached from it by a directed path.

Definition (Lean source)
V :
Type u_2
shared
G :
DAG V
shared
v :
V
nonDescendants G v :
Finset.univ.filter (fun w => ¬G.isAncestor v w ∧ w ≠ v)
Causalean.DAG.nonDescendants · Causalean/Graph/DAG.lean:348 · uses DAG
def ancestorRank reviewed
Causalean.DAG

For a finite directed acyclic graph on a vertex population and a vertex, its ancestor rank is the number of that vertex’s strict ancestors.

Definition (Lean source)
V :
Type u_2
shared
G :
DAG V
shared
v :
V
ancestorRank G v :
(G.ancClosure v).card
Causalean.DAG.ancestorRank · Causalean/Graph/DAG.lean:356 · uses DAG
def topoOrder reviewed
Causalean.DAG

For a finite directed acyclic graph on a vertex population and a vertex, the derived topological number is its number of strict ancestors times the population size, plus a fixed tie-breaking enumeration number. This number is injective across vertices and strictly increases along every directed edge.

Definition (Lean source)
V :
Type u_2
shared
G :
DAG V
shared
v :
V
topoOrder G v :
G.ancestorRank v * card V + (Fintype.equivFin V v).val
Causalean.DAG.topoOrder · Causalean/Graph/DAG.lean:372 · uses DAG
def isRoot reviewed
Causalean.DAG

For a finite directed acyclic graph on a vertex population and a vertex, the root condition holds exactly when no directed edge enters that vertex.

Definition (Lean source)
V :
Type u_2
shared
G :
DAG V
shared
v :
V
isRoot G v :
Prop
G.parents v = ∅
Causalean.DAG.isRoot · Causalean/Graph/DAG.lean:424 · uses DAG
def roots reviewed
Causalean.DAG

For a finite directed acyclic graph on a vertex population, the root set is the finite set of all vertices with no incoming directed edge.

Definition (Lean source)
V :
Type u_2
shared
G :
DAG V
shared
roots G :
Finset.univ.filter (fun v => G.isRoot v)
Causalean.DAG.roots · Causalean/Graph/DAG.lean:433 · uses DAG
theorem acyclic_of_topoOrder reviewed
Causalean.DAG

Acyclicity from a topological ranking. Given an edge relation e on V and a ranking function τ into a type equipped with a transitive, irreflexive relation r, if τ strictly increases (with respect to r) along every edge of e, then e has no directed cycle: no vertex is reachable from itself via the transitive closure of e.

Formal statement
V :
Type u_2
shared
W :
Type*
W → W → Prop
e :
V → V → Prop
τ :
V → W
:
∀ u v
if
e u v
then
r (τ u) (τ v)
v :
¬ TransGen e v v
Proof (Lean source)
theorem acyclic_of_topoOrder {W : Type*} {r : W → W → Prop} [IsTrans W r] [Irrefl r] {e : V → V → Prop} {τ : V → W} (hτ : ∀ u v, e u v → r (τ u) (τ v)) : ∀ v, ¬ TransGen e v v := by have key : ∀ {a b : V}, TransGen e a b → r (τ a) (τ b) := by intro a b h induction h with | single hab => exact hτ _ _ hab | tail _ hbc ih => exact IsTrans.trans _ _ _ ih (hτ _ _ hbc) intro v hv exact absurd (key hv) (Std.Irrefl.irrefl _)
Causalean.DAG.acyclic_of_topoOrder · Causalean/Graph/DAG.lean:442
24 supporting declarations (lemmas, instances)
  • mem_parents theorem — Membership characterization for parents: u ∈ G.parents v ↔ G.edge u v.
    V :
    Type u_2
    shared
    G :
    DAG V
    shared
    v u :
    V
    u ∈ G.parents v ↔ G.edge u v
    Proof (Lean source)
    theorem mem_parents {v u : V} : u ∈ G.parents v ↔ G.edge u v := by simp [parents]
    Causalean.DAG.mem_parents · Causalean/Graph/DAG.lean:89
  • mem_children theorem — Membership characterization for children: w ∈ G.children v ↔ G.edge v w.
    V :
    Type u_2
    shared
    G :
    DAG V
    shared
    v w :
    V
    w ∈ G.children v ↔ G.edge v w
    Proof (Lean source)
    theorem mem_children {v w : V} : w ∈ G.children v ↔ G.edge v w := by simp [children]
    Causalean.DAG.mem_children · Causalean/Graph/DAG.lean:93
  • isAncestor_iff_transGen theorem — The inductive ancestor relation coincides with Relation.TransGen of the edge relation: both are the transitive closure of the edge relation.
    V :
    Type u_2
    shared
    G :
    DAG V
    shared
    u v :
    V
    G.isAncestor u v ↔ TransGen G.edge u v
    Proof (Lean source)
    theorem isAncestor_iff_transGen {u v : V} : G.isAncestor u v ↔ TransGen G.edge u v := by constructor · intro h induction h with | edge he => exact Relation.TransGen.single he | trans _ he ih => exact ih.tail he · intro h induction h with | single he => exact isAncestor.edge he | tail _ he ih => exact isAncestor.trans ih he
    Causalean.DAG.isAncestor_iff_transGen · Causalean/Graph/DAG.lean:106
  • irrefl theorem — No vertex has an edge to itself (a directed self-loop would be a length-one cycle).
    V :
    Type u_2
    shared
    G :
    DAG V
    shared
    v :
    V
    ¬G.edge v v
    Proof (Lean source)
    theorem irrefl (v : V) : ¬G.edge v v := by intro h exact G.acyclic v (Relation.TransGen.single h)
    Causalean.DAG.irrefl · Causalean/Graph/DAG.lean:120
  • asymm theorem — If there is an edge from u to v, then there is no edge from v to u (a two-cycle is forbidden by acyclicity).
    V :
    Type u_2
    shared
    G :
    DAG V
    shared
    u v :
    V
    h :
    G.edge u v
    ¬G.edge v u
    Proof (Lean source)
    theorem asymm {u v : V} (h : G.edge u v) : ¬G.edge v u := by intro h' exact G.acyclic u ((Relation.TransGen.single h).tail h')
    Causalean.DAG.asymm · Causalean/Graph/DAG.lean:125
  • isAncestor_irrefl theorem — Ancestor relation is irreflexive: no vertex is its own ancestor (this is acyclicity, restated for the inductive ancestor relation).
    V :
    Type u_2
    shared
    G :
    DAG V
    shared
    v :
    V
    ¬G.isAncestor v v
    Proof (Lean source)
    theorem isAncestor_irrefl (v : V) : ¬G.isAncestor v v := by intro h exact G.acyclic v (G.isAncestor_iff_transGen.mp h)
    Causalean.DAG.isAncestor_irrefl · Causalean/Graph/DAG.lean:131
  • isAncestor_trans theorem — Ancestor relation is transitive.
    V :
    Type u_2
    shared
    G :
    DAG V
    shared
    u v w :
    V
    h1 :
    G.isAncestor u v
    h2 :
    G.isAncestor v w
    G.isAncestor u w
    Proof (Lean source)
    theorem isAncestor_trans {u v w : V} (h1 : G.isAncestor u v) (h2 : G.isAncestor v w) : G.isAncestor u w := by induction h2 with | edge he => exact isAncestor.trans h1 he | trans _ he ih => exact isAncestor.trans ih he
    Causalean.DAG.isAncestor_trans · Causalean/Graph/DAG.lean:137
  • isAncestor_child theorem — First-step decomposition: if u is an ancestor of v, then either edge u v or there exists a child c of u such that c is an ancestor of v.
    V :
    Type u_2
    shared
    G :
    DAG V
    shared
    u v :
    V
    h :
    G.isAncestor u v
    G.edge u v ∨ ∃ c, G.edge u c ∧ G.isAncestor c v
    Proof (Lean source)
    theorem isAncestor_child {u v : V} (h : G.isAncestor u v) : G.edge u v ∨ ∃ c, G.edge u c ∧ G.isAncestor c v := by induction h with | edge he => exact inl he | trans _ he' ih => rcases ih with he | ⟨c, huc, hcw⟩ · exact inr ⟨_, he, isAncestor.edge he'⟩ · exact inr ⟨c, huc, isAncestor.trans hcw he'⟩
    Causalean.DAG.isAncestor_child · Causalean/Graph/DAG.lean:144
  • subset_iterate_ancStep theorem — Any finite set of graph nodes remains contained after applying the graph's ancestor-step operation any number of times.
    V :
    Type u_2
    shared
    G :
    DAG V
    shared
    S :
    k :
    S ⊆ (G.ancStep)^[k] S
    Proof (Lean source)
    theorem subset_iterate_ancStep (S : Finset V) (k : ℕ) : S ⊆ (G.ancStep)^[k] S := by induction k with | zero => simp | succ k ih => rw [Function.iterate_succ_apply'] exact ih.trans (G.subset_ancStep _)
    Causalean.DAG.subset_iterate_ancStep · Causalean/Graph/DAG.lean:194
  • le_card_iterate theorem — If each of a specified number of successive applications of a function on finite sets strictly increases cardinality, the final set has grown by at least that number.
    V :
    Type u_2
    shared
    f :
    Finset V → Finset V
    S₀ :
    k :
    hstrict :
    ∀ j
    if
    j < k
    then
    (f^[j] S₀).card < (f^[j + 1] S₀).card
    (f^[0] S₀).card + k ≤ (f^[k] S₀).card
    Proof (Lean source)
    theorem le_card_iterate (f : Finset V → Finset V) (S₀ : Finset V) (k : ℕ) (hstrict : ∀ j, j < k → (f^[j] S₀).card < (f^[j + 1] S₀).card) : (f^[0] S₀).card + k ≤ (f^[k] S₀).card := by induction k with | zero => simp | succ k ih => have ihk := ih (fun j hj => hstrict j (Nat.lt_succ_of_lt hj)) have hlast := hstrict k (Nat.lt_succ_self k) omega
    Causalean.DAG.le_card_iterate · Causalean/Graph/DAG.lean:212
  • ancClosure_closed theorem — Every parent of a vertex in its computed ancestor set also belongs to that ancestor set.
    V :
    Type u_2
    shared
    G :
    DAG V
    shared
    v :
    V
    x :
    V
    hx :
    x ∈ G.ancClosure v
    G.parents x ⊆ G.ancClosure v
    Proof (Lean source)
    theorem ancClosure_closed (v : V) {x : V} (hx : x ∈ G.ancClosure v) : G.parents x ⊆ G.ancClosure v := by intro p hp have hbu : p ∈ (G.ancClosure v).biUnion G.parents := Finset.mem_biUnion.mpr ⟨x, hx, hp⟩ have hstep : p ∈ G.ancStep (G.ancClosure v) := by rw [ancStep, mem_union]; exact inr hbu rwa [G.ancStep_ancClosure v] at hstep
    Causalean.DAG.ancClosure_closed · Causalean/Graph/DAG.lean:261
  • isAncestor_mem_of_closed theorem — A finite set that contains every parent of each of its vertices contains every ancestor of each vertex it contains.
    V :
    Type u_2
    shared
    G :
    DAG V
    shared
    T :
    hT :
    ∀ x ∈ T, G.parents x ⊆ T
    u w :
    V
    h :
    G.isAncestor u w
    w ∈ T
    u ∈ T
    Proof (Lean source)
    theorem isAncestor_mem_of_closed {T : Finset V} (hT : ∀ x ∈ T, G.parents x ⊆ T) {u w : V} (h : G.isAncestor u w) : w ∈ T → u ∈ T := by induction h with | edge e => intro hw; exact hT _ hw (G.mem_parents.mpr e) | trans _ e ih => intro hw; exact ih (hT _ hw (G.mem_parents.mpr e))
    Causalean.DAG.isAncestor_mem_of_closed · Causalean/Graph/DAG.lean:270
  • mem_ancClosure theorem — Membership in the backward-reachability fixpoint is exactly ancestry: a vertex lies in G.ancClosure v iff it is an ancestor of v. This makes the ancestor relation decidable using only the (decidable) edge relation, with no reference to any topological order.
    V :
    Type u_2
    shared
    G :
    DAG V
    shared
    u v :
    V
    u ∈ G.ancClosure v ↔ G.isAncestor u v
    Proof (Lean source)
    theorem mem_ancClosure {u v : V} : u ∈ G.ancClosure v ↔ G.isAncestor u v := by constructor · intro hu have hbase : ∀ x ∈ G.parents v, G.isAncestor x v := fun x hx => isAncestor.edge (G.mem_parents.mp hx) exact G.iterate_ancStep_sound hbase (card V) u hu · intro h have hclosed : ∀ x ∈ G.ancClosure v, G.parents x ⊆ G.ancClosure v := fun x hx => G.ancClosure_closed v hx have hpar : G.parents v ⊆ G.ancClosure v := G.subset_iterate_ancStep (G.parents v) (card V) cases h with | edge e => exact hpar (G.mem_parents.mpr e) | trans h' e => exact G.isAncestor_mem_of_closed hclosed h' (hpar (G.mem_parents.mpr e))
    Causalean.DAG.mem_ancClosure · Causalean/Graph/DAG.lean:279
  • decIsAncestor instance — For a finite vertex population with decidable equality and a directed acyclic graph on that population, the decision procedure for the ancestor relation determines, for every ordered pair of vertices, whether the first is an ancestor of the second.
    V :
    Type u_2
    shared
    G :
    DAG V
    shared
    decIsAncestor G :
    DecidableRel G.isAncestor
    fun u v => decidable_of_iff _ (G.mem_ancClosure (u := u) (v := v))
    Causalean.DAG.decIsAncestor · Causalean/Graph/DAG.lean:298
  • mem_ancestors theorem — Membership characterization for ancestors: u ∈ G.ancestors v ↔ G.isAncestor u v.
    V :
    Type u_2
    shared
    G :
    DAG V
    shared
    v u :
    V
    u ∈ G.ancestors v ↔ G.isAncestor u v
    Proof (Lean source)
    theorem mem_ancestors {v u : V} : u ∈ G.ancestors v ↔ G.isAncestor u v := by simp [ancestors]
    Causalean.DAG.mem_ancestors · Causalean/Graph/DAG.lean:312
  • mem_descendants theorem — Membership characterization for descendants: w ∈ G.descendants v ↔ G.isAncestor v w.
    V :
    Type u_2
    shared
    G :
    DAG V
    shared
    v w :
    V
    w ∈ G.descendants v ↔ G.isAncestor v w
    Proof (Lean source)
    theorem mem_descendants {v w : V} : w ∈ G.descendants v ↔ G.isAncestor v w := by simp [descendants]
    Causalean.DAG.mem_descendants · Causalean/Graph/DAG.lean:316
  • parents_subset_ancestors theorem — Parents are a subset of ancestors.
    V :
    Type u_2
    shared
    G :
    DAG V
    shared
    v :
    V
    G.parents v ⊆ G.ancestors v
    Proof (Lean source)
    theorem parents_subset_ancestors (v : V) : G.parents v ⊆ G.ancestors v := by intro u hu rw [mem_ancestors] exact isAncestor.edge (G.mem_parents.mp hu)
    Causalean.DAG.parents_subset_ancestors · Causalean/Graph/DAG.lean:320
  • children_subset_descendants theorem — Children are a subset of descendants.
    V :
    Type u_2
    shared
    G :
    DAG V
    shared
    v :
    V
    G.children v ⊆ G.descendants v
    Proof (Lean source)
    theorem children_subset_descendants (v : V) : G.children v ⊆ G.descendants v := by intro w hw rw [mem_descendants] exact isAncestor.edge (G.mem_children.mp hw)
    Causalean.DAG.children_subset_descendants · Causalean/Graph/DAG.lean:326
  • ancestorRank_lt_of_edge theorem — Along an edge the strict-ancestor count strictly increases.
    V :
    Type u_2
    shared
    G :
    DAG V
    shared
    a b :
    V
    hab :
    G.edge a b
    G.ancestorRank a < G.ancestorRank b
    Proof (Lean source)
    theorem ancestorRank_lt_of_edge {a b : V} (hab : G.edge a b) : G.ancestorRank a < G.ancestorRank b := by unfold ancestorRank apply Finset.card_lt_card rw [Finset.ssubset_iff_of_subset] · refine ⟨a, ?_, ?_⟩ · rw [mem_ancClosure]; exact isAncestor.edge hab · rw [mem_ancClosure]; exact G.isAncestor_irrefl a · intro w hw rw [mem_ancClosure] at hw ⊢ exact G.isAncestor_trans hw (isAncestor.edge hab)
    Causalean.DAG.ancestorRank_lt_of_edge · Causalean/Graph/DAG.lean:359
  • topoOrder_injective theorem — The derived topological order is injective, so it provides a canonical total order on the finite vertex type.
    V :
    Type u_2
    shared
    G :
    DAG V
    shared
    Injective G.topoOrder
    Proof (Lean source)
    theorem topoOrder_injective : Injective G.topoOrder := by intro u v huv unfold topoOrder at huv have hu : (Fintype.equivFin V u).val < card V := (Fintype.equivFin V u).isLt have hv : (Fintype.equivFin V v).val < card V := (Fintype.equivFin V v).isLt have hmod : (Fintype.equivFin V u).val = (Fintype.equivFin V v).val := by have := congrArg (· % card V) huv simpa [Nat.mul_add_mod, Nat.mod_eq_of_lt hu, Nat.mod_eq_of_lt hv] using this have : (Fintype.equivFin V u) = (Fintype.equivFin V v) := Fin.ext hmod exact (Fintype.equivFin V).injective this
    Causalean.DAG.topoOrder_injective · Causalean/Graph/DAG.lean:383
  • topoOrder_lt theorem — The derived topological order is edge-consistent: if there is an edge from u to v, then topoOrder u < topoOrder v. This witnesses acyclicity.
    V :
    Type u_2
    shared
    G :
    DAG V
    shared
    u :
    V
    shared
    v :
    V
    shared
    u v :
    G.edge u v
    G.topoOrder u < G.topoOrder v
    Proof (Lean source)
    theorem topoOrder_lt : ∀ u v, G.edge u v → G.topoOrder u < G.topoOrder v := by intro u v huv unfold topoOrder have hrank : G.ancestorRank u < G.ancestorRank v := G.ancestorRank_lt_of_edge huv have hv : (Fintype.equivFin V v).val < card V := (Fintype.equivFin V v).isLt have hu : (Fintype.equivFin V u).val < card V := (Fintype.equivFin V u).isLt have hle : G.ancestorRank u + 1 ≤ G.ancestorRank v := by omega have key : (G.ancestorRank u + 1) * card V ≤ G.ancestorRank v * card V := Nat.mul_le_mul hle (le_refl (card V)) have expand : (G.ancestorRank u + 1) * card V = G.ancestorRank u * card V + card V := by rw [Nat.add_mul, Nat.one_mul] omega
    Causalean.DAG.topoOrder_lt · Causalean/Graph/DAG.lean:396
  • isAncestor_topoOrder_lt theorem — Ancestors respect the topological order: if u is an ancestor of v then G.topoOrder u < G.topoOrder v, so ancestor pairs are strictly ordered by topoOrder.
    V :
    Type u_2
    shared
    G :
    DAG V
    shared
    u v :
    V
    h :
    G.isAncestor u v
    G.topoOrder u < G.topoOrder v
    Proof (Lean source)
    theorem isAncestor_topoOrder_lt {u v : V} (h : G.isAncestor u v) : G.topoOrder u < G.topoOrder v := by induction h with | edge he => exact G.topoOrder_lt _ _ he | trans _ he ih => exact Nat.lt_trans ih (G.topoOrder_lt _ _ he)
    Causalean.DAG.isAncestor_topoOrder_lt · Causalean/Graph/DAG.lean:412
  • decIsRoot instance — For a finite vertex population with decidable equality, a directed acyclic graph on that population, and a vertex, the decision procedure for the root condition determines whether no directed edge enters that vertex.
    V :
    Type u_2
    shared
    G :
    DAG V
    shared
    v :
    V
    decIsRoot G v :
    Decidable (G.isRoot v)
    inferInstanceAs (Decidable (G.parents v = ∅))
    Causalean.DAG.decIsRoot · Causalean/Graph/DAG.lean:427
  • isAncestor_has_parent theorem — Every vertex reached by a nonempty directed path has an incoming edge, namely the final edge of that path.
    V :
    Type u_2
    shared
    G :
    DAG V
    u v :
    V
    h :
    G.isAncestor u v
    G.parents v ≠ ∅
    Proof (Lean source)
    theorem isAncestor_has_parent (G : DAG V) {u v : V} (h : G.isAncestor u v) : G.parents v ≠ ∅ := by intro hempty have hmem : ∀ w, w ∉ G.parents v := fun w => (Finset.eq_empty_iff_forall_notMem.mp hempty) w induction h with | edge he => exact hmem u (G.mem_parents.mpr he) | trans _ he _ => exact hmem _ (G.mem_parents.mpr he)
    Causalean.DAG.isAncestor_has_parent · Causalean/Graph/DAG.lean:467
SWIG 18 core · 21 supporting This file defines the graph-theoretic structure of a Single World Intervention Graph (SWIG). ★ SWIGNode★ swig_fixed_are_roots★ swig_target_parents★ initialSWIG_random_edge★ initialSWIG_fixed_isolated★ SWIGGraph

Single World Intervention Graphs

This file defines the graph-theoretic structure of a Single World Intervention Graph (SWIG). A base variable n : N has two SWIG nodes: .random n, used for the natural random variable, and .fixed n, used for intervention values. The shared value-space family swigΩ gives both copies the same measurable value space as the base variable.

The core construction is swigDAG G targets. It keeps incoming edges into targeted random nodes, reroutes outgoing edges from each targeted random node to the corresponding fixed node, and leaves fixed nodes for non-targets isolated. The interleaved order swigTopo proves that this edge relation is acyclic, and the basic lemmas describe roots, target parents, and the initial no-intervention SWIG.

The structure SWIGGraph packages a DAG on SWIG nodes together with the fixed, observed, and unobserved node sets, the link map ι from fixed nodes to their random counterparts, and root/classification invariants used by structural causal models. The namespace also provides graph equivalence up to edge and partition equality, plus parent/child classification lemmas. The monolithic multi-target split operation is defined in Causalean.Graph.SWIGSplitMono.

inductive SWIGNode reviewed
Causalean

For a collection of base variables, the population of nodes in a single-world intervention graph consists of a random-node constructor that assigns each base variable its natural random copy and a fixed-node constructor that assigns each base variable its intervention copy. Thus it is the disjoint union of two copies of the base-variable population.

Definition (Lean source)
N :
Type*
SWIGNode N :
Type u_1
random :
N → SWIGNode N
fixed :
N → SWIGNode N
deriving DecidableEq, Repr
Causalean.SWIGNode · Causalean/Graph/SWIG.lean:73
def equiv reviewed
Causalean.SWIGNode

The split-node equivalence bijects each random copy of a base variable with the first copy of that variable and each fixed copy with the second copy.

Definition (Lean source)
N :
Type u_1
shared
equiv :
SWIGNode N ≃ N ⊕ N
where
| .random n => inl n
| .fixed n => inr n
invFun
| .inl n => .random n
| .inr n => .fixed n
left_inv := by intro x; cases x <;> rfl
right_inv := by intro x; cases x <;> rfl
Causalean.SWIGNode.equiv · Causalean/Graph/SWIG.lean:102 · uses SWIGNode
abbrev swigΩ reviewed
Causalean

For a family of value spaces indexed by base variables, the single-world-intervention value-space family assigns to each random copy its base variable's value space and to each fixed copy that same base variable's value space.

Definition (Lean source)
N :
Type*
Ω :
N → Type*
swigΩ Ω :
SWIGNode N → Type _
clause 1
| .random n => Ω n
clause 2
| .fixed n => Ω n
def swigEdge reviewed
Causalean

For a finite directed acyclic graph and a set of intervention targets, the single-world-intervention edge relation declares that a random copy points to a random copy precisely when the corresponding original edge starts outside the targets, that a fixed copy points to a random copy precisely when its base variable is targeted and has the corresponding original outgoing edge, and that every other ordered pair has no edge.

Definition (Lean source)
N :
Type u_1
shared
G :
DAG N
targets :
swigEdge G targets :
SWIGNode N → SWIGNode N → Prop
clause 1
| .random u, .random v => G.edge u v ∧ u ∉ targets
clause 2
| .fixed d, .random v => d ∈ targets ∧ G.edge d v
clause 3
| _, _ => False
def swigTopo reviewed
Causalean

For a finite directed acyclic graph, the interleaved topological order of its split nodes assigns each random copy one plus twice its base variable's topological position and each fixed copy twice that position.

Definition (Lean source)
N :
Type u_1
shared
G :
DAG N
swigTopo G :
SWIGNode N → ℕ
clause 1
| .random n => 2 * G.topoOrder n + 1
clause 2
| .fixed n => 2 * G.topoOrder n
def swigDAG reviewed
Causalean

For a finite directed acyclic graph and a set of intervention targets, the single-world intervention graph as a directed acyclic graph is obtained by replacing every outgoing edge of a targeted variable by an edge from its fixed copy while retaining all incoming edges to its random copy.

Definition (Lean source)
N :
Type u_1
shared
G :
DAG N
targets :
swigDAG G targets :
clause 1
edge := swigEdge G targets
clause 2
decEdge := swigEdge_decidable G targets
clause 3
acyclic := DAG.acyclic_of_topoOrder (swigTopo_lt G targets)
def iotaMap reviewed
Causalean

The link map on split nodes sends every fixed copy of a base variable to its random copy and leaves every random copy at that random copy.

Definition (Lean source)
N :
Type u_1
shared
iotaMap :
clause 1
| .fixed n => .random n
clause 2
| .random n => .random n
Causalean.iotaMap · Causalean/Graph/SWIG.lean:267 · uses SWIGNode
theorem swig_fixed_are_roots reviewed
Causalean

For any base DAG G, any set of intervention targets, and any node n, the fixed copy of n has no parents in the single-world intervention graph built from G and targets.

Formal statement
N :
Type u_1
shared
G :
DAG N
targets :
n :
N
(swigDAG G targets).parents (.fixed n) = ∅
Proof (Lean source)
theorem swig_fixed_are_roots (G : DAG N) (targets : Finset N) (n : N) : (swigDAG G targets).parents (.fixed n) = ∅ := by rw [Finset.eq_empty_iff_forall_notMem] intro x hx simp only [DAG.parents, swigDAG] at hx cases x <;> simp [swigEdge] at hx
theorem swig_target_parents reviewed
Causalean

For any base DAG G, any set of intervention targets, and any node d, the parents of the random copy of d in the single-world intervention graph are exactly the copies of d's original parents in G, each represented by its random version if it is not a target and by its fixed version if it is.

Formal statement
N :
Type u_1
shared
G :
DAG N
targets :
d :
N
x :
x ∈ (swigDAG G targets).parents (.random d)
↔ ∃ p, G.edge p d ∧ x = .random p ∧ p ∉ targets ∨ G.edge p d ∧ x = .fixed p ∧ p ∈ targets
Proof (Lean source)
theorem swig_target_parents (G : DAG N) (targets : Finset N) (d : N) : ∀ x : SWIGNode N, x ∈ (swigDAG G targets).parents (.random d) ↔ ∃ p, G.edge p d ∧ x = .random p ∧ p ∉ targets ∨ G.edge p d ∧ x = .fixed p ∧ p ∈ targets := by intro x rw [DAG.mem_parents] show swigEdge G targets x (.random d) ↔ _ constructor · intro hedge cases x with | random u => simp only [swigEdge] at hedge exact ⟨u, inl ⟨hedge.1, rfl, hedge.2⟩⟩ | fixed f => simp only [swigEdge] at hedge exact ⟨f, inr ⟨hedge.2, rfl, hedge.1⟩⟩ · intro ⟨p, hp⟩ rcases hp with ⟨hedge, rfl, hnt⟩ | ⟨hedge, rfl, ht⟩ · simp only [swigEdge]; exact ⟨hedge, hnt⟩ · simp only [swigEdge]; exact ⟨ht, hedge⟩
def initialSWIG reviewed
Causalean

For a finite directed acyclic graph, the initial single-world intervention graph is its split-node graph with no intervention targets, so every original edge joins random copies and every fixed copy is isolated.

Definition (Lean source)
N :
Type u_1
shared
G :
DAG N
initialSWIG G :
swigDAG G ∅
theorem initialSWIG_random_edge reviewed
Causalean

For any base DAG G and any nodes u, v, in the initial SWIG of G (the SWIG with no intervention targets), the random copies of u and v are joined by an edge exactly when u and v are joined by an edge in G.

Formal statement
N :
Type u_1
shared
G :
DAG N
u v :
N
(initialSWIG G).edge (.random u) (.random v) ↔ G.edge u v
Proof (Lean source)
theorem initialSWIG_random_edge (G : DAG N) (u v : N) : (initialSWIG G).edge (.random u) (.random v) ↔ G.edge u v := by simp [initialSWIG, swigDAG, swigEdge]
theorem initialSWIG_fixed_isolated reviewed
Causalean

For any base DAG G and any node n, the fixed copy of n has no parents in the initial SWIG of G (the SWIG with no intervention targets).

Formal statement
N :
Type u_1
shared
G :
DAG N
n :
N
(initialSWIG G).parents (.fixed n) = ∅
Proof (Lean source)
theorem initialSWIG_fixed_isolated (G : DAG N) (n : N) : (initialSWIG G).parents (.fixed n) = ∅ := swig_fixed_are_roots G ∅ n
structure SWIGGraph reviewed
Causalean

A Single-World Intervention Graph (SWIG), G = (S, V, U, E, ι) (Definition 4 from Basic Concepts.tex): a directed acyclic graph on the SWIG nodes whose vertices are partitioned into fixed intervention nodes, observed random nodes, and unobserved random nodes, where every fixed node is genuinely of fixed form, every observed node is of random form, every unobserved node is of random form, and the observed and unobserved sets are disjoint. Every edge of the graph has both endpoints classified as fixed, observed, or unobserved; the map sending each fixed intervention node to its random counterpart lands inside the observed nodes; fixed nodes and unobserved nodes have no parents; a fixed-form node absent from the fixed set is isolated, with neither parents nor children; and every child of a classified node is observed.

Definition (Lean source)
N :
The underlying DAG on SWIG nodes.
dag :
Fixed (intervention) nodes `S` as fixed SWIG nodes.
fixed :
Observed/endogenous random nodes `V` as random SWIG nodes.
observed :
Unobserved/exogenous random nodes `U` as random SWIG nodes.
unobserved :
All elements of `fixed` are of the form `.fixed n`.
fixed_is_fixed :
∀ s ∈ fixed, ∃ n : N, s = SWIGNode.fixed n
All elements of `observed` are of the form `.random n`.
observed_is_random :
∀ v ∈ observed, ∃ n : N, v = SWIGNode.random n
All elements of `unobserved` are of the form `.random n`.
unobserved_is_random :
∀ u ∈ unobserved, ∃ n : N, u = SWIGNode.random n
`observed` and `unobserved` are disjoint.
obs_unobs_disjoint :
Disjoint observed unobserved
Every vertex participating in any edge of `dag` is classified as fixed, observed, or unobserved. Trivially preserved under edge removal, which is why it replaces the older `obs_unobs_cover_random` that did not survive the `induce` operation.
dag_edges_classified :
∀ u v
if
dag.edge u v
then
u ∈ fixed ∪ observed ∪ unobserved ∧ v ∈ fixed ∪ observed ∪ unobserved
The image of `fixed` under `iotaMap` lies in `observed`.
fixed_image_in_observed :
∀ s ∈ fixed, iotaMap s ∈ observed
Fixed nodes are roots in `dag`.
fixed_are_roots :
∀ s ∈ fixed, dag.parents s = ∅
Unobserved nodes are roots in `dag`.
unobs_are_roots :
∀ u ∈ unobserved, dag.parents u = ∅
Any fixed-form node not listed in `fixed` is isolated in `dag`.
fixed_outside_fixed_isolated :
∀ n : N
if
SWIGNode.fixed n ∉ fixed
then
dag.parents (SWIGNode.fixed n) = ∅ ∧ dag.children (SWIGNode.fixed n) = ∅
Every child of any classified node is observed. This global child-classification invariant is used to rule out outgoing edges into fixed or latent-root nodes.
all_children_in_observed :
∀ u ∈ unobserved ∪ fixed ∪ observed, dag.children u ⊆ observed
Causalean.SWIGGraph · Causalean/Graph/SWIG.lean:367
def iota reviewed
Causalean.SWIGGraph

For a single-world intervention graph and a fixed intervention node in that graph, the canonical link map returns its random counterpart, together with the fact that this counterpart is observed.

Definition (Lean source)
N :
Type u_2
shared
G :
s :
{s // s ∈ G.fixed}
iota G s :
{v // v ∈ G.observed}
⟨iotaMap s, G.fixed_image_in_observed s s.property⟩
Causalean.SWIGGraph.iota · Causalean/Graph/SWIG.lean:435 · uses SWIGGraph , SWIGNode
def iotaNode reviewed
Causalean.SWIGGraph

For a single-world intervention graph and a fixed intervention node in that graph, the node-level canonical link is that node's random counterpart, with the membership certification omitted.

Definition (Lean source)
N :
Type u_2
shared
G :
s :
{s // s ∈ G.fixed}
iotaNode G s :
(G.iota s).1
Causalean.SWIGGraph.iotaNode · Causalean/Graph/SWIG.lean:440 · uses SWIGGraph , SWIGNode
def iotaN reviewed
Causalean.SWIGGraph

For a single-world intervention graph and a base variable whose fixed copy belongs to its fixed nodes, the base-variable link map returns the same base variable together with the fact that its random copy is observed.

Definition (Lean source)
N :
Type u_2
shared
G :
d :
{n : N // SWIGNode.fixed n ∈ G.fixed}
iotaN G d :
{n : N // SWIGNode.random n ∈ G.observed}
by refine ⟨d, ?_⟩ have := G.fixed_image_in_observed (SWIGNode.fixed d) d.property simpa [iotaMap] using this
Causalean.SWIGGraph.iotaN · Causalean/Graph/SWIG.lean:448 · uses SWIGGraph , SWIGNode
def isStandard reviewed
Causalean.SWIGGraph

For a single-world intervention graph, the standard-graph property holds exactly when it contains no fixed intervention nodes.

Definition (Lean source)
N :
Type u_2
shared
G :
isStandard G :
Prop
G.fixed = ∅
Causalean.SWIGGraph.isStandard · Causalean/Graph/SWIG.lean:457 · uses SWIGGraph
def Equivalent reviewed
Causalean.SWIGGraph

For two single-world intervention graphs, graph equivalence ignoring topological order holds exactly when they have the same directed edges, the same fixed nodes, the same observed nodes, and the same unobserved nodes.

Definition (Lean source)
N :
Type u_2
shared
G H :
Equivalent G H :
Prop
clause 1
u v :
G.dag.edge u v ↔ H.dag.edge u v
clause 2
G.fixed = H.fixed
clause 3
G.observed = H.observed
clause 4
G.unobserved = H.unobserved
Causalean.SWIGGraph.Equivalent · Causalean/Graph/SWIG.lean:464 · uses SWIGGraph
21 supporting declarations (lemmas, instances)
  • instDecidableEqSWIGNode instance
    deriving DecidableEq, Repr
    Causalean.instDecidableEqSWIGNode · Causalean/Graph/SWIG.lean:86
  • instReprSWIGNode instance
    deriving DecidableEq, Repr
    Causalean.instReprSWIGNode · Causalean/Graph/SWIG.lean:86
  • random_injective theorem — The random-node constructor is injective: equal random SWIG nodes come from the same base variable.
    N :
    Type u_1
    shared
    Injective (@SWIGNode.random N)
    Proof (Lean source)
    theorem random_injective : Injective (@SWIGNode.random N) := by intro a b h; cases h; rfl
    Causalean.SWIGNode.random_injective · Causalean/Graph/SWIG.lean:92
  • fixed_injective theorem — The fixed-node constructor is injective: equal fixed SWIG nodes come from the same base variable.
    N :
    Type u_1
    shared
    Injective (@SWIGNode.fixed N)
    Proof (Lean source)
    theorem fixed_injective : Injective (@SWIGNode.fixed N) := by intro a b h; cases h; rfl
    Causalean.SWIGNode.fixed_injective · Causalean/Graph/SWIG.lean:97
  • instFintype instance — For a finite collection of base variables, the finite enumeration of its split SWIG nodes contains exactly the random and fixed copy of every base variable.
    N :
    Type u_1
    shared
    instFintype :
    Fintype.ofEquiv (N ⊕ N) equiv.symm
    Causalean.SWIGNode.instFintype · Causalean/Graph/SWIG.lean:113
  • instMeasurableSpaceSwigΩ instance — For a collection of base variables, a family of base-variable value spaces, each equipped with a σ-algebra, and any split node, the σ-algebra on that node's SWIG value space is the σ-algebra of the corresponding base-variable value space.
    N :
    Type*
    Ω :
    N → Type*
    ∀ n, MeasurableSpace (Ω n)
    sn :
    instMeasurableSpaceSwigΩ Ω sn :
    clause 1
    | .random _ => inferInstance
    clause 2
    | .fixed _ => inferInstance
    Causalean.instMeasurableSpaceSwigΩ · Causalean/Graph/SWIG.lean:135
  • measurable_cast_family theorem — Transporting a value along an equality of indices is measurable.
    I :
    Type*
    I → Type*
    ∀ i, MeasurableSpace (X i)
    a b :
    I
    hab :
    a = b
    Measurable (cast (congrArg X hab) : X a → X b)
    Proof (Lean source)
    @[fun_prop] theorem measurable_cast_family {I : Type*} {X : I → Type*} [∀ i, MeasurableSpace (X i)] {a b : I} (hab : a = b) : Measurable (cast (congrArg X hab) : X a → X b) := by subst hab exact measurable_id
    Causalean.SCM.measurable_cast_family · Causalean/Graph/SWIG.lean:143
  • measurable_family_cast theorem — A measurable function remains measurable after transporting its codomain index.
    I γ :
    Type*
    I → Type*
    ∀ i, MeasurableSpace (X i)
    v w :
    I
    h :
    v = w
    f :
    γ → X v
    hf :
    Measurable (fun x => (h ▸ f x : X w))
    Proof (Lean source)
    @[fun_prop] theorem measurable_family_cast {I γ : Type*} {X : I → Type*} [∀ i, MeasurableSpace (X i)] [MeasurableSpace γ] {v w : I} (h : v = w) {f : γ → X v} (hf : Measurable f) : Measurable (fun x => (h ▸ f x : X w)) := by subst h exact hf
    Causalean.SCM.measurable_family_cast · Causalean/Graph/SWIG.lean:151
  • instStandardBorelSpaceSwigΩ instance — For a collection of base variables, a family of value spaces each equipped with a σ-algebra and forming a standard Borel space, and any split node, the standard Borel-space structure on that node's SWIG value space is inherited from the corresponding base-variable value space.
    N :
    Type*
    Ω :
    N → Type*
    ∀ n, MeasurableSpace (Ω n)
    ∀ n, StandardBorelSpace (Ω n)
    sn :
    instStandardBorelSpaceSwigΩ Ω sn :
    clause 1
    | .random _ => inferInstance
    clause 2
    | .fixed _ => inferInstance
    Causalean.instStandardBorelSpaceSwigΩ · Causalean/Graph/SWIG.lean:162
  • instNonemptySwigΩ instance — For a collection of base variables, a family of nonempty base-variable value spaces, and any split node, the nonemptiness guarantee for that node's SWIG value space is inherited from the corresponding base-variable value space.
    N :
    Type*
    Ω :
    N → Type*
    ∀ n, Nonempty (Ω n)
    sn :
    instNonemptySwigΩ Ω sn :
    Nonempty (swigΩ Ω sn)
    clause 1
    | .random _ => inferInstance
    clause 2
    | .fixed _ => inferInstance
    Causalean.instNonemptySwigΩ · Causalean/Graph/SWIG.lean:169
  • swigEdge_decidable instance — For a finite collection of base variables with decidable equality, a directed acyclic graph on those variables, and a finite set of intervention targets, the decision procedure for the single-world-intervention edge relation determines, for every ordered pair of split nodes, whether the pair is joined by a SWIG edge.
    N :
    Type u_1
    shared
    G :
    DAG N
    targets :
    swigEdge_decidable G targets :
    by intro a b cases a <;> cases b <;> simp only [swigEdge] <;> infer_instance
    Causalean.swigEdge_decidable · Causalean/Graph/SWIG.lean:199
  • swigTopo_lt theorem — Every SWIG edge points from a lower to a higher position in the interleaved topological order.
    N :
    Type u_1
    shared
    G :
    DAG N
    targets :
    u v :
    swigEdge G targets u v
    swigTopo G u < swigTopo G v
    Proof (Lean source)
    theorem swigTopo_lt (G : DAG N) (targets : Finset N) : ∀ u v, swigEdge G targets u v → swigTopo G u < swigTopo G v := by intro u v h cases u with | random u => cases v with | random v => simp only [swigEdge] at h simp only [swigTopo] have := G.topoOrder_lt u v h.1 omega | fixed _ => exact absurd h (by simp [swigEdge]) | fixed d => cases v with | random v => simp only [swigEdge] at h simp only [swigTopo] have := G.topoOrder_lt d v h.2 omega | fixed _ => exact absurd h (by simp [swigEdge])
    Causalean.swigTopo_lt · Causalean/Graph/SWIG.lean:223
  • iotaMap_fixed theorem — The link map sends the fixed copy of a base variable to its random copy.
    N :
    Type u_1
    shared
    n :
    N
    iotaMap (.fixed n : SWIGNode N) = .random n
    Proof (Lean source)
    theorem iotaMap_fixed (n : N) : iotaMap (.fixed n : SWIGNode N) = .random n := rfl
    Causalean.iotaMap_fixed · Causalean/Graph/SWIG.lean:279
  • swig_random_root_of_root theorem — If n is a root in G, then random n is a root in the SWIG.
    N :
    Type u_1
    shared
    G :
    DAG N
    targets :
    n :
    N
    hroot :
    G.parents n = ∅
    (swigDAG G targets).parents (.random n) = ∅
    Proof (Lean source)
    theorem swig_random_root_of_root (G : DAG N) (targets : Finset N) (n : N) (hroot : G.parents n = ∅) : (swigDAG G targets).parents (.random n) = ∅ := by rw [Finset.eq_empty_iff_forall_notMem] intro x hx rw [DAG.mem_parents] at hx replace hx : swigEdge G targets x (.random n) := hx cases x with | random u => obtain ⟨hedge, _⟩ := hx have : u ∈ G.parents n := G.mem_parents.mpr hedge simp [hroot] at this | fixed d => obtain ⟨_, hedge⟩ := hx have : d ∈ G.parents n := G.mem_parents.mpr hedge simp [hroot] at this
    Causalean.swig_random_root_of_root · Causalean/Graph/SWIG.lean:321
  • iotaNode_eq_iotaMap theorem — Forgetting the membership proof in the graph-level link map gives the node-level link map.
    N :
    Type u_2
    shared
    G :
    s :
    {s // s ∈ G.fixed}
    G.iotaNode s = iotaMap s
    Proof (Lean source)
    @[simp] theorem iotaNode_eq_iotaMap (G : SWIGGraph N) (s : {s // s ∈ G.fixed}) : G.iotaNode s = iotaMap s := rfl
    Causalean.SWIGGraph.iotaNode_eq_iotaMap · Causalean/Graph/SWIG.lean:444
  • refl theorem — SWIG graph equivalence is reflexive.
    N :
    Type u_2
    shared
    G :
    Proof (Lean source)
    @[refl] theorem Equivalent.refl (G : SWIGGraph N) : Equivalent G G := by unfold Equivalent refine intro ?hedge ?hfix · intro u v; exact Iff.rfl · exact intro rfl (intro rfl rfl)
    Causalean.SWIGGraph.Equivalent.refl · Causalean/Graph/SWIG.lean:481
  • symm theorem — SWIG graph equivalence is symmetric.
    N :
    Type u_2
    shared
    Proof (Lean source)
    @[symm] theorem Equivalent.symm {G H : SWIGGraph N} : Equivalent G H → Equivalent H G := by intro h rcases h with ⟨hedge, hfix, hobs, hunobs⟩ refine intro ?hedge' ?rest · intro u v have := hedge u v exact this.symm · refine intro ?hfix' ?hobs_unobs' · simp [hfix] · refine intro ?hobs' ?hunobs' · simp [hobs] · simp [hunobs]
    Causalean.SWIGGraph.Equivalent.symm · Causalean/Graph/SWIG.lean:488
  • trans theorem — SWIG graph equivalence is transitive.
    N :
    Type u_2
    shared
    Proof (Lean source)
    @[trans] theorem Equivalent.trans {G H K : SWIGGraph N} : Equivalent G H → Equivalent H K → Equivalent G K := by intro hGH hHK rcases hGH with ⟨hedgeGH, hfixGH, hobsGH, hunobsGH⟩ rcases hHK with ⟨hedgeHK, hfixHK, hobsHK, hunobsHK⟩ refine intro ?hedge ?rest · intro u v exact Iff.trans (hedgeGH u v) (hedgeHK u v) · refine intro ?hfix ?hobs_unobs · -- fixed sets simp [hfixGH, hfixHK] · refine intro ?hobs ?hunobs · -- observed sets simp [hobsGH, hobsHK] · -- unobserved sets simp [hunobsGH, hunobsHK]
    Causalean.SWIGGraph.Equivalent.trans · Causalean/Graph/SWIG.lean:503
  • parents_eq theorem — Equivalent SWIGGraphs have the same parents Finset at every node.
    N :
    Type u_2
    shared
    hEdge :
    ∀ u v, G.dag.edge u v ↔ H.dag.edge u v
    v :
    G.dag.parents v = H.dag.parents v
    Proof (Lean source)
    theorem Equivalent.parents_eq {G H : SWIGGraph N} (hEdge : ∀ u v, G.dag.edge u v ↔ H.dag.edge u v) (v : SWIGNode N) : G.dag.parents v = H.dag.parents v := by ext u rw [G.dag.mem_parents, H.dag.mem_parents] exact hEdge u v
    Causalean.SWIGGraph.Equivalent.parents_eq · Causalean/Graph/SWIG.lean:521
  • parent_classified theorem — If u is a parent of v in G, then u is classified (fixed, observed, or unobserved).
    N :
    Type u_2
    shared
    G :
    u v :
    h :
    u ∈ G.dag.parents v
    u ∈ G.fixed ∪ G.observed ∪ G.unobserved
    Proof (Lean source)
    theorem parent_classified (G : SWIGGraph N) {u v : SWIGNode N} (h : u ∈ G.dag.parents v) : u ∈ G.fixed ∪ G.observed ∪ G.unobserved := (G.dag_edges_classified u v (G.dag.mem_parents.mp h)).1
    Causalean.SWIGGraph.parent_classified · Causalean/Graph/SWIG.lean:532
  • child_classified theorem — If w is a child of u in G, then w is classified (fixed, observed, or unobserved).
    N :
    Type u_2
    shared
    G :
    u w :
    h :
    w ∈ G.dag.children u
    w ∈ G.fixed ∪ G.observed ∪ G.unobserved
    Proof (Lean source)
    theorem child_classified (G : SWIGGraph N) {u w : SWIGNode N} (h : w ∈ G.dag.children u) : w ∈ G.fixed ∪ G.observed ∪ G.unobserved := (G.dag_edges_classified u w (G.dag.mem_children.mp h)).2
    Causalean.SWIGGraph.child_classified · Causalean/Graph/SWIG.lean:539
Induce 4 core · 5 supporting This file defines graph-level restriction of a Single World Intervention Graph to an observed subset. ★ inducedDag_edge_iff★ induce

Induced SWIG Subgraphs

This file defines graph-level restriction of a Single World Intervention Graph to an observed subset. The construction keeps the relevant observed nodes, retains only fixed nodes whose random counterparts remain observed, retains only latent roots that feed those observed nodes, and filters edges to the resulting active vertex set.

The auxiliary inducedEdge and inducedDag restrict the ambient DAG while preserving its topological order. The main constructor SWIGGraph.induce builds the restricted SWIG and proves all structural invariants; inducedDag_edge_iff and the parent/child subset lemmas expose the relationship with the ambient graph. The theorem induce_isAncestor_mem_R shows that every nontrivial descendant in an induced subgraph lies in the retained observed part of R.

def inducedEdge reviewed
Causalean.SWIGGraph

For a single-world intervention graph, a set of active split nodes, and two split nodes, the induced edge relation holds exactly when the original graph has the directed edge, the first endpoint belongs to the active set, and the second endpoint belongs to the active set.

Definition (Lean source)
N :
Type u_1
shared
G :
shared
active :
u v :
inducedEdge G active u v :
Prop
clause 1
G.dag.edge u v
clause 2
u ∈ active
clause 3
v ∈ active
Causalean.SWIGGraph.inducedEdge · Causalean/Graph/Induce.lean:54 · uses SWIGGraph , SWIGNode
def inducedDag reviewed
Causalean.SWIGGraph

For a single-world intervention graph and a set of active split nodes, the induced directed acyclic graph retains exactly the original directed edges whose two endpoints are active.

Definition (Lean source)
N :
Type u_1
shared
G :
shared
active :
inducedDag G active :
clause 1
edge := G.inducedEdge active
clause 2
decEdge := G.inducedEdge_decidable active
clause 3
acyclic := DAG.acyclic_of_topoOrder (τ := G.dag.topoOrder) (fun u v h => G.dag.topoOrder_lt u v h.1)
lemma inducedDag_edge_iff reviewed
Causalean.SWIGGraph

For a set of active nodes and vertices u, v, u and v are joined by an edge of the DAG restricted to the active nodes exactly when they are joined by an edge of the original DAG and both are active.

Formal statement
N :
Type u_1
shared
G :
shared
active :
u v :
(G.inducedDag active).edge u v ↔ G.dag.edge u v ∧ u ∈ active ∧ v ∈ active
Proof (Lean source)
lemma inducedDag_edge_iff (active : Finset (SWIGNode N)) (u v : SWIGNode N) : (G.inducedDag active).edge u v ↔ G.dag.edge u v ∧ u ∈ active ∧ v ∈ active := Iff.rfl
def induce reviewed
Causalean.SWIGGraph

For a single-world intervention graph and a retained set of split nodes, the induced single-world intervention graph has as its observed nodes the retained observed nodes, fixed nodes precisely those original fixed nodes whose random counterparts remain observed, unobserved nodes precisely those original unobserved nodes with an edge into the retained observed nodes, and directed edges precisely the original edges with both endpoints among these retained nodes.

Definition (Lean source)
N :
Type u_1
shared
G :
shared
R :
induce G R :
newObserved :
R ∩ G.observed (: Finset (SWIGNode N))
newFixed :
G.fixed.filter (fun s => iotaMap s ∈ newObserved) (: Finset (SWIGNode N))
newUnobserved :
G.unobserved.filter (fun u => ∃ v ∈ newObserved, G.dag.edge u v) (: Finset (SWIGNode N))
newActive :
newFixed ∪ newObserved ∪ newUnobserved (: Finset (SWIGNode N))
{ dag := G.inducedDag newActive fixed := newFixed observed := newObserved unobserved := newUnobserved fixed_is_fixed := by intro s hs exact G.fixed_is_fixed s ((Finset.mem_filter.mp hs).1) observed_is_random := by intro v hv exact G.observed_is_random v (Finset.mem_inter.mp hv).2 unobserved_is_random := by intro u hu exact G.unobserved_is_random u (Finset.mem_filter.mp hu).1 obs_unobs_disjoint := by rw [Finset.disjoint_left] intro u huObs huUnobs exact (Finset.disjoint_left.mp G.obs_unobs_disjoint (inter_subset_right huObs) (Finset.mem_filter.mp huUnobs).1).elim dag_edges_classified := by intro u v huv have hu : u ∈ newActive := huv.2.1 have hv : v ∈ newActive := huv.2.2 refine ⟨?_, ?_⟩ · simpa [newActive] using hu · simpa [newActive] using hv fixed_image_in_observed := by intro s hs exact (Finset.mem_filter.mp hs).2 fixed_are_roots := by intro s hs have hsFixed : s ∈ G.fixed := (Finset.mem_filter.mp hs).1 have hGroot : G.dag.parents s = ∅ := G.fixed_are_roots s hsFixed have hsub := G.inducedDag_parents_subset newActive s rw [hGroot] at hsub exact Finset.subset_empty.mp hsub unobs_are_roots := by intro u hu have hGroot : G.dag.parents u = ∅ := G.unobs_are_roots u (Finset.mem_filter.mp hu).1 have hsub := G.inducedDag_parents_subset newActive u rw [hGroot] at hsub exact Finset.subset_empty.mp hsub fixed_outside_fixed_isolated := by intro n hnNotFixed by_cases horig : SWIGNode.fixed n ∈ G.fixed · have hnotActive : SWIGNode.fixed n ∉ newActive := by intro hin rcases Finset.mem_union.mp hin with hin | hin · rcases Finset.mem_union.mp hin with hin | hin · exact hnNotFixed hin · have hinObs : SWIGNode.fixed n ∈ G.observed := (Finset.mem_inter.mp hin).2 obtain ⟨_, hm⟩ := G.observed_is_random _ hinObs cases hm · obtain ⟨_, hm⟩ := G.unobserved_is_random _ (Finset.mem_filter.mp hin).1 cases hm refine ⟨?_, ?_⟩ · rw [Finset.eq_empty_iff_forall_notMem] intro w hw have hedge := ((G.inducedDag newActive).mem_parents.mp hw) exact hnotActive hedge.2.2 · rw [Finset.eq_empty_iff_forall_notMem] intro w hw have hedge := ((G.inducedDag newActive).mem_children.mp hw) exact hnotActive hedge.2.1 · have hGiso := G.fixed_outside_fixed_isolated n horig refine ⟨?_, ?_⟩ · have hsub := G.inducedDag_parents_subset newActive (SWIGNode.fixed n) rw [hGiso.1] at hsub exact Finset.subset_empty.mp hsub · have hsub := G.inducedDag_children_subset newActive (SWIGNode.fixed n) rw [hGiso.2] at hsub exact Finset.subset_empty.mp hsub all_children_in_observed := by intro u hu v hv have hedge := (G.inducedDag newActive).mem_children.mp hv have hvActive : v ∈ newActive := hedge.2.2 have hvChildOrig : v ∈ G.dag.children u := G.dag.mem_children.mpr hedge.1 have huOld : u ∈ G.unobserved ∪ G.fixed ∪ G.observed := by rcases Finset.mem_union.mp hu with hu' | hu' · rcases Finset.mem_union.mp hu' with hu' | hu' · exact mem_union_left _ (mem_union_left _ (Finset.mem_filter.mp hu').1) · have : u ∈ G.fixed := (Finset.mem_filter.mp hu').1 exact mem_union_left _ (Finset.mem_union_right _ this) · have : u ∈ G.observed := (Finset.mem_inter.mp hu').2 exact Finset.mem_union_right _ this have hvOldObs : v ∈ G.observed := G.all_children_in_observed u huOld hvChildOrig rcases Finset.mem_union.mp hvActive with hv' | hv' · rcases Finset.mem_union.mp hv' with hv' | hv' · have hvFixed : v ∈ G.fixed := (Finset.mem_filter.mp hv').1 obtain ⟨m, hm⟩ := G.fixed_is_fixed _ hvFixed obtain ⟨k, hk⟩ := G.observed_is_random _ hvOldObs rw [hm] at hk cases hk · exact hv' · exact (Finset.disjoint_left.mp G.obs_unobs_disjoint hvOldObs (Finset.mem_filter.mp hv').1).elim }
Causalean.SWIGGraph.induce · Causalean/Graph/Induce.lean:109 · uses SWIGGraph , SWIGNode
5 supporting declarations (lemmas, instances)
  • inducedEdge_decidable instance — For a finite collection of base variables with decidable equality, a single-world intervention graph, and a set of active split nodes, the decision procedure for the induced edge relation determines, for every ordered pair of split nodes, whether the original graph joins them by an edge and both endpoints are active.
    N :
    Type u_1
    shared
    G :
    shared
    active :
    inducedEdge_decidable G active :
    DecidableRel (G.inducedEdge active)
    by intro u v unfold inducedEdge infer_instance
    Causalean.SWIGGraph.inducedEdge_decidable · Causalean/Graph/Induce.lean:58
  • inducedDag_parents_subset lemma — Every parent in the restricted DAG is also a parent in the original graph.
    N :
    Type u_1
    shared
    G :
    shared
    active :
    v :
    (G.inducedDag active).parents v ⊆ G.dag.parents v
    Proof (Lean source)
    lemma inducedDag_parents_subset (active : Finset (SWIGNode N)) (v : SWIGNode N) : (G.inducedDag active).parents v ⊆ G.dag.parents v := by intro u hu have h := (G.inducedDag active).mem_parents.mp hu exact G.dag.mem_parents.mpr h.1
    Causalean.SWIGGraph.inducedDag_parents_subset · Causalean/Graph/Induce.lean:81
  • inducedDag_children_subset lemma — Every child in the restricted DAG is also a child in the original graph.
    N :
    Type u_1
    shared
    G :
    shared
    active :
    u :
    (G.inducedDag active).children u ⊆ G.dag.children u
    Proof (Lean source)
    lemma inducedDag_children_subset (active : Finset (SWIGNode N)) (u : SWIGNode N) : (G.inducedDag active).children u ⊆ G.dag.children u := by intro v hv have h := (G.inducedDag active).mem_children.mp hv exact G.dag.mem_children.mpr h.1
    Causalean.SWIGGraph.inducedDag_children_subset · Causalean/Graph/Induce.lean:88
  • inducedDag_isAncestor_mem_active lemma — If (G.inducedDag active).isAncestor u v, then both endpoints belong to active.
    N :
    Type u_1
    shared
    G :
    shared
    active :
    u v :
    h :
    (G.inducedDag active).isAncestor u v
    conclusion 1
    u ∈ active
    conclusion 2
    v ∈ active
    Proof (Lean source)
    lemma inducedDag_isAncestor_mem_active (active : Finset (SWIGNode N)) {u v : SWIGNode N} (h : (G.inducedDag active).isAncestor u v) : u ∈ active ∧ v ∈ active := by induction h with | edge he => exact ⟨((G.inducedDag_edge_iff active _ _).mp he).2.1, ((G.inducedDag_edge_iff active _ _).mp he).2.2⟩ | trans _ he ih => exact ⟨ih.1, ((G.inducedDag_edge_iff active _ _).mp he).2.2⟩
    Causalean.SWIGGraph.inducedDag_isAncestor_mem_active · Causalean/Graph/Induce.lean:95
  • induce_isAncestor_mem_R lemma — In the induced subgraph, every vertex with a proper ancestor lies in the retained observed support.
    N :
    Type u_1
    shared
    G :
    shared
    R :
    u v :
    h :
    (G.induce R).dag.isAncestor u v
    v ∈ R ∩ G.observed
    Proof (Lean source)
    lemma induce_isAncestor_mem_R (R : Finset (SWIGNode N)) {u v : SWIGNode N} (h : (G.induce R).dag.isAncestor u v) : v ∈ R ∩ G.observed := by -- v has at least one parent in the induced DAG have hpar : (G.induce R).dag.parents v ≠ ∅ := (G.induce R).dag.isAncestor_has_parent h -- v is in the active set of the induced DAG have hactive : v ∈ (G.fixed.filter (fun s => iotaMap s ∈ R ∩ G.observed)) ∪ (R ∩ G.observed) ∪ (G.unobserved.filter (fun u => ∃ w ∈ R ∩ G.observed, G.dag.edge u w)) := (G.inducedDag_isAncestor_mem_active _ h).2 -- v is not fixed (fixed nodes are roots, but v has a parent) have hnotFixed : v ∉ G.fixed.filter (fun s => iotaMap s ∈ R ∩ G.observed) := by intro hv have := (G.induce R).fixed_are_roots v hv exact hpar this -- v is not unobserved (unobserved nodes are roots, but v has a parent) have hnotUnobs : v ∉ G.unobserved.filter (fun u => ∃ w ∈ R ∩ G.observed, G.dag.edge u w) := by intro hv have := (G.induce R).unobs_are_roots v hv exact hpar this -- So v ∈ R ∩ G.observed. rcases Finset.mem_union.mp hactive with hv | hv · rcases Finset.mem_union.mp hv with hv | hv · exact absurd hv hnotFixed · exact hv · exact absurd hv hnotUnobs
    Causalean.SWIGGraph.induce_isAncestor_mem_R · Causalean/Graph/Induce.lean:249
Acyclic­Construct 2 core · 0 supporting Since DAG stores acyclicity directly (acyclic : ∀ v, ¬ Relation.TransGen edge v v), building one only requires exhibiting the edge relation, its decidability, and a proof that it has no directed cycle. ★ ofAcyclic★ ofAcyclic_edge

Constructing a DAG from a raw acyclic edge relation

Since DAG stores acyclicity directly (acyclic : ∀ v, ¬ Relation.TransGen edge v v), building one only requires exhibiting the edge relation, its decidability, and a proof that it has no directed cycle. This file provides:

* DAG.ofAcyclic e hac — from an edge relation e whose transitive closure is irreflexive (hac). Materialises the graph directly. (Used e.g. for the Verma–Pearl covered-edge reversal, where acyclicity of the modified relation is known before any topological numbering.)

For constructions that already carry a topological numbering, build the DAG structure directly and discharge its acyclic field with DAG.acyclic_of_topoOrder (in Causalean.Graph.DAG), which keeps the edge relation definitionally transparent.

def ofAcyclic reviewed
Causalean.DAG

Given an edge relation on a finite vertex set for which no vertex can return to itself by a nonempty directed path, the directed acyclic graph constructed from that relation has precisely that edge relation.

Definition (Lean source)
V :
Type u_1
shared
e :
V → V → Prop
hac :
∀ v, ¬ TransGen e v v
ofAcyclic e hac :
DAG V
clause 1
edge := e
clause 2
decEdge := Classical.decRel e
clause 3
acyclic := hac
Causalean.DAG.ofAcyclic · Causalean/Graph/AcyclicConstruct.lean:32 · uses DAG
theorem ofAcyclic_edge reviewed
Causalean.DAG

The directed acyclic graph ofAcyclic e hac, built from an edge relation e together with a proof that e has no directed cycle, has exactly e as its edge relation.

Formal statement
V :
Type u_1
shared
e :
V → V → Prop
hac :
∀ v, ¬ TransGen e v v
(ofAcyclic e hac).edge = e
Proof (Lean source)
@[simp] theorem ofAcyclic_edge (e : V → V → Prop) (hac : ∀ v, ¬ TransGen e v v) : (ofAcyclic e hac).edge = e := rfl
Causalean.DAG.ofAcyclic_edge · Causalean/Graph/AcyclicConstruct.lean:41 · uses ofAcyclic
CComponents 11 core · 22 supporting This file defines c-components for a Single World Intervention Graph. ★ cComponentSet★ mem_bidirectedBFS_iff_reachable★ cComponentSet_biUnion★ mem_cComponentOf_iff_reachable★ cComponentSet_pairwise_disjoint

C-Components

This file defines c-components for a Single World Intervention Graph. Two observed variables are directly confounded when they share an unobserved parent, and c-components are the connected components generated by that bidirected confounding relation.

The executable side is bidirectedBFS, cComponentOf, cComponents, and the canonical order-independent cComponentSet. The main correctness theorem mem_bidirectedBFS_iff_reachable identifies the BFS output with bidirectedReachable; the partition lemmas cComponentSet_biUnion and cComponentSet_pairwise_disjoint show that cComponentSet covers exactly the observed variables with disjoint components. The boundary lemmas rule out shared latent parents across distinct c-components and provide the induced-graph bridge induce_cComponentOf_eq_of_shared_unobserved_parent.

def directlyConfounded reviewed
Causalean.SWIGGraph

For a single-world intervention graph and two split nodes, the direct-confounding relation holds exactly when the nodes are distinct and some unobserved node has a directed edge to each of them.

Definition (Lean source)
N :
Type u_1
shared
G :
shared
v₁ v₂ :
directlyConfounded G v₁ v₂ :
Prop
clause 1
v₁ ≠ v₂
clause 2
∃ u ∈ G.unobserved,
G.dag.edge u v₁
G.dag.edge u v₂
Causalean.SWIGGraph.directlyConfounded · Causalean/Graph/CComponents.lean:62 · uses SWIGGraph , SWIGNode
def bidirectedNeighbors reviewed
Causalean.SWIGGraph

For a single-world intervention graph and a split node, the bidirected-neighbor set consists exactly of the graph's observed nodes that are directly confounded with that node.

Definition (Lean source)
N :
Type u_1
shared
G :
shared
v :
bidirectedNeighbors G v :
G.observed.filter (fun w => G.directlyConfounded v w)
Causalean.SWIGGraph.bidirectedNeighbors · Causalean/Graph/CComponents.lean:78 · uses SWIGGraph , SWIGNode
inductive bidirectedReachable reviewed
Causalean.SWIGGraph

For a finite collection of base variables with decidable equality and a single-world intervention graph, the bidirected-reachability relation on split nodes is generated by relating each observed split node to itself and by extending any such relation across a direct-confounding link from its current endpoint. Two observed nodes are therefore related exactly through a finite chain of direct-confounding links.

Definition (Lean source)
N :
Type u_1
shared
G :
shared
bidirectedReachable G :
SWIGNode N → SWIGNode N → Prop
refl :
{v : SWIGNode N} (hv : v ∈ G.observed) : bidirectedReachable v v
step :
{u v w : SWIGNode N} : bidirectedReachable u v → G.directlyConfounded v w → bidirectedReachable u w
Causalean.SWIGGraph.bidirectedReachable · Causalean/Graph/CComponents.lean:82 · uses SWIGGraph , SWIGNode
def bidirectedBFS reviewed
Causalean.SWIGGraph

For a single-world intervention graph and a starting split node, the breadth-first bidirected-reachability set is the set reached by repeatedly adding observed nodes directly confounded with the current frontier, starting from the given node when it is observed and otherwise returning the empty set.

Definition (Lean source)
N :
Type u_1
shared
G :
shared
start :
bidirectedBFS G start :
rec :
match fuel with | 0
=> visited | fuel
+ 1
=> let newNeighbors := frontier.biUnion (G.bidirectedNeighbors) \ visited if newNeighbors = ∅ then visited else go newNeighbors (visited ∪ newNeighbors) fuel (: go (frontier visited : Finset (SWIGNode N)) (fuel : ℕ) : Finset (SWIGNode N))
if start ∈ G.observed then go {start} {start} (card (SWIGNode N)) else ∅
Causalean.SWIGGraph.bidirectedBFS · Causalean/Graph/CComponents.lean:92 · uses SWIGGraph , SWIGNode
def cComponentOf reviewed
Causalean.SWIGGraph

For a single-world intervention graph and a split node, the c-component associated with that node is its breadth-first bidirected-reachability set.

Definition (Lean source)
N :
Type u_1
shared
G :
shared
v :
cComponentOf G v :
G.bidirectedBFS v
Causalean.SWIGGraph.cComponentOf · Causalean/Graph/CComponents.lean:112 · uses SWIGGraph , SWIGNode
def cComponents reviewed
Causalean.SWIGGraph

For a single-world intervention graph, the array of c-components is formed by scanning its observed nodes and appending a node's c-component exactly when that node is not already contained in a previously appended component.

Definition (Lean source)
N :
Type u_1
shared
G :
shared
cComponents G :
G.observed.val.toList.foldl (fun acc v => if acc.any (fun comp => v ∈ comp) then acc else acc.push (G.cComponentOf v) ) #[]
Causalean.SWIGGraph.cComponents · Causalean/Graph/CComponents.lean:122 · uses SWIGGraph , SWIGNode
def cComponentSet reviewed
Causalean.SWIGGraph

For a single-world intervention graph, the canonical set of c-components is the set of c-components associated with its observed nodes, with duplicate components identified.

Definition (Lean source)
N :
Type u_1
shared
G :
shared
cComponentSet G :
G.observed.image G.cComponentOf
Causalean.SWIGGraph.cComponentSet · Causalean/Graph/CComponents.lean:134 · uses SWIGGraph , SWIGNode
theorem mem_bidirectedBFS_iff_reachable reviewed
Causalean.SWIGGraph

BFS computes bidirected reachability. Fix a single-world intervention graph G and a node start that is observed in G. Then a node w is found by the breadth-first search from start exactly when w is bidirected-reachable from start — connected to it by a chain of directly-confounded (shared-unobserved-parent) pairs.

Formal statement
N :
Type u_1
shared
G :
shared
start w :
hstart :
start ∈ G.observed
w ∈ G.bidirectedBFS start ↔ G.bidirectedReachable start w
Proof (Lean source)
theorem mem_bidirectedBFS_iff_reachable {start w : SWIGNode N} (hstart : start ∈ G.observed) : w ∈ G.bidirectedBFS start ↔ G.bidirectedReachable start w := by constructor · -- Soundness: fully proved. intro hw rw [bidirectedBFS] at hw simp only [hstart, if_true] at hw refine G.bidirectedBFS_go_reachable _ _ _ ?_ ?_ w hw · intro y hy rw [Finset.mem_singleton.mp hy] exact bidirectedReachable.refl hstart · intro y hy rw [Finset.mem_singleton.mp hy] exact bidirectedReachable.refl hstart · -- Completeness: the BFS result is confounding-closed (saturation), and it -- contains `start`; induct on the reachability derivation. intro hreach rw [bidirectedBFS] simp only [hstart, if_true] -- The initial state satisfies the closure invariant with sufficient fuel. have hclosed := G.bidirectedBFS_go_closed (card (SWIGNode N)) {start} {start} (by simpa using hstart) (by intro a ha haf; exact absurd ha haf) (by have h1 : G.observed.card ≤ card (SWIGNode N) := (Finset.card_le_univ G.observed).trans_eq (Fintype.card_eq.mpr ⟨Equiv.refl _⟩).symm simp only [Finset.card_singleton] omega) have hself : start ∈ bidirectedBFS.go G {start} {start} (card (SWIGNode N)) := G.subset_bidirectedBFS_go _ _ _ (mem_singleton_self start) induction hreach with | refl _ => exact hself | step _ hconf ih => exact hclosed _ ih _ hconf
Causalean.SWIGGraph.mem_bidirectedBFS_iff_reachable · Causalean/Graph/CComponents.lean:409 · uses SWIGGraph , bidirectedBFS , bidirectedReachable , SWIGNode
theorem cComponentSet_biUnion reviewed
Causalean.SWIGGraph

The c-components cover exactly the observed nodes: their union recovers the set of observed nodes exactly.

Formal statement
N :
Type u_1
shared
G :
shared
G.cComponentSet.biUnion id = G.observed
Proof (Lean source)
theorem cComponentSet_biUnion : G.cComponentSet.biUnion id = G.observed := by apply Finset.Subset.antisymm · intro w hw rw [Finset.mem_biUnion] at hw obtain ⟨C, hC, hwC⟩ := hw exact G.cComponentSet_subset_observed C hC (id_eq C ▸ hwC) · intro v hv rw [Finset.mem_biUnion] refine ⟨G.cComponentOf v, ?_, ?_⟩ · rw [cComponentSet, mem_image] exact ⟨v, hv, rfl⟩ · exact id_eq _ ▸ G.mem_cComponentOf_self hv
theorem mem_cComponentOf_iff_reachable reviewed
Causalean.SWIGGraph

Membership in a c-component is exactly bidirected reachability from its seed. For a node v that is observed in G, a node w belongs to the c-component seeded at v exactly when w is bidirected-reachable from v.

Formal statement
N :
Type u_1
shared
G :
shared
v w :
hv :
v ∈ G.observed
w ∈ G.cComponentOf v ↔ G.bidirectedReachable v w
Proof (Lean source)
theorem mem_cComponentOf_iff_reachable {v w : SWIGNode N} (hv : v ∈ G.observed) : w ∈ G.cComponentOf v ↔ G.bidirectedReachable v w := G.mem_bidirectedBFS_iff_reachable hv
Causalean.SWIGGraph.mem_cComponentOf_iff_reachable · Causalean/Graph/CComponents.lean:490 · uses SWIGGraph , bidirectedReachable , cComponentOf , SWIGNode
theorem cComponentSet_pairwise_disjoint reviewed
Causalean.SWIGGraph

Distinct c-components are pairwise disjoint: no observed node belongs to two different c-components.

Formal statement
N :
Type u_1
shared
G :
shared
(G.cComponentSet : Set (Finset (SWIGNode N))).PairwiseDisjoint id
Proof (Lean source)
theorem cComponentSet_pairwise_disjoint : (G.cComponentSet : Set (Finset (SWIGNode N))).PairwiseDisjoint id := by intro C hC D hD hCD rw [Finset.mem_coe, cComponentSet, mem_image] at hC hD obtain ⟨v, hv, rfl⟩ := hC obtain ⟨w, hw, rfl⟩ := hD -- If the components intersect at some `x`, the seeds are mutually reachable, -- hence the components are equal — contradicting `hCD`. rw [onFun, id_eq, id_eq, Finset.disjoint_left] intro x hxv hxw apply hCD rw [G.mem_cComponentOf_iff_reachable hv] at hxv rw [G.mem_cComponentOf_iff_reachable hw] at hxw -- `v` reaches `x` and `w` reaches `x`, so `v` reaches `w`. exact G.cComponentOf_eq_of_reachable (G.bidirectedReachable_trans hxv (G.bidirectedReachable_symm hxw))
Causalean.SWIGGraph.cComponentSet_pairwise_disjoint · Causalean/Graph/CComponents.lean:601 · uses SWIGGraph , cComponentSet , SWIGNode
22 supporting declarations (lemmas, instances)
  • decDirectlyConfounded instance — For a finite collection of base variables with decidable equality, a single-world intervention graph, and two split nodes, the decision procedure for direct confounding determines whether the nodes are distinct and have a common unobserved parent in the graph.
    N :
    Type u_1
    shared
    G :
    shared
    v₁ v₂ :
    decDirectlyConfounded G v₁ v₂ :
    Decidable (G.directlyConfounded v₁ v₂)
    inferInstanceAs (Decidable (_ ∧ ∃ _, _))
    Causalean.SWIGGraph.decDirectlyConfounded · Causalean/Graph/CComponents.lean:73
  • directlyConfounded_symm theorem — The directly-confounded relation is symmetric: if v₁ and v₂ share an unobserved parent, then so do v₂ and v₁ (the shared parent and the observed-ness conditions are symmetric in the two arguments).
    N :
    Type u_1
    shared
    G :
    shared
    v₁ v₂ :
    h :
    G.directlyConfounded v₁ v₂
    G.directlyConfounded v₂ v₁
    Proof (Lean source)
    theorem directlyConfounded_symm {v₁ v₂ : SWIGNode N} (h : G.directlyConfounded v₁ v₂) : G.directlyConfounded v₂ v₁ := by obtain ⟨hne, u, hu, e1, e2⟩ := h exact ⟨hne.symm, u, hu, e2, e1⟩
    Causalean.SWIGGraph.directlyConfounded_symm · Causalean/Graph/CComponents.lean:146
  • bidirectedReachable_observed_left theorem — Both endpoints of a bidirected-reachability derivation are observed (left endpoint).
    N :
    Type u_1
    shared
    G :
    shared
    u v :
    h :
    G.bidirectedReachable u v
    u ∈ G.observed
    Proof (Lean source)
    theorem bidirectedReachable_observed_left {u v : SWIGNode N} (h : G.bidirectedReachable u v) : u ∈ G.observed := by induction h with | refl hv => exact hv | step _ _ ih => exact ih
    Causalean.SWIGGraph.bidirectedReachable_observed_left · Causalean/Graph/CComponents.lean:154
  • bidirectedReachable_observed_right theorem — Both endpoints of a bidirected-reachability derivation are observed (right endpoint).
    N :
    Type u_1
    shared
    G :
    shared
    u v :
    h :
    G.bidirectedReachable u v
    v ∈ G.observed
    Proof (Lean source)
    theorem bidirectedReachable_observed_right {u v : SWIGNode N} (h : G.bidirectedReachable u v) : v ∈ G.observed := by induction h with | refl hv => exact hv | step _ hconf _ => obtain ⟨_, u, hu, _, huw⟩ := hconf exact G.all_children_in_observed u (mem_union_left _ (mem_union_left _ hu)) (G.dag.mem_children.mpr huw)
    Causalean.SWIGGraph.bidirectedReachable_observed_right · Causalean/Graph/CComponents.lean:162
  • bidirectedReachable_head theorem — Prepend a directly-confounded step at the head of a reachability chain: if u and v are directly confounded and v reaches w, then u reaches w. Proved by induction on the v-to-w derivation.
    N :
    Type u_1
    shared
    G :
    shared
    u v w :
    huv :
    G.directlyConfounded u v
    hvw :
    G.bidirectedReachable v w
    G.bidirectedReachable u w
    Proof (Lean source)
    theorem bidirectedReachable_head {u v w : SWIGNode N} (huv : G.directlyConfounded u v) (hvw : G.bidirectedReachable v w) : G.bidirectedReachable u w := by induction hvw with | refl hv => obtain ⟨hne, x, hx, hux, hxv⟩ := huv have huObs : u ∈ G.observed := G.all_children_in_observed x (mem_union_left _ (mem_union_left _ hx)) (G.dag.mem_children.mpr hux) exact bidirectedReachable.step (bidirectedReachable.refl huObs) ⟨hne, x, hx, hux, hxv⟩ | step _ hconf ih => exact bidirectedReachable.step ih hconf
    Causalean.SWIGGraph.bidirectedReachable_head · Causalean/Graph/CComponents.lean:174
  • bidirectedReachable_symm theorem — Bidirected reachability is symmetric.
    N :
    Type u_1
    shared
    G :
    shared
    u v :
    h :
    G.bidirectedReachable u v
    G.bidirectedReachable v u
    Proof (Lean source)
    theorem bidirectedReachable_symm {u v : SWIGNode N} (h : G.bidirectedReachable u v) : G.bidirectedReachable v u := by induction h with | refl hv => exact bidirectedReachable.refl hv | step _ hconf ih => exact bidirectedReachable_head G (G.directlyConfounded_symm hconf) ih
    Causalean.SWIGGraph.bidirectedReachable_symm · Causalean/Graph/CComponents.lean:192
  • bidirectedReachable_trans theorem — Bidirected reachability is transitive.
    N :
    Type u_1
    shared
    G :
    shared
    u v w :
    huv :
    G.bidirectedReachable u v
    hvw :
    G.bidirectedReachable v w
    G.bidirectedReachable u w
    Proof (Lean source)
    theorem bidirectedReachable_trans {u v w : SWIGNode N} (huv : G.bidirectedReachable u v) (hvw : G.bidirectedReachable v w) : G.bidirectedReachable u w := by induction hvw with | refl _ => exact huv | step _ hconf ih => exact bidirectedReachable.step ih hconf
    Causalean.SWIGGraph.bidirectedReachable_trans · Causalean/Graph/CComponents.lean:200
  • bidirectedNeighbors_subset_observed theorem — The bidirected neighbors of a node are observed.
    N :
    Type u_1
    shared
    G :
    shared
    v :
    G.bidirectedNeighbors v ⊆ G.observed
    Proof (Lean source)
    theorem bidirectedNeighbors_subset_observed (v : SWIGNode N) : G.bidirectedNeighbors v ⊆ G.observed := by intro w hw exact (Finset.mem_filter.mp hw).1
    Causalean.SWIGGraph.bidirectedNeighbors_subset_observed · Causalean/Graph/CComponents.lean:210
  • subset_bidirectedBFS_go theorem — The visited set only grows: it is contained in the result of go.
    N :
    Type u_1
    shared
    G :
    shared
    frontier visited :
    fuel :
    visited ⊆ bidirectedBFS.go G frontier visited fuel
    Proof (Lean source)
    theorem subset_bidirectedBFS_go (frontier visited : Finset (SWIGNode N)) (fuel : ℕ) : visited ⊆ bidirectedBFS.go G frontier visited fuel := by induction fuel generalizing frontier visited with | zero => rw [bidirectedBFS.go] | succ n ih => rw [bidirectedBFS.go] by_cases h : frontier.biUnion (G.bidirectedNeighbors) \ visited = ∅ · simp [h] · simp only [h, if_false] exact (subset_union_left).trans (ih _ _)
    Causalean.SWIGGraph.subset_bidirectedBFS_go · Causalean/Graph/CComponents.lean:216
  • bidirectedBFS_go_subset_observed theorem — If the visited set and frontier are within observed, so is the result of go.
    N :
    Type u_1
    shared
    G :
    shared
    frontier visited :
    fuel :
    hvis :
    visited ⊆ G.observed
    bidirectedBFS.go G frontier visited fuel ⊆ G.observed
    Proof (Lean source)
    theorem bidirectedBFS_go_subset_observed (frontier visited : Finset (SWIGNode N)) (fuel : ℕ) (hvis : visited ⊆ G.observed) : bidirectedBFS.go G frontier visited fuel ⊆ G.observed := by induction fuel generalizing frontier visited with | zero => rw [bidirectedBFS.go]; exact hvis | succ n ih => rw [bidirectedBFS.go] by_cases h : frontier.biUnion (G.bidirectedNeighbors) \ visited = ∅ · simpa [h] using hvis · simp only [h, if_false] apply ih apply union_subset hvis intro w hw obtain ⟨hw', _⟩ := Finset.mem_sdiff.mp hw obtain ⟨x, _, hx⟩ := Finset.mem_biUnion.mp hw' exact G.bidirectedNeighbors_subset_observed x hx
    Causalean.SWIGGraph.bidirectedBFS_go_subset_observed · Causalean/Graph/CComponents.lean:228
  • bidirectedBFS_subset_observed theorem — The bidirected BFS from start is contained in observed.
    N :
    Type u_1
    shared
    G :
    shared
    start :
    G.bidirectedBFS start ⊆ G.observed
    Proof (Lean source)
    theorem bidirectedBFS_subset_observed (start : SWIGNode N) : G.bidirectedBFS start ⊆ G.observed := by rw [bidirectedBFS] by_cases h : start ∈ G.observed · simp only [h, if_true] exact G.bidirectedBFS_go_subset_observed _ _ _ (by simpa using h) · simp [h]
    Causalean.SWIGGraph.bidirectedBFS_subset_observed · Causalean/Graph/CComponents.lean:247
  • mem_bidirectedBFS_self theorem — The start node belongs to its own BFS result (when observed).
    N :
    Type u_1
    shared
    G :
    shared
    start :
    h :
    start ∈ G.observed
    start ∈ G.bidirectedBFS start
    Proof (Lean source)
    theorem mem_bidirectedBFS_self {start : SWIGNode N} (h : start ∈ G.observed) : start ∈ G.bidirectedBFS start := by rw [bidirectedBFS] simp only [h, if_true] exact G.subset_bidirectedBFS_go _ _ _ (by simp)
    Causalean.SWIGGraph.mem_bidirectedBFS_self · Causalean/Graph/CComponents.lean:256
  • bidirectedBFS_go_reachable theorem — Soundness of BFS. Every node produced by bidirectedBFS.go from a frontier and visited set all of whose elements are bidirected-reachable from start is itself bidirected-reachable from start. Fully proved by induction on the fuel.
    N :
    Type u_1
    shared
    G :
    shared
    start :
    fuel :
    frontier visited :
    (∀ y ∈ visited, G.bidirectedReachable start y)
    (∀ y ∈ frontier, G.bidirectedReachable start y)
    z ∈ bidirectedBFS.go G frontier visited fuel :
    G.bidirectedReachable start z
    Proof (Lean source)
    theorem bidirectedBFS_go_reachable {start : SWIGNode N} : ∀ (fuel : ℕ) (frontier visited : Finset (SWIGNode N)), (∀ y ∈ visited, G.bidirectedReachable start y) → (∀ y ∈ frontier, G.bidirectedReachable start y) → ∀ z ∈ bidirectedBFS.go G frontier visited fuel, G.bidirectedReachable start z := by intro fuel induction fuel with | zero => intro frontier visited hv _ z hz rw [bidirectedBFS.go] at hz; exact hv z hz | succ n ih => intro frontier visited hv hf z hz rw [bidirectedBFS.go] at hz by_cases h : frontier.biUnion (G.bidirectedNeighbors) \ visited = ∅ · simp only [h, if_true] at hz; exact hv z hz · simp only [h, if_false] at hz have hnew : ∀ y ∈ frontier.biUnion (G.bidirectedNeighbors) \ visited, G.bidirectedReachable start y := by intro y hy obtain ⟨hy', _⟩ := Finset.mem_sdiff.mp hy obtain ⟨q, hq, hqy⟩ := Finset.mem_biUnion.mp hy' exact bidirectedReachable.step (hf q hq) ((Finset.mem_filter.mp hqy).2) apply ih _ _ _ _ z hz · intro y hy rcases Finset.mem_union.mp hy with hy | hy · exact hv y hy · exact hnew y hy · exact hnew
    Causalean.SWIGGraph.bidirectedBFS_go_reachable · Causalean/Graph/CComponents.lean:265
  • bidirectedBFS_go_closed theorem — Closure at saturation. Under the BFS invariants — the visited set is observed, and every *already-expanded* visited node (one outside the frontier) has all its directly-confounded neighbors in visited — and given enough remaining fuel (card observed - card visited ≤ fuel), the result of go is closed under the directly-confounded relation: every neighbor of a node in the result is again in the result.
    N :
    Type u_1
    shared
    G :
    shared
    fuel :
    shared
    frontier :
    shared
    visited :
    shared
    a :
    shared
    b :
    shared
    fuel :
    frontier visited :
    visited ⊆ G.observed
    (∀ a ∈ visited, a ∉ frontier → ∀ b, G.directlyConfounded a b → b ∈ visited)
    (G.observed.card - visited.card ≤ fuel)
    a ∈ bidirectedBFS.go G frontier visited fuel :
    b :
    G.directlyConfounded a b
    b ∈ bidirectedBFS.go G frontier visited fuel
    Proof (Lean source)
    theorem bidirectedBFS_go_closed : ∀ (fuel : ℕ) (frontier visited : Finset (SWIGNode N)), visited ⊆ G.observed → (∀ a ∈ visited, a ∉ frontier → ∀ b, G.directlyConfounded a b → b ∈ visited) → (G.observed.card - visited.card ≤ fuel) → ∀ a ∈ bidirectedBFS.go G frontier visited fuel, ∀ b, G.directlyConfounded a b → b ∈ bidirectedBFS.go G frontier visited fuel := by intro fuel induction fuel with | zero => intro frontier visited hvo hexp hbudget a ha b hab rw [bidirectedBFS.go] at ha ⊢ -- fuel = 0: budget forces visited to already cover observed, so frontier -- nodes' neighbors are visited too. have hcard : G.observed.card ≤ visited.card := by omega have hveq : visited = G.observed := Finset.eq_of_subset_of_card_le hvo hcard by_cases haf : a ∈ frontier · -- a ∈ frontier ⊆ visited = observed; b ∈ observed ⊆ visited. obtain ⟨_, u, hu, _, hub⟩ := hab have hbo : b ∈ G.observed := G.all_children_in_observed u (mem_union_left _ (mem_union_left _ hu)) (G.dag.mem_children.mpr hub) rwa [hveq] · exact hexp a ha haf b hab | succ n ih => intro frontier visited hvo hexp hbudget a ha b hab rw [bidirectedBFS.go] at ha ⊢ set newNeighbors := frontier.biUnion (G.bidirectedNeighbors) \ visited with hnn by_cases h : newNeighbors = ∅ · -- Terminating branch: result = visited, which is confounding-closed. simp only [h, if_true] at ha ⊢ by_cases haf : a ∈ frontier · -- a in frontier: its neighbor b is in biUnion; since newNeighbors = ∅, -- b must already be in visited. have hbn : b ∈ G.bidirectedNeighbors a := by rw [bidirectedNeighbors, mem_filter] obtain ⟨hne, u, hu, hua, hub⟩ := hab have hbo : b ∈ G.observed := G.all_children_in_observed u (mem_union_left _ (mem_union_left _ hu)) (G.dag.mem_children.mpr hub) exact ⟨hbo, ⟨hne, u, hu, hua, hub⟩⟩ have hbU : b ∈ frontier.biUnion (G.bidirectedNeighbors) := Finset.mem_biUnion.mpr ⟨a, haf, hbn⟩ by_contra hbv have : b ∈ newNeighbors := Finset.mem_sdiff.mpr ⟨hbU, hbv⟩ rw [h] at this exact absurd this (by simp) · exact hexp a ha haf b hab · -- Recursing branch: apply IH to the grown visited set. simp only [h, if_false] at ha ⊢ have hUobs : ∀ x ∈ frontier.biUnion (G.bidirectedNeighbors), x ∈ G.observed := by intro x hx obtain ⟨q, _, hq⟩ := Finset.mem_biUnion.mp hx exact G.bidirectedNeighbors_subset_observed q hq -- New invariant pieces. have hfv' : newNeighbors ⊆ visited ∪ newNeighbors := Finset.subset_union_right have hvo' : visited ∪ newNeighbors ⊆ G.observed := by apply union_subset hvo intro x hx exact hUobs x (Finset.mem_sdiff.mp hx).1 have hexp' : ∀ x ∈ visited ∪ newNeighbors, x ∉ newNeighbors → ∀ c, G.directlyConfounded x c → c ∈ visited ∪ newNeighbors := by intro x hx hxnn c hxc have hxv : x ∈ visited := by rcases Finset.mem_union.mp hx with hxv | hxnew · exact hxv · exact absurd hxnew hxnn by_cases hxf : x ∈ frontier · -- x ∈ frontier: c is a neighbor, so c ∈ biUnion, hence c ∈ visited or -- c ∈ newNeighbors. have hcn : c ∈ G.bidirectedNeighbors x := by rw [bidirectedNeighbors, mem_filter] obtain ⟨hne, u, hu, hux, huc⟩ := hxc have hco : c ∈ G.observed := G.all_children_in_observed u (mem_union_left _ (mem_union_left _ hu)) (G.dag.mem_children.mpr huc) exact ⟨hco, ⟨hne, u, hu, hux, huc⟩⟩ have hcU : c ∈ frontier.biUnion (G.bidirectedNeighbors) := Finset.mem_biUnion.mpr ⟨x, hxf, hcn⟩ by_cases hcv : c ∈ visited · exact mem_union_left _ hcv · exact mem_union_right _ (Finset.mem_sdiff.mpr ⟨hcU, hcv⟩) · exact mem_union_left _ (hexp x hxv hxf c hxc) -- Budget decreases by at least one because newNeighbors is nonempty and -- disjoint from visited. have hdisj : Disjoint visited newNeighbors := disjoint_sdiff_self_right have hcardU : (visited ∪ newNeighbors).card = visited.card + newNeighbors.card := Finset.card_union_of_disjoint hdisj have hnnpos : 0 < newNeighbors.card := Finset.card_pos.mpr (Finset.nonempty_of_ne_empty h) have hbudget' : G.observed.card - (visited ∪ newNeighbors).card ≤ n := by rw [hcardU]; omega exact ih newNeighbors (visited ∪ newNeighbors) hvo' hexp' hbudget' a ha b hab
    Causalean.SWIGGraph.bidirectedBFS_go_closed · Causalean/Graph/CComponents.lean:299
  • cComponentOf_subset_observed theorem — The c-component of v is contained in the observed nodes.
    N :
    Type u_1
    shared
    G :
    shared
    v :
    G.cComponentOf v ⊆ G.observed
    Proof (Lean source)
    theorem cComponentOf_subset_observed (v : SWIGNode N) : G.cComponentOf v ⊆ G.observed := G.bidirectedBFS_subset_observed v
    Causalean.SWIGGraph.cComponentOf_subset_observed · Causalean/Graph/CComponents.lean:455
  • mem_cComponentOf_self theorem — An observed node belongs to its own c-component.
    N :
    Type u_1
    shared
    G :
    shared
    v :
    hv :
    v ∈ G.observed
    v ∈ G.cComponentOf v
    Proof (Lean source)
    theorem mem_cComponentOf_self {v : SWIGNode N} (hv : v ∈ G.observed) : v ∈ G.cComponentOf v := G.mem_bidirectedBFS_self hv
    Causalean.SWIGGraph.mem_cComponentOf_self · Causalean/Graph/CComponents.lean:460
  • cComponentSet_subset_observed theorem — Every c-component (in the canonical set) is contained in the observed nodes.
    N :
    Type u_1
    shared
    G :
    shared
    C :
    shared
    C ∈ G.cComponentSet :
    C ⊆ G.observed
    Proof (Lean source)
    theorem cComponentSet_subset_observed : ∀ C ∈ G.cComponentSet, C ⊆ G.observed := by intro C hC rw [cComponentSet, mem_image] at hC obtain ⟨v, _, rfl⟩ := hC exact G.cComponentOf_subset_observed v
    Causalean.SWIGGraph.cComponentSet_subset_observed · Causalean/Graph/CComponents.lean:465
  • cComponentOf_eq_of_reachable theorem — Two c-components seeded by reachable observed nodes are equal.
    N :
    Type u_1
    shared
    G :
    shared
    v w :
    h :
    G.bidirectedReachable v w
    G.cComponentOf v = G.cComponentOf w
    Proof (Lean source)
    theorem cComponentOf_eq_of_reachable {v w : SWIGNode N} (h : G.bidirectedReachable v w) : G.cComponentOf v = G.cComponentOf w := by have hv := G.bidirectedReachable_observed_left h have hw := G.bidirectedReachable_observed_right h apply Finset.Subset.antisymm · intro x hx rw [G.mem_cComponentOf_iff_reachable hw] rw [G.mem_cComponentOf_iff_reachable hv] at hx exact G.bidirectedReachable_trans (G.bidirectedReachable_symm h) hx · intro x hx rw [G.mem_cComponentOf_iff_reachable hv] rw [G.mem_cComponentOf_iff_reachable hw] at hx exact G.bidirectedReachable_trans h hx
    Causalean.SWIGGraph.cComponentOf_eq_of_reachable · Causalean/Graph/CComponents.lean:497
  • cComponentOf_eq_of_mem_cComponentSet theorem — A node in a listed c-component has that component as its computed c-component.
    N :
    Type u_1
    shared
    G :
    shared
    C :
    hC :
    C ∈ G.cComponentSet
    v :
    hvC :
    v ∈ C
    G.cComponentOf v = C
    Proof (Lean source)
    theorem cComponentOf_eq_of_mem_cComponentSet {C : Finset (SWIGNode N)} (hC : C ∈ G.cComponentSet) {v : SWIGNode N} (hvC : v ∈ C) : G.cComponentOf v = C := by classical rw [cComponentSet, mem_image] at hC obtain ⟨s, hsObs, rfl⟩ := hC exact (G.cComponentOf_eq_of_reachable ((G.mem_cComponentOf_iff_reachable hsObs).mp hvC)).symm
    Causalean.SWIGGraph.cComponentOf_eq_of_mem_cComponentSet · Causalean/Graph/CComponents.lean:513
  • not_directlyConfounded_of_mem_cComponentSet_of_not_mem theorem — A node outside a c-component is not directly confounded with a node inside it.
    N :
    Type u_1
    shared
    G :
    shared
    C :
    hC :
    C ∈ G.cComponentSet
    v w :
    hvC :
    v ∈ C
    hwNotC :
    w ∉ C
    ¬ G.directlyConfounded v w
    Proof (Lean source)
    theorem not_directlyConfounded_of_mem_cComponentSet_of_not_mem {C : Finset (SWIGNode N)} (hC : C ∈ G.cComponentSet) {v w : SWIGNode N} (hvC : v ∈ C) (hwNotC : w ∉ C) : ¬ G.directlyConfounded v w := by intro hconf rw [cComponentSet, mem_image] at hC obtain ⟨s, hsObs, rfl⟩ := hC have hsv : G.bidirectedReachable s v := (G.mem_cComponentOf_iff_reachable hsObs).mp hvC have hsw : G.bidirectedReachable s w := bidirectedReachable.step hsv hconf exact hwNotC ((G.mem_cComponentOf_iff_reachable hsObs).mpr hsw)
    Causalean.SWIGGraph.not_directlyConfounded_of_mem_cComponentSet_of_not_mem · Causalean/Graph/CComponents.lean:525
  • no_shared_unobserved_parent_of_mem_cComponentSet_of_not_mem theorem — No latent root can be a shared parent of a c-component node and a node outside that c-component.
    N :
    Type u_1
    shared
    G :
    shared
    C :
    hC :
    C ∈ G.cComponentSet
    v w u :
    hvC :
    v ∈ C
    hwNotC :
    w ∉ C
    hu :
    u ∈ G.unobserved
    huv :
    G.dag.edge u v
    huw :
    G.dag.edge u w
    Proof (Lean source)
    theorem no_shared_unobserved_parent_of_mem_cComponentSet_of_not_mem {C : Finset (SWIGNode N)} (hC : C ∈ G.cComponentSet) {v w u : SWIGNode N} (hvC : v ∈ C) (hwNotC : w ∉ C) (hu : u ∈ G.unobserved) (huv : G.dag.edge u v) (huw : G.dag.edge u w) : False := by have hvw : v ≠ w := by intro h exact hwNotC (h ▸ hvC) exact (G.not_directlyConfounded_of_mem_cComponentSet_of_not_mem hC hvC hwNotC) ⟨hvw, u, hu, huv, huw⟩
    Causalean.SWIGGraph.no_shared_unobserved_parent_of_mem_cComponentSet_of_not_mem · Causalean/Graph/CComponents.lean:544
  • induce_cComponentOf_eq_of_shared_unobserved_parent theorem — If a latent node has edges into two observed nodes retained by an induced graph, those observed nodes seed the same induced c-component.
    N :
    Type u_1
    shared
    G :
    shared
    R :
    u v w :
    hu :
    u ∈ G.unobserved
    hvR :
    v ∈ R
    hwR :
    w ∈ R
    huv :
    G.dag.edge u v
    huw :
    G.dag.edge u w
    (G.induce R).cComponentOf v = (G.induce R).cComponentOf w
    Proof (Lean source)
    theorem induce_cComponentOf_eq_of_shared_unobserved_parent (R : Finset (SWIGNode N)) {u v w : SWIGNode N} (hu : u ∈ G.unobserved) (hvR : v ∈ R) (hwR : w ∈ R) (huv : G.dag.edge u v) (huw : G.dag.edge u w) : (G.induce R).cComponentOf v = (G.induce R).cComponentOf w := by classical have hvObs : v ∈ G.observed := G.all_children_in_observed u (mem_union_left _ (mem_union_left _ hu)) (G.dag.mem_children.mpr huv) have hwObs : w ∈ G.observed := G.all_children_in_observed u (mem_union_left _ (mem_union_left _ hu)) (G.dag.mem_children.mpr huw) by_cases hvw : v = w · subst hvw rfl have hvInd : v ∈ (G.induce R).observed := by simp [SWIGGraph.induce, hvR, hvObs] have hwInd : w ∈ (G.induce R).observed := by simp [SWIGGraph.induce, hwR, hwObs] have huInd : u ∈ (G.induce R).unobserved := by change u ∈ G.unobserved.filter (fun u => ∃ z ∈ R ∩ G.observed, G.dag.edge u z) exact Finset.mem_filter.mpr ⟨hu, v, Finset.mem_inter.mpr ⟨hvR, hvObs⟩, huv⟩ have huvInd : (G.induce R).dag.edge u v := by rw [SWIGGraph.induce] rw [SWIGGraph.inducedDag_edge_iff] exact ⟨huv, mem_union_right _ huInd, by simp [hvR, hvObs]⟩ have huwInd : (G.induce R).dag.edge u w := by rw [SWIGGraph.induce] rw [SWIGGraph.inducedDag_edge_iff] exact ⟨huw, mem_union_right _ huInd, by simp [hwR, hwObs]⟩ have hconf : (G.induce R).directlyConfounded v w := ⟨hvw, u, huInd, huvInd, huwInd⟩ exact (G.induce R).cComponentOf_eq_of_reachable (SWIGGraph.bidirectedReachable.step (SWIGGraph.bidirectedReachable.refl hvInd) hconf)
    Causalean.SWIGGraph.induce_cComponentOf_eq_of_shared_unobserved_parent · Causalean/Graph/CComponents.lean:560
SWIGSplit­Mono 5 core · 6 supporting This file defines the one-shot split of a Single World Intervention Graph at a finite set of intervention targets. ★ splitMono★ splitMono_parents_char

Monolithic Multi-Target SWIG Split

This file defines the one-shot split of a Single World Intervention Graph at a finite set of intervention targets. The construction reroutes every outgoing edge from a targeted random node .random D to its fixed counterpart .fixed D in one graph transformation, while preserving the observed and unobserved node sets and adding the fixed copies of the targets to the fixed set.

The main definitions are:

* splitMonoEdgeRel — the edge relation after rerouting all targeted outgoing edges; * splitMonoTopo and splitMonoDAG — the topological order and DAG proof for the rerouted graph; * SWIGGraph.splitMono — the packaged SWIG graph after the split; * splitMono_parents_char — an exact parent-set characterization; and * splitMono_parents_eq_of_no_fixed_parent — the parent-set coincidence lemma used by the SCM Rule 3 evaluation-map compatibility bridge.

The operation is monolithic rather than an iterated single-target split so that parents at unaffected vertices reduce definitionally in downstream SCM bookkeeping.

def splitMonoEdgeRel reviewed
Causalean.SWIGGraph

For a finite vertex set with decidable equality, a directed-edge relation on its random and fixed copies, and a finite set of vertices selected for splitting, the monolithic split edge relation removes every edge leaving the random copy of a selected vertex and otherwise retains the corresponding original edge, while each fixed copy of a selected vertex inherits the outgoing edges of its random copy and every other fixed copy retains its original outgoing edges.

Definition (Lean source)
N :
Type u_1
shared
dagEdge :
SWIGNode N → SWIGNode N → Prop
X :
splitMonoEdgeRel dagEdge X :
SWIGNode N → SWIGNode N → Prop
clause 1
| .random u, b => if u ∈ X then False else dagEdge (.random u) b
clause 2
| .fixed d, b => if d ∈ X then dagEdge (.random d) b else dagEdge (.fixed d) b
Causalean.SWIGGraph.splitMonoEdgeRel · Causalean/Graph/SWIGSplitMono.lean:75 · uses SWIGNode
def splitMonoTopo reviewed
Causalean.SWIGGraph

For a finite vertex set with decidable equality, a SWIG graph, and a finite set of vertices selected for splitting, the topological-order assignment for the monolithically split graph assigns each random copy twice its original topological rank plus one, and assigns each selected fixed copy twice the original rank of its random copy, while assigning each unselected fixed copy twice its own original rank plus one.

Definition (Lean source)
N :
Type u_1
shared
G :
X :
splitMonoTopo G X :
SWIGNode N → ℕ
clause 1
| .random u => 2 * G.dag.topoOrder (SWIGNode.random u) + 1
clause 2
| .fixed d
=> if d ∈ X then 2 * G.dag.topoOrder (SWIGNode.random d) else 2 * G.dag.topoOrder (SWIGNode.fixed d)
+ 1
Causalean.SWIGGraph.splitMonoTopo · Causalean/Graph/SWIGSplitMono.lean:122 · uses SWIGGraph , SWIGNode
def splitMonoDAG reviewed
Causalean.SWIGGraph

For a finite vertex set with decidable equality, a SWIG graph, and a finite set of vertices selected for splitting, the monolithically split directed acyclic graph is the directed acyclic graph obtained by rerouting, in one operation, every edge from the random copy of a selected vertex to instead leave that vertex's fixed copy.

Definition (Lean source)
N :
Type u_1
shared
G :
X :
splitMonoDAG G X :
clause 1
edge := splitMonoEdgeRel G.dag.edge X
clause 2
decEdge := splitMonoEdgeRel_decidable G.dag.edge X
clause 3
acyclic := DAG.acyclic_of_topoOrder (τ := splitMonoTopo G X) (by intro u v h cases u with | random u => simp only [splitMonoEdgeRel] at h by_cases hu : u ∈ X · simp [hu] at h · have hOld : G.dag.edge (SWIGNode.random u) v := by simpa [hu] using h have hltOld := G.dag.topoOrder_lt _ _ hOld cases v with | random v => exact Nat.add_lt_add_right ((Nat.mul_lt_mul_left (by omega : 0 < 2)).mpr hltOld) 1 | fixed d => exfalso by_cases hd_in_fix : SWIGNode.fixed d ∈ G.fixed · have hroot : G.dag.parents (SWIGNode.fixed d) = ∅ := G.fixed_are_roots _ hd_in_fix have : SWIGNode.random u ∈ G.dag.parents (SWIGNode.fixed d) := G.dag.mem_parents.mpr hOld simp [hroot] at this · have hiso := G.fixed_outside_fixed_isolated d hd_in_fix have : SWIGNode.random u ∈ G.dag.parents (SWIGNode.fixed d) := G.dag.mem_parents.mpr hOld simp [hiso.1] at this | fixed d => simp only [splitMonoEdgeRel] at h by_cases hd : d ∈ X · have hOld : G.dag.edge (SWIGNode.random d) v := by simpa [hd] using h have hltOld := G.dag.topoOrder_lt _ _ hOld cases v with | random v => simp only [splitMonoTopo, if_pos hd] exact Nat.lt_succ_of_le (Nat.mul_le_mul_left 2 (le_of_lt hltOld)) | fixed d' => exfalso by_cases hd'_in_fix : SWIGNode.fixed d' ∈ G.fixed · have hroot : G.dag.parents (SWIGNode.fixed d') = ∅ := G.fixed_are_roots _ hd'_in_fix have : SWIGNode.random d ∈ G.dag.parents (SWIGNode.fixed d') := G.dag.mem_parents.mpr hOld simp [hroot] at this · have hiso := G.fixed_outside_fixed_isolated d' hd'_in_fix have : SWIGNode.random d ∈ G.dag.parents (SWIGNode.fixed d') := G.dag.mem_parents.mpr hOld simp [hiso.1] at this · have hOld : G.dag.edge (SWIGNode.fixed d) v := by simpa [hd] using h have hltOld := G.dag.topoOrder_lt _ _ hOld cases v with | random v => simp only [splitMonoTopo, if_neg hd] exact Nat.add_lt_add_right ((Nat.mul_lt_mul_left (by omega : 0 < 2)).mpr hltOld) 1 | fixed d' => exfalso by_cases hd'_in_fix : SWIGNode.fixed d' ∈ G.fixed · have hroot : G.dag.parents (SWIGNode.fixed d') = ∅ := G.fixed_are_roots _ hd'_in_fix have : SWIGNode.fixed d ∈ G.dag.parents (SWIGNode.fixed d') := G.dag.mem_parents.mpr hOld simp [hroot] at this · have hiso := G.fixed_outside_fixed_isolated d' hd'_in_fix have : SWIGNode.fixed d ∈ G.dag.parents (SWIGNode.fixed d') := G.dag.mem_parents.mpr hOld simp [hiso.1] at this)
def splitMono reviewed
Causalean.SWIGGraph

For a finite vertex set with decidable equality, a SWIG graph, and a finite set of vertices selected for splitting, provided that the random copy of every selected vertex is an observed vertex of the graph and the fixed copy of every selected vertex is not already among the graph's fixed vertices, the monolithic multi-target split SWIG graph is obtained by rerouting every outgoing edge of each selected random copy to leave the corresponding fixed copy, while adding those fixed copies to the fixed vertices and retaining the observed and unobserved vertices.

Definition (Lean source)
N :
Type u_1
shared
G :
X :
hObs :
∀ D ∈ X, SWIGNode.random D ∈ G.observed
hFix :
∀ D ∈ X, SWIGNode.fixed D ∉ G.fixed
splitMono G X hObs hFix :
clause 1
dag := G.splitMonoDAG X
clause 2
fixed := G.fixed ∪ X.image SWIGNode.fixed
clause 3
observed := G.observed
clause 4
unobserved := G.unobserved
clause 5
fixed_is_fixed := by intro s hs rcases Finset.mem_union.mp hs with hs_old | hs_new · exact G.fixed_is_fixed s hs_old · rcases Finset.mem_image.mp hs_new with ⟨d, _, rfl⟩ exact ⟨d, rfl⟩
clause 6
observed_is_random := G.observed_is_random
clause 7
unobserved_is_random := G.unobserved_is_random
clause 8
obs_unobs_disjoint := G.obs_unobs_disjoint
clause 9
dag_edges_classified := by intro u v huv change splitMonoEdgeRel G.dag.edge X u v at huv have hsplit : splitMonoEdgeRel G.dag.edge X u v := huv cases u with | random a
=> by_cases ha : a ∈ X · exfalso simp [splitMonoEdgeRel, ha] at hsplit · have hold : G.dag.edge (SWIGNode.random a) v := by simpa [splitMonoEdgeRel, ha] using hsplit have hcls := G.dag_edges_classified _ _ hold refine ⟨?_, ?_⟩ · rcases Finset.mem_union.mp hcls.1 with h | h · rcases Finset.mem_union.mp h with h | h · exact mem_union_left _ (mem_union_left _ (mem_union_left _ h)) · exact mem_union_left _ (mem_union_right _ h) · exact mem_union_right _ h · rcases Finset.mem_union.mp hcls.2 with h | h · rcases Finset.mem_union.mp h with h | h · exact mem_union_left _ (mem_union_left _ (mem_union_left _ h)) · exact mem_union_left _ (mem_union_right _ h) · exact mem_union_right _ h | fixed d => by_cases hd : d ∈ X · have hold : G.dag.edge (SWIGNode.random d) v := by simpa [splitMonoEdgeRel, hd] using hsplit have hd_obs : SWIGNode.random d ∈ G.observed := hObs d hd have hvObs : v ∈ G.observed := G.all_children_in_observed (SWIGNode.random d) (mem_union_right _ hd_obs) (G.dag.mem_children.mpr hold) refine ⟨?_, ?_⟩ · have : SWIGNode.fixed d ∈ X.image SWIGNode.fixed := Finset.mem_image.mpr ⟨d, hd, rfl⟩ exact mem_union_left _ (mem_union_left _ (mem_union_right _ this)) · exact mem_union_left _ (mem_union_right _ hvObs) · have hold : G.dag.edge (SWIGNode.fixed d) v := by simpa [splitMonoEdgeRel, hd] using hsplit have hcls := G.dag_edges_classified _ _ hold refine ⟨?_, ?_⟩ · rcases Finset.mem_union.mp hcls.1 with h | h · rcases Finset.mem_union.mp h with h | h · exact mem_union_left _ (mem_union_left _ (mem_union_left _ h)) · exact mem_union_left _ (mem_union_right _ h) · exact mem_union_right _ h · rcases Finset.mem_union.mp hcls.2 with h | h · rcases Finset.mem_union.mp h with h | h · exact mem_union_left _ (mem_union_left _ (mem_union_left _ h)) · exact mem_union_left _ (mem_union_right _ h) · exact mem_union_right _ h
clause 10
fixed_image_in_observed := by intro s hs rcases Finset.mem_union.mp hs with hs_old | hs_new · exact G.fixed_image_in_observed s hs_old · rcases Finset.mem_image.mp hs_new with ⟨d, hd, rfl⟩ simpa [iotaMap] using hObs d hd
clause 11
fixed_are_roots := by intro s hs have hNoG : ∀ x : SWIGNode N, ¬ G.dag.edge x s := by intro x hxE rcases Finset.mem_union.mp hs with hs_old | hs_new · have hroot : G.dag.parents s
= ∅ := G.fixed_are_roots s hs_old have : x ∈ G.dag.parents s := G.dag.mem_parents.mpr hxE simp [hroot] at this · rcases Finset.mem_image.mp hs_new with ⟨D, hD, rfl⟩ have hroot : G.dag.parents (SWIGNode.fixed D) = ∅ := (G.fixed_outside_fixed_isolated D (hFix D hD)).1 have : x ∈ G.dag.parents (SWIGNode.fixed D) := G.dag.mem_parents.mpr hxE simp [hroot] at this ext x constructor · intro hxPar have hxEdge := (G.splitMonoDAG X).mem_parents.mp hxPar change splitMonoEdgeRel G.dag.edge X x s at hxEdge exfalso cases x with | random u => by_cases hu : u ∈ X · simp [splitMonoEdgeRel, hu] at hxEdge · have : G.dag.edge (SWIGNode.random u) s := by simpa [splitMonoEdgeRel, hu] using hxEdge exact hNoG _ this | fixed d => by_cases hd : d ∈ X · have : G.dag.edge (SWIGNode.random d) s := by simpa [splitMonoEdgeRel, hd] using hxEdge exact hNoG _ this · have : G.dag.edge (SWIGNode.fixed d) s := by simpa [splitMonoEdgeRel, hd] using hxEdge exact hNoG _ this · intro hxPar simp at hxPar
clause 12
unobs_are_roots := by intro u hu have hrootOld : G.dag.parents u
= ∅ := G.unobs_are_roots u hu ext x constructor · intro hxPar have hxEdge := (G.splitMonoDAG X).mem_parents.mp hxPar change splitMonoEdgeRel G.dag.edge X x u at hxEdge exfalso cases x with | random n => by_cases hn : n ∈ X · simp [splitMonoEdgeRel, hn] at hxEdge · have : G.dag.edge (SWIGNode.random n) u := by simpa [splitMonoEdgeRel, hn] using hxEdge have : SWIGNode.random n ∈ G.dag.parents u := G.dag.mem_parents.mpr this simp [hrootOld] at this | fixed d => by_cases hd : d ∈ X · have : G.dag.edge (SWIGNode.random d) u := by simpa [splitMonoEdgeRel, hd] using hxEdge have : SWIGNode.random d ∈ G.dag.parents u := G.dag.mem_parents.mpr this simp [hrootOld] at this · have : G.dag.edge (SWIGNode.fixed d) u := by simpa [splitMonoEdgeRel, hd] using hxEdge have : SWIGNode.fixed d ∈ G.dag.parents u := G.dag.mem_parents.mpr this simp [hrootOld] at this · intro hxPar simp at hxPar
clause 13
fixed_outside_fixed_isolated := by intro n hn have hn_old : SWIGNode.fixed n ∉ G.fixed := by intro hmem exact hn (mem_union_left _ hmem) have hn_notX : n ∉ X := by intro hmem exact hn (mem_union_right _ (Finset.mem_image.mpr ⟨n, hmem, rfl⟩)) have hIsoOld := G.fixed_outside_fixed_isolated n hn_old refine ⟨?_, ?_⟩ · ext x constructor · intro hxPar have hxEdge := (G.splitMonoDAG X).mem_parents.mp hxPar change splitMonoEdgeRel G.dag.edge X x (SWIGNode.fixed n) at hxEdge exfalso cases x with | random u
=> by_cases hu : u ∈ X · simp [splitMonoEdgeRel, hu] at hxEdge · have : G.dag.edge (SWIGNode.random u) (SWIGNode.fixed n) := by simpa [splitMonoEdgeRel, hu] using hxEdge have : SWIGNode.random u ∈ G.dag.parents (SWIGNode.fixed n) := G.dag.mem_parents.mpr this simp [hIsoOld.1] at this | fixed d => by_cases hd : d ∈ X · have : G.dag.edge (SWIGNode.random d) (SWIGNode.fixed n) := by simpa [splitMonoEdgeRel, hd] using hxEdge have : SWIGNode.random d ∈ G.dag.parents (SWIGNode.fixed n) := G.dag.mem_parents.mpr this simp [hIsoOld.1] at this · have : G.dag.edge (SWIGNode.fixed d) (SWIGNode.fixed n) := by simpa [splitMonoEdgeRel, hd] using hxEdge have : SWIGNode.fixed d ∈ G.dag.parents (SWIGNode.fixed n) := G.dag.mem_parents.mpr this simp [hIsoOld.1] at this · intro hxPar simp at hxPar · ext x constructor · intro hxCh have hxEdge := (G.splitMonoDAG X).mem_children.mp hxCh change splitMonoEdgeRel G.dag.edge X (SWIGNode.fixed n) x at hxEdge have : G.dag.edge (SWIGNode.fixed n) x := by simpa [splitMonoEdgeRel, hn_notX] using hxEdge have : x ∈ G.dag.children (SWIGNode.fixed n) := G.dag.mem_children.mpr this simp [hIsoOld.2] at this · intro hxCh simp at hxCh
clause 14
all_children_in_observed := by intro u hu w hw have hwEdge := (G.splitMonoDAG X).mem_children.mp hw change splitMonoEdgeRel G.dag.edge X u w at hwEdge cases u with | random a
=> by_cases ha : a ∈ X · exfalso simp [splitMonoEdgeRel, ha] at hwEdge · have hold : G.dag.edge (SWIGNode.random a) w := by simpa [splitMonoEdgeRel, ha] using hwEdge have hu_old : SWIGNode.random a ∈ G.unobserved ∪ G.fixed ∪ G.observed := by rcases Finset.mem_union.mp hu with hu' | huObs · rcases Finset.mem_union.mp hu' with huUnobs | huFixNew · exact mem_union_left _ (mem_union_left _ huUnobs) · rcases Finset.mem_union.mp huFixNew with huFix | huImg · exact mem_union_left _ (mem_union_right _ huFix) · exfalso rcases Finset.mem_image.mp huImg with ⟨d, _, hfix_eq⟩ cases hfix_eq · exact mem_union_right _ huObs have := G.all_children_in_observed _ hu_old (G.dag.mem_children.mpr hold) exact this | fixed d => by_cases hd : d ∈ X · have hold : G.dag.edge (SWIGNode.random d) w := by
Causalean.SWIGGraph.splitMono · Causalean/Graph/SWIGSplitMono.lean:231 · uses SWIGGraph , SWIGNode
theorem splitMono_parents_char reviewed
Causalean.SWIGGraph

Characterization of parents in splitMono. Fix a SWIG G and a set X of variables to split, where the random copy of every variable in X is observed in G and the fixed copy of every variable in X is not already among G's fixed nodes. Then, for any node v, a node x is a parent of v in the graph obtained by monolithically splitting X exactly when either x is a parent of v in the original graph and is not the random copy of any variable in X, or x is the fixed copy of some variable D ∈ X whose random copy is a parent of v in the original graph.

Formal statement
N :
Type u_1
shared
G :
X :
hObs :
∀ D ∈ X, SWIGNode.random D ∈ G.observed
hFix :
∀ D ∈ X, SWIGNode.fixed D ∉ G.fixed
v :
x :
x ∈ (G.splitMono X hObs hFix).dag.parents v
↔ (x ∈ G.dag.parents v ∧ ∀ D ∈ X, x ≠ SWIGNode.random D) ∨ (∃ D ∈ X, x = SWIGNode.fixed D ∧ SWIGNode.random D ∈ G.dag.parents v)
Proof (Lean source)
theorem splitMono_parents_char (G : SWIGGraph N) (X : Finset N) (hObs : ∀ D ∈ X, SWIGNode.random D ∈ G.observed) (hFix : ∀ D ∈ X, SWIGNode.fixed D ∉ G.fixed) (v : SWIGNode N) : ∀ x : SWIGNode N, x ∈ (G.splitMono X hObs hFix).dag.parents v ↔ (x ∈ G.dag.parents v ∧ ∀ D ∈ X, x ≠ SWIGNode.random D) ∨ (∃ D ∈ X, x = SWIGNode.fixed D ∧ SWIGNode.random D ∈ G.dag.parents v) := by intro x -- Bridge to edge relation. have hiff : x ∈ (G.splitMono X hObs hFix).dag.parents v ↔ splitMonoEdgeRel G.dag.edge X x v := by change x ∈ (G.splitMonoDAG X).parents v ↔ splitMonoEdgeRel G.dag.edge X x v rw [DAG.mem_parents] rfl rw [hiff] cases x with | random u => simp only [splitMonoEdgeRel] by_cases hu : u ∈ X · constructor · intro h; simp [hu] at h · rintro (⟨hPar, hNoRand⟩ | ⟨D, hD, hEq, _⟩) · exact absurd rfl (hNoRand u hu) · exact absurd hEq (by intro h; cases h) · constructor · intro hEdge have hEdgeG : G.dag.edge (SWIGNode.random u) v := by rw [if_neg hu] at hEdge; exact hEdge refine inl ⟨G.dag.mem_parents.mpr hEdgeG, ?_⟩ intro D hD heq have : u = D := SWIGNode.random.inj heq exact hu (this ▸ hD) · rintro (⟨hPar, _⟩ | ⟨D, _, hEq, _⟩) · rw [if_neg hu]; exact G.dag.mem_parents.mp hPar · exact absurd hEq (by intro h; cases h) | fixed d => simp only [splitMonoEdgeRel] by_cases hd : d ∈ X · constructor · intro hEdge have hEdgeG : G.dag.edge (SWIGNode.random d) v := by rw [if_pos hd] at hEdge; exact hEdge exact inr ⟨d, hd, rfl, G.dag.mem_parents.mpr hEdgeG⟩ · rintro (⟨hPar, _⟩ | ⟨D, hD, hEq, hRD⟩) · exfalso have hfix_notin : SWIGNode.fixed d ∉ G.fixed := hFix d hd have hiso := (G.fixed_outside_fixed_isolated d hfix_notin).2 have hch : v ∈ G.dag.children (SWIGNode.fixed d) := G.dag.mem_children.mpr (G.dag.mem_parents.mp hPar) simp [hiso] at hch · have : d = D := SWIGNode.fixed.inj hEq subst this rw [if_pos hd]; exact G.dag.mem_parents.mp hRD · constructor · intro hEdge have hEdgeG : G.dag.edge (SWIGNode.fixed d) v := by rw [if_neg hd] at hEdge; exact hEdge refine inl ⟨G.dag.mem_parents.mpr hEdgeG, ?_⟩ intro D hD heq exact absurd heq (by intro h; cases h) · rintro (⟨hPar, _⟩ | ⟨D, hD, hEq, _⟩) · rw [if_neg hd]; exact G.dag.mem_parents.mp hPar · have : d = D := SWIGNode.fixed.inj hEq exact absurd (this ▸ hD) hd
6 supporting declarations (lemmas, instances)
  • splitMonoEdgeRel_decidable instance — For a collection of base variables with decidable equality, a directed-edge relation on split nodes for which every proposed edge can be decided, and a finite set of vertices selected for splitting, the decision procedure for the monolithic split edge relation determines, for every ordered pair of split nodes, whether that pair is joined after the split.
    N :
    Type u_1
    shared
    dagEdge :
    SWIGNode N → SWIGNode N → Prop
    DecidableRel dagEdge
    X :
    splitMonoEdgeRel_decidable dagEdge X :
    by intro a b cases a with | random u
    => simp only [splitMonoEdgeRel] by_cases h : u ∈ X · rw [if_pos h] exact instDecidableFalse · rw [if_neg h] infer_instance | fixed d => simp only [splitMonoEdgeRel] by_cases h : d ∈ X · rw [if_pos h] infer_instance · rw [if_neg h] infer_instance
    Causalean.SWIGGraph.splitMonoEdgeRel_decidable · Causalean/Graph/SWIGSplitMono.lean:97
  • splitMono_observed lemma — Monolithic splitting preserves the observed node set.
    N :
    Type u_1
    shared
    G :
    X :
    hObs :
    ∀ D ∈ X, SWIGNode.random D ∈ G.observed
    hFix :
    ∀ D ∈ X, SWIGNode.fixed D ∉ G.fixed
    (G.splitMono X hObs hFix).observed = G.observed
    Proof (Lean source)
    @[simp] lemma splitMono_observed (G : SWIGGraph N) (X : Finset N) (hObs : ∀ D ∈ X, SWIGNode.random D ∈ G.observed) (hFix : ∀ D ∈ X, SWIGNode.fixed D ∉ G.fixed) : (G.splitMono X hObs hFix).observed = G.observed := rfl
    Causalean.SWIGGraph.splitMono_observed · Causalean/Graph/SWIGSplitMono.lean:505
  • splitMono_unobserved lemma — Monolithic splitting preserves the unobserved node set.
    N :
    Type u_1
    shared
    G :
    X :
    hObs :
    ∀ D ∈ X, SWIGNode.random D ∈ G.observed
    hFix :
    ∀ D ∈ X, SWIGNode.fixed D ∉ G.fixed
    (G.splitMono X hObs hFix).unobserved = G.unobserved
    Proof (Lean source)
    @[simp] lemma splitMono_unobserved (G : SWIGGraph N) (X : Finset N) (hObs : ∀ D ∈ X, SWIGNode.random D ∈ G.observed) (hFix : ∀ D ∈ X, SWIGNode.fixed D ∉ G.fixed) : (G.splitMono X hObs hFix).unobserved = G.unobserved := rfl
    Causalean.SWIGGraph.splitMono_unobserved · Causalean/Graph/SWIGSplitMono.lean:511
  • splitMono_fixed lemma — Monolithic splitting adds the fixed copies of the target variables to the fixed node set.
    N :
    Type u_1
    shared
    G :
    X :
    hObs :
    ∀ D ∈ X, SWIGNode.random D ∈ G.observed
    hFix :
    ∀ D ∈ X, SWIGNode.fixed D ∉ G.fixed
    (G.splitMono X hObs hFix).fixed = G.fixed ∪ X.image SWIGNode.fixed
    Proof (Lean source)
    @[simp] lemma splitMono_fixed (G : SWIGGraph N) (X : Finset N) (hObs : ∀ D ∈ X, SWIGNode.random D ∈ G.observed) (hFix : ∀ D ∈ X, SWIGNode.fixed D ∉ G.fixed) : (G.splitMono X hObs hFix).fixed = G.fixed ∪ X.image SWIGNode.fixed := rfl
    Causalean.SWIGGraph.splitMono_fixed · Causalean/Graph/SWIGSplitMono.lean:517
  • splitMono_parents_eq_of_no_fixed_parent theorem — Parent-set coincidence at non-.fixed-targeted vertices.
    N :
    Type u_1
    shared
    G :
    X :
    hObs :
    ∀ D ∈ X, SWIGNode.random D ∈ G.observed
    hFix :
    ∀ D ∈ X, SWIGNode.fixed D ∉ G.fixed
    v :
    hNoFP :
    ∀ D ∈ X, SWIGNode.fixed D ∉ (G.splitMono X hObs hFix).dag.parents v
    (G.splitMono X hObs hFix).dag.parents v = G.dag.parents v
    Proof (Lean source)
    theorem splitMono_parents_eq_of_no_fixed_parent (G : SWIGGraph N) (X : Finset N) (hObs : ∀ D ∈ X, SWIGNode.random D ∈ G.observed) (hFix : ∀ D ∈ X, SWIGNode.fixed D ∉ G.fixed) (v : SWIGNode N) (hNoFP : ∀ D ∈ X, SWIGNode.fixed D ∉ (G.splitMono X hObs hFix).dag.parents v) : (G.splitMono X hObs hFix).dag.parents v = G.dag.parents v := by -- Under hNoFP, no .random D (D ∈ X) can be a parent of v in G.dag either, -- because if it were, splitMono_parents_char would place .fixed D in parents — contradiction. have hNoRD : ∀ D ∈ X, SWIGNode.random D ∉ G.dag.parents v := by intro D hD hRD apply hNoFP D hD exact (splitMono_parents_char G X hObs hFix v (SWIGNode.fixed D)).mpr (inr ⟨D, hD, rfl, hRD⟩) ext x rw [splitMono_parents_char G X hObs hFix v x] constructor · rintro (⟨hP, _⟩ | ⟨D, hD, rfl, hRD⟩) · exact hP · exact absurd hRD (hNoRD D hD) · intro hP left refine ⟨hP, ?_⟩ intro D hD heq subst heq exact hNoRD D hD hP
    Causalean.SWIGGraph.splitMono_parents_eq_of_no_fixed_parent · Causalean/Graph/SWIGSplitMono.lean:601
  • splitMono_congr theorem — Congruence of splitMono under SWIGGraph.Equivalent.
    N :
    Type u_1
    shared
    h :
    Equivalent G₁ G₂
    X :
    hObs₁ :
    ∀ D ∈ X, SWIGNode.random D ∈ G₁.observed
    hFix₁ :
    ∀ D ∈ X, SWIGNode.fixed D ∉ G₁.fixed
    hObs₂ :
    ∀ D ∈ X, SWIGNode.random D ∈ G₂.observed
    hFix₂ :
    ∀ D ∈ X, SWIGNode.fixed D ∉ G₂.fixed
    Equivalent (G₁.splitMono X hObs₁ hFix₁) (G₂.splitMono X hObs₂ hFix₂)
    Proof (Lean source)
    theorem Equivalent.splitMono_congr {G₁ G₂ : SWIGGraph N} (h : Equivalent G₁ G₂) (X : Finset N) (hObs₁ : ∀ D ∈ X, SWIGNode.random D ∈ G₁.observed) (hFix₁ : ∀ D ∈ X, SWIGNode.fixed D ∉ G₁.fixed) (hObs₂ : ∀ D ∈ X, SWIGNode.random D ∈ G₂.observed) (hFix₂ : ∀ D ∈ X, SWIGNode.fixed D ∉ G₂.fixed) : Equivalent (G₁.splitMono X hObs₁ hFix₁) (G₂.splitMono X hObs₂ hFix₂) := by obtain ⟨hEdge, hFix_eq, hObs_eq, hUnobs_eq⟩ := h refine ⟨?_, ?_, ?_, ?_⟩ · -- (1) Edge iff: 4-way case split on (random/fixed) × (∈ X / ∉ X). intro u v cases u with | random u => change (if u ∈ X then False else G₁.dag.edge (.random u) v) ↔ (if u ∈ X then False else G₂.dag.edge (.random u) v) by_cases hu : u ∈ X · simp [hu] · simp [hu] exact hEdge _ _ | fixed d => change (if d ∈ X then G₁.dag.edge (.random d) v else G₁.dag.edge (.fixed d) v) ↔ (if d ∈ X then G₂.dag.edge (.random d) v else G₂.dag.edge (.fixed d) v) by_cases hd : d ∈ X · simp [hd] exact hEdge _ _ · simp [hd] exact hEdge _ _ · -- (2) Fixed sets: G.fixed ∪ X.image .fixed = G₂.fixed ∪ X.image .fixed simp only [splitMono_fixed] rw [hFix_eq] · -- (3) Observed: preserved by rfl. simp only [splitMono_observed] exact hObs_eq · -- (4) Unobserved: preserved by rfl. simp only [splitMono_unobserved] exact hUnobs_eq
    Causalean.SWIGGraph.Equivalent.splitMono_congr · Causalean/Graph/SWIGSplitMono.lean:636