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_scmFunction
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 Model
  • exogenous_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.7

Notes

  • Computes values in topological order so parent values are available
  • Each equation receives parent values followed by the exogenous value
  • exogenous_values fixes exogenous noise U for this unit (creative advance held constant)
  • Returned endogenous values are the settled outcomes each node contributes to its descendants
source
CausalDynamics.compute_counterfactualFunction
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:

  1. Abduction: Use provided exogenous values (assumed already inferred from evidence)
  2. Action: Create mutilated model via counterfactual_graph
  3. Prediction: Simulate factual and counterfactual outcomes with the same U

Arguments

  • scm::GraphSCM: Original Structural Causal Model
  • intervention::DoIntervention: Counterfactual intervention
  • exogenous_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 SCM
    • counterfactual::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.7
source
CausalDynamics.AbstractSCMType
AbstractSCM

Abstract 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
source
CausalDynamics.GraphSCMType
GraphSCM

A 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 structure
  • equations::Dict{Int, Function}: Dictionary mapping node indices to functions
    • Function signature: (parents, exogenous) -> value
  • 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_scm call settles all endogenous values for a fixed realisation of U
source
CausalDynamics.SymbolicSCMType
SymbolicSCM

Experimental. 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 structure
  • system::Any: Intended ModelingToolkit system (placeholder)
  • exogenous::Set{Symbol}: Set of exogenous variable names (symbols)

See Also

  • GraphSCM: Function-based SCM (supported implementation)
source
CausalDynamics.DoInterventionType
DoIntervention

Represents 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 future SymbolicSCM / CausalGraph name maps.
source
CausalDynamics.apply_interventionFunction
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 Model
  • intervention::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
source
apply_intervention(scm::GraphSCM, interventions::Vector{DoIntervention})

Apply multiple simultaneous interventions to a GraphSCM.

Arguments

  • scm::GraphSCM: Structural Causal Model
  • interventions::Vector{DoIntervention}: Vector of interventions to apply

Returns

  • GraphSCM: New SCM with all interventions applied
source
CausalDynamics.do_interventionFunction
do_intervention(variable, value)

Convenience function to create a DoIntervention object.

Arguments

  • variable::Union{Int, Symbol}: Variable to intervene on
  • value::Any: Value to set the variable to

Returns

  • DoIntervention: Intervention object representing do(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 interventions
  • apply_intervention: Apply intervention to an SCM
source
CausalDynamics.counterfactual_graphFunction
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):

  1. Abduction: Infer exogenous noise values U from observed evidence
  2. Action: Apply the intervention to create the mutilated model
  3. 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 Model
  • intervention::DoIntervention: Intervention for the counterfactual world

Returns

  • GraphSCM: Mutilated SCM (identical to apply_intervention output, 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.7

Notes

  • Shared exogenous noise: Use the same U for 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 noise
  • compute_counterfactual: Full counterfactual computation (abduction + action + prediction)
source