SCM.Model

The structural-causal-model substrate: causal models, edge types, mechanisms, interventions, and evaluation under overrides.

SCM 16 core · 9 supporting This file defines the core measure-theoretic structural causal model object used by the single-world intervention graph framework. ★ observed_parent_index_lt★ trans

Structural causal models

This file defines the core measure-theoretic structural causal model object used by the single-world intervention graph framework. It provides the SCM structure itself, aliases for fixed, observed, latent, and random value assignments, the latent product measure, canonical topological-order indexing of observed nodes, and structural equivalence of models. The underlying value-space bookkeeping (ValuesOn, valuesProjection, and the coordinate-restriction measurability lemmas) lives in Causalean.Mathlib.MeasureTheory.FinsetValues.

An SCM consists of a SWIG graph, value spaces, deterministic measurable structural functions for observed variables, edge labels, and one probability measure for each latent root. Later evaluation and kernel files build the joint and observational laws from these primitive ingredients.

structure SCM reviewed
Causalean

A generalized structural causal model bundles a SWIG graph together with an edge-type assignment recording functional restrictions on each edge, the requirement that each fixed parameter and its corresponding random node share the same value space, a deterministic structural function for each observed node mapping its parents' values to its own, and an independent probability law on each latent root node.

Definition (Lean source)
N :
Ω :
N → Type*
∀ n, MeasurableSpace (Ω n)
extends :
Edge type assignment for the DAG.
edgeTypes :
Fixed parameter and its random counterpart share the same value space: `X_d = X_{ι(d)}` for each `d ∈ S`.
iota_valueSpace :
∀ s : {s // s ∈ fixed}, swigΩ Ω s.val = swigΩ Ω (iotaMap s.val)
Deterministic measurable structural function for each observed node `v ∈ V`: takes parent values (from `S ∪ V ∪ U`) to a value in `Ω v`. In paper notation this is `f_v : ∏_{w ∈ Pa(v)} X_w → X_v`.
structFun :
∀ v : {v // v ∈ observed}
if
(∀ w : {w // w ∈ dag.parents v.val}, swigΩ Ω w.val)
then
swigΩ Ω v.val
Each structural function is measurable.
structFun_measurable :
∀ v : {v // v ∈ observed}, Measurable (structFun v)
Probability measure on each latent root. In paper notation this is `ℙ(L)` for `L ∈ 𝐋`; in the Lean code we keep the `unobserved` set name from `SWIGGraph`.
latentDist :
∀ u : {u // u ∈ unobserved}, Measure (swigΩ Ω u.val)
Each `latentDist u` is a probability measure.
isProbability_latent :
∀ u : {u // u ∈ unobserved}, IsProbabilityMeasure (latentDist u)
def randomVars reviewed
Causalean.SWIGGraph

The random variables of a SWIG graph are its observed and latent nodes.

Definition (Lean source)
def randomVars (G : SWIGGraph N) : Finset (SWIGNode N) := G.observed ∪ G.unobserved
Causalean.SWIGGraph.randomVars · Causalean/SCM/Model/SCM.lean:93 · uses SWIGGraph , SWIGNode
abbrev FixedValues reviewed
Causalean.SCM

Fixed values assign intervention or fixed-node values in a structural causal model.

Definition (Lean source)
abbrev FixedValues (M : SCM N Ω) := ValuesOn M.fixed (swigΩ Ω)
Causalean.SCM.FixedValues · Causalean/SCM/Model/SCM.lean:110 · uses SCM
abbrev ObservedValues reviewed
Causalean.SCM

Observed values assign values to the observed nodes of a structural causal model.

Definition (Lean source)
abbrev ObservedValues (M : SCM N Ω) := ValuesOn M.observed (swigΩ Ω)
Causalean.SCM.ObservedValues · Causalean/SCM/Model/SCM.lean:114 · uses SCM
abbrev LatentValues reviewed
Causalean.SCM

Latent values assign values to the unobserved root nodes of a structural causal model.

Definition (Lean source)
abbrev LatentValues (M : SCM N Ω) := ValuesOn M.unobserved (swigΩ Ω)
Causalean.SCM.LatentValues · Causalean/SCM/Model/SCM.lean:118 · uses SCM
abbrev UnobservedValues reviewed
Causalean.SCM

Unobserved values are the same object as latent values and are kept as a compatibility alias.

Definition (Lean source)
abbrev UnobservedValues (M : SCM N Ω) := LatentValues M
Causalean.SCM.UnobservedValues · Causalean/SCM/Model/SCM.lean:122 · uses SCM
def isStandard reviewed
Causalean.SCM

A standard structural causal model has no fixed intervention variables.

Definition (Lean source)
def isStandard (M : SCM N Ω) : Prop := M.toSWIGGraph.isStandard
Causalean.SCM.isStandard · Causalean/SCM/Model/SCM.lean:128 · uses SCM
def randomVars reviewed
Causalean.SCM

The random variables of a structural causal model are its observed and latent nodes.

Definition (Lean source)
def randomVars (M : SCM N Ω) : Finset (SWIGNode N) := M.toSWIGGraph.randomVars
Causalean.SCM.randomVars · Causalean/SCM/Model/SCM.lean:131 · uses SCM , SWIGNode
abbrev RandomValues reviewed
Causalean.SCM

Random values assign values to every observed or latent node in a structural causal model.

Definition (Lean source)
abbrev RandomValues (M : SCM N Ω) := ValuesOn M.randomVars (swigΩ Ω)
Causalean.SCM.RandomValues · Causalean/SCM/Model/SCM.lean:135 · uses SCM
def latentProduct reviewed
Causalean.SCM

The latent product is the joint distribution over all latent roots formed from their individual laws.

Definition (Lean source)
noncomputable def latentProduct (M : SCM N Ω) : Measure (LatentValues M) := letI := M.isProbability_latent pi (fun u => M.latentDist u)
def topoLinearOrder reviewed
Causalean.SCM

The graph's topological order gives a canonical linear order on SWIG nodes.

Definition (Lean source)
noncomputable def topoLinearOrder (M : SCM N Ω) : LinearOrder (SWIGNode N) := LinearOrder.lift' M.dag.topoOrder M.dag.topoOrder_injective
Causalean.SCM.topoLinearOrder · Causalean/SCM/Model/SCM.lean:190 · uses SCM , SWIGNode
def observedAt reviewed
Causalean.SCM

The observed-node enumeration returns the node at a given position in canonical topological order.

Definition (Lean source)
noncomputable def observedAt (M : SCM N Ω) (i : Fin M.observed.card) : {v // v ∈ M.observed} := by classical letI := M.topoLinearOrder exact M.observed.orderIsoOfFin rfl i
Causalean.SCM.observedAt · Causalean/SCM/Model/SCM.lean:194 · uses SCM , SWIGNode
def observedIndex reviewed
Causalean.SCM

The observed-node index returns the canonical topological position of an observed node.

Definition (Lean source)
noncomputable def observedIndex (M : SCM N Ω) (v : {v // v ∈ M.observed}) : Fin M.observed.card := by classical letI := M.topoLinearOrder exact (M.observed.orderIsoOfFin rfl).symm v
Causalean.SCM.observedIndex · Causalean/SCM/Model/SCM.lean:202 · uses SCM , SWIGNode
theorem observed_parent_index_lt reviewed
Causalean.SCM

For a structural causal model M, fix a valid position n among the observed nodes, and let p be a node such that there is an edge from p to the n-th node in the canonical observed order and p itself is observed; then the canonical index of p among the observed nodes is strictly less than n — an observed parent always precedes its child in the canonical observed topological order.

Formal statement
M :
SCM N Ω
n :
hn :
n < M.observed.card
p :
hparent :
M.dag.edge p (M.observedAt ⟨n, hn⟩).val
hobs :
p ∈ M.observed
M.observedIndex ⟨p, hobs⟩ < ⟨n, hn⟩
Proof (Lean source)
theorem observed_parent_index_lt (M : SCM N Ω) {n : ℕ} (hn : n < M.observed.card) {p : SWIGNode N} (hparent : M.dag.edge p (M.observedAt ⟨n, hn⟩).val) (hobs : p ∈ M.observed) : M.observedIndex ⟨p, hobs⟩ < ⟨n, hn⟩ := by classical letI := M.topoLinearOrder have hp_lt : (⟨p, hobs⟩ : {v // v ∈ M.observed}) < M.observedAt ⟨n, hn⟩ := by change p < (M.observedAt ⟨n, hn⟩).val exact M.dag.topoOrder_lt p _ hparent have hidx : (M.observed.orderIsoOfFin rfl).symm ⟨p, hobs⟩ < (M.observed.orderIsoOfFin rfl).symm (M.observedAt ⟨n, hn⟩) := (M.observed.orderIsoOfFin rfl).symm.strictMono hp_lt simpa [SCM.observedIndex, SCM.observedAt] using hidx
def Equiv reviewed
Causalean.SCM

Two structural causal models are structurally equivalent when their graph, edge labels, structural functions, and latent laws agree.

Definition (Lean source)
def Equiv (M₁ M₂ : SCM N Ω) : Prop := Equivalent M₁.toSWIGGraph M₂.toSWIGGraph ∧ (∀ u v, M₁.dag.edge u v → M₁.edgeTypes.edgeType u v = M₂.edgeTypes.edgeType u v) ∧ HEq M₁.structFun M₂.structFun ∧ HEq M₁.latentDist M₂.latentDist
Causalean.SCM.Equiv · Causalean/SCM/Model/SCM.lean:253 · uses SCM
lemma trans reviewed
Causalean.SCM.Equiv

Structural equivalence is transitive: if M₁ and M₂ are structurally equivalent and M₂ and M₃ are structurally equivalent, then M₁ and M₃ are structurally equivalent.

Formal statement
M₁ M₂ M₃ :
SCM N Ω
h₁ :
Equiv M₁ M₂
h₂ :
Equiv M₂ M₃
Equiv M₁ M₃
Proof (Lean source)
lemma Equiv.trans {M₁ M₂ M₃ : SCM N Ω} (h₁ : Equiv M₁ M₂) (h₂ : Equiv M₂ M₃) : Equiv M₁ M₃ := by rcases h₁ with ⟨hG₁, hE₁, hF₁, hL₁⟩ rcases h₂ with ⟨hG₂, hE₂, hF₂, hL₂⟩ refine intro (trans hG₁ hG₂) ?_ refine intro ?_ ?_ · intro u v hu have hM₂_edge : M₂.dag.edge u v := (hG₁.1 u v).1 hu exact (hE₁ u v hu).trans (hE₂ u v hM₂_edge) · exact intro (hF₁.trans hF₂) (hL₁.trans hL₂)
Causalean.SCM.Equiv.trans · Causalean/SCM/Model/SCM.lean:285 · uses SCM , Equiv
9 supporting declarations (lemmas, instances)
  • not_unobs_of_obs theorem — An observed node cannot also be an unobserved node.
    G :
    n :
    h :
    n ∈ G.observed
    n ∉ G.unobserved
    Proof (Lean source)
    theorem not_unobs_of_obs (G : SWIGGraph N) {n : SWIGNode N} (h : n ∈ G.observed) : n ∉ G.unobserved := Finset.disjoint_left.mp G.obs_unobs_disjoint h
    Causalean.SCM.not_unobs_of_obs · Causalean/SCM/Model/SCM.lean:139
  • not_obs_of_unobs theorem — An unobserved node cannot also be an observed node.
    G :
    n :
    h :
    n ∈ G.unobserved
    n ∉ G.observed
    Proof (Lean source)
    theorem not_obs_of_unobs (G : SWIGGraph N) {n : SWIGNode N} (h : n ∈ G.unobserved) : n ∉ G.observed := Finset.disjoint_right.mp G.obs_unobs_disjoint h
    Causalean.SCM.not_obs_of_unobs · Causalean/SCM/Model/SCM.lean:144
  • not_fixed_of_obs theorem — An observed node cannot also be a fixed node.
    G :
    n :
    h :
    n ∈ G.observed
    n ∉ G.fixed
    Proof (Lean source)
    theorem not_fixed_of_obs (G : SWIGGraph N) {n : SWIGNode N} (h : n ∈ G.observed) : n ∉ G.fixed := by intro hfix obtain ⟨m, hm⟩ := G.fixed_is_fixed n hfix obtain ⟨k, hk⟩ := G.observed_is_random n h rw [hm] at hk cases hk
    Causalean.SCM.not_fixed_of_obs · Causalean/SCM/Model/SCM.lean:149
  • instProbabilityLatentProduct instance — The latent product measure has total mass one.
    instance instProbabilityLatentProduct (M : SCM N Ω) : IsProbabilityMeasure (M.latentProduct) := by letI := M.isProbability_latent change IsProbabilityMeasure (pi (fun u => M.latentDist u)) infer_instance
    Causalean.SCM.instProbabilityLatentProduct · Causalean/SCM/Model/SCM.lean:178
  • observedAt_observedIndex theorem — Looking up an observed node by its canonical index recovers the same node.
    M :
    SCM N Ω
    v :
    {v // v ∈ M.observed}
    (M.observedAt (M.observedIndex v)).val = v.val
    Proof (Lean source)
    @[simp] theorem observedAt_observedIndex (M : SCM N Ω) (v : {v // v ∈ M.observed}) : (M.observedAt (M.observedIndex v)).val = v.val := by classical letI := M.topoLinearOrder simp [SCM.observedAt, SCM.observedIndex]
    Causalean.SCM.observedAt_observedIndex · Causalean/SCM/Model/SCM.lean:209
  • observedIndex_observedAt theorem — Looking up the canonical index of the observed node at a position recovers that position.
    M :
    SCM N Ω
    k :
    Fin M.observed.card
    M.observedIndex (M.observedAt k) = k
    Proof (Lean source)
    @[simp] theorem observedIndex_observedAt (M : SCM N Ω) (k : Fin M.observed.card) : M.observedIndex (M.observedAt k) = k := by classical letI := M.topoLinearOrder simp [SCM.observedAt, SCM.observedIndex]
    Causalean.SCM.observedIndex_observedAt · Causalean/SCM/Model/SCM.lean:217
  • refl lemma — Structural equivalence is reflexive.
    M :
    SCM N Ω
    Equiv M M
    Proof (Lean source)
    lemma Equiv.refl (M : SCM N Ω) : Equiv M M := by refine intro (refl _) ?_ refine intro ?_ ?_ · intro u v _ rfl · exact intro HEq.rfl HEq.rfl
    Causalean.SCM.Equiv.refl · Causalean/SCM/Model/SCM.lean:267
  • symm lemma — Structural equivalence is symmetric.
    M₁ M₂ :
    SCM N Ω
    h :
    Equiv M₁ M₂
    Equiv M₂ M₁
    Proof (Lean source)
    lemma symm {M₁ M₂ : SCM N Ω} (h : Equiv M₁ M₂) : Equiv M₂ M₁ := by rcases h with ⟨hG, hE, hF, hL⟩ refine intro hG.symm ?_ refine intro ?_ ?_ · intro u v hu exact (hE u v ((hG.1 u v).2 hu)).symm · exact intro hF.symm hL.symm
    Causalean.SCM.Equiv.symm · Causalean/SCM/Model/SCM.lean:275
  • instSetoidSCM instance — Structural causal models form a setoid under structural equivalence.
    instance instSetoidSCM : Setoid (SCM N Ω) where r := Equiv iseqv := ⟨Equiv.refl, symm, Equiv.trans⟩
    Causalean.SCM.instSetoidSCM · Causalean/SCM/Model/SCM.lean:299
Evaluation 4 core · 10 supporting This file defines the evaluation map that sends fixed intervention values and a latent realization to the values of all random nodes in a structural causal model. ★ ancestralFactorization

Evaluation Map

This file defines the evaluation map that sends fixed intervention values and a latent realization to the values of all random nodes in a structural causal model. It proves the measurability and ancestral factorization facts that support the joint-kernel, Markov, and do-calculus layers, using the model's stored topological order of observed nodes.

Main definitions and results

* SCM.parentMap assembles the fixed, latent, and recursive observed parent values for one observed node. * SCM.evalObservedAux evaluates observed nodes by strong recursion over the stored topological order. * SCM.evalMap evaluates all random nodes, projecting latent roots directly and computing observed nodes through evalObservedAux. * SCM.evalMap_observed_unfold exposes the structural-function form of evaluation at an observed node. * SCM.evalMap_measurable and SCM.ancestralFactorization provide the measurability and ancestor-agreement interfaces used by the kernel and Markov developments.

def parentMap reviewed
Causalean.SCM

A parent-value tuple is assembled from fixed, latent, and earlier observed values.

Definition (Lean source)
noncomputable def parentMap (M : SCM N Ω) (s : FixedValues M) (ℓ : LatentValues M) {n : ℕ} (hn : n < M.observed.card) (prev : ∀ m : ℕ, m < n → ∀ hm : m < M.observed.card, swigΩ Ω (M.observedAt ⟨m, hm⟩).val) (w : {w // w ∈ M.dag.parents (M.observedAt ⟨n, hn⟩).val}) : swigΩ Ω w.val := if huo : w.val ∈ M.unobserved then ℓ ⟨w.val, huo⟩ else if hfix : w.val ∈ M.fixed then s ⟨w.val, hfix⟩ else have hedge : M.dag.edge w.val (M.observedAt ⟨n, hn⟩).val := M.dag.mem_parents.mp w.property have hcls := (M.dag_edges_classified w.val (M.observedAt ⟨n, hn⟩).val hedge).1 have hobs : w.val ∈ M.observed := (Finset.mem_union.mp hcls).elim (fun hfo => (Finset.mem_union.mp hfo).elim (fun hf => absurd hf hfix) id) (fun huo' => absurd huo' huo) (M.observedAt_observedIndex ⟨w.val, hobs⟩) ▸ prev (M.observedIndex ⟨w.val, hobs⟩).val (M.observed_parent_index_lt hn hedge hobs) (M.observedIndex ⟨w.val, hobs⟩).isLt
def evalObservedAux reviewed
Causalean.SCM

The auxiliary evaluator recursively applies structural functions in topological order.

Definition (Lean source)
noncomputable def evalObservedAux (M : SCM N Ω) (s : FixedValues M) (ℓ : LatentValues M) (n : ℕ) : ∀ hn : n < M.observed.card, swigΩ Ω (M.observedAt ⟨n, hn⟩).val := Nat.strongRec (motive := fun k => ∀ hk : k < M.observed.card, swigΩ Ω (M.observedAt ⟨k, hk⟩).val) (fun k ih hk => M.structFun (M.observedAt ⟨k, hk⟩) (fun w => parentMap M s ℓ hk ih w)) n
def evalMap reviewed
Causalean.SCM

The evaluation map sends fixed values and a latent realization to the resulting values of all observed and latent random nodes.

Definition (Lean source)
noncomputable def evalMap (M : SCM N Ω) : FixedValues M → LatentValues M → RandomValues M := fun s ℓ w => if hobs : w.val ∈ M.observed then (M.observedAt_observedIndex ⟨w.val, hobs⟩) ▸ evalObservedAux M s ℓ (M.observedIndex ⟨w.val, hobs⟩).val (M.observedIndex ⟨w.val, hobs⟩).isLt else have hrand : w.val ∈ M.observed ∪ M.unobserved := by have hw := w.property change w.val ∈ M.observed ∪ M.unobserved at hw exact hw have huo : w.val ∈ M.unobserved := (Finset.mem_union.mp hrand).elim (fun ho => absurd ho hobs) id ℓ ⟨w.val, huo⟩
theorem ancestralFactorization reviewed
Causalean.SCM

Ancestral factorization (Lemma lem:scm-ancestral-factor). Fix a structural causal model M, a target node set T, two fixed-value assignments s, s', two latent assignments ℓ, ℓ', and a node v with v belonging to T and v an observed node. If s and s' agree on every fixed node that equals or is an ancestor of some node of T and and ℓ' agree on every unobserved node that equals or is an ancestor of some node of T, then the evaluation of M at v with (s, ℓ) equals the evaluation with (s', ℓ').

Formal statement
M :
SCM N Ω
T :
s s' :
ℓ ℓ' :
hs :
∀ (d : SWIGNode N) (hd : d ∈ M.fixed)
if
(∃ v ∈ T, d = v ∨ M.dag.isAncestor d v)
then
s ⟨d, hd⟩ = s' ⟨d, hd⟩
hℓ :
∀ (u : SWIGNode N) (hu : u ∈ M.unobserved)
if
(∃ v ∈ T, u = v ∨ M.dag.isAncestor u v)
then
ℓ ⟨u, hu⟩ = ℓ' ⟨u, hu⟩
v :
hv :
v ∈ T
hv_obs :
v ∈ M.observed
M.evalMap s ℓ ⟨v, mem_union_left _ hv_obs⟩
= M.evalMap s' ℓ' ⟨v, mem_union_left _ hv_obs⟩
Proof (Lean source)
theorem ancestralFactorization (M : SCM N Ω) (T : Finset (SWIGNode N)) {s s' : FixedValues M} {ℓ ℓ' : LatentValues M} (hs : ∀ (d : SWIGNode N) (hd : d ∈ M.fixed), (∃ v ∈ T, d = v ∨ M.dag.isAncestor d v) → s ⟨d, hd⟩ = s' ⟨d, hd⟩) (hℓ : ∀ (u : SWIGNode N) (hu : u ∈ M.unobserved), (∃ v ∈ T, u = v ∨ M.dag.isAncestor u v) → ℓ ⟨u, hu⟩ = ℓ' ⟨u, hu⟩) {v : SWIGNode N} (hv : v ∈ T) (hv_obs : v ∈ M.observed) : M.evalMap s ℓ ⟨v, mem_union_left _ hv_obs⟩ = M.evalMap s' ℓ' ⟨v, mem_union_left _ hv_obs⟩ := by rw [evalMap_observed M s ℓ ⟨v, mem_union_left _ hv_obs⟩ hv_obs, evalMap_observed M s' ℓ' ⟨v, mem_union_left _ hv_obs⟩ hv_obs] congr 1 apply evalObservedAux_agree_anc M T hs hℓ refine ⟨v, hv, inl ?_⟩ exact M.observedAt_observedIndex ⟨v, hv_obs⟩
10 supporting declarations (lemmas, instances)
  • parentMap_unobserved lemma — The parent-value tuple reads a latent parent directly from the latent assignment.
    M :
    SCM N Ω
    s :
    :
    n :
    hn :
    n < M.observed.card
    prev :
    ∀ m : ℕ
    if
    m < n
    then
    ∀ hm : m < M.observed.card, swigΩ Ω (M.observedAt ⟨m, hm⟩).val
    w :
    {w // w ∈ M.dag.parents (M.observedAt ⟨n, hn⟩).val}
    huo :
    w.val ∈ M.unobserved
    parentMap M s ℓ hn prev w = ℓ ⟨w.val, huo⟩
    Proof (Lean source)
    lemma parentMap_unobserved (M : SCM N Ω) (s : FixedValues M) (ℓ : LatentValues M) {n : ℕ} (hn : n < M.observed.card) (prev : ∀ m : ℕ, m < n → ∀ hm : m < M.observed.card, swigΩ Ω (M.observedAt ⟨m, hm⟩).val) (w : {w // w ∈ M.dag.parents (M.observedAt ⟨n, hn⟩).val}) (huo : w.val ∈ M.unobserved) : parentMap M s ℓ hn prev w = ℓ ⟨w.val, huo⟩ := by unfold parentMap rw [dif_pos huo]
    Causalean.SCM.parentMap_unobserved · Causalean/SCM/Model/Evaluation.lean:83
  • parentMap_fixed lemma — The parent-value tuple reads a fixed parent directly from the fixed-value assignment.
    M :
    SCM N Ω
    s :
    :
    n :
    hn :
    n < M.observed.card
    prev :
    ∀ m : ℕ
    if
    m < n
    then
    ∀ hm : m < M.observed.card, swigΩ Ω (M.observedAt ⟨m, hm⟩).val
    w :
    {w // w ∈ M.dag.parents (M.observedAt ⟨n, hn⟩).val}
    hfix :
    w.val ∈ M.fixed
    parentMap M s ℓ hn prev w = s ⟨w.val, hfix⟩
    Proof (Lean source)
    lemma parentMap_fixed (M : SCM N Ω) (s : FixedValues M) (ℓ : LatentValues M) {n : ℕ} (hn : n < M.observed.card) (prev : ∀ m : ℕ, m < n → ∀ hm : m < M.observed.card, swigΩ Ω (M.observedAt ⟨m, hm⟩).val) (w : {w // w ∈ M.dag.parents (M.observedAt ⟨n, hn⟩).val}) (hfix : w.val ∈ M.fixed) : parentMap M s ℓ hn prev w = s ⟨w.val, hfix⟩ := by unfold parentMap have huo : w.val ∉ M.unobserved := by intro h obtain ⟨m, hm⟩ := M.unobserved_is_random _ h obtain ⟨k, hk⟩ := M.fixed_is_fixed _ hfix rw [hk] at hm exact absurd hm (by simp) rw [dif_neg huo, dif_pos hfix]
    Causalean.SCM.parentMap_fixed · Causalean/SCM/Model/Evaluation.lean:95
  • parentMap_observed lemma — The parent-value tuple reads an observed parent from the previously computed observed values.
    M :
    SCM N Ω
    s :
    :
    n :
    hn :
    n < M.observed.card
    prev :
    ∀ m : ℕ
    if
    m < n
    then
    ∀ hm : m < M.observed.card, swigΩ Ω (M.observedAt ⟨m, hm⟩).val
    w :
    {w // w ∈ M.dag.parents (M.observedAt ⟨n, hn⟩).val}
    hobs :
    w.val ∈ M.observed
    parentMap M s ℓ hn prev w
    = (M.observedAt_observedIndex ⟨w.val, hobs⟩) ▸ prev (M.observedIndex ⟨w.val, hobs⟩).val (M.observed_parent_index_lt hn (M.dag.mem_parents.mp w.property) hobs) (M.observedIndex ⟨w.val, hobs⟩).isLt
    Proof (Lean source)
    lemma parentMap_observed (M : SCM N Ω) (s : FixedValues M) (ℓ : LatentValues M) {n : ℕ} (hn : n < M.observed.card) (prev : ∀ m : ℕ, m < n → ∀ hm : m < M.observed.card, swigΩ Ω (M.observedAt ⟨m, hm⟩).val) (w : {w // w ∈ M.dag.parents (M.observedAt ⟨n, hn⟩).val}) (hobs : w.val ∈ M.observed) : parentMap M s ℓ hn prev w = (M.observedAt_observedIndex ⟨w.val, hobs⟩) ▸ prev (M.observedIndex ⟨w.val, hobs⟩).val (M.observed_parent_index_lt hn (M.dag.mem_parents.mp w.property) hobs) (M.observedIndex ⟨w.val, hobs⟩).isLt := by unfold parentMap have huo : w.val ∉ M.unobserved := not_unobs_of_obs M.toSWIGGraph hobs have hfix : w.val ∉ M.fixed := not_fixed_of_obs M.toSWIGGraph hobs rw [dif_neg huo, dif_neg hfix]
    Causalean.SCM.parentMap_observed · Causalean/SCM/Model/Evaluation.lean:113
  • evalObservedAux_eq lemma — The auxiliary evaluator unfolds to its structural function applied to the parent tuple.
    M :
    SCM N Ω
    s :
    :
    n :
    hn :
    n < M.observed.card
    evalObservedAux M s ℓ n hn
    = M.structFun (M.observedAt ⟨n, hn⟩) (fun w => parentMap M s ℓ hn (fun m _ hm_card => evalObservedAux M s ℓ m hm_card) w)
    Proof (Lean source)
    lemma evalObservedAux_eq (M : SCM N Ω) (s : FixedValues M) (ℓ : LatentValues M) (n : ℕ) (hn : n < M.observed.card) : evalObservedAux M s ℓ n hn = M.structFun (M.observedAt ⟨n, hn⟩) (fun w => parentMap M s ℓ hn (fun m _ hm_card => evalObservedAux M s ℓ m hm_card) w) := by unfold evalObservedAux rw [Nat.strongRec_eq]
    Causalean.SCM.evalObservedAux_eq · Causalean/SCM/Model/Evaluation.lean:152
  • evalMap_observed lemma — On an observed node, the evaluation map is the auxiliary topological-order evaluation transported to that node.
    M :
    SCM N Ω
    s :
    :
    w :
    {w // w ∈ M.randomVars}
    hobs :
    w.val ∈ M.observed
    M.evalMap s ℓ w
    = (M.observedAt_observedIndex ⟨w.val, hobs⟩) ▸ evalObservedAux M s ℓ (M.observedIndex ⟨w.val, hobs⟩).val (M.observedIndex ⟨w.val, hobs⟩).isLt
    Proof (Lean source)
    lemma evalMap_observed (M : SCM N Ω) (s : FixedValues M) (ℓ : LatentValues M) (w : {w // w ∈ M.randomVars}) (hobs : w.val ∈ M.observed) : M.evalMap s ℓ w = (M.observedAt_observedIndex ⟨w.val, hobs⟩) ▸ evalObservedAux M s ℓ (M.observedIndex ⟨w.val, hobs⟩).val (M.observedIndex ⟨w.val, hobs⟩).isLt := by unfold evalMap rw [dif_pos hobs]
    Causalean.SCM.evalMap_observed · Causalean/SCM/Model/Evaluation.lean:198
  • evalMap_unobserved lemma — On a latent node, the evaluation map is direct projection from the latent assignment.
    M :
    SCM N Ω
    s :
    :
    w :
    {w // w ∈ M.randomVars}
    huo :
    w.val ∈ M.unobserved
    M.evalMap s ℓ w = ℓ ⟨w.val, huo⟩
    Proof (Lean source)
    lemma evalMap_unobserved (M : SCM N Ω) (s : FixedValues M) (ℓ : LatentValues M) (w : {w // w ∈ M.randomVars}) (huo : w.val ∈ M.unobserved) : M.evalMap s ℓ w = ℓ ⟨w.val, huo⟩ := by unfold evalMap rw [dif_neg (not_obs_of_unobs M.toSWIGGraph huo)]
    Causalean.SCM.evalMap_unobserved · Causalean/SCM/Model/Evaluation.lean:210
  • evalMap_observed_unfold lemma — At an observed node, the evaluation map unfolds to the structural function applied to fixed, latent, or recursively evaluated parent values.
    M :
    SCM N Ω
    s :
    :
    v :
    {v // v ∈ M.observed}
    M.evalMap s ℓ ⟨v.val, mem_union_left _ v.property⟩
    = M.structFun v (fun w : {w // w ∈ M.dag.parents v.val} => if huo : w.val ∈ M.unobserved then ℓ ⟨w.val, huo⟩ else if hfix : w.val ∈ M.fixed then s ⟨w.val, hfix⟩ else have hedge : M.dag.edge w.val v.val := M.dag.mem_parents.mp w.property have hobs : w.val ∈ M.observed := by rcases Finset.mem_union.mp (M.dag_edges_classified _ _ hedge).1 with h1 | h2 · rcases Finset.mem_union.mp h1 with hfx | hob · exact absurd hfx hfix · exact hob · exact absurd h2 huo M.evalMap s ℓ ⟨w.val, mem_union_left _ hobs⟩)
    Proof (Lean source)
    lemma evalMap_observed_unfold (M : SCM N Ω) (s : FixedValues M) (ℓ : LatentValues M) (v : {v // v ∈ M.observed}) : M.evalMap s ℓ ⟨v.val, mem_union_left _ v.property⟩ = M.structFun v (fun w : {w // w ∈ M.dag.parents v.val} => if huo : w.val ∈ M.unobserved then ℓ ⟨w.val, huo⟩ else if hfix : w.val ∈ M.fixed then s ⟨w.val, hfix⟩ else have hedge : M.dag.edge w.val v.val := M.dag.mem_parents.mp w.property have hobs : w.val ∈ M.observed := by rcases Finset.mem_union.mp (M.dag_edges_classified _ _ hedge).1 with h1 | h2 · rcases Finset.mem_union.mp h1 with hfx | hob · exact absurd hfx hfix · exact hob · exact absurd h2 huo M.evalMap s ℓ ⟨w.val, mem_union_left _ hobs⟩) := by -- Reduce to the `parentDispatch` form via a helper that takes the Subtype witness -- `hw : M.observedAt j = w` as a parameter. `subst hw` inside the helper eliminates -- the circular dependency between `v` and `M.observedIndex v`. suffices h : ∀ (j : Fin M.observed.card) (w : {v // v ∈ M.observed}) (_ : M.observedAt j = w), M.evalMap s ℓ ⟨w.val, mem_union_left _ w.property⟩ = M.structFun w (parentDispatch M s ℓ w) by have key := h (M.observedIndex ⟨v.val, v.property⟩) v (Subtype.ext (M.observedAt_observedIndex ⟨v.val, v.property⟩)) rw [key] rfl intro j w hw subst hw -- `w` eliminated. Goal mentions `M.observedAt j` only. -- `rw` cannot key on `M.evalMap s ℓ ⟨_, _⟩` here: `M.randomVars` is semireducible, so -- the membership witness (typed at `M.observed ∪ M.unobserved`) is not accepted at -- `implicit` transparency. Chain the rewrite at term level instead. refine (evalMap_observed M s ℓ ⟨(M.observedAt j).val, mem_union_left _ (M.observedAt j).property⟩ (M.observedAt j).property).trans ?_ -- Apply the cast helper: the Fin index `M.observedIndex ⟨(M.observedAt j).val, _⟩` -- reduces to `j` via `observedIndex_observedAt` (after Subtype eta), and the cast -- proof is discharged via proof irrelevance inside the helper. exact evalObservedAux_cast_eq_structFunAt M s ℓ (M.observedIndex_observedAt j) (M.observedAt_observedIndex ⟨(M.observedAt j).val, (M.observedAt j).property⟩)
    Causalean.SCM.evalMap_observed_unfold · Causalean/SCM/Model/Evaluation.lean:298
  • evalObservedAux_measurable lemma — At every position in a causal model's topological ordering of observed variables, the recursively evaluated observed value is measurable as a function of the model's fixed and latent inputs.
    M :
    SCM N Ω
    ∀ (n : ℕ) (hn : n < M.observed.card),
    Measurable (fun p : FixedValues M × LatentValues M => evalObservedAux M p.1 p.2 n hn)
    Proof (Lean source)
    lemma evalObservedAux_measurable (M : SCM N Ω) : ∀ (n : ℕ) (hn : n < M.observed.card), Measurable (fun p : FixedValues M × LatentValues M => evalObservedAux M p.1 p.2 n hn) := by intro n induction n using Nat.strongRecOn with | _ n ih => intro hn have hrw : (fun p : FixedValues M × LatentValues M => evalObservedAux M p.1 p.2 n hn) = (fun p => M.structFun (M.observedAt ⟨n, hn⟩) (fun w => parentMap M p.1 p.2 hn (fun m _ hm_card => evalObservedAux M p.1 p.2 m hm_card) w)) := by funext p exact evalObservedAux_eq M p.1 p.2 n hn rw [hrw] refine (M.structFun_measurable _).comp ?_ refine measurable_pi_lambda _ (fun w => ?_) by_cases huo : w.val ∈ M.unobserved · have hfun : (fun p : FixedValues M × LatentValues M => parentMap M p.1 p.2 hn (fun m _ hm_card => evalObservedAux M p.1 p.2 m hm_card) w) = (fun p => p.2 ⟨w.val, huo⟩) := by funext p exact parentMap_unobserved M p.1 p.2 hn _ w huo rw [hfun] exact (measurable_pi_apply _).comp measurable_snd · by_cases hfix : w.val ∈ M.fixed · have hfun : (fun p : FixedValues M × LatentValues M => parentMap M p.1 p.2 hn (fun m _ hm_card => evalObservedAux M p.1 p.2 m hm_card) w) = (fun p => p.1 ⟨w.val, hfix⟩) := by funext p exact parentMap_fixed M p.1 p.2 hn _ w hfix rw [hfun] exact (measurable_pi_apply _).comp measurable_fst · have hedge : M.dag.edge w.val (M.observedAt ⟨n, hn⟩).val := M.dag.mem_parents.mp w.property have hobs : w.val ∈ M.observed := by have hcls := (M.dag_edges_classified _ _ hedge).1 rcases Finset.mem_union.mp hcls with hfo | huo' · rcases Finset.mem_union.mp hfo with hf | ho · exact absurd hf hfix · exact ho · exact absurd huo' huo have hj : (M.observedIndex ⟨w.val, hobs⟩).val < n := M.observed_parent_index_lt hn hedge hobs have hfun : (fun p : FixedValues M × LatentValues M => parentMap M p.1 p.2 hn (fun m _ hm_card => evalObservedAux M p.1 p.2 m hm_card) w) = (fun p => (M.observedAt_observedIndex ⟨w.val, hobs⟩) ▸ evalObservedAux M p.1 p.2 (M.observedIndex ⟨w.val, hobs⟩).val (M.observedIndex ⟨w.val, hobs⟩).isLt) := by funext p exact parentMap_observed M p.1 p.2 hn _ w hobs rw [hfun] exact measurable_family_cast _ (ih _ hj _)
    Causalean.SCM.evalObservedAux_measurable · Causalean/SCM/Model/Evaluation.lean:349
  • evalMap_measurable theorem — The evaluation map is jointly measurable in the fixed-value assignment and latent realization.
    M :
    SCM N Ω
    Measurable (uncurry M.evalMap)
    Proof (Lean source)
    theorem evalMap_measurable (M : SCM N Ω) : Measurable (uncurry M.evalMap) := by refine measurable_pi_iff.mpr (fun w => ?_) by_cases hobs : w.val ∈ M.observed · have hfun : (fun p : FixedValues M × LatentValues M => uncurry M.evalMap p w) = (fun p => (M.observedAt_observedIndex ⟨w.val, hobs⟩) ▸ evalObservedAux M p.1 p.2 (M.observedIndex ⟨w.val, hobs⟩).val (M.observedIndex ⟨w.val, hobs⟩).isLt) := by funext p exact evalMap_observed M p.1 p.2 w hobs rw [hfun] exact measurable_family_cast _ (evalObservedAux_measurable M _ _) · have hrand : w.val ∈ M.observed ∪ M.unobserved := by have hw := w.property change w.val ∈ M.observed ∪ M.unobserved at hw exact hw have huo : w.val ∈ M.unobserved := (Finset.mem_union.mp hrand).elim (fun ho => absurd ho hobs) id have hfun : (fun p : FixedValues M × LatentValues M => uncurry M.evalMap p w) = (fun p => p.2 ⟨w.val, huo⟩) := by funext p exact evalMap_unobserved M p.1 p.2 w huo rw [hfun] exact (measurable_pi_apply _).comp measurable_snd
    Causalean.SCM.evalMap_measurable · Causalean/SCM/Model/Evaluation.lean:415
  • evalObservedAux_agree_anc lemma — An observed variable has the same recursively evaluated value under two inputs when those inputs agree on every fixed and latent cause that can affect the target variables. This expresses the local ancestral dependence of a structural causal model.
    M :
    SCM N Ω
    T :
    s s' :
    ℓ ℓ' :
    hs :
    ∀ (d : SWIGNode N) (hd : d ∈ M.fixed)
    if
    (∃ v ∈ T, d = v ∨ M.dag.isAncestor d v)
    then
    s ⟨d, hd⟩ = s' ⟨d, hd⟩
    hℓ :
    ∀ (u : SWIGNode N) (hu : u ∈ M.unobserved)
    if
    (∃ v ∈ T, u = v ∨ M.dag.isAncestor u v)
    then
    ℓ ⟨u, hu⟩ = ℓ' ⟨u, hu⟩
    ∀ (n : ℕ) (hn : n < M.observed.card) (_ : ∃ v ∈ T, (M.observedAt ⟨n, hn⟩).val = v ∨ M.dag.isAncestor (M.observedAt ⟨n, hn⟩).val v),
    evalObservedAux M s ℓ n hn = evalObservedAux M s' ℓ' n hn
    Proof (Lean source)
    lemma evalObservedAux_agree_anc (M : SCM N Ω) (T : Finset (SWIGNode N)) {s s' : FixedValues M} {ℓ ℓ' : LatentValues M} (hs : ∀ (d : SWIGNode N) (hd : d ∈ M.fixed), (∃ v ∈ T, d = v ∨ M.dag.isAncestor d v) → s ⟨d, hd⟩ = s' ⟨d, hd⟩) (hℓ : ∀ (u : SWIGNode N) (hu : u ∈ M.unobserved), (∃ v ∈ T, u = v ∨ M.dag.isAncestor u v) → ℓ ⟨u, hu⟩ = ℓ' ⟨u, hu⟩) : ∀ (n : ℕ) (hn : n < M.observed.card) (_ : ∃ v ∈ T, (M.observedAt ⟨n, hn⟩).val = v ∨ M.dag.isAncestor (M.observedAt ⟨n, hn⟩).val v), evalObservedAux M s ℓ n hn = evalObservedAux M s' ℓ' n hn := by intro n induction n using Nat.strongRecOn with | _ n ih => intro hn hAnc rw [evalObservedAux_eq M s ℓ n hn, evalObservedAux_eq M s' ℓ' n hn] congr 1 funext w have hedge : M.dag.edge w.val (M.observedAt ⟨n, hn⟩).val := M.dag.mem_parents.mp w.property have hw_anc_obs : M.dag.isAncestor w.val (M.observedAt ⟨n, hn⟩).val := DAG.isAncestor.edge hedge -- Chain the ancestor witness from `observedAt n` through `w`. have hAncW : ∃ v ∈ T, w.val = v ∨ M.dag.isAncestor w.val v := by rcases hAnc with ⟨v, hv, hOrAnc⟩ refine ⟨v, hv, ?_⟩ rcases hOrAnc with hEq | hAncToV · -- `observedAt n = v`, so `w` is a direct ancestor of `v`. exact inr (hEq ▸ hw_anc_obs) · -- `observedAt n` ancestor of `v`: transit through `w`. exact inr (M.dag.isAncestor_trans hw_anc_obs hAncToV) by_cases huo : w.val ∈ M.unobserved · rw [parentMap_unobserved M s ℓ hn _ w huo, parentMap_unobserved M s' ℓ' hn _ w huo] exact hℓ w.val huo hAncW · by_cases hfix : w.val ∈ M.fixed · rw [parentMap_fixed M s ℓ hn _ w hfix, parentMap_fixed M s' ℓ' hn _ w hfix] exact hs w.val hfix hAncW · -- `w` is observed: apply IH at its smaller topological index. have hobs : w.val ∈ M.observed := by rcases Finset.mem_union.mp (M.dag_edges_classified _ _ hedge).1 with h1 | h2 · rcases Finset.mem_union.mp h1 with hfx | hob · exact absurd hfx hfix · exact hob · exact absurd h2 huo have hj : (M.observedIndex ⟨w.val, hobs⟩).val < n := M.observed_parent_index_lt hn hedge hobs rw [parentMap_observed M s ℓ hn _ w hobs, parentMap_observed M s' ℓ' hn _ w hobs] congr 1 apply ih _ hj -- Re-cast the ancestor witness at `observedAt (observedIndex w) = w`. rcases hAncW with ⟨v, hv, hwv⟩ refine ⟨v, hv, ?_⟩ have h_at : (M.observedAt ⟨(M.observedIndex ⟨w.val, hobs⟩).val, (M.observedIndex ⟨w.val, hobs⟩).isLt⟩).val = w.val := M.observedAt_observedIndex ⟨w.val, hobs⟩ rw [h_at] exact hwv
    Causalean.SCM.evalObservedAux_agree_anc · Causalean/SCM/Model/Evaluation.lean:454
Kernel 7 core · 6 supporting This file constructs the joint kernel induced by a structural causal model from its latent distribution and evaluation map, and then obtains the observational kernel by projecting to observed coordinates. ★ jointKernel_apply_eq★ obsCondKernel_ae_eq_condDistrib

Kernels Generated by Structural Causal Models

This file constructs the joint kernel induced by a structural causal model from its latent distribution and evaluation map, and then obtains the observational kernel by projecting to observed coordinates. It also provides the conditional kernels used to state kernel-level do-calculus rules.

Do-calculus Rule 2 and Rule 3 kernel statements and their value-space helpers live in the dedicated do-calculus files rather than in this core kernel file.

Main definitions and results

* SCM.jointKernel is the law of SCM.evalMap under the latent product, parameterized by fixed values. * SCM.randomToObserved and SCM.obsKernel project the joint law to observed coordinates. * SCM.jointKernel_apply_eq and SCM.jointKernel_map_commute expose pushforward forms used by downstream measure proofs. * SCM.obsCondPairKernel and SCM.obsCondKernel construct jointly measurable conditional laws of target coordinates given conditioning coordinates. * SCM.obsCondKernel_ae_eq_condDistrib connects the kernel-native conditional object to the measure-level conditional distribution.

def jointKernel reviewed
Causalean.SCM

The joint kernel maps each fixed-value assignment to the law of the model evaluation under the latent product distribution.

Definition (Lean source)
noncomputable def jointKernel (M : SCM N Ω) : Kernel (FixedValues M) (RandomValues M) := ((const (FixedValues M) M.latentProduct) ⊗ₖ (deterministic (uncurry M.evalMap) M.evalMap_measurable)).map snd
def randomToObserved reviewed
Causalean.SCM

The observed projection keeps only the observed coordinates of a full random assignment.

Definition (Lean source)
def randomToObserved (M : SCM N Ω) : RandomValues M → ObservedValues M := fun ξ v => ξ ⟨v.val, mem_union_left _ v.property⟩
lemma jointKernel_apply_eq reviewed
Causalean.SCM

For a structural causal model M and a fixed-node assignment s, the joint kernel evaluated at s equals the pushforward of the latent-variable product measure through the evaluation map at s.

Formal statement
M :
SCM N Ω
s :
M.jointKernel s = M.latentProduct.map (fun ℓ => M.evalMap s ℓ)
Proof (Lean source)
lemma jointKernel_apply_eq (M : SCM N Ω) (s : FixedValues M) : M.jointKernel s = M.latentProduct.map (fun ℓ => M.evalMap s ℓ) := by have hf : Measurable (fun ℓ : LatentValues M => M.evalMap s ℓ) := by have : M.evalMap s = fun ℓ => uncurry M.evalMap (s, ℓ) := rfl rw [this] exact M.evalMap_measurable.comp (prodMk measurable_const measurable_id) refine MeasureTheory.Measure.ext fun A hA => ?_ unfold jointKernel rw [ProbabilityTheory.Kernel.map_apply _ measurable_snd, MeasureTheory.Measure.map_apply measurable_snd hA, ProbabilityTheory.Kernel.compProd_apply (measurable_snd hA), ProbabilityTheory.Kernel.const_apply, MeasureTheory.Measure.map_apply hf hA] -- LHS: ∫⁻ ℓ, (deterministic (uncurry evalMap)) (s, ℓ) -- (Prod.mk ℓ ⁻¹' (snd ⁻¹' A)) ∂latentProduct -- RHS: latentProduct ((fun ℓ => evalMap s ℓ) ⁻¹' A) simp only [ProbabilityTheory.Kernel.deterministic_apply, Function.uncurry_apply_pair] -- Simplify the preimage `Prod.mk ℓ ⁻¹' (snd ⁻¹' A) = A` (rfl on Set membership). have hset : ∀ ℓ : LatentValues M, (Prod.mk ℓ ⁻¹' (snd ⁻¹' A) : Set (RandomValues M)) = A := fun _ => rfl simp only [hset] -- Goal: ∫⁻ ℓ, Measure.dirac (M.evalMap s ℓ) A ∂latentProduct -- = latentProduct ((fun ℓ => evalMap s ℓ) ⁻¹' A) -- Bridge through indicator form. trans (∫⁻ ℓ, indicator ((fun ℓ => M.evalMap s ℓ) ⁻¹' A) (fun _ => (1 : ENNReal)) ℓ ∂M.latentProduct) · apply MeasureTheory.lintegral_congr intro ℓ rw [MeasureTheory.Measure.dirac_apply' _ hA] simp only [indicator, Set.mem_preimage, Pi.one_apply] rfl · exact MeasureTheory.lintegral_indicator_one (hf hA)
def obsKernel reviewed
Causalean.SCM

The observational kernel maps each fixed-value assignment to the induced law of the observed nodes.

Definition (Lean source)
noncomputable def obsKernel (M : SCM N Ω) : Kernel (FixedValues M) (ObservedValues M) := (M.jointKernel).map M.randomToObserved
def obsCondPairKernel reviewed
Causalean.SCM

The conditional-pair kernel pushes the observational law to conditioning coordinates paired with target coordinates.

Definition (Lean source)
noncomputable def obsCondPairKernel (M : SCM N Ω) (Y CC : Finset (SWIGNode N)) (hY : Y ⊆ M.observed) (hCC : CC ⊆ M.observed) : Kernel (FixedValues M) (ValuesOn CC (swigΩ Ω) × ValuesOn Y (swigΩ Ω)) := (M.obsKernel).map (fun ω => (valuesProjection hCC ω, valuesProjection hY ω))
def obsCondKernel reviewed
Causalean.SCM

The observational conditional kernel is a jointly measurable conditional law of target coordinates given conditioning coordinates.

Definition (Lean source)
noncomputable def obsCondKernel (M : SCM N Ω) (Y CC : Finset (SWIGNode N)) (hY : Y ⊆ M.observed) (hCC : CC ⊆ M.observed) [StandardBorelSpace (ValuesOn Y (swigΩ Ω))] [Nonempty (ValuesOn Y (swigΩ Ω))] [CountableOrCountablyGenerated (FixedValues M) (ValuesOn CC (swigΩ Ω))] : Kernel (FixedValues M × ValuesOn CC (swigΩ Ω)) (ValuesOn Y (swigΩ Ω)) := letI : IsFiniteKernel (M.obsCondPairKernel Y CC hY hCC) := by letI : IsMarkovKernel (M.obsCondPairKernel Y CC hY hCC) := by unfold obsCondPairKernel exact ProbabilityTheory.Kernel.IsMarkovKernel.map _ (prodMk (measurable_valuesProjection hCC) (measurable_valuesProjection hY)) infer_instance (M.obsCondPairKernel Y CC hY hCC).condKernel
theorem obsCondKernel_ae_eq_condDistrib reviewed
Causalean.SCM

At a fixed value s of the intervention-free (fixed) coordinates, provided the target coordinates Y are observed and the conditioning coordinates CC are observed, the jointly-measurable conditional kernel obsCondKernel built from Y and CC, evaluated at s, agrees almost everywhere — under the pushforward of the observational kernel onto CC — with the measure-theoretic conditional distribution of Y given CC under the observational kernel at s.

Formal statement
M :
SCM N Ω
Y CC :
hY :
Y ⊆ M.observed
hCC :
CC ⊆ M.observed
Nonempty (ValuesOn Y (swigΩ Ω))
s :
M.FixedValues
(fun c => M.obsCondKernel Y CC hY hCC (s, c))
=ᵐ[(M.obsKernel s).map (valuesProjection hCC)] condDistrib (valuesProjection hY) (valuesProjection hCC) (M.obsKernel s)
Proof (Lean source)
theorem obsCondKernel_ae_eq_condDistrib (M : SCM N Ω) (Y CC : Finset (SWIGNode N)) (hY : Y ⊆ M.observed) (hCC : CC ⊆ M.observed) [StandardBorelSpace (ValuesOn Y (swigΩ Ω))] [Nonempty (ValuesOn Y (swigΩ Ω))] [CountableOrCountablyGenerated (FixedValues M) (ValuesOn CC (swigΩ Ω))] (s : M.FixedValues) : (fun c => M.obsCondKernel Y CC hY hCC (s, c)) =ᵐ[(M.obsKernel s).map (valuesProjection hCC)] condDistrib (valuesProjection hY) (valuesProjection hCC) (M.obsKernel s) := by letI : IsMarkovKernel (M.obsCondPairKernel Y CC hY hCC) := by unfold obsCondPairKernel exact ProbabilityTheory.Kernel.IsMarkovKernel.map _ (prodMk (measurable_valuesProjection hCC) (measurable_valuesProjection hY)) -- `Kernel.condKernel_apply_eq_condKernel` gives a.e. equality with the -- measure-level `condKernel`; rewrite `Kernel.fst` of the pair kernel. have h := ProbabilityTheory.Kernel.condKernel_apply_eq_condKernel (M.obsCondPairKernel Y CC hY hCC) s -- `Kernel.fst (M.obsKernel.map (π_CC, π_Y)) s = (M.obsKernel s).map π_CC`. have hfst : ProbabilityTheory.Kernel.fst (M.obsCondPairKernel Y CC hY hCC) s = (M.obsKernel s).map (valuesProjection hCC) := by unfold obsCondPairKernel rw [ProbabilityTheory.Kernel.fst_map_prod _ (measurable_valuesProjection hY), ProbabilityTheory.Kernel.map_apply _ (measurable_valuesProjection hCC)] rw [hfst] at h -- `condDistrib π_Y π_CC μ = (μ.map (π_CC, π_Y)).condKernel = (obsCondPairKernel s).condKernel`. have hmap : (M.obsCondPairKernel Y CC hY hCC) s = map (fun ω => (valuesProjection hCC ω, valuesProjection hY ω)) (M.obsKernel s) := by unfold obsCondPairKernel exact ProbabilityTheory.Kernel.map_apply M.obsKernel ((measurable_valuesProjection hCC).prodMk (measurable_valuesProjection hY)) s have hcd : condDistrib (valuesProjection hY) (valuesProjection hCC) (M.obsKernel s) = (M.obsCondPairKernel Y CC hY hCC s).condKernel := by rw [condDistrib] congr 1 exact hmap.symm rw [hcd] exact h
6 supporting declarations (lemmas, instances)
Edge­Type 9 core · 7 supporting This file records functional restrictions that may be attached to directed edges in a causal graph, including nonparametric, monotone, linear, and parametric cases. ★ refines_nonparametric

Edge Type Hierarchy

This file records functional restrictions that may be attached to directed edges in a causal graph, including nonparametric, monotone, linear, and parametric cases. The hierarchy is separate from the probabilistic semantics of structural causal models and is used to track functional assumptions.

Main definitions

* MonotonicityKind records the four monotonicity directions. * EdgeType classifies an edge as nonparametric, monotone, linear, or parametrically restricted. * EdgeType.refinesBool and EdgeType.refines encode the assumption-refinement order in which every edge type refines the nonparametric top element. * EdgeTypeAssignment attaches an EdgeType label to each ordered edge slot of a directed acyclic graph, with EdgeTypeAssignment.allNonparametric as the default assignment.

inductive MonotonicityKind reviewed
Causalean

A monotonicity kind records whether an edge is nondecreasing, nonincreasing, strictly increasing, or strictly decreasing.

Definition (Lean source)
inductive MonotonicityKind | nonDecreasing | nonIncreasing | strictlyIncreasing | strictlyDecreasing deriving DecidableEq, Repr
Causalean.MonotonicityKind · Causalean/SCM/Model/EdgeType.lean:30
inductive EdgeType reviewed
Causalean

An edge type records whether an edge is nonparametric, monotonic, linear, or parametric.

Definition (Lean source)
inductive EdgeType | nonparametric | monotonic (kind : MonotonicityKind) | linear | parametric deriving DecidableEq, Repr
def refinesBool reviewed
Causalean.EdgeType

The Boolean refinement check decides whether one edge-type assumption is at least as specific as another.

Definition (Lean source)
def refinesBool : EdgeTypeEdgeTypeBool | _, .nonparametric => true | .monotonic k₁, .monotonic k₂ => k₁ == k₂ | .linear, .linear => true | .parametric, .parametric => true | _, _ => false
Causalean.EdgeType.refinesBool · Causalean/SCM/Model/EdgeType.lean:70 · uses EdgeType
def refines reviewed
Causalean.EdgeType

The refinement relation says that one edge-type assumption is at least as specific as another.

Definition (Lean source)
def refines (e₁ e₂ : EdgeType) : Prop := e₁.refinesBool e₂ = true
Causalean.EdgeType.refines · Causalean/SCM/Model/EdgeType.lean:85 · uses EdgeType
theorem refines_nonparametric reviewed
Causalean.EdgeType

Every edge-type functional-form assumption e refines the nonparametric assumption: nonparametric is the weakest assumption in the refinement order, so every other assumption is at least as specific as it.

Formal statement
e :
e.refines .nonparametric
Proof (Lean source)
theorem refines_nonparametric (e : EdgeType) : e.refines .nonparametric := by cases e <;> simp [refines, refinesBool]
Causalean.EdgeType.refines_nonparametric · Causalean/SCM/Model/EdgeType.lean:101 · uses EdgeType , refines
structure EdgeTypeAssignment reviewed
Causalean

An edge type assignment attaches a functional-assumption label to each directed edge of a graph.

Definition (Lean source)
G :
DAG V
The edge type of each directed edge `(u, v)`. Only meaningful when `G.edge u v` holds.
edgeType :
V → V → EdgeType
Causalean.EdgeTypeAssignment · Causalean/SCM/Model/EdgeType.lean:111 · uses DAG
def allNonparametric reviewed
Causalean.EdgeTypeAssignment

The default edge-type assignment labels every edge as nonparametric.

Definition (Lean source)
def allNonparametric (G : DAG V) : EdgeTypeAssignment G where edgeType := fun _ _ => .nonparametric
Causalean.EdgeTypeAssignment.allNonparametric · Causalean/SCM/Model/EdgeType.lean:130 · uses DAG , EdgeTypeAssignment
def incomingTypes reviewed
Causalean.EdgeTypeAssignment

The incoming edge-type set collects the labels on all edges pointing into a vertex.

Definition (Lean source)
def incomingTypes (a : EdgeTypeAssignment G) (v : V) : Finset EdgeType := (G.parents v).image (fun u => a.edgeType u v)
Causalean.EdgeTypeAssignment.incomingTypes · Causalean/SCM/Model/EdgeType.lean:134 · uses DAG , EdgeType , EdgeTypeAssignment
def isFullyNonparametric reviewed
Causalean.EdgeTypeAssignment

A graph is fully nonparametric under an assignment when every directed edge is labeled nonparametric.

Definition (Lean source)
def isFullyNonparametric (a : EdgeTypeAssignment G) : Prop := ∀ u v, G.edge u v → a.edgeType u v = .nonparametric
Causalean.EdgeTypeAssignment.isFullyNonparametric · Causalean/SCM/Model/EdgeType.lean:138 · uses DAG , EdgeTypeAssignment
7 supporting declarations (lemmas, instances)
Intervention­Set 5 core · 17 supporting This file exposes the public interface for set-valued interventions on structural causal models. ★ fixSet_empty_equiv★ fixSet_equiv_congr

Multi-Target Intervention Interface

This file exposes the public interface for set-valued interventions on structural causal models. It packages the monolithic intervention construction with preservation lemmas and the fixed-value projections used by the do-calculus kernel statements.

Main definitions and results

* SCM.fixSet is the public multi-target do-operation, implemented as the monolithic intervention SCM.fixMono. * SCM.fixSet_empty_equiv shows that intervening on the empty set is structurally equivalent to the original model. * SCM.fixSet_equiv_congr transports SCM.Equiv through a common intervention target set. * SCM.swigInterventionSet_insert_equiv relates iterative singleton insertion to the one-shot intervention on insert y X. * SCM.fixSetProj and SCM.fixSetZSlice provide the fixed-coordinate projections used by kernel-level do-calculus statements.

def fixSet reviewed
Causalean.SCM

Standard (Pearl) multi-target do — definitional alias for fixMono.

Definition (Lean source)
noncomputable def fixSet (M : SCM N Ω) (X : Finset N) (hObs : ∀ D ∈ X, SWIGNode.random D ∈ M.observed) (hFix : ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed) : SCM N Ω := M.fixMono X hObs hFix
theorem fixSet_empty_equiv reviewed
Causalean.SCM

For a structural causal model M, intervening on the empty set of nodes yields a structural causal model that is equivalent to M.

Formal statement
M :
SCM N Ω
SCM.Equiv (M.fixSet ∅ (fun _ hD => absurd hD (notMem_empty _)) (fun _ hD => absurd hD (notMem_empty _))) M
Proof (Lean source)
theorem fixSet_empty_equiv (M : SCM N Ω) : SCM.Equiv (M.fixSet ∅ (fun _ hD => absurd hD (notMem_empty _)) (fun _ hD => absurd hD (notMem_empty _))) M := by let hObs : ∀ D ∈ (∅ : Finset N), SWIGNode.random D ∈ M.observed := fun _ hD => absurd hD (notMem_empty _) let hFix : ∀ D ∈ (∅ : Finset N), SWIGNode.fixed D ∉ M.fixed := fun _ hD => absurd hD (notMem_empty _) -- (1) Graph-level equivalence (reused for the parent-set transport below). have hGraph : SWIGGraph.Equivalent (M.fixSet ∅ (fun _ hD => absurd hD (notMem_empty _)) (fun _ hD => absurd hD (notMem_empty _))).toSWIGGraph M.toSWIGGraph := by refine ⟨?_, ?_, rfl, rfl⟩ · simpa [SCM.fixSet, SCM.fixMono] using fixSet_empty_edge M.toSWIGGraph · simp refine ⟨hGraph, ?_, ?_, ?_⟩ · -- (2) Edge-type agreement: the `∃ D ∈ ∅, u = .fixed D` guard is vacuous. intro u v _ change (if h : ∃ D ∈ (∅ : Finset N), u = SWIGNode.fixed D then M.edgeTypes.edgeType (SWIGNode.random (choose h)) v else M.edgeTypes.edgeType u v) = M.edgeTypes.edgeType u v rw [dif_neg] rintro ⟨D, hD, _⟩ exact absurd hD (notMem_empty _) · -- (3) HEq structFun, by the standard parent-tuple HEq recipe: -- outer `hfunext` over `{v // v ∈ observed}` (defeq on both sides), -- inner `hfunext` over parent tuples (domains propositionally equal via -- `hGraph.parents_eq`), then reduce `HEq` to `Eq` and show the -- `fixMonoParentMap ∅` reindex is the pointwise identity. refine Function.hfunext rfl ?_ rintro v _ hv have hv_eq : v = _ := eq_of_heq hv subst hv_eq refine Function.hfunext ?_ ?_ · rw [SWIGGraph.Equivalent.parents_eq hGraph.1 v.val] · rintro ξ₁ ξ₂ hξ apply heq_of_eq have hP : (M.fixSet ∅ hObs hFix).toSWIGGraph.dag.parents v.val = M.toSWIGGraph.dag.parents v.val := SWIGGraph.Equivalent.parents_eq hGraph.1 v.val -- Pointwise equality of ξ₁ and ξ₂ at matching coordinates, obtained by -- reverting both and rewriting `hP` so they end up with the same type. have hξ_apply : ∀ (x : SWIGNode N) (h₁ : x ∈ (M.fixSet ∅ hObs hFix).toSWIGGraph.dag.parents v.val) (h₂ : x ∈ M.toSWIGGraph.dag.parents v.val), ξ₁ ⟨x, h₁⟩ = ξ₂ ⟨x, h₂⟩ := by revert ξ₁ ξ₂ hξ rw [hP] rintro ξ₁ ξ₂ hξ x h₁ h₂ have hξ_eq : ξ₁ = ξ₂ := eq_of_heq hξ subst hξ_eq rfl have h_obs_M : v.val ∈ M.observed := v.property -- Unfold `(M.fixSet ∅).structFun v ξ₁` via its `fixMono` definition. change M.structFun ⟨v.val, h_obs_M⟩ (fixMonoParentMap M.toSWIGGraph ∅ hObs hFix v.val ξ₁) = M.structFun v ξ₂ -- Peel `M.structFun ⟨v.val, _⟩`; reduce to tuple-level equality. congr 1 funext ⟨w, hw⟩ cases w with | random u => -- `u ∈ ∅` is vacuous, so `fixMonoParentMap` reads `ξ₁` at `.random u`. have hu : u ∉ (∅ : Finset N) := notMem_empty _ rw [fixMonoParentMap_apply_random_notMem M.toSWIGGraph ∅ hObs hFix v.val ξ₁ u hu hw] exact hξ_apply (SWIGNode.random u) _ hw | fixed d => -- `fixMonoParentMap` on a `.fixed` coordinate is `rfl`-identity. rw [fixMonoParentMap_apply_fixed M.toSWIGGraph ∅ hObs hFix v.val ξ₁ d hw] exact hξ_apply (SWIGNode.fixed d) _ hw · -- (4) HEq latentDist: inherited verbatim by `fixMono`. rfl
theorem fixSet_equiv_congr reviewed
Causalean.SCM

fixSet preserves SCM.Equiv. Given a structural equivalence h between M₁ and M₂ and a finite set of nodes X whose members are, in M₁, observed in their random coordinate and not already held fixed, intervening on X in both models produces two structurally equivalent post-intervention models — the analogous observed/not-fixed conditions transfer automatically to M₂ via h.

Formal statement
M₁ M₂ :
SCM N Ω
h :
SCM.Equiv M₁ M₂
X :
hObs₁ :
∀ D ∈ X, SWIGNode.random D ∈ M₁.observed
hFix₁ :
∀ D ∈ X, SWIGNode.fixed D ∉ M₁.fixed
let hObs₂ : ∀ D ∈ X, SWIGNode.random D ∈ M₂.observed := by intro D hD rw [← h.1.2.2.1] exact hObs₁ D hD let hFix₂ : ∀ D ∈ X, SWIGNode.fixed D ∉ M₂.fixed := by intro D hD hDfix apply hFix₁ D hD rw [h.1.2.1] exact hDfix SCM.Equiv (M₁.fixSet X hObs₁ hFix₁) (M₂.fixSet X hObs₂ hFix₂)
Proof (Lean source)
theorem fixSet_equiv_congr {M₁ M₂ : SCM N Ω} (h : SCM.Equiv M₁ M₂) (X : Finset N) (hObs₁ : ∀ D ∈ X, SWIGNode.random D ∈ M₁.observed) (hFix₁ : ∀ D ∈ X, SWIGNode.fixed D ∉ M₁.fixed) : let hObs₂ : ∀ D ∈ X, SWIGNode.random D ∈ M₂.observed := by intro D hD rw [← h.1.2.2.1] exact hObs₁ D hD let hFix₂ : ∀ D ∈ X, SWIGNode.fixed D ∉ M₂.fixed := by intro D hD hDfix apply hFix₁ D hD rw [h.1.2.1] exact hDfix SCM.Equiv (M₁.fixSet X hObs₁ hFix₁) (M₂.fixSet X hObs₂ hFix₂) := by obtain ⟨hGraph, hEdgeType, hSF, hLat⟩ := h have hObs₂ : ∀ D ∈ X, SWIGNode.random D ∈ M₂.observed := by intro D hD rw [← hGraph.2.2.1] exact hObs₁ D hD have hFix₂ : ∀ D ∈ X, SWIGNode.fixed D ∉ M₂.fixed := by intro D hD hDfix apply hFix₁ D hD rw [hGraph.2.1] exact hDfix change SCM.Equiv (M₁.fixSet X hObs₁ hFix₁) (M₂.fixSet X hObs₂ hFix₂) -- (1) Graph-level equivalence via `splitMono_congr`. have hGraph' : SWIGGraph.Equivalent (M₁.fixSet X hObs₁ hFix₁).toSWIGGraph (M₂.fixSet X hObs₂ hFix₂).toSWIGGraph := hGraph.splitMono_congr X hObs₁ hFix₁ hObs₂ hFix₂ refine ⟨hGraph', ?_, ?_, ?_⟩ · -- (2) Edge-type agreement. -- The `edgeType` of `fixSet` uses a `dite` on `∃ D ∈ X, u = .fixed D`. intro u v hu -- Both sides expand to the same dite structure; split on the guard. change (if hh : ∃ D ∈ X, u = SWIGNode.fixed D then M₁.edgeTypes.edgeType (SWIGNode.random (choose hh)) v else M₁.edgeTypes.edgeType u v) = (if hh : ∃ D ∈ X, u = SWIGNode.fixed D then M₂.edgeTypes.edgeType (SWIGNode.random (choose hh)) v else M₂.edgeTypes.edgeType u v) split_ifs with hh · -- u = .fixed D branch: underlying edge is M.dag.edge (.random D) v. -- Retrieve the underlying M₁ and M₂ edges from splitMonoEdgeRel. have hD := Classical.choose_spec hh -- hu : (M₁.fixSet X).dag.edge u v, which is splitMonoEdgeRel M₁.dag.edge X u v. -- With u = .fixed D and D ∈ X, this simplifies to M₁.dag.edge (.random D) v. have hu₁ : M₁.dag.edge (SWIGNode.random (choose hh)) v := by have huEq : u = SWIGNode.fixed (choose hh) := hD.2 rw [huEq] at hu simp only [SCM.fixSet, SCM.fixMono, SWIGGraph.splitMono, SWIGGraph.splitMonoDAG, SWIGGraph.splitMonoEdgeRel, if_pos hD.1] at hu exact hu exact hEdgeType _ _ hu₁ · -- else branch: direct M.dag.edge u v. have hu₁ : M₁.dag.edge u v := by simp only [SCM.fixSet, SCM.fixMono, SWIGGraph.splitMono, SWIGGraph.splitMonoDAG, SWIGGraph.splitMonoEdgeRel] at hu cases u with | random r => by_cases hr : r ∈ X · simp [hr] at hu · simpa [hr] using hu | fixed d => by_cases hd : d ∈ X · exfalso; apply hh; exact ⟨d, hd, rfl⟩ · simpa [hd] using hu exact hEdgeType _ _ hu₁ · -- (3) HEq structFun. -- Both `(M_i.fixSet X).structFun v' ξ` unfold to -- `M_i.structFun ⟨v'.val, _⟩ (fixMonoParentMap M_i.toSWIGGraph X _ _ v'.val ξ)`. -- Outer hfunext: observed domain. -- `(M_i.fixSet X).observed = M_i.observed` by rfl, and `M₁.observed = M₂.observed` by hGraph. have hObsSplit : (M₁.fixSet X hObs₁ hFix₁).observed = (M₂.fixSet X hObs₂ hFix₂).observed := hGraph'.2.2.1 refine Function.hfunext (by rw [hObsSplit]) ?_ rintro v₁ v₂ hv have hv_eq : v₁.val = v₂.val := by exact (Subtype.heq_iff_coe_eq (by intro x; rw [hObsSplit])).mp hv -- Inner hfunext: parent-tuple domain. -- Observed sets equal, so split parent sets are equal. have hPar : (M₁.fixSet X hObs₁ hFix₁).dag.parents v₁.val = (M₂.fixSet X hObs₂ hFix₂).dag.parents v₂.val := by rw [hv_eq]; exact SWIGGraph.Equivalent.parents_eq hGraph'.1 v₂.val refine Function.hfunext ?_ ?_ · rw [hPar] · rintro ξ₁ ξ₂ hξ -- Goal: (M₁.fixSet X).structFun v₁ ξ₁ ≍ (M₂.fixSet X).structFun v₂ ξ₂ -- Both sides unfold to M_i.structFun ⟨v_i.val, _⟩ (fixMonoParentMap ...). have hObsEq : M₁.observed = M₂.observed := hGraph.2.2.1 have hv_obs₁ : v₁.val ∈ M₁.observed := v₁.property have hv_obs₂' : v₂.val ∈ M₂.observed := v₂.property -- Parent sets. have hPar₀ : M₁.dag.parents v₁.val = M₂.dag.parents v₂.val := by rw [SWIGGraph.Equivalent.parents_eq hGraph.1 v₁.val, hv_eq] -- Split parent set equality (via hGraph' and hv_eq). have hSplitPar : (M₁.fixSet X hObs₁ hFix₁).dag.parents v₁.val = (M₂.fixSet X hObs₂ hFix₂).dag.parents v₂.val := by rw [SWIGGraph.Equivalent.parents_eq hGraph'.1 v₁.val, hv_eq] -- hξ_apply: pointwise equality from hξ : ξ₁ ≍ ξ₂ over propEq domain types. -- Use dcongr_heq since ξ₁, ξ₂ are dependent pi types. have hξ_apply : ∀ (x : SWIGNode N) (h₁ : x ∈ (M₁.fixSet X hObs₁ hFix₁).dag.parents v₁.val) (h₂ : x ∈ (M₂.fixSet X hObs₂ hFix₂).dag.parents v₂.val), ξ₁ ⟨x, h₁⟩ = ξ₂ ⟨x, h₂⟩ := by intro x h₁ h₂ apply eq_of_heq apply dcongr_heq · -- ⟨x, h₁⟩ ≍ ⟨x, h₂⟩ exact (Subtype.heq_iff_coe_eq (by intro a; rw [hSplitPar])).mpr rfl · -- type: swigΩ Ω t₁.val = swigΩ Ω t₂.val given t₁ ≍ t₂ intro t₁ t₂ ht have : t₁.val = t₂.val := (Subtype.heq_iff_coe_eq (by intro a; rw [hSplitPar])).mp ht simp [this] · -- function HEq intro _ _; exact hξ -- Step 1: HEq of the structFun applications at v₁ vs v₂. have hvHeq : (⟨v₁.val, hv_obs₁⟩ : {v // v ∈ M₁.observed}) ≍ (⟨v₂.val, hv_obs₂'⟩ : {v // v ∈ M₂.observed}) := by apply (Subtype.heq_iff_coe_eq (by intro x; rw [hObsEq])).mpr exact hv_eq have hApp : HEq (M₁.structFun ⟨v₁.val, hv_obs₁⟩) (M₂.structFun ⟨v₂.val, hv_obs₂'⟩) := by apply dcongr_heq hvHeq · intro t₁ t₂ ht have hval : t₁.val = t₂.val := (Subtype.heq_iff_coe_eq (by intro x; rw [hObsEq])).mp ht rw [hval, SWIGGraph.Equivalent.parents_eq hGraph.1 t₂.val] · intro _ _; exact hSF -- Step 2: HEq of the fixMonoParentMap outputs. -- Subtype domain equality for fixMonoParentMap. have hPar₀_sub : ({w // w ∈ M₁.dag.parents v₁.val} : Type _) = {w // w ∈ M₂.dag.parents v₂.val} := congrArg (fun s : Finset (SWIGNode N) => {w : SWIGNode N // w ∈ s}) hPar₀ have hξHeq : HEq (fixMonoParentMap M₁.toSWIGGraph X hObs₁ hFix₁ v₁.val ξ₁) (fixMonoParentMap M₂.toSWIGGraph X hObs₂ hFix₂ v₂.val ξ₂) := by apply Function.hfunext hPar₀_sub rintro ⟨w₁val, w₁prop⟩ ⟨w₂val, w₂prop⟩ hw have hwval : w₁val = w₂val := (Subtype.heq_iff_coe_eq (by intro x; rw [hPar₀])).mp hw subst hwval apply heq_of_eq cases w₁val with | random u => by_cases hu : u ∈ X · rw [fixMonoParentMap_apply_random M₁.toSWIGGraph X hObs₁ hFix₁ v₁.val u hu ξ₁ w₁prop, fixMonoParentMap_apply_random M₂.toSWIGGraph X hObs₂ hFix₂ v₂.val u hu ξ₂ w₂prop] exact hξ_apply (SWIGNode.fixed u) _ _ · rw [fixMonoParentMap_apply_random_notMem M₁.toSWIGGraph X hObs₁ hFix₁ v₁.val ξ₁ u hu w₁prop, fixMonoParentMap_apply_random_notMem M₂.toSWIGGraph X hObs₂ hFix₂ v₂.val ξ₂ u hu w₂prop] exact hξ_apply (SWIGNode.random u) _ _ | fixed d => rw [fixMonoParentMap_apply_fixed M₁.toSWIGGraph X hObs₁ hFix₁ v₁.val ξ₁ d w₁prop, fixMonoParentMap_apply_fixed M₂.toSWIGGraph X hObs₂ hFix₂ v₂.val ξ₂ d w₂prop] exact hξ_apply (SWIGNode.fixed d) _ _ -- Combine: goal unfolds definitionally to M_i.structFun at v_i applied to fixMonoParentMap. -- Use dcongr_heq: hξHeq as the argument HEq, hApp as the function HEq. -- Codomain: swigΩ Ω v₁.val = swigΩ Ω v₂.val follows from hv_eq. change HEq (M₁.structFun ⟨v₁.val, hv_obs₁⟩ (fixMonoParentMap M₁.toSWIGGraph X hObs₁ hFix₁ v₁.val ξ₁)) (M₂.structFun ⟨v₂.val, hv_obs₂'⟩ (fixMonoParentMap M₂.toSWIGGraph X hObs₂ hFix₂ v₂.val ξ₂)) exact dcongr_heq hξHeq (fun _ _ _ => by rw [hv_eq]) (fun _ _ => hApp) · -- (4) HEq latentDist: fixMono inherits latentDist by rfl; unobserved sets equal. -- Both `(M_i.fixSet X).latentDist = M_i.latentDist` by rfl (same unobserved Finset). -- `hLat : HEq M₁.latentDist M₂.latentDist`. exact hLat
def fixSetProj reviewed
Causalean.SCM

Canonical projection of post-intervention fixed assignments onto the original fixed coordinates.

Definition (Lean source)
noncomputable def fixSetProj (M : SCM N Ω) (X : Finset N) (hObs : ∀ D ∈ X, SWIGNode.random D ∈ M.observed) (hFix : ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed) : (M.fixSet X hObs hFix).FixedValues → M.FixedValues := valuesProjection (fixSet_fixed_subset M X hObs hFix)
def fixSetZSlice reviewed
Causalean.SCM

Z-fixed-slice extractor. Reads a FixedValues assignment of the double-intervention ((M.fixSet X).fixSet Z) on the Z.image .fixed slice, producing a ValuesOn (Z.image SWIGNode.fixed) (swigΩ Ω). General-purpose extractor for the inner do(z) coordinate slice of a two-layer intervention.

Definition (Lean source)
noncomputable def fixSetZSlice (M : SCM N Ω) (X Z : Finset N) (hX_obs : ∀ D ∈ X, SWIGNode.random D ∈ M.observed) (hX_fixed : ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed) (hZ_obs : ∀ D ∈ Z, SWIGNode.random D ∈ (M.fixSet X hX_obs hX_fixed).observed) (hZ_fixed : ∀ D ∈ Z, SWIGNode.fixed D ∉ (M.fixSet X hX_obs hX_fixed).fixed) : ((M.fixSet X hX_obs hX_fixed).fixSet Z hZ_obs hZ_fixed).FixedValuesValuesOn (Z.image SWIGNode.fixed) (swigΩ Ω) := valuesProjection (fixSet_image_fixed_subset (M.fixSet X hX_obs hX_fixed) Z hZ_obs hZ_fixed)
17 supporting declarations (lemmas, instances)
  • fixSet_observed lemma — fixSet preserves observed.
    M :
    SCM N Ω
    X :
    hObs :
    ∀ D ∈ X, SWIGNode.random D ∈ M.observed
    hFix :
    ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed
    (M.fixSet X hObs hFix).observed = M.observed
    Proof (Lean source)
    @[simp] lemma fixSet_observed (M : SCM N Ω) (X : Finset N) (hObs : ∀ D ∈ X, SWIGNode.random D ∈ M.observed) (hFix : ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed) : (M.fixSet X hObs hFix).observed = M.observed := rfl
    Causalean.SCM.fixSet_observed · Causalean/SCM/Model/InterventionSet.lean:52
  • fixSet_unobserved lemma — fixSet preserves unobserved.
    M :
    SCM N Ω
    X :
    hObs :
    ∀ D ∈ X, SWIGNode.random D ∈ M.observed
    hFix :
    ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed
    (M.fixSet X hObs hFix).unobserved = M.unobserved
    Proof (Lean source)
    @[simp] lemma fixSet_unobserved (M : SCM N Ω) (X : Finset N) (hObs : ∀ D ∈ X, SWIGNode.random D ∈ M.observed) (hFix : ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed) : (M.fixSet X hObs hFix).unobserved = M.unobserved := rfl
    Causalean.SCM.fixSet_unobserved · Causalean/SCM/Model/InterventionSet.lean:58
  • fixSet_fixed lemma — fixSet enlarges fixed by exactly X.image SWIGNode.fixed.
    M :
    SCM N Ω
    X :
    hObs :
    ∀ D ∈ X, SWIGNode.random D ∈ M.observed
    hFix :
    ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed
    (M.fixSet X hObs hFix).fixed = M.fixed ∪ X.image SWIGNode.fixed
    Proof (Lean source)
    @[simp] lemma fixSet_fixed (M : SCM N Ω) (X : Finset N) (hObs : ∀ D ∈ X, SWIGNode.random D ∈ M.observed) (hFix : ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed) : (M.fixSet X hObs hFix).fixed = M.fixed ∪ X.image SWIGNode.fixed := rfl
    Causalean.SCM.fixSet_fixed · Causalean/SCM/Model/InterventionSet.lean:64
  • fixSet_latentDist lemma — fixSet inherits latentDist verbatim. Both sides have the same type ((M.fixSet X _ _).unobserved = M.unobserved by rfl) and equal body.
    M :
    SCM N Ω
    X :
    hObs :
    ∀ D ∈ X, SWIGNode.random D ∈ M.observed
    hFix :
    ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed
    u :
    {u // u ∈ (M.fixSet X hObs hFix).unobserved}
    (M.fixSet X hObs hFix).latentDist u = M.latentDist u
    Proof (Lean source)
    @[simp] lemma fixSet_latentDist (M : SCM N Ω) (X : Finset N) (hObs : ∀ D ∈ X, SWIGNode.random D ∈ M.observed) (hFix : ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed) (u : {u // u ∈ (M.fixSet X hObs hFix).unobserved}) : (M.fixSet X hObs hFix).latentDist u = M.latentDist u := rfl
    Causalean.SCM.fixSet_latentDist · Causalean/SCM/Model/InterventionSet.lean:70
  • fixSet_fixed_subset lemma — fixSet only enlarges the fixed set.
    M :
    SCM N Ω
    X :
    hObs :
    ∀ D ∈ X, SWIGNode.random D ∈ M.observed
    hFix :
    ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed
    M.fixed ⊆ (M.fixSet X hObs hFix).fixed
    Proof (Lean source)
    lemma fixSet_fixed_subset (M : SCM N Ω) (X : Finset N) (hObs : ∀ D ∈ X, SWIGNode.random D ∈ M.observed) (hFix : ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed) : M.fixed ⊆ (M.fixSet X hObs hFix).fixed := fixMono_fixed_subset M.toSWIGGraph X hObs hFix
    Causalean.SCM.fixSet_fixed_subset · Causalean/SCM/Model/InterventionSet.lean:78
  • fixSet_image_fixed_subset lemma — X.image SWIGNode.fixed ⊆ (M.fixSet X _ _).fixed.
    M :
    SCM N Ω
    X :
    hObs :
    ∀ D ∈ X, SWIGNode.random D ∈ M.observed
    hFix :
    ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed
    X.image SWIGNode.fixed ⊆ (M.fixSet X hObs hFix).fixed
    Proof (Lean source)
    lemma fixSet_image_fixed_subset (M : SCM N Ω) (X : Finset N) (hObs : ∀ D ∈ X, SWIGNode.random D ∈ M.observed) (hFix : ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed) : X.image SWIGNode.fixed ⊆ (M.fixSet X hObs hFix).fixed := fixMono_image_fixed_subset M.toSWIGGraph X hObs hFix
    Causalean.SCM.fixSet_image_fixed_subset · Causalean/SCM/Model/InterventionSet.lean:85
  • fixed_mem_fixSet lemma — Every SWIGNode.fixed D with D ∈ X is in the fixed set of fixSet X.
    M :
    SCM N Ω
    X :
    hObs :
    ∀ D ∈ X, SWIGNode.random D ∈ M.observed
    hFix :
    ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed
    D :
    N
    hD :
    D ∈ X
    SWIGNode.fixed D ∈ (M.fixSet X hObs hFix).fixed
    Proof (Lean source)
    lemma fixed_mem_fixSet (M : SCM N Ω) (X : Finset N) (hObs : ∀ D ∈ X, SWIGNode.random D ∈ M.observed) (hFix : ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed) {D : N} (hD : D ∈ X) : SWIGNode.fixed D ∈ (M.fixSet X hObs hFix).fixed := fixSet_image_fixed_subset M X hObs hFix (Finset.mem_image.mpr ⟨D, hD, rfl⟩)
    Causalean.SCM.fixed_mem_fixSet · Causalean/SCM/Model/InterventionSet.lean:92
  • fixSet_parents_eq_of_no_fixed_parent lemma — SCM-level parent-set coincidence at non-.fixed-targeted vertices.
    M :
    SCM N Ω
    X :
    hObs :
    ∀ D ∈ X, SWIGNode.random D ∈ M.observed
    hFix :
    ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed
    v :
    hNoFP :
    ∀ D ∈ X, SWIGNode.fixed D ∉ (M.fixSet X hObs hFix).dag.parents v
    (M.fixSet X hObs hFix).dag.parents v = M.dag.parents v
    Proof (Lean source)
    lemma fixSet_parents_eq_of_no_fixed_parent (M : SCM N Ω) (X : Finset N) (hObs : ∀ D ∈ X, SWIGNode.random D ∈ M.observed) (hFix : ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed) {v : SWIGNode N} (hNoFP : ∀ D ∈ X, SWIGNode.fixed D ∉ (M.fixSet X hObs hFix).dag.parents v) : (M.fixSet X hObs hFix).dag.parents v = M.dag.parents v := fixMono_parents_eq_of_no_fixed_parent M X hObs hFix hNoFP
    Causalean.SCM.fixSet_parents_eq_of_no_fixed_parent · Causalean/SCM/Model/InterventionSet.lean:101
  • splitMono_empty_parents lemma — Splitting no variables leaves every parent set unchanged.
    G :
    v :
    (G.splitMono ∅ (fun _ hD => absurd hD (notMem_empty _)) (fun _ hD => absurd hD (notMem_empty _))).dag.parents v
    = G.dag.parents v
    Proof (Lean source)
    lemma splitMono_empty_parents (G : SWIGGraph N) (v : SWIGNode N) : (G.splitMono ∅ (fun _ hD => absurd hD (notMem_empty _)) (fun _ hD => absurd hD (notMem_empty _))).dag.parents v = G.dag.parents v := splitMono_parents_eq_of_no_fixed_parent G ∅ (fun _ hD => absurd hD (notMem_empty _)) (fun _ hD => absurd hD (notMem_empty _)) v (fun _ hD => absurd hD (notMem_empty _))
    Causalean.SWIGGraph.splitMono_empty_parents · Causalean/SCM/Model/InterventionSet.lean:123
  • fixSet_empty_parents lemma — Parent set of fixSet ∅ coincides with the base.
    M :
    SCM N Ω
    v :
    (M.fixSet ∅ (fun _ hD => absurd hD (notMem_empty _)) (fun _ hD => absurd hD (notMem_empty _))).dag.parents v
    = M.dag.parents v
    Proof (Lean source)
    lemma fixSet_empty_parents (M : SCM N Ω) (v : SWIGNode N) : (M.fixSet ∅ (fun _ hD => absurd hD (notMem_empty _)) (fun _ hD => absurd hD (notMem_empty _))).dag.parents v = M.dag.parents v := SWIGGraph.splitMono_empty_parents M.toSWIGGraph v
    Causalean.SCM.fixSet_empty_parents · Causalean/SCM/Model/InterventionSet.lean:143
  • fixSet_empty_edge lemma — Edges of fixSet ∅ coincide with the base.
    G :
    u v :
    (G.splitMono ∅ (fun _ hD => absurd hD (notMem_empty _)) (fun _ hD => absurd hD (notMem_empty _))).dag.edge u v
    ↔ G.dag.edge u v
    Proof (Lean source)
    lemma fixSet_empty_edge (G : SWIGGraph N) (u v : SWIGNode N) : (G.splitMono ∅ (fun _ hD => absurd hD (notMem_empty _)) (fun _ hD => absurd hD (notMem_empty _))).dag.edge u v ↔ G.dag.edge u v := by -- `splitMono ∅ .dag.edge = splitMonoEdgeRel G.dag.edge ∅`, which reduces to -- `G.dag.edge` by simp. cases u with | random u => simp [SWIGGraph.splitMono, SWIGGraph.splitMonoDAG, SWIGGraph.splitMonoEdgeRel] | fixed d => simp [SWIGGraph.splitMono, SWIGGraph.splitMonoDAG, SWIGGraph.splitMonoEdgeRel]
    Causalean.SCM.fixSet_empty_edge · Causalean/SCM/Model/InterventionSet.lean:157
  • fixSet_edgeType_random_eq lemma — After intervening on a set of variables, the label of every edge leaving a random-coordinate node is unchanged from the original causal model.
    M :
    SCM N Ω
    X :
    hObs :
    ∀ D ∈ X, SWIGNode.random D ∈ M.observed
    hFix :
    ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed
    u :
    N
    v :
    (M.fixSet X hObs hFix).edgeTypes.edgeType (SWIGNode.random u) v
    = M.edgeTypes.edgeType (SWIGNode.random u) v
    Proof (Lean source)
    lemma fixSet_edgeType_random_eq (M : SCM N Ω) (X : Finset N) (hObs : ∀ D ∈ X, SWIGNode.random D ∈ M.observed) (hFix : ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed) (u : N) (v : SWIGNode N) : (M.fixSet X hObs hFix).edgeTypes.edgeType (SWIGNode.random u) v = M.edgeTypes.edgeType (SWIGNode.random u) v := by change (if h : ∃ D ∈ X, SWIGNode.random u = SWIGNode.fixed D then M.edgeTypes.edgeType (SWIGNode.random (choose h)) v else M.edgeTypes.edgeType (SWIGNode.random u) v) = M.edgeTypes.edgeType (SWIGNode.random u) v rw [dif_neg] rintro ⟨D, _hD, hEq⟩ cases hEq
    Causalean.SCM.fixSet_edgeType_random_eq · Causalean/SCM/Model/InterventionSet.lean:448
  • fixSet_edgeType_fixed_mem_eq lemma — For a variable included in the intervention set, the edge label from its fixed-coordinate node equals the original label from its random-coordinate node.
    M :
    SCM N Ω
    X :
    hObs :
    ∀ D ∈ X, SWIGNode.random D ∈ M.observed
    hFix :
    ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed
    d :
    N
    v :
    hd :
    d ∈ X
    (M.fixSet X hObs hFix).edgeTypes.edgeType (SWIGNode.fixed d) v
    = M.edgeTypes.edgeType (SWIGNode.random d) v
    Proof (Lean source)
    lemma fixSet_edgeType_fixed_mem_eq (M : SCM N Ω) (X : Finset N) (hObs : ∀ D ∈ X, SWIGNode.random D ∈ M.observed) (hFix : ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed) (d : N) (v : SWIGNode N) (hd : d ∈ X) : (M.fixSet X hObs hFix).edgeTypes.edgeType (SWIGNode.fixed d) v = M.edgeTypes.edgeType (SWIGNode.random d) v := by change (if h : ∃ D ∈ X, SWIGNode.fixed d = SWIGNode.fixed D then M.edgeTypes.edgeType (SWIGNode.random (choose h)) v else M.edgeTypes.edgeType (SWIGNode.fixed d) v) = M.edgeTypes.edgeType (SWIGNode.random d) v let hmem : ∃ D ∈ X, SWIGNode.fixed d = SWIGNode.fixed D := ⟨d, hd, rfl⟩ rw [dif_pos hmem] have hchoose : choose hmem = d := by exact (SWIGNode.fixed.inj (Classical.choose_spec hmem).2).symm rw [hchoose]
    Causalean.SCM.fixSet_edgeType_fixed_mem_eq · Causalean/SCM/Model/InterventionSet.lean:465
  • fixSet_edgeType_fixed_notMem_eq lemma — For a variable outside the intervention set, the edge label from its fixed-coordinate node is unchanged by the intervention.
    M :
    SCM N Ω
    X :
    hObs :
    ∀ D ∈ X, SWIGNode.random D ∈ M.observed
    hFix :
    ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed
    d :
    N
    v :
    hd :
    d ∉ X
    (M.fixSet X hObs hFix).edgeTypes.edgeType (SWIGNode.fixed d) v
    = M.edgeTypes.edgeType (SWIGNode.fixed d) v
    Proof (Lean source)
    lemma fixSet_edgeType_fixed_notMem_eq (M : SCM N Ω) (X : Finset N) (hObs : ∀ D ∈ X, SWIGNode.random D ∈ M.observed) (hFix : ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed) (d : N) (v : SWIGNode N) (hd : d ∉ X) : (M.fixSet X hObs hFix).edgeTypes.edgeType (SWIGNode.fixed d) v = M.edgeTypes.edgeType (SWIGNode.fixed d) v := by change (if h : ∃ D ∈ X, SWIGNode.fixed d = SWIGNode.fixed D then M.edgeTypes.edgeType (SWIGNode.random (choose h)) v else M.edgeTypes.edgeType (SWIGNode.fixed d) v) = M.edgeTypes.edgeType (SWIGNode.fixed d) v rw [dif_neg] rintro ⟨D, hD, hEq⟩ exact hd ((SWIGNode.fixed.inj hEq) ▸ hD)
    Causalean.SCM.fixSet_edgeType_fixed_notMem_eq · Causalean/SCM/Model/InterventionSet.lean:484
  • swigInterventionSet_insert_equiv theorem — Insert form of monolithic intervention composition.
    M :
    SCM N Ω
    X :
    y :
    N
    hyX :
    y ∉ X
    hInsert_obs :
    ∀ D ∈ insert y X, SWIGNode.random D ∈ M.observed
    hInsert_fixed :
    ∀ D ∈ insert y X, SWIGNode.fixed D ∉ M.fixed
    let hX_obs : ∀ D ∈ X, SWIGNode.random D ∈ M.observed := by intro D hD exact hInsert_obs D (mem_insert_of_mem hD) let hX_fixed : ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed := by intro D hD exact hInsert_fixed D (mem_insert_of_mem hD) let hy_obs : ∀ D ∈ ({y} : Finset N), SWIGNode.random D ∈ (M.fixSet X hX_obs hX_fixed).observed := by intro D hD have hDy : D
    = y := Finset.mem_singleton.mp hD subst D simpa only [fixSet_observed] using hInsert_obs y (mem_insert_self y X) let hy_fixed : ∀ D ∈ ({y} : Finset N), SWIGNode.fixed D ∉ (M.fixSet X hX_obs hX_fixed).fixed := by intro D hD hDfixed have hDy : D = y := Finset.mem_singleton.mp hD subst D rw [fixSet_fixed] at hDfixed rcases Finset.mem_union.mp hDfixed with hyfixed | hyimage · exact hInsert_fixed y (mem_insert_self y X) hyfixed · rcases Finset.mem_image.mp hyimage with ⟨z, hz, hzy⟩ exact hyX ((SWIGNode.fixed.inj hzy).symm ▸ hz) SCM.Equiv ((M.fixSet X hX_obs hX_fixed).fixSet ({y} : Finset N) hy_obs hy_fixed) (M.fixSet (insert y X) hInsert_obs hInsert_fixed)
    Proof (Lean source)
    theorem swigInterventionSet_insert_equiv (M : SCM N Ω) (X : Finset N) (y : N) (hyX : y ∉ X) (hInsert_obs : ∀ D ∈ insert y X, SWIGNode.random D ∈ M.observed) (hInsert_fixed : ∀ D ∈ insert y X, SWIGNode.fixed D ∉ M.fixed) : let hX_obs : ∀ D ∈ X, SWIGNode.random D ∈ M.observed := by intro D hD exact hInsert_obs D (mem_insert_of_mem hD) let hX_fixed : ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed := by intro D hD exact hInsert_fixed D (mem_insert_of_mem hD) let hy_obs : ∀ D ∈ ({y} : Finset N), SWIGNode.random D ∈ (M.fixSet X hX_obs hX_fixed).observed := by intro D hD have hDy : D = y := Finset.mem_singleton.mp hD subst D simpa only [fixSet_observed] using hInsert_obs y (mem_insert_self y X) let hy_fixed : ∀ D ∈ ({y} : Finset N), SWIGNode.fixed D ∉ (M.fixSet X hX_obs hX_fixed).fixed := by intro D hD hDfixed have hDy : D = y := Finset.mem_singleton.mp hD subst D rw [fixSet_fixed] at hDfixed rcases Finset.mem_union.mp hDfixed with hyfixed | hyimage · exact hInsert_fixed y (mem_insert_self y X) hyfixed · rcases Finset.mem_image.mp hyimage with ⟨z, hz, hzy⟩ exact hyX ((SWIGNode.fixed.inj hzy).symm ▸ hz) SCM.Equiv ((M.fixSet X hX_obs hX_fixed).fixSet ({y} : Finset N) hy_obs hy_fixed) (M.fixSet (insert y X) hInsert_obs hInsert_fixed) := by dsimp apply swigInterventionSet_insert_equiv_aux M X y hyX (fun D hD => hInsert_obs D (mem_insert_of_mem hD)) (fun D hD => hInsert_fixed D (mem_insert_of_mem hD)) (hInsert_obs := hInsert_obs) (hInsert_fixed := hInsert_fixed)
    Causalean.SCM.swigInterventionSet_insert_equiv · Causalean/SCM/Model/InterventionSet.lean:681
  • measurable_fixSetProj theorem — fixSetProj is measurable.
    M :
    SCM N Ω
    X :
    hObs :
    ∀ D ∈ X, SWIGNode.random D ∈ M.observed
    hFix :
    ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed
    Measurable (M.fixSetProj X hObs hFix)
    Proof (Lean source)
    theorem measurable_fixSetProj (M : SCM N Ω) (X : Finset N) (hObs : ∀ D ∈ X, SWIGNode.random D ∈ M.observed) (hFix : ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed) : Measurable (M.fixSetProj X hObs hFix) := measurable_valuesProjection _
    Causalean.SCM.measurable_fixSetProj · Causalean/SCM/Model/InterventionSet.lean:740
  • measurable_fixSetZSlice theorem — fixSetZSlice is measurable.
    M :
    SCM N Ω
    X Z :
    hX_obs :
    ∀ D ∈ X, SWIGNode.random D ∈ M.observed
    hX_fixed :
    ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed
    hZ_obs :
    ∀ D ∈ Z, SWIGNode.random D ∈ (M.fixSet X hX_obs hX_fixed).observed
    hZ_fixed :
    ∀ D ∈ Z, SWIGNode.fixed D ∉ (M.fixSet X hX_obs hX_fixed).fixed
    Measurable (M.fixSetZSlice X Z hX_obs hX_fixed hZ_obs hZ_fixed)
    Proof (Lean source)
    theorem measurable_fixSetZSlice (M : SCM N Ω) (X Z : Finset N) (hX_obs : ∀ D ∈ X, SWIGNode.random D ∈ M.observed) (hX_fixed : ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed) (hZ_obs : ∀ D ∈ Z, SWIGNode.random D ∈ (M.fixSet X hX_obs hX_fixed).observed) (hZ_fixed : ∀ D ∈ Z, SWIGNode.fixed D ∉ (M.fixSet X hX_obs hX_fixed).fixed) : Measurable (M.fixSetZSlice X Z hX_obs hX_fixed hZ_obs hZ_fixed) := measurable_valuesProjection _
    Causalean.SCM.measurable_fixSetZSlice · Causalean/SCM/Model/InterventionSet.lean:769
Eval­Override­C 4 core · 12 supporting This file defines and analyzes a structural-model evaluation rule that holds a chosen set of observed variables fixed while evaluating the rest of the model, supporting conditional kernels aligned with single-world inter ★ evalMap_overrideC_at_self

This file defines and analyzes a structural-model evaluation rule that holds a chosen set of observed variables fixed while evaluating the rest of the model, supporting conditional kernels aligned with single-world intervention graphs.

The override evaluation short-circuits the chosen observed block and is proved measurable jointly in the fixed values, override values, and latent realization.

Main definitions and results

* SCM.parentMapOverride and SCM.evalObservedAuxOverride are the overridden parent-tuple assembly and topological-order evaluator. * SCM.evalMap_overrideC returns values on a target set while short-circuiting every coordinate in the override block. * SCM.evalMap_overrideC_apply_of_mem_C and SCM.evalMap_overrideC_apply_of_not_mem_C are the two public unfold rules for target nodes inside and outside the override block. * SCM.evalMap_overrideC_at_self shows that overriding by the model's own evaluated values leaves the target evaluation unchanged. * SCM.measurable_evalMap_overrideC proves joint measurability of the override evaluation.

def parentMapOverride reviewed
Causalean.SCM

The override parent-value tuple reads latent, fixed, overridden, or recursively evaluated observed parent values as appropriate.

Definition (Lean source)
noncomputable def parentMapOverride (M : SCM N Ω) {C : Finset (SWIGNode N)} (s : FixedValues M) (c : ValuesOn C (swigΩ Ω)) (ℓ : LatentValues M) {n : ℕ} (hn : n < M.observed.card) (prev : ∀ m : ℕ, m < n → ∀ hm : m < M.observed.card, swigΩ Ω (M.observedAt ⟨m, hm⟩).val) (w : {w // w ∈ M.dag.parents (M.observedAt ⟨n, hn⟩).val}) : swigΩ Ω w.val := if huo : w.val ∈ M.unobserved then ℓ ⟨w.val, huo⟩ else if hfix : w.val ∈ M.fixed then s ⟨w.val, hfix⟩ else have hedge : M.dag.edge w.val (M.observedAt ⟨n, hn⟩).val := M.dag.mem_parents.mp w.property have hcls := (M.dag_edges_classified w.val (M.observedAt ⟨n, hn⟩).val hedge).1 have hobs : w.val ∈ M.observed := (Finset.mem_union.mp hcls).elim (fun hfo => (Finset.mem_union.mp hfo).elim (fun hf => absurd hf hfix) id) (fun huo' => absurd huo' huo) if hc : w.val ∈ C then c ⟨w.val, hc⟩ else (M.observedAt_observedIndex ⟨w.val, hobs⟩) ▸ prev (M.observedIndex ⟨w.val, hobs⟩).val (M.observed_parent_index_lt hn hedge hobs) (M.observedIndex ⟨w.val, hobs⟩).isLt
def evalObservedAuxOverride reviewed
Causalean.SCM

The override auxiliary evaluator computes each observed node in topological order, short-circuiting nodes in the override block.

Definition (Lean source)
noncomputable def evalObservedAuxOverride (M : SCM N Ω) {C : Finset (SWIGNode N)} (hC : C ⊆ M.observed) (s : FixedValues M) (c : ValuesOn C (swigΩ Ω)) (ℓ : LatentValues M) (n : ℕ) : ∀ hn : n < M.observed.card, swigΩ Ω (M.observedAt ⟨n, hn⟩).val := Nat.strongRec (motive := fun k => ∀ hk : k < M.observed.card, swigΩ Ω (M.observedAt ⟨k, hk⟩).val) (fun k ih hk => if hcSelf : (M.observedAt ⟨k, hk⟩).val ∈ C then c ⟨(M.observedAt ⟨k, hk⟩).val, hcSelf⟩ else M.structFun (M.observedAt ⟨k, hk⟩) (fun w => parentMapOverride M s c ℓ hk ih w)) n
def evalMap_overrideC reviewed
Causalean.SCM

The overridden evaluation map returns target observed values while holding the override block fixed.

Definition (Lean source)
noncomputable def evalMap_overrideC (M : SCM N Ω) {Y C : Finset (SWIGNode N)} (hY : Y ⊆ M.observed) (hC : C ⊆ M.observed) (s : M.FixedValues) (c : ValuesOn C (swigΩ Ω)) (ℓ : M.LatentValues) : ValuesOn Y (swigΩ Ω) := fun v => (M.observedAt_observedIndex ⟨v.val, hY v.property⟩) ▸ evalObservedAuxOverride M hC s c ℓ (M.observedIndex ⟨v.val, hY v.property⟩).val (M.observedIndex ⟨v.val, hY v.property⟩).isLt
theorem evalMap_overrideC_at_self reviewed
Causalean.SCM

Cornerstone: overriding by the model's own values changes nothing. Fix a structural causal model M, a target set Y and an override set C with Y contained in the observed nodes and C contained in the observed nodes, a fixed-value assignment s, and a latent assignment . Then overriding C with the values that evalMap s ℓ would itself have produced there leaves the resulting evaluation on Y equal to the plain evaluation evalMap s ℓ on Y. No d-separation hypothesis is needed.

Formal statement
M :
SCM N Ω
Y C :
hY :
Y ⊆ M.observed
hC :
C ⊆ M.observed
s :
M.FixedValues
:
M.LatentValues
M.evalMap_overrideC hY hC s (fun v' : {v // v ∈ C} => (M.observedAt_observedIndex ⟨v'.val, hC v'.property⟩) ▸ evalObservedAux M s ℓ (M.observedIndex ⟨v'.val, hC v'.property⟩).val (M.observedIndex ⟨v'.val, hC v'.property⟩).isLt) ℓ
= fun v : {v // v ∈ Y} => (M.observedAt_observedIndex ⟨v.val, hY v.property⟩) ▸ evalObservedAux M s ℓ (M.observedIndex ⟨v.val, hY v.property⟩).val (M.observedIndex ⟨v.val, hY v.property⟩).isLt
Proof (Lean source)
theorem evalMap_overrideC_at_self (M : SCM N Ω) {Y C : Finset (SWIGNode N)} (hY : Y ⊆ M.observed) (hC : C ⊆ M.observed) (s : M.FixedValues) (ℓ : M.LatentValues) : M.evalMap_overrideC hY hC s (fun v' : {v // v ∈ C} => (M.observedAt_observedIndex ⟨v'.val, hC v'.property⟩) ▸ evalObservedAux M s ℓ (M.observedIndex ⟨v'.val, hC v'.property⟩).val (M.observedIndex ⟨v'.val, hC v'.property⟩).isLt) ℓ = fun v : {v // v ∈ Y} => (M.observedAt_observedIndex ⟨v.val, hY v.property⟩) ▸ evalObservedAux M s ℓ (M.observedIndex ⟨v.val, hY v.property⟩).val (M.observedIndex ⟨v.val, hY v.property⟩).isLt := by funext v rw [evalMap_overrideC_eq] congr 1 exact evalObservedAuxOverride_eq_evalObservedAux_at_self M hC s ℓ _ _
12 supporting declarations (lemmas, instances)
  • parentMapOverride_unobserved lemma — The override parent-value tuple reads a latent parent directly from the latent assignment.
    M :
    SCM N Ω
    C :
    hC :
    C ⊆ M.observed
    s :
    c :
    :
    n :
    hn :
    n < M.observed.card
    prev :
    ∀ m : ℕ
    if
    m < n
    then
    ∀ hm : m < M.observed.card, swigΩ Ω (M.observedAt ⟨m, hm⟩).val
    w :
    {w // w ∈ M.dag.parents (M.observedAt ⟨n, hn⟩).val}
    huo :
    w.val ∈ M.unobserved
    parentMapOverride M s c ℓ hn prev w = ℓ ⟨w.val, huo⟩
    Proof (Lean source)
    lemma parentMapOverride_unobserved (M : SCM N Ω) {C : Finset (SWIGNode N)} (hC : C ⊆ M.observed) (s : FixedValues M) (c : ValuesOn C (swigΩ Ω)) (ℓ : LatentValues M) {n : ℕ} (hn : n < M.observed.card) (prev : ∀ m : ℕ, m < n → ∀ hm : m < M.observed.card, swigΩ Ω (M.observedAt ⟨m, hm⟩).val) (w : {w // w ∈ M.dag.parents (M.observedAt ⟨n, hn⟩).val}) (huo : w.val ∈ M.unobserved) : parentMapOverride M s c ℓ hn prev w = ℓ ⟨w.val, huo⟩ := by unfold parentMapOverride rw [dif_pos huo]
    Causalean.SCM.parentMapOverride_unobserved · Causalean/SCM/Model/EvalOverrideC.lean:104
  • parentMapOverride_fixed lemma — The override parent-value tuple reads a fixed parent directly from the fixed-value assignment.
    M :
    SCM N Ω
    C :
    hC :
    C ⊆ M.observed
    s :
    c :
    :
    n :
    hn :
    n < M.observed.card
    prev :
    ∀ m : ℕ
    if
    m < n
    then
    ∀ hm : m < M.observed.card, swigΩ Ω (M.observedAt ⟨m, hm⟩).val
    w :
    {w // w ∈ M.dag.parents (M.observedAt ⟨n, hn⟩).val}
    hfix :
    w.val ∈ M.fixed
    parentMapOverride M s c ℓ hn prev w = s ⟨w.val, hfix⟩
    Proof (Lean source)
    lemma parentMapOverride_fixed (M : SCM N Ω) {C : Finset (SWIGNode N)} (hC : C ⊆ M.observed) (s : FixedValues M) (c : ValuesOn C (swigΩ Ω)) (ℓ : LatentValues M) {n : ℕ} (hn : n < M.observed.card) (prev : ∀ m : ℕ, m < n → ∀ hm : m < M.observed.card, swigΩ Ω (M.observedAt ⟨m, hm⟩).val) (w : {w // w ∈ M.dag.parents (M.observedAt ⟨n, hn⟩).val}) (hfix : w.val ∈ M.fixed) : parentMapOverride M s c ℓ hn prev w = s ⟨w.val, hfix⟩ := by unfold parentMapOverride have huo : w.val ∉ M.unobserved := by intro h obtain ⟨m, hm⟩ := M.unobserved_is_random _ h obtain ⟨k, hk⟩ := M.fixed_is_fixed _ hfix rw [hk] at hm exact absurd hm (by simp) rw [dif_neg huo, dif_pos hfix]
    Causalean.SCM.parentMapOverride_fixed · Causalean/SCM/Model/EvalOverrideC.lean:117
  • parentMapOverride_C lemma — The override parent-value tuple reads an overridden observed parent directly from the override assignment.
    M :
    SCM N Ω
    C :
    hC :
    C ⊆ M.observed
    s :
    c :
    :
    n :
    hn :
    n < M.observed.card
    prev :
    ∀ m : ℕ
    if
    m < n
    then
    ∀ hm : m < M.observed.card, swigΩ Ω (M.observedAt ⟨m, hm⟩).val
    w :
    {w // w ∈ M.dag.parents (M.observedAt ⟨n, hn⟩).val}
    hc :
    w.val ∈ C
    parentMapOverride M s c ℓ hn prev w = c ⟨w.val, hc⟩
    Proof (Lean source)
    lemma parentMapOverride_C (M : SCM N Ω) {C : Finset (SWIGNode N)} (hC : C ⊆ M.observed) (s : FixedValues M) (c : ValuesOn C (swigΩ Ω)) (ℓ : LatentValues M) {n : ℕ} (hn : n < M.observed.card) (prev : ∀ m : ℕ, m < n → ∀ hm : m < M.observed.card, swigΩ Ω (M.observedAt ⟨m, hm⟩).val) (w : {w // w ∈ M.dag.parents (M.observedAt ⟨n, hn⟩).val}) (hc : w.val ∈ C) : parentMapOverride M s c ℓ hn prev w = c ⟨w.val, hc⟩ := by unfold parentMapOverride have hobs : w.val ∈ M.observed := hC hc have huo : w.val ∉ M.unobserved := not_unobs_of_obs M.toSWIGGraph hobs have hfix : w.val ∉ M.fixed := not_fixed_of_obs M.toSWIGGraph hobs rw [dif_neg huo, dif_neg hfix, dif_pos hc]
    Causalean.SCM.parentMapOverride_C · Causalean/SCM/Model/EvalOverrideC.lean:137
  • parentMapOverride_observed lemma — The override parent-value tuple reads a non-overridden observed parent from the previous recursive values.
    M :
    SCM N Ω
    C :
    hC :
    C ⊆ M.observed
    s :
    c :
    :
    n :
    hn :
    n < M.observed.card
    prev :
    ∀ m : ℕ
    if
    m < n
    then
    ∀ hm : m < M.observed.card, swigΩ Ω (M.observedAt ⟨m, hm⟩).val
    w :
    {w // w ∈ M.dag.parents (M.observedAt ⟨n, hn⟩).val}
    hobs :
    w.val ∈ M.observed
    hc :
    w.val ∉ C
    parentMapOverride M s c ℓ hn prev w
    = (M.observedAt_observedIndex ⟨w.val, hobs⟩) ▸ prev (M.observedIndex ⟨w.val, hobs⟩).val (M.observed_parent_index_lt hn (M.dag.mem_parents.mp w.property) hobs) (M.observedIndex ⟨w.val, hobs⟩).isLt
    Proof (Lean source)
    lemma parentMapOverride_observed (M : SCM N Ω) {C : Finset (SWIGNode N)} (hC : C ⊆ M.observed) (s : FixedValues M) (c : ValuesOn C (swigΩ Ω)) (ℓ : LatentValues M) {n : ℕ} (hn : n < M.observed.card) (prev : ∀ m : ℕ, m < n → ∀ hm : m < M.observed.card, swigΩ Ω (M.observedAt ⟨m, hm⟩).val) (w : {w // w ∈ M.dag.parents (M.observedAt ⟨n, hn⟩).val}) (hobs : w.val ∈ M.observed) (hc : w.val ∉ C) : parentMapOverride M s c ℓ hn prev w = (M.observedAt_observedIndex ⟨w.val, hobs⟩) ▸ prev (M.observedIndex ⟨w.val, hobs⟩).val (M.observed_parent_index_lt hn (M.dag.mem_parents.mp w.property) hobs) (M.observedIndex ⟨w.val, hobs⟩).isLt := by unfold parentMapOverride have huo : w.val ∉ M.unobserved := not_unobs_of_obs M.toSWIGGraph hobs have hfix : w.val ∉ M.fixed := not_fixed_of_obs M.toSWIGGraph hobs rw [dif_neg huo, dif_neg hfix, dif_neg hc]
    Causalean.SCM.parentMapOverride_observed · Causalean/SCM/Model/EvalOverrideC.lean:153
  • evalObservedAuxOverride_eq lemma — The override auxiliary evaluator unfolds to either the override value or the structural function applied to overridden parents.
    M :
    SCM N Ω
    C :
    hC :
    C ⊆ M.observed
    s :
    c :
    :
    n :
    hn :
    n < M.observed.card
    evalObservedAuxOverride M hC s c ℓ n hn
    = (if hcSelf : (M.observedAt ⟨n, hn⟩).val ∈ C then c ⟨(M.observedAt ⟨n, hn⟩).val, hcSelf⟩ else M.structFun (M.observedAt ⟨n, hn⟩) (fun w => parentMapOverride M s c ℓ hn (fun m _ hm_card => evalObservedAuxOverride M hC s c ℓ m hm_card) w))
    Proof (Lean source)
    lemma evalObservedAuxOverride_eq (M : SCM N Ω) {C : Finset (SWIGNode N)} (hC : C ⊆ M.observed) (s : FixedValues M) (c : ValuesOn C (swigΩ Ω)) (ℓ : LatentValues M) (n : ℕ) (hn : n < M.observed.card) : evalObservedAuxOverride M hC s c ℓ n hn = (if hcSelf : (M.observedAt ⟨n, hn⟩).val ∈ C then c ⟨(M.observedAt ⟨n, hn⟩).val, hcSelf⟩ else M.structFun (M.observedAt ⟨n, hn⟩) (fun w => parentMapOverride M s c ℓ hn (fun m _ hm_card => evalObservedAuxOverride M hC s c ℓ m hm_card) w)) := by unfold evalObservedAuxOverride rw [Nat.strongRec_eq]
    Causalean.SCM.evalObservedAuxOverride_eq · Causalean/SCM/Model/EvalOverrideC.lean:198
  • evalMap_overrideC_eq lemma — At any target node, the overridden evaluation map is the transported override auxiliary value at that node's topological index.
    M :
    SCM N Ω
    Y C :
    hY :
    Y ⊆ M.observed
    hC :
    C ⊆ M.observed
    s :
    M.FixedValues
    c :
    :
    M.LatentValues
    v :
    {v // v ∈ Y}
    M.evalMap_overrideC hY hC s c ℓ v
    = (M.observedAt_observedIndex ⟨v.val, hY v.property⟩) ▸ evalObservedAuxOverride M hC s c ℓ (M.observedIndex ⟨v.val, hY v.property⟩).val (M.observedIndex ⟨v.val, hY v.property⟩).isLt
    Proof (Lean source)
    lemma evalMap_overrideC_eq (M : SCM N Ω) {Y C : Finset (SWIGNode N)} (hY : Y ⊆ M.observed) (hC : C ⊆ M.observed) (s : M.FixedValues) (c : ValuesOn C (swigΩ Ω)) (ℓ : M.LatentValues) (v : {v // v ∈ Y}) : M.evalMap_overrideC hY hC s c ℓ v = (M.observedAt_observedIndex ⟨v.val, hY v.property⟩) ▸ evalObservedAuxOverride M hC s c ℓ (M.observedIndex ⟨v.val, hY v.property⟩).val (M.observedIndex ⟨v.val, hY v.property⟩).isLt := rfl
    Causalean.SCM.evalMap_overrideC_eq · Causalean/SCM/Model/EvalOverrideC.lean:234
  • evalMap_overrideC_apply_of_mem_C theorem — At a target node inside the override block, the overridden evaluation returns the assigned override value.
    M :
    SCM N Ω
    Y C :
    hY :
    Y ⊆ M.observed
    hC :
    C ⊆ M.observed
    s :
    M.FixedValues
    c :
    :
    M.LatentValues
    v :
    {v // v ∈ Y}
    hvC :
    v.val ∈ C
    M.evalMap_overrideC hY hC s c ℓ v = c ⟨v.val, hvC⟩
    Proof (Lean source)
    theorem evalMap_overrideC_apply_of_mem_C (M : SCM N Ω) {Y C : Finset (SWIGNode N)} (hY : Y ⊆ M.observed) (hC : C ⊆ M.observed) (s : M.FixedValues) (c : ValuesOn C (swigΩ Ω)) (ℓ : M.LatentValues) (v : {v // v ∈ Y}) (hvC : v.val ∈ C) : M.evalMap_overrideC hY hC s c ℓ v = c ⟨v.val, hvC⟩ := by rw [evalMap_overrideC_eq] set j : Fin M.observed.card := M.observedIndex ⟨v.val, hY v.property⟩ with hj_def rw [evalObservedAuxOverride_eq] have hAtJ : M.observedAt ⟨j.val, j.isLt⟩ = M.observedAt j := rfl have hcSelf : (M.observedAt ⟨j.val, j.isLt⟩).val ∈ C := by rw [hAtJ] rw [show M.observedAt j = ⟨v.val, hY v.property⟩ from observedAt_observedIndex_subtype M (hY v.property)] exact hvC rw [dif_pos hcSelf] -- Goal: transport ▸ c ⟨(M.observedAt ⟨j.val, j.isLt⟩).val, hcSelf⟩ = c ⟨v.val, hvC⟩ -- The transport is along `(M.observedAt j).val = v.val`. -- Reduce by `c_at_observedAt_eq_c_at_self`, with j ≡ M.observedIndex ⟨v.val, hY v.property⟩. -- First: rewrite ⟨j.val, j.isLt⟩ as j using Fin eta. have hFinEta : (⟨j.val, j.isLt⟩ : Fin M.observed.card) = j := Fin.ext rfl -- Now use a direct calculation. -- The expression `(M.observedAt ⟨j.val, j.isLt⟩).val` is defeq to `(M.observedAt j).val`. -- The cast proof on the LHS is `M.observedAt_observedIndex ⟨v.val, hY v.property⟩`. change (M.observedAt_observedIndex ⟨v.val, hY v.property⟩) ▸ c ⟨(M.observedAt ⟨j.val, j.isLt⟩).val, hcSelf⟩ = c ⟨v.val, hvC⟩ -- Reduce to the helper form. exact c_at_observedAt_eq_c_at_self M c (hY v.property) hvC hcSelf
    Causalean.SCM.evalMap_overrideC_apply_of_mem_C · Causalean/SCM/Model/EvalOverrideC.lean:288
  • evalMap_overrideC_apply_of_not_mem_C theorem — At a target node outside the override block, the overridden evaluation applies the structural function to overridden parent values.
    M :
    SCM N Ω
    Y C :
    hY :
    Y ⊆ M.observed
    hC :
    C ⊆ M.observed
    s :
    M.FixedValues
    c :
    :
    M.LatentValues
    v :
    {v // v ∈ Y}
    hvC :
    v.val ∉ C
    M.evalMap_overrideC hY hC s c ℓ v
    = (M.observedAt_observedIndex ⟨v.val, hY v.property⟩) ▸ M.structFun (M.observedAt (M.observedIndex ⟨v.val, hY v.property⟩)) (fun w => parentMapOverride M s c ℓ (M.observedIndex ⟨v.val, hY v.property⟩).isLt (fun m _ hm_card => evalObservedAuxOverride M hC s c ℓ m hm_card) w)
    Proof (Lean source)
    theorem evalMap_overrideC_apply_of_not_mem_C (M : SCM N Ω) {Y C : Finset (SWIGNode N)} (hY : Y ⊆ M.observed) (hC : C ⊆ M.observed) (s : M.FixedValues) (c : ValuesOn C (swigΩ Ω)) (ℓ : M.LatentValues) (v : {v // v ∈ Y}) (hvC : v.val ∉ C) : M.evalMap_overrideC hY hC s c ℓ v = (M.observedAt_observedIndex ⟨v.val, hY v.property⟩) ▸ M.structFun (M.observedAt (M.observedIndex ⟨v.val, hY v.property⟩)) (fun w => parentMapOverride M s c ℓ (M.observedIndex ⟨v.val, hY v.property⟩).isLt (fun m _ hm_card => evalObservedAuxOverride M hC s c ℓ m hm_card) w) := by rw [evalMap_overrideC_eq] set j : Fin M.observed.card := M.observedIndex ⟨v.val, hY v.property⟩ with hj_def rw [evalObservedAuxOverride_eq] have hAtJ : M.observedAt ⟨j.val, j.isLt⟩ = M.observedAt j := rfl have hcSelf : (M.observedAt ⟨j.val, j.isLt⟩).val ∉ C := by rw [hAtJ] rw [show M.observedAt j = ⟨v.val, hY v.property⟩ from observedAt_observedIndex_subtype M (hY v.property)] exact hvC rw [dif_neg hcSelf]
    Causalean.SCM.evalMap_overrideC_apply_of_not_mem_C · Causalean/SCM/Model/EvalOverrideC.lean:318
  • evalObservedAux_cast_collapse_at_observedAt lemma — For an observed node, transporting its recursively evaluated value through the round-trip topological-index lookup leaves that value unchanged.
    M :
    SCM N Ω
    s :
    :
    j :
    Fin M.observed.card
    hvObs :
    (M.observedAt j).val ∈ M.observed
    ((M.observedAt_observedIndex ⟨(M.observedAt j).val, hvObs⟩) ▸ evalObservedAux M s ℓ (M.observedIndex ⟨(M.observedAt j).val, hvObs⟩).val (M.observedIndex ⟨(M.observedAt j).val, hvObs⟩).isLt)
    = evalObservedAux M s ℓ j.val j.isLt
    Proof (Lean source)
    lemma evalObservedAux_cast_collapse_at_observedAt (M : SCM N Ω) (s : FixedValues M) (ℓ : LatentValues M) (j : Fin M.observed.card) (hvObs : (M.observedAt j).val ∈ M.observed) : ((M.observedAt_observedIndex ⟨(M.observedAt j).val, hvObs⟩) ▸ evalObservedAux M s ℓ (M.observedIndex ⟨(M.observedAt j).val, hvObs⟩).val (M.observedIndex ⟨(M.observedAt j).val, hvObs⟩).isLt) = evalObservedAux M s ℓ j.val j.isLt := by -- Step 1: the Subtype ⟨(observedAt j).val, hvObs⟩ equals observedAt j (Subtype.ext). have hsubEq : (⟨(M.observedAt j).val, hvObs⟩ : {v // v ∈ M.observed}) = M.observedAt j := Subtype.ext rfl -- Step 2: the index of observedAt j is j (observedIndex_observedAt). have hjEq : M.observedIndex ⟨(M.observedAt j).val, hvObs⟩ = j := by rw [hsubEq]; exact M.observedIndex_observedAt _ -- Step 3: factor through a free Fin `k` and Subtype `w`. This is the -- `evalObservedAux_cast_eq_structFunAt` trick applied here. suffices h : ∀ (k : Fin M.observed.card) (hkj : k = j) (hcast : (M.observedAt k).val = (M.observedAt j).val), (hcast ▸ evalObservedAux M s ℓ k.val k.isLt : swigΩ Ω (M.observedAt j).val) = evalObservedAux M s ℓ j.val j.isLt by -- Plug in k := observedIndex ⟨(observedAt j).val, hvObs⟩, hkj := hjEq. -- The cast proof becomes (observedAt (observedIndex ⟨...⟩)).val = (observedAt j).val, -- but we have it as (observedAt (observedIndex ⟨...⟩)).val = ⟨...⟩.val = (observedAt j).val. have hcast' : (M.observedAt (M.observedIndex ⟨(M.observedAt j).val, hvObs⟩)).val = (M.observedAt j).val := M.observedAt_observedIndex ⟨(M.observedAt j).val, hvObs⟩ exact h _ hjEq hcast' intro k hkj hcast subst k have hpr_rfl : hcast = rfl := Subsingleton.elim _ _ rw [hpr_rfl]
    Causalean.SCM.evalObservedAux_cast_collapse_at_observedAt · Causalean/SCM/Model/EvalOverrideC.lean:348
  • evalObservedAuxOverride_eq_evalObservedAux_at_self lemma — When an observed-node override uses the model's own recursively evaluated values, the overridden recursive evaluator agrees with the original evaluator at every topological position.
    M :
    SCM N Ω
    C :
    hC :
    C ⊆ M.observed
    s :
    :
    ∀ (n : ℕ) (hn : n < M.observed.card),
    evalObservedAuxOverride M hC s (fun v' : {v // v ∈ C} => (M.observedAt_observedIndex ⟨v'.val, hC v'.property⟩) ▸ evalObservedAux M s ℓ (M.observedIndex ⟨v'.val, hC v'.property⟩).val (M.observedIndex ⟨v'.val, hC v'.property⟩).isLt) ℓ n hn
    = evalObservedAux M s ℓ n hn
    Proof (Lean source)
    lemma evalObservedAuxOverride_eq_evalObservedAux_at_self (M : SCM N Ω) {C : Finset (SWIGNode N)} (hC : C ⊆ M.observed) (s : FixedValues M) (ℓ : LatentValues M) : ∀ (n : ℕ) (hn : n < M.observed.card), evalObservedAuxOverride M hC s (fun v' : {v // v ∈ C} => (M.observedAt_observedIndex ⟨v'.val, hC v'.property⟩) ▸ evalObservedAux M s ℓ (M.observedIndex ⟨v'.val, hC v'.property⟩).val (M.observedIndex ⟨v'.val, hC v'.property⟩).isLt) ℓ n hn = evalObservedAux M s ℓ n hn := by intro n induction n using Nat.strongRecOn with | _ n ih => intro hn rw [evalObservedAuxOverride_eq] by_cases hcSelf : (M.observedAt ⟨n, hn⟩).val ∈ C · rw [dif_pos hcSelf] -- Goal: c-value at v_sub = evalObservedAux M s ℓ n hn. -- Beta-reduce the c-lambda first. change ((M.observedAt_observedIndex ⟨(M.observedAt ⟨n, hn⟩).val, hC hcSelf⟩) ▸ evalObservedAux M s ℓ (M.observedIndex ⟨(M.observedAt ⟨n, hn⟩).val, hC hcSelf⟩).val (M.observedIndex ⟨(M.observedAt ⟨n, hn⟩).val, hC hcSelf⟩).isLt) = evalObservedAux M s ℓ n hn -- Apply the cast-collapse helper at index ⟨n, hn⟩. exact evalObservedAux_cast_collapse_at_observedAt M s ℓ ⟨n, hn⟩ (hC hcSelf) · rw [dif_neg hcSelf] rw [evalObservedAux_eq] congr 1 funext w have hedge : M.dag.edge w.val (M.observedAt ⟨n, hn⟩).val := M.dag.mem_parents.mp w.property by_cases huo : w.val ∈ M.unobserved · rw [parentMapOverride_unobserved M hC s _ _ _ _ _ huo, parentMap_unobserved _ _ _ _ _ _ huo] · by_cases hfix : w.val ∈ M.fixed · rw [parentMapOverride_fixed M hC s _ _ _ _ _ hfix, parentMap_fixed _ _ _ _ _ _ hfix] · have hobs : w.val ∈ M.observed := by rcases Finset.mem_union.mp (M.dag_edges_classified _ _ hedge).1 with h1 | h2 · rcases Finset.mem_union.mp h1 with hfx | hob · exact absurd hfx hfix · exact hob · exact absurd h2 huo have hj : (M.observedIndex ⟨w.val, hobs⟩).val < n := M.observed_parent_index_lt hn hedge hobs by_cases hcW : w.val ∈ C · rw [parentMapOverride_C M hC s _ _ _ _ _ hcW] -- Goal: c ⟨w.val, hcW⟩ = parentMap s ℓ hn _ w -- The c-lambda body at w.val matches parentMap_observed's body (def. eq). rw [parentMap_observed _ _ _ _ _ _ hobs] · rw [parentMapOverride_observed M hC s _ _ _ _ _ hobs hcW] rw [parentMap_observed _ _ _ _ _ _ hobs] congr 1 exact ih _ hj _
    Causalean.SCM.evalObservedAuxOverride_eq_evalObservedAux_at_self · Causalean/SCM/Model/EvalOverrideC.lean:386
  • evalObservedAuxOverride_measurable lemma — At every position in an SCM's topological order, its observed-variable evaluator with specified observed values overridden is jointly measurable in fixed, override, and latent inputs.
    M :
    SCM N Ω
    C :
    hC :
    C ⊆ M.observed
    ∀ (n : ℕ) (hn : n < M.observed.card),
    Measurable (fun p : (FixedValues M × ValuesOn C (swigΩ Ω)) × LatentValues M => evalObservedAuxOverride M hC p.1.1 p.1.2 p.2 n hn)
    Proof (Lean source)
    lemma evalObservedAuxOverride_measurable (M : SCM N Ω) {C : Finset (SWIGNode N)} (hC : C ⊆ M.observed) : ∀ (n : ℕ) (hn : n < M.observed.card), Measurable (fun p : (FixedValues M × ValuesOn C (swigΩ Ω)) × LatentValues M => evalObservedAuxOverride M hC p.1.1 p.1.2 p.2 n hn) := by intro n induction n using Nat.strongRecOn with | _ n ih => intro hn have hrw : (fun p : (FixedValues M × ValuesOn C (swigΩ Ω)) × LatentValues M => evalObservedAuxOverride M hC p.1.1 p.1.2 p.2 n hn) = (fun p => if hcSelf : (M.observedAt ⟨n, hn⟩).val ∈ C then p.1.2 ⟨(M.observedAt ⟨n, hn⟩).val, hcSelf⟩ else M.structFun (M.observedAt ⟨n, hn⟩) (fun w => parentMapOverride M p.1.1 p.1.2 p.2 hn (fun m _ hm_card => evalObservedAuxOverride M hC p.1.1 p.1.2 p.2 m hm_card) w)) := by funext p exact evalObservedAuxOverride_eq M hC p.1.1 p.1.2 p.2 n hn rw [hrw] by_cases hcSelf : (M.observedAt ⟨n, hn⟩).val ∈ C · simp only [dif_pos hcSelf] exact (measurable_pi_apply _).comp (measurable_snd.comp measurable_fst) · simp only [dif_neg hcSelf] refine (M.structFun_measurable _).comp ?_ refine measurable_pi_lambda _ (fun w => ?_) by_cases huo : w.val ∈ M.unobserved · have hfun : (fun p : (FixedValues M × ValuesOn C (swigΩ Ω)) × LatentValues M => parentMapOverride M p.1.1 p.1.2 p.2 hn (fun m _ hm_card => evalObservedAuxOverride M hC p.1.1 p.1.2 p.2 m hm_card) w) = (fun p => p.2 ⟨w.val, huo⟩) := by funext p exact parentMapOverride_unobserved M hC p.1.1 p.1.2 p.2 hn _ w huo rw [hfun] exact (measurable_pi_apply _).comp measurable_snd · by_cases hfix : w.val ∈ M.fixed · have hfun : (fun p : (FixedValues M × ValuesOn C (swigΩ Ω)) × LatentValues M => parentMapOverride M p.1.1 p.1.2 p.2 hn (fun m _ hm_card => evalObservedAuxOverride M hC p.1.1 p.1.2 p.2 m hm_card) w) = (fun p => p.1.1 ⟨w.val, hfix⟩) := by funext p exact parentMapOverride_fixed M hC p.1.1 p.1.2 p.2 hn _ w hfix rw [hfun] exact (measurable_pi_apply _).comp (measurable_fst.comp measurable_fst) · have hedge : M.dag.edge w.val (M.observedAt ⟨n, hn⟩).val := M.dag.mem_parents.mp w.property have hobs : w.val ∈ M.observed := by have hcls := (M.dag_edges_classified _ _ hedge).1 rcases Finset.mem_union.mp hcls with hfo | huo' · rcases Finset.mem_union.mp hfo with hf | ho · exact absurd hf hfix · exact ho · exact absurd huo' huo by_cases hcW : w.val ∈ C · have hfun : (fun p : (FixedValues M × ValuesOn C (swigΩ Ω)) × LatentValues M => parentMapOverride M p.1.1 p.1.2 p.2 hn (fun m _ hm_card => evalObservedAuxOverride M hC p.1.1 p.1.2 p.2 m hm_card) w) = (fun p => p.1.2 ⟨w.val, hcW⟩) := by funext p exact parentMapOverride_C M hC p.1.1 p.1.2 p.2 hn _ w hcW rw [hfun] exact (measurable_pi_apply _).comp (measurable_snd.comp measurable_fst) · have hj : (M.observedIndex ⟨w.val, hobs⟩).val < n := M.observed_parent_index_lt hn hedge hobs have hfun : (fun p : (FixedValues M × ValuesOn C (swigΩ Ω)) × LatentValues M => parentMapOverride M p.1.1 p.1.2 p.2 hn (fun m _ hm_card => evalObservedAuxOverride M hC p.1.1 p.1.2 p.2 m hm_card) w) = (fun p => (M.observedAt_observedIndex ⟨w.val, hobs⟩) ▸ evalObservedAuxOverride M hC p.1.1 p.1.2 p.2 (M.observedIndex ⟨w.val, hobs⟩).val (M.observedIndex ⟨w.val, hobs⟩).isLt) := by funext p exact parentMapOverride_observed M hC p.1.1 p.1.2 p.2 hn _ w hobs hcW rw [hfun] exact measurable_family_cast _ (ih _ hj _)
    Causalean.SCM.evalObservedAuxOverride_measurable · Causalean/SCM/Model/EvalOverrideC.lean:489
  • measurable_evalMap_overrideC theorem — The overridden evaluation map is jointly measurable in fixed values, override values, and the latent realization.
    M :
    SCM N Ω
    Y C :
    hY :
    Y ⊆ M.observed
    hC :
    C ⊆ M.observed
    Measurable (fun p : (M.FixedValues × ValuesOn C (swigΩ Ω)) × M.LatentValues => M.evalMap_overrideC hY hC p.1.1 p.1.2 p.2)
    Proof (Lean source)
    theorem measurable_evalMap_overrideC (M : SCM N Ω) {Y C : Finset (SWIGNode N)} (hY : Y ⊆ M.observed) (hC : C ⊆ M.observed) : Measurable (fun p : (M.FixedValues × ValuesOn C (swigΩ Ω)) × M.LatentValues => M.evalMap_overrideC hY hC p.1.1 p.1.2 p.2) := by refine measurable_pi_iff.mpr (fun v => ?_) have hfun : (fun p : (M.FixedValues × ValuesOn C (swigΩ Ω)) × M.LatentValues => M.evalMap_overrideC hY hC p.1.1 p.1.2 p.2 v) = (fun p => (M.observedAt_observedIndex ⟨v.val, hY v.property⟩) ▸ evalObservedAuxOverride M hC p.1.1 p.1.2 p.2 (M.observedIndex ⟨v.val, hY v.property⟩).val (M.observedIndex ⟨v.val, hY v.property⟩).isLt) := by funext p exact evalMap_overrideC_eq M hY hC p.1.1 p.1.2 p.2 v rw [hfun] exact measurable_family_cast _ (evalObservedAuxOverride_measurable M hC _ _)
    Causalean.SCM.measurable_evalMap_overrideC · Causalean/SCM/Model/EvalOverrideC.lean:580
Intervention­Mono 5 core · 8 supporting This file defines the one-shot version of a multi-target intervention on a structural causal model. ★ fixMono_observed★ fixMono_fixed★ fixMono_latentDist

Monolithic Multi-Target Intervention

This file defines the one-shot version of a multi-target intervention on a structural causal model. It reroutes all affected outgoing edges in a single graph transformation, which gives later comparison lemmas direct access to the unchanged structural functions at unaffected vertices.

The graph layer is the monolithic SWIG split; latent distributions are inherited unchanged, and structural functions use one parent reindexing map rather than iterated single-target interventions.

Main definitions and results

* SCM.fixMonoParentMap reindexes split-graph parent tuples back to the parent tuple expected by the original structural function. * SCM.fixMono builds the monolithic multi-target intervention SCM, inheriting latent laws and reusing structural functions through fixMonoParentMap. * SCM.fixMono_observed, SCM.fixMono_unobserved, SCM.fixMono_fixed, and SCM.fixMono_latentDist expose the preserved or enlarged primitive fields. * SCM.fixMono_parents_eq_of_no_fixed_parent gives parent-set coincidence at vertices whose post-intervention parents contain no targeted fixed copy.

def fixMonoParentMap reviewed
Causalean.SCM

The monolithic parent reindexer converts split-graph parent values into the parent values expected by the original structural function.

Definition (Lean source)
noncomputable def fixMonoParentMap (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) (ξ : ∀ w' : {w' // w' ∈ (G.splitMono X hObs hFix).dag.parents v}, swigΩ Ω w'.val) : ∀ w : {w // w ∈ G.dag.parents v}, swigΩ Ω w.val | ⟨SWIGNode.random u, hwVal⟩ => if hu : u ∈ X then ξ ⟨SWIGNode.fixed u, (SWIGGraph.splitMono_parents_char G X hObs hFix v (SWIGNode.fixed u)).2 (inr ⟨u, hu, rfl, hwVal⟩)⟩ else ξ ⟨SWIGNode.random u, (SWIGGraph.splitMono_parents_char G X hObs hFix v (SWIGNode.random u)).2 (inl ⟨hwVal, fun D hD heq => hu (SWIGNode.random.inj heq ▸ hD)⟩)⟩ | ⟨SWIGNode.fixed d, hwVal⟩ => ξ ⟨SWIGNode.fixed d, (SWIGGraph.splitMono_parents_char G X hObs hFix v (SWIGNode.fixed d)).2 (inl ⟨hwVal, fun _ _ heq => by cases heq⟩)⟩
def fixMono reviewed
Causalean.SCM

The monolithic generalized intervention applies all target splits at once while inheriting latent laws and reindexing structural parents.

Definition (Lean source)
noncomputable def fixMono (M : SCM N Ω) (X : Finset N) (hObs : ∀ D ∈ X, SWIGNode.random D ∈ M.observed) (hFix : ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed) : SCM N Ω := by classical let G' : SWIGGraph N := M.toSWIGGraph.splitMono X hObs hFix -- Edge-type assignment on the split graph: edges out of `.fixed D` (D ∈ X) -- inherit the types of the corresponding `.random D`-outgoing edges. let edgeTypes' : EdgeTypeAssignment G'.dag := { edgeType := fun u v => if h : ∃ D ∈ X, u = SWIGNode.fixed D then M.edgeTypes.edgeType (SWIGNode.random (choose h)) v else M.edgeTypes.edgeType u v } -- Observed set is unchanged by `splitMono`, so every observed node of the -- split graph is observed in the original model. have observed_in_original : ∀ v' : {v // v ∈ G'.observed}, v'.val ∈ M.observed := by intro v' exact v'.property refine { dag := G'.dag fixed := G'.fixed observed := G'.observed unobserved := G'.unobserved fixed_is_fixed := G'.fixed_is_fixed observed_is_random := G'.observed_is_random unobserved_is_random := G'.unobserved_is_random obs_unobs_disjoint := G'.obs_unobs_disjoint dag_edges_classified := G'.dag_edges_classified fixed_image_in_observed := G'.fixed_image_in_observed fixed_are_roots := G'.fixed_are_roots unobs_are_roots := G'.unobs_are_roots fixed_outside_fixed_isolated := G'.fixed_outside_fixed_isolated all_children_in_observed := G'.all_children_in_observed edgeTypes := edgeTypes' iota_valueSpace := ?_ structFun := ?_ structFun_measurable := ?_ latentDist := M.latentDist isProbability_latent := M.isProbability_latent } · -- `iota_valueSpace` intro s rcases s with ⟨sVal, hs⟩ rcases G'.fixed_is_fixed sVal hs with ⟨n, rfl⟩ simp [iotaMap] · -- `structFun` intro v' ξ have hv_obs : v'.val ∈ M.observed := observed_in_original v' exact M.structFun ⟨v'.val, hv_obs⟩ (fixMonoParentMap M.toSWIGGraph X hObs hFix v'.val ξ) · -- `structFun_measurable` intro v' have hv_obs : v'.val ∈ M.observed := observed_in_original v' exact (M.structFun_measurable ⟨v'.val, hv_obs⟩).comp (measurable_fixMonoParentMap M.toSWIGGraph X hObs hFix v'.val)
lemma fixMono_observed reviewed
Causalean.SCM

Observed-node invariance of the monolithic intervention. For a SWIG graph G and an intervention target set X such that every targeted node is currently a random observed node and none of its fixed copies is already fixed, the monolithic intervention graph obtained by fixing X has the same observed node set as G.

Formal statement
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 fixMono_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
lemma fixMono_fixed reviewed
Causalean.SCM

Fixed-node set of the monolithic intervention. For a SWIG graph G and an intervention target set X such that every targeted node is currently a random observed node and none of its fixed copies is already fixed, the monolithic intervention graph's fixed node set equals G's fixed node set together with the fixed copies of the targeted nodes in X.

Formal statement
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 fixMono_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
lemma fixMono_latentDist reviewed
Causalean.SCM

Latent-distribution invariance of the monolithic intervention. For a structural causal model M and an intervention target set X such that every targeted node is currently a random observed node and none of its fixed copies is already fixed, and any latent-root node u of the monolithically intervened model, the intervened model's latent distribution at u equals M's original latent distribution at u.

Formal statement
M :
SCM N Ω
X :
hObs :
∀ D ∈ X, SWIGNode.random D ∈ M.observed
hFix :
∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed
u :
{u // u ∈ (M.fixMono X hObs hFix).unobserved}
(M.fixMono X hObs hFix).latentDist u = M.latentDist u
Proof (Lean source)
@[simp] lemma fixMono_latentDist (M : SCM N Ω) (X : Finset N) (hObs : ∀ D ∈ X, SWIGNode.random D ∈ M.observed) (hFix : ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed) (u : {u // u ∈ (M.fixMono X hObs hFix).unobserved}) : (M.fixMono X hObs hFix).latentDist u = M.latentDist u := rfl
8 supporting declarations (lemmas, instances)
  • fixMonoParentMap_apply_fixed lemma — At a fixed-coordinate parent, the monolithic parent reindexer reads the same fixed coordinate from the split graph.
    G :
    X :
    hObs :
    ∀ D ∈ X, SWIGNode.random D ∈ G.observed
    hFix :
    ∀ D ∈ X, SWIGNode.fixed D ∉ G.fixed
    v :
    ξ :
    ∀ w' : {w' // w' ∈ (G.splitMono X hObs hFix).dag.parents v}, swigΩ Ω w'.val
    d :
    N
    hwVal :
    SWIGNode.fixed d ∈ G.dag.parents v
    fixMonoParentMap (Ω := Ω) G X hObs hFix v ξ (⟨SWIGNode.fixed d, hwVal⟩ : {w // w ∈ G.dag.parents v})
    = ξ ⟨SWIGNode.fixed d, (SWIGGraph.splitMono_parents_char G X hObs hFix v (SWIGNode.fixed d)).2 (inl ⟨hwVal, fun _ _ heq => by cases heq⟩)⟩
    Proof (Lean source)
    lemma fixMonoParentMap_apply_fixed (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) (ξ : ∀ w' : {w' // w' ∈ (G.splitMono X hObs hFix).dag.parents v}, swigΩ Ω w'.val) (d : N) (hwVal : SWIGNode.fixed d ∈ G.dag.parents v) : fixMonoParentMap (Ω := Ω) G X hObs hFix v ξ (⟨SWIGNode.fixed d, hwVal⟩ : {w // w ∈ G.dag.parents v}) = ξ ⟨SWIGNode.fixed d, (SWIGGraph.splitMono_parents_char G X hObs hFix v (SWIGNode.fixed d)).2 (inl ⟨hwVal, fun _ _ heq => by cases heq⟩)⟩ := rfl
    Causalean.SCM.fixMonoParentMap_apply_fixed · Causalean/SCM/Model/InterventionMono.lean:79
  • fixMonoParentMap_apply_random_notMem lemma — At an untreated random-coordinate parent, the monolithic parent reindexer reads the same random coordinate from the split graph.
    G :
    X :
    hObs :
    ∀ D ∈ X, SWIGNode.random D ∈ G.observed
    hFix :
    ∀ D ∈ X, SWIGNode.fixed D ∉ G.fixed
    v :
    ξ :
    ∀ w' : {w' // w' ∈ (G.splitMono X hObs hFix).dag.parents v}, swigΩ Ω w'.val
    u :
    N
    hu :
    u ∉ X
    hwVal :
    SWIGNode.random u ∈ G.dag.parents v
    fixMonoParentMap (Ω := Ω) G X hObs hFix v ξ (⟨SWIGNode.random u, hwVal⟩ : {w // w ∈ G.dag.parents v})
    = ξ ⟨SWIGNode.random u, (SWIGGraph.splitMono_parents_char G X hObs hFix v (SWIGNode.random u)).2 (inl ⟨hwVal, fun _ hD heq => hu (SWIGNode.random.inj heq ▸ hD)⟩)⟩
    Proof (Lean source)
    lemma fixMonoParentMap_apply_random_notMem (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) (ξ : ∀ w' : {w' // w' ∈ (G.splitMono X hObs hFix).dag.parents v}, swigΩ Ω w'.val) (u : N) (hu : u ∉ X) (hwVal : SWIGNode.random u ∈ G.dag.parents v) : fixMonoParentMap (Ω := Ω) G X hObs hFix v ξ (⟨SWIGNode.random u, hwVal⟩ : {w // w ∈ G.dag.parents v}) = ξ ⟨SWIGNode.random u, (SWIGGraph.splitMono_parents_char G X hObs hFix v (SWIGNode.random u)).2 (inl ⟨hwVal, fun _ hD heq => hu (SWIGNode.random.inj heq ▸ hD)⟩)⟩ := by unfold fixMonoParentMap simp only [dif_neg hu]
    Causalean.SCM.fixMonoParentMap_apply_random_notMem · Causalean/SCM/Model/InterventionMono.lean:100
  • fixMonoParentMap_apply_random lemma — At a treated random-coordinate parent, the monolithic parent reindexer reads the corresponding fixed coordinate from the split graph.
    G :
    X :
    hObs :
    ∀ D ∈ X, SWIGNode.random D ∈ G.observed
    hFix :
    ∀ D ∈ X, SWIGNode.fixed D ∉ G.fixed
    v :
    D :
    N
    hD :
    D ∈ X
    ξ :
    ∀ w' : {w' // w' ∈ (G.splitMono X hObs hFix).dag.parents v}, swigΩ Ω w'.val
    hD_parent :
    SWIGNode.random D ∈ G.dag.parents v
    fixMonoParentMap (Ω := Ω) G X hObs hFix v ξ (⟨SWIGNode.random D, hD_parent⟩ : {w // w ∈ G.dag.parents v})
    = ξ ⟨SWIGNode.fixed D, (SWIGGraph.splitMono_parents_char G X hObs hFix v (SWIGNode.fixed D)).2 (inr ⟨D, hD, rfl, hD_parent⟩)⟩
    Proof (Lean source)
    lemma fixMonoParentMap_apply_random (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) (D : N) (hD : D ∈ X) (ξ : ∀ w' : {w' // w' ∈ (G.splitMono X hObs hFix).dag.parents v}, swigΩ Ω w'.val) (hD_parent : SWIGNode.random D ∈ G.dag.parents v) : fixMonoParentMap (Ω := Ω) G X hObs hFix v ξ (⟨SWIGNode.random D, hD_parent⟩ : {w // w ∈ G.dag.parents v}) = ξ ⟨SWIGNode.fixed D, (SWIGGraph.splitMono_parents_char G X hObs hFix v (SWIGNode.fixed D)).2 (inr ⟨D, hD, rfl, hD_parent⟩)⟩ := by unfold fixMonoParentMap simp only [dif_pos hD]
    Causalean.SCM.fixMonoParentMap_apply_random · Causalean/SCM/Model/InterventionMono.lean:124
  • measurable_fixMonoParentMap lemma — The parent values used by a monolithic intervention depend measurably on the original parent values, so this reindexing can be used safely when constructing intervened structural equations and probability kernels.
    G :
    X :
    hObs :
    ∀ D ∈ X, SWIGNode.random D ∈ G.observed
    hFix :
    ∀ D ∈ X, SWIGNode.fixed D ∉ G.fixed
    v :
    Measurable (fixMonoParentMap (Ω := Ω) G X hObs hFix v)
    Proof (Lean source)
    lemma measurable_fixMonoParentMap (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) : Measurable (fixMonoParentMap (Ω := Ω) G X hObs hFix v) := by classical refine measurable_pi_iff.mpr ?_ rintro ⟨wVal, hwVal⟩ match wVal, hwVal with | SWIGNode.random u, hwVal => by_cases hu : u ∈ X · have h_eq : (fun ξ : ∀ w' : {w' // w' ∈ (G.splitMono X hObs hFix).dag.parents v}, swigΩ Ω w'.val => fixMonoParentMap G X hObs hFix v ξ (⟨SWIGNode.random u, hwVal⟩ : {w // w ∈ G.dag.parents v})) = (fun ξ => ξ ⟨SWIGNode.fixed u, (SWIGGraph.splitMono_parents_char G X hObs hFix v (SWIGNode.fixed u)).2 (inr ⟨u, hu, rfl, hwVal⟩)⟩) := by funext ξ exact fixMonoParentMap_apply_random (Ω := Ω) G X hObs hFix v u hu ξ hwVal rw [h_eq] exact measurable_pi_apply _ · have h_eq : (fun ξ : ∀ w' : {w' // w' ∈ (G.splitMono X hObs hFix).dag.parents v}, swigΩ Ω w'.val => fixMonoParentMap G X hObs hFix v ξ (⟨SWIGNode.random u, hwVal⟩ : {w // w ∈ G.dag.parents v})) = (fun ξ => ξ ⟨SWIGNode.random u, (SWIGGraph.splitMono_parents_char G X hObs hFix v (SWIGNode.random u)).2 (inl ⟨hwVal, fun D hD heq => hu (SWIGNode.random.inj heq ▸ hD)⟩)⟩) := by funext ξ exact fixMonoParentMap_apply_random_notMem (Ω := Ω) G X hObs hFix v ξ u hu hwVal rw [h_eq] exact measurable_pi_apply _ | SWIGNode.fixed d, hwVal => have h_eq : (fun ξ : ∀ w' : {w' // w' ∈ (G.splitMono X hObs hFix).dag.parents v}, swigΩ Ω w'.val => fixMonoParentMap G X hObs hFix v ξ (⟨SWIGNode.fixed d, hwVal⟩ : {w // w ∈ G.dag.parents v})) = (fun ξ => ξ ⟨SWIGNode.fixed d, (SWIGGraph.splitMono_parents_char G X hObs hFix v (SWIGNode.fixed d)).2 (inl ⟨hwVal, fun _ _ heq => by cases heq⟩)⟩) := by funext ξ exact fixMonoParentMap_apply_fixed (Ω := Ω) G X hObs hFix v ξ d hwVal rw [h_eq] exact measurable_pi_apply _
    Causalean.SCM.measurable_fixMonoParentMap · Causalean/SCM/Model/InterventionMono.lean:147
  • fixMono_unobserved lemma — The monolithic intervention preserves the unobserved node set.
    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 fixMono_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.SCM.fixMono_unobserved · Causalean/SCM/Model/InterventionMono.lean:282
  • fixMono_fixed_subset lemma — The original fixed node set is contained in the fixed node set after the monolithic intervention.
    G :
    X :
    hObs :
    ∀ D ∈ X, SWIGNode.random D ∈ G.observed
    hFix :
    ∀ D ∈ X, SWIGNode.fixed D ∉ G.fixed
    G.fixed ⊆ (G.splitMono X hObs hFix).fixed
    Proof (Lean source)
    lemma fixMono_fixed_subset (G : SWIGGraph N) (X : Finset N) (hObs : ∀ D ∈ X, SWIGNode.random D ∈ G.observed) (hFix : ∀ D ∈ X, SWIGNode.fixed D ∉ G.fixed) : G.fixed ⊆ (G.splitMono X hObs hFix).fixed := by intro x hx rw [fixMono_fixed] exact mem_union_left _ hx
    Causalean.SCM.fixMono_fixed_subset · Causalean/SCM/Model/InterventionMono.lean:310
  • fixMono_image_fixed_subset lemma — The fixed copies of the intervention targets are contained in the fixed node set after the monolithic intervention.
    G :
    X :
    hObs :
    ∀ D ∈ X, SWIGNode.random D ∈ G.observed
    hFix :
    ∀ D ∈ X, SWIGNode.fixed D ∉ G.fixed
    X.image SWIGNode.fixed ⊆ (G.splitMono X hObs hFix).fixed
    Proof (Lean source)
    lemma fixMono_image_fixed_subset (G : SWIGGraph N) (X : Finset N) (hObs : ∀ D ∈ X, SWIGNode.random D ∈ G.observed) (hFix : ∀ D ∈ X, SWIGNode.fixed D ∉ G.fixed) : X.image SWIGNode.fixed ⊆ (G.splitMono X hObs hFix).fixed := by intro x hx rw [fixMono_fixed] exact mem_union_right _ hx
    Causalean.SCM.fixMono_image_fixed_subset · Causalean/SCM/Model/InterventionMono.lean:320
  • fixMono_parents_eq_of_no_fixed_parent lemma — If no fixed copy of a target is a parent of a vertex after intervention, that vertex has the same parents as before.
    M :
    SCM N Ω
    X :
    hObs :
    ∀ D ∈ X, SWIGNode.random D ∈ M.observed
    hFix :
    ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed
    v :
    hNoFP :
    ∀ D ∈ X, SWIGNode.fixed D ∉ (M.fixMono X hObs hFix).dag.parents v
    (M.fixMono X hObs hFix).dag.parents v = M.dag.parents v
    Proof (Lean source)
    lemma fixMono_parents_eq_of_no_fixed_parent (M : SCM N Ω) (X : Finset N) (hObs : ∀ D ∈ X, SWIGNode.random D ∈ M.observed) (hFix : ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed) {v : SWIGNode N} (hNoFP : ∀ D ∈ X, SWIGNode.fixed D ∉ (M.fixMono X hObs hFix).dag.parents v) : (M.fixMono X hObs hFix).dag.parents v = M.dag.parents v := by -- `(M.fixMono X).dag = (M.toSWIGGraph.splitMono X …).dag` by definition. exact SWIGGraph.splitMono_parents_eq_of_no_fixed_parent M.toSWIGGraph X hObs hFix v hNoFP
    Causalean.SCM.fixMono_parents_eq_of_no_fixed_parent · Causalean/SCM/Model/InterventionMono.lean:330
Counterfactual­Lemmas 2 core · 3 supporting This file proves pathwise identities relating evaluation of a structural causal model before and after interventions. ★ evalMap_fixSet_factual_eq★ evalMap_fixSet_union_eq

Counterfactual Identities for Structural Causal Models

This file proves pathwise identities relating evaluation of a structural causal model before and after interventions. The one-step lemmas fixMono_structFun_apply, fixSet_structFun_apply, and evalMap_fixSet_observed_apply expose how intervened parent values enter the original structural equations. The main theorems evalMap_fixSet_factual_eq and evalMap_fixSet_union_eq provide SCM-level factual consistency and composition with an intermediate-value hypothesis, which are used by the potential-outcome bridge.

theorem evalMap_fixSet_factual_eq reviewed
Causalean.SCM

prop:scm-cf-consistency (Basic Concepts.tex L475–480). Fix a structural causal model M, an intervention target set X such that every targeted node is currently a random observed node and none of its fixed copies is already fixed, a base fixed-value assignment s, a latent assignment , and an intervened fixed-value assignment sx for the model obtained by fixing X. If sx agrees with s on the model's original fixed coordinates and for every targeted node the base evaluation at its random form already equals sx's value at its fixed form — the factual consistency condition, then for every observed node v, evaluating the intervened model with sx and at v agrees with evaluating the base model with s and at v.

Formal statement
M :
SCM N Ω
X :
hObs :
∀ D ∈ X, SWIGNode.random D ∈ M.observed
hFix :
∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed
s :
SCM.FixedValues M
:
SCM.LatentValues M
sx :
SCM.FixedValues (M.fixSet X hObs hFix)
sx agrees with s on the original fixed coordinates
hOld :
∀ (v : SWIGNode N) (hv : v ∈ M.fixed), sx ⟨v, mem_union_left _ hv⟩ = s ⟨v, hv⟩
factual condition: base evaluation at .random D equals sx's new fixed value
hNew :
∀ D (hD : D ∈ X),
M.evalMap s ℓ ⟨SWIGNode.random D, mem_union_left _ (hObs D hD)⟩
= sx ⟨SWIGNode.fixed D, mem_union_right _ (Finset.mem_image.mpr ⟨D, hD, rfl⟩)⟩
v :
{v // v ∈ M.observed}
(M.fixSet X hObs hFix).evalMap sx ℓ ⟨v.val, mem_union_left _ v.property⟩
= M.evalMap s ℓ ⟨v.val, mem_union_left _ v.property⟩
Proof (Lean source)
theorem evalMap_fixSet_factual_eq (M : SCM N Ω) (X : Finset N) (hObs : ∀ D ∈ X, SWIGNode.random D ∈ M.observed) (hFix : ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed) (s : SCM.FixedValues M) (ℓ : SCM.LatentValues M) (sx : SCM.FixedValues (M.fixSet X hObs hFix)) -- sx agrees with s on the original fixed coordinates (hOld : ∀ (v : SWIGNode N) (hv : v ∈ M.fixed), sx ⟨v, Finset.mem_union_left _ hv⟩ = s ⟨v, hv⟩) -- factual condition: base evaluation at .random D equals sx's new fixed value (hNew : ∀ D (hD : D ∈ X), M.evalMap s ℓ ⟨SWIGNode.random D, Finset.mem_union_left _ (hObs D hD)⟩ = sx ⟨SWIGNode.fixed D, mem_union_right _ (Finset.mem_image.mpr ⟨D, hD, rfl⟩)⟩) (v : {v // v ∈ M.observed}) : (M.fixSet X hObs hFix).evalMap sx ℓ ⟨v.val, Finset.mem_union_left _ v.property⟩ = M.evalMap s ℓ ⟨v.val, Finset.mem_union_left _ v.property⟩ := by classical -- Observed/unobserved/fixed-of-M coincide on (M.fixSet X); only X.image .fixed is extra. -- Strengthened claim: agreement at EVERY observed node, by strong induction on M's topo index. suffices hstr : ∀ (n : ℕ) (hn : n < M.observed.card), (M.fixSet X hObs hFix).evalMap sx ℓ ⟨(M.observedAt ⟨n, hn⟩).val, Finset.mem_union_left _ ((M.observedAt ⟨n, hn⟩).property)⟩ = M.evalMap s ℓ ⟨(M.observedAt ⟨n, hn⟩).val, Finset.mem_union_left _ ((M.observedAt ⟨n, hn⟩).property)⟩ by have hkey := hstr (M.observedIndex ⟨v.val, v.property⟩).val (M.observedIndex ⟨v.val, v.property⟩).isLt have hat : (M.observedAt (M.observedIndex ⟨v.val, v.property⟩)).val = v.val := M.observedAt_observedIndex ⟨v.val, v.property⟩ -- rewrite node back to v.val using subtype-value equality on both evalMap args have hsub : (⟨(M.observedAt (M.observedIndex ⟨v.val, v.property⟩)).val, Finset.mem_union_left M.unobserved (M.observedAt (M.observedIndex ⟨v.val, v.property⟩)).property⟩ : {w // w ∈ M.randomVars}) = ⟨v.val, Finset.mem_union_left _ v.property⟩ := Subtype.ext hat exact hsub ▸ hkey intro n induction n using Nat.strongRecOn with | _ n ih => intro hn -- The target observed node, viewed in M and in M.fixSet X (same observed set, by rfl). set w₀ : {v // v ∈ M.observed} := M.observedAt ⟨n, hn⟩ with hw₀ have hw₀' : w₀.val ∈ (M.fixSet X hObs hFix).observed := w₀.property -- Unfold both evalMap calls one structFun step. rw [evalMap_observed_unfold M s ℓ w₀, evalMap_observed_unfold (M.fixSet X hObs hFix) sx ℓ ⟨w₀.val, hw₀'⟩] -- The fixSet structFun is M.structFun reindexed via fixMonoParentMap (definitional). refine trans (b := M.structFun w₀ (fixMonoParentMap M.toSWIGGraph X hObs hFix w₀.val (fun w => if huo : (w : {w // w ∈ (M.fixSet X hObs hFix).dag.parents w₀.val}).val ∈ (M.fixSet X hObs hFix).unobserved then ℓ ⟨w.val, huo⟩ else if hfix : w.val ∈ (M.fixSet X hObs hFix).fixed then sx ⟨w.val, hfix⟩ else (M.fixSet X hObs hFix).evalMap sx ℓ ⟨w.val, by rcases Finset.mem_union.mp (((M.fixSet X hObs hFix).dag_edges_classified _ _ ((M.fixSet X hObs hFix).dag.mem_parents.mp w.property)).1) with h1 | h2 · rcases Finset.mem_union.mp h1 with hfx | hob · exact absurd hfx hfix · exact Finset.mem_union_left _ hob · exact absurd h2 huo⟩))) rfl ?_ -- Now: M.structFun w₀ (fixMonoParentMap ... ξdo) = M.structFun w₀ ξbase. -- Peel structFun, prove the parent tuples agree pointwise on M-parents. congr 1 funext w -- `w` is an M-parent of `w₀.val`; classify it. have hedgeM : M.dag.edge w.val w₀.val := M.dag.mem_parents.mp w.property obtain ⟨wVal, hwMem⟩ := w simp only at * cases wVal with | fixed d => -- `.fixed d` is an M-parent ⇒ it must be in `M.fixed` (else isolated). have hdfix : SWIGNode.fixed d ∈ M.fixed := by by_contra hdf have hiso := M.fixed_outside_fixed_isolated d hdf have : w₀.val ∈ M.dag.children (SWIGNode.fixed d) := M.dag.mem_children.mpr hedgeM rw [hiso.2] at this exact (notMem_empty _) this have hdfix' : SWIGNode.fixed d ∈ (M.fixSet X hObs hFix).fixed := Finset.mem_union_left _ hdfix have hdnuo : SWIGNode.fixed d ∉ M.unobserved := by intro h obtain ⟨m, hm⟩ := M.unobserved_is_random _ h exact absurd hm (by simp) rw [fixMonoParentMap_apply_fixed M.toSWIGGraph X hObs hFix w₀.val _ d hwMem] simp only [Subtype.coe_mk] -- ξdo at `.fixed d` reads sx; ξbase reads s; equal by hOld. split_ifs with h1 <;> first | exact hOld (SWIGNode.fixed d) hdfix | exact absurd h1 hdnuo | random u => by_cases hu : u ∈ X · -- intervened node: fixMonoParentMap reads `.fixed u`; equal by hNew. -- `.random u ∈ M.observed`, hence neither fixed nor unobserved. have hru_obs : SWIGNode.random u ∈ M.observed := hObs u hu have hru_nuo : SWIGNode.random u ∉ M.unobserved := fun h => (Finset.disjoint_left.mp M.obs_unobs_disjoint hru_obs) h have hru_nfix : SWIGNode.random u ∉ M.fixed := by intro h; obtain ⟨m, hm⟩ := M.fixed_is_fixed _ h; exact absurd hm (by simp) -- `.fixed u ∈ (M.fixSet X).fixed`, not unobserved. have hfu : SWIGNode.fixed u ∈ (M.fixSet X hObs hFix).fixed := fixed_mem_fixSet M X hObs hFix hu have hfu_nuo : SWIGNode.fixed u ∉ (M.fixSet X hObs hFix).unobserved := by intro h obtain ⟨m, hm⟩ := (M.fixSet X hObs hFix).unobserved_is_random _ h exact absurd hm (by simp) rw [fixMonoParentMap_apply_random M.toSWIGGraph X hObs hFix w₀.val u hu _ hwMem] simp only [Subtype.coe_mk] rw [dif_neg hru_nuo, dif_neg hru_nfix] -- ξdo at `.fixed u`: not unobserved, is fixed ⇒ sx ⟨.fixed u⟩. rw [dif_neg hfu_nuo, dif_pos hfu] exact (hNew u hu).symm · -- non-intervened random parent: same classification on both sides. have hru_nfix : SWIGNode.random u ∉ M.fixed := by intro h; obtain ⟨m, hm⟩ := M.fixed_is_fixed _ h; exact absurd hm (by simp) have hru_ndfix : SWIGNode.random u ∉ (M.fixSet X hObs hFix).fixed := by intro h obtain ⟨m, hm⟩ := (M.fixSet X hObs hFix).fixed_is_fixed _ h exact absurd hm (by simp) rw [fixMonoParentMap_apply_random_notMem M.toSWIGGraph X hObs hFix w₀.val _ u hu hwMem] simp only [Subtype.coe_mk] -- Same three-way classification on both sides; resolve simultaneously. split_ifs with hd hm hm · rfl -- both unobserved ⇒ ℓ · exact absurd hd hm -- fixSet-unobs but base-not-unobs (defeq contra) · exact absurd hm hd -- base-unobs but fixSet-not-unobs (defeq contra) · -- both observed: recurse via IH at strictly smaller M-index. have hru_obs : SWIGNode.random u ∈ M.observed := by rcases Finset.mem_union.mp (M.dag_edges_classified _ _ hedgeM).1 with hc1 | hc2 · rcases Finset.mem_union.mp hc1 with hfx | hob · exact absurd hfx hru_nfix · exact hob · exact absurd hc2 hm set j := M.observedIndex ⟨SWIGNode.random u, hru_obs⟩ with hj have hlt : j.val < n := M.observed_parent_index_lt hn hedgeM hru_obs have hihm := ih j.val hlt j.isLt -- `⟨j.val, j.isLt⟩ = j` by Fin eta (definitional); node value back to `.random u`. have hat : (M.observedAt ⟨j.val, j.isLt⟩).val = SWIGNode.random u := M.observedAt_observedIndex ⟨SWIGNode.random u, hru_obs⟩ have hsub : (⟨(M.observedAt ⟨j.val, j.isLt⟩).val, Finset.mem_union_left M.unobserved (M.observedAt ⟨j.val, j.isLt⟩).property⟩ : {w // w ∈ M.randomVars}) = ⟨SWIGNode.random u, Finset.mem_union_left _ hru_obs⟩ := Subtype.ext hat -- Transport `hihm` along the node-value equality via an explicit motive. refine Eq.ndrec (motive := fun w : {w // w ∈ M.randomVars} => (M.fixSet X hObs hFix).evalMap sx ℓ w = M.evalMap s ℓ w) hihm hsub
theorem evalMap_fixSet_union_eq reviewed
Causalean.SCM

Composition of a combined intervention with a single-stage intervention. Fix a structural causal model M, a latent assignment , and two intervention target sets X₁, X₂ such that every node of X₁ is currently a random observed node with no fixed copy already fixed, and likewise every node of X₁ ∪ X₂, with an X₁-only intervened fixed-value assignment sx₁ and a combined-intervention fixed-value assignment sxU for X₁ ∪ X₂. If sxU agrees with sx₁ on the original fixed coordinates and on the X₁ intervention coordinates and the X₁-intervened model's value at each X₂ node already equals sxU's assignment there — the intermediate condition, then for every observed node v, evaluating the combined-intervention model with sxU and at v agrees with evaluating the X₁-only intervened model with sx₁ and at v.

Formal statement
M :
SCM N Ω
X₁ X₂ :
hObs₁ :
∀ D ∈ X₁, SWIGNode.random D ∈ M.observed
hFix₁ :
∀ D ∈ X₁, SWIGNode.fixed D ∉ M.fixed
hObsU :
∀ D ∈ X₁ ∪ X₂, SWIGNode.random D ∈ M.observed
hFixU :
∀ D ∈ X₁ ∪ X₂, SWIGNode.fixed D ∉ M.fixed
:
SCM.LatentValues M
sx₁ :
SCM.FixedValues (M.fixSet X₁ hObs₁ hFix₁)
sxU :
SCM.FixedValues (M.fixSet (X₁ ∪ X₂) hObsU hFixU)
compatibility: sxU agrees with sx₁ on M.fixed ∪ X₁.image .fixed coords
hCompat_old :
∀ (v : SWIGNode N) (hv : v ∈ M.fixed),
sxU ⟨v, mem_union_left _ hv⟩ = sx₁ ⟨v, mem_union_left _ hv⟩
hCompat_x₁ :
∀ D (hD : D ∈ X₁),
sxU ⟨SWIGNode.fixed D, mem_union_right _ (Finset.mem_image.mpr ⟨D, mem_union_left _ hD, rfl⟩)⟩
= sx₁ ⟨SWIGNode.fixed D, mem_union_right _ (Finset.mem_image.mpr ⟨D, hD, rfl⟩)⟩
intermediate condition: post-X₁ value of .random D equals sxU's X₂ fixed value
hIntermediate :
∀ D (hD : D ∈ X₂),
(M.fixSet X₁ hObs₁ hFix₁).evalMap sx₁ ℓ ⟨SWIGNode.random D, mem_union_left _ (hObsU D (mem_union_right _ hD))⟩
= sxU ⟨SWIGNode.fixed D, mem_union_right _ (Finset.mem_image.mpr ⟨D, mem_union_right _ hD, rfl⟩)⟩
v :
{v // v ∈ M.observed}
(M.fixSet (X₁ ∪ X₂) hObsU hFixU).evalMap sxU ℓ ⟨v.val, mem_union_left _ v.property⟩
= (M.fixSet X₁ hObs₁ hFix₁).evalMap sx₁ ℓ ⟨v.val, mem_union_left _ v.property⟩
Proof (Lean source)
theorem evalMap_fixSet_union_eq (M : SCM N Ω) (X₁ X₂ : Finset N) (hObs₁ : ∀ D ∈ X₁, SWIGNode.random D ∈ M.observed) (hFix₁ : ∀ D ∈ X₁, SWIGNode.fixed D ∉ M.fixed) (hObsU : ∀ D ∈ X₁ ∪ X₂, SWIGNode.random D ∈ M.observed) (hFixU : ∀ D ∈ X₁ ∪ X₂, SWIGNode.fixed D ∉ M.fixed) (ℓ : SCM.LatentValues M) (sx₁ : SCM.FixedValues (M.fixSet X₁ hObs₁ hFix₁)) (sxU : SCM.FixedValues (M.fixSet (X₁ ∪ X₂) hObsU hFixU)) -- compatibility: sxU agrees with sx₁ on M.fixed ∪ X₁.image .fixed coords (hCompat_old : ∀ (v : SWIGNode N) (hv : v ∈ M.fixed), sxU ⟨v, Finset.mem_union_left _ hv⟩ = sx₁ ⟨v, Finset.mem_union_left _ hv⟩) (hCompat_x₁ : ∀ D (hD : D ∈ X₁), sxU ⟨SWIGNode.fixed D, Finset.mem_union_right _ (Finset.mem_image.mpr ⟨D, Finset.mem_union_left _ hD, rfl⟩)⟩ = sx₁ ⟨SWIGNode.fixed D, Finset.mem_union_right _ (Finset.mem_image.mpr ⟨D, hD, rfl⟩)⟩) -- intermediate condition: post-X₁ value of .random D equals sxU's X₂ fixed value (hIntermediate : ∀ D (hD : D ∈ X₂), (M.fixSet X₁ hObs₁ hFix₁).evalMap sx₁ ℓ ⟨SWIGNode.random D, Finset.mem_union_left _ (hObsU D (Finset.mem_union_right _ hD))⟩ = sxU ⟨SWIGNode.fixed D, Finset.mem_union_right _ (Finset.mem_image.mpr ⟨D, Finset.mem_union_right _ hD, rfl⟩)⟩) (v : {v // v ∈ M.observed}) : (M.fixSet (X₁ ∪ X₂) hObsU hFixU).evalMap sxU ℓ ⟨v.val, Finset.mem_union_left _ v.property⟩ = (M.fixSet X₁ hObs₁ hFix₁).evalMap sx₁ ℓ ⟨v.val, Finset.mem_union_left _ v.property⟩ := by classical -- Mirror of `evalMap_fixSet_factual_eq`: strong induction on M's topo index, -- comparing `(M.fixSet (X₁∪X₂)).evalMap sxU` with `(M.fixSet X₁).evalMap sx₁` -- at every observed node. The per-parent case analysis collapses the X₂ -- step via `hIntermediate` and the X₁ step via `hCompat_x₁`. suffices hstr : ∀ (n : ℕ) (hn : n < M.observed.card), (M.fixSet (X₁ ∪ X₂) hObsU hFixU).evalMap sxU ℓ ⟨(M.observedAt ⟨n, hn⟩).val, Finset.mem_union_left _ ((M.observedAt ⟨n, hn⟩).property)⟩ = (M.fixSet X₁ hObs₁ hFix₁).evalMap sx₁ ℓ ⟨(M.observedAt ⟨n, hn⟩).val, Finset.mem_union_left _ ((M.observedAt ⟨n, hn⟩).property)⟩ by have hkey := hstr (M.observedIndex ⟨v.val, v.property⟩).val (M.observedIndex ⟨v.val, v.property⟩).isLt have hat : (M.observedAt (M.observedIndex ⟨v.val, v.property⟩)).val = v.val := M.observedAt_observedIndex ⟨v.val, v.property⟩ have hsub : (⟨(M.observedAt (M.observedIndex ⟨v.val, v.property⟩)).val, Finset.mem_union_left M.unobserved (M.observedAt (M.observedIndex ⟨v.val, v.property⟩)).property⟩ : {w // w ∈ M.randomVars}) = ⟨v.val, Finset.mem_union_left _ v.property⟩ := Subtype.ext hat exact hsub ▸ hkey intro n induction n using Nat.strongRecOn with | _ n ih => intro hn set w₀ : {v // v ∈ M.observed} := M.observedAt ⟨n, hn⟩ with hw₀ have hw₀U : w₀.val ∈ (M.fixSet (X₁ ∪ X₂) hObsU hFixU).observed := w₀.property have hw₀₁ : w₀.val ∈ (M.fixSet X₁ hObs₁ hFix₁).observed := w₀.property -- Unfold both evalMap calls one structFun step. rw [evalMap_observed_unfold (M.fixSet X₁ hObs₁ hFix₁) sx₁ ℓ ⟨w₀.val, hw₀₁⟩, evalMap_observed_unfold (M.fixSet (X₁ ∪ X₂) hObsU hFixU) sxU ℓ ⟨w₀.val, hw₀U⟩] -- Both fixSet structFuns are `M.structFun w₀` reindexed via `fixMonoParentMap`. -- Reduce to: M.structFun w₀ (fixMonoParentMap … X₁∪X₂ ξU) -- = M.structFun w₀ (fixMonoParentMap … X₁ ξ₁). refine trans (b := M.structFun w₀ (fixMonoParentMap M.toSWIGGraph (X₁ ∪ X₂) hObsU hFixU w₀.val (fun w => if huo : (w : {w // w ∈ (M.fixSet (X₁ ∪ X₂) hObsU hFixU).dag.parents w₀.val}).val ∈ (M.fixSet (X₁ ∪ X₂) hObsU hFixU).unobserved then ℓ ⟨w.val, huo⟩ else if hfix : w.val ∈ (M.fixSet (X₁ ∪ X₂) hObsU hFixU).fixed then sxU ⟨w.val, hfix⟩ else (M.fixSet (X₁ ∪ X₂) hObsU hFixU).evalMap sxU ℓ ⟨w.val, by rcases Finset.mem_union.mp (((M.fixSet (X₁ ∪ X₂) hObsU hFixU).dag_edges_classified _ _ ((M.fixSet (X₁ ∪ X₂) hObsU hFixU).dag.mem_parents.mp w.property)).1) with h1 | h2 · rcases Finset.mem_union.mp h1 with hfx | hob · exact absurd hfx hfix · exact Finset.mem_union_left _ hob · exact absurd h2 huo⟩))) rfl ?_ -- Now compare the two parent dispatches directly. refine symm (trans (b := M.structFun w₀ (fixMonoParentMap M.toSWIGGraph X₁ hObs₁ hFix₁ w₀.val (fun w => if huo : (w : {w // w ∈ (M.fixSet X₁ hObs₁ hFix₁).dag.parents w₀.val}).val ∈ (M.fixSet X₁ hObs₁ hFix₁).unobserved then ℓ ⟨w.val, huo⟩ else if hfix : w.val ∈ (M.fixSet X₁ hObs₁ hFix₁).fixed then sx₁ ⟨w.val, hfix⟩ else (M.fixSet X₁ hObs₁ hFix₁).evalMap sx₁ ℓ ⟨w.val, by rcases Finset.mem_union.mp (((M.fixSet X₁ hObs₁ hFix₁).dag_edges_classified _ _ ((M.fixSet X₁ hObs₁ hFix₁).dag.mem_parents.mp w.property)).1) with h1 | h2 · rcases Finset.mem_union.mp h1 with hfx | hob · exact absurd hfx hfix · exact Finset.mem_union_left _ hob · exact absurd h2 huo⟩))) rfl ?_) -- Peel structFun; prove the parent tuples agree pointwise on M-parents. -- Goal (after symm): structFun(X₁ ξ₁) = structFun(X₁∪X₂ ξU). congr 1 funext w have hedgeM : M.dag.edge w.val w₀.val := M.dag.mem_parents.mp w.property obtain ⟨wVal, hwMem⟩ := w simp only at * cases wVal with | fixed d => -- `.fixed d` M-parent ⇒ in M.fixed; both sides read their resp. fixed value. have hdfix : SWIGNode.fixed d ∈ M.fixed := by by_contra hdf have hiso := M.fixed_outside_fixed_isolated d hdf have : w₀.val ∈ M.dag.children (SWIGNode.fixed d) := M.dag.mem_children.mpr hedgeM rw [hiso.2] at this exact (notMem_empty _) this have hdnuo : SWIGNode.fixed d ∉ M.unobserved := by intro h obtain ⟨m, hm⟩ := M.unobserved_is_random _ h exact absurd hm (by simp) have hd1uo : SWIGNode.fixed d ∉ (M.fixSet X₁ hObs₁ hFix₁).unobserved := hdnuo have hdUuo : SWIGNode.fixed d ∉ (M.fixSet (X₁ ∪ X₂) hObsU hFixU).unobserved := hdnuo have hd1fix : SWIGNode.fixed d ∈ (M.fixSet X₁ hObs₁ hFix₁).fixed := Finset.mem_union_left _ hdfix have hdUfix : SWIGNode.fixed d ∈ (M.fixSet (X₁ ∪ X₂) hObsU hFixU).fixed := Finset.mem_union_left _ hdfix rw [fixMonoParentMap_apply_fixed M.toSWIGGraph X₁ hObs₁ hFix₁ w₀.val _ d hwMem, fixMonoParentMap_apply_fixed M.toSWIGGraph (X₁ ∪ X₂) hObsU hFixU w₀.val _ d hwMem] simp only [Subtype.coe_mk] rw [dif_neg hd1uo, dif_pos hd1fix, dif_neg hdUuo, dif_pos hdUfix] exact (hCompat_old (SWIGNode.fixed d) hdfix).symm | random u => have hru_nfix : SWIGNode.random u ∉ M.fixed := by intro h; obtain ⟨m, hm⟩ := M.fixed_is_fixed _ h; exact absurd hm (by simp) by_cases hu1 : u ∈ X₁ · -- u ∈ X₁ ⇒ u ∈ X₁∪X₂; both interventions fix it; equal by hCompat_x₁. have huU : u ∈ X₁ ∪ X₂ := Finset.mem_union_left _ hu1 have hru_nuo : SWIGNode.random u ∉ M.unobserved := fun h => (Finset.disjoint_left.mp M.obs_unobs_disjoint (hObs₁ u hu1)) h have hf1 : SWIGNode.fixed u ∈ (M.fixSet X₁ hObs₁ hFix₁).fixed := fixed_mem_fixSet M X₁ hObs₁ hFix₁ hu1 have hfU : SWIGNode.fixed u ∈ (M.fixSet (X₁ ∪ X₂) hObsU hFixU).fixed := fixed_mem_fixSet M (X₁ ∪ X₂) hObsU hFixU huU have hf1_nuo : SWIGNode.fixed u ∉ (M.fixSet X₁ hObs₁ hFix₁).unobserved := by intro h obtain ⟨m, hm⟩ := (M.fixSet X₁ hObs₁ hFix₁).unobserved_is_random _ h exact absurd hm (by simp) have hfU_nuo : SWIGNode.fixed u ∉ (M.fixSet (X₁ ∪ X₂) hObsU hFixU).unobserved := by intro h obtain ⟨m, hm⟩ := (M.fixSet (X₁ ∪ X₂) hObsU hFixU).unobserved_is_random _ h exact absurd hm (by simp) rw [fixMonoParentMap_apply_random M.toSWIGGraph X₁ hObs₁ hFix₁ w₀.val u hu1 _ hwMem, fixMonoParentMap_apply_random M.toSWIGGraph (X₁ ∪ X₂) hObsU hFixU w₀.val u huU _ hwMem] simp only [Subtype.coe_mk] rw [dif_neg hf1_nuo, dif_pos hf1, dif_neg hfU_nuo, dif_pos hfU] -- LHS: sx₁ ⟨.fixed u⟩, RHS: sxU ⟨.fixed u⟩; equal by hCompat_x₁ (symm). exact (hCompat_x₁ u hu1).symm · by_cases hu2 : u ∈ X₂ · -- u ∈ X₂ \ X₁: union fixes it (reads sxU ⟨.fixed u⟩); -- do(X₁) recurses (reads (M.fixSet X₁).evalMap sx₁ ⟨.random u⟩). have huU : u ∈ X₁ ∪ X₂ := Finset.mem_union_right _ hu2 have hru_obs : SWIGNode.random u ∈ M.observed := hObsU u (Finset.mem_union_right _ hu2) have hru_nuo : SWIGNode.random u ∉ M.unobserved := fun h => (Finset.disjoint_left.mp M.obs_unobs_disjoint hru_obs) h -- RHS (do X₁) side: u ∉ X₁, so reads .random u; classify it. have hru_nd1fix : SWIGNode.random u ∉ (M.fixSet X₁ hObs₁ hFix₁).fixed := by intro h obtain ⟨m, hm⟩ := (M.fixSet X₁ hObs₁ hFix₁).fixed_is_fixed _ h exact absurd hm (by simp) have hru_nd1uo : SWIGNode.random u ∉ (M.fixSet X₁ hObs₁ hFix₁).unobserved := hru_nuo -- LHS (union) side: reads .fixed u. have hfU : SWIGNode.fixed u ∈ (M.fixSet (X₁ ∪ X₂) hObsU hFixU).fixed := fixed_mem_fixSet M (X₁ ∪ X₂) hObsU hFixU huU have hfU_nuo : SWIGNode.fixed u ∉ (M.fixSet (X₁ ∪ X₂) hObsU hFixU).unobserved := by intro h obtain ⟨m, hm⟩ := (M.fixSet (X₁ ∪ X₂) hObsU hFixU).unobserved_is_random _ h exact absurd hm (by simp) rw [fixMonoParentMap_apply_random_notMem M.toSWIGGraph X₁ hObs₁ hFix₁ w₀.val _ u hu1 hwMem, fixMonoParentMap_apply_random M.toSWIGGraph (X₁ ∪ X₂) hObsU hFixU w₀.val u huU _ hwMem] simp only [Subtype.coe_mk] rw [dif_neg hru_nd1uo, dif_neg hru_nd1fix, dif_neg hfU_nuo, dif_pos hfU] -- Goal: (M.fixSet X₁).evalMap sx₁ ℓ ⟨.random u⟩ = sxU ⟨.fixed u⟩. -- This is exactly hIntermediate u hu2 (up to membership-proof irrelevance). exact hIntermediate u hu2 · -- u ∉ X₁ ∪ X₂: both recurse; equal by IH at strictly smaller index. have huU : u ∉ X₁ ∪ X₂ := fun h => (Finset.mem_union.mp h).elim hu1 hu2 have hru_nd1fix : SWIGNode.random u ∉ (M.fixSet X₁ hObs₁ hFix₁).fixed := by intro h obtain ⟨m, hm⟩ := (M.fixSet X₁ hObs₁ hFix₁).fixed_is_fixed _ h exact absurd hm (by simp) have hru_ndUfix : SWIGNode.random u ∉ (M.fixSet (X₁ ∪ X₂) hObsU hFixU).fixed := by intro h obtain ⟨m, hm⟩ := (M.fixSet (X₁ ∪ X₂) hObsU hFixU).fixed_is_fixed _ h exact absurd hm (by simp) rw [fixMonoParentMap_apply_random_notMem M.toSWIGGraph X₁ hObs₁ hFix₁ w₀.val _ u hu1 hwMem, fixMonoParentMap_apply_random_notMem M.toSWIGGraph (X₁ ∪ X₂) hObsU hFixU w₀.val _ u huU hwMem] simp only [Subtype.coe_mk] -- Same three-way classification on both sides. split_ifs with hd hm hm · rfl · exact absurd hd hm · exact absurd hm hd · -- both observed: recurse via IH at strictly smaller M-index. have hru_obs : SWIGNode.random u ∈ M.observed := by rcases Finset.mem_union.mp (M.dag_edges_classified _ _ hedgeM).1 with hc1 | hc2 · rcases Finset.mem_union.mp hc1 with hfx | hob · exact absurd hfx hru_nfix · exact hob · exact absurd hc2 hm set j := M.observedIndex ⟨SWIGNode.random u, hru_obs⟩ with hj have hlt : j.val < n := M.observed_parent_index_lt hn hedgeM hru_obs have hihm := ih j.val hlt j.isLt have hat : (M.observedAt ⟨j.val, j.isLt⟩).val = SWIGNode.random u := M.observedAt_observedIndex ⟨SWIGNode.random u, hru_obs⟩ have hsub : (⟨(M.observedAt ⟨j.val, j.isLt⟩).val, Finset.mem_union_left M.unobserved (M.observedAt ⟨j.val, j.isLt⟩).property⟩ : {w // w ∈ M.randomVars}) = ⟨SWIGNode.random u, Finset.mem_union_left _ hru_obs⟩ := Subtype.ext hat -- IH gives `(union).evalMap = (X₁).evalMap`; goal here is the symm. refine symm (Eq.ndrec (motive := fun w : {w // w ∈ M.randomVars} => (M.fixSet (X₁ ∪ X₂) hObsU hFixU).evalMap sxU ℓ w = -- … truncated; follow the source link for the rest …
3 supporting declarations (lemmas, instances)
  • fixMono_structFun_apply lemma — After an intervention on a set of observed variables, each observed variable is still computed by the original structural equation, with intervened parents replaced by their assigned intervention values.
    M :
    SCM N Ω
    X :
    hObs :
    ∀ D ∈ X, SWIGNode.random D ∈ M.observed
    hFix :
    ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed
    v :
    {v // v ∈ (M.fixMono X hObs hFix).observed}
    ξ :
    ∀ w : {w // w ∈ (M.fixMono X hObs hFix).dag.parents v.val}, swigΩ Ω w.val
    (M.fixMono X hObs hFix).structFun v ξ
    = M.structFun ⟨v.val, v.property⟩ (fixMonoParentMap M.toSWIGGraph X hObs hFix v.val ξ)
    Proof (Lean source)
    @[simp] lemma fixMono_structFun_apply (M : SCM N Ω) (X : Finset N) (hObs : ∀ D ∈ X, SWIGNode.random D ∈ M.observed) (hFix : ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed) (v : {v // v ∈ (M.fixMono X hObs hFix).observed}) (ξ : ∀ w : {w // w ∈ (M.fixMono X hObs hFix).dag.parents v.val}, swigΩ Ω w.val) : (M.fixMono X hObs hFix).structFun v ξ = M.structFun ⟨v.val, v.property⟩ (fixMonoParentMap M.toSWIGGraph X hObs hFix v.val ξ) := by rfl
    Causalean.SCM.fixMono_structFun_apply · Causalean/SCM/Model/CounterfactualLemmas.lean:45
  • fixSet_structFun_apply lemma — After an intervention on a set of observed variables, each observed variable is still computed by the original structural equation, with intervened parents replaced by their assigned intervention values.
    M :
    SCM N Ω
    X :
    hObs :
    ∀ D ∈ X, SWIGNode.random D ∈ M.observed
    hFix :
    ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed
    v :
    {v // v ∈ (M.fixSet X hObs hFix).observed}
    ξ :
    ∀ w : {w // w ∈ (M.fixSet X hObs hFix).dag.parents v.val}, swigΩ Ω w.val
    (M.fixSet X hObs hFix).structFun v ξ
    = M.structFun ⟨v.val, v.property⟩ (fixMonoParentMap M.toSWIGGraph X hObs hFix v.val ξ)
    Proof (Lean source)
    @[simp] lemma fixSet_structFun_apply (M : SCM N Ω) (X : Finset N) (hObs : ∀ D ∈ X, SWIGNode.random D ∈ M.observed) (hFix : ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed) (v : {v // v ∈ (M.fixSet X hObs hFix).observed}) (ξ : ∀ w : {w // w ∈ (M.fixSet X hObs hFix).dag.parents v.val}, swigΩ Ω w.val) : (M.fixSet X hObs hFix).structFun v ξ = M.structFun ⟨v.val, v.property⟩ (fixMonoParentMap M.toSWIGGraph X hObs hFix v.val ξ) := by rfl
    Causalean.SCM.fixSet_structFun_apply · Causalean/SCM/Model/CounterfactualLemmas.lean:59
  • evalMap_fixSet_observed_apply lemma — For one recursive evaluation step after an intervention, the value of an observed variable is the original structural equation evaluated at the parent values where intervened parents are pinned to their assigned intervention values.
    M :
    SCM N Ω
    X :
    hObs :
    ∀ D ∈ X, SWIGNode.random D ∈ M.observed
    hFix :
    ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed
    sx :
    FixedValues (M.fixSet X hObs hFix)
    :
    v :
    {v // v ∈ M.observed}
    (M.fixSet X hObs hFix).evalMap sx ℓ ⟨v.val, mem_union_left _ v.property⟩
    = M.structFun v (fixMonoParentMap M.toSWIGGraph X hObs hFix v.val (fun w : {w // w ∈ (M.fixSet X hObs hFix).dag.parents v.val} => if huo : w.val ∈ (M.fixSet X hObs hFix).unobserved then ℓ ⟨w.val, huo⟩ else if hfix : w.val ∈ (M.fixSet X hObs hFix).fixed then sx ⟨w.val, hfix⟩ else have hedge : (M.fixSet X hObs hFix).dag.edge w.val v.val := (M.fixSet X hObs hFix).dag.mem_parents.mp w.property have hobs : w.val ∈ (M.fixSet X hObs hFix).observed := by rcases Finset.mem_union.mp ((M.fixSet X hObs hFix).dag_edges_classified _ _ hedge).1 with h1 | h2 · rcases Finset.mem_union.mp h1 with hfx | hob · exact absurd hfx hfix · exact hob · exact absurd h2 huo (M.fixSet X hObs hFix).evalMap sx ℓ ⟨w.val, mem_union_left _ hobs⟩))
    Proof (Lean source)
    lemma evalMap_fixSet_observed_apply (M : SCM N Ω) (X : Finset N) (hObs : ∀ D ∈ X, SWIGNode.random D ∈ M.observed) (hFix : ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed) (sx : FixedValues (M.fixSet X hObs hFix)) (ℓ : LatentValues M) (v : {v // v ∈ M.observed}) : (M.fixSet X hObs hFix).evalMap sx ℓ ⟨v.val, mem_union_left _ v.property⟩ = M.structFun v (fixMonoParentMap M.toSWIGGraph X hObs hFix v.val (fun w : {w // w ∈ (M.fixSet X hObs hFix).dag.parents v.val} => if huo : w.val ∈ (M.fixSet X hObs hFix).unobserved then ℓ ⟨w.val, huo⟩ else if hfix : w.val ∈ (M.fixSet X hObs hFix).fixed then sx ⟨w.val, hfix⟩ else have hedge : (M.fixSet X hObs hFix).dag.edge w.val v.val := (M.fixSet X hObs hFix).dag.mem_parents.mp w.property have hobs : w.val ∈ (M.fixSet X hObs hFix).observed := by rcases Finset.mem_union.mp ((M.fixSet X hObs hFix).dag_edges_classified _ _ hedge).1 with h1 | h2 · rcases Finset.mem_union.mp h1 with hfx | hob · exact absurd hfx hfix · exact hob · exact absurd h2 huo (M.fixSet X hObs hFix).evalMap sx ℓ ⟨w.val, mem_union_left _ hobs⟩)) := by rw [evalMap_observed_unfold (M.fixSet X hObs hFix) sx ℓ ⟨v.val, v.property⟩] rw [fixSet_structFun_apply] rfl
    Causalean.SCM.evalMap_fixSet_observed_apply · Causalean/SCM/Model/CounterfactualLemmas.lean:73
Cutset­Latent 3 core · 7 supporting This file isolates the block of latent roots that drive a target set Y without being mediated by an overridden block C. ★ exists_evalMap_overrideC_factors_cutset

The latent cutset C_W for the continuous-backdoor witness kernel

This file isolates the block of latent roots that drive a target set Y without being mediated by an overridden block C. A latent node belongs to the cutset when it reaches some node of Y along a directed path whose interior nodes all avoid C. The keystone result is a structural factorization: the override evaluation evalMap_overrideC with override block C depends on the latent assignment only through its values on the cutset, so two latent vectors that agree on the cutset produce the same override evaluation on Y.

Main definitions

* DAG.isAncestorAvoiding — there is a directed path from u to v whose strictly interior nodes all avoid a forbidden set C. * SCM.cutsetLatent — the latent roots reaching Y along a C-avoiding path (the block C_W).

Main results

* SCM.evalMap_overrideC_agree_cutset — the structural cutset factorization: agreement of two latent vectors on the cutset forces the override evaluations on Y to coincide. * SCM.exists_evalMap_overrideC_factors_cutset — packages the agreement lemma as a measurable factorization through the cutset projection.

inductive isAncestorAvoiding reviewed
Causalean.DAG

Avoiding ancestry means there is a directed path whose strictly interior nodes avoid a forbidden set.

Definition (Lean source)
inductive isAncestorAvoiding (C : Finset V) : V → V → Prop | edge {u v : V} : G.edge u v → isAncestorAvoiding C u v | trans {u w v : V} : isAncestorAvoiding C u w → w ∉ C → G.edge w v → isAncestorAvoiding C u v
Causalean.DAG.isAncestorAvoiding · Causalean/SCM/Model/CutsetLatent.lean:43 · uses DAG
def cutsetLatent reviewed
Causalean.SCM

The latent cutset contains the unobserved roots that can drive a target set along paths avoiding an override block.

Definition (Lean source)
noncomputable def cutsetLatent (M : SCM N Ω) (Y C : Finset (SWIGNode N)) : Finset (SWIGNode N) := letI : DecidablePred (fun u : SWIGNode N => ∃ y ∈ Y, u = y ∨ M.dag.isAncestorAvoiding C u y) := Classical.decPred _ M.unobserved.filter (fun u => ∃ y ∈ Y, u = y ∨ M.dag.isAncestorAvoiding C u y)
theorem exists_evalMap_overrideC_factors_cutset reviewed
Causalean.SCM

Cutset factorization of an override evaluation. Fix a structural causal model M, a target node set Y and an override node set C with Y contained in the observed nodes and C contained in the observed nodes, a fixed-value assignment s, and an override assignment c on C. Then there is a measurable map from assignments on the latent cutset of Y relative to C to assignments on Y such that, for every latent assignment, the overridden evaluation at Y equals this map applied to the latent assignment's projection onto the cutset.

Formal statement
M :
SCM N Ω
Y C :
hY :
Y ⊆ M.observed
hC :
C ⊆ M.observed
s :
c :
∃ h : ValuesOn (M.cutsetLatent Y C) (swigΩ Ω) → ValuesOn Y (swigΩ Ω),
∀ ℓ : LatentValues M,
M.evalMap_overrideC hY hC s c ℓ = h (valuesProjection (M.cutsetLatent_subset Y C) ℓ)
Proof (Lean source)
theorem exists_evalMap_overrideC_factors_cutset (M : SCM N Ω) {Y C : Finset (SWIGNode N)} (hY : Y ⊆ M.observed) (hC : C ⊆ M.observed) (s : FixedValues M) (c : ValuesOn C (swigΩ Ω)) : ∃ h : ValuesOn (M.cutsetLatent Y C) (swigΩ Ω) → ValuesOn Y (swigΩ Ω), Measurable h ∧ ∀ ℓ : LatentValues M, M.evalMap_overrideC hY hC s c ℓ = h (valuesProjection (M.cutsetLatent_subset Y C) ℓ) := by classical -- Extend a cutset projection to a full latent vector (default off the cutset). let ℓ₀ : LatentValues M := fun u => by letI : IsProbabilityMeasure (M.latentDist u) := M.isProbability_latent u exact MeasureTheory.nonempty_of_isProbabilityMeasure (M.latentDist u) |>.some let extend : ValuesOn (M.cutsetLatent Y C) (swigΩ Ω) → LatentValues M := fun cwProj u => if h : u.val ∈ M.cutsetLatent Y C then cwProj ⟨u.val, h⟩ else ℓ₀ u have hext_meas : Measurable extend := by refine measurable_pi_lambda _ (fun u => ?_) by_cases h : u.val ∈ M.cutsetLatent Y C · have : (fun cwProj : ValuesOn (M.cutsetLatent Y C) (swigΩ Ω) => extend cwProj u) = fun cwProj => cwProj ⟨u.val, h⟩ := by funext cwProj; simp [extend, h] rw [this]; exact measurable_pi_apply _ · have : (fun cwProj : ValuesOn (M.cutsetLatent Y C) (swigΩ Ω) => extend cwProj u) = fun _ => ℓ₀ u := by funext cwProj; simp [extend, h] rw [this]; exact measurable_const refine ⟨fun cwProj => M.evalMap_overrideC hY hC s c (extend cwProj), ?_, ?_⟩ · -- Measurability: override map (jointly measurable) precomposed with `extend`. have hcomp : (fun cwProj => M.evalMap_overrideC hY hC s c (extend cwProj)) = (fun ℓ : LatentValues M => M.evalMap_overrideC hY hC s c ℓ) ∘ extend := by funext cwProj; rfl rw [hcomp] have hcurry : (fun ℓ : LatentValues M => M.evalMap_overrideC hY hC s c ℓ) = (fun p : (M.FixedValues × ValuesOn C (swigΩ Ω)) × M.LatentValues => M.evalMap_overrideC hY hC p.1.1 p.1.2 p.2) ∘ (fun ℓ => ((s, c), ℓ)) := by funext ℓ; rfl rw [hcurry, Function.comp_assoc] exact (measurable_evalMap_overrideC M hY hC).comp ((measurable_const.prodMk measurable_id).comp hext_meas) · -- Agreement: `extend (proj ℓ)` agrees with `ℓ` on the cutset, so apply (A). intro ℓ change M.evalMap_overrideC hY hC s c ℓ = M.evalMap_overrideC hY hC s c (extend (valuesProjection (M.cutsetLatent_subset Y C) ℓ)) refine evalMap_overrideC_agree_cutset M hY hC s c ℓ _ ?_ funext u simp only [valuesProjection, extend, dif_pos u.property]
7 supporting declarations (lemmas, instances)
  • toIsAncestor theorem — Every avoiding ancestry relation is also ordinary directed ancestry.
    C :
    u v :
    V
    h :
    G.isAncestorAvoiding C u v
    G.isAncestor u v
    Proof (Lean source)
    theorem isAncestorAvoiding.toIsAncestor {C : Finset V} {u v : V} (h : G.isAncestorAvoiding C u v) : G.isAncestor u v := by induction h with | edge he => exact isAncestor.edge he | trans _ _ he ih => exact isAncestor.trans ih he
    Causalean.DAG.isAncestorAvoiding.toIsAncestor · Causalean/SCM/Model/CutsetLatent.lean:54
  • cons theorem — An avoiding path can be extended backward by one edge when the new interior node avoids the forbidden set.
    C :
    u w v :
    V
    he :
    G.edge u w
    hw :
    w ∉ C
    h :
    G.isAncestorAvoiding C w v
    G.isAncestorAvoiding C u v
    Proof (Lean source)
    theorem isAncestorAvoiding.cons {C : Finset V} {u w v : V} (he : G.edge u w) (hw : w ∉ C) (h : G.isAncestorAvoiding C w v) : G.isAncestorAvoiding C u v := by induction h with | edge he' => exact (isAncestorAvoiding.edge he).trans hw he' | trans _ hmid he' ih => exact ih.trans hmid he'
    Causalean.DAG.isAncestorAvoiding.cons · Causalean/SCM/Model/CutsetLatent.lean:64
  • exists_path theorem — Avoiding ancestry can be materialized as a concrete directed path with all strictly interior nodes avoiding the forbidden set.
    C :
    u v :
    V
    h :
    G.isAncestorAvoiding C u v
    ∃ q : List V,
    q.length ≥ 2 ∧
    q.head? = some u ∧
    q.getLast? = some v ∧
    (∀ (i : ℕ) (hi : i + 1 < q.length), G.edge (q.get ⟨i, by omega⟩) (q.get ⟨i + 1, hi⟩)) ∧
    (∀ (i : ℕ) (hi : i + 2 < q.length), q.get ⟨i + 1, by omega⟩ ∉ C)
    Proof (Lean source)
    theorem isAncestorAvoiding.exists_path {C : Finset V} {u v : V} (h : G.isAncestorAvoiding C u v) : ∃ q : List V, q.length ≥ 2 ∧ q.head? = some u ∧ q.getLast? = some v ∧ (∀ (i : ℕ) (hi : i + 1 < q.length), G.edge (q.get ⟨i, by omega⟩) (q.get ⟨i + 1, hi⟩)) ∧ (∀ (i : ℕ) (hi : i + 2 < q.length), q.get ⟨i + 1, by omega⟩ ∉ C) := by induction h with | edge he => rename_i u v refine ⟨[u, v], by simp, rfl, rfl, ?_, ?_⟩ · intro i hi have : i = 0 := by simp at hi; omega subst this; exact he · intro i hi; simp at hi | trans hav hwC he ih => rename_i u w v obtain ⟨q, hlen, hhead, hlast, hedge, hint⟩ := ih have hqne : q ≠ [] := by intro hq; rw [hq] at hlen; simp at hlen have hlen_q : (q ++ [v]).length = q.length + 1 := by rw [length_append, List.length_singleton] refine ⟨q ++ [v], ?_, ?_, ?_, ?_, ?_⟩ · rw [hlen_q]; omega · rw [List.head?_append_of_ne_nil _ hqne]; exact hhead · rw [getLast?_append]; simp · -- Directed edges: the new last edge `w → v` joins at the old last node `w`. intro i hi rw [hlen_q] at hi by_cases hlast_i : i + 1 = q.length · -- Seam edge: q[i] = q.getLast = w, and q ++ [v] at i+1 = v. have hi_lt : i < q.length := by omega have hwq : q.get ⟨i, hi_lt⟩ = w := by have hgl := getLast?_eq_some_getLast hqne rw [hlast] at hgl have hw_eq : q.getLast hqne = w := Option.some_inj.mp hgl.symm have hidx : (⟨i, hi_lt⟩ : Fin q.length) = ⟨q.length - 1, by omega⟩ := by simp only [Fin.mk.injEq]; omega rw [hidx, List.get_eq_getElem, ← hw_eq, List.getLast_eq_getElem] have hgi : (q ++ [v]).get ⟨i, by omega⟩ = q.get ⟨i, hi_lt⟩ := by rw [List.get_eq_getElem, List.get_eq_getElem, List.getElem_append_left (h := hi_lt)] have hgi1 : (q ++ [v]).get ⟨i + 1, by omega⟩ = v := by rw [List.get_eq_getElem, List.getElem_append_right (by simp; omega)] simp [hlast_i] rw [hgi, hgi1, hwq]; exact he · -- Edge fully inside `q`. have hi_lt : i + 1 < q.length := by omega have hi_lt0 : i < q.length := by omega have hgi : (q ++ [v]).get ⟨i, by omega⟩ = q.get ⟨i, hi_lt0⟩ := by rw [List.get_eq_getElem, List.get_eq_getElem, List.getElem_append_left (h := hi_lt0)] have hgi1 : (q ++ [v]).get ⟨i + 1, by omega⟩ = q.get ⟨i + 1, hi_lt⟩ := by rw [List.get_eq_getElem, List.get_eq_getElem, List.getElem_append_left (h := hi_lt)] rw [hgi, hgi1]; exact hedge i hi_lt · -- Interior avoidance: interior nodes of `q ++ [v]` are either interior of `q`, -- or the seam node `w` (the old last of `q`), which avoids `C` by `hwC`. intro i hi rw [hlen_q] at hi by_cases hlast_i : i + 1 = q.length - 1 · -- The seam node q[i+1] = q.getLast = w. have hi1_lt : i + 1 < q.length := by omega have hwq : q.get ⟨i + 1, hi1_lt⟩ = w := by have hgl := getLast?_eq_some_getLast hqne rw [hlast] at hgl have hw_eq : q.getLast hqne = w := Option.some_inj.mp hgl.symm have hidx : (⟨i + 1, hi1_lt⟩ : Fin q.length) = ⟨q.length - 1, by omega⟩ := by simp only [Fin.mk.injEq]; omega rw [hidx, List.get_eq_getElem, ← hw_eq, List.getLast_eq_getElem] have hgi1 : (q ++ [v]).get ⟨i + 1, by omega⟩ = q.get ⟨i + 1, hi1_lt⟩ := by rw [List.get_eq_getElem, List.get_eq_getElem, List.getElem_append_left (h := hi1_lt)] rw [hgi1, hwq]; exact hwC · -- Interior node of `q`: i + 2 < q.length. have hi2_lt : i + 2 < q.length := by omega have hi1_lt : i + 1 < q.length := by omega have hgi1 : (q ++ [v]).get ⟨i + 1, by omega⟩ = q.get ⟨i + 1, hi1_lt⟩ := by rw [List.get_eq_getElem, List.get_eq_getElem, List.getElem_append_left (h := hi1_lt)] rw [hgi1]; exact hint i hi2_lt
    Causalean.DAG.isAncestorAvoiding.exists_path · Causalean/SCM/Model/CutsetLatent.lean:76
  • mem_cutsetLatent theorem — Membership in the latent cutset means being unobserved and reaching the target set along an avoiding path.
    M :
    SCM N Ω
    Y C :
    u :
    u ∈ M.cutsetLatent Y C ↔ u ∈ M.unobserved ∧
    ∃ y ∈ Y, u = y ∨ M.dag.isAncestorAvoiding C u y
    Proof (Lean source)
    theorem mem_cutsetLatent (M : SCM N Ω) {Y C : Finset (SWIGNode N)} {u : SWIGNode N} : u ∈ M.cutsetLatent Y C ↔ u ∈ M.unobserved ∧ ∃ y ∈ Y, u = y ∨ M.dag.isAncestorAvoiding C u y := by letI : DecidablePred (fun u : SWIGNode N => ∃ y ∈ Y, u = y ∨ M.dag.isAncestorAvoiding C u y) := Classical.decPred _ change u ∈ M.unobserved.filter _ ↔ _ exact mem_filter
    Causalean.SCM.mem_cutsetLatent · Causalean/SCM/Model/CutsetLatent.lean:189
  • cutsetLatent_subset theorem — The latent cutset is contained in the model's unobserved node set.
    M :
    SCM N Ω
    Y C :
    M.cutsetLatent Y C ⊆ M.unobserved
    Proof (Lean source)
    theorem cutsetLatent_subset (M : SCM N Ω) (Y C : Finset (SWIGNode N)) : M.cutsetLatent Y C ⊆ M.unobserved := by intro u hu exact (M.mem_cutsetLatent.mp hu).1
    Causalean.SCM.cutsetLatent_subset · Causalean/SCM/Model/CutsetLatent.lean:202
  • evalObservedAuxOverride_agree_cutset lemma — If two latent assignments agree at every unobserved node that is a target or reaches a target along a directed path whose interior avoids the override block, their recursive override evaluations agree at every observed node that is a target or reaches one along such a path.
    M :
    SCM N Ω
    T :
    C :
    hC :
    C ⊆ M.observed
    s :
    c :
    ℓ₁ ℓ₂ :
    hAgree :
    ∀ (u : SWIGNode N) (hu : u ∈ M.unobserved)
    if
    (∃ t ∈ T, u = t ∨ M.dag.isAncestorAvoiding C u t)
    then
    ℓ₁ ⟨u, hu⟩ = ℓ₂ ⟨u, hu⟩
    ∀ (n : ℕ) (hn : n < M.observed.card) (_ : ∃ t ∈ T, (M.observedAt ⟨n, hn⟩).val = t ∨ M.dag.isAncestorAvoiding C (M.observedAt ⟨n, hn⟩).val t),
    evalObservedAuxOverride M hC s c ℓ₁ n hn = evalObservedAuxOverride M hC s c ℓ₂ n hn
    Proof (Lean source)
    lemma evalObservedAuxOverride_agree_cutset (M : SCM N Ω) (T : Finset (SWIGNode N)) {C : Finset (SWIGNode N)} (hC : C ⊆ M.observed) (s : FixedValues M) (c : ValuesOn C (swigΩ Ω)) (ℓ₁ ℓ₂ : LatentValues M) (hAgree : ∀ (u : SWIGNode N) (hu : u ∈ M.unobserved), (∃ t ∈ T, u = t ∨ M.dag.isAncestorAvoiding C u t) → ℓ₁ ⟨u, hu⟩ = ℓ₂ ⟨u, hu⟩) : ∀ (n : ℕ) (hn : n < M.observed.card) (_ : ∃ t ∈ T, (M.observedAt ⟨n, hn⟩).val = t ∨ M.dag.isAncestorAvoiding C (M.observedAt ⟨n, hn⟩).val t), evalObservedAuxOverride M hC s c ℓ₁ n hn = evalObservedAuxOverride M hC s c ℓ₂ n hn := by intro n induction n using Nat.strongRecOn with | _ n ih => intro hn hReach rw [evalObservedAuxOverride_eq M hC s c ℓ₁ n hn, evalObservedAuxOverride_eq M hC s c ℓ₂ n hn] by_cases hSelf : (M.observedAt ⟨n, hn⟩).val ∈ C · rw [dif_pos hSelf, dif_pos hSelf] · rw [dif_neg hSelf, dif_neg hSelf] congr 1 funext w have hedge : M.dag.edge w.val (M.observedAt ⟨n, hn⟩).val := M.dag.mem_parents.mp w.property -- Extend the avoiding witness from `observedAt n` through `w`. have hReachW : ∃ t ∈ T, w.val = t ∨ M.dag.isAncestorAvoiding C w.val t := by rcases hReach with ⟨t, ht, hOr⟩ refine ⟨t, ht, ?_⟩ rcases hOr with hEq | hAv · exact inr (hEq ▸ DAG.isAncestorAvoiding.edge hedge) · exact inr (DAG.isAncestorAvoiding.cons M.dag hedge hSelf hAv) by_cases huo : w.val ∈ M.unobserved · rw [parentMapOverride_unobserved M hC s c ℓ₁ hn _ w huo, parentMapOverride_unobserved M hC s c ℓ₂ hn _ w huo] exact hAgree w.val huo hReachW · by_cases hfix : w.val ∈ M.fixed · rw [parentMapOverride_fixed M hC s c ℓ₁ hn _ w hfix, parentMapOverride_fixed M hC s c ℓ₂ hn _ w hfix] · have hobs : w.val ∈ M.observed := by rcases Finset.mem_union.mp (M.dag_edges_classified _ _ hedge).1 with h1' | h2' · rcases Finset.mem_union.mp h1' with hfx | hob · exact absurd hfx hfix · exact hob · exact absurd h2' huo by_cases hcW : w.val ∈ C · rw [parentMapOverride_C M hC s c ℓ₁ hn _ w hcW, parentMapOverride_C M hC s c ℓ₂ hn _ w hcW] · rw [parentMapOverride_observed M hC s c ℓ₁ hn _ w hobs hcW, parentMapOverride_observed M hC s c ℓ₂ hn _ w hobs hcW] have hj : (M.observedIndex ⟨w.val, hobs⟩).val < n := M.observed_parent_index_lt hn hedge hobs congr 1 apply ih _ hj rcases hReachW with ⟨t, ht, hwt⟩ refine ⟨t, ht, ?_⟩ have h_at : (M.observedAt ⟨(M.observedIndex ⟨w.val, hobs⟩).val, (M.observedIndex ⟨w.val, hobs⟩).isLt⟩).val = w.val := M.observedAt_observedIndex ⟨w.val, hobs⟩ rw [h_at] exact hwt
    Causalean.SCM.evalObservedAuxOverride_agree_cutset · Causalean/SCM/Model/CutsetLatent.lean:213
  • evalMap_overrideC_agree_cutset theorem — The overridden evaluation on the target set depends on latent values only through the latent cutset.
    M :
    SCM N Ω
    Y C :
    hY :
    Y ⊆ M.observed
    hC :
    C ⊆ M.observed
    s :
    c :
    ℓ₁ ℓ₂ :
    hAgree :
    valuesProjection (M.cutsetLatent_subset Y C) ℓ₁
    = valuesProjection (M.cutsetLatent_subset Y C) ℓ₂
    M.evalMap_overrideC hY hC s c ℓ₁ = M.evalMap_overrideC hY hC s c ℓ₂
    Proof (Lean source)
    theorem evalMap_overrideC_agree_cutset (M : SCM N Ω) {Y C : Finset (SWIGNode N)} (hY : Y ⊆ M.observed) (hC : C ⊆ M.observed) (s : FixedValues M) (c : ValuesOn C (swigΩ Ω)) (ℓ₁ ℓ₂ : LatentValues M) (hAgree : valuesProjection (M.cutsetLatent_subset Y C) ℓ₁ = valuesProjection (M.cutsetLatent_subset Y C) ℓ₂) : M.evalMap_overrideC hY hC s c ℓ₁ = M.evalMap_overrideC hY hC s c ℓ₂ := by -- Turn the cutset-projection agreement into pointwise latent agreement on the -- avoiding-reachable latents. have hAgree' : ∀ (u : SWIGNode N) (hu : u ∈ M.unobserved), (∃ y ∈ Y, u = y ∨ M.dag.isAncestorAvoiding C u y) → ℓ₁ ⟨u, hu⟩ = ℓ₂ ⟨u, hu⟩ := by intro u hu hReach have huMem : u ∈ M.cutsetLatent Y C := M.mem_cutsetLatent.mpr ⟨hu, hReach⟩ have := congrFun hAgree ⟨u, huMem⟩ simpa [valuesProjection] using this funext v rw [evalMap_overrideC_eq M hY hC s c ℓ₁ v, evalMap_overrideC_eq M hY hC s c ℓ₂ v] congr 1 apply evalObservedAuxOverride_agree_cutset M Y hC s c ℓ₁ ℓ₂ hAgree' refine ⟨v.val, v.property, inl ?_⟩ exact M.observedAt_observedIndex ⟨v.val, hY v.property⟩
    Causalean.SCM.evalMap_overrideC_agree_cutset · Causalean/SCM/Model/CutsetLatent.lean:296
Equiv­Kernel 2 core · 4 supporting This file proves that structurally equivalent causal models have matching evaluation maps, latent product measures, joint kernels, and observational kernels. ★ evalMap_eq_of_equiv★ heq_obsKernel

Kernel Transport Across Equivalent Models

This file proves that structurally equivalent causal models have matching evaluation maps, latent product measures, joint kernels, and observational kernels. These transport results allow later do-calculus arguments to compare models whose topological orderings or proof witnesses differ but whose causal content is the same.

Main results

* SCM.evalMap_eq_of_equiv proves pointwise equality of evaluation maps under SCM.Equiv, assuming matching fixed and latent inputs. * SCM.Equiv.heq_latentProduct, SCM.Equiv.heq_jointKernel, and SCM.Equiv.heq_obsKernel transport the induced measures and kernels across equivalent structural causal models. * SCM.Equiv.heq_obsCondKernel transports observational conditional kernels when the conditioning and target coordinate sets are shared.

theorem evalMap_eq_of_equiv reviewed
Causalean.SCM

Cross-SCM pointwise equality of evalMap under SCM.Equiv. For two structural causal models M₁, M₂ that are structurally equivalent — the same graph up to topological reordering, matching edge types and have heterogeneously equal structural functions, fixed-value assignments s₁, s₂ and latent assignments ℓ₁, ℓ₂ on the two models, and a shared node w that is a random-variable node of M₁ and a random-variable node of M₂: if s₁ and s₂ agree coordinatewise on every fixed node shared by both models and ℓ₁ and ℓ₂ agree coordinatewise on every unobserved node shared by both models, then the two models' evaluation maps agree at w: M₁'s evaluation with (s₁, ℓ₁) equals M₂'s evaluation with (s₂, ℓ₂).

Formal statement
M₁ M₂ :
SCM N Ω
hGraph :
SWIGGraph.Equivalent M₁.toSWIGGraph M₂.toSWIGGraph
hSF :
HEq M₁.structFun M₂.structFun
s₁ :
ℓ₁ :
s₂ :
ℓ₂ :
hs :
∀ {d : SWIGNode N} (hd₁ : d ∈ M₁.fixed) (hd₂ : d ∈ M₂.fixed), s₁ ⟨d, hd₁⟩ = s₂ ⟨d, hd₂⟩
hℓ :
∀ {u : SWIGNode N} (hu₁ : u ∈ M₁.unobserved) (hu₂ : u ∈ M₂.unobserved),
ℓ₁ ⟨u, hu₁⟩ = ℓ₂ ⟨u, hu₂⟩
w :
hw₁ :
w ∈ M₁.randomVars
hw₂ :
w ∈ M₂.randomVars
M₁.evalMap s₁ ℓ₁ ⟨w, hw₁⟩ = M₂.evalMap s₂ ℓ₂ ⟨w, hw₂⟩
Proof (Lean source)
theorem evalMap_eq_of_equiv {M₁ M₂ : SCM N Ω} (hGraph : SWIGGraph.Equivalent M₁.toSWIGGraph M₂.toSWIGGraph) (hSF : HEq M₁.structFun M₂.structFun) (s₁ : FixedValues M₁) (ℓ₁ : LatentValues M₁) (s₂ : FixedValues M₂) (ℓ₂ : LatentValues M₂) (hs : ∀ {d : SWIGNode N} (hd₁ : d ∈ M₁.fixed) (hd₂ : d ∈ M₂.fixed), s₁ ⟨d, hd₁⟩ = s₂ ⟨d, hd₂⟩) (hℓ : ∀ {u : SWIGNode N} (hu₁ : u ∈ M₁.unobserved) (hu₂ : u ∈ M₂.unobserved), ℓ₁ ⟨u, hu₁⟩ = ℓ₂ ⟨u, hu₂⟩) {w : SWIGNode N} (hw₁ : w ∈ M₁.randomVars) (hw₂ : w ∈ M₂.randomVars) : M₁.evalMap s₁ ℓ₁ ⟨w, hw₁⟩ = M₂.evalMap s₂ ℓ₂ ⟨w, hw₂⟩ := by classical -- Case on `w ∈ M₁.observed` vs `w ∈ M₁.unobserved`. by_cases hw_obs₁ : w ∈ M₁.observed · -- Observed branch: strong induction on M₁-topological-index of `w`. have hw_obs₂ : w ∈ M₂.observed := hGraph.2.2.1 ▸ hw_obs₁ -- Reduce to a predicate indexed by the `Fin` index in M₁'s `observed`. suffices key : ∀ (n : ℕ), ∀ (v : SWIGNode N) (hv_obs₁ : v ∈ M₁.observed) (hv_obs₂ : v ∈ M₂.observed), (M₁.observedIndex ⟨v, hv_obs₁⟩).val = n → M₁.evalMap s₁ ℓ₁ ⟨v, mem_union_left _ hv_obs₁⟩ = M₂.evalMap s₂ ℓ₂ ⟨v, mem_union_left _ hv_obs₂⟩ by exact key (M₁.observedIndex ⟨w, hw_obs₁⟩).val w hw_obs₁ hw_obs₂ rfl intro n induction n using Nat.strong_induction_on with | _ n ih => intro v hv_obs₁ hv_obs₂ hn_eq -- Unfold both sides via `evalMap_observed_unfold`. rw [evalMap_observed_unfold M₁ s₁ ℓ₁ ⟨v, hv_obs₁⟩, evalMap_observed_unfold M₂ s₂ ℓ₂ ⟨v, hv_obs₂⟩] -- Reduce `structFun` application equality to pointwise parent agreement. apply structFun_apply_eq_of_equiv hGraph hSF v hv_obs₁ hv_obs₂ intro p hp₁ hp₂ -- Dispatch on where `p` lives: unobserved / fixed / observed. by_cases hpuo : p ∈ M₁.unobserved · have hpuo₂ : p ∈ M₂.unobserved := hGraph.2.2.2 ▸ hpuo rw [dif_pos hpuo, dif_pos hpuo₂] exact hℓ hpuo hpuo₂ · have hpuo₂ : p ∉ M₂.unobserved := fun h => hpuo (hGraph.2.2.2.symm ▸ h) rw [dif_neg hpuo, dif_neg hpuo₂] by_cases hpfix : p ∈ M₁.fixed · have hpfix₂ : p ∈ M₂.fixed := hGraph.2.1 ▸ hpfix rw [dif_pos hpfix, dif_pos hpfix₂] exact hs hpfix hpfix₂ · have hpfix₂ : p ∉ M₂.fixed := fun h => hpfix (hGraph.2.1.symm ▸ h) rw [dif_neg hpfix, dif_neg hpfix₂] -- Observed parent: extract hp_obs₁ and apply IH. have hedge₁ : M₁.dag.edge p v := M₁.dag.mem_parents.mp hp₁ have hp_obs₁ : p ∈ M₁.observed := by rcases Finset.mem_union.mp (M₁.dag_edges_classified _ _ hedge₁).1 with h1 | h2 · rcases Finset.mem_union.mp h1 with hfx | hob · exact absurd hfx hpfix · exact hob · exact absurd h2 hpuo have hp_obs₂ : p ∈ M₂.observed := hGraph.2.2.1 ▸ hp_obs₁ have hp_lt : (M₁.observedIndex ⟨p, hp_obs₁⟩).val < n := by have hlt : (M₁.observedIndex ⟨p, hp_obs₁⟩).val < (M₁.observedIndex ⟨v, hv_obs₁⟩).val := M₁.observed_parent_index_lt (M₁.observedIndex ⟨v, hv_obs₁⟩).isLt (by have := M₁.observedAt_observedIndex ⟨v, hv_obs₁⟩ rw [this] exact hedge₁) hp_obs₁ rw [hn_eq] at hlt exact hlt exact ih _ hp_lt p hp_obs₁ hp_obs₂ rfl · -- Unobserved branch. have hw_unobs₁ : w ∈ M₁.unobserved := by rcases Finset.mem_union.mp hw₁ with ho | hu · exact absurd ho hw_obs₁ · exact hu have hw_unobs₂ : w ∈ M₂.unobserved := hGraph.2.2.2 ▸ hw_unobs₁ rw [M₁.evalMap_unobserved s₁ ℓ₁ ⟨w, hw₁⟩ hw_unobs₁, M₂.evalMap_unobserved s₂ ℓ₂ ⟨w, hw₂⟩ hw_unobs₂] exact hℓ hw_unobs₁ hw_unobs₂
theorem heq_obsKernel reviewed
Causalean.SCM.Equiv

HEq transport for obsKernel. For two structural causal models M₁, M₂ that are structurally equivalent, have heterogeneously equal structural functions, and have heterogeneously equal latent-root distributions, then their observational kernels are heterogeneously equal.

Formal statement
M₁ M₂ :
SCM N Ω
hGraph :
SWIGGraph.Equivalent M₁.toSWIGGraph M₂.toSWIGGraph
hSF :
HEq M₁.structFun M₂.structFun
hLD :
HEq M₁.latentDist M₂.latentDist
HEq M₁.obsKernel M₂.obsKernel
Proof (Lean source)
theorem Equiv.heq_obsKernel {M₁ M₂ : SCM N Ω} (hGraph : SWIGGraph.Equivalent M₁.toSWIGGraph M₂.toSWIGGraph) (hSF : HEq M₁.structFun M₂.structFun) (hLD : HEq M₁.latentDist M₂.latentDist) : HEq M₁.obsKernel M₂.obsKernel := by have h_jk : HEq M₁.jointKernel M₂.jointKernel := Equiv.heq_jointKernel hGraph hSF hLD obtain ⟨⟨dag₁, fixed₁, observed₁, unobserved₁, fio₁, oi₁, od₁, oou₁, foi₁, fou₁, aic₁, dc₁⟩, eT₁, iota₁, sf₁, mf₁, lD₁, pL₁⟩ := M₁ obtain ⟨⟨dag₂, fixed₂, observed₂, unobserved₂, fio₂, oi₂, od₂, oou₂, foi₂, fou₂, aic₂, dc₂⟩, eT₂, iota₂, sf₂, mf₂, lD₂, pL₂⟩ := M₂ rcases hGraph with ⟨_hEdge, rfl, rfl, rfl⟩ have hLD_eq : lD₁ = lD₂ := eq_of_heq hLD subst hLD_eq apply heq_of_eq have hjk := eq_of_heq h_jk unfold SCM.obsKernel -- Goal: M₁.jointKernel.map M₁.randomToObserved = M₂.jointKernel.map M₂.randomToObserved congr 1
4 supporting declarations (lemmas, instances)
  • structFun_apply_eq_of_equiv theorem — Equivalent structural graphs with matching structural functions produce the same output at an observed node whenever corresponding parent values agree.
    M₁ M₂ :
    SCM N Ω
    hGraph :
    SWIGGraph.Equivalent M₁.toSWIGGraph M₂.toSWIGGraph
    hSF :
    HEq M₁.structFun M₂.structFun
    v :
    hv_obs₁ :
    v ∈ M₁.observed
    hv_obs₂ :
    v ∈ M₂.observed
    ξ₁ :
    (w : {w // w ∈ M₁.dag.parents v}) → swigΩ Ω w.val
    ξ₂ :
    (w : {w // w ∈ M₂.dag.parents v}) → swigΩ Ω w.val
    :
    ∀ (p : SWIGNode N) (hp₁ : p ∈ M₁.dag.parents v) (hp₂ : p ∈ M₂.dag.parents v),
    ξ₁ ⟨p, hp₁⟩ = ξ₂ ⟨p, hp₂⟩
    M₁.structFun ⟨v, hv_obs₁⟩ ξ₁ = M₂.structFun ⟨v, hv_obs₂⟩ ξ₂
    Proof (Lean source)
    theorem structFun_apply_eq_of_equiv {M₁ M₂ : SCM N Ω} (hGraph : SWIGGraph.Equivalent M₁.toSWIGGraph M₂.toSWIGGraph) (hSF : HEq M₁.structFun M₂.structFun) (v : SWIGNode N) (hv_obs₁ : v ∈ M₁.observed) (hv_obs₂ : v ∈ M₂.observed) {ξ₁ : (w : {w // w ∈ M₁.dag.parents v}) → swigΩ Ω w.val} {ξ₂ : (w : {w // w ∈ M₂.dag.parents v}) → swigΩ Ω w.val} (hξ : ∀ (p : SWIGNode N) (hp₁ : p ∈ M₁.dag.parents v) (hp₂ : p ∈ M₂.dag.parents v), ξ₁ ⟨p, hp₁⟩ = ξ₂ ⟨p, hp₂⟩) : M₁.structFun ⟨v, hv_obs₁⟩ ξ₁ = M₂.structFun ⟨v, hv_obs₂⟩ ξ₂ := by have hObsEq : M₁.observed = M₂.observed := hGraph.2.2.1 have pEq : M₁.dag.parents v = M₂.dag.parents v := SWIGGraph.Equivalent.parents_eq hGraph.1 v -- Target: both sides live in `swigΩ Ω v`. Lift to HEq, then back to Eq. apply eq_of_heq -- Step 1: HEq of the `structFun` applications at `⟨v, ·⟩`. -- M₁.structFun ⟨v, hv_obs₁⟩ : (inputs₁) → swigΩ Ω v -- M₂.structFun ⟨v, hv_obs₂⟩ : (inputs₂) → swigΩ Ω v -- where inputs_i = (w : {w // w ∈ M_i.dag.parents v}) → swigΩ Ω w.val. -- `hSF` gives HEq of the full structFuns; apply it at v-level. have hvHeq : (⟨v, hv_obs₁⟩ : {v // v ∈ M₁.observed}) ≍ (⟨v, hv_obs₂⟩ : {v // v ∈ M₂.observed}) := by apply (Subtype.heq_iff_coe_eq (by intro x; rw [hObsEq])).mpr rfl have hApp1 : HEq (M₁.structFun ⟨v, hv_obs₁⟩) (M₂.structFun ⟨v, hv_obs₂⟩) := by apply dcongr_heq hvHeq · -- β₁ t₁ = β₂ t₂ when t₁ ≍ t₂ (same .val). intro t₁ t₂ ht have hval : t₁.val = t₂.val := by have := (Subtype.heq_iff_coe_eq (by intro x; rw [hObsEq])).mp ht exact this -- Output β is `((w : ...parents t.val) → ...) → swigΩ Ω t.val`, depends on t.val. rw [hval] -- Remaining type difference is in parents via `M₁.dag.parents` vs `M₂.dag.parents`. rw [SWIGGraph.Equivalent.parents_eq hGraph.1 t₂.val] · intro _ _; exact hSF -- Step 2: HEq of the parent tuples. have hξHeq : HEq ξ₁ ξ₂ := by apply Function.hfunext (by rw [pEq]) rintro ⟨p₁val, p₁prop⟩ ⟨p₂val, p₂prop⟩ hp have hval : p₁val = p₂val := by have := (Subtype.heq_iff_coe_eq (by intro x; rw [pEq])).mp hp exact this subst hval apply heq_of_eq exact hξ p₁val p₁prop p₂prop -- Step 3: combine hApp1 and hξHeq via `congr_heq` (common codomain `swigΩ Ω v`). exact heq_of_eq (congr_heq hApp1 hξHeq)
    Causalean.SCM.structFun_apply_eq_of_equiv · Causalean/SCM/Model/EquivKernel.lean:40
  • heq_latentProduct theorem — HEq transport for latentProduct.
    M₁ M₂ :
    SCM N Ω
    hUnobs :
    M₁.unobserved = M₂.unobserved
    hLD :
    HEq M₁.latentDist M₂.latentDist
    HEq M₁.latentProduct M₂.latentProduct
    Proof (Lean source)
    theorem Equiv.heq_latentProduct {M₁ M₂ : SCM N Ω} (hUnobs : M₁.unobserved = M₂.unobserved) (hLD : HEq M₁.latentDist M₂.latentDist) : HEq M₁.latentProduct M₂.latentProduct := by obtain ⟨⟨dag₁, fixed₁, observed₁, unobserved₁, fio₁, oi₁, od₁, oou₁, foi₁, fou₁, aic₁, dc₁⟩, eT₁, iota₁, sf₁, mf₁, lD₁, pL₁⟩ := M₁ obtain ⟨⟨dag₂, fixed₂, observed₂, unobserved₂, fio₂, oi₂, od₂, oou₂, foi₂, fou₂, aic₂, dc₂⟩, eT₂, iota₂, sf₂, mf₂, lD₂, pL₂⟩ := M₂ subst hUnobs -- After subst, lD₁, lD₂ : (u : {u // u ∈ unobserved₁}) → Measure (swigΩ Ω u.val) -- have the same type. have hLD_eq : lD₁ = lD₂ := eq_of_heq hLD subst hLD_eq -- Both sides are `Measure.pi (fun u => lD₁ u)`, the `pL₁`/`pL₂` measurability -- witnesses are `Subsingleton`. rfl
    Causalean.SCM.Equiv.heq_latentProduct · Causalean/SCM/Model/EquivKernel.lean:216
  • heq_jointKernel theorem — HEq transport for jointKernel.
    M₁ M₂ :
    SCM N Ω
    hGraph :
    SWIGGraph.Equivalent M₁.toSWIGGraph M₂.toSWIGGraph
    hSF :
    HEq M₁.structFun M₂.structFun
    hLD :
    HEq M₁.latentDist M₂.latentDist
    HEq M₁.jointKernel M₂.jointKernel
    Proof (Lean source)
    theorem Equiv.heq_jointKernel {M₁ M₂ : SCM N Ω} (hGraph : SWIGGraph.Equivalent M₁.toSWIGGraph M₂.toSWIGGraph) (hSF : HEq M₁.structFun M₂.structFun) (hLD : HEq M₁.latentDist M₂.latentDist) : HEq M₁.jointKernel M₂.jointKernel := by -- Extract the latent-product HEq first, while `h` still has its -- original (non-destructured) type. Chaining into `eq_of_heq` later -- would force the two `latentProduct` types to unify, prematurely -- identifying the M₁, M₂ metas of `heq_latentProduct`. have h_lp : HEq M₁.latentProduct M₂.latentProduct := Equiv.heq_latentProduct hGraph.2.2.2 hLD have h_evalMap_eq := evalMap_eq_of_equiv hGraph hSF obtain ⟨⟨dag₁, fixed₁, observed₁, unobserved₁, fio₁, oi₁, od₁, oou₁, foi₁, fou₁, aic₁, dc₁⟩, eT₁, iota₁, sf₁, mf₁, lD₁, pL₁⟩ := M₁ obtain ⟨⟨dag₂, fixed₂, observed₂, unobserved₂, fio₂, oi₂, od₂, oou₂, foi₂, fou₂, aic₂, dc₂⟩, eT₂, iota₂, sf₂, mf₂, lD₂, pL₂⟩ := M₂ rcases hGraph with ⟨_hEdge, rfl, rfl, rfl⟩ have hLD_eq : lD₁ = lD₂ := eq_of_heq hLD subst hLD_eq -- After subst: FixedValues, RandomValues, latentProduct all have matching types. apply heq_of_eq have hlp := eq_of_heq h_lp -- Work at the compProd definition to avoid rw-matching issues on `jointKernel`. unfold SCM.jointKernel -- Goal: (const _ lp₁ ⊗ₖ det₁).map snd = (const _ lp₂ ⊗ₖ det₂).map snd congr 1 -- Goal: const _ lp₁ ⊗ₖ det₁ = const _ lp₂ ⊗ₖ det₂ rw [hlp] -- Goal: const _ lp₂ ⊗ₖ det₁ = const _ lp₂ ⊗ₖ det₂ congr 1 -- Goal: det₁ = det₂ (Kernel.deterministic (uncurry evalMap_i) _) apply ProbabilityTheory.Kernel.ext rintro ⟨s, ℓ⟩ simp only [ProbabilityTheory.Kernel.deterministic_apply] congr 1 apply funext rintro ⟨w, hw⟩ exact h_evalMap_eq s ℓ s ℓ (fun _ _ => rfl) (fun _ _ => rfl) hw hw
    Causalean.SCM.Equiv.heq_jointKernel · Causalean/SCM/Model/EquivKernel.lean:245
  • heq_obsCondKernel theorem — HEq transport for obsCondKernel.
    M₁ M₂ :
    SCM N Ω
    hGraph :
    SWIGGraph.Equivalent M₁.toSWIGGraph M₂.toSWIGGraph
    hSF :
    HEq M₁.structFun M₂.structFun
    hLD :
    HEq M₁.latentDist M₂.latentDist
    Y CC :
    hY₁ :
    Y ⊆ M₁.observed
    hCC₁ :
    CC ⊆ M₁.observed
    Nonempty (ValuesOn Y (swigΩ Ω))
    let hY₂ : Y ⊆ M₂.observed := by intro v hv rw [← hGraph.2.2.1] exact hY₁ hv let hCC₂ : CC ⊆ M₂.observed := by intro v hv rw [← hGraph.2.2.1] exact hCC₁ hv HEq (M₁.obsCondKernel Y CC hY₁ hCC₁) (M₂.obsCondKernel Y CC hY₂ hCC₂)
    Proof (Lean source)
    theorem Equiv.heq_obsCondKernel {M₁ M₂ : SCM N Ω} (hGraph : SWIGGraph.Equivalent M₁.toSWIGGraph M₂.toSWIGGraph) (hSF : HEq M₁.structFun M₂.structFun) (hLD : HEq M₁.latentDist M₂.latentDist) (Y CC : Finset (SWIGNode N)) (hY₁ : Y ⊆ M₁.observed) (hCC₁ : CC ⊆ M₁.observed) [StandardBorelSpace (ValuesOn Y (swigΩ Ω))] [Nonempty (ValuesOn Y (swigΩ Ω))] [CountableOrCountablyGenerated (FixedValues M₁) (ValuesOn CC (swigΩ Ω))] [CountableOrCountablyGenerated (FixedValues M₂) (ValuesOn CC (swigΩ Ω))] : let hY₂ : Y ⊆ M₂.observed := by intro v hv rw [← hGraph.2.2.1] exact hY₁ hv let hCC₂ : CC ⊆ M₂.observed := by intro v hv rw [← hGraph.2.2.1] exact hCC₁ hv HEq (M₁.obsCondKernel Y CC hY₁ hCC₁) (M₂.obsCondKernel Y CC hY₂ hCC₂) := by dsimp apply heq_obsCondKernel_aux hGraph hSF hLD Y CC hY₁
    Causalean.SCM.Equiv.heq_obsCondKernel · Causalean/SCM/Model/EquivKernel.lean:378
Eval­Factorization 4 core · 4 supporting This file proves that values produced by the evaluation map factor through the appropriate parent or ancestor coordinates. ★ evalMap_factors_through_parents★ evalMap_factors_through_ancestors

Evaluation Map Factorization

This file proves that values produced by the evaluation map factor through the appropriate parent or ancestor coordinates. These factorization results provide the measurable-function inputs needed for local and global Markov arguments in structural causal models.

Main definitions and results

* SCM.evalMap_factors_through_parents expresses an observed node value as a measurable function of the evaluated random parent coordinates. * SCM.latentAncestorsOfNode and SCM.fixedAncestorsOfNode collect the latent and fixed ancestor coordinates relevant to a single observed node. * SCM.evalMap_factors_through_ancestors upgrades ancestral agreement into a measurable factorization through the fixed-ancestor and latent-ancestor projections.

theorem evalMap_factors_through_parents reviewed
Causalean.SCM

Parent factorization of evalMap at observed nodes. Fix a structural causal model M, a fixed-value assignment s, and a node v with v an observed node. Then there is a measurable function g of the assignment restricted to v's random parent coordinates such that, for every latent assignment , the evaluated value at v equals g applied to that restriction of the joint evaluation.

Formal statement
M :
SCM N Ω
s :
v :
hv :
v ∈ M.observed
∃ g : ValuesOn (M.dag.parents v ∩ M.randomVars) (swigΩ Ω) → swigΩ Ω v,
∀ ℓ : LatentValues M,
M.evalMap s ℓ ⟨v, mem_union_left _ hv⟩
= g (valuesProjection (inter_subset_right (s₁ := M.dag.parents v) (s₂ := M.randomVars)) (M.evalMap s ℓ))
Proof (Lean source)
theorem evalMap_factors_through_parents (M : SCM N Ω) (s : FixedValues M) (v : SWIGNode N) (hv : v ∈ M.observed) : ∃ g : ValuesOn (M.dag.parents v ∩ M.randomVars) (swigΩ Ω) → swigΩ Ω v, Measurable g ∧ ∀ ℓ : LatentValues M, M.evalMap s ℓ ⟨v, mem_union_left _ hv⟩ = g (valuesProjection (inter_subset_right (s₁ := M.dag.parents v) (s₂ := M.randomVars)) (M.evalMap s ℓ)) := by classical -- Disjointness helper: a random-root node is not fixed. have hfix_disj_unobs : ∀ w : SWIGNode N, w ∈ M.unobserved → w ∉ M.fixed := by intro w hu hf obtain ⟨n, hn⟩ := M.fixed_is_fixed w hf obtain ⟨m, hm⟩ := M.unobserved_is_random w hu rw [hn] at hm; cases hm -- Build `g`: for each parent `w`, either read `s` (if fixed) or read the -- projection `t` (otherwise). `w ∈ parents v` lands in `fixed ∪ randomVars` -- via `dag_edges_classified`. refine ⟨fun t => M.structFun ⟨v, hv⟩ (fun w : {w // w ∈ M.dag.parents v} => if hfix : w.val ∈ M.fixed then s ⟨w.val, hfix⟩ else have hedge : M.dag.edge w.val v := M.dag.mem_parents.mp w.property have hrand : w.val ∈ M.randomVars := by rcases Finset.mem_union.mp (M.dag_edges_classified _ _ hedge).1 with h1 | h2 · rcases Finset.mem_union.mp h1 with hfx | hob · exact absurd hfx hfix · exact mem_union_left _ hob · exact mem_union_right _ h2 t ⟨w.val, Finset.mem_inter.mpr ⟨w.property, hrand⟩⟩), ?_, ?_⟩ · -- Measurability: structFun ∘ (assembly of constants and coord-reads). refine (M.structFun_measurable ⟨v, hv⟩).comp ?_ refine measurable_pi_lambda _ (fun w => ?_) by_cases hfix : w.val ∈ M.fixed · simp only [dif_pos hfix] exact measurable_const · simp only [dif_neg hfix] exact measurable_pi_apply _ · -- Pointwise agreement via `evalMap_observed_unfold`. intro ℓ rw [evalMap_observed_unfold M s ℓ ⟨v, hv⟩] congr 1 funext w by_cases hfix : w.val ∈ M.fixed · -- Fixed parent: both sides are `s ⟨w.val, hfix⟩`. On the unfold side -- need `w.val ∉ M.unobserved` to peel the first `dite`. have huo : w.val ∉ M.unobserved := fun hu => hfix_disj_unobs _ hu hfix simp only [dif_neg huo, dif_pos hfix] · -- Non-fixed: both sides read the random coord at `w`. have hedge : M.dag.edge w.val v := M.dag.mem_parents.mp w.property have hrand : w.val ∈ M.randomVars := by rcases Finset.mem_union.mp (M.dag_edges_classified _ _ hedge).1 with h1 | h2 · rcases Finset.mem_union.mp h1 with hfx | hob · exact absurd hfx hfix · exact mem_union_left _ hob · exact mem_union_right _ h2 simp only [dif_neg hfix] by_cases huo : w.val ∈ M.unobserved · -- Unobserved parent: unfold side gives `ℓ ⟨w, huo⟩`, right side gives -- `evalMap s ℓ ⟨w, _⟩` which collapses to `ℓ ⟨w, huo⟩` via -- `evalMap_unobserved`. simp only [dif_pos huo] rw [valuesProjection, evalMap_unobserved M s ℓ _ huo] · -- Observed parent: unfold side gives `evalMap s ℓ ⟨w, _⟩`, right side -- is the same projection read. simp only [dif_neg huo] rfl
def latentAncestorsOfNode reviewed
Causalean.SCM

The latent ancestors of a node are the unobserved nodes equal to that node or ancestral to it.

Definition (Lean source)
noncomputable def latentAncestorsOfNode (M : SCM N Ω) (v : SWIGNode N) : Finset (SWIGNode N) := letI : DecidablePred (fun u : SWIGNode N => u = v ∨ M.dag.isAncestor u v) := Classical.decPred _ M.unobserved.filter (fun u => u = v ∨ M.dag.isAncestor u v)
Causalean.SCM.latentAncestorsOfNode · Causalean/SCM/Model/EvalFactorization.lean:151 · uses SCM , SWIGNode
def fixedAncestorsOfNode reviewed
Causalean.SCM

The fixed ancestors of a node are the fixed nodes equal to that node or ancestral to it.

Definition (Lean source)
noncomputable def fixedAncestorsOfNode (M : SCM N Ω) (v : SWIGNode N) : Finset (SWIGNode N) := letI : DecidablePred (fun d : SWIGNode N => d = v ∨ M.dag.isAncestor d v) := Classical.decPred _ M.fixed.filter (fun d => d = v ∨ M.dag.isAncestor d v)
Causalean.SCM.fixedAncestorsOfNode · Causalean/SCM/Model/EvalFactorization.lean:167 · uses SCM , SWIGNode
theorem evalMap_factors_through_ancestors reviewed
Causalean.SCM

Ancestor factorization (existence form). Fix a structural causal model M and a node v with v an observed node. Then there is a measurable function g of the fixed-ancestor projection and the latent-ancestor projection of v such that, for every fixed-value assignment s and latent assignment , the evaluated value at v equals g applied to those two projections of s and .

Formal statement
M :
SCM N Ω
∀ n, Nonempty (Ω n)
v :
hv :
v ∈ M.observed
∃ g : ValuesOn (M.fixedAncestorsOfNode v) (swigΩ Ω) → ValuesOn (M.latentAncestorsOfNode v) (swigΩ Ω) → swigΩ Ω v,
∀ (s : FixedValues M) (ℓ : LatentValues M),
M.evalMap s ℓ ⟨v, mem_union_left _ hv⟩
= g (valuesProjection (M.fixedAncestorsOfNode_subset v) s) (valuesProjection (M.latentAncestorsOfNode_subset v) ℓ)
Proof (Lean source)
theorem evalMap_factors_through_ancestors (M : SCM N Ω) [∀ n, Nonempty (Ω n)] (v : SWIGNode N) (hv : v ∈ M.observed) : ∃ g : ValuesOn (M.fixedAncestorsOfNode v) (swigΩ Ω) → ValuesOn (M.latentAncestorsOfNode v) (swigΩ Ω) → swigΩ Ω v, Measurable (uncurry g) ∧ ∀ (s : FixedValues M) (ℓ : LatentValues M), M.evalMap s ℓ ⟨v, mem_union_left _ hv⟩ = g (valuesProjection (M.fixedAncestorsOfNode_subset v) s) (valuesProjection (M.latentAncestorsOfNode_subset v) ℓ) := by classical -- `swigΩ Ω w` is nonempty for every `w` since `Ω n` is. have hNEΩ : ∀ w : SWIGNode N, Nonempty (swigΩ Ω w) := by intro w; cases w <;> exact inferInstance -- Default full assignments. let s₀ : FixedValues M := fun d => Classical.arbitrary _ let ℓ₀ : LatentValues M := fun u => Classical.arbitrary _ -- Extension maps: read from projection on ancestor coords, default otherwise. let extend_s : ValuesOn (M.fixedAncestorsOfNode v) (swigΩ Ω) → FixedValues M := fun sproj d => if h : d.val ∈ M.fixedAncestorsOfNode v then sproj ⟨d.val, h⟩ else s₀ d let extend_ℓ : ValuesOn (M.latentAncestorsOfNode v) (swigΩ Ω) → LatentValues M := fun ℓproj u => if h : u.val ∈ M.latentAncestorsOfNode v then ℓproj ⟨u.val, h⟩ else ℓ₀ u have hext_s_meas : Measurable extend_s := by refine measurable_pi_lambda _ (fun d => ?_) by_cases h : d.val ∈ M.fixedAncestorsOfNode v · have : (fun sproj : ValuesOn (M.fixedAncestorsOfNode v) (swigΩ Ω) => extend_s sproj d) = fun sproj => sproj ⟨d.val, h⟩ := by funext sproj; simp [extend_s, h] rw [this]; exact measurable_pi_apply _ · have : (fun sproj : ValuesOn (M.fixedAncestorsOfNode v) (swigΩ Ω) => extend_s sproj d) = fun _ => s₀ d := by funext sproj; simp [extend_s, h] rw [this]; exact measurable_const have hext_ℓ_meas : Measurable extend_ℓ := by refine measurable_pi_lambda _ (fun u => ?_) by_cases h : u.val ∈ M.latentAncestorsOfNode v · have : (fun ℓproj : ValuesOn (M.latentAncestorsOfNode v) (swigΩ Ω) => extend_ℓ ℓproj u) = fun ℓproj => ℓproj ⟨u.val, h⟩ := by funext ℓproj; simp [extend_ℓ, h] rw [this]; exact measurable_pi_apply _ · have : (fun ℓproj : ValuesOn (M.latentAncestorsOfNode v) (swigΩ Ω) => extend_ℓ ℓproj u) = fun _ => ℓ₀ u := by funext ℓproj; simp [extend_ℓ, h] rw [this]; exact measurable_const refine ⟨fun sproj ℓproj => M.evalMap (extend_s sproj) (extend_ℓ ℓproj) ⟨v, mem_union_left _ hv⟩, ?_, ?_⟩ · -- Measurability: `evalMap` is jointly measurable; precompose with the -- extensions (measurable) and post-project the `v` coordinate. have hpair : Measurable (fun p : ValuesOn (M.fixedAncestorsOfNode v) (swigΩ Ω) × ValuesOn (M.latentAncestorsOfNode v) (swigΩ Ω) => (extend_s p.1, extend_ℓ p.2)) := prodMk (hext_s_meas.comp measurable_fst) (hext_ℓ_meas.comp measurable_snd) have huncurry : (fun p : ValuesOn (M.fixedAncestorsOfNode v) (swigΩ Ω) × ValuesOn (M.latentAncestorsOfNode v) (swigΩ Ω) => M.evalMap (extend_s p.1) (extend_ℓ p.2) ⟨v, mem_union_left _ hv⟩) = (fun w : RandomValues M => w ⟨v, mem_union_left _ hv⟩) ∘ (fun p => uncurry M.evalMap (extend_s p.1, extend_ℓ p.2)) := by funext p; rfl change Measurable (fun p : ValuesOn (M.fixedAncestorsOfNode v) (swigΩ Ω) × ValuesOn (M.latentAncestorsOfNode v) (swigΩ Ω) => M.evalMap (extend_s p.1) (extend_ℓ p.2) ⟨v, mem_union_left _ hv⟩) rw [huncurry] exact (measurable_pi_apply _).comp (M.evalMap_measurable.comp hpair) · -- Pointwise agreement via `ancestralFactorization` with `T := {v}`. intro s ℓ symm have hT_sub : ({v} : Finset (SWIGNode N)) ⊆ M.observed := Finset.singleton_subset_iff.mpr hv have hv_mem : v ∈ ({v} : Finset (SWIGNode N)) := Finset.mem_singleton.mpr rfl refine M.ancestralFactorization ({v} : Finset (SWIGNode N)) ?_ ?_ hv_mem hv · -- Agreement on fixed ancestors of {v}. intro d hd hAnc obtain ⟨v', hv', hOr⟩ := hAnc have hv'eq : v' = v := Finset.mem_singleton.mp hv' have hOr' : d = v ∨ M.dag.isAncestor d v := hv'eq ▸ hOr have hd_anc : d ∈ M.fixedAncestorsOfNode v := (M.mem_fixedAncestorsOfNode).mpr ⟨hd, hOr'⟩ change extend_s (valuesProjection (M.fixedAncestorsOfNode_subset v) s) ⟨d, hd⟩ = s ⟨d, hd⟩ simp [extend_s, hd_anc, valuesProjection] · -- Agreement on latent ancestors of {v}. intro u hu hAnc obtain ⟨v', hv', hOr⟩ := hAnc have hv'eq : v' = v := Finset.mem_singleton.mp hv' have hOr' : u = v ∨ M.dag.isAncestor u v := hv'eq ▸ hOr have hu_anc : u ∈ M.latentAncestorsOfNode v := (M.mem_latentAncestorsOfNode).mpr ⟨hu, hOr'⟩ change extend_ℓ (valuesProjection (M.latentAncestorsOfNode_subset v) ℓ) ⟨u, hu⟩ = ℓ ⟨u, hu⟩ simp [extend_ℓ, hu_anc, valuesProjection]
4 supporting declarations (lemmas, instances)
  • mem_latentAncestorsOfNode lemma — Membership in the latent-ancestor set means being unobserved and equal to or ancestral to the node.
    M :
    SCM N Ω
    v u :
    u ∈ M.latentAncestorsOfNode v ↔ u ∈ M.unobserved ∧ (u = v ∨ M.dag.isAncestor u v)
    Proof (Lean source)
    lemma mem_latentAncestorsOfNode (M : SCM N Ω) {v u : SWIGNode N} : u ∈ M.latentAncestorsOfNode v ↔ u ∈ M.unobserved ∧ (u = v ∨ M.dag.isAncestor u v) := by letI : DecidablePred (fun u : SWIGNode N => u = v ∨ M.dag.isAncestor u v) := Classical.decPred _ change u ∈ M.unobserved.filter (fun u => u = v ∨ M.dag.isAncestor u v) ↔ _ exact mem_filter
    Causalean.SCM.mem_latentAncestorsOfNode · Causalean/SCM/Model/EvalFactorization.lean:175
  • mem_fixedAncestorsOfNode lemma — Membership in the fixed-ancestor set means being fixed and equal to or ancestral to the node.
    M :
    SCM N Ω
    v d :
    d ∈ M.fixedAncestorsOfNode v ↔ d ∈ M.fixed ∧ (d = v ∨ M.dag.isAncestor d v)
    Proof (Lean source)
    lemma mem_fixedAncestorsOfNode (M : SCM N Ω) {v d : SWIGNode N} : d ∈ M.fixedAncestorsOfNode v ↔ d ∈ M.fixed ∧ (d = v ∨ M.dag.isAncestor d v) := by letI : DecidablePred (fun d : SWIGNode N => d = v ∨ M.dag.isAncestor d v) := Classical.decPred _ change d ∈ M.fixed.filter (fun d => d = v ∨ M.dag.isAncestor d v) ↔ _ exact mem_filter
    Causalean.SCM.mem_fixedAncestorsOfNode · Causalean/SCM/Model/EvalFactorization.lean:185
  • fixedAncestorsOfNode_subset lemma — The fixed-ancestor set is contained in the fixed node set.
    M :
    SCM N Ω
    v :
    M.fixedAncestorsOfNode v ⊆ M.fixed
    Proof (Lean source)
    lemma fixedAncestorsOfNode_subset (M : SCM N Ω) (v : SWIGNode N) : M.fixedAncestorsOfNode v ⊆ M.fixed := by intro d hd exact ((M.mem_fixedAncestorsOfNode).mp hd).1
    Causalean.SCM.fixedAncestorsOfNode_subset · Causalean/SCM/Model/EvalFactorization.lean:194
  • latentAncestorsOfNode_subset lemma — The latent-ancestor set is contained in the unobserved node set.
    M :
    SCM N Ω
    v :
    M.latentAncestorsOfNode v ⊆ M.unobserved
    Proof (Lean source)
    lemma latentAncestorsOfNode_subset (M : SCM N Ω) (v : SWIGNode N) : M.latentAncestorsOfNode v ⊆ M.unobserved := by intro u hu exact ((M.mem_latentAncestorsOfNode).mp hu).1
    Causalean.SCM.latentAncestorsOfNode_subset · Causalean/SCM/Model/EvalFactorization.lean:200
Eval­Latent 1 core · 0 supporting This file proves that, for a latent root and a set of non-descendant random coordinates, the corresponding evaluation projection does not depend on the chosen latent coordinate. ★ evalMap_factors_excluding_latent

Latent-Restricted Evaluation Factorization

This file proves that, for a latent root and a set of non-descendant random coordinates, the corresponding evaluation projection does not depend on the chosen latent coordinate. The result is used to establish independence claims for the global Markov property of structural causal models.

The main theorem, SCM.evalMap_factors_excluding_latent, states that if T ⊆ M.randomVars contains no descendants of a latent root a, then the projection of M.evalMap s to T factors through all latent coordinates except the coordinate at a.

theorem evalMap_factors_excluding_latent reviewed
Causalean.SCM

Latent-restricted factorization away from a chosen latent root. Fix a structural causal model M, a fixed-value assignment s, and a latent root node a. For a set T of random-variable nodes such that no node of T is a descendant of a, and none equals a, then the projection of the evaluation evalMap s to T factors through a measurable function of the latent coordinates other than a's — i.e. it does not depend on the latent value at a.

Formal statement
M :
SCM N Ω
s :
a :
ha :
a ∈ M.unobserved
T :
hT_sub :
T ⊆ M.randomVars
h_excl :
∀ v ∈ T, ¬ M.dag.isAncestor a v ∧ v ≠ a
∃ g : ((i : {i // i ∈ (Finset.univ.erase (⟨a, ha⟩ : {u // u ∈ M.unobserved}))}) → swigΩ Ω i.val.val) → ValuesOn T (swigΩ Ω),
∀ ℓ : LatentValues M, valuesProjection hT_sub (M.evalMap s ℓ) = g (fun i => ℓ i.val)
Proof (Lean source)
theorem evalMap_factors_excluding_latent (M : SCM N Ω) (s : FixedValues M) (a : SWIGNode N) (ha : a ∈ M.unobserved) (T : Finset (SWIGNode N)) (hT_sub : T ⊆ M.randomVars) (h_excl : ∀ v ∈ T, ¬ M.dag.isAncestor a v ∧ v ≠ a) : ∃ g : ((i : {i // i ∈ (Finset.univ.erase (⟨a, ha⟩ : {u // u ∈ M.unobserved}))}) → swigΩ Ω i.val.val) → ValuesOn T (swigΩ Ω), Measurable g ∧ ∀ ℓ : LatentValues M, valuesProjection hT_sub (M.evalMap s ℓ) = g (fun i => ℓ i.val) := by classical -- The subtype index we are erasing. set A : {u // u ∈ M.unobserved} := ⟨a, ha⟩ with hA_def -- Default value at `a`, obtained from `IsProbabilityMeasure`. haveI : IsProbabilityMeasure (M.latentDist A) := M.isProbability_latent A haveI hNE_a : Nonempty (swigΩ Ω a) := MeasureTheory.nonempty_of_isProbabilityMeasure (M.latentDist A) let default_at_a : swigΩ Ω a := Classical.arbitrary _ -- `extend` fills in `default_at_a` at index `A` and copies the rest. let extend : ((i : {i // i ∈ Finset.univ.erase A}) → swigΩ Ω i.val.val) → LatentValues M := fun r u => if hu : u = A then (by subst hu; exact default_at_a) else r ⟨u, Finset.mem_erase.mpr ⟨hu, Finset.mem_univ _⟩⟩ -- Measurability of `extend`. have hext_meas : Measurable extend := by refine measurable_pi_iff.mpr (fun u => ?_) by_cases hu : u = A · -- Constant branch: `extend r u = default_at_a` (up to cast). have hfun : (fun r : (i : {i // i ∈ Finset.univ.erase A}) → swigΩ Ω i.val.val => extend r u) = fun _ => (by subst hu; exact default_at_a) := by funext r simp [extend, hu] rw [hfun]; exact measurable_const · -- Projection branch: `extend r u = r ⟨u, _⟩`. have hfun : (fun r : (i : {i // i ∈ Finset.univ.erase A}) → swigΩ Ω i.val.val => extend r u) = fun r => r ⟨u, Finset.mem_erase.mpr ⟨hu, Finset.mem_univ _⟩⟩ := by funext r simp [extend, hu] rw [hfun]; exact measurable_pi_apply _ -- Build `g`. refine ⟨fun r => valuesProjection hT_sub (M.evalMap s (extend r)), ?_, ?_⟩ · -- Measurability: projection ∘ evalMap(s, ·) ∘ extend. have hev_s : Measurable (fun ℓ : LatentValues M => M.evalMap s ℓ) := by have := M.evalMap_measurable exact this.comp (prodMk measurable_const measurable_id) exact (measurable_valuesProjection hT_sub).comp (hev_s.comp hext_meas) · -- Pointwise factorization. intro ℓ -- Unfold `valuesProjection` on both sides via `funext` over `w ∈ T`. apply funext intro w -- Notation for the target (full) membership on `M.randomVars`. have hw_rand : w.val ∈ M.randomVars := hT_sub w.property -- Destruct whether `w.val` is unobserved or observed. rcases Finset.mem_union.mp hw_rand with hw_obs | hw_unobs · -- Observed case: use `ancestralFactorization` with `T = {w.val}`. have hwT_sub : ({w.val} : Finset (SWIGNode N)) ⊆ M.observed := Finset.singleton_subset_iff.mpr hw_obs have hwmem : w.val ∈ ({w.val} : Finset (SWIGNode N)) := Finset.mem_singleton.mpr rfl -- Show `evalMap s ℓ` and `evalMap s (extend (fun i => ℓ i.val))` agree at `w`. -- The `s` agreement is trivial (same `s`). -- The `ℓ` agreement: at any ancestor `u` of `w.val` (including `u = w.val`), -- `extend (fun i => ℓ i.val) ⟨u, hu⟩ = ℓ ⟨u, hu⟩`. This holds because -- `u ≠ a`: otherwise `isAncestor a w.val` (from `isAncestor u w.val`) or -- `a = w.val` (from `u = w.val`), both contradicting `h_excl w.val hwT`. have heq : M.evalMap s ℓ ⟨w.val, mem_union_left _ hw_obs⟩ = M.evalMap s (extend (fun i => ℓ i.val)) ⟨w.val, mem_union_left _ hw_obs⟩ := by refine M.ancestralFactorization ({w.val} : Finset (SWIGNode N)) ?_ ?_ hwmem hw_obs · -- Fixed agreement: same `s` on both sides, `rfl`. intro d hd _; rfl · -- Latent agreement: `extend (fun i => ℓ i.val) ⟨u, hu⟩ = ℓ ⟨u, hu⟩`. intro u hu hAnc obtain ⟨v', hv', hOr⟩ := hAnc have hv'eq : v' = w.val := Finset.mem_singleton.mp hv' have hOr' : u = w.val ∨ M.dag.isAncestor u w.val := hv'eq ▸ hOr -- Get the exclusion hypothesis for `w`. have hT_w : w.val ∈ T := w.property have hexcl_w := h_excl w.val hT_w -- Now show `u ≠ a`. have hu_ne_a : u ≠ a := by intro hu_eq subst hu_eq rcases hOr' with hueq | hanc · -- `u = w.val`: so `w.val = a`, but `w.val ≠ a`. exact hexcl_w.2 hueq.symm · -- `isAncestor a w.val`, but `¬ isAncestor a w.val`. exact hexcl_w.1 hanc -- Subtype inequality. have hsub_ne : (⟨u, hu⟩ : {u // u ∈ M.unobserved}) ≠ A := by intro heq_sub apply hu_ne_a exact (Subtype.mk.injEq _ _ _ _).mp heq_sub -- Unfold `extend` in the `else` branch. change ℓ ⟨u, hu⟩ = extend (fun i => ℓ i.val) ⟨u, hu⟩ simp [extend, hsub_ne] -- Conclude the `valuesProjection` equation. change (M.evalMap s ℓ) ⟨w.val, hw_rand⟩ = valuesProjection hT_sub (M.evalMap s (extend (fun i => ℓ i.val))) w -- `valuesProjection hT_sub ξ w = ξ ⟨w.val, hT_sub w.property⟩`. change (M.evalMap s ℓ) ⟨w.val, hw_rand⟩ = M.evalMap s (extend (fun i => ℓ i.val)) ⟨w.val, hT_sub w.property⟩ -- The two RHS memberships are proof-irrelevant; collapse via `heq`. rw [show (⟨w.val, hw_rand⟩ : {w // w ∈ M.randomVars}) = ⟨w.val, mem_union_left _ hw_obs⟩ from Subtype.ext rfl] rw [show (⟨w.val, hT_sub w.property⟩ : {w // w ∈ M.randomVars}) = ⟨w.val, mem_union_left _ hw_obs⟩ from Subtype.ext rfl] exact heq · -- Unobserved case: both sides collapse to a latent read. -- Exclusion at `w`. have hT_w : w.val ∈ T := w.property have hexcl_w := h_excl w.val hT_w have hw_ne_a : w.val ≠ a := hexcl_w.2 -- LHS: `evalMap s ℓ ⟨w, _⟩ = ℓ ⟨w, hw_unobs⟩`. have hlhs : M.evalMap s ℓ ⟨w.val, hw_rand⟩ = ℓ ⟨w.val, hw_unobs⟩ := M.evalMap_unobserved s ℓ ⟨w.val, hw_rand⟩ hw_unobs -- RHS: similarly equals `extend _ ⟨w, hw_unobs⟩`. have hrhs : M.evalMap s (extend (fun i => ℓ i.val)) ⟨w.val, hw_rand⟩ = extend (fun i => ℓ i.val) ⟨w.val, hw_unobs⟩ := M.evalMap_unobserved s (extend (fun i => ℓ i.val)) ⟨w.val, hw_rand⟩ hw_unobs -- Subtype inequality `⟨w.val, hw_unobs⟩ ≠ A`. have hsub_ne : (⟨w.val, hw_unobs⟩ : {u // u ∈ M.unobserved}) ≠ A := by intro heq_sub apply hw_ne_a exact (Subtype.mk.injEq _ _ _ _).mp heq_sub -- `extend` in the `else` branch reduces to the `r`-read, which is -- `(fun i => ℓ i.val) ⟨⟨w.val, hw_unobs⟩, _⟩ = ℓ ⟨w.val, hw_unobs⟩`. have hext_val : extend (fun i => ℓ i.val) ⟨w.val, hw_unobs⟩ = ℓ ⟨w.val, hw_unobs⟩ := by simp [extend, hsub_ne] change (M.evalMap s ℓ) ⟨w.val, hw_rand⟩ = valuesProjection hT_sub (M.evalMap s (extend (fun i => ℓ i.val))) w change (M.evalMap s ℓ) ⟨w.val, hw_rand⟩ = M.evalMap s (extend (fun i => ℓ i.val)) ⟨w.val, hT_sub w.property⟩ have hrand_eq : (⟨w.val, hT_sub w.property⟩ : {w // w ∈ M.randomVars}) = ⟨w.val, hw_rand⟩ := rfl rw [hrand_eq, hlhs, hrhs, hext_val]
Induced 4 core · 4 supporting This file defines ancestral closure for structural causal models and states an induced submodel construction on an ancestrally closed set of nodes. ★ induce_evalMap_compat★ induce_marginal_compat

Induced Structural Causal Models

This file defines ancestral closure for structural causal models and states an induced submodel construction on an ancestrally closed set of nodes. The construction supports marginal-compatibility results that relate a submodel to the original structural causal model.

Main definitions and results

* SCM.isAncestrallyClosedSCM is the observed-parent and fixed-counterpart closure condition needed to inherit structural functions. * SCM.induce builds the induced sub-SCM on an ancestrally closed node set. * SCM.measure_pi_map_valuesProjection and SCM.induce_latentProduct_eq_map identify the latent product of the induced model as a projected product measure. * SCM.induce_evalMap_compat compares evaluation in the induced model with evaluation in the original model. * SCM.induce_marginal_compat proves the observational-kernel marginal compatibility theorem for induced submodels.

def isAncestrallyClosedSCM reviewed
Causalean.SCM

R ⊆ V ∪ S ∪ L is ancestrally closed in the SCM sense if it satisfies both clauses of def:scm-anc-closed:

Definition (Lean source)
def isAncestrallyClosedSCM (M : SCM N Ω) (R : Finset (SWIGNode N)) : Prop := (∀ v ∈ R, ∀ v' ∈ M.dag.parents v, v' ∈ M.observed → v' ∈ R) ∧ (∀ v ∈ R, ∀ d ∈ M.dag.parents v, d ∈ M.fixed → iotaMap d ∈ R)
Causalean.SCM.isAncestrallyClosedSCM · Causalean/SCM/Model/Induced.lean:67 · uses SCM , SWIGNode
def induce reviewed
Causalean.SCM

The induced sub-SCM for an ancestrally closed node set keeps the observed and fixed parts selected by the induced SWIG graph and keeps exactly the original latent roots that feed retained observed nodes.

Definition (Lean source)
noncomputable def induce (M : SCM N Ω) (R : Finset (SWIGNode N)) (hR : M.isAncestrallyClosedSCM R) : SCM N Ω := let G : SWIGGraph N := M.toSWIGGraph.induce R -- `G.observed = R ∩ M.observed ⊆ M.observed` have hObs : G.observed ⊆ M.observed := inter_subset_right -- `G.fixed ⊆ M.fixed` by construction (it's a `filter`) have hFix : G.fixed ⊆ M.fixed := filter_subset _ _ -- `G.unobserved ⊆ M.unobserved` by construction (it's a `filter`). have hUnobs : G.unobserved ⊆ M.unobserved := by intro u hu simpa [G, SWIGGraph.induce] using (Finset.mem_filter.mp hu).1 { toSWIGGraph := G edgeTypes := { edgeType := fun u v => M.edgeTypes.edgeType u v } iota_valueSpace := fun s => M.iota_valueSpace ⟨s.val, hFix s.property⟩ -- Structural functions are inherited unchanged. Ancestral closure -- (via `induce_parents_eq_of_ancClosed`) ensures that the induced parent -- set coincides with the original, so the parent tuple fed to `M.structFun` -- is obtained by a coordinate-level rewrite with no pruning. structFun := fun v parentVals => M.structFun ⟨v.val, hObs v.property⟩ fun w => parentVals ⟨w.val, by rw [induce_parents_eq_of_ancClosed M R hR v.property] exact w.property⟩ structFun_measurable := fun v => by refine (M.structFun_measurable ⟨v.val, hObs v.property⟩).comp ?_ refine measurable_pi_lambda _ (fun w => ?_) exact measurable_pi_apply _ -- Retained latent roots inherit their original distributions. latentDist := fun u => M.latentDist ⟨u.val, hUnobs u.property⟩ isProbability_latent := fun u => M.isProbability_latent ⟨u.val, hUnobs u.property⟩ }
lemma induce_evalMap_compat reviewed
Causalean.SCM

Evaluation-map bridge for the induced sub-SCM. Fix a structural causal model M and a node set R that is ancestrally closed in the SCM sense. Then at every random node v retained by the induced model M.induce R hR, the induced evaluation map at the restriction of sTilde to R and the projected latent assignment computes the same value as the original evaluation map at sTilde.

Formal statement
M :
SCM N Ω
R :
hR :
M.isAncestrallyClosedSCM R
sTilde :
:
∀ {v : SWIGNode N} (hvI : v ∈ (M.induce R hR).randomVars),
let hvM : v ∈ M.randomVars := by rcases Finset.mem_union.mp hvI with hvIObs | hvIUo · exact mem_union_left _ (inter_subset_right hvIObs) · exact mem_union_right _ (induce_unobserved_subset M R hR hvIUo) (M.induce R hR).evalMap (valuesProjection (filter_subset _ _) sTilde) (valuesProjection (induce_unobserved_subset M R hR) ℓ) ⟨v, hvI⟩
= M.evalMap sTilde ℓ ⟨v, hvM⟩
Proof (Lean source)
lemma induce_evalMap_compat (M : SCM N Ω) (R : Finset (SWIGNode N)) (hR : M.isAncestrallyClosedSCM R) (sTilde : FixedValues M) (ℓ : LatentValues M) : ∀ {v : SWIGNode N} (hvI : v ∈ (M.induce R hR).randomVars), let hvM : v ∈ M.randomVars := by rcases Finset.mem_union.mp hvI with hvIObs | hvIUo · exact mem_union_left _ (Finset.inter_subset_right hvIObs) · exact mem_union_right _ (induce_unobserved_subset M R hR hvIUo) (M.induce R hR).evalMap (valuesProjection (filter_subset _ _) sTilde) (valuesProjection (induce_unobserved_subset M R hR) ℓ) ⟨v, hvI⟩ = M.evalMap sTilde ℓ ⟨v, hvM⟩ := by classical -- Reduce to the observed-strong-recursion form. suffices h_obs : ∀ (n : ℕ), ∀ (v : SWIGNode N) (hvI : v ∈ (M.induce R hR).observed), ((M.induce R hR).observedIndex ⟨v, hvI⟩).val = n → (M.induce R hR).evalMap (valuesProjection (filter_subset _ _) sTilde) (valuesProjection (induce_unobserved_subset M R hR) ℓ) ⟨v, mem_union_left _ hvI⟩ = M.evalMap sTilde ℓ ⟨v, mem_union_left _ (Finset.inter_subset_right hvI)⟩ by intro v hvI -- Dispatch: `v` is either observed or unobserved in the induced SCM. rcases Finset.mem_union.mp hvI with hvIObs | hvIUo · -- Observed case: apply the strong-recursion helper. have := h_obs _ v hvIObs rfl -- Witness equality for `hvM` via proof irrelevance. convert this using 2 · -- Unobserved case: both sides unfold to the projected original latent coordinate. have hvMUo : v ∈ M.unobserved := induce_unobserved_subset M R hR hvIUo rw [SCM.evalMap_unobserved (M.induce R hR) _ (valuesProjection (induce_unobserved_subset M R hR) ℓ) ⟨v, hvI⟩ hvIUo] dsimp rw [SCM.evalMap_unobserved M sTilde ℓ ⟨v, mem_union_right _ hvMUo⟩ hvMUo] rfl -- Strong recursion on `n = (M.induce R hR).observedIndex ⟨v, hvI⟩`. intro n induction n using Nat.strongRecOn with | _ n ih => intro v hvI hidx have hvMObs : v ∈ M.observed := Finset.inter_subset_right hvI have hvR : v ∈ R := (Finset.mem_inter.mp hvI).1 have h_parents_eq : (M.induce R hR).dag.parents v = M.dag.parents v := induce_parents_eq_of_ancClosed M R hR hvI -- Unfold both `evalMap`s via the proved recursive-form helper from -- Evaluation.lean. rw [SCM.evalMap_observed_unfold (M.induce R hR) _ (valuesProjection (induce_unobserved_subset M R hR) ℓ) ⟨v, hvI⟩, SCM.evalMap_observed_unfold M sTilde ℓ ⟨v, hvMObs⟩] -- LHS: (M.induce R hR).structFun ⟨v, hvI⟩ (if-else-dispatch-induced) -- RHS: M.structFun ⟨v, hvMObs⟩ (if-else-dispatch-M) -- Unfold (M.induce R hR).structFun via the `SCM.induce` definition (beta reduction). change M.structFun ⟨v, hvMObs⟩ (fun w : {w // w ∈ M.dag.parents v} => if huo : w.val ∈ (M.induce R hR).unobserved then valuesProjection (induce_unobserved_subset M R hR) ℓ ⟨w.val, huo⟩ else if hfix : w.val ∈ (M.induce R hR).fixed then valuesProjection (filter_subset _ _) sTilde ⟨w.val, hfix⟩ else have hedge : (M.induce R hR).dag.edge w.val v := (M.induce R hR).dag.mem_parents.mp (h_parents_eq.symm ▸ w.property) have hobs : w.val ∈ (M.induce R hR).observed := by rcases Finset.mem_union.mp ((M.induce R hR).dag_edges_classified _ _ hedge).1 with h1 | h2 · rcases Finset.mem_union.mp h1 with hfx | hob · exact absurd hfx hfix · exact hob · exact absurd h2 huo (M.induce R hR).evalMap (valuesProjection (filter_subset _ _) sTilde) (valuesProjection (induce_unobserved_subset M R hR) ℓ) ⟨w.val, mem_union_left _ hobs⟩) = M.structFun ⟨v, hvMObs⟩ (fun w : {w // w ∈ M.dag.parents v} => if huo : w.val ∈ M.unobserved then ℓ ⟨w.val, huo⟩ else if hfix : w.val ∈ M.fixed then sTilde ⟨w.val, hfix⟩ else have hedge : M.dag.edge w.val v := M.dag.mem_parents.mp w.property have hobs : w.val ∈ M.observed := by rcases Finset.mem_union.mp (M.dag_edges_classified _ _ hedge).1 with h1 | h2 · rcases Finset.mem_union.mp h1 with hfx | hob · exact absurd hfx hfix · exact hob · exact absurd h2 huo M.evalMap sTilde ℓ ⟨w.val, mem_union_left _ hobs⟩) congr 1 funext w -- Per-parent three-way case split. have hedge_M : M.dag.edge w.val v := M.dag.mem_parents.mp w.property have hcls : w.val ∈ M.fixed ∪ M.observed ∪ M.unobserved := (M.dag_edges_classified _ _ hedge_M).1 by_cases huo : w.val ∈ M.unobserved · -- Unobserved parent of a retained observed node is retained by the induced -- latent filter. have huoInd : w.val ∈ (M.induce R hR).unobserved := by change w.val ∈ M.unobserved.filter (fun u => ∃ z ∈ R ∩ M.observed, M.dag.edge u z) exact Finset.mem_filter.mpr ⟨huo, v, hvI, hedge_M⟩ rw [dif_pos huo, dif_pos huoInd] change ℓ ⟨w.val, induce_unobserved_subset M R hR huoInd⟩ = ℓ ⟨w.val, huo⟩ congr · have huoInd : w.val ∉ (M.induce R hR).unobserved := by intro h exact huo (induce_unobserved_subset M R hR h) rw [dif_neg huo, dif_neg huoInd] by_cases hfix : w.val ∈ M.fixed · -- Fixed: by ancestral closure clause (b), `iotaMap w ∈ R ∩ M.observed`, so -- `w ∈ (M.induce R hR).fixed` (the filter witness). have hIotaR : iotaMap w.val ∈ R := hR.2 v hvR w.val w.property hfix have hIotaObs : iotaMap w.val ∈ M.observed := M.fixed_image_in_observed w.val hfix have hIotaInter : iotaMap w.val ∈ R ∩ M.observed := Finset.mem_inter.mpr ⟨hIotaR, hIotaObs⟩ have hfixInd : w.val ∈ (M.induce R hR).fixed := by change w.val ∈ M.fixed.filter (fun s => iotaMap s ∈ R ∩ M.observed) exact Finset.mem_filter.mpr ⟨hfix, hIotaInter⟩ rw [dif_pos hfix, dif_pos hfixInd] -- `(sTilde|_R) ⟨w.val, hfixInd⟩ = sTilde ⟨w.val, hfix⟩` by proof irrelevance -- since `valuesProjection` is `fun ξ j => ξ ⟨j.val, hJI j.property⟩`. rfl · -- Observed: by ancestral closure clause (a), `w ∈ R ∩ M.observed`, so -- `w ∈ (M.induce R hR).observed`. Apply the IH at the induced index. have hobs : w.val ∈ M.observed := by rcases Finset.mem_union.mp hcls with h1 | h2 · rcases Finset.mem_union.mp h1 with hfx | hob · exact absurd hfx hfix · exact hob · exact absurd h2 huo have hobsR : w.val ∈ R := hR.1 v hvR w.val w.property hobs have hobsInd : w.val ∈ (M.induce R hR).observed := by change w.val ∈ R ∩ M.observed exact Finset.mem_inter.mpr ⟨hobsR, hobs⟩ have hfixInd : w.val ∉ (M.induce R hR).fixed := by intro h exact absurd ((Finset.mem_filter.mp h).1) hfix rw [dif_neg hfix, dif_neg hfixInd] -- Apply strong-recursion IH at `w.val` on the induced side. have hidx_w : ((M.induce R hR).observedIndex ⟨w.val, hobsInd⟩).val < ((M.induce R hR).observedIndex ⟨v, hvI⟩).val := by have hedge_ind : (M.induce R hR).dag.edge w.val v := (M.induce R hR).dag.mem_parents.mp (h_parents_eq.symm ▸ w.property) -- Reindex the target of `hedge_ind` through `observedAt_observedIndex`. have hv_eq : ((M.induce R hR).observedAt ⟨((M.induce R hR).observedIndex ⟨v, hvI⟩).val, ((M.induce R hR).observedIndex ⟨v, hvI⟩).isLt⟩).val = v := by have := (M.induce R hR).observedAt_observedIndex ⟨v, hvI⟩ convert this have hedge_ind' : (M.induce R hR).dag.edge w.val ((M.induce R hR).observedAt ⟨((M.induce R hR).observedIndex ⟨v, hvI⟩).val, ((M.induce R hR).observedIndex ⟨v, hvI⟩).isLt⟩).val := by rw [hv_eq]; exact hedge_ind exact (M.induce R hR).observed_parent_index_lt ((M.induce R hR).observedIndex ⟨v, hvI⟩).isLt hedge_ind' hobsInd rw [hidx] at hidx_w exact ih _ hidx_w w.val hobsInd rfl
theorem induce_marginal_compat reviewed
Causalean.SCM

Marginal compatibility of the induced sub-SCM (prop:scm-induced-marginal). Fix a structural causal model M, an ancestrally closed node set R, and a fixed-value assignment sTilde on M. Then the observational kernel of the induced sub-SCM at the restriction of sTilde to R equals the pushforward of M's observational kernel at sTilde onto the induced observed coordinates R ∩ M.observed.

Formal statement
M :
SCM N Ω
R :
hR :
M.isAncestrallyClosedSCM R
sTilde :
(M.induce R hR).obsKernel (valuesProjection (filter_subset _ _) sTilde)
= (M.obsKernel sTilde).map (valuesProjection (show (M.induce R hR).observed ⊆ M.observed from inter_subset_right))
Proof (Lean source)
theorem induce_marginal_compat (M : SCM N Ω) (R : Finset (SWIGNode N)) (hR : M.isAncestrallyClosedSCM R) (sTilde : FixedValues M) : (M.induce R hR).obsKernel (valuesProjection (filter_subset _ _) sTilde) = (M.obsKernel sTilde).map (valuesProjection (show (M.induce R hR).observed ⊆ M.observed from inter_subset_right)) := by classical -- Measurability of the components. let MI := M.induce R hR have hf_M : Measurable (fun ℓ : LatentValues M => M.evalMap sTilde ℓ) := by exact M.evalMap_measurable.comp (prodMk measurable_const measurable_id) have hf_I : Measurable (fun ℓ : MI.LatentValues => MI.evalMap (valuesProjection (filter_subset _ _) sTilde) ℓ) := by exact MI.evalMap_measurable.comp (prodMk measurable_const measurable_id) have hRTO_M : Measurable M.randomToObserved := M.measurable_randomToObserved have hRTO_I : Measurable MI.randomToObserved := MI.measurable_randomToObserved have hπ : Measurable (valuesProjection (show MI.observed ⊆ M.observed from inter_subset_right) : ObservedValues M → ObservedValues MI) := by refine measurable_pi_lambda _ (fun _ => measurable_pi_apply _) have hLatentProj : Measurable (valuesProjection (induce_unobserved_subset M R hR) : LatentValues M → LatentValues MI) := measurable_valuesProjection (induce_unobserved_subset M R hR) have hLatent : MI.latentProduct = M.latentProduct.map (valuesProjection (induce_unobserved_subset M R hR)) := by simpa [MI] using induce_latentProduct_eq_map M R hR have hFI : Measurable (MI.randomToObserved ∘ (fun ℓ : LatentValues MI => MI.evalMap (valuesProjection (filter_subset _ _) sTilde) ℓ)) := hRTO_I.comp hf_I -- Step 1: Unfold both obsKernels to `latentProduct.map (composition)`. -- LHS: MI.obsKernel (sTilde|_R) = MI.jointKernel (sTilde|_R) |>.map randomToObserved_I -- = (MI.latentProduct.map (MI.evalMap (sTilde|_R))) |>.map randomToObserved_I -- RHS: (M.obsKernel sTilde).map π = ((M.jointKernel sTilde).map randomToObserved_M).map π -- = (M.latentProduct.map (M.evalMap sTilde)).map (π ∘ randomToObserved_M) unfold obsKernel rw [ProbabilityTheory.Kernel.map_apply _ hRTO_I, ProbabilityTheory.Kernel.map_apply _ hRTO_M, jointKernel_apply_eq MI (valuesProjection (filter_subset _ _) sTilde), jointKernel_apply_eq M sTilde] -- Step 2: Compose maps via Measure.map_map. rw [MeasureTheory.Measure.map_map hRTO_I hf_I, MeasureTheory.Measure.map_map hRTO_M hf_M, MeasureTheory.Measure.map_map hπ (hRTO_M.comp hf_M)] -- Step 3: replace the induced latent product by the projected original -- latent product and compose maps once more. rw [hLatent, MeasureTheory.Measure.map_map hFI hLatentProj] -- Step 4: Close via funext + induce_evalMap_compat. congr 1 funext ℓ -- Goal: (MI.randomToObserved ∘ MI.evalMap (sTilde|_R) ∘ latentProjection) ℓ -- = (π ∘ M.randomToObserved ∘ M.evalMap sTilde) ℓ -- Both sides are ObservedValues MI. simp only [Function.comp_apply] -- Goal: MI.randomToObserved (MI.evalMap (sTilde|_R) ℓ) -- = π (M.randomToObserved (M.evalMap sTilde ℓ)) -- Expand randomToObserved and π as functions on observed subtypes. funext v -- v : { v // v ∈ MI.observed } -- Each side evaluates to an evalMap value at v. simp only [randomToObserved, valuesProjection] -- Goal: MI.evalMap (sTilde|_R) ℓ ⟨v.val, mem_union_left _ v.property⟩ -- = M.evalMap sTilde ℓ ⟨v.val, mem_union_left _ (inter_subset_right v.property)⟩ exact induce_evalMap_compat M R hR sTilde ℓ (mem_union_left _ v.property)
4 supporting declarations (lemmas, instances)
  • induce_parents_eq_of_ancClosed lemma — An ancestrally closed retained observed set keeps every parent needed to evaluate each retained observed node, so its induced graph has exactly the same parent set at those nodes.
    M :
    SCM N Ω
    R :
    hR :
    M.isAncestrallyClosedSCM R
    v :
    hv :
    v ∈ (M.toSWIGGraph.induce R).observed
    (M.toSWIGGraph.induce R).dag.parents v = M.dag.parents v
    Proof (Lean source)
    lemma induce_parents_eq_of_ancClosed (M : SCM N Ω) (R : Finset (SWIGNode N)) (hR : M.isAncestrallyClosedSCM R) {v : SWIGNode N} (hv : v ∈ (M.toSWIGGraph.induce R).observed) : (M.toSWIGGraph.induce R).dag.parents v = M.dag.parents v := by classical have hvInter : v ∈ R ∩ M.observed := hv have hvR : v ∈ R := (Finset.mem_inter.mp hvInter).1 have hvMObs : v ∈ M.observed := (Finset.mem_inter.mp hvInter).2 -- `v` lives in the induced active set via the `newObserved` summand. have hvActive : v ∈ (M.toSWIGGraph.fixed.filter (fun s => iotaMap s ∈ R ∩ M.observed)) ∪ (R ∩ M.observed) ∪ (M.toSWIGGraph.unobserved.filter (fun u => ∃ w ∈ R ∩ M.observed, M.toSWIGGraph.dag.edge u w)) := by refine mem_union_left _ ?_ exact Finset.mem_union_right _ hvInter apply Finset.Subset.antisymm · exact M.toSWIGGraph.inducedDag_parents_subset _ v · intro u huM have huEdge : M.dag.edge u v := M.dag.mem_parents.mp huM have huClass : u ∈ M.fixed ∪ M.observed ∪ M.unobserved := (M.dag_edges_classified u v huEdge).1 have huActive : u ∈ (M.toSWIGGraph.fixed.filter (fun s => iotaMap s ∈ R ∩ M.observed)) ∪ (R ∩ M.observed) ∪ (M.toSWIGGraph.unobserved.filter (fun u => ∃ w ∈ R ∩ M.observed, M.toSWIGGraph.dag.edge u w)) := by rcases Finset.mem_union.mp huClass with hu | hu · rcases Finset.mem_union.mp hu with huFix | huObs · -- `u ∈ M.fixed`: use pairing closure (clause b). have hIotaR : iotaMap u ∈ R := hR.2 v hvR u huM huFix have hIotaObs : iotaMap u ∈ M.observed := M.fixed_image_in_observed u huFix have hIotaNewObs : iotaMap u ∈ R ∩ M.observed := Finset.mem_inter.mpr ⟨hIotaR, hIotaObs⟩ have huNewFixed : u ∈ M.toSWIGGraph.fixed.filter (fun s => iotaMap s ∈ R ∩ M.observed) := Finset.mem_filter.mpr ⟨huFix, hIotaNewObs⟩ exact mem_union_left _ (mem_union_left _ huNewFixed) · -- `u ∈ M.observed`: use observed-ancestor closure (clause a). have huR : u ∈ R := hR.1 v hvR u huM huObs have huNewObs : u ∈ R ∩ M.observed := Finset.mem_inter.mpr ⟨huR, huObs⟩ exact mem_union_left _ (Finset.mem_union_right _ huNewObs) · -- `u ∈ M.unobserved`: a latent parent of retained `v` survives the latent filter. exact Finset.mem_union_right _ (Finset.mem_filter.mpr ⟨hu, v, hvInter, huEdge⟩) refine (M.toSWIGGraph.induce R).dag.mem_parents.mpr ?_ exact ⟨huEdge, huActive, hvActive⟩
    Causalean.SCM.induce_parents_eq_of_ancClosed · Causalean/SCM/Model/Induced.lean:87
  • induce_unobserved_subset lemma — The latent roots of an induced sub-SCM are original latent roots.
    M :
    SCM N Ω
    R :
    hR :
    M.isAncestrallyClosedSCM R
    (M.induce R hR).unobserved ⊆ M.unobserved
    Proof (Lean source)
    lemma induce_unobserved_subset (M : SCM N Ω) (R : Finset (SWIGNode N)) (hR : M.isAncestrallyClosedSCM R) : (M.induce R hR).unobserved ⊆ M.unobserved := by intro u hu simpa [SCM.induce, SWIGGraph.induce] using (Finset.mem_filter.mp hu).1
    Causalean.SCM.induce_unobserved_subset · Causalean/SCM/Model/Induced.lean:185
  • measure_pi_map_valuesProjection lemma — Product measures marginalize under coordinate restriction.
    I J :
    hJI :
    J ⊆ I
    μ :
    (i : {i // i ∈ I}) → Measure (swigΩ Ω i.val)
    ∀ i, IsProbabilityMeasure (μ i)
    (pi μ).map (valuesProjection (Ω := swigΩ Ω) hJI)
    = pi (fun j : {j // j ∈ J} => μ ⟨j.val, hJI j.property⟩)
    Proof (Lean source)
    lemma measure_pi_map_valuesProjection {I J : Finset (SWIGNode N)} (hJI : J ⊆ I) (μ : (i : {i // i ∈ I}) → Measure (swigΩ Ω i.val)) [∀ i, IsProbabilityMeasure (μ i)] : (pi μ).map (valuesProjection (Ω := swigΩ Ω) hJI) = pi (fun j : {j // j ∈ J} => μ ⟨j.val, hJI j.property⟩) := by classical symm refine MeasureTheory.Measure.pi_eq (fun s hs => ?_) rw [MeasureTheory.Measure.map_apply (measurable_valuesProjection hJI) (.univ_pi hs)] let t : (i : {i // i ∈ I}) → Set (swigΩ Ω i.val) := fun i => if h : i.val ∈ J then s ⟨i.val, h⟩ else Set.univ have hpre : valuesProjection (Ω := swigΩ Ω) hJI ⁻¹' pi Set.univ s = pi Set.univ t := by ext x constructor · intro hx a _haI by_cases haJ : a.val ∈ J · have hxj := hx ⟨a.val, haJ⟩ (Set.mem_univ _) simpa [t, valuesProjection, haJ] using hxj · simp [t, haJ] · intro hx a _haJ have hxi := hx ⟨a.val, hJI a.property⟩ (Set.mem_univ _) simpa [t, valuesProjection, a.property] using hxi rw [hpre] rw [MeasureTheory.Measure.pi_pi] let g : SWIGNode N → ℝ≥0∞ := fun a => if h : a ∈ J then μ ⟨a, hJI h⟩ (s ⟨a, h⟩) else 1 have hleft : (∏ i, μ i (t i)) = ∏ a ∈ I, g a := by calc (∏ i : {i // i ∈ I}, μ i (t i)) = ∏ i : {i // i ∈ I}, g i.val := by refine Fintype.prod_congr _ _ ?_ intro i by_cases hiJ : i.val ∈ J · simp [g, t, hiJ] · simp [g, t, hiJ] _ = ∏ a ∈ I, g a := Finset.prod_coe_sort (s := I) (f := g) have hright : (∏ j : {j // j ∈ J}, μ ⟨j.val, hJI j.property⟩ (s j)) = ∏ a ∈ J, g a := by calc (∏ j : {j // j ∈ J}, μ ⟨j.val, hJI j.property⟩ (s j)) = ∏ j : {j // j ∈ J}, g j.val := by refine Fintype.prod_congr _ _ ?_ intro j simp [g, j.property] _ = ∏ a ∈ J, g a := Finset.prod_coe_sort (s := J) (f := g) rw [hleft, hright] exact (Finset.prod_subset_one_on_sdiff hJI (fun a ha => by have haJ : a ∉ J := (Finset.mem_sdiff.mp ha).2 simp [g, haJ]) (fun a ha => by simp [g, ha])).symm
    Causalean.SCM.measure_pi_map_valuesProjection · Causalean/SCM/Model/Induced.lean:193
  • induce_latentProduct_eq_map lemma — The latent product of an induced sub-SCM is the marginal of the original latent product.
    M :
    SCM N Ω
    R :
    hR :
    M.isAncestrallyClosedSCM R
    (M.induce R hR).latentProduct
    = M.latentProduct.map (valuesProjection (induce_unobserved_subset M R hR))
    Proof (Lean source)
    lemma induce_latentProduct_eq_map (M : SCM N Ω) (R : Finset (SWIGNode N)) (hR : M.isAncestrallyClosedSCM R) : (M.induce R hR).latentProduct = M.latentProduct.map (valuesProjection (induce_unobserved_subset M R hR)) := by classical haveI hprob : ∀ u : {u // u ∈ M.unobserved}, IsProbabilityMeasure (M.latentDist u) := M.isProbability_latent rw [SCM.latentProduct, SCM.latentProduct] rw [measure_pi_map_valuesProjection (hJI := induce_unobserved_subset M R hR) (μ := fun u : {u // u ∈ M.unobserved} => M.latentDist u)] simp only [SCM.induce] rfl
    Causalean.SCM.induce_latentProduct_eq_map · Causalean/SCM/Model/Induced.lean:249
Intervention­Ancestry 1 core · 1 supporting This file relates ancestry in the graph after a set intervention to ancestry in the original structural causal model. ★ fixSet_isAncestor_fixed_forward

Intervention Ancestry

This file relates ancestry in the graph after a set intervention to ancestry in the original structural causal model. It supplies the graph bridge used to turn a non-descendant condition in a back-door criterion into the non-ancestry hypothesis needed for Rule 3 of do-calculus.

The main theorem, SCM.fixSet_isAncestor_fixed_forward, lifts a directed ancestry path starting at an intervened fixed copy in (M.fixSet X).dag to an ancestry path starting at the corresponding random node in the base graph. The auxiliary DAG.not_isAncestor_of_root' records that a root has no proper ancestors.

theorem fixSet_isAncestor_fixed_forward reviewed
Causalean.SCM

Forward direction: .fixed D-ancestry in fixSet X lifts to .random D-ancestry in the base graph. Fix a structural causal model M and an intervention target set X such that every targeted node is currently a random observed node and none of its fixed copies is already fixed. For a targeted node D and a node v, if the fixed copy of D is a proper ancestor of v in the post-intervention graph obtained by fixing X, then the random copy of D is a proper ancestor of v in the original base graph.

Formal statement
M :
SCM N Ω
X :
hX_obs :
∀ D ∈ X, SWIGNode.random D ∈ M.observed
hX_fixed :
∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed
D :
N
hD :
D ∈ X
v :
h :
(M.fixSet X hX_obs hX_fixed).dag.isAncestor (SWIGNode.fixed D) v
M.toSWIGGraph.dag.isAncestor (SWIGNode.random D) v
Proof (Lean source)
theorem fixSet_isAncestor_fixed_forward (M : SCM N Ω) (X : Finset N) (hX_obs : ∀ D ∈ X, SWIGNode.random D ∈ M.observed) (hX_fixed : ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed) {D : N} (hD : D ∈ X) {v : SWIGNode N} (h : (M.fixSet X hX_obs hX_fixed).dag.isAncestor (SWIGNode.fixed D) v) : M.toSWIGGraph.dag.isAncestor (SWIGNode.random D) v := by induction h with | edge he => -- Direct edge `.fixed D → v` in `fixSet X`. rw [fixSet_edge_iff] at he simp only [SWIGGraph.splitMonoEdgeRel, if_pos hD] at he exact DAG.isAncestor.edge he | @trans w _ _ he ih => -- Case split on the intermediate vertex `w`. rw [fixSet_edge_iff] at he cases w with | random u => simp only [SWIGGraph.splitMonoEdgeRel] at he by_cases hu : u ∈ X · -- `.random u` (u ∈ X) has no outgoing edges in split; `he` is False. simp only [if_pos hu] at he · rw [if_neg hu] at he exact DAG.isAncestor.trans ih he | fixed d => simp only [SWIGGraph.splitMonoEdgeRel] at he by_cases hd : d ∈ X · -- IH claims `isAncestor_M (.random D) (.fixed d)`, but `.fixed d` -- (d ∈ X) is isolated in the base graph, so has no ancestors. exfalso have hiso := M.toSWIGGraph.fixed_outside_fixed_isolated d (hX_fixed d hd) have hNoInc : ∀ x, ¬ M.toSWIGGraph.dag.edge x (SWIGNode.fixed d) := by intro x hx have : x ∈ M.toSWIGGraph.dag.parents (SWIGNode.fixed d) := M.toSWIGGraph.dag.mem_parents.mpr hx rw [hiso.1] at this exact (notMem_empty _) this exact M.toSWIGGraph.dag.not_isAncestor_of_root' hNoInc _ ih · rw [if_neg hd] at he exact DAG.isAncestor.trans ih he
1 supporting declaration (lemmas, instances)
  • fixSet_edge_iff lemma — An edge in a causal model after intervention is exactly the corresponding edge produced by splitting the intervened variables in the original graph.
    M :
    SCM N Ω
    X :
    hX_obs :
    ∀ D ∈ X, SWIGNode.random D ∈ M.observed
    hX_fixed :
    ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed
    u v :
    (M.fixSet X hX_obs hX_fixed).dag.edge u v
    ↔ SWIGGraph.splitMonoEdgeRel M.toSWIGGraph.dag.edge X u v
    Proof (Lean source)
    lemma fixSet_edge_iff (M : SCM N Ω) (X : Finset N) (hX_obs : ∀ D ∈ X, SWIGNode.random D ∈ M.observed) (hX_fixed : ∀ D ∈ X, SWIGNode.fixed D ∉ M.fixed) (u v : SWIGNode N) : (M.fixSet X hX_obs hX_fixed).dag.edge u v ↔ SWIGGraph.splitMonoEdgeRel M.toSWIGGraph.dag.edge X u v := by simp only [SCM.fixSet, SCM.fixMono, SWIGGraph.splitMono, SWIGGraph.splitMonoDAG]