SCM Framework
Structural Causal Models encode a causal graph and structural equations. Use simulate_scm for forward simulation and compute_counterfactual for factual vs counterfactual outcomes with shared exogenous noise U.
CausalDynamics.simulate_scm — Function
simulate_scm(scm::GraphSCM, exogenous_values::Dict{Int, <:Any})Simulate an SCM forward given exogenous noise values, producing endogenous variable values in topological order.
Arguments
scm::GraphSCM: Structural Causal Modelexogenous_values::Dict{Int, <:Any}: Dictionary mapping node indices to exogenous noise values
Returns
Dict{Int, Any}: Dictionary mapping each node to its computed value
Examples
using CausalDynamics, Graphs
g = DiGraph(3)
add_edge!(g, 1, 2) # X → Y
add_edge!(g, 2, 3) # Y → Z
equations = Dict{Int, Function}(
1 => (u) -> u,
2 => (x, u) -> x + u,
3 => (y, u) -> 2y + u
)
scm = GraphSCM(g, equations, Set{Int}())
# Simulate with exogenous values
U = Dict(1 => 1.0, 2 => 0.5, 3 => -0.3)
values = simulate_scm(scm, U)
# values[1] = 1.0, values[2] = 1.5, values[3] = 2.7Notes
- Computes values in topological order so parent values are available
- Each equation receives parent values followed by the exogenous value
exogenous_valuesfixes exogenous noiseUfor this unit (creative advance held constant)- Returned endogenous values are the settled outcomes each node contributes to its descendants
CausalDynamics.compute_counterfactual — Function
compute_counterfactual(scm::GraphSCM, intervention::DoIntervention,
exogenous_values::Dict{Int, <:Any})Perform full counterfactual computation: given an SCM, an intervention, and the exogenous noise realisation for a specific unit, compute both the factual and counterfactual outcomes.
This implements Pearl's three-step procedure:
- Abduction: Use provided exogenous values (assumed already inferred from evidence)
- Action: Create mutilated model via
counterfactual_graph - Prediction: Simulate factual and counterfactual outcomes with the same
U
Arguments
scm::GraphSCM: Original Structural Causal Modelintervention::DoIntervention: Counterfactual interventionexogenous_values::Dict{Int, <:Any}: Exogenous noise values for this unit
Returns
NamedTuple{(:factual, :counterfactual)}: Named tuple with:factual::Dict{Int, Any}: Values under the original SCMcounterfactual::Dict{Int, Any}: Values under the counterfactual intervention
Examples
using CausalDynamics, Graphs
g = DiGraph(3)
add_edge!(g, 1, 2)
add_edge!(g, 2, 3)
equations = Dict{Int, Function}(
1 => (u) -> u,
2 => (z, u) -> z + u,
3 => (x, u) -> 2x + u
)
scm = GraphSCM(g, equations, Set{Int}())
U = Dict(1 => 1.0, 2 => 0.5, 3 => -0.3)
result = compute_counterfactual(scm, do_intervention(2, 10.0), U)
result.factual[3] # 2.7
result.counterfactual[3] # 19.7CausalDynamics.AbstractSCM — Type
AbstractSCMAbstract type for Structural Causal Models (SCMs).
An SCM consists of:
- A causal graph (DAG)
- Structural equations: each variable from its parents and exogenous noise
- Exogenous variables (
U): unmodelled unit-level factors
Subtypes
GraphSCM: SCM with graph and function-based equations (supported)SymbolicSCM: Experimental placeholder for ModelingToolkit-backed equations
References
- Pearl, J. (2009). Causality, Chapter 1
CausalDynamics.GraphSCM — Type
GraphSCMA Structural Causal Model represented as a graph and structural equations.
Each variable X is defined by a function X = f_X(Pa(X), U_X), where Pa(X) are parents and U_X is exogenous noise.
Fields
graph::DiGraph: Directed acyclic graph representing causal structureequations::Dict{Int, Function}: Dictionary mapping node indices to functions- Function signature:
(parents, exogenous) -> value
- Function signature:
exogenous::Set{Int}: Set of nodes that are exogenous (have no parents)
Examples
using CausalDynamics, Graphs
# Create graph: X → Y, with U_X and U_Y exogenous
g = DiGraph(4)
add_edge!(g, 1, 3) # X → Y
# Nodes: 1=X, 2=U_X, 3=Y, 4=U_Y
equations = Dict(
1 => (pa, ex) -> ex[2], # X = U_X
3 => (pa, ex) -> pa[1] + ex[4] # Y = X + U_Y
)
scm = GraphSCM(g, equations, Set([2, 4]))Notes
- Functions should be deterministic given parents and exogenous noise
- Exogenous nodes must have no parents in the graph
- One
simulate_scmcall settles all endogenous values for a fixed realisation ofU
CausalDynamics.SymbolicSCM — Type
SymbolicSCMExperimental. Placeholder Structural Causal Model for future ModelingToolkit.jl integration. Not part of the supported identify / simulate_scm path; use GraphSCM for executable models. The system field is typed as Any until a concrete MTK backend lands.
Fields
graph::DiGraph: Directed acyclic graph representing causal structuresystem::Any: Intended ModelingToolkit system (placeholder)exogenous::Set{Symbol}: Set of exogenous variable names (symbols)
See Also
GraphSCM: Function-based SCM (supported implementation)
CausalDynamics.DoIntervention — Type
DoInterventionRepresents a do(·) intervention on a single variable, fixing it to a definite value (the mechanism is imposed, not merely observed).
For static GraphSCM this replaces the structural assignment. For continuous CDMs it is accepted as a hard pin (prefer DoPin / do_pin).
Fields
variable::Union{Int, Symbol}: Variable to intervene on (node index or symbol)value::Any: Value to set the variable to
Notes
- For
GraphSCM, use integer node indices; symbol resolution is reserved for futureSymbolicSCM/CausalGraphname maps.
CausalDynamics.apply_intervention — Function
apply_intervention(scm::GraphSCM, intervention::DoIntervention)Apply a do(·) intervention to a GraphSCM, returning a new SCM.
An intervention do(X = x) replaces the structural equation for variable X with a constant assignment X := x, removing its dependence on parents (modularity principle). All other equations remain unchanged.
Arguments
scm::GraphSCM: Structural Causal Modelintervention::DoIntervention: Intervention to apply
Returns
GraphSCM: New SCM with the intervention applied (mutilated model)
Examples
using CausalDynamics, Graphs
g = DiGraph(3)
add_edge!(g, 1, 2) # Z → X
add_edge!(g, 2, 3) # X → Y
equations = Dict{Int, Function}(
1 => (args...) -> args[end], # Z = U_Z
2 => (z, u) -> z + u, # X = Z + U_X
3 => (x, u) -> 2x + u # Y = 2X + U_Y
)
scm = GraphSCM(g, equations, Set([1]))
# Intervene: do(X = 5)
intervention = do_intervention(2, 5.0)
scm_do = apply_intervention(scm, intervention)
# In scm_do, node 2's equation is replaced with constant 5.0,
# and the edge Z → X is removed.Notes
- Modularity principle: Only the intervened variable's equation is changed
- Incoming edges to the intervened node are removed (parents no longer prehend into it)
- The intervened variable's equation becomes
(args...) -> value
References
- Pearl, J. (2009). Causality, Chapter 1.3 and Chapter 3.2
apply_intervention(scm::GraphSCM, interventions::Vector{DoIntervention})Apply multiple simultaneous interventions to a GraphSCM.
Arguments
scm::GraphSCM: Structural Causal Modelinterventions::Vector{DoIntervention}: Vector of interventions to apply
Returns
GraphSCM: New SCM with all interventions applied
CausalDynamics.do_intervention — Function
do_intervention(variable, value)Convenience function to create a DoIntervention object.
Arguments
variable::Union{Int, Symbol}: Variable to intervene onvalue::Any: Value to set the variable to
Returns
DoIntervention: Intervention object representingdo(variable = value)
Examples
using CausalDynamics
# Intervene on variable :x, setting it to 1.0
intervention = do_intervention(:x, 1.0)
# Intervene on node 2, setting it to 0
intervention2 = do_intervention(2, 0)See Also
DoIntervention: Type representing interventionsapply_intervention: Apply intervention to an SCM
CausalDynamics.counterfactual_graph — Function
counterfactual_graph(scm::GraphSCM, intervention::DoIntervention)Generate a counterfactual (twin-network) graph for reasoning about what would have happened under an alternative intervention, for the same unit.
Counterfactual reasoning requires three steps (Pearl 2009, Chapter 7):
- Abduction: Infer exogenous noise values U from observed evidence
- Action: Apply the intervention to create the mutilated model
- Prediction: Simulate the mutilated model with the same U
This function performs step 2 — creating the mutilated SCM that shares the same exogenous noise structure as the original.
Arguments
scm::GraphSCM: Original Structural Causal Modelintervention::DoIntervention: Intervention for the counterfactual world
Returns
GraphSCM: Mutilated SCM (identical toapply_interventionoutput, but semantically used for counterfactual reasoning with shared exogenous noise)
Examples
using CausalDynamics, Graphs
# SCM: Z → X → Y
g = DiGraph(3)
add_edge!(g, 1, 2)
add_edge!(g, 2, 3)
equations = Dict{Int, Function}(
1 => (u) -> u,
2 => (z, u) -> z + u,
3 => (x, u) -> 2x + u
)
scm = GraphSCM(g, equations, Set{Int}())
# Step 1: Abduction — infer U from observations
U_observed = Dict(1 => 1.0, 2 => 0.5, 3 => -0.3)
# Step 2: Action — create counterfactual graph
intervention = do_intervention(2, 10.0) # What if X had been 10?
scm_cf = counterfactual_graph(scm, intervention)
# Step 3: Prediction — simulate with SAME exogenous noise
factual = simulate_scm(scm, U_observed)
counterfactual = simulate_scm(scm_cf, U_observed)
# factual[3] = 2 * (1.0 + 0.5) + (-0.3) = 2.7
# counterfactual[3] = 2 * 10.0 + (-0.3) = 19.7Notes
- Shared exogenous noise: Use the same
Ufor factual and counterfactual simulation so you compare alternative outcomes for one unit, not two populations. - For deterministic SCMs, abduction is straightforward (solve for U).
- For stochastic SCMs, abduction requires posterior inference P(U | evidence).
- The returned SCM is structurally identical to
apply_intervention(scm, intervention), but the semantic intent is different: counterfactual (same U) vs interventional (marginalising over U).
References
- Pearl, J. (2009). Causality, Chapter 7
- Shpitser, I., & Pearl, J. (2009). Complete identification methods for the causal hierarchy
See Also
apply_intervention: Apply intervention (for interventional reasoning)simulate_scm: Simulate SCM forward given exogenous noisecompute_counterfactual: Full counterfactual computation (abduction + action + prediction)