Integration API
Functions for integrating CausalDynamics.jl with other packages (TMLE.jl, RxInfer/GraphPPL).
TMLE
CausalDynamics.prepare_for_tmle — Function
prepare_for_tmle(g, X, Y; node_names=nothing)Prepare adjustment set from causal graph for use with TMLE.jl.
Arguments
g: Directed acyclic graph (DiGraph or CausalGraph)X: Treatment node (integer or symbol)Y: Outcome node (integer or symbol)node_names: Optional mapping from node indices to symbols/names. AcceptsDict{Int,Symbol}or aVectorof names (indexinames nodei). If provided, returns symbol names; otherwise returns node indices. ForCausalGraph, node names are automatically extracted from graph properties if not provided.
Returns
adj_set: Vector of confounders (symbols or integers) ready for TMLE.jlis_identifiable: Boolean indicating if effect is identifiable via backdoor adjustment
Examples
using CausalDynamics, TMLE, DataFrames
# Create graph: Z → X → Y, Z → Y
g = DiGraph(3)
add_edge!(g, 1, 2) # Z → X
add_edge!(g, 1, 3) # Z → Y
add_edge!(g, 2, 3) # X → Y
# With node names (Dict or vector indexed by node)
node_names = Dict(1 => :Z, 2 => :X, 3 => :Y)
confounders, identifiable = prepare_for_tmle(g, 2, 3; node_names=node_names)
# Returns: ([:Z], true)
confounders, identifiable = prepare_for_tmle(g, 2, 3; node_names=[:Z, :X, :Y])
# Returns: ([:Z], true)
# Without node names (returns indices)
confounders, identifiable = prepare_for_tmle(g, 2, 3)
# Returns: ([1], true)
# With CausalGraph (auto-detects node names)
g2 = CausalGraph(3)
add_edge!(g2, 1, 2)
add_edge!(g2, 1, 3)
add_edge!(g2, 2, 3)
set_node_prop!(g2, 1, :name, :Z)
set_node_prop!(g2, 2, :name, :X)
set_node_prop!(g2, 3, :name, :Y)
confounders, identifiable = prepare_for_tmle(g2, 2, 3) # Auto-uses node names
# Returns: ([:Z], true)See Also
backdoor_adjustment_set: Find backdoor adjustment setis_backdoor_adjustable: Check if backdoor adjustment is possible
CausalDynamics.estimate_effect — Function
estimate_effect(g, data, X, Y; node_names=nothing, method=:tmle, kwargs...)
estimate_effect(g, X, Y; method=:tmle, kwargs...)Estimate causal effect using identified adjustment set and TMLE.jl.
This function combines identification (CausalDynamics.jl) with estimation (TMLE.jl) in a single workflow. For CausalGraph, data and node names can be automatically extracted from graph properties.
Arguments
g: Directed acyclic graph (DiGraph or CausalGraph)data: DataFrame or table with observed data (optional for CausalGraph if data is attached)X: Treatment variable (integer index or symbol/name)Y: Outcome variable (integer index or symbol/name)node_names: Optional mapping from node indices to symbols/names. ForCausalGraph, node names are automatically extracted from graph properties if not provided.method: Estimation method (currently only:tmlesupported)kwargs...: Additional arguments passed to TMLE.jl estimation function
Returns
- TMLE.jl result object with effect estimate, confidence intervals, etc.
Examples
using CausalDynamics, TMLE, DataFrames
# Create graph: Z → X → Y, Z → Y
g = DiGraph(3)
add_edge!(g, 1, 2) # Z → X
add_edge!(g, 1, 3) # Z → Y
add_edge!(g, 2, 3) # X → Y
# Create data
data = DataFrame(
Z = randn(100),
X = rand([0, 1], 100),
Y = randn(100)
)
# Estimate effect with node names
node_names = Dict(1 => :Z, 2 => :X, 3 => :Y)
result = estimate_effect(g, data, 2, 3; node_names=node_names)
# With CausalGraph - data and names attached
g2 = CausalGraph(3)
add_edge!(g2, 1, 2)
add_edge!(g2, 1, 3)
add_edge!(g2, 2, 3)
set_node_prop!(g2, 1, :name, :Z)
set_node_prop!(g2, 2, :name, :X)
set_node_prop!(g2, 3, :name, :Y)
attach_data!(g2, data)
result = estimate_effect(g2, 2, 3) # Auto-uses attached data and namesNotes
- Requires TMLE.jl to be loaded:
using TMLE - If no adjustment set is found, a warning is issued but estimation may still proceed
- Additional TMLE.jl arguments can be passed via
kwargs... - For
CausalGraph, if data is attached and node names are set, they are used automatically
See Also
prepare_for_tmle: Prepare adjustment set for TMLE.jlbackdoor_adjustment_set: Find backdoor adjustment setattach_data!: Attach data to CausalGraph
CausalDynamics.get_tmle_confounders — Function
get_tmle_confounders(g, X, Y; node_names=nothing)Get confounders for TMLE.jl from causal graph.
Convenience function that returns just the confounders vector, without the identifiability check.
Arguments
g: Directed acyclic graphX: Treatment node (integer or symbol)Y: Outcome node (integer or symbol)node_names: Optional mapping from node indices to symbols/names
Returns
- Vector of confounders (symbols or integers) ready for TMLE.jl
Examples
using CausalDynamics
g = DiGraph(3)
add_edge!(g, 1, 2)
add_edge!(g, 1, 3)
add_edge!(g, 2, 3)
node_names = Dict(1 => :Z, 2 => :X, 3 => :Y)
confounders = get_tmle_confounders(g, 2, 3; node_names=node_names)
# Returns: [:Z]See Also
prepare_for_tmle: Full preparation with identifiability checkbackdoor_adjustment_set: Find backdoor adjustment set
RxInfer / GraphPPL (optional)
Requires using RxInfer to load the CausalDynamicsRxInfer extension.
CausalDynamics.prepare_for_rxinfer — Function
prepare_for_rxinfer(g, X, Y; node_names=nothing, data=nothing)Prepare causal graph and data for use with RxInfer.jl.
RxInfer.jl uses various data formats and requires explicit variable specifications. This function extracts the necessary information from a CausalGraph or prepares it from a plain graph.
Arguments
g: Directed acyclic graph (DiGraph or CausalGraph)X::Int: Treatment node indexY::Int: Outcome node indexnode_names::Union{Dict{Int, Symbol}, Nothing}: Optional mapping from node indices to names. ForCausalGraph, automatically extracted if not provided.data: Optional data (DataFrame, NamedTuple, or other). ForCausalGraph, automatically extracted if attached and not provided.
Returns
NamedTuplewith fields:graph: The underlying graph (DiGraph)treatment: Treatment variable name (Symbol) or index (Int)outcome: Outcome variable name (Symbol) or index (Int)confounders: Vector of confounder names (Symbol) or indices (Int)data: Data in format compatible with RxInfernode_names: Dictionary mapping node indices to namesis_identifiable: Boolean indicating if effect is identifiable
Examples
using CausalDynamics, DataFrames
# With CausalGraph
g = CausalGraph(3)
add_edge!(g, 1, 2)
add_edge!(g, 1, 3)
add_edge!(g, 2, 3)
set_node_prop!(g, 1, :name, :Z)
set_node_prop!(g, 2, :name, :X)
set_node_prop!(g, 3, :name, :Y)
data = DataFrame(Z=randn(100), X=rand([0,1], 100), Y=randn(100))
attach_data!(g, data)
spec = prepare_for_rxinfer(g, 2, 3)
# Returns: (graph=..., treatment=:X, outcome=:Y, confounders=[:Z], data=..., ...)
# Use in RxInfer model
using RxInfer
# Use spec.treatment, spec.outcome, spec.confounders, spec.dataNotes
- Data is returned as-is - RxInfer accepts various formats
- Node names are required for meaningful variable names in models
- If node names are not available, indices are used instead
- RxInfer typically uses NamedTuples or dictionaries for data
See Also
prepare_for_turing: Prepare for Turing.jlget_tmle_confounders: Get confounders for TMLE
CausalDynamics.has_rxinfer — Function
has_rxinfer() -> BoolReturn true when the CausalDynamicsRxInfer extension is loaded (using RxInfer).
CausalDynamics.infer_backdoor_effect — Function
infer_backdoor_effect(g, data, X, Y; kwargs...) -> BackdoorInferenceResultBackdoor-adjusted variational inference for treatment effect τ (GraphPPL + RxInfer).
Requires using RxInfer before calling. See extension docstring for arguments.
CausalDynamics.backdoor_graphppl_model — Function
backdoor_graphppl_model(; n_conf=0)GraphPPL @model generator for the Gaussian backdoor head. Requires using RxInfer.
CausalDynamics.ppl_data_from_spec — Function
ppl_data_from_spec(spec; kwargs...) -> NamedTupleConvert prepare_for_rxinfer output to RxInfer data (outcome, treatment, confounder matrix). Requires using RxInfer.
CausalDynamics.prepare_for_turing — Function
prepare_for_turing(g, X, Y; node_names=nothing, data=nothing)Prepare causal graph and data for use with Turing.jl.
Turing.jl uses DataFrames or NamedTuples for data, and requires explicit variable names. This function extracts the necessary information from a CausalGraph or prepares it from a plain graph.
Arguments
g: Directed acyclic graph (DiGraph or CausalGraph)X::Int: Treatment node indexY::Int: Outcome node indexnode_names::Union{Dict{Int, Symbol}, Nothing}: Optional mapping from node indices to names. ForCausalGraph, automatically extracted if not provided.data: Optional data (DataFrame, NamedTuple, or other). ForCausalGraph, automatically extracted if attached and not provided.
Returns
NamedTuplewith fields:graph: The underlying graph (DiGraph)treatment: Treatment variable name (Symbol) or index (Int)outcome: Outcome variable name (Symbol) or index (Int)confounders: Vector of confounder names (Symbol) or indices (Int)data: Data in format compatible with Turingnode_names: Dictionary mapping node indices to namesis_identifiable: Boolean indicating if effect is identifiable
Examples
using CausalDynamics, DataFrames
# With CausalGraph
g = CausalGraph(3)
add_edge!(g, 1, 2)
add_edge!(g, 1, 3)
add_edge!(g, 2, 3)
set_node_prop!(g, 1, :name, :Z)
set_node_prop!(g, 2, :name, :X)
set_node_prop!(g, 3, :name, :Y)
data = DataFrame(Z=randn(100), X=rand([0,1], 100), Y=randn(100))
attach_data!(g, data)
spec = prepare_for_turing(g, 2, 3)
# Returns: (graph=..., treatment=:X, outcome=:Y, confounders=[:Z], data=..., ...)
# Use in Turing model
using Turing
@model function causal_model(spec)
# Use spec.treatment, spec.outcome, spec.confounders, spec.data
# ...
endNotes
- Data is returned as-is (DataFrame or NamedTuple) - Turing accepts both
- Node names are required for meaningful variable names in models
- If node names are not available, indices are used instead
See Also
prepare_for_rxinfer: Prepare for RxInfer.jlget_tmle_confounders: Get confounders for TMLE
CausalDynamics.get_data_for_ppl — Function
get_data_for_ppl(g; format=:dataframe)Extract data from CausalGraph in a format suitable for probabilistic programming.
Arguments
g::CausalGraph: The causal graph with attached dataformat::Symbol: Desired output format (:dataframe,:namedtuple,:dict)
Returns
- Data in the requested format, or
nothingif no data attached
Examples
using CausalDynamics, DataFrames
g = CausalGraph(3)
data = DataFrame(Z=randn(100), X=rand([0,1], 100), Y=randn(100))
attach_data!(g, data)
# Get as DataFrame (default)
df = get_data_for_ppl(g) # DataFrame
# Get as NamedTuple (for Turing/RxInfer)
nt = get_data_for_ppl(g; format=:namedtuple) # NamedTuple
# Get as Dict
dict = get_data_for_ppl(g; format=:dict) # DictNotes
- Requires data to be attached to the graph
- Format conversion may have performance implications for large datasets
- NamedTuple format is efficient for Turing.jl
- Dict format is flexible for various PPL packages
See Also
get_data: Get data as-isattach_data!: Attach data to graph
Associations.jl (optional)
Requires using Associations to load the CausalDynamicsAssociationsExt extension.
CausalDynamics.has_associations — Function
has_associations() -> BoolReturn true when the CausalDynamicsAssociationsExt extension is loaded (using Associations).
CausalDynamics.prepare_from_discovery — Function
prepare_from_discovery(g, treatment, outcome; node_names=nothing, complete=false)Run backdoor identification on a candidate graph from discovery (PC, OCE, domain knowledge).
The input graph is treated as structural hypothesis, not ground truth. Discovery typically returns a CPDAG or lag parent set under faithfulness and sufficiency assumptions.
Returns
confounders: adjustment set (symbols ifnode_namesgiven, else node indices)identifiable: whether a backdoor adjustment set exists
See Also
CausalDynamics.cpdag_to_dag — Function
cpdag_to_dag(g::Graphs.DiGraph) -> Graphs.DiGraphBreak remaining bidirectional edges in a PC output (CPDAG) to obtain a DAG.
When both i → j and j → i are present, drops j → i if i < j. Discovery outputs may still be only partially oriented; this is a pragmatic heuristic before calling backdoor identification.
CausalDynamics.oce_parents_to_temporal_spec — Function
oce_parents_to_temporal_spec(parents, variables::AbstractVector{Symbol}) -> TemporalDAGSpecConvert OCE OCESelectedParents output to a TemporalDAGSpec.
Arguments
parents: vector of parent selections (one per variable), e.g. frominfer_graph(OCE(), ts)variables: symbol name for each series index (variables[i]is variablei)
Lag convention
OCE stores embedding lags in parents_τs (typically negative, e.g. -1 for xⱼ(-1)). These map to LaggedEdge lags as lag = abs(τ), so τ = -1 becomes lag = 1 (parent at t-1 causes child at t), matching Ch. 28 unrolling.
CausalDynamics.digraph_with_names — Function
digraph_with_names(g::AbstractGraph, names::AbstractVector{Symbol}) -> CausalGraphWrap g in a CausalGraph and attach :name properties from names.
length(names) must equal nv(g).
CausalDynamics.infer_pc_graph — Function
infer_pc_graph(data, names; kwargs...) -> CausalGraphPC discovery with node names attached. Requires using Associations.
CausalDynamics.infer_pc_digraph — Function
infer_pc_digraph(data; kwargs...) -> SimpleDiGraphRun PC via Associations.jl. Requires using Associations.
CausalDynamics.infer_oce_parents — Function
infer_oce_parents(ts; kwargs...) -> VectorOCE parent selection. Requires using Associations.
CausalDynamics.infer_oce_temporal_spec — Function
infer_oce_temporal_spec(ts, variables; kwargs...) -> TemporalDAGSpecOCE discovery converted to TemporalDAGSpec. Requires using Associations.
CausalDynamics.discover_and_prepare — Function
discover_and_prepare(data, treatment, outcome; method=:pc, kwargs...)Discover a graph then call prepare_from_discovery. Requires using Associations.
CausalDynamics.DiscoveryGraphMetadata — Type
DiscoveryGraphMetadataLightweight record of how a candidate graph was obtained (for logging and reproducibility).
CausalDynamics.delay_embed_cause — Function
delay_embed_cause(x, p) -> MatrixCause embedding X_t = (x_t, …, x_{t-p+1}) with rows as time (length T-p). x may be a vector (univariate) or dx × T matrix (variables × time).
CausalDynamics.delay_embed_effect — Function
delay_embed_effect(y, p) -> MatrixEffect embedding Y_{t+1} = (y_{t+1}, y_t, …, y_{t-p+1}) (rows as time).
CausalDynamics.aggregate_parent_inclusion — Function
aggregate_parent_inclusion(models, model_scores; K=nothing, n_variables=nothing)Inclusion frequency among the K lowest-scoring models (default: about one third of the model list). Returns (variable_scores, ranking) where ranking lists variable indices best-first.
CausalDynamics.posterior_mean_τ — Function
posterior_mean_τ(τ_posterior) -> Float64Posterior mean of τ from RxInfer marginals. Requires using RxInfer.
CausalDynamics.state_index_map — Function
state_index_map(variables) -> Dict{Symbol, Int}Map each symbol in variables to its 1-based state index.
SciML / OrdinaryDiffEq (optional)
Requires using OrdinaryDiffEq to load CausalDynamicsSciMLExt for solve_cdm. The types and façade functions below are always exported from core.
CausalDynamics.AbstractCausalIntervention — Type
AbstractCausalInterventionRoot type for all do(·) interventions in CausalDynamics: static SCM assignments, discrete-time CDM sequences/policies, and continuous-time CDM interventions.
Dispatch on the concrete subtype and the model (SCM / DiscreteTimeCDM / ContinuousCDMSpec) determines how the intervention is applied.
CausalDynamics.AbstractContinuousIntervention — Type
AbstractContinuousInterventionContinuous-time interventions for solve_cdm / SciML: pins, initial conditions, soft forces, and RHS replacements (continuous-CDM taxonomy).
CausalDynamics.ContinuousCDMSpec — Type
ContinuousCDMSpecNamed continuous-time CDM: maps endogenous symbols to state-vector indices, with optional parent sets for each coordinate (ODE / continuous causal graph).
Use with ode_problem_cdm and solve_cdm when the mechanism is an ODE rather than a DiscreteTimeCDM.
CausalDynamics.continuous_cdm_graph — Function
continuous_cdm_graph(spec::ContinuousCDMSpec) -> SimpleDiGraphDirected graph with an edge p → child for each declared parent set entry.
CausalDynamics.with_parents — Function
with_parents(spec::ContinuousCDMSpec, parents) -> ContinuousCDMSpecReturn a copy of spec with parent sets replaced by parents (same validation as ContinuousCDMSpec).
CausalDynamics.ranked_variables_to_parents — Function
ranked_variables_to_parents(ranking, variables; target, max_parents=2) -> DictBuild a single-target parent map from a variable ranking (ranking[i] is the rank of variables[i], lower is better; omit target).
CausalDynamics.has_sciml — Function
has_sciml() -> BoolReturn true when the CausalDynamicsSciMLExt extension is loaded (using OrdinaryDiffEq).
CausalDynamics.has_sciml_sensitivity — Function
has_sciml_sensitivity() -> BoolReturn true when the SciMLSensitivity extension is loaded.
CausalDynamics.has_data_interpolations — Function
has_data_interpolations() -> BoolReturn true when the DataInterpolations extension is loaded.
CausalDynamics.ode_problem_cdm — Function
ode_problem_cdm(spec, rhs!, u0, tspan, p; intervention=nothing, kwargs...)Build an ODEProblem with length(u0) == length(spec.variables). Requires using OrdinaryDiffEq.
CausalDynamics.solve_cdm — Function
solve_cdm(spec, rhs!, u0, tspan, p; intervention=nothing, kwargs...) -> solIntegrate a continuous CDM and return the SciML solution. Requires using OrdinaryDiffEq.
CausalDynamics.terminal_state — Function
terminal_state(spec::ContinuousCDMSpec, sol) -> NamedTupleTerminal endogenous state keyed by symbol. Requires using OrdinaryDiffEq.
CausalDynamics.state_series — Function
state_series(spec::ContinuousCDMSpec, sol) -> NamedTupleTime series of each endogenous variable. Requires using OrdinaryDiffEq.
CausalDynamics.interventional_rhs — Function
interventional_rhs(rhs!, spec::ContinuousCDMSpec, intervention)Wrap a continuous RHS for a continuous-CDM intervention. Requires using OrdinaryDiffEq.
CausalDynamics.intervention_callback — Function
intervention_callback(spec, intervention)SciML DiscreteCallback / CallbackSet maintaining hard pins. Requires using OrdinaryDiffEq.
CausalDynamics.apply_initial_conditions! — Function
apply_initial_conditions!(u0, spec, intervention) -> u0Mutate a copy-friendly u0 for initial-condition and hard-pin interventions. Soft force / RHS replacements leave u0 unchanged.
CausalDynamics.continuous_interventions — Function
continuous_interventions(items...)Bundle one or more causal interventions for solve_cdm.
CausalDynamics.ContinuousInterventionSet — Type
ContinuousInterventionSetOrdered collection of continuous interventions applied left-to-right.
CausalDynamics.DoPin — Type
DoPinHard pin: set the initial condition to value and hold the coordinate fixed (ẋᵏ := 0). SciML applies this without mutating u inside the RHS; a DiscreteCallback reasserts the value after accepted steps to control drift.
CausalDynamics.DoInitialCondition — Type
DoInitialConditionSet only the initial value of variable to value (leave the RHS unchanged).
Corresponds to do(x₀ᵏ := ξ) in continuous-time CDMs [@peters2022causal].
CausalDynamics.DoForce — Type
DoForceAdd a soft restoring term -κ (u - target) to the RHS of variable.
CausalDynamics.DoRhs — Type
DoRhsReplace the RHS of variable with du_fn(u, p, t) -> Real.
CausalDynamics.do_pin — Function
do_pin(variable, value)Hard pin for continuous CDMs (DoPin). Also accepted: static DoIntervention as a pin for backwards compatibility.
CausalDynamics.do_ic — Function
do_ic(variable, value)Initial-condition intervention do(x₀ := value).
CausalDynamics.do_force — Function
do_force(variable, target; κ = 1.0)Soft force toward target with strength κ.
CausalDynamics.do_rhs — Function
do_rhs(variable, du_fn)Replace the coordinate RHS of variable with du_fn(u, p, t).
CausalDynamics.forward_sensitivity_cdm — Function
forward_sensitivity_cdm(spec, rhs!, u0, tspan, p; kwargs...) -> solForward local sensitivity of a continuous CDM via SciMLSensitivity. Requires using OrdinaryDiffEq, SciMLSensitivity.
Interventional Embedding Entropy (IEE)
Julia port of the smsxiaomayi/IEE reference algorithm (Shi et al.). Prefer mi = :auto (Associations KSG1 when loaded); use :reference for MATLAB concordance.
CausalDynamics.interventional_embedding_entropy — Function
interventional_embedding_entropy(x, y; p=1, k=2, theiler=nothing, n_delta=10, mi=:auto) -> Float64Compute IEE from cause series x to effect series y (IntDC ranking score).
Arguments
x,y: univariate vectors, ord × Tmatrices (variables × time)p: embedding order (delay length)k: k-NN order for MI (k ≥ 2)theiler: half-width of Theiler window (defaults top)n_delta: number of neighbours used as local perturbation proxiesmi: mutual-information estimator —:auto(Associations KSG1 when loaded, else reference),:associations(requiresusing Associations), or:reference(MATLAB-faithfulMIknnport for concordance tests)backend: deprecated alias formi(kept for notebook sessions that still expect the old keyword)
References
- Shi et al. (2026), The Innovation; arXiv:2407.01621
- Reference code: https://github.com/smsxiaomayi/IEE
CausalDynamics.iee_score_matrix — Function
iee_score_matrix(series; variables=nothing, kwargs...) -> Matrix{Float64}Pairwise IEE matrix for a vector of univariate series (series[i] is variable i). Diagonal is zero. Keyword arguments are forwarded to interventional_embedding_entropy.
CausalDynamics.iee_to_temporal_spec — Function
iee_to_temporal_spec(scores, variables; threshold, lag=1) -> TemporalDAGSpecConvert an IEE score matrix into a TemporalDAGSpec by retaining directed edges with score ≥ threshold at lag lag (default 1: parent at t-1).
CausalDynamics.infer_iee_temporal_spec — Function
infer_iee_temporal_spec(series, variables; threshold, kwargs...) -> TemporalDAGSpecCompute pairwise IEE and threshold into a TemporalDAGSpec.
ODE parent discovery
Leave-one-environment ranking for continuous-CDM parent sets (CausalKinetiX reference method). Load DataInterpolations for cubic-spline derivatives.
CausalDynamics.ODEParentRanking — Type
ODEParentRankingResult of infer_ode_parents: candidate parent-index sets, their cross-environment instability scores (lower is better), per-variable inclusion scores, and a variable ranking (best first).
CausalDynamics.infer_ode_parents — Function
infer_ode_parents(times, trajectories, env, target; max_size=2, K=nothing)Infer an ODEParentRanking for main-effect parent sets of target.
Uses cubic-spline derivatives when DataInterpolations is loaded; otherwise finite differences. Reference method: CausalKinetiX [@pfister2019causalkinetix].
CausalDynamics.score_ode_parent_sets — Function
score_ode_parent_sets(times, trajectories, env, target, models; differentiate=nothing)Score each candidate parent-index set for target coordinate target.
Arguments
times: length-Lobservation gridtrajectories: vector ofd × Lmatrices (one per repetition)env: environment id per repetitiontarget: 1-based target coordinatemodels: candidate parent-index setsdifferentiate:(t, y) -> dy(defaults tofinite_difference_derivative)
CausalDynamics.ode_parent_ranking_to_continuous_spec — Function
ode_parent_ranking_to_continuous_spec(result, variables; max_parents=2) -> ContinuousCDMSpecMap an ODEParentRanking onto a ContinuousCDMSpec parent map for the ranked target.
CausalDynamics.candidate_parent_sets — Function
candidate_parent_sets(d; max_size=2) -> Vector{Vector{Int}}All non-empty subsets of {1,…,d} with size at most max_size (1-based indices).
CausalDynamics.finite_difference_derivative — Function
finite_difference_derivative(t, y) -> Vector{Float64}Central finite differences on possibly irregular grids (one-sided at endpoints).