CausalEngine usage and implementation guide

Contents

Use this guide to choose a workflow, declare its assumptions, inspect results, and find the implementation. Runtime API baseline: 0.1.0a13 with CSCM_capo at 2062d7ac. The canonical source layout is src/causalengine/; older pins and validation records describe their own earlier layouts. English is the documentation default.

Reader question Workflow
I have data but no graph Graph-free means: supply a valid functional; data alone does not certify it
I have a graph and data Graph-based mean
I need simulated tasks or known conditional outcome means SCM generation and truth
I need an ATE or CATE Paired effects
I need to change implementation code Implementation map

Install · Assumptions and results · Limitations · Validation/provenance · Complete tutorial

Installation

Python 3.11+ is required. Install the public PyPI distribution CausalScience in your project environment:

python -m pip install CausalScience
python -m causalscience.demo

Use import causalscience in Python. The published release is 0.1.0a13; python -m pip install "CausalScience==0.1.0a13" pins that release. CausalEngine remains the project/repository name while its separate PyPI name request is pending. See the installation guide for environment setup, verification, and explicitly historical source pins.

Workflows

Identify and estimate a mean

Identification asks whether a graph query has an observational formula. Estimation fits that formula from observed data. This complete binary example runs the current default UCA score for the mean under A=1:

import networkx as nx
import numpy as np
import pandas as pd
from causalscience import fit_query_mean

rng = np.random.default_rng(4)
x = rng.binomial(1, .5, 500)
a = rng.binomial(1, .2 + .5*x)
y = rng.binomial(1, .1 + .3*x + .4*a)
G = nx.DiGraph([("X", "A"), ("X", "Y"), ("A", "Y")])
for node in G:
    G.nodes[node].update(observed=True, variable_type="binary")
fit = fit_query_mean(G, ["A"], ["Y"], pd.DataFrame(dict(X=x, A=a, Y=y)),
    {"A": 1}, num_boost_round=20, max_seconds=None)
assert fit["mode"] == "UCA_SCORE" and fit["criterion"] == "FMC"
assert fit["causal_interval"] is None
print(fit["estimate"], fit["inference_status"])

estimate is an interventional mean, not an automatic treatment-effect difference. identify_query(G, ["A"], ["Y"]) can inspect graph identification separately; fit_query_mean performs its own identification and does not consume the earlier result dictionary. Its automatic fit checks the Fixed-partition Multilinearity Criterion (FMC) and then the Multilinearity Criterion (MC) if needed; FMC implies MC. Their time budget is shared. A nonaccepted or unfinished search is not a license to estimate an unsupported target. method="FD" also uses the default full score; explicit backend="native" retains earlier plug-in means.

Graph-free mean estimators

Use these when you already choose the identifying functional. All require explicit schema/support information and fixed discrete actions. They reuse the same XGBoost/minimum-L2 score engine and report graph_verification="not_run". They do not claim a graph was supplied or MC/FMC was verified.

API Functional declaration
estimate_bd Treatment, outcome, adjustment set and action
estimate_sbd Ordered treatments, action dictionary and new covariate block before each action; prior history accumulates automatically
estimate_fd Treatment, mediator(s), outcome, action and optional baseline covariates; covariates enter every conditional term and the outer average
estimate_uca Explicit graph-free expression or typed program; variable names alone do not define a functional

Start with the complete API examples or the six-API walkthrough. Each mean API targets one fixed regime; it is not an ATE until contrasted with another regime.

ATE and CATE

An average treatment effect (ATE) contrasts two regimes. estimate_ate pairs their score values on the same source/evaluation units, preserving covariance. A conditional average treatment effect (CATE) varies with baseline modifiers; estimate_cate fits XGBoost to paired pseudo-outcomes and exposes result["model"].predict(new_dataframe).

CATE requires a verified structural baseline anchor in the supplied functional and the actual compact plan, shared evaluation and aligned positional row IDs. Modifiers must be baseline: BD adjustment variables, SBD first-block covariates, or conditional-FD baseline covariates. General UCA needs the supported baseline anchor; arbitrary global-score regression is rejected. Nuisance training, CATE training and CATE validation rows are separated.

In a CATE result, inherited estimate, standard_error and conditional_score_interval summarize the marginal paired ATE fitted score. They are not conditional predictions or pointwise CATE uncertainty. The validation pseudo-outcome MSE is not true CATE error. See the effect API contract and executable examples.

SCM generation and truth

A structural causal model (SCM) fixes mechanisms and noise distributions. Its known simulator truth is separate from an estimate fitted to observed rows. A conditional average potential outcome (CAPO) is the mean under a fixed action given observed X: μa(x)=E[Y(a) | X=x].

Generator Outcome truth
StructuralCausalModel, random_scm, random_cluster_scm General mechanisms, observational/interventional sampling; random graph queries still need status checks
BDSCM / StructuralCausalModel.backdoor Observed-X mu0, mu1, propensity e, and shared-noise Y0/Y1
CSCM / StructuralCausalModel.confounded Sample truth mu0_xu/mu1_xu conditional on X and U; truth_at(X) integrates U given X for observed-X CAPO
CSCM_capo Direct observed-X mu0/mu1; sampling corrects f_Y by conditional centering while preserving the old X/A generation

task_seed fixes a task; data_seed and start_row address its rows/chunks. sample() separates observed X/A/Y from truth. sample_episode() supplies context data and query X with separate query truth. Keep U and generator truth out of learner inputs unless your experiment explicitly permits them. X is one vector node; its columns are not separate graph nodes.

Choose synthetic mechanism="parametric" or "mixed" for offline runs. The unchanged random-X default can select OpenML and need network access. The covariate/outcome options control family, roles, dependence and noise; SCM class tutorial covers custom mechanisms and copy-returning interventions.

Observed-X CAPO

Calling truth_at(x) without u computes the conditional average potential outcomes (CAPOs), μa(x)=E[Y(a) | X=x], for arms zero and one. This query uses the known simulator law: it averages the arm mean over P(U | X=x), not the marginal prior P(U) or the treatment-conditioned P(U | X=x,A=a). It is simulator truth, not an estimator fitted to observational data.

from causalscience import CSCM, CovariateOptions, OutcomeOptions

task = CSCM(d_X=5, task_seed=42,
    covariates=CovariateOptions(mechanism="parametric"),
    outcome=OutcomeOptions(family="gaussian"))
observed, latent_truth = task.sample(3, data_seed=7)
capo = task.truth_at(observed["X"])
print(capo["mu0"].shape, capo["mu1"].shape)  # (3,) (3,)
print(capo["diagnostics"]["methods"])  # analytic_gaussian for these interior rows

The result contains only mu0, mu1, and diagnostics. The Gaussian outcome, normal latent variable, linear latent effect and continuous-interior case uses an analytic Gaussian posterior. For the same latent/outcome setting, mixed X uses truncated-Gaussian first moments (numerical Sobol integration when several correlated intervals remain); OpenML uses analytic moments of its donor-bin union. These paths avoid a separate latent quadrature for every query. Other built-in parametric, mixed, and openml configurations use finite Bayes sums or numerical posterior integration. Discrete covariates and clipped continuous endpoints use probability masses; OpenML combines all donor rows producing the same deterministically filled X. truth_at_frame(frame) also supports this path and conditions on its filled, preprocessed X, not on an unspecified missing-data event.

Controls are atol=1e-6, rtol=1e-5, max_subdivisions=200, and cdf_maxpts=20000. Diagnostics distinguish analytic results from numerical quadrature and Gaussian-rectangle refinement. Reported error estimates and agreement after increasing the rectangle budget are numerical convergence checks, not certified error bounds. A support violation or unresolved numerical convergence raises an error; increasing a budget may help difficult queries. Non-Gaussian outcomes or nonlinear/non-normal latent configurations with several correlated discrete covariates can be substantially slower than sampling. Query small batches before generating benchmark labels at scale; unresolved queries require a larger numerical budget rather than a fabricated truth value.

Omit u_y, data_seed, and nonzero start_row when querying observed-X CAPOs: these means already average outcome noise. Supplying explicit u continues to support individual-noise replay through the original API. Custom covariate mechanisms without the built-in likelihood are rejected. This feature does not calibrate marginal treatment probability or change sample generation.

Direct-CAPO CSCM_capo

CSCM_capo is an alternative outcome parameterization for supervised CAPO benchmarks. It keeps the same CSCM covariate and assignment mechanisms, latent law, graph, role masks and row-noise streams for the same task/data seeds. Existing CSCM behavior is unchanged. Only the outcome mechanism changes.

from causalscience import CSCM_capo, CovariateOptions, OutcomeOptions

# family="bernoulli" gives binary outcomes; "poisson" gives counts.
task = CSCM_capo(d_X=2, task_seed=42,
    covariates=CovariateOptions(mechanism="parametric"),
    outcome=OutcomeOptions(family="gaussian"))
observed, truth = task.sample(4, data_seed=7)
capo = task.truth_at(observed["X"])  # direct network means; no posterior integration
assert set(capo) == {"mu0", "mu1"}
latent = task.truth_at(observed["X"], u=truth["U"], data_seed=7)
print(capo["mu0"].shape, latent["mu0_xu"].shape)  # (4,) (4,)

The base outcome networks specify μa(X) directly: identity for Gaussian-family continuous outcomes, sigmoid for Bernoulli outcomes, and softplus for Poisson outcomes. The same shared scalar loading ℓY is reused for both arms; this class does not introduce arbitrary arm-specific loading functions. Let g be the configured ConfounderOptions.function, and define h(U)=tanh(g(U)), c(X)=E[h(U) | X], and r=(h(U)-c(X))/2. Thus |r|≤1 and E[r | X]=0 under exact centering. Given X,U, the outcome means are:

Family Conditional mean given X,U
Gaussian-family μa(X)+ℓY r
Bernoulli μa(X)+tanh(ℓY)min(μa(X),1-μa(X))r
Poisson μa(X)(1+tanh(ℓY)r)

Averaging these means over U | X recovers the direct target μa(X). The mean link is applied before the correction. No subsequent link or probability clipping changes that identity. Bernoulli targets zero/one and Poisson target zero remain valid. Gaussian-family noise, including its existing heteroscedastic scale, and binary/count inverse-CDF noise replay are retained. The sampler retains the existing machine-level inverse-CDF tail approximation; these statements do not certify exact floating-point simulation.

truth_at(X) returns only the two direct targets and is inexpensive. sample() returns observed X/A/Y plus array-valued truth: mu0, mu1, mu0_xu, mu1_xu, e_xu, U, Y0, Y1, and numerical centering-error estimates. sample_episode() keeps the existing context/query separation. Explicit truth_at(X, u=...) additionally returns numerical diagnostics. mu*_xu conditions on U; it is distinct from observed-X mu*.

Sampling and explicit-U truth compute c(X) using the same built-in conditional likelihoods as CSCM, once per distinct X and shared across arms. Configure centering_options={"atol": 1e-6, "rtol": 1e-5, "max_subdivisions": 200, "cdf_maxpts": 20000} on construction when needed. A one-batch cache reuses repeated queries and is invalidated when these options change. Other task mechanisms/loadings are fixed task configuration and must not be mutated after construction. Custom mechanisms without built-in likelihoods are unsupported.

Exact conditional centering defines the mathematical generator; the numerical sampler approximates it. If the centering error is δc, the arm-mean error is |sa(X)| |δc|/2, where sa is the residual coefficient in the table. Returned centering_error_estimate and mu0_centering_error_estimate/mu1_centering_error_estimate propagate quadrature and rectangle-refinement diagnostics by that factor. They are not certified error bounds (numerical_errors_certified=False). Unresolved integration raises an error instead of returning a fabricated mean. Mixed covariates with many correlated discrete intervals can make generation expensive or fail a numerical tolerance check; try a small batch before a large training run. A local normal-latent Gaussian-outcome smoke (task seed 42, data seed 7) took about 3.08 seconds to generate 1,000 parametric rows with two X columns, versus 49.73 seconds for only two mixed rows with 20 columns. Direct truth lookup took about 0.0018 and 0.00022 seconds respectively. These single-run observations are not throughput guarantees: high-dimensional mixed generation remains a major limitation for large training sets. Direct target lookup remains inexpensive. This class does not preserve marginal treatment probability across confounding powers.

CSCM controls and replay

ConfounderOptions supports normal, uniform, Laplace, Student-t, Bernoulli or random U; linear, tanh, signed-log or random latent effects; positive loading ranges; and the applicable tail/probability controls. Choices are frozen per task and are metadata, not editable configuration. U still affects X and Y when assignment is unconfounded. truth_at(X, u=..., data_seed=...) replays individual outcome noise; omit replay options when asking for an observed-X mean.

unconfounded=True removes the direct U-to-A contribution and retains the base assignment conditional on measured X. It is not unconditional randomization. confounding_power=0 has the same assignment effect; signed nonzero values scale only the U-to-A loading, leaving X and potential outcomes fixed for the same seeds. This does not calibrate marginal treatment probability or guarantee that larger magnitude monotonically worsens every finite-data estimator.

The retained power diagnostic compares observed differences, oracle ATE and overlap summaries without treating them as learned causal effects. Generic mechanism edits return plain SCM copies; analytic preset truth does not transfer to arbitrary replacement mechanisms.

HTML graph tool

The HTML tool and its documentation use English as the default language.

The HTML API was introduced in 0.1.0a10 and is included in the current 0.1.0a13 source pin and regular wheel. A terminal user can keep it running with:

python -m causalscience.graph_tool

This opens a local HTML editor in the user’s browser. Keep the process running; Ctrl-C closes its server. --no-browser prints the URL without opening a browser. In a notebook or an interactive Python session, pass the same graph and query used for identification:

from causalscience import BDSCM, CovariateOptions, show_graph_tool

model = BDSCM(d_X=3, task_seed=42,
    covariates=CovariateOptions(mechanism="mixed"))
tool = show_graph_tool(model.graph, X=["A"], Y=["Y"])
print(tool.url)
# Keep this session and handle alive while editing. When finished:
# tool.close()

The tool edits nodes/edges, observed/hidden roles, vector dimensions and the action/outcome query, then calls the installed identify_query. It displays the actual status, selected method, and formula; invalid requests appear as errors. It accepts at most 50 string-named nodes, with dimensions from 1 through 8. It reuses the experimental lab’s SVG editor design for graph/query identification. CSV, SCM generation, estimation, and benchmark tabs from the full website are outside this smaller tool. It has no Pyodide/CDN runtime dependency.

The server binds only to 127.0.0.1. The returned handle has .url, .close(), and context-manager support. A one-line call in a script that immediately exits cannot keep the daemon server alive; use the blocking CLI for terminal use. See graph_tool.py and its asset provenance.

Assumptions and API contracts

Identification, statistical execution and causal inference are separate gates. A graph-free call records a user assumption; a graph witness establishes only what its supported criterion proves. Neither finite samples nor fitted weights verify iid sampling, causal consistency/exchangeability, overlap or nuisance learning rates. Schema/domain declarations are population assumptions; observed unique values or min/max are not population-support certificates.

Public inputs and outputs

The supported exports are declared in causalengine.__all__.

Entry point Input Output / main boundary
StructuralCausalModel Registered variable functions, parents, noise, and observed/vector metadata Fixed model; observational/interventional DataFrames; copy-returning mechanism replacement. See SCM.py.
random_scm, random_cluster_scm Graph sizes, model seed, family choices, search budgets (model, X, Y); a random query still requires status inspection. See random_generator.py.
BDSCM, StructuralCausalModel.backdoor Task seed and covariate/treatment/outcome option objects Back-door task facade, observed samples, separate truth, episodes, shared task.scm.
CSCM, StructuralCausalModel.confounded BDSCM controls plus ConfounderOptions, confounding_power, unconfounded Hidden-common-cause task facade; observed data exclude U.
CSCM_capo CSCM options plus numerical centering controls Direct observed-X mu0/mu1; sampling preserves X/A but changes the outcome parameterization.
estimate_bd, estimate_sbd, estimate_fd, estimate_uca DataFrame, schema, fixed actions and a caller-supplied template/expression/program One interventional mean; user-assumed identification, not graph verification.
estimate_ate, estimate_cate Two regimes, shared source/evaluation units and a supported baseline anchor for CATE Paired marginal contrast / prediction model; no automatic causal interval.
identify_query networkx.DiGraph, action-name argument X, outcome-name argument Y, criterion/search settings Dictionary with status, method, query, attempted, witness, expression, compiler; no numeric estimate.
fit_query_mean Graph/query, observed DataFrame, every original whole-node action value, backend settings Identification fields; on successful execution, estimation_status and an estimate array; backend-specific diagnostics may also be present.

witness is graph information checked by a criterion. expression is the structured observational formula; compiler executes/compiles that representation. These returned Python objects are experimental, not a stable JSON storage format. fit_query_mean runs identification itself; it does not consume a previously returned identified dictionary. Outcome array width follows the declared outcome columns. The query’s preprocessing record preserves original/pruned roles. In the preset example, the treatment variable is named A, so the API argument X receives ["A"]; the preset covariate node named X is not the action.

Result field / value How to use it
status == 'ACCEPT' A producer returned a verified identifying expression. Check estimation separately.
status == 'UNKNOWN' The configured routes and limits produced no definitive answer; this includes unfinished searches and combinations with unsupported routes. It is not a negative proof.
status == 'NO_BD', NO_FD, or another NO_* The selected producer did not supply its criterion; another method may succeed.
status == 'NOT_IDENTIFIABLE' The indexed route reports non-identification within its supported graph scope.
status == 'UNSUPPORTED' The identification route cannot handle the requested input/domain.
estimation_status == 'UNSUPPORTED' The expression was accepted but this numerical backend cannot execute it; inspect reason when provided.
Missing estimation_status Identification may have failed before numerical fitting; inspect status first.

Malformed arguments can raise ValueError, and some unsupported domains raise NotImplementedError; callers must handle exceptions as well as result fields. identify_query(method='auto') searches the fixed METHODS order; the current default shared deadline is 3 seconds. max_seconds=None removes that time limit, not necessarily every other search limit. A criterion selector is a software interface, not a claim that its name is the current paper’s theorem name. The Wiki method/backend map locates the individual routes and their constraints.

Default weighting

In the current 0.1.0a13 implementation, the estimator default is backend='score' with XGBoost backward conditional-mean regressions. Weight fitting remains the normalized minimum-L2 procedure below; it is not replaced by XGBoost. Explicit backend='native' retains native plug-in means.

For a stage’s design histories hj, let wj be the weight at hj and qj their source probabilities, φ(hj) the declared feature vector, and F the matrix with rows φ(hj)ᵀ. Its first feature is the constant one. Let b denote the target feature moments, with b0=1. The default weight fit solves

minw∑jqjwj2subject to∑jqjwj=1,F⊤diag(q)w=b.

For a Riesz design with n sampled histories, qj=1/n. The current feature class is retained; the public affine score model keeps its degree-one features and full-rank inference guard. Nonconstant target moments follow the existing forward recursion; the known constant target is fixed to one rather than propagating empirical mass drift. The solver supports compatible redundant constraints, allows signed weights, and explicitly fails for infeasible or numerically unverified balance. It does not substitute a ridge approximation to constraints. ridge still controls backward regressions where previously supported.

For finite execution, the source law retains its common-joint histogram, prior mass, and carrier probabilities. Full-indicator balance has the analytic minimum-L2 solution: target-prefix mass divided by source-design mass. Those state probabilities are generally nonuniform. Positive prior gives full finite source coverage; zero-prior fits require a bounded target-support check and fail if support is missing or cannot be certified within the budget.

Exact balance/normalization refers to the fitted design and chosen feature class. It does not certify all population test functions or force heldout sample weights to average to one. Heldout weights are not renormalized. Inspect score_results[*].diagnostics['weight_balancing'] for the solver, normalization, balance and finite-support certificates. Implementation: weighting.py, uca_estimation.fit_weights.

Learners and inference

XGBoost squared-error regressions are the backward learner, including finite weight designs. Use params/num_boost_round or an explicit regression_factory. Signed balancing weights enter the score, not XGBoost sample_weight. Explicit finite/affine protocols retain their support, feature closure, rank and nuisance-rate assumptions. A successful finite-feature fit is not an automatic population-consistency or causal-coverage certificate.

Implementation map

The package has 23 top-level Python modules plus seven in bdscm/. Keep this single canonical layout; use the following logical reading groups rather than changing import locations again. Source, regular wheel and editable mode use the same Python bytes.

Work on Canonical source
Public graph-free means/effects estimators.py, effects.py, __init__.py
Graph dispatch and typed expressions identification_engine.py, indexed_identification.py
Graph criteria adjustment.py, SBD.py, frontdoor.py, identify.py, tian.py
Score execution and nuisance fits uca_compiler.py, uca_estimation.py, weighting.py, statmodules.py
Graphs and simulation SCM.py, CSCM.py, bdscm/, random_generator.py, scm_registry.py
Graph primitives/fixtures graph.py, indexed_graph.py, examples.py
Browser/CLI entry points graph_tool.py, demo.py

The Wiki has detailed routes; the indexed compiler reference keeps finite symbolic and trace interfaces separate from the public statistical workflow. experimental/backdoor_scm/ is a separate research component, outside the wheel. archived/ and historical notebooks are preserved reference material.

Source development and compatibility

This section applies to the CausalEngine source checkout, not the PyPI install. Use python -m pip install ., or editable python -m pip install --no-deps --no-build-isolation -e . after dependencies are installed. Use causalengine.* imports and python -m causalengine.demo, or PYTHONPATH=src for source-only checks. The public examples above instead use causalscience.*. Old flat imports such as import SCM are retired, without root shims. External research adapters consuming this checkout should import from causalengine import indexed_identification as source and use source.__file__ for provenance. Frozen paper copies remain unchanged.

StructuralCausalModel(..., equation_families=..., param_seeds=..., param_priors={"linear": {"coef_range": (-1, 1)}}) keeps per-model equation priors. Overrides are validated on the model, not written into global registries. See scm_registry.py for supported prior fields.

Limitations

  • Default point scores require declared discrete actions; continuous fixed-point actions are unsupported under the current dominated-source design.
  • Compiler/design scope, finite moments and feasible numerical balance are separate from graph identification. Unfinished searches retain UNKNOWN.
  • conditional_score_interval describes a frozen fitted score on evaluation data. No default automatic causal interval or pointwise CATE interval is returned.
  • General UCA CATE requires the supported baseline anchor and conditional calibration plus second-stage learning assumptions; arbitrary score grouping is not a conditional identification argument.
  • CSCM truth may need posterior integration. CSCM_capo direct truth is fast, but generation can be costly with high-dimensional mixed X; numerical error diagnostics are not certified bounds. Generator truth is not observed-data input.
  • Historical indexed finite/trace uncertainty covers its declared Monte Carlo calculation, not automatically regression error or causal uncertainty.
  • This is an alpha research package; returned compiler objects/private module interfaces are not stable serialization or storage contracts.

Validation and provenance

The October 27 layout check ran 226 source tests and 79 installed tests (67 unittest cases plus 12 BDSCM function checks), all passing. Source and installed public demo results matched, ten demo/CLI checks passed, and editable import was verified. All 17 consolidated modules matched the preceding installed Python bytes; the 13 unmoved runtime modules stayed unchanged. The separate experimental component passed 84/88 tests, with the same four failures reproduced before the move. These are bounded implementation checks, not statistical guarantees.

For reproducible source checks, run PYTHONPATH=src python -B -m unittest discover -s tests/checkout -v from the repository root. Run installed contracts in a separate process using an absolute tests path; execute tests/test_bdscm.py separately for its function-based checks. Reinstall regular consumers after an update; editable environments read their configured checkout.

ENGINE_SOURCES.json records current canonical hashes and prior snapshots; ENGINE_LICENSE.txt, LICENSING.md and LICENSE state scope and rights. The whole-call API pilot times four complete mean API calls; the component pilot profiles shared-fit phases. Do not compare them as the same runtime experiment. Original raw results and source hashes are retained.

Historical validation records preserve a9/a10/a11/a13 checks with their original dates/pins and scope. They are not current totals.

Earlier guide section links [current-a13-validation](/docs/causalengine/validation-history/#published-a13-validation-october-26-2026-bd14e4dc) [historical-documentation-validation-a9](/docs/causalengine/validation-history/#historical-documentation-validation-a9) [candidate-010a10-verification](/docs/causalengine/validation-history/#candidate-010a10-verification) [local-checkout-organization-october-27-2026](#source-development-and-compatibility) [candidate-010a11-verification-october-26-2026](/docs/causalengine/validation-history/#candidate-010a11-verification-october-26-2026) [uca-component-performance-pilot](/docs/causalengine/uca-benchmark/) [graph-free-mean-estimators-introduced-in-a12](/docs/causalengine/graphless-estimators/) [effects-and-whole-call-pilot-a13](/docs/causalengine/estimator-benchmark/)