Structural Equation Model #
@cite{nadathur-lauer-2020}
Theory-neutral infrastructure for deterministic causal and counterfactual reasoning based on Pearl's structural causal model framework.
- Variable: Named causal variables
- Situation: Partial valuations (Variable → Option Bool)
- CausalLaw: Structural equations (preconditions → effect)
- CausalDynamics: Collections of causal laws
- normalDevelopment: Forward propagation to fixpoint
- intervene: Pearl's do-operator (set variable, cut incoming laws)
- causallyNecessary / causallySufficient: Counterfactual tests
- manipulates: Does intervening on X change Y?
For the probabilistic causal Bayes net layer (WorldState, CausalRelation,
NoisyOR), see Core.CausalBayesNet.
A variable in a causal model. Variables are named entities whose values are determined by causal laws.
- name : String
Instances For
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
Instances For
Equations
- Core.StructuralEquationModel.instBEqVariable = { beq := fun (a b : Core.StructuralEquationModel.Variable) => decide (a = b) }
Equations
- Core.StructuralEquationModel.instToStringVariable = { toString := fun (v : Core.StructuralEquationModel.Variable) => v.name }
Create a variable from a string literal.
Equations
- Core.StructuralEquationModel.mkVar name = { name := name }
Instances For
Situation (Definition 9 from @cite{nadathur-lauer-2020})
A partial valuation: some variables have known values, others are undetermined. Situations are partial functions — crucial for modeling what's "given" vs what's computed, and for counterfactual reasoning (removing a cause = setting it to false).
The partial valuation: Variable → Option Bool
Instances For
The empty situation: nothing is determined.
Equations
- Core.StructuralEquationModel.Situation.empty = { valuation := fun (x : Core.StructuralEquationModel.Variable) => none }
Instances For
Extend a situation with a new assignment: s ⊕ {v = val}. If the variable was already determined, the new value overwrites.
Equations
Instances For
Remove a variable from the situation (set to undetermined).
Equations
Instances For
Enumerate all extensions of a situation over a list of free variables.
Each variable can be left undetermined, set true, or set false.
Called only on variables NOT already determined in s.
Scalability: produces 3^n situations for n free variables. This is acceptable for the small models in @cite{nadathur-lauer-2020} (typically 2–4 variables) but would need pruning or symbolic reasoning for larger causal networks.
Instances For
s₁ ⊑ s₂ in the true-content ordering: every variable true in s₁
is also true in s₂. This is the natural preorder for reasoning about
monotonicity of positive causal dynamics.
Equations
Instances For
Causal Law (Definition 10 from @cite{nadathur-lauer-2020})
A causal law specifies: if all preconditions hold, the effect follows. In notation: ⟨{(v₁, val₁), …, (vₙ, valₙ)}, (vₑ, valₑ)⟩.
Preconditions that must all hold
- effect : Variable
The variable affected by this law
- effectValue : Bool
The value the effect variable gets (default: true)
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
Instances For
Check if all preconditions of a law are satisfied in a situation.
Equations
- law.preconditionsMet s = law.preconditions.all fun (x : Core.StructuralEquationModel.Variable × Bool) => match x with | (v, val) => s.hasValue v val
Instances For
Apply a law to a situation (if preconditions met, set effect).
Equations
- law.apply s = bif law.preconditionsMet s then s.extend law.effect law.effectValue else s
Instances For
Create a conjunctive law: if cause1 = true ∧ cause2 = true, then effect = true.
Equations
Instances For
If preconditions are not met, applying the law is a no-op.
Avoids leaving stuck if false = true then … terms in goals.
If preconditions are met, applying the law extends the situation.
The effect value of a simple law (always true).
Causal Dynamics: A collection of causal laws. Represents the structural equations of a causal model. Multiple laws can affect the same variable (disjunctive causation).
The laws governing causal propagation
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
Instances For
Empty dynamics (no causal laws).
Instances For
Create dynamics from a list of laws.
Equations
- Core.StructuralEquationModel.CausalDynamics.ofList laws = { laws := laws }
Instances For
Disjunctive causation: A ∨ B → C. Either cause alone suffices.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Conjunctive causation: A ∧ B → C. Both causes required.
Equations
- Core.StructuralEquationModel.CausalDynamics.conjunctiveCausation cause1 cause2 effect = { laws := [Core.StructuralEquationModel.CausalLaw.conjunctive cause1 cause2 effect] }
Instances For
Causal chain: A → B → C.
Equations
Instances For
A dynamics is positive if all preconditions require true and all
effect values are true. Positive dynamics have no inhibitory connections:
causes can only enable effects, never block them.
This is the key structural condition for monotonicity results: in positive
dynamics, adding true values to a situation can only enable more laws,
never block them.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Apply all laws once to a situation (one step of forward propagation).
Equations
- Core.StructuralEquationModel.applyLawsOnce dyn s = List.foldl (fun (s' : Core.StructuralEquationModel.Situation) (law : Core.StructuralEquationModel.CausalLaw) => law.apply s') s dyn.laws
Instances For
Check if a situation is a fixpoint (no law can change it).
Equations
- Core.StructuralEquationModel.isFixpoint dyn s = dyn.laws.all fun (law : Core.StructuralEquationModel.CausalLaw) => !law.preconditionsMet s || s.hasValue law.effect law.effectValue
Instances For
Normal Causal Development (Definition 15)
Iterate forward propagation until fixpoint. Uses bounded iteration (fuel) to ensure termination.
Equations
- One or more equations did not get rendered due to their size.
- Core.StructuralEquationModel.normalDevelopment dyn s 0 = s
Instances For
Unfold normalDevelopment one step.
If the first round reaches a fixpoint, normalDevelopment returns it.
If the first round is NOT a fixpoint, normalDevelopment recurses.
If the result of one round of law application is a fixpoint, normalDevelopment returns that result.
If the first round is not a fixpoint but the second is, normalDevelopment returns the second-round result.
General fixpoint theorem: if applyLawsOnce reaches a fixpoint after
exactly n + 1 rounds, then normalDevelopment with sufficient fuel
returns (applyLawsOnce dyn)^[n + 1] s.
_after_one and _after_two are special cases for n = 0 and n = 1.
If a law's fixpoint condition holds (preconditions unmet or effect present), applying the law is a no-op.
Applying all laws to a fixpoint situation is a no-op.
Normal development from a fixpoint is a no-op regardless of fuel.
Empty dynamics: any situation is a fixpoint.
Normal development of empty dynamics returns the input unchanged.
For positive dynamics, normal development is inflationary (extensive):
every truth in s is preserved. This is one of the three closure-operator
axioms. Used by CausalClosure.lean to build the ClosureOperator instance.
For positive dynamics, normalDevelopment is monotone in the trueLE ordering. If s₁ ⊑ s₂ (every true in s₁ is true in s₂), then normalDevelopment(s₁) ⊑ normalDevelopment(s₂).
Check if a variable develops to a specific value.
Equations
- Core.StructuralEquationModel.developsToBe dyn s v val = (Core.StructuralEquationModel.normalDevelopment dyn s).hasValue v val
Instances For
Check if a variable becomes true after normal development.
Equations
Instances For
The cause is present and the effect holds after normal development.
Shared primitive for actuallyCaused (Necessity.lean) and
complementActualized (Ability.lean).
Equations
- Core.StructuralEquationModel.factuallyDeveloped dyn s cause effect = (s.hasValue cause true && (Core.StructuralEquationModel.normalDevelopment dyn s).hasValue effect true)
Instances For
factuallyDeveloped unfolds to a conjunction of two hasValue checks.
Does any causal law directly connect cause to effect?
Equations
- One or more equations did not get rendered due to their size.
Instances For
Does the intermediate have a causal law not depending on cause?
Equations
- One or more equations did not get rendered due to their size.
Instances For
All variables mentioned in a dynamics (preconditions and effects).
Equations
- One or more equations did not get rendered due to their size.
Instances For
Inner (endogenous) variables: those appearing as effects of laws.
Equations
- Core.StructuralEquationModel.innerVariables dyn = (List.map (fun (x : Core.StructuralEquationModel.CausalLaw) => x.effect) dyn.laws).eraseDups
Instances For
Intervene: Pearl's do(X = val).
Sets variable target to val and removes all laws that would
determine target, so the intervention overrides the structural
equations rather than being overwritten by them.
Equations
- Core.StructuralEquationModel.intervene dyn s target val = ({ laws := List.filter (fun (x : Core.StructuralEquationModel.CausalLaw) => x.effect != target) dyn.laws }, s.extend target val)
Instances For
Manipulates: Does intervening on cause change the value of effect?
Compares normal development under do(cause = true) vs do(cause = false).
If the effect's value differs, then cause manipulates effect.
This is the interventionist criterion for causation: X causes Y iff there exists an intervention on X that changes Y.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Causal Sufficiency (@cite{nadathur-lauer-2020}, Definition 23). C is causally sufficient for E in situation s iff adding C and developing normally produces E.
Equations
- Core.StructuralEquationModel.causallySufficient dyn s cause effect = (Core.StructuralEquationModel.normalDevelopment dyn (s.extend cause true)).hasValue effect true
Instances For
Is s' a consistent supersituation of base under dynamics dyn?
(@cite{nadathur-2024} Definition 9b)
For each inner variable newly determined in s' (not in base),
the dynamics from base must not entail the opposite value.
Approximation: this is a per-variable check — it verifies each inner
variable independently against the development of base, not the joint
development of s'. This is sound (anything inconsistent per-variable is
also inconsistent jointly) but conservative: it may admit supersituations
that become inconsistent only through variable interactions. For the small
models in @cite{nadathur-lauer-2020} this is adequate.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Causal Necessity (@cite{nadathur-2024} Definition 10b).
⟨cause, true⟩ is causally necessary for ⟨effect, true⟩ relative
to situation s under dynamics dyn iff:
- Precondition:
s ⊭_D ⟨cause, true⟩ands ⊭_D ⟨effect, true⟩ - (i) Achievability:
s[cause ↦ true]has a consistent supersituations'witheffect ∉ dom(s')wheres' ⊨_D ⟨effect, true⟩ - (ii) But-for: no consistent supersituation
s'ofswitheffect ∉ dom(s')satisfiess' ⊨_D ⟨effect, true⟩whiles' ⊭_D ⟨cause, true⟩
This supersedes the simple but-for test from @cite{nadathur-lauer-2020} Definition 24. The key improvement: the precondition prevents vacuous necessity when cause/effect are already entailed, and achievability ensures the effect is reachable before testing but-for.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- One or more equations did not get rendered due to their size.
- Core.StructuralEquationModel.instBEqCausalProfile.beq x✝¹ x✝ = false
Instances For
Extract the causal profile of a cause-effect pair.
Equations
- One or more equations did not get rendered due to their size.
Instances For
For a simple causal law c → e, the cause is necessary for the effect
relative to the empty background under @cite{nadathur-2024} Definition 10b.
The argument:
- Precondition:
normalDevelopment(empty)= empty, so neither cause nor effect is entailed - Achievability: extending with
c=truefires the law →e=true - But-for: every consistent supersituation of empty either (a) has
c=true, in which case normalDev preserves it (blocking the¬causeconjunct), or (b) hasc≠true, in which case the law doesn't fire anderemains unset (blocking theeffectconjunct)