Reference
Every public symbol
843 of them, across
18 subpackages. The list is generated from each subpackage’s
__all__ at build time, so it cannot drift from the code. Gate 12 fails the
build if any symbol here lacks an executed notebook that uses it.
Click a symbol for its signature, its one-line
docstring, and a real call — the shortest statement in the notebook
series that actually uses it, lifted out of the executed .ipynb at build
time. Nothing on this page is a hand-written example, which is why none of it can
quietly stop compiling. 838 of the 843 have such a
line; the 5 that do not say so where they sit.
Specs, dimensions, intervals, expressions — the vocabulary everything else is written in.
AcceptanceRegionspec
AcceptanceRegion(n: int, p: float, alpha: float, lower: int, upper: int)
An exact binomial acceptance region for a rate criterion.
region: AcceptanceRegion = clopper_pearson(n=500, p=0.05, alpha=0.001)
Addspec
Add(node: Literal['add'] = 'add', terms: tuple[Const | Data | Param | Add | Mul | Div | Pow | Apply | Convolve | Reduce | Gather | Link | Opaque, ...])
Sum of terms sharing one dimension.
response = Equation(lhs=y, rhs=Add(terms=(Param(name="a", dimension=D.outcome), hill)), name="response")
Applyspec
Apply(node: Literal['apply'] = 'apply', fn: Literal['exp', 'log', 'log1p', 'expm1', 'tanh', 'sigmoid', 'logit', 'softplus', 'neg', 'relu', 'step', 'sin', 'cos'], arg: Const | Data | Param | Add | Mul | Div | Pow | Apply | Convolve | Reduce | Gather | Link | Opaque)
A scalar function of one argument. Argument and result are dimensionless.
print(fn, value(Apply(fn=fn, arg=Const(value=0.0, dimension=dimensionless()))))
ApplyFncallable
ApplyFn(*args, **kwargs)
fns: list[ApplyFn] = ["exp", "sigmoid", "softplus"]
Assumptionspec
Assumption(name: str, facet: str, statement: str, challenged_by: str = '', state: Literal['unverified', 'satisfied', 'violated', 'asserted'] = 'unverified', detail: dict[str, str] = {})
A named, falsifiable condition that licenses a step.
overlap = Assumption(
name="overlap",
facet="population",
statement="every target stratum has positive support in the source",
challenged_by="propensity / dose overlap diagnostic",
)
BASESvalue
The declarable set of base dimensions.
print(list(BASES))
BaseRegistryclass
BaseRegistry(defaults: tuple[tuple[str, str], ...] = ...) -> None
The declarable set of base dimensions.
private_bases = BaseRegistry()
Blockedspec
Blocked(reason: str, detail: dict[str, str] = {}, status: Literal['blocked'] = 'blocked')
The request is not licensed: no route, no transfer, no identification.
Blocked(reason=" ")
Capabilityclass
Capability(*values)
What a producer can do. A request needing an absent one degrades, typed.
return frozenset({Capability.COUNTERFACTUAL})
Constspec
Const(node: Literal['const'] = 'const', value: float | tuple[float, ...], dimension: Dimension)
A literal with a declared dimension: a scalar, or a short vector (a lag index, knots).
one = Const(value=1.0, dimension=dimensionless())
Constraintspec
Constraint(name: str, expr: Const | Data | Param | Add | Mul | Div | Pow | Apply | Convolve | Reduce | Gather | Link | Opaque, family: Literal['normal', 'lognormal'], observed: float, scale: float, detail: dict[str, str] = {})
A soft constraint on a scalar function of the parameters: ``observed ~ p(expr(θ), scale)``.
lift = Constraint(
name="mean_lift@experiment_1",
expr=Reduce(op="mean", arg=hill),
family="normal",
observed=6.0,
scale=0.5,
detail={"measurement": "experiment_1", "doses": "observed"},
)
Convolvespec
Convolve(node: Literal['convolve'] = 'convolve', signal: Const | Data | Param | Add | Mul | Div | Pow | Apply | Convolve | Reduce | Gather | Link | Opaque, kernel: Const | Data | Param | Add | Mul | Div | Pow | Apply | Convolve | Reduce | Gather | Link | Opaque)
Causal convolution of ``signal`` with ``kernel`` along the time axis.
carry = Convolve(signal=dose, kernel=weights)
Covariatespec
Covariate(name: str, dimension: Dimension | None = None, unit: str | None = None, description: str = '')
Measured, not intervened on.
rainfall = Covariate(name="rainfall", dimension=dimensionless(), description="standardized")
Dvalue
The declarable set of base dimensions.
print(D.currency, D.time, D.outcome, D.entity)
DIGITSvalue
int([x]) -> integer int(x, base=10) -> integer
print("digits kept of the uncertainty:", DIGITS)
Dataspec
Data(node: Literal['data'] = 'data', name: str, dimension: Dimension)
A column of the panel. Its dimension is the one its role declares.
dose = Data(name="dose", dimension=D.currency)
DesignMatrixclass
DesignMatrix(X: NDArray[float64], columns: tuple[str, ...], offset: NDArray[float64], at: Mapping[str, NDArray[float64]]) -> None
``mu = offset + X @ theta[columns]`` at a fixed point of the nonlinear parameters.
dm = DesignMatrix(X=X, columns=("a", "b"), offset=np.zeros(4), at={"k": np.array(50.0)})
Dimensionspec
Dimension(exponents: dict[str, Fraction])
A product of declared base dimensions raised to rational exponents.
Dimension(exponents={"temperature": 1})
DimensionErrorerror
Two quantities of different dimension were combined.
try:
D.currency.require_equal(D.time, context="Add(term_1, term_2)")
except DimensionError as e:
print(e)
Divspec
Div(node: Literal['div'] = 'div', numerator: Const | Data | Param | Add | Mul | Div | Pow | Apply | Convolve | Reduce | Gather | Link | Opaque, denominator: Const | Data | Param | Add | Mul | Div | Pow | Apply | Convolve | Reduce | Gather | Link | Opaque)
Quotient; exponents subtract.
x = Div(numerator=dose, denominator=k)
Dosespec
Dose(name: str, dimension: Dimension | None = None, unit: str | None = None, description: str = '', numeraire: str | None = None)
The magnitude of an intervention on a treatment, with units and a numeraire.
dose = Dose(name="fertilizer_dose", dimension=D.currency, unit="USD", numeraire="USD")
Entityclass
Entity(*args, **kwargs)
What every entity spec provides: a name, an optional dimension and unit.
print(isinstance(dose, Entity), Treatment.__mro__[1].__name__)
EntityNamecallable
EntityName(*args, **kwargs)
Runtime representation of an annotated type.
name: EntityName = "tv-ads.v2"
Equationspec
Equation(node: Literal['equation'] = 'equation', lhs: Const | Data | Param | Add | Mul | Div | Pow | Apply | Convolve | Reduce | Gather | Link | Opaque, rhs: Const | Data | Param | Add | Mul | Div | Pow | Apply | Convolve | Reduce | Gather | Link | Opaque, name: str = '')
``lhs = rhs``; both sides must share a dimension.
response = Equation(lhs=y, rhs=Add(terms=(Param(name="a", dimension=D.outcome), hill)), name="response")
Exprcallable
Expr(*args, **kwargs)
Runtime representation of an annotated type.
hill: Expr = Mul(factors=(beta, Div(numerator=Pow(base=x, exponent=s),
denominator=Add(terms=(one, Pow(base=x, exponent=s))))))
Failurevalue
Represent a PEP 604 union type
def realize(value_ok: bool) -> float | Failure:
if not value_ok:
return Blocked(reason="adjustment set contains an unmeasured variable", detail={"node": "U"})
return 0.42
Gatherspec
Gather(node: Literal['gather'] = 'gather', source: Const | Data | Param | Add | Mul | Div | Pow | Apply | Convolve | Reduce | Gather | Link | Opaque, index: Data)
``source[..., index]``: pick entries of a vector-valued expression by an integer column.
mean = Add(terms=(Gather(source=alpha, index=unit), hill))
Intervalspec
Interval(lower: float, upper: float, definition: Literal['eti', 'hdi', 'wald'], mass: float)
``[lower, upper]`` with the definition and the mass it was computed at.
Interval(lower=0.0, upper=1.0)
Interventionspec
Intervention(doses: dict[str, float], mode: Literal['set', 'scale', 'shift'] = 'set', version: str = 'unspecified', window: TimeWindow | None = None)
What is set, to what value, how, over what support.
iv = Intervention(doses={"fertilizer": 100.0, "irrigation": 20.0}, version="granular-v2")
LedgerLinespec
LedgerLine(kind: str, statement: str, assumption: Assumption | None = None, detail: dict[str, str] = {}, source: str = '', target: str = '')
One entry in the assumption ledger.
line = LedgerLine(
kind="facet:population",
statement="transferred from region=north to region=all under overlap",
assumption=overlap.asserted(),
detail={"from": "north", "to": "all"},
)
Likelihoodspec
Likelihood(family: Literal['normal', 'lognormal', 'student_t', 'poisson'], scale: str | None = None, scale_expr: Optional[Const | Data | Param | Add | Mul | Div | Pow | Apply | Convolve | Reduce | Gather | Link | Opaque] = None, df: float | None = None)
How the outcome column is distributed around the mean expression.
Likelihood(family="normal", scale="sigma", scale_expr=se_col)
LikelihoodFamilycallable
LikelihoodFamily(*args, **kwargs)
family: LikelihoodFamily = "normal"
Linkspec
Link(node: Literal['link'] = 'link', fn: Literal['identity', 'log', 'logit'], arg: Const | Data | Param | Add | Mul | Div | Pow | Apply | Convolve | Reduce | Gather | Link | Opaque)
A GLM link applied to a dimensionless argument (after a reference divide).
print(dimension(Link(fn=link, arg=Div(numerator=y, denominator=y_ref))))
LinkFncallable
LinkFn(*args, **kwargs)
link: LinkFn = "log"
Modelcallable
Model(*args, **kwargs)
bad_trees: list[Model] = [
Apply(fn="log", arg=dose),
Add(terms=(beta, Mul(factors=(beta, Apply(fn="log", arg=dose))))),
Pow(base=dose, exponent=s),
Convolve(signal=dose, kernel=k),
]
ModelSpecspec
ModelSpec(name: str, mean: Const | Data | Param | Add | Mul | Div | Pow | Apply | Convolve | Reduce | Gather | Link | Opaque, outcome: Data, likelihood: Likelihood, parameters: tuple[Param, ...], constraints: tuple[Constraint, ...] = ())
Mean expression + outcome column + likelihood + every parameter's prior.
model = ModelSpec(
name="panel_hill",
mean=mean,
outcome=y,
likelihood=Likelihood(family=family, scale="sigma"),
parameters=(k, s, beta, lam, a_mean, a_sd, alpha, sigma),
)
Mulspec
Mul(node: Literal['mul'] = 'mul', factors: tuple[Const | Data | Param | Add | Mul | Div | Pow | Apply | Convolve | Reduce | Gather | Link | Opaque, ...])
Product; exponents add.
print(dimension(Pow(base=Mul(factors=(dose, dose)), exponent=Fraction(1, 2))))
NonEmptyStrcallable
NonEmptyStr(*args, **kwargs)
Runtime representation of an annotated type.
reason: NonEmptyStr = "a reason that is not blank"
ODESystemspec
ODESystem(node: Literal['ode'] = 'ode', states: tuple[Data, ...], rhs: tuple[Const | Data | Param | Add | Mul | Div | Pow | Apply | Convolve | Reduce | Gather | Link | Opaque, ...], time: Data)
``d(state_i)/dt = rhs_i`` for each state. Dimension-check-only in 1.0 (C1).
ode = ODESystem(states=(S,), rhs=(Mul(factors=(r, S)),), time=t)
Opaquespec
Opaque(node: Literal['opaque'] = 'opaque', name: str, inputs: tuple[Const | Data | Param | Add | Mul | Div | Pow | Apply | Convolve | Reduce | Gather | Link | Opaque, ...] = (), dimension: Dimension)
A user-supplied function the tree cannot express.
weights = Opaque(name="geometric", inputs=(lam,), dimension=dimensionless())
OpaqueFncallable
OpaqueFn(*args, **kwargs)
geometric: OpaqueFn = lambda lam, L=6: lam ** np.arange(L)
OpaqueRegistrycallable
OpaqueRegistry(*args, **kwargs)
A Mapping is a generic container for associating key/value pairs.
registry: OpaqueRegistry = {"geometric": geometric}
Outcomespec
Outcome(name: str, dimension: Dimension | None = None, unit: str | None = None, description: str = '', aggregation: Literal['sum', 'mean', 'rate'] = 'sum')
What you are trying to move.
yield_total = Outcome(name="yield_total", dimension=D.outcome, unit="kg", aggregation="sum")
PRIOR_HYPERvalue
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
print(PRIOR_HYPER)
Paramspec
Param(node: Literal['param'] = 'param', name: str, dimension: Dimension, prior: Prior | None = None, shape: tuple[int, ...] = ())
A parameter to be inferred, with a declared dimension and an optional prior.
k = Param(name="k", dimension=D.currency, prior=Prior(family="lognormal", hyper={"mu": 3.0, "sigma": 1.0}))
Populationspec
Population(name: str, description: str = '', strata: dict[str, dict[str, float]] = {})
A target population: a name and, where known, the strata weights that define it.
north = Population(name="north", strata={"soil": {"clay": 0.3, "loam": 0.7}})
Posteriorclass
Posterior(draws: Mapping[str, ArrayLike], *, coords: Mapping[str, Sequence[Any]] | None = None, provenance: Mapping[str, Any] | None = None) -> None
Draws keyed by name, each ``(chain, draw, *shape)``, plus coords and provenance.
post = Posterior(
{
"beta": rng.normal(0.3, 0.1, size=(4, 500, 2)),
"sigma": rng.lognormal(-1, 0.2, size=(4, 500)),
},
coords={"treatment": ["fertilizer", "irrigation"]},
provenance={"seed": 42, "backend": "hand-built"},
)
Powspec
Pow(node: Literal['pow'] = 'pow', base: Const | Data | Param | Add | Mul | Div | Pow | Apply | Convolve | Reduce | Gather | Link | Opaque, exponent: Union[Const | Data | Param | Add | Mul | Div | Pow | Apply | Convolve | Reduce | Gather | Link | Opaque, Fraction])
``base ** exponent``.
print(dimension(Pow(base=Mul(factors=(dose, dose)), exponent=Fraction(1, 2))))
PredictiveDrawsclass
PredictiveDraws(values: NDArray[float64], intervention: Intervention, window: TimeWindow | None = None, coords: Mapping[str, Sequence[Any]] = <factory>, seed: int | None = None) -> None
Outcome draws under an intervention: ``values`` is ``(chain, draw, *shape)``.
return PredictiveDraws(values=beta @ dose, intervention=iv, window=window, seed=seed)
Priorspec
Prior(family: Literal['normal', 'halfnormal', 'lognormal', 'beta', 'gamma', 'uniform', 'fixed'], hyper: dict[str, float | str])
A named prior family with hyperparameters.
k = Param(name="k", dimension=D.currency, prior=Prior(family="lognormal", hyper={"mu": 3.0, "sigma": 1.0}))
PriorFamilycallable
PriorFamily(*args, **kwargs)
fam: PriorFamily = "halfnormal"
Reducespec
Reduce(node: Literal['reduce'] = 'reduce', op: Literal['sum', 'mean', 'max'], arg: Const | Data | Param | Add | Mul | Div | Pow | Apply | Convolve | Reduce | Gather | Link | Opaque, keepdims: bool = False)
Reduce ``arg`` along its last axis: ``sum``, ``mean``, ``max``. Dimension is preserved.
weights = Div(numerator=raw, denominator=Reduce(op="sum", arg=raw))
SchemaVersionErrorerror
SchemaVersionError(spec: str, found: str, expected: str) -> None
A serialized spec's version has no migration to the current class version.
try:
ProbeDesignV2.from_json(json.dumps(v1_payload))
except SchemaVersionError as e:
print("no migration:", e)
Specspec
Spec()
Frozen, versioned, hashable configuration object. Subclass freely.
Spec.from_json('{"not": "an envelope"}')
SpecDiffspec
SpecDiff(spec: str, changed: dict[str, tuple[Any, Any]])
Field-level differences between two specs of the same type.
d: SpecDiff = design.diff(
design.model_copy(update={"levels": (0.0, 50.0, 150.0), "window": TimeWindow(start=0, stop=12)})
)
SpecErrorerror
Base class for spec construction and (de)serialization failures.
try:
Spec.from_json('{"not": "an envelope"}')
except SpecError as e:
print("bad envelope refused:", e)
Summaryspec
Summary(mean: float, median: float, sd: float, interval: Interval, n: int)
Point summaries of a set of draws plus one provenance-carrying interval.
s: Summary = summarize(draws, definition="hdi", mass=0.95)
SupportsEstimandsclass
SupportsEstimands(*args, **kwargs)
A fitted producer: posterior draws + counterfactual predictions + declared estimands.
print([m for m in dir(SupportsEstimands) if not m.startswith("_")])
SupportsForwardclass
SupportsForward(*args, **kwargs)
A deterministic response surface: ``forward`` == ``interpret.value(expr)``.
print(isinstance(surface, SupportsForward), surface.forward({"dose": d}, {"k": 50.0, "s": 2.0, "beta": 10.0}))
SupportsInterventionclass
SupportsIntervention(*args, **kwargs)
Anything you can ask a counterfactual of.
print(isinstance(surface, SupportsIntervention))
SupportsPosteriorclass
SupportsPosterior(*args, **kwargs)
Anything with draws you can ask questions of.
print(isinstance(post, SupportsPosterior), post.names(), post.n_chains, post.n_draws())
Systemspec
System(node: Literal['system'] = 'system', equations: tuple[Equation, ...])
A set of equations, each checked independently.
model = System(equations=(response, cost))
TimeWindowspec
TimeWindow(start: int, stop: int, basis: Literal['per_period', 'cumulative'] = 'cumulative')
A half-open index window ``[start, stop)`` on the panel's time axis, with a basis.
d: SpecDiff = design.diff(
design.model_copy(update={"levels": (0.0, 50.0, 150.0), "window": TimeWindow(start=0, stop=12)})
)
Treatmentspec
Treatment(name: str, dimension: Dimension | None = None, unit: str | None = None, description: str = '')
The thing you can intervene on. Its dimension is the dose's dimension.
design = ProbeDesign(
treatment=Treatment(name="fertilizer", dimension=D.currency, unit="USD"),
levels=(0.0, 50.0, 100.0),
window=TimeWindow(start=0, stop=8),
)
UNITSvalue
Units of measure, each attached to a base dimension, with conversions.
UNITS.declare("USD", "currency")
UndeclaredBaseErrorerror
A dimension named a base that the registry has not declared.
try:
Dimension(exponents={"temperature": 1})
except UndeclaredBaseError as e:
print(type(e).__name__, "->", e)
UndimensionedWarningerror
An entity was declared without a dimension and is treated as dimensionless (D6).
print(loose.dim, "|", [w.category.__name__ for w in caught], "|", issubclass(caught[0].category, UndimensionedWarning))
Unitspec
Unit(name: str, dimension: Dimension | None = None, unit: str | None = None, description: str = '', kind: Literal['individual', 'cluster', 'aggregate'] = 'individual')
The randomization / observation unit: a plot, a patient, a region.
plot = Unit(name="plot", dimension=D.entity, kind="cluster")
UnitConversionErrorerror
No conversion is registered between two units of the same dimension.
try:
UNITS.convert(1.0, src, dst)
except (DimensionError, UnitConversionError) as e:
print(f"{src} -> {dst}: {type(e).__name__}: {e}")
UnitSystemclass
UnitSystem() -> None
Units of measure, each attached to a base dimension, with conversions.
private_units = UnitSystem()
UnknownSpecErrorerror
The envelope names a class that cannot be imported or is not a Spec.
try:
TimeWindow.from_json(design.to_json())
except UnknownSpecError as e:
print("wrong class refused:", e)
Unsupportedspec
Unsupported(reason: str, detail: dict[str, str] = {}, missing: tuple[str, ...] = (), status: Literal['unsupported'] = 'unsupported')
The producer lacks a capability the request needs.
result = Unsupported(reason=f"surface lacks {', '.join(missing)}", missing=missing)
Unverifiedspec
Unverified(reason: str, detail: dict[str, str] = {}, status: Literal['unverified'] = 'unverified')
An assumption the machinery cannot check; the caller decides.
print(bool(Unsupported(reason="no marginal capability", missing=("marginal",))), bool(Unverified(reason="overlap not checked")))
Verdictspec
Verdict(status: Literal['identified', 'downgraded', 'blocked', 'unsupported', 'unverified'], reason: str = '', assumptions: tuple[Assumption, ...] = (), route: str = '')
An honest answer about whether a quantity is licensed, and under what.
v = Verdict(status="downgraded", route="backdoor", assumptions=(overlap,))
causal_convolvefn
causal_convolve(signal: Array, weights: Array) -> Array
``y[..., t] = Σ_l w[..., l] · x[..., t-l]`` with zero history before ``t=0``.
print(causal_convolve(np.ones(4), np.array([0.5, 0.25])))
checkfn
check(model: Model, expected: Dimension | None = None) -> Dimension
``dimension`` plus an optional assertion against a declared dimension.
print(check(hill, D.outcome))
childrenfn
children(node: Model) -> tuple[Model, ...]
Direct sub-nodes, in a stable order.
print(children(hill)[0], "|", node_path(hill, s))
clopper_pearsonfn
clopper_pearson(n: int, p: float, alpha: float = 0.001) -> AcceptanceRegion
Exact two-sided binomial acceptance region for ``k ~ Binomial(n, p)``.
region: AcceptanceRegion = clopper_pearson(n=500, p=0.05, alpha=0.001)
compile_jaxfn
compile_jax(model: Model, *, opaque: JaxOpaqueRegistry | None = None) -> JaxFn
Build a traceable ``f(data, params)`` for ``model``.
f_mean = compile_jax(mean)
compile_log_densityfn
compile_log_density(model: ModelSpec, *, opaque: JaxOpaqueRegistry | None = None) -> Callable[[Mapping[str, Any], Mapping[str, Any]], Any]
``f(data, z) -> log posterior`` in unconstrained coordinates, traceable by jax.
f_ld = compile_log_density(model)
constrainfn
constrain(model: ModelSpec, z: Mapping[str, ArrayLike]) -> tuple[dict[str, Array], float]
Inverse of ``unconstrain``; also returns the summed log-Jacobian ``log|dθ/dz|``.
back, log_jac = constrain(model, z)
data_namesfn
data_names(node: Model) -> tuple[str, ...]
Every distinct ``Data`` name in the tree, by first appearance.
print([p.name for p in params(model)], data_names(model))
decimals_forfn
decimals_for(uncertainty: float, *, digits: int = 2) -> int
Decimal places warranted by ``uncertainty``, for ``round`` and ``format``.
print(f"12.3456789 ± {u:<6} -> {format_measured(12.3456789, u):>8} "
f"decimals={decimals_for(u)}")
dimensionfn
dimension(model: Model, path: str = '') -> Dimension
The dimension of ``model``; raises ``DimensionError`` naming the failing node.
print(dimension(hill))
dimension_offn
dimension_of(entity: Entity) -> Dimension
The entity's dimension; dimensionless when none was declared (D6).
print(UNITS.factor("USD", "GBP"), "|", UNITS.dimension_of("week"))
dimensionlessfn
dimensionless() -> Dimension
print("x / x is dimensionless:", (rate / rate) == dimensionless(), (rate / rate).is_dimensionless)
effective_sample_sizefn
effective_sample_size(draws: ArrayLike) -> float
Bulk ESS via initial positive sequence of autocorrelations (Geyer 1992).
print("ESS iid :", round(effective_sample_size(chains)))
etifn
eti(draws: ArrayLike, mass: float) -> Interval
Equal-tailed interval: the ``(1-mass)/2`` and ``1-(1-mass)/2`` quantiles.
e = eti(draws, 0.9)
format_intervalfn
format_interval(lower: float, upper: float, *, uncertainty: float | None = None, digits: int = 2, fallback: int = 6, group: bool = False) -> str
``[lower, upper]`` with both bounds at the precision the width supports.
print(format_interval(h.lower, h.upper), "from a half-width of", round(h.half_width, 4))
format_measuredfn
format_measured(value: float, uncertainty: float | None = None, *, digits: int = 2, fallback: int = 6, group: bool = False) -> str
``value`` as text, carrying only the digits ``uncertainty`` supports.
print("no stated uncertainty ->", format_measured(12.3456789))
free_parametersfn
free_parameters(model: ModelSpec) -> tuple[Param, ...]
Parameters that are inferred (everything not ``fixed``), in declaration order.
print([p.name for p in free_parameters(model)])
hdifn
hdi(draws: ArrayLike, mass: float) -> Interval
Highest-density interval: the narrowest window containing ``mass`` of the draws.
h = hdi(draws, 0.9)
intervalfn
interval(draws: ArrayLike, *, definition: IntervalDefinition, mass: float) -> Interval
i = interval(draws, definition="eti", mass=0.5)
is_failurefn
is_failure(x: object) -> TypeGuard[Failure]
if is_failure(r):
print(r.status, "->", r.reason, r.detail)
else:
print("value", r)
jax_availablefn
jax_available() -> bool
print("jax available:", jax_available(), "|", require_jax())
latexfn
latex(model: Model) -> str
print(latex(hill))
latex_or_unsupportedfn
latex_or_unsupported(model: Model) -> str | Unsupported
``latex``, unless the tree contains an ``Opaque`` node — then a typed refusal.
print(latex_or_unsupported(carry))
likelihood_scalefn
likelihood_scale(model: ModelSpec, data: Mapping[str, ArrayLike], theta: Mapping[str, ArrayLike]) -> Array
The likelihood's scale at constrained values: the named parameter, or ``scale_expr`` evaluated on ``data`` and ``theta``. ``ValueError`` for a family without one.
print("scale per row:", likelihood_scale(known_scale, {"se": np.array([0.1, 0.2, 0.4])}, {"mu": 1.0}))
load_specfn
load_spec(s: str) -> Spec
Deserialize any spec from its envelope, resolving the class by name.
generic = load_spec(design.to_json())
log_densityfn
log_density(model: ModelSpec, data: Mapping[str, ArrayLike], z: Mapping[str, ArrayLike]) -> float
Unnormalized log posterior in unconstrained coordinates: prior + Jacobian + likelihood.
print("log prior:", round(log_prior(model, theta), 3), "| log density:", round(log_density(model, data, z), 3))
log_likelihoodfn
log_likelihood(model: ModelSpec, data: Mapping[str, ArrayLike], theta: Mapping[str, ArrayLike]) -> float
Outcome log-likelihood at constrained values, plus every soft constraint's term.
print('decomposition available:', callable(log_likelihood), callable(log_prior))
log_priorfn
log_prior(model: ModelSpec, theta: Mapping[str, ArrayLike]) -> float
Sum of log prior densities of the free parameters at constrained values.
print("log prior:", round(log_prior(model, theta), 3), "| log density:", round(log_density(model, data, z), 3))
mc_standard_errorfn
mc_standard_error(draws: ArrayLike) -> float
Monte-Carlo SE of the mean, ``sd / sqrt(ESS)``.
print("MCSE :", mc_standard_error(ar))
missing_capabilitiesfn
missing_capabilities(producer: SupportsIntervention, required: frozenset[Capability] | set[Capability]) -> tuple[str, ...]
Names of required capabilities the producer lacks, sorted; empty means go.
missing = missing_capabilities(surface, needed)
node_pathfn
node_path(root: Model, target: Model) -> str
print(children(hill)[0], "|", node_path(hill, s))
paramsfn
params(node: Model) -> tuple[Param, ...]
Every distinct ``Param`` in the tree, by first appearance.
print([p.name for p in params(model)], data_names(model))
require_jaxfn
require_jax() -> Unsupported | None
print("jax available:", jax_available(), "|", require_jax())
round_tofn
round_to(value: float, uncertainty: float, *, digits: int = 2) -> float
``value`` rounded to the place ``uncertainty`` reaches.
print(round_to(s.mean, s.sd), "vs the stored", s.mean)
spec_type_namefn
spec_type_name(cls: type[Spec]) -> str
``"<module>:<qualname>"`` — the name written into the envelope.
print(spec_type_name(ProbeDesign))
summarizefn
summarize(draws: ArrayLike, *, definition: IntervalDefinition = 'hdi', mass: float = 0.9) -> Summary
s: Summary = summarize(draws, definition="hdi", mass=0.95)
unconstrainfn
unconstrain(model: ModelSpec, theta: Mapping[str, ArrayLike]) -> dict[str, Array]
Map constrained parameter values to R^k (log / logit per support).
z = unconstrain(model, theta)
valuefn
value(model: Model, *, data: Mapping[str, Any] | None = None, params: Mapping[str, Any] | None = None, opaque: OpaqueRegistry | None = None) -> Array
Evaluate ``model`` numerically. Missing inputs raise ``KeyError`` naming the name.
value(hill, data={"dose": d}, params={"k": 50.0, "s": 2.0, "beta": 10.0})
waldfn
wald(estimate: float, se: float, mass: float) -> Interval
Frequentist ``estimate ± z_{(1+mass)/2} · se``, labelled as such.
print(wald(estimate=1.2, se=0.3, mass=0.95))
walkfn
walk(node: Model, path: str = '') -> Iterator[tuple[str, Model]]
Pre-order traversal yielding ``(path, node)``; paths look like ``add[1].mul[0]``.
print([path for path, _ in walk(hill)][:5])
z_scorefn
z_score(value: float, reference: float, reference_se: float) -> float
``(value - reference) / se``: how the statistical golden fixtures compare.
print("z vs ref :", z_score(value=ar.mean(), reference=0.0, reference_se=mc_standard_error(ar)))
Panels and role maps: which column is the treatment, which is the outcome.
ColumnScalingspec
ColumnScaling(method: Literal['none', 'max', 'mean', 'standardize'], loc: float = 0.0, scale: float = 1.0)
``scaled = (x - loc) / scale``. ``loc`` is 0 unless standardizing.
manual = ScalingParameters(columns={"x": ColumnScaling(method=ref, scale=1000.0)})
Completenessspec
Completeness(n_units: int, n_periods: int, n_rows: int, balanced: bool, missing_cells: int, null_cells: int, gaps: dict[str, int] = {}, duplicate_rows: int = 0)
What the panel covers, so an estimator can decide whether it accepts it (review D5).
c: Completeness = panel.completeness()
Panelclass
Panel(frame: DataFrame, roles: RoleMap) -> None
A ``DataFrame`` plus a ``RoleMap``. Sorted by (unit, time) on construction.
panel = Panel(df, roles)
PanelErrorerror
The frame does not satisfy the role map.
try:
holey.require_balanced(context="a synthetic-control estimator")
except PanelError as e:
print("refused:", e)
RoleKindcallable
RoleKind(*args, **kwargs)
kind: RoleKind = roles.kind_of("fert_usd")
RoleMapspec
RoleMap(unit: str, time: str, outcome: tuple[str, Outcome], treatments: dict[str, Treatment] = {}, covariates: dict[str, Covariate] = {})
Column → role for a panel.
roles = RoleMap(
unit="plot",
time="week",
outcome=("yield_kg", Outcome(name="yield_total", dimension=D.outcome, unit="kg")),
treatments={
"fert_usd": Treatment(name="fertilizer", dimension=D.currency, unit="USD"),
"irrig_usd": Treatment(name="irrigation", dimension=D.currency, unit="USD"),
},
covariates={"rain_z": Covariate(name="rainfall", dimension=dimensionless())},
)
ScalingMethodcallable
ScalingMethod(*args, **kwargs)
ref: ScalingMethod = "max"
ScalingParametersspec
ScalingParameters(columns: dict[str, ColumnScaling])
Per-column scaling for a panel's measured columns.
manual = ScalingParameters(columns={"x": ColumnScaling(method=ref, scale=1000.0)})
fit_scalingfn
fit_scaling(panel: Panel, *, treatments: ScalingMethod = 'max', outcome: ScalingMethod = 'max', covariates: ScalingMethod = 'standardize') -> ScalingParameters
Fit scaling per role. Indices (unit, time) are never scaled.
params: ScalingParameters = fit_scaling(panel)
Save and load a whole analysis as JSON plus a posterior file. Never a pickle.
Analysisclass
Analysis(specs: Mapping[str, Spec] = <factory>, panel: Panel | None = None, posterior: Posterior | None = None, evidence: tuple[Spec, ...] = (), ledger: tuple[LedgerLine, ...] = (), provenance: Provenance | None = None) -> None
Everything an analysis produced, keyed and hashed.
save_analysis(Analysis(specs={"m": Treatment(name="seed_mass", dimension=D.mass, unit="kg")}), root2)
ArtifactRegistryclass
ArtifactRegistry(root: str | Path) -> None
``root/<hash>.json`` per spec, plus ``root/index.tsv`` of ``hash\ttype``.
reg = ArtifactRegistry(root.parent / "registry")
FORMAT_VERSIONvalue
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
print(FORMAT_VERSION, "|", saved.provenance.seed, saved.provenance.created)
FormatErrorerror
The directory is not a readable ``analysis.axiom`` of a known format version.
try:
load_analysis(root)
except FormatError as e:
print("FormatError:", e)
Provenancespec
Provenance(axiom_version: str, created: str, hashes: dict[str, str] = {}, seed: int | None = None, environment: dict[str, str] = {}, notes: tuple[str, ...] = ())
Who made this, from what, with which seed.
prov = Provenance(axiom_version="0.0.0", created="2026-08-21T00:00:00+00:00", hashes=analysis.hashes(), seed=3)
environment_fingerprintfn
environment_fingerprint() -> dict[str, str]
print(environment_fingerprint())
load_analysisfn
load_analysis(path: str | Path) -> Analysis
loaded = load_analysis(root)
save_analysisfn
save_analysis(analysis: Analysis, path: str | Path, *, seed: int | None = None, overwrite: bool = False) -> Analysis
Write an analysis and return it with its provenance stamped.
saved = save_analysis(analysis, root, seed=3)
The sampler seam. Laplace in core; NumPyro and PyMC behind extras.
BACKEND_NAMESvalue
Built-in immutable sequence.
print(BACKEND_NAMES)
Backendclass
Backend(*args, **kwargs)
Something that turns a ``ModelSpec`` plus data into a ``Posterior``.
print(isinstance(lap, Backend), getattr(lap, "name", lap))
ConvergenceReportspec
ConvergenceReport(rows: tuple[ParameterDiagnostics, ...], divergences: int, thresholds: ConvergenceThresholds, converged: bool, n_chains: int, n_draws: int, nonfinite_draw_frac: float = 0.0)
Per-parameter diagnostics, the divergence count, and a verdict with its thresholds.
report: ConvergenceReport = diagnose(post)
ConvergenceThresholdsspec
ConvergenceThresholds(rhat_max: float = 1.01, ess_min: float = 400.0, divergences_max: int = 0)
What ``converged`` means. Defaults follow Vehtari et al. (2021).
print(row.passes(ConvergenceThresholds(rhat_max=1.05, ess_min=100.0)))
LaplaceBackendclass
LaplaceBackend()
The always-available backend: no sampler, a Gaussian at the mode.
backend = LaplaceBackend()
NUTS_SAMPLERSvalue
Built-in immutable sequence.
print("PyMC can dispatch NUTS to:", NUTS_SAMPLERS, "\n")
NumpyroBackendclass
NumpyroBackend(*, target_accept: float = 0.9, max_tree_depth: int = 10) -> None
``Backend`` over NumPyro's NUTS. ``optimize`` and ``laplace`` reuse ``infer.laplace``.
nuts = NumpyroBackend()
NutsSamplercallable
NutsSampler(*args, **kwargs)
choice: NutsSampler = "pymc"
ParameterDiagnosticsspec
ParameterDiagnostics(name: str, rhat: float | None, ess_bulk: float | None, ess_tail: float | None, mcse_mean: float | None, mean: float | None, sd: float | None, n_nonfinite: int = 0)
One row of the report: a scalar variable or one element of a vector one.
row: ParameterDiagnostics = report.row("tau")
PointEstimatespec
PointEstimate(theta: dict[str, float | tuple[float, ...]], log_density: float, converged: bool, method: str, n_iter: int, hessian_pd: bool | None = None, min_eigenvalue: float | None = None, newton_decrement: float | None = None)
A mode (MAP) in *constrained* coordinates with the optimizer's verdict.
pe: PointEstimate = backend.optimize(model, data, seed=0)
Posteriorclass
Posterior(draws: Mapping[str, ArrayLike], *, coords: Mapping[str, Sequence[Any]] | None = None, provenance: Mapping[str, Any] | None = None) -> None
Draws keyed by name, each ``(chain, draw, *shape)``, plus coords and provenance.
post = Posterior({"mu": good, "tau": np.exp(ar)}, provenance={"seed": 0, "divergences": 0})
PymcBackendclass
PymcBackend(*, target_accept: float = 0.9, nuts_sampler: NutsSampler = 'pymc') -> None
``Backend`` over PyMC's NUTS. ``optimize`` and ``laplace`` reuse ``infer.laplace``.
pymc_backend = PymcBackend(nuts_sampler=choice, target_accept=0.9)
SampleSettingsspec
SampleSettings(draws: int = 1000, tune: int = 1000, chains: int = 4, target_accept: float = 0.9)
How much to sample. The defaults are the usual NUTS defaults.
settings = SampleSettings(draws=300, tune=300, chains=2)
SamplerStatusclass
SamplerStatus(name: str, missing: tuple[str, ...] = (), error: str = '') -> None
Whether one NUTS sampler can actually be used, and why not when it cannot.
probed: SamplerStatus = samplers(probe=True)[name] if status.usable else status
diagnosefn
diagnose(posterior: SupportsPosterior, *, divergences: int | None = None, rhat_max: float = 1.01, ess_min: float = 400.0, divergences_max: int = 0) -> ConvergenceReport
Diagnose every variable of ``posterior`` and judge it against the thresholds.
report: ConvergenceReport = diagnose(post)
ess_bulkfn
ess_bulk(draws: ArrayLike) -> float
Bulk ESS: ESS of the rank-normalized split chains.
print(f"{name:8s} rhat={split_rhat(d):.3f} ess_bulk={ess_bulk(d):7.1f} ess_tail={ess_tail(d):7.1f} mcse={mcse_mean(d):.4f}")
ess_tailfn
ess_tail(draws: ArrayLike) -> float
Tail ESS: the smaller of the ESS of the 5% and 95% quantile indicators.
print(f"{name:8s} rhat={split_rhat(d):.3f} ess_bulk={ess_bulk(d):7.1f} ess_tail={ess_tail(d):7.1f} mcse={mcse_mean(d):.4f}")
find_modefn
find_mode(model: ModelSpec, data: Mapping[str, ArrayLike], *, init: Mapping[str, ArrayLike] | None = None, derivatives: Derivatives = 'auto', seed: int | None = None, allow_unverified: bool = False) -> PointEstimate | Unverified
Posterior mode in unconstrained space, reported in constrained coordinates.
mode = find_mode(model, data)
from_inference_datafn
from_inference_data(idata: Any) -> Posterior
Read the ``posterior`` group of an arviz object back into a ``Posterior``.
back = from_inference_data(idata)
get_backendfn
get_backend(name: str) -> Backend | Unsupported
Resolve a backend by name; ``Unsupported`` names the missing extra.
lap = get_backend("laplace")
hessian_atfn
hessian_at(model: ModelSpec, data: Mapping[str, ArrayLike], z: Mapping[str, ArrayLike], *, derivatives: Derivatives = 'auto') -> Array | Unverified
Hessian of the negative log density at unconstrained ``z``, in ``flat_layout`` order.
H_fd = hessian_at(model, data, z, derivatives="finite_difference")
laplacefn
laplace(model: ModelSpec, data: Mapping[str, ArrayLike], *, draws: int, seed: int | None, init: Mapping[str, ArrayLike] | None = None, chains: int = 1, derivatives: Derivatives = 'auto', allow_unverified: bool = False) -> Posterior | Unverified
Gaussian approximation at the mode, drawn and mapped back to constrained space.
post = backend.laplace(model, data, draws=4000, seed=0)
mcse_meanfn
mcse_mean(draws: ArrayLike) -> float
Monte-Carlo standard error of the posterior mean, ``sd / sqrt(ess)`` on the raw draws.
print(f"{name:8s} rhat={split_rhat(d):.3f} ess_bulk={ess_bulk(d):7.1f} ess_tail={ess_tail(d):7.1f} mcse={mcse_mean(d):.4f}")
samplersfn
samplers(*, probe: bool = False) -> dict[str, SamplerStatus]
Every NUTS sampler PyMC can dispatch to, and whether it is usable here.
probed: SamplerStatus = samplers(probe=True)[name] if status.usable else status
split_rhatfn
split_rhat(draws: ArrayLike) -> float
Rank-normalized split-R̂: the max of the bulk and the folded (tail) versions.
print(f"{name:8s} rhat={split_rhat(d):.3f} ess_bulk={ess_bulk(d):7.1f} ess_tail={ess_tail(d):7.1f} mcse={mcse_mean(d):.4f}")
to_inference_datafn
to_inference_data(posterior: Posterior) -> Any | Unsupported
Build an arviz object with a ``posterior`` group; provenance goes into its attrs.
idata = to_inference_data(post)
A language for systems that are simultaneous or have time structure, compiled into ordinary expression trees.
AffineFormclass
AffineForm(coefficients: Mapping[str, Expr], intercept: Expr) -> None
``expr = sum_j coefficients[j] * unknown_j + intercept``, with expression coefficients.
form: AffineForm | None = affine_split(market.equation("quantity").rhs, ["quantity", "price"])
Blockspec
Block(variables: tuple[str, ...], simultaneous: bool)
One set of variables solved together, and whether it is a cycle.
first: Block = order.blocks[0]
BlockOrderspec
BlockOrder(blocks: tuple[Block, ...] = (), system: str = '')
The system's blocks in solution order, sources first.
order: BlockOrder = block_order(system)
BlockSolutionspec
BlockSolution(variables: tuple[str, ...], method: Literal['substitution', 'linear', 'fixed_point'], expressions: dict[str, Const | Data | Param | Add | Mul | Div | Pow | Apply | Convolve | Reduce | Gather | Link | Opaque], residuals: dict[str, Const | Data | Param | Add | Mul | Div | Pow | Apply | Convolve | Reduce | Gather | Link | Opaque] = {}, sweeps: int = 0, node_count: int, detail: dict[str, str] = {})
How one block was compiled, and into what.
solution: BlockSolution = solve_block(
{v: market.equation(v).rhs for v in ("quantity", "price")},
dims,
simultaneous=True,
)
DynamicEquationspec
DynamicEquation(target: str, rhs: Const | Data | Param | Add | Mul | Div | Pow | Apply | Convolve | Reduce | Gather | Link | Opaque, name: str = '')
``target[t] = rhs``, where ``rhs`` reads variables at ``t`` and earlier.
No executed statement uses this symbol — it appears in the notebooks only inside an import, which is all gate 12 requires. Worth a worked line.
DynamicSystemspec
DynamicSystem(name: str = '', variables: tuple[Variable, ...], equations: tuple[DynamicEquation, ...] = (), description: str = '')
A set of variables and one equation per endogenous variable.
DynamicSystem(variables=(Variable(name="y", dimension=NONE),), equations=())
DynamicsErrorerror
The system is malformed: an unknown reference, a missing equation, a bad name.
No executed statement uses this symbol — it appears in the notebooks only inside an import, which is all gate 12 requires. Worth a worked line.
Formcallable
Form(*args, **kwargs)
form: Form = marginal.form
LeadNotSupportedErrorerror
An equation refers to a future value; solving that needs rational expectations.
try:
parse_ref("y.f1")
except LeadNotSupportedError as e:
print("lead ->", e)
ParseErrorerror
The equation text does not parse, or names something undeclared.
try:
parse_equations("y = wobble", variables=(Variable(name="y", dimension=NONE),))
except ParseError as e:
print("unknown name ->", e)
Rolecallable
Role(*args, **kwargs)
role: Role = system.variable("inflow").role
SolveMethodcallable
SolveMethod(*args, **kwargs)
method: SolveMethod = solution.method
Unrolledspec
Unrolled(system: str = '', system_hash: str = '', form: Literal['conditional', 'marginal'], periods: int, expressions: dict[str, Const | Data | Param | Add | Mul | Div | Pow | Apply | Convolve | Reduce | Gather | Link | Opaque], solutions: tuple[BlockSolution, ...] = (), columns: tuple[str, ...] = (), edges: tuple[tuple[str, str], ...] = (), node_count: int, detail: dict[str, str] = {})
A compiled dynamic system: one expression per solved node, and how it got there.
No executed statement uses this symbol — it appears in the notebooks only inside an import, which is all gate 12 requires. Worth a worked line.
Variablespec
Variable(name: str, dimension: Dimension, role: Literal['endogenous', 'exogenous'] = 'endogenous', observed: bool = True, initial: float = 0.0, description: str = '')
One named quantity of the system, with its dimension and its role.
parse_equations("y = wobble", variables=(Variable(name="y", dimension=NONE),))
affine_splitfn
affine_split(expr: Expr, unknowns: Sequence[str]) -> AffineForm | None
Split ``expr`` into coefficients on ``unknowns`` and a remainder, or ``None``.
form: AffineForm | None = affine_split(market.equation("quantity").rhs, ["quantity", "price"])
block_orderfn
block_order(system: DynamicSystem) -> BlockOrder
Block-triangular decomposition of the contemporaneous dependency graph.
order: BlockOrder = block_order(system)
conditional_formfn
conditional_form(system: DynamicSystem, *, sweeps: int = 0, max_nodes: int = 20000) -> Unrolled | Unsupported
Compile one generic period: ``y = f(exogenous at t, every lag as a column)``.
print("without sweeps:", conditional_form(saturating).reason[:120])
lag_reffn
lag_ref(var: str, lag: int) -> str
``("y", 0) -> "y"``; ``("y", 2) -> "y.l2"``.
print(lag_ref("stock", 2), parse_ref("stock.l2"), time_ref("stock", 3))
lagged_columnsfn
lagged_columns(system: DynamicSystem) -> tuple[str, ...]
Every lagged reference the conditional form needs as a materialized column.
print("columns:", conditional.columns, "| lagged:", lagged_columns(system))
parse_equationsfn
parse_equations(text: str, *, variables: Sequence[Variable], parameters: Sequence[Param] = ()) -> tuple[DynamicEquation, ...]
Parse ``target = expression`` lines against declared variables and parameters.
parse_equations("y = wobble", variables=(Variable(name="y", dimension=NONE),))
parse_reffn
parse_ref(name: str) -> tuple[str, int]
``"y.l2" -> ("y", 2)``; ``"y" -> ("y", 0)``. A lead raises.
print(lag_ref("stock", 2), parse_ref("stock.l2"), time_ref("stock", 3))
parse_systemfn
parse_system(text: str, *, variables: Sequence[Variable], parameters: Sequence[Param] = (), name: str = '', description: str = '') -> DynamicSystem
Parse equations and assemble the ``DynamicSystem``, dimension checks included.
parse_system(
"y = x",
variables=(
Variable(name="y", dimension=D.outcome),
Variable(name="x", dimension=D.currency, role="exogenous"),
),
)
prepare_panelfn
prepare_panel(system: DynamicSystem, frame: DataFrame, *, unit: str | None = None, time: str) -> DataFrame
Add the lagged columns the conditional form reads, per unit, in time order.
prepared = prepare_panel(system, frame, unit="unit", time="t")
reduced_formfn
reduced_form(equations: Mapping[str, Expr], dimensions: Mapping[str, Dimension], *, max_nodes: int = 20000) -> dict[str, Expr] | Unsupported
Solve ``v = rhs_v(v, ...)`` exactly when every right-hand side is affine in the unknowns.
solved = reduced_form(
{v: market.equation(v).rhs for v in ("quantity", "price")},
dims,
)
refs_infn
refs_in(expr: Model) -> tuple[tuple[str, int], ...]
Every ``(variable, lag)`` the expression reads, by first appearance.
print(refs_in(system.equation("stock").rhs))
solve_blockfn
solve_block(equations: Mapping[str, Expr], dimensions: Mapping[str, Dimension], *, simultaneous: bool, start: Mapping[str, Expr] | None = None, sweeps: int = 0, max_nodes: int = 20000) -> BlockSolution | Unsupported
Compile one block: substitution, exact reduced form, or declared fixed-point sweeps.
solution: BlockSolution = solve_block(
{v: market.equation(v).rhs for v in ("quantity", "price")},
dims,
simultaneous=True,
)
strongly_connected_componentsfn
strongly_connected_components(nodes: tuple[str, ...], edges: tuple[tuple[str, str], ...]) -> tuple[tuple[str, ...], ...]
Tarjan's SCCs, each sorted, the tuple in reverse topological order (sources last).
print(strongly_connected_components(("a", "b", "c"), (("a", "b"), ("b", "a"), ("b", "c"))))
time_reffn
time_ref(var: str, period: int) -> str
``("y", 3) -> "y.t3"`` — the name of ``y`` at absolute period 3.
print(lag_ref("stock", 2), parse_ref("stock.l2"), time_ref("stock", 3))
to_model_specfn
to_model_spec(unrolled: Unrolled, outcome: str, *, likelihood: Likelihood, priors: Mapping[str, Prior], extra_parameters: Sequence[Param] = (), period: int | None = None, name: str = '') -> ModelSpec
Wrap one compiled node as a ``ModelSpec`` that any backend fits.
model = to_model_spec(
conditional,
"stock",
likelihood=Likelihood(family="normal", scale="sigma"),
priors={
"decay": Prior(family="beta", hyper={"alpha": 1.5, "beta": 1.5}),
"beta": Prior(family="lognormal", hyper={"mu": 0.0, "sigma": 1.0}),
},
extra_parameters=(
Param(name="sigma", dimension=D.outcome, prior=Prior(family="halfnormal", hyper={"sigma": 1.0})),
),
)
unrollfn
unroll(system: DynamicSystem, periods: int, *, sweeps: int = 0, max_nodes: int = 20000) -> Unrolled | Unsupported
Compile every period into expressions over exogenous inputs alone.
marginal = unroll(system, periods=4)
unrolled_edgesfn
unrolled_edges(system: DynamicSystem, periods: int, *, reduced: bool = True) -> tuple[tuple[str, str], ...]
Edges of the time-indexed graph, ``("x.t1", "y.t2")``, sorted and deduplicated.
print("as written (may be cyclic):", unrolled_edges(system, periods=2, reduced=False))
Causal graphs, back-door / front-door / IV routes, transport verdicts.
CausalGraphspec
CausalGraph(nodes: tuple[str, ...] = (), edges: tuple[tuple[str, str], ...] = (), bidirected: tuple[tuple[str, str], ...] = (), unmeasured: tuple[str, ...] = (), selection: tuple[str, ...] = (), feedback: bool = False, name: str = '')
A directed acyclic graph over named nodes, with optional bidirected edges.
g = CausalGraph.from_edges("Z -> X, Z -> Y, X -> M, M -> Y, X <-> W, W -> Y", unmeasured=["W"], name="toy")
ClusterDAGspec
ClusterDAG(clusters: dict[str, tuple[str, ...]], edges: tuple[tuple[str, str], ...] = (), bidirected: tuple[tuple[str, str], ...] = (), unmeasured: tuple[str, ...] = (), name: str = '')
A DAG over clusters of variables, with the micro-level structure left open.
market = ClusterDAG(
clusters={"cost": ("wage", "fuel"), "price": ("price",), "demand": ("units",)},
edges=(("cost", "price"), ("price", "demand")),
name="a two-block market",
)
Densityspec
Density(node: Literal['density'] = 'density', outcomes: tuple[str, ...], given: tuple[str, ...] = ())
``P(outcomes | given)`` — a conditional of the observational distribution.
ratio_example: Ratio = Ratio(
numerator=Density(outcomes=("X", "Y")), denominator=Density(outcomes=("X",))
)
EndogeneityTestspec
EndogeneityTest(statistic: float, p_value: float, df: int, method: str, conclusion: Literal['endogenous', 'exogenous'], alpha: float = 0.05, treatment: str = '', outcome: str = '', detail: dict[str, float] = {})
Outcome of an endogeneity test, with the level the conclusion used.
print(dwh if not isinstance(dwh, EndogeneityTest) else (dwh.conclusion, round(dwh.statistic, 2), dwh.p_value))
Formulacallable
Formula(*args, **kwargs)
Runtime representation of an annotated type.
formula: Formula = front_door.formula
FrontDoorRoutespec
FrontDoorRoute(mediators: tuple[str, ...], treatment: str, outcome: str)
A front-door identification route: ``P(y | do(x))`` through ``mediators``.
route = FrontDoorRoute(mediators=("M",), treatment="X", outcome="Y")
GraphErrorerror
The graph is malformed (cycle, unknown node, self-edge).
try:
CausalGraph.from_edges("A -> B, B -> C, C -> A")
except GraphError as e:
print("refused:", e)
Hedgespec
Hedge(root: tuple[str, ...], subset: tuple[str, ...], variables: tuple[str, ...])
The witness that an effect is *not* identifiable — a proof, not a failed search.
hedge: Hedge = blocked.hedge
IdentificationVerdictspec
IdentificationVerdict(treatment: str, outcome: str, graph_hash: str, verdict: Verdict, route: Literal['no_causal_path', 'backdoor', 'frontdoor', 'instrument', 'backdoor_unmeasured', 'none'], adjustment_set: tuple[str, ...] = (), mediators: tuple[str, ...] = (), instrument: str = '', conditioning: tuple[str, ...] = (), unmeasured_required: tuple[str, ...] = (), alternatives: tuple[Literal['no_causal_path', 'backdoor', 'frontdoor', 'instrument', 'backdoor_unmeasured', 'none'], ...] = (), search_limits_hit: tuple[str, ...] = (), ... and 1 more field)
What the graph says about ``treatment -> outcome``, and by which route.
v: IdentificationVerdict = identify(simple, "X", "Y")
IdentifiedEffectspec
IdentifiedEffect(treatment: tuple[str, ...], outcome: tuple[str, ...], conditioned: tuple[str, ...] = (), graph_hash: str = '', formula: Optional[Density | Product | Marginal | Ratio] = None, hedge: Hedge | None = None, verdict: Verdict, detail: dict[str, str] = {})
What ID returned: a formula, or a hedge, and a verdict either way.
result: IdentifiedEffect = identify_effect(graph, "X", "Y")
InstrumentRoutespec
InstrumentRoute(instrument: str, conditioning: tuple[str, ...] = (), treatment: str, outcome: str)
An instrumental-variable route: ``instrument`` for ``treatment -> outcome``.
print(InstrumentRoute(instrument="Z", conditioning=("W",), treatment="X", outcome="Y"))
JointTableclass
JointTable(names: tuple[str, ...], table: Array) -> None
A joint distribution over discrete variables, as an array with one axis per variable.
joint = JointTable(names=("X", "Y", "Z"), table=observational)
LinearEstimatespec
LinearEstimate(estimate: float, se: float, n: int, method: Literal['ols', '2sls', 'frontdoor'], treatment: str, outcome: str, covariates: tuple[str, ...] = (), detail: dict[str, float] = {})
A point estimate with its classical standard error and provenance.
naive: LinearEstimate = ols(frame, "Y", "X")
Marginalspec
Marginal(node: Literal['marginal'] = 'marginal', over: tuple[str, ...], term: Density | Product | Marginal | Ratio)
``sum over `over` of `term``` — marginalization.
print("is it a Marginal of a Product of Density terms?", isinstance(formula, Marginal))
MixedGraphspec
MixedGraph(nodes: tuple[str, ...] = (), edges: tuple[tuple[str, str], ...] = (), bidirected: tuple[tuple[str, str], ...] = (), unmeasured: tuple[str, ...] = (), name: str = '')
A directed mixed graph. Directed edges may form cycles; separation is sigma.
loop = MixedGraph.from_edges("x -> a, a -> b, b -> a, b -> y")
Productspec
Product(node: Literal['product'] = 'product', factors: tuple[Density | Product | Marginal | Ratio, ...])
A product of formulas.
product_example: Product = Product(
factors=(Density(outcomes=("Y",), given=("X",)), Density(outcomes=("X",)))
)
Ratiospec
Ratio(node: Literal['ratio'] = 'ratio', numerator: Density | Product | Marginal | Ratio, denominator: Density | Product | Marginal | Ratio)
``numerator / denominator``; how a conditional of a derived expression is written.
ratio_example: Ratio = Ratio(
numerator=Density(outcomes=("X", "Y")), denominator=Density(outcomes=("X",))
)
Rolecallable
Role(*args, **kwargs)
r: dict[str, Role] = roles(m_bias, "X", "Y")
RoleAssignmentspec
RoleAssignment(graph_hash: str, treatment: str, outcome: str, roles: dict[str, Literal['treatment', 'outcome', 'confounder', 'mediator', 'collider', 'instrument', 'proxy', 'descendant_of_outcome', 'neutral']])
The roles of every node of one graph relative to one treatment–outcome pair.
ra: RoleAssignment = assign_roles(g, "X", "Y")
Routecallable
Route(*args, **kwargs)
pref: tuple[Route, ...] = ("frontdoor", "backdoor")
SequentialPlanspec
SequentialPlan(stages: tuple[str, ...], outcome: str, graph_hash: str, adjustments: tuple[tuple[str, ...], ...], verdict: Verdict, static_adjustment_fails: bool = False, detail: dict[str, str] = {})
Per-stage adjustment sets for a sequence of treatments, and the verdict on them.
plan: SequentialPlan = sequential_plan(graph, ["dose.t0", "dose.t1", "dose.t2"], "outcome.t2")
TransportVerdictspec
TransportVerdict(treatment: str, outcome: str, verdict: Verdict, s_admissible_set: tuple[str, ...] = (), mediators: tuple[str, ...] = (), given: tuple[str, ...] | None = None, selection: tuple[str, ...] = (), graph_hash: str = '', formula: str = '', missing: tuple[str, ...] = ())
Whether, and by what formula, an effect transports from source to target.
unadjusted: TransportVerdict = transport_verdict(g, "X", "Y", given=())
acyclifyfn
acyclify(graph: MixedGraph | CausalGraph) -> CausalGraph
The acyclification of a directed mixed graph (Mooij & Claassen 2020, Def. 3).
print("acyclified:", acyclify(loop).to_text())
adjustment_setsfn
adjustment_sets(graph: CausalGraph, x: str, y: str, *, measured_only: bool = True, max_candidates: int = 16) -> AdjustmentSets | Unsupported
Every back-door admissible set among the candidates, deterministically ordered.
print(adjustment_sets(simple, "X", "Y"), minimal_adjustment_sets(simple, "X", "Y"))
admissible_set_existsfn
admissible_set_exists(graph: CausalGraph, x: str, y: str, *, measured_only: bool = True) -> bool
Whether any back-door admissible set exists, in polynomial time.
print(canonical_adjustment_set(simple, "X", "Y"), admissible_set_exists(simple, "X", "Y"))
assign_rolesfn
assign_roles(graph: CausalGraph, x: str, y: str) -> RoleAssignment
``roles`` packaged as a ``Spec`` with the graph's content hash.
ra: RoleAssignment = assign_roles(g, "X", "Y")
backdoor_admissiblefn
backdoor_admissible(graph: CausalGraph, x: str, y: str, z: Iterable[str]) -> bool
Pearl's back-door criterion for ``z`` relative to ``(x, y)``.
print(backdoor_admissible(simple, "X", "Y", ["Z"]), backdoor_admissible(simple, "X", "Y", []))
canonical_adjustment_setfn
canonical_adjustment_set(graph: CausalGraph, x: str, y: str, *, measured_only: bool = True) -> frozenset[str] | None
The canonical back-door set, or ``None`` when no admissible set exists.
print(canonical_adjustment_set(simple, "X", "Y"), admissible_set_exists(simple, "X", "Y"))
compatiblefn
compatible(cdag: ClusterDAG, graph: CausalGraph) -> bool
Whether a micro-level DAG is compatible with the cluster-level claim.
print("compatible:", compatible(market, micro))
conditional_instrumentsfn
conditional_instruments(graph: CausalGraph, x: str, y: str, *, measured_only: bool = True, max_candidates: int = 12, max_conditioning: int = 2) -> tuple[tuple[str, frozenset[str]], ...] | Unsupported
All ``(z, W)`` pairs with ``|W| <= max_conditioning`` that satisfy the criterion.
print(instruments(civ, "X", "Y"), conditional_instruments(civ, "X", "Y"))
directly_transportablefn
directly_transportable(graph: CausalGraph, x: str, y: str) -> bool
Whether ``P*(y | do(x)) = P(y | do(x))`` (B&P 2014 Def. 7).
print("direct:", directly_transportable(g, "X", "Y"))
districtsfn
districts(graph: CausalGraph) -> tuple[tuple[str, ...], ...]
The C-components: connected components of the bidirected part (Tian & Pearl 2002).
print("districts:", districts(admg))
durbin_wu_hausmanfn
durbin_wu_hausman(frame: DataFrame, y: str, x: str, instruments: str | Sequence[str], covariates: str | Sequence[str] = (), alpha: float = 0.05) -> EndogeneityTest | Unverified
Control-function Durbin–Wu–Hausman test of the exogeneity of ``x``.
dwh = durbin_wu_hausman(frame, "Y", "X", instruments=["Z"])
frontdoor_admissiblefn
frontdoor_admissible(graph: CausalGraph, x: str, y: str, m: Iterable[str]) -> bool
Whether ``m`` satisfies the front-door criterion relative to ``(x, y)``.
print(frontdoor_admissible(fd_graph, "X", "Y", ["M"]), frontdoor_sets(fd_graph, "X", "Y"))
frontdoor_linearfn
frontdoor_linear(frame: DataFrame, y: str, x: str, mediators: str | Sequence[str], covariates: str | Sequence[str] = ()) -> LinearEstimate
Linear front-door estimate of the effect of ``x`` on ``y`` through ``mediators``.
fd = frontdoor_linear(frame, "Y", "X", mediators=v.mediators)
frontdoor_setsfn
frontdoor_sets(graph: CausalGraph, x: str, y: str, *, measured_only: bool = True, max_candidates: int = 12) -> tuple[frozenset[str], ...] | Unsupported
Every front-door admissible mediator set, smallest first, then lexicographic.
print(frontdoor_admissible(fd_graph, "X", "Y", ["M"]), frontdoor_sets(fd_graph, "X", "Y"))
hausman_iv_vs_olsfn
hausman_iv_vs_ols(est_ols: LinearEstimate, est_2sls: LinearEstimate, alpha: float = 0.05) -> EndogeneityTest | Unverified
Classic Hausman contrast of an OLS and a 2SLS estimate of the same effect.
h = hausman_iv_vs_ols(ols(frame, "Y", "X"), two_stage_least_squares(frame, "Y", "X", instruments=["Z"]))
identifyfn
identify(graph: CausalGraph, treatment: str, outcome: str, *, prefer: Sequence[Route] = ..., has_carryover: bool = False, max_candidates: int = 16) -> IdentificationVerdict
Decide how (and whether) ``treatment -> outcome`` is identified in ``graph``.
v: IdentificationVerdict = identify(simple, "X", "Y")
identify_cluster_effectfn
identify_cluster_effect(cdag: ClusterDAG, treatment: str | Iterable[str], outcome: str | Iterable[str]) -> IdentifiedEffect
``P(outcome cluster | do(treatment cluster))`` by running ID on the cluster graph.
print("effect of the cost block on demand:", identify_cluster_effect(market, "cost", "demand").to_text())
identify_conditional_effectfn
identify_conditional_effect(graph: CausalGraph, treatment: str | Iterable[str], outcome: str | Iterable[str], conditioned: Iterable[str]) -> IdentifiedEffect
``P(outcome | do(treatment), conditioned)`` by IDC (Shpitser & Pearl 2008).
conditional = identify_conditional_effect(
CausalGraph.from_edges("Z -> X, Z -> Y, X -> Y, X -> W, W -> Y"), "X", "Y", ["W"]
)
identify_effectfn
identify_effect(graph: CausalGraph, treatment: str | Iterable[str], outcome: str | Iterable[str]) -> IdentifiedEffect
``P(outcome | do(treatment))`` by the ID algorithm: the estimand, or the hedge.
result: IdentifiedEffect = identify_effect(graph, "X", "Y")
instrument_admissiblefn
instrument_admissible(graph: CausalGraph, x: str, y: str, z: str, conditioning: Iterable[str] = ()) -> bool
Whether ``z`` is an instrument for the total effect of ``x`` on ``y`` given ``W``.
print(instruments(iv_graph, "X", "Y"), instrument_admissible(iv_graph, "X", "Y", "Z"))
instrumentsfn
instruments(graph: CausalGraph, x: str, y: str, *, measured_only: bool = True) -> tuple[str, ...]
All unconditional instruments (``W = ∅``) for ``x -> y``, sorted by name.
print(instruments(iv_graph, "X", "Y"), instrument_admissible(iv_graph, "X", "Y", "Z"))
latent_projectionfn
latent_projection(graph: CausalGraph, keep: Iterable[str] | None = None) -> CausalGraph
Project the unmeasured nodes out, leaving an ADMG over the ones kept.
admg = latent_projection(explicit)
minimal_adjustment_setsfn
minimal_adjustment_sets(graph: CausalGraph, x: str, y: str, *, measured_only: bool = True, max_candidates: int = 16) -> AdjustmentSets | Unsupported
Inclusion-minimal back-door admissible sets, ordered as ``adjustment_sets``.
print(adjustment_sets(simple, "X", "Y"), minimal_adjustment_sets(simple, "X", "Y"))
minimal_s_admissible_setsfn
minimal_s_admissible_sets(graph: CausalGraph, x: str, y: str, *, measured_only: bool = True, max_candidates: int = 14) -> tuple[frozenset[str], ...] | Unsupported
The S-admissible sets with no S-admissible proper subset, smallest first.
print(s_admissible_sets(g, "X", "Y"), minimal_s_admissible_sets(g, "X", "Y"))
olsfn
ols(frame: DataFrame, y: str, x: str, covariates: str | Sequence[str] = ()) -> LinearEstimate
Coefficient on ``x`` in OLS of ``y`` on ``[1, x, covariates]``.
naive: LinearEstimate = ols(frame, "Y", "X")
parse_edgesfn
parse_edges(text: str) -> tuple[tuple[Edge, ...], tuple[Edge, ...]]
Parse ``"Z -> X, Z -> Y, X -> Y, A <-> B"`` into (directed, bidirected) edge tuples.
print(parse_edges("A -> B; C <- B"))
requires_unmeasuredfn
requires_unmeasured(graph: CausalGraph, x: str, y: str) -> bool
True iff adjustment is possible only through a node the panel does not observe.
print("needs an unmeasured node:", requires_unmeasured(g, "X", "Y"))
rolesfn
roles(graph: CausalGraph, x: str, y: str) -> dict[str, Role]
Assign each node a descriptive role relative to ``(x, y)``.
r: dict[str, Role] = roles(m_bias, "X", "Y")
s_admissiblefn
s_admissible(graph: CausalGraph, x: str, y: str, z: Iterable[str]) -> bool
Whether ``Z`` is S-admissible for the effect of ``x`` on ``y`` (B&P 2014 Def. 8).
print("S-admissible {Z}:", s_admissible(g, "X", "Y", ["Z"]), "| {}:", s_admissible(g, "X", "Y", []))
s_admissible_setsfn
s_admissible_sets(graph: CausalGraph, x: str, y: str, *, measured_only: bool = True, max_candidates: int = 14) -> tuple[frozenset[str], ...] | Unsupported
Every S-admissible set, smallest first, then lexicographic.
print(s_admissible_sets(g, "X", "Y"), minimal_s_admissible_sets(g, "X", "Y"))
selection_diagramfn
selection_diagram(graph: CausalGraph) -> CausalGraph
The selection diagram of ``graph`` as a plain DAG (B&P 2014 Def. 4).
sd = selection_diagram(g)
sequential_backdoor_admissiblefn
sequential_backdoor_admissible(graph: CausalGraph, treatments: Sequence[str], outcome: str, adjustments: Sequence[Iterable[str]], *, measured_only: bool = True) -> tuple[bool, str]
The sequential back-door criterion; ``(True, "")`` or ``(False, why)``.
ok, why = sequential_backdoor_admissible(
graph, ["dose.t0", "dose.t1"], "outcome.t2", [("outcome.t1",), ()]
)
sequential_ignorabilityfn
sequential_ignorability(graph: CausalGraph, stages: Sequence[str], outcome: str, adjustments: Sequence[Iterable[str]]) -> tuple[bool, str]
Sequential ignorability, read off one SWIG with every stage split.
print("SWIG agrees :", sequential_ignorability(graph, stages, "outcome.t2", plan.adjustments))
sequential_planfn
sequential_plan(graph: CausalGraph, treatments: Sequence[str], outcome: str, *, candidates: Iterable[str] | None = None, prune: bool = True) -> SequentialPlan
Search for per-stage adjustment sets satisfying the sequential back-door criterion.
plan: SequentialPlan = sequential_plan(graph, ["dose.t0", "dose.t1", "dose.t2"], "outcome.t2")
sigma_separatedfn
sigma_separated(graph: MixedGraph | CausalGraph, x: str | Iterable[str], y: str | Iterable[str], z: Iterable[str] = ()) -> bool
Sigma-separation of ``X`` and ``Y`` given ``Z`` (Forré & Mooij 2017).
print(" quantity indep price given the block's parents?",
structural.sigma_separated("quantity.t0", "price.t0", ["cost.t0"]))
single_world_ignorabilityfn
single_world_ignorability(graph: CausalGraph, treatment: str, outcome: str, adjustment: Iterable[str] = (), *, label: str = '') -> bool
Whether ``Y(x)`` is independent of ``X`` given ``Z`` on the SWIG.
print(f"Y(x) indep X | {adjustment or '{}'}:", single_world_ignorability(g, "X", "Y", adjustment))
swigfn
swig(graph: CausalGraph, interventions: Mapping[str, str] | Iterable[str]) -> CausalGraph
Split every intervened node; relabel its descendants as potential outcomes.
print("swig :", swig(g, ["X"]).to_text())
transport_verdictfn
transport_verdict(graph: CausalGraph, x: str, y: str, *, given: Iterable[str] | None = None, max_candidates: int = 14) -> TransportVerdict
Read the transportability of ``P*(y | do(x))`` off ``graph``'s selection diagram.
unadjusted: TransportVerdict = transport_verdict(g, "X", "Y", given=())
trivially_transportablefn
trivially_transportable(graph: CausalGraph, x: str, y: str, *, max_candidates: int = 14) -> bool
Whether ``P*(y | do(x))`` is identifiable from the target alone (B&P 2014 Def. 6).
print("trivially (from target data alone):", trivially_transportable(g, "X", "Y"))
two_stage_least_squaresfn
two_stage_least_squares(frame: DataFrame, y: str, x: str, instruments: str | Sequence[str], covariates: str | Sequence[str] = ()) -> LinearEstimate
2SLS of ``y`` on ``x`` with ``instruments``, adjusting for ``covariates``.
iv = two_stage_least_squares(frame, "Y", "X", instruments=[v.instrument])
unrolled_graphfn
unrolled_graph(system: DynamicSystem, periods: int, *, name: str = '') -> CausalGraph
The acyclification of the time-indexed graph: an acyclic graph, honestly obtained.
graph = unrolled_graph(system, periods=3)
unrolled_mixed_graphfn
unrolled_mixed_graph(system: DynamicSystem, periods: int, *, name: str = '') -> MixedGraph
The time-indexed graph exactly as the equations were written, cycles included.
structural = unrolled_mixed_graph(market, periods=2)
weak_instrument_checkfn
weak_instrument_check(est: LinearEstimate, threshold: float = 10.0) -> Assumption
The instrument-strength assumption, checked against the first-stage F.
strength = weak_instrument_check(iv)
Learn the graph instead of assuming it: essential graphs, GES/GIES, bootstrap stability, and FCI when a common cause may be unmeasured.
Datasetclass
Dataset(values: Array, names: tuple[str, ...], regimes: tuple[frozenset[str], ...]) -> None
Rows of measurements, each labelled with what was intervened on when it was taken.
return Dataset(np.column_stack([values[n] for n in order]), tuple(order), (frozenset(do),) * n)
DiscoveryResultspec
DiscoveryResult(essential: EssentialGraph, score: float, n_observations: int, targets: tuple[tuple[str, ...], ...] = (), phases: tuple[str, ...] = (), steps: tuple[str, ...] = (), penalty: float, limits_hit: tuple[str, ...] = (), detail: dict[str, str] = {})
What a structure search found, and how far it is entitled to be believed.
found: DiscoveryResult = ges(GaussianBIC(observational))
EdgeSupportspec
EdgeSupport(a: str, b: str, adjacent: float, forward: float, backward: float, undirected: float)
How often one pair of variables was joined, and which way it pointed.
assert isinstance(support, EdgeSupport)
EssentialGraphspec
EssentialGraph(nodes: tuple[str, ...] = (), directed: tuple[tuple[str, str], ...] = (), undirected: tuple[tuple[str, str], ...] = (), targets: tuple[tuple[str, ...], ...] = (), name: str = '')
A partially directed graph: compelled edges directed, the rest undirected.
partial = EssentialGraph(nodes=truth.nodes, directed=(("a", "b"),), undirected=(("b", "c"),))
GaussianBICclass
GaussianBIC(data: Dataset, penalty: float = 1.0) -> None
Decomposable BIC for linear-Gaussian structure, aware of which rows were randomized.
found: DiscoveryResult = ges(GaussianBIC(observational))
IndependenceResultspec
IndependenceResult(x: str, y: str, given: tuple[str, ...] = (), correlation: float, p_value: float, n: int, statistic: float = 0.0)
One conditional-independence test: what was asked, and both answers.
assert isinstance(check, IndependenceResult)
Markcallable
Mark(*args, **kwargs)
left: Mark = edge.mark_a
PAGspec
PAG(nodes: tuple[str, ...] = (), edges: tuple[PagEdge, ...] = (), limits_hit: tuple[str, ...] = (), detail: dict[str, str] = {})
A partial ancestral graph: what the data determines when latents are possible.
pag: PAG = fci(observed, oracle_independence(world))
PagEdgespec
PagEdge(a: str, b: str, mark_a: Literal['circle', 'arrow', 'tail'] = 'circle', mark_b: Literal['circle', 'arrow', 'tail'] = 'circle')
One edge of a PAG: two variables and the mark at each end.
assert isinstance(edge, PagEdge)
PartialCorrelationclass
PartialCorrelation(data: Dataset) -> None
A Gaussian conditional-independence test over a dataset.
check = PartialCorrelation(measured).test("seed", "harvest")
StabilityReportspec
StabilityReport(variables: tuple[str, ...], edges: tuple[EdgeSupport, ...] = (), n_bootstrap: int, n_rows: int, targets: tuple[tuple[str, ...], ...] = (), penalty: float, seed: int | None = None, detail: dict[str, str] = {})
Per-edge support across bootstrap resamples of the same data.
assert isinstance(stability, StabilityReport)
consistent_extensionfn
consistent_extension(graph: EssentialGraph) -> CausalGraph
One DAG from the class, by Dor & Tarsi's (1992) algorithm.
print("a representative:", consistent_extension(essential).to_text())
consistent_extensionsfn
consistent_extensions(graph: EssentialGraph, *, limit: int = 4096) -> tuple[CausalGraph, ...]
Every DAG in the class, by orienting the undirected edges every way.
for member in consistent_extensions(essential):
print(" ", member.to_text())
cpdagfn
cpdag(graph: CausalGraph) -> EssentialGraph
The essential graph of a DAG: what observational data could ever orient.
print("chain :", cpdag(CausalGraph.from_edges("a -> b, b -> c")).to_text())
edge_stabilityfn
edge_stability(data: Dataset, *, n_bootstrap: int = 100, penalty: float = 1.0, targets: Sequence[Iterable[str]] | None = None, seed: int | None = None) -> StabilityReport
Resample the rows, re-run the search, and count how often each edge returns.
stability = edge_stability(trial, n_bootstrap=60, seed=7)
fcifn
fci(variables: Sequence[str], independent: Independent, *, max_conditioning: int = 3, name: str = '') -> PAG
Discover a PAG without assuming causal sufficiency.
pag: PAG = fci(observed, oracle_independence(world))
fci_from_datafn
fci_from_data(data: Dataset, *, alpha: float = 0.05, max_conditioning: int = 3, variables: Iterable[str] | None = None, name: str = '') -> PAG
Run FCI with a partial-correlation test at level ``alpha``.
from_data = fci_from_data(measured, alpha=0.01)
gesfn
ges(score: GaussianBIC, *, name: str = '') -> DiscoveryResult
Greedy equivalence search on observational data: forward, then backward.
found: DiscoveryResult = ges(GaussianBIC(observational))
giesfn
gies(score: GaussianBIC, targets: Sequence[Iterable[str]] | None = None, *, name: str = '') -> DiscoveryResult
Greedy interventional equivalence search: the same, over I-essential graphs.
one = gies(GaussianBIC(pooled))
interventional_essential_graphfn
interventional_essential_graph(graph: CausalGraph, targets: Sequence[Iterable[str]]) -> EssentialGraph
What the data could orient after intervening on each target in the family.
after = interventional_essential_graph(truth, [["a"]])
markov_equivalentfn
markov_equivalent(first: CausalGraph, second: CausalGraph) -> bool
Same skeleton and same v-structures — the classical characterization.
print("all Markov equivalent to the truth:",
all(markov_equivalent(m, truth) for m in consistent_extensions(essential)))
meek_closurefn
meek_closure(graph: EssentialGraph) -> EssentialGraph
Orient every edge forced by what is already oriented (Meek 1995, rules 1-4).
print("R1 propagation:", meek_closure(partial).to_text())
oracle_independencefn
oracle_independence(graph: CausalGraph) -> Independent
A perfect conditional-independence test, answered by d-separation in a known graph.
pag: PAG = fci(observed, oracle_independence(world))
orientation_gainfn
orientation_gain(graph: CausalGraph, targets: Sequence[Iterable[str]], *, baseline: Sequence[Iterable[str]] = ()) -> tuple[Edge, ...]
The edges an experiment would orient that are undirected without it.
gain = orientation_gain(truth, [target])
v_structuresfn
v_structures(graph: CausalGraph) -> frozenset[tuple[str, str, str]]
``(a, c, b)`` with ``a -> c <- b`` and ``a``, ``b`` non-adjacent; ``a < b``.
print("v-structures of the collider:", v_structures(CausalGraph.from_edges("a -> c, b -> c")))
Declare the number you want as a nine-facet object, then realize it.
Estimandspec
Estimand(name: str, quantity: Quantity, treatment: Treatment, intervention: Intervention, reference: Intervention | None = None, outcome: Outcome, population: Population, window: TimeWindow, level: Level, conditioning: tuple[str, ...] = (), dimension: Dimension, description: str = '')
A named, versioned, content-hashed counterfactual quantity. All facets required.
lift = Estimand(
name="lift_at_100",
quantity=Quantity(kind="contrast"),
treatment=fertilizer,
intervention=Intervention(doses={"fertilizer": 100.0}, version="granular"),
reference=Intervention(doses={"fertilizer": 0.0}, version="granular"),
outcome=yield_total,
population=north,
window=season,
level=Level(unit="cluster", interference="none"),
conditioning=(),
dimension=D.outcome,
description="season-total yield lift from 100 USD of granular fertilizer vs none, north region",
)
EstimandRegistryclass
EstimandRegistry(estimands: Iterable[Estimand] = ()) -> None
Estimands by name, with their content hashes.
registry: EstimandRegistry = standard_estimands(
treatment=spec.treatments[0], outcome=spec.outcome, population=Population(name="all"),
window=TimeWindow(start=0, stop=1), level=Level(unit="individual"), dose=60.0, reference_dose=0.0,
)
EstimandResultspec
EstimandResult(estimand_hash: str, estimand_name: str, kind: Literal['contrast', 'marginal', 'ratio', 'elasticity', 'area'], summary: Summary, dimension: Dimension, unit: str | None = None, status: Literal['identified', 'downgraded', 'blocked', 'unsupported'], identification: Verdict | None = None, assumptions: tuple[Assumption, ...] = (), ledger: tuple[LedgerLine, ...] = (), n_draws: int, producer_hash: str = '', ... and 1 more field)
A realized estimand: the summary, its dimension and unit, and how it was licensed.
if isinstance(asserted, EstimandResult):
status: ResultStatus = asserted.status
print(status, [line.kind for line in asserted.ledger], [a.name for a in asserted.assumptions])
FACETSvalue
Built-in immutable sequence.
print(FACETS)
Facetcallable
Facet(*args, **kwargs)
f: Facet = "intervention"
FacetDiffspec
FacetDiff(facet: Literal['quantity', 'intervention', 'outcome', 'population', 'window', 'level', 'conditioning', 'dimension'], statement: str, assumptions: tuple[Assumption, ...] = (), blocked: Blocked | None = None, corrections: tuple[str, ...] = ())
What one differing facet requires: assumptions, or a block, plus any correction.
entry: FacetDiff = plan.entry("intervention")
Levelspec
Level(unit: Literal['individual', 'cluster', 'aggregate'], interference: Literal['none', 'within_cluster', 'declared'] = 'none', interference_model: str = '')
Unit of analysis and interference model (review B2: SUTVA-2).
lift = Estimand(
name="lift_at_100",
quantity=Quantity(kind="contrast"),
treatment=fertilizer,
intervention=Intervention(doses={"fertilizer": 100.0}, version="granular"),
reference=Intervention(doses={"fertilizer": 0.0}, version="granular"),
outcome=yield_total,
population=north,
window=season,
level=Level(unit="cluster", interference="none"),
conditioning=(),
dimension=D.outcome,
description="season-total yield lift from 100 USD of granular fertilizer vs none, north region",
)
Quantityspec
Quantity(kind: Literal['contrast', 'marginal', 'ratio', 'elasticity', 'area'], scale: Literal['natural', 'log'] = 'natural')
The functional applied to the response.
mroi = lift.model_copy(update={
"name": "marginal_at_100",
"quantity": Quantity(kind="marginal"),
"reference": None,
"dimension": D.outcome / D.currency,
})
QuantityKindcallable
QuantityKind(*args, **kwargs)
kinds: list[QuantityKind] = ["contrast", "marginal", "ratio", "elasticity", "area"]
RealizedDrawsclass
RealizedDraws(result: EstimandResult, draws: Array) -> None
``realize(..., keep_draws=True)``: the result plus the draws it summarizes, flat.
if isinstance(rd, RealizedDraws):
print(rd.draws.shape, rd.result.summary.n)
ResultStatuscallable
ResultStatus(*args, **kwargs)
status: ResultStatus = asserted.status
TransferPlanspec
TransferPlan(status: Literal['identified', 'downgraded', 'blocked', 'unsupported', 'unverified'], source: str, target: str, differing: tuple[Literal['quantity', 'intervention', 'outcome', 'population', 'window', 'level', 'conditioning', 'dimension'], ...], entries: tuple[FacetDiff, ...], assumptions: tuple[Assumption, ...], corrections: tuple[str, ...] = (), ledger_lines: tuple[LedgerLine, ...], reason: str = '')
The typed diff between two estimands and what licenses reading one as the other.
plan: TransferPlan = experiment.transfer_to(national)
check_estimand_dimensionfn
check_estimand_dimension(estimand: Estimand, expr: Expr) -> Dimension
``dimension(expr)``, asserted equal to the estimand's declared dimension (gate 10).
print(dimension(tree), "==", check_estimand_dimension(lift, tree))
derived_dimensionfn
derived_dimension(kind: QuantityKind, outcome: Dimension, dose: Dimension) -> Dimension
print(f"{kind:11s} -> {derived_dimension(kind, D.outcome, D.currency)}")
estimand_exprfn
estimand_expr(estimand: Estimand, *, mean: Expr, treatment_data: Data, reference: Mapping[str, float] | None = None, derivative: Expr | None = None) -> Expr | Unsupported
The estimand as a per-row expression over ``mean``, or a typed ``Unsupported``.
tree = estimand_expr(lift, mean=world.model.mean, treatment_data=Data(name="dose", dimension=D.currency))
evaluatefn
evaluate(estimands: Sequence[Estimand], producer: SupportsEstimands, *, verdicts: Mapping[str, Verdict] | None = None, assume_identified: bool = False, definition: IntervalDefinition = 'hdi', mass: float = 0.9, seed: int | None = None, unit_weights: Mapping[str, float] | None = None, unit_strata: Mapping[str, str] | None = None) -> dict[str, EstimandResult | Unsupported | Blocked]
``realize`` each estimand, keyed by name; ``verdicts`` maps estimand name to its verdict.
results = evaluate(list(registry), res, verdicts={name: verdict for name in registry.names()}, definition="hdi", mass=0.9)
realizefn
realize(estimand: Estimand, producer: SupportsEstimands, *, verdict: Verdict | None = None, assume_identified: bool = False, definition: IntervalDefinition = 'hdi', mass: float = 0.9, seed: int | None = None, unit_weights: Mapping[str, float] | None = None, unit_strata: Mapping[str, str] | None = None, keep_draws: bool = False) -> EstimandResult | RealizedDraws | Unsupported | Blocked
Realize one estimand against a producer; see the module docstring for the arithmetic.
print(realize(lift, crippled))
standard_estimandsfn
standard_estimands(treatment: Treatment, outcome: Outcome, population: Population, window: TimeWindow, level: Level, *, dose: float, reference_dose: float = 0.0, version: str = 'unspecified') -> EstimandRegistry
The five domain-general estimands for one treatment at one dose.
registry: EstimandRegistry = standard_estimands(
treatment=spec.treatments[0], outcome=spec.outcome, population=Population(name="all"),
window=TimeWindow(start=0, stop=1), level=Level(unit="individual"), dose=60.0, reference_dose=0.0,
)
substitutefn
substitute(expr: Expr, replacements: Mapping[str, Expr]) -> Expr
Rebuild ``expr`` with every ``Data`` node named in ``replacements`` swapped out.
pinned = substitute(world.model.mean, {"dose": __import__("axiom.core", fromlist=["Const"]).Const(value=60.0, dimension=D.currency)})
Dose-response kernels, carryover, the one forward(), optimal allocation.
Allocationspec
Allocation(doses: dict[str, float], expected_outcome: float, budget: float, objective: Literal['mean', 'quantile'], q: float = 0.5, method: str, status: Literal['converged'] = 'converged', seed: int | None = None, detail: dict[str, float] = {})
One allocation: a dose per treatment and the objective it achieves.
if isinstance(alloc, Allocation):
print(alloc.doses, round(alloc.expected_outcome, 3), alloc.status, round(alloc.total_dose, 3))
else:
show(alloc)
AllocationMethodcallable
AllocationMethod(*args, **kwargs)
meth: AllocationMethod = "slsqp"
AnyCarryovercallable
AnyCarryover(*args, **kwargs)
Runtime representation of an annotated type.
cc: AnyCarryover = c
AnyKernelcallable
AnyKernel(*args, **kwargs)
Runtime representation of an annotated type.
k: AnyKernel = fam
AnyNuisanceTermcallable
AnyNuisanceTerm(*args, **kwargs)
Runtime representation of an annotated type.
any_term: AnyNuisanceTerm = FourierSeasonality(period=12.0, order=1)
AscentPathspec
AscentPath(treatments: tuple[str, ...], points: tuple[tuple[float, ...], ...], values: tuple[float, ...], stop: Literal['decrease', 'converged', 'boundary', 'n_steps'])
The visited points of a steepest-ascent walk and the surface value at each.
if isinstance(path, AscentPath):
print(path.n, path.stop, path.best(), round(path.values[-1], 3))
else:
show(path)
BASIS_PRIOR_FAMILIESvalue
Build an immutable unordered collection of unique elements.
print("\ncoefficient priors may be signed:", sorted(BASIS_PRIOR_FAMILIES))
BandKindcallable
BandKind(*args, **kwargs)
kind: BandKind = band.kind
Boundsspec
Bounds(treatments: tuple[str, ...], low: tuple[float, ...], high: tuple[float, ...])
The experimental region: a box ``[low_i, high_i]`` per treatment, in dose units.
bounds = Bounds(treatments=("a", "b"), low=(0.0, 0.0), high=(100.0, 40.0))
CARRYOVERSvalue
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
print(sorted(KERNELS), "|", sorted(CARRYOVERS))
CarryoverKernelclass
CarryoverKernel(*args, **kwargs)
A lag-weight family that builds its own expression tree.
geo: CarryoverKernel = GeometricCarryover(max_lag=6)
CarryoverRolecallable
CarryoverRole(*args, **kwargs)
role: CarryoverRole = "shape"
CovarianceFamilycallable
CovarianceFamily(*args, **kwargs)
covariance: CovarianceFamily = gp.covariance
Criterioncallable
Criterion(*args, **kwargs)
crit: Criterion = "d"
DelayedCarryoverspec
DelayedCarryover(name: Literal['delayed'] = 'delayed', max_lag: int)
``w_l ∝ λ^((l − θ)^2)``: a peak at lag ``θ`` with symmetric geometric decay around it.
delayed = DelayedCarryover(max_lag=8)
Designspec
Design(treatments: tuple[str, ...], points: tuple[tuple[float, ...], ...], kind: str, detail: dict[str, float] = {})
A set of dose points (``points[i][j]`` is treatment ``j`` at run ``i``) and its recipe.
ccd: Design = central_composite(bounds, alpha="rotatable", center_points=3)
EventIndicatorsspec
EventIndicators(kind: Literal['events'] = 'events', events: tuple[str, ...], coefficient_scale: float = 1.0)
User-supplied 0/1 columns, one coefficient each.
No executed statement uses this symbol — it appears in the notebooks only inside an import, which is all gate 12 requires. Worth a worked line.
ExponentialKernelspec
ExponentialKernel(name: Literal['exponential'] = 'exponential', reference_dose: float = 1.0, amplitude_scale: float = 1.0, amplitude_prior: Prior | None = None)
``f(u) = 1 − exp(−u)``, ``u = dose / k``. Saturating; ``f(0) = 0``; concave everywhere.
for fam in (LogisticKernel(reference_dose=50.0), ExponentialKernel(reference_dose=50.0), PowerKernel(reference_dose=50.0), LinearKernel(reference_dose=50.0)):
k: AnyKernel = fam
names = [p.name for p in k.parameters("a", D.currency, D.outcome)]
print(f"{k.name:12s} saturating={k.saturating!s:5s} params={names}")
FitResultclass
FitResult(surface: Surface, posterior: Posterior | Unverified | Unsupported, data: Mapping[str, NDArray[Any]], report: ConvergenceReport | None, provenance: dict[str, Any], dropped: frozenset[Capability] = frozenset(), declared: tuple[str, ...] = ()) -> None
What ``fit`` returns: the surface, its posterior (or a typed failure), and the record.
result: FitResult = fit(world.spec, world.panel, backend="laplace", draws=2000, seed=0)
FourierSeasonalityspec
FourierSeasonality(kind: Literal['seasonality'] = 'seasonality', period: float, order: int, coefficient_scale: float = 1.0)
``sin(2πk t / period)``, ``cos(2πk t / period)`` for ``k = 1 … order``.
any_term: AnyNuisanceTerm = FourierSeasonality(period=12.0, order=1)
Frontierspec
Frontier(budgets: tuple[float, ...], outcomes: tuple[float, ...], allocations: tuple[Allocation, ...])
Optimal outcome per budget: ``budgets`` ascend; ``allocations[i]`` reaches ``outcomes[i]``.
if isinstance(fr, Frontier):
print(fr.as_frame())
print("shadow prices:", np.round(fr.shadow_prices(), 4))
else:
show(fr)
GRID_VERSIONvalue
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
print("predict marks a dose grid with version", repr(GRID_VERSION))
GaussianProcessKernelspec
GaussianProcessKernel(name: Literal['gaussian_process'] = 'gaussian_process', reference_dose: float = 1.0, amplitude_scale: float = 1.0, amplitude_prior: Prior | None = None, n_basis: int = 24, boundary_factor: float = 3.0, covariance: Literal['squared_exponential', 'matern32', 'matern52'] = 'squared_exponential', lengthscale_median: float = 0.3, lengthscale_spread: float = 0.5)
A Gaussian process over the dose axis, as its Hilbert-space basis approximation.
gp = GaussianProcessKernel(reference_dose=40.0, amplitude_scale=8.0)
GeometricCarryoverspec
GeometricCarryover(name: Literal['geometric'] = 'geometric', max_lag: int)
``w_l ∝ λ^l``: exponential decay from lag zero. ``λ ~ beta(2, 2)`` on (0, 1).
geo: CarryoverKernel = GeometricCarryover(max_lag=6)
HillKernelspec
HillKernel(name: Literal['hill'] = 'hill', reference_dose: float = 1.0, amplitude_scale: float = 1.0, amplitude_prior: Prior | None = None)
``f(u) = u^s / (1 + u^s)``, ``u = dose / k``. Saturating; ``f(0) = 0``, ``f(1) = 1/2``.
hill: ResponseKernel = HillKernel(reference_dose=50.0, amplitude_scale=10.0)
InterceptKindcallable
InterceptKind(*args, **kwargs)
kind: InterceptKind = "hierarchical"
KERNELSvalue
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
print(sorted(KERNELS), "|", sorted(CARRYOVERS))
KernelRolecallable
KernelRole(*args, **kwargs)
role: KernelRole = hill.roles[p.name.rsplit("_", 1)[0]] if p.name.rsplit("_", 1)[0] in hill.roles else "shape"
LinearKernelspec
LinearKernel(name: Literal['linear'] = 'linear', reference_dose: float = 1.0, amplitude_scale: float = 1.0, amplitude_prior: Prior | None = None)
``response = beta_rate · dose``. No scale, no shape; never saturates.
for fam in (LogisticKernel(reference_dose=50.0), ExponentialKernel(reference_dose=50.0), PowerKernel(reference_dose=50.0), LinearKernel(reference_dose=50.0)):
k: AnyKernel = fam
names = [p.name for p in k.parameters("a", D.currency, D.outcome)]
print(f"{k.name:12s} saturating={k.saturating!s:5s} params={names}")
LinearTrendspec
LinearTrend(kind: Literal['trend'] = 'trend', origin: float | None = None, scale: float | None = None, coefficient_scale: float = 1.0)
``trend = (t − origin) / scale``: one dimensionless basis column.
term: NuisanceTerm = LinearTrend()
LogisticKernelspec
LogisticKernel(name: Literal['logistic'] = 'logistic', reference_dose: float = 1.0, amplitude_scale: float = 1.0, amplitude_prior: Prior | None = None)
``f(u) = 2 / (1 + exp(-u)) − 1 = tanh(u / 2)``, ``u = dose / k``. Saturating; ``f(0) = 0``.
for fam in (LogisticKernel(reference_dose=50.0), ExponentialKernel(reference_dose=50.0), PowerKernel(reference_dose=50.0), LinearKernel(reference_dose=50.0)):
k: AnyKernel = fam
names = [p.name for p in k.parameters("a", D.currency, D.outcome)]
print(f"{k.name:12s} saturating={k.saturating!s:5s} params={names}")
MarginalHorizoncallable
MarginalHorizon(*args, **kwargs)
horizon: MarginalHorizon = "shift"
Methodcallable
Method(*args, **kwargs)
method: Method = "auto"
NoCarryoverspec
NoCarryover(name: Literal['none'] = 'none')
The identity: all weight at lag zero. ``apply`` returns the dose expression itself.
for c in (DelayedCarryover(max_lag=6), WeibullCarryover(max_lag=6), NoCarryover()):
cc: AnyCarryover = c
print(cc.name, [p.name for p in cc.parameters("a")])
NuisanceSetspec
NuisanceSet(kind: Literal['set'] = 'set', terms: tuple[FourierSeasonality | LinearTrend | EventIndicators, ...])
Several nuisance terms with one ``augment`` / ``expr`` / ``parameters``.
trend_spec = spec.model_copy(update={"nuisance": NuisanceSet(terms=(LinearTrend(),)), "carryover": {}})
NuisanceTermclass
NuisanceTerm(*args, **kwargs)
A baseline term: basis columns in the frame, coefficients in the tree.
term: NuisanceTerm = LinearTrend()
Objectivecallable
Objective(*args, **kwargs)
obj: Objective = "mean"
ParameterRolecallable
ParameterRole(*args, **kwargs)
roles: dict[str, ParameterRole] = parameter_roles(spec)
PiecewiseLinearKernelspec
PiecewiseLinearKernel(name: Literal['piecewise_linear'] = 'piecewise_linear', reference_dose: float = 1.0, amplitude_scale: float = 1.0, amplitude_prior: Prior | None = None, knots: tuple[float, ...] = (0.5,))
A **linear spline** in ``u = dose / reference_dose``: a broken stick on fixed knots.
broken = PiecewiseLinearKernel(reference_dose=40.0, knots=(15.0, 25.0))
PolynomialKernelspec
PolynomialKernel(name: Literal['polynomial'] = 'polynomial', reference_dose: float = 1.0, amplitude_scale: float = 1.0, amplitude_prior: Prior | None = None, degree: int = 3)
``response = Σ_{j=1..degree} beta_j · u^j``, ``u = dose / reference_dose``.
poly = PolynomialKernel(reference_dose=40.0, degree=3)
PowerKernelspec
PowerKernel(name: Literal['power'] = 'power', reference_dose: float = 1.0, amplitude_scale: float = 1.0, amplitude_prior: Prior | None = None)
``f(u) = u^s``, ``u = dose / k``. **Not saturating**: unbounded as the dose grows.
for fam in (LogisticKernel(reference_dose=50.0), ExponentialKernel(reference_dose=50.0), PowerKernel(reference_dose=50.0), LinearKernel(reference_dose=50.0)):
k: AnyKernel = fam
names = [p.name for p in k.parameters("a", D.currency, D.outcome)]
print(f"{k.name:12s} saturating={k.saturating!s:5s} params={names}")
ResponseBandspec
ResponseBand(treatment: str, outcome: str, kind: Literal['response', 'marginal'], doses: tuple[float, ...], mean: tuple[float, ...], median: tuple[float, ...], lower: tuple[float, ...], upper: tuple[float, ...], definition: Literal['eti', 'hdi', 'wald'], mass: float, n_draws: int, dimension: Dimension, ... and 2 more fields)
A dose grid, the curve on it, and the interval around the curve at every point.
assert isinstance(band, ResponseBand)
ResponseKernelclass
ResponseKernel(*args, **kwargs)
A dose-response family that builds its own expression tree.
hill: ResponseKernel = HillKernel(reference_dose=50.0, amplitude_scale=10.0)
SplineKernelspec
SplineKernel(name: Literal['spline'] = 'spline', reference_dose: float = 1.0, amplitude_scale: float = 1.0, amplitude_prior: Prior | None = None, knots: tuple[float, ...] = (0.25, 0.5, 0.75))
A **natural cubic spline** in ``u = dose / reference_dose``, on fixed knots.
spline = SplineKernel(reference_dose=40.0, knots=(8.0, 16.0, 24.0, 32.0))
StationaryPointspec
StationaryPoint(treatments: tuple[str, ...], origin: tuple[float, ...], point: tuple[float, ...], value: float, kind: Literal['maximum', 'minimum', 'saddle'], gradient: tuple[float, ...], eigenvalues: tuple[float, ...], eigenvectors: tuple[tuple[float, ...], ...])
Canonical analysis at ``origin``: the stationary point of the local quadratic and its shape.
No executed statement uses this symbol — it appears in the notebooks only inside an import, which is all gate 12 requires. Worth a worked line.
SupportsBandsclass
SupportsBands(*args, **kwargs)
What a band needs from a fit: draws under an intervention, and the fitted grid.
print("a fit satisfies the band protocol:", isinstance(result, SupportsBands))
Surfaceclass
Surface(spec: SurfaceSpec, *, provenance: Mapping[str, Any] | None = None) -> None
A ``SurfaceSpec`` with its built model: the ``SupportsForward`` the upper layers use.
surface = Surface(spec)
SurfaceSpecspec
SurfaceSpec(name: str, treatments: tuple[Treatment, ...], outcome: Outcome, kernels: dict[str, HillKernel | LogisticKernel | ExponentialKernel | PowerKernel | LinearKernel | PolynomialKernel | SplineKernel | PiecewiseLinearKernel | GaussianProcessKernel] = {}, carryover: dict[str, GeometricCarryover | DelayedCarryover | WeibullCarryover | NoCarryover] = {}, nuisance: NuisanceSet = NuisanceSet(kind='set', terms=()), intercept: Literal['none', 'shared', 'per_unit', 'hierarchical'] = 'shared', interactions: tuple[tuple[str, str], ...] = (), likelihood: Likelihood = ..., time_column: str = 't', unit_column: str = 'unit', unit_labels: tuple[str, ...] = (), ... and 3 more fields)
A response surface over named treatments: what is fit, declared once.
spec = SurfaceSpec(
name="two_treatment_panel",
treatments=(Treatment(name="a", dimension=D.currency, unit="USD"), Treatment(name="b", dimension=D.currency, unit="USD")),
outcome=Outcome(name="y", dimension=D.outcome, unit="units"),
kernels={"a": HillKernel(reference_dose=50.0), "b": HillKernel(reference_dose=20.0)},
carryover={"a": GeometricCarryover(max_lag=4)},
nuisance=NuisanceSet(terms=(FourierSeasonality(period=12.0, order=1),)),
intercept=kind,
interactions=(("a", "b"),),
likelihood=Likelihood(family="normal", scale="sigma"),
unit_labels=("u0", "u1", "u2"),
)
WeibullCarryoverspec
WeibullCarryover(name: Literal['weibull'] = 'weibull', max_lag: int)
``w_l ∝ exp(−(l / λ)^κ)``: the Weibull survival function over lags.
for c in (DelayedCarryover(max_lag=6), WeibullCarryover(max_lag=6), NoCarryover()):
cc: AnyCarryover = c
print(cc.name, [p.name for p in cc.parameters("a")])
a_criterionfn
a_criterion(X: DesignMatrix | ArrayLike) -> float
``-trace((X'X)^-1)`` (larger is better); ``-inf`` when singular.
print("D:", round(d_criterion(X), 3), "A:", round(a_criterion(X), 3), "E:", round(e_criterion(X), 4))
allocatefn
allocate(surface: SupportsForward, posterior_or_theta: SupportsPosterior | Theta | Failure, *, budget: float, bounds: Bounds, objective: Objective = 'mean', q: float = 0.5, seed: int | None = None, method: AllocationMethod = 'slsqp', start: Mapping[str, float] | None = None, n_starts: int = 1, n_draws: int | None = None, maxiter: int = 200, tol: float = 1e-12, n_grid: int = 101, context: Context | None = None) -> Allocation | Failure
Maximize the posterior-expected outcome subject to ``Σ dose ≤ budget`` and ``bounds``.
alloc = allocate(surface, theta, budget=60.0, bounds=bounds, objective=obj, method=meth, seed=0)
bayesian_criterionfn
bayesian_criterion(surface: SupportsForward, design: Design, theta_draws: Sequence[Mapping[str, ArrayLike]], criterion: Criterion = 'd') -> float
Mean over ``theta_draws`` of the criterion on ``surface.linearize(design, theta).X``.
print("naive :", round(bayesian_criterion(surface, naive, prior_draws), 3))
box_behnkenfn
box_behnken(bounds: Bounds, *, center_points: int = 3) -> Design
The published Box-Behnken design for ``k`` in 3..7: blocked two-level factorials.
print("box-behnken k=3:", box_behnken(b3, center_points=2).n)
buildfn
build(spec: SurfaceSpec) -> ModelSpec
The ``ModelSpec`` a backend fits: ``mean = intercept + Σ_i beta_i f_i(carry_i(dose_i)) + Σ gamma_ij f_i f_j + nuisance``, every parameter with its prior, dimension-checked.
model = build(spec)
canonical_analysisfn
canonical_analysis(surface: SupportsForward, theta: Theta, x0: Mapping[str, float], *, method: Method = 'auto', tol: float = 1e-08, bounds: Bounds | None = None) -> StationaryPoint | Unsupported
Second-order expansion at ``x0``: stationary point ``x0 - H^-1 g`` and its nature.
sp = canonical_analysis(surface, theta, {"a": 30.0, "b": 10.0}, method=method)
carryover_from_namefn
carryover_from_name(name: str, **fields: Any) -> CarryoverKernel
Build a carryover by family name, e.g. ``carryover_from_name("geometric", max_lag=8)``.
print(carryover_from_name("geometric", max_lag=4))
central_compositefn
central_composite(bounds: Bounds, *, alpha: float | Literal['rotatable', 'face'] = 'rotatable', center_points: int = 4, fraction: int | None = None, inscribed: bool = False) -> Design
Cube (2^k, or 2^(k-p) with ``fraction=p``), 2k axial points at ``±alpha``, centre runs.
ccd: Design = central_composite(bounds, alpha="rotatable", center_points=3)
check_linearizationfn
check_linearization(surface: SupportsForward, dose: Mapping[str, ArrayLike], theta: Mapping[str, ArrayLike]) -> float
``‖X @ theta_lin + offset − forward(dose, theta)‖∞`` — the gate's number, for one point.
print("invariant:", check_linearization(ws, world.data, world.theta))
column_namesfn
column_names(model: ModelSpec, linear: Sequence[str]) -> tuple[str, ...]
One column per scalar entry of every linear parameter, in ``linear`` order.
print(term.column_names("n0_"), any_term.column_names("n1_"))
counterfactual_dosesfn
counterfactual_doses(surface: Surface, data: Mapping[str, ArrayLike], iv: Intervention) -> dict[str, NDArray[Any]]
The fitted data dict with ``iv`` applied to the treatments it names.
cf = counterfactual_doses(surface, world.data, Intervention(doses={"a": 2.0}, mode="scale", window=TimeWindow(start=5, stop=10)))
d_criterionfn
d_criterion(X: DesignMatrix | ArrayLike) -> float
``log det(X'X)``; ``-inf`` when the information matrix is singular.
print("D:", round(d_criterion(X), 3), "A:", round(a_criterion(X), 3), "E:", round(e_criterion(X), 4))
defining_relationfn
defining_relation(generators: Sequence[str]) -> tuple[str, ...]
The words of ``I = ...`` implied by two-level generators, e.g. ``("ABCD",)`` for ``D=ABC``.
print("2^(4-1):", ff.n, ff.detail, defining_relation(("D=ABC",)))
design_matrixfn
design_matrix(model: ModelSpec, linear: Sequence[str], dose: Mapping[str, ArrayLike], theta_at: Mapping[str, ArrayLike]) -> DesignMatrix
``mu = offset + X @ theta[columns]`` for ``model.mean`` at ``theta_at``.
print(design_matrix(ws.model, ws.linear, world.data, world.theta).at.keys())
e_criterionfn
e_criterion(X: DesignMatrix | ArrayLike) -> float
Smallest eigenvalue of ``X'X``; ``0`` when singular (a zero eigenvalue, not a guess).
print("D:", round(d_criterion(X), 3), "A:", round(a_criterion(X), 3), "E:", round(e_criterion(X), 4))
equal_spacingfn
equal_spacing(bounds: Bounds, n_per_treatment: int) -> Design
The naive comparator: a regular grid with ``n_per_treatment`` levels on every axis.
print("LHS:", lhs.n, "| equal spacing:", equal_spacing(bounds, 4).n)
fitfn
fit(spec: SurfaceSpec, panel: Panel, *, backend: Backend | str = 'laplace', draws: int = 1000, tune: int = 1000, chains: int = 4, seed: int | None = None) -> FitResult
Prepare the panel, sample the built model through ``backend``, and record everything.
result: FitResult = fit(world.spec, world.panel, backend="laplace", draws=2000, seed=0)
forwardfn
forward(surface: SupportsForward, dose: Mapping[str, ArrayLike], theta: Mapping[str, ArrayLike]) -> Array
``value(surface.expr, data=dose, params=theta)`` — the one forward().
mu = forward(ws, world.data, world.theta)
fractional_factorialfn
fractional_factorial(bounds: Bounds, generators: tuple[str, ...]) -> Design
A two-level 2^(k-p) fraction from generators like ``("D=ABC",)``; A is the first treatment.
ff = fractional_factorial(Bounds(treatments=("A", "B", "C", "D"), low=(0.0,) * 4, high=(1.0,) * 4), generators=("D=ABC",))
frontierfn
frontier(surface: SupportsForward, posterior_or_theta: SupportsPosterior | Theta | Failure, budgets: Sequence[float], bounds: Bounds, *, objective: Objective = 'mean', q: float = 0.5, seed: int | None = None, method: AllocationMethod = 'slsqp', n_starts: int = 1, n_draws: int | None = None, maxiter: int = 200, tol: float = 1e-12, n_grid: int = 101, context: Context | None = None) -> Frontier | Failure
``allocate`` at every budget (sorted ascending), each warm-started from the last.
fr = frontier(surface, theta, budgets=[10.0, 30.0, 60.0, 120.0], bounds=bounds, seed=0)
full_factorialfn
full_factorial(bounds: Bounds, levels: int | tuple[int, ...]) -> Design
Every combination of equally spaced levels (``levels`` per treatment, or one count).
print("3^2 factorial:", full_factorial(bounds, 3).n)
gradientfn
gradient(surface: SupportsForward, theta: Theta, x: Mapping[str, float], *, method: Method = 'auto', bounds: Bounds | None = None) -> dict[str, float] | Unsupported
``d forward / d dose`` at ``x``, keyed like ``x``; the key order fixes treatment order.
print(gradient(surface, theta, {"a": 30.0, "b": 10.0}, method=method))
hessianfn
hessian(surface: SupportsForward, theta: Theta, x: Mapping[str, float], *, method: Method = 'auto', bounds: Bounds | None = None) -> Array | Unsupported
Second derivatives of ``forward`` at ``x``; rows and columns follow ``x``'s key order.
print(np.round(hessian(surface, theta, {"a": 30.0, "b": 10.0}, method=method), 5))
interaction_namefn
interaction_name(first: str, second: str) -> str
The parameter name of the pairwise interaction ``gamma`` between two treatments.
print(interaction_name("a", "b"), "|", dimension(model.mean))
kernel_from_namefn
kernel_from_name(name: str, **fields: Any) -> ResponseKernel
Build a kernel by family name, e.g. ``kernel_from_name("hill", reference_dose=50.0)``.
print(kernel_from_name("hill", reference_dose=10.0))
latin_hypercubefn
latin_hypercube(bounds: Bounds, n: int, *, seed: int | None = None, centered: bool = False, n_restarts: int = 10) -> Design
``n`` points with one per stratum per treatment; maximin over ``n_restarts`` draws.
lhs = latin_hypercube(bounds, 12, seed=0)
linear_coefficientsfn
linear_coefficients(columns: Sequence[str], theta: Mapping[str, ArrayLike]) -> Array
The vector ``theta[columns]``: scalars by name, vector entries by ``name[i]``.
print(column_names(ws.model, ws.linear)[:4], linear_coefficients(dm.columns, world.theta)[:4].round(3))
marginalfn
marginal(surface: Surface, theta: Mapping[str, ArrayLike], dose: Mapping[str, ArrayLike], treatment: str) -> Array
``w_0 · ∂mu_t/∂c_t``: the effect of one more unit of dose in period ``t`` on outcome ``t``.
print(marginal(surface, world.theta, world.data, "a")[0, :4].round(4), marginal_total(surface, world.theta, world.data, "a")[0, :4].round(4))
marginal_bandfn
marginal_band(result: SupportsBands, treatment: str, *, doses: Sequence[float] | None = None, n_grid: int = 25, mass: float = 0.9, definition: IntervalDefinition = 'eti', window: TimeWindow | None = None, seed: int | None = None) -> ResponseBand | Unsupported
``d outcome / d dose`` against dose, with the posterior interval at every point.
slope = marginal_band(result, "a", n_grid=9, mass=0.9)
marginal_exprfn
marginal_expr(surface: Surface, treatment: str) -> Expr
``∂mu / ∂c`` with respect to a treatment's *carried* dose, as a tree.
print(dimension(marginal_expr(surface, "a")))
marginal_totalfn
marginal_total(surface: Surface, theta: Mapping[str, ArrayLike], dose: Mapping[str, ArrayLike], treatment: str) -> Array
``Σ_l w_l · ∂mu_{t+l}/∂c_{t+l}``: the total effect of one more unit in period ``t``.
print(marginal(surface, world.theta, world.data, "a")[0, :4].round(4), marginal_total(surface, world.theta, world.data, "a")[0, :4].round(4))
optimal_exchangefn
optimal_exchange(surface: SupportsForward, candidates: Design, n: int, theta_draws: Sequence[Mapping[str, ArrayLike]], *, criterion: Criterion = 'd', seed: int | None = None, max_passes: int = 20, n_restarts: int = 3) -> Design | Unsupported
Fedorov point exchange: the best ``n`` candidate rows (replicates allowed) by the criterion.
opt = optimal_exchange(surface, candidates, n=naive.n, theta_draws=prior_draws, criterion=crit, seed=0)
parameter_rolesfn
parameter_roles(spec: SurfaceSpec) -> dict[str, ParameterRole]
``{parameter name: "linear" | "nonlinear" | "auxiliary"}`` in declaration order.
roles: dict[str, ParameterRole] = parameter_roles(spec)
predictfn
predict(surface: Surface, posterior: SupportsPosterior, dose: Mapping[str, ArrayLike], *, seed: int | None = None, noise: bool = False) -> PredictiveDraws
Every draw pushed through ``forward``: ``values`` is ``(chain, draw, *forward shape)``.
print(np.allclose(dm.predict(world.theta).reshape(mu.shape), mu))
predict_marginalfn
predict_marginal(surface: Surface, posterior: SupportsPosterior, dose: Mapping[str, ArrayLike], treatment: str, *, horizon: MarginalHorizon = 'total', support: TimeWindow | None = None, seed: int | None = None) -> PredictiveDraws
Every draw pushed through ``marginal`` / ``marginal_total`` / ``marginal_shift``.
pm = predict_marginal(surface, post, world.data, "a", horizon=horizon, support=TimeWindow(start=5, stop=10))
preparefn
prepare(spec: SurfaceSpec, panel: Panel, *, conventions: Conventions | None = None) -> DataDict
The data dict the built model reads, laid out ``(n_units, n_periods)``.
data = prepare(world.spec, world.panel)
resolve_conventionsfn
resolve_conventions(spec: SurfaceSpec, panel: Panel, conventions: Conventions | None = None) -> dict[str, dict[str, float]]
The trend conventions ``prepare`` lays the panel out with, one entry per trend column.
conv = resolve_conventions(world_t.spec, world_t.panel)
response_bandfn
response_band(result: SupportsBands, treatment: str, *, doses: Sequence[float] | None = None, n_grid: int = 25, mass: float = 0.9, definition: IntervalDefinition = 'eti', window: TimeWindow | None = None, seed: int | None = None) -> ResponseBand | Unsupported
Expected outcome against dose, with the posterior interval at every grid point.
band = response_band(result, "a", n_grid=9, mass=0.9, definition="eti")
steepest_ascentfn
steepest_ascent(surface: SupportsForward, theta: Theta, start: Mapping[str, float], *, step: float, n_steps: int, bounds: Bounds | None = None, method: Method = 'auto', recompute: bool = False) -> AscentPath | Unsupported
Walk ``step`` dose units per move up the gradient until the value stops rising.
path = steepest_ascent(surface, theta, {"a": 10.0, "b": 5.0}, step=5.0, n_steps=20, bounds=bounds)
Synthetic worlds with known truth, so every claim has a recovery test.
DoseDistributioncallable
DoseDistribution(*args, **kwargs)
dist: DoseDistribution = "lognormal"
DosePlanspec
DosePlan(distribution: Literal['lognormal', 'uniform'] = 'lognormal', scale: float = 1.0, spread: float = 0.5, zero_fraction: float = 0.0)
How doses are drawn for one treatment: a family, a scale, a spread, and a zero share.
plan = DosePlan(distribution=dist, scale=50.0, spread=0.6, zero_fraction=0.1)
Horizoncallable
Horizon(*args, **kwargs)
horizon: Horizon = "total"
LinearSCMspec
LinearSCM(graph: CausalGraph, coefficients: dict[str, float] = {}, noise_sd: dict[str, float] = {}, latent_sd: dict[str, float] = {}, intercepts: dict[str, float] = {})
A linear-Gaussian SCM over a ``CausalGraph`` with optional latent confounding.
scm = LinearSCM.from_text("Z -> X: 0.8, Z -> Y: 1.5, X -> Y: 2.0, X <-> Y: 0.7", name="demo")
SCMErrorerror
The structural model is malformed (missing coefficient, unknown node, bad key).
try:
LinearSCM.from_text("X -> Y: 1.0, X -> Y: 2.0")
except SCMError as e:
print("refused:", e)
SurfaceWorldclass
SurfaceWorld(spec: SurfaceSpec, model: ModelSpec, theta: dict[str, Array], panel: Panel, data: dict[str, Array], mean: Array, seed: int | None, noise_sd: float | None, provenance: dict[str, Any] = <factory>) -> None
A ``SurfaceSpec``, its true parameters, and a balanced panel generated from them.
world: SurfaceWorld = surface_world(
n_units=4, n_periods=26, treatments=("a", "b"),
kernels={"a": HillKernel(reference_dose=50.0), "b": HillKernel(reference_dose=20.0)},
carryover={"a": GeometricCarryover(max_lag=4)},
doses={"a": plan, "b": DosePlan(scale=20.0, spread=0.4)},
nuisance=NuisanceSet(terms=(FourierSeasonality(period=13.0, order=1),)),
intercept="hierarchical", truth_mode=mode, noise_sd=0.5, seed=3,
)
TruthModecallable
TruthMode(*args, **kwargs)
mode: TruthMode = "centre"
arms_worldfn
arms_world(*, n_units: int, treatments: Sequence[Treatment | str] = ('dose',), kernels: Mapping[str, Kernel] | Kernel | None = None, doses: Mapping[str, DosePlan] | DosePlan | Mapping[str, ArrayLike] | None = None, outcome: Outcome | None = None, truth: Mapping[str, ArrayLike] | None = None, truth_mode: TruthMode = 'centre', noise_sd: float | None = None, intercept_scale: float = 1.0, seed: int | None = None, name: str = 'arms_world') -> SurfaceWorld
The single-period world: one row per unit, a shared intercept, no carryover.
arms = arms_world(n_units=12, treatments=("dose",), doses={"dose": np.linspace(0, 100, 12)}, seed=0)
coefficient_keyfn
coefficient_key(a: str, b: str) -> str
The ``coefficients`` key for the directed edge ``a -> b``: ``"a->b"``.
print(coefficient_key("Z", "X"), latent_key("Y", "X"))
confounded_worldfn
confounded_world() -> LinearSCM
``Z -> X`` (0.8), ``Z -> Y`` (1.5), ``X -> Y`` (2.0); everything measured.
for w in (confounded_world, hidden_confounder_world, iv_world, frontdoor_world, mediator_world, feedback_world):
s = w()
print(f"{w.__name__:24s} {s.graph.to_text():40s} truth={s.total_effect('X', 'Y'):.2f} unmeasured={s.graph.unmeasured} feedback={s.graph.feedback}")
draw_dosesfn
draw_doses(plan: DosePlan, n_units: int, n_periods: int, *, seed: int | None = None) -> Array
``(n_units, n_periods)`` doses from ``plan``; deterministic given ``seed``.
print(draw_doses(plan, 2, 6, seed=0).round(1))
feedback_worldfn
feedback_world() -> LinearSCM
The confounded world's summary graph, flagged ``feedback=True``.
for w in (confounded_world, hidden_confounder_world, iv_world, frontdoor_world, mediator_world, feedback_world):
s = w()
print(f"{w.__name__:24s} {s.graph.to_text():40s} truth={s.total_effect('X', 'Y'):.2f} unmeasured={s.graph.unmeasured} feedback={s.graph.feedback}")
frontdoor_worldfn
frontdoor_world() -> LinearSCM
``X -> M`` (1.2), ``M -> Y`` (1.5), ``X <-> Y`` (latent scale 1.0).
for w in (confounded_world, hidden_confounder_world, iv_world, frontdoor_world, mediator_world, feedback_world):
s = w()
print(f"{w.__name__:24s} {s.graph.to_text():40s} truth={s.total_effect('X', 'Y'):.2f} unmeasured={s.graph.unmeasured} feedback={s.graph.feedback}")
hidden_confounder_worldfn
hidden_confounder_world() -> LinearSCM
The confounded world with ``Z`` unmeasured.
print(scm.observed(frame).columns.tolist(), "| unmeasured dropped:", hidden_confounder_world().observed(hidden_confounder_world().simulate(3, seed=0)).columns.tolist())
iv_worldfn
iv_world() -> LinearSCM
``Z -> X`` (1.0), ``X -> Y`` (2.0), ``X <-> Y`` (latent scale 1.0).
for w in (confounded_world, hidden_confounder_world, iv_world, frontdoor_world, mediator_world, feedback_world):
s = w()
print(f"{w.__name__:24s} {s.graph.to_text():40s} truth={s.total_effect('X', 'Y'):.2f} unmeasured={s.graph.unmeasured} feedback={s.graph.feedback}")
latent_keyfn
latent_key(a: str, b: str) -> str
The ``latent_sd`` key for the bidirected edge ``a <-> b``: ``"a<->b"`` with ``a < b``.
print(coefficient_key("Z", "X"), latent_key("Y", "X"))
long_framefn
long_frame(columns: Mapping[str, ArrayLike], *, units: Sequence[str], periods: Sequence[int] | None = None, unit_column: str = 'unit', time_column: str = 't') -> DataFrame
Stack ``(n_units, n_periods)`` arrays into a unit-major, period-minor long table.
frame = long_frame({"x": [[1.0, 2.0], [3.0, 4.0]], "y": [[0.5, 0.6], [0.7, 0.8]]}, units=("u0", "u1"))
mediator_worldfn
mediator_world() -> LinearSCM
``X -> M`` (1.0), ``M -> Y`` (0.5), ``X -> Y`` (1.0); everything measured, no confounding.
for w in (confounded_world, hidden_confounder_world, iv_world, frontdoor_world, mediator_world, feedback_world):
s = w()
print(f"{w.__name__:24s} {s.graph.to_text():40s} truth={s.total_effect('X', 'Y'):.2f} unmeasured={s.graph.unmeasured} feedback={s.graph.feedback}")
panel_from_arraysfn
panel_from_arrays(*, doses: Mapping[str, ArrayLike], outcome: ArrayLike, treatments: Sequence[Treatment], outcome_entity: Outcome, covariates: Mapping[str, tuple[Covariate, ArrayLike]] | None = None, units: Sequence[str], periods: Sequence[int] | None = None, unit_column: str = 'unit', time_column: str = 't') -> Panel
A balanced ``Panel`` from wide arrays plus the entities that give the columns roles.
panel = panel_from_arrays(
doses={"x": [[1.0, 2.0], [3.0, 4.0]]}, outcome=[[0.5, 0.6], [0.7, 0.8]],
treatments=[Treatment(name="x", dimension=D.currency, unit="USD")],
outcome_entity=Outcome(name="y", dimension=D.outcome), units=("u0", "u1"),
)
surface_worldfn
surface_world(*, n_units: int, n_periods: int, treatments: Sequence[Treatment | str] = ('a', 'b'), kernels: Mapping[str, Kernel] | Kernel | None = None, carryover: Mapping[str, Carryover] | Carryover | None = None, doses: Mapping[str, DosePlan] | DosePlan | None = None, nuisance: NuisanceSet | None = None, intercept: InterceptKind = 'hierarchical', interactions: Sequence[tuple[str, str]] = (), outcome: Outcome | None = None, likelihood: Likelihood | None = None, truth: Mapping[str, ArrayLike] | None = None, truth_mode: TruthMode = 'centre', noise_sd: float | None = None, intercept_scale: float = 1.0, seed: int | None = None, name: str = 'surface_world') -> SurfaceWorld
A units × periods panel world with saturation, carryover, intercepts, and nuisance.
world: SurfaceWorld = surface_world(
n_units=4, n_periods=26, treatments=("a", "b"),
kernels={"a": HillKernel(reference_dose=50.0), "b": HillKernel(reference_dose=20.0)},
carryover={"a": GeometricCarryover(max_lag=4)},
doses={"a": plan, "b": DosePlan(scale=20.0, spread=0.4)},
nuisance=NuisanceSet(terms=(FourierSeasonality(period=13.0, order=1),)),
intercept="hierarchical", truth_mode=mode, noise_sd=0.5, seed=3,
)
transport_pairfn
transport_pair() -> tuple[LinearSCM, LinearSCM]
A source and a target population that differ only in ``Z``'s distribution.
src, tgt = transport_pair()
true_parametersfn
true_parameters(model: ModelSpec, *, structural: Sequence[str] = (), truth: Mapping[str, ArrayLike] | None = None, mode: TruthMode = 'centre', seed: int | None = None) -> dict[str, Array]
A full true-parameter dictionary for ``model`` (see the module docstring for the rules).
theta_prior = true_parameters(world.model, structural=world.structural, mode="prior", seed=11)
unit_labelsfn
unit_labels(n_units: int, prefix: str = 'u') -> tuple[str, ...]
``("u00", "u01", …)`` — zero-padded so that string order is numeric order.
print(unit_labels(3))
Power, MDE, value of information, method leaderboards, sequential boundaries.
ASSUMPTIONSvalue
Read-only proxy of a mapping.
print("assumption 'parallel_trends':", ASSUMPTIONS["parallel_trends"].statement)
AnchoredEffectspec
AnchoredEffect(parameter: str, posterior_mean: float, posterior_sd: float, posterior_interval: Interval, n_draws: int, mde: float, probability_exceeds_mde: float, probability_exceeds_mde_gaussian: float, credence: float, anchored_effect: float, already_believed: bool)
A posterior's view of an MDE.
assert isinstance(ae, AnchoredEffect)
Assignmentspec
Assignment(unit: Unit, labels: tuple[str, ...], treated: tuple[int, ...], control: tuple[int, ...], pairs: tuple[tuple[int, int], ...], unpaired: tuple[int, ...], metric: Literal['level', 'trajectory'], seed: int | None, pre_mean_treated: float, pre_mean_control: float, pre_smd: float, mean_pair_distance: float)
A matched-pair treatment assignment of clusters.
asg: Assignment = match_clusters(pre, labels=[f"r{i}" for i in range(9)], unit=region, metric=metric, seed=1)
Boundaryspec
Boundary(kind: Literal['efficacy', 'harm', 'futility'], side: Literal['upper', 'lower', 'two_sided'], z: tuple[float, ...], binding: bool = True, spent: tuple[float, ...] = (), detail: dict[str, str] = {})
Signed thresholds on the monitoring statistic, one per look.
futility = Boundary(kind="futility", side="lower", z=(-1.5, -0.8, -0.2, 0.3, 0.8), binding=False)
BoundaryKindcallable
BoundaryKind(*args, **kwargs)
kind: BoundaryKind = with_margin.kind
CANONICALvalue
A named, falsifiable condition that licenses a step.
print("named assumptions:", CANONICAL.name, "|", STOPPED_ESTIMATE_BIAS.name)
CalibrationResultspec
CalibrationResult(method: str, design: Literal['holdout', 'switchback', 'ghost'], n_simulations: int, n_unsupported: int, false_positive_count: int, false_positive_rate: float, alpha: float, region: AcceptanceRegion, passed: bool, reason: str = '', seed: int)
A method's A/A false-positive count against its exact binomial acceptance region.
cal: CalibrationResult = calibrate_method("difference_in_differences", spec, alpha=0.05)
CandidateScorespec
CandidateScore(candidate: DesignCandidate, eig: float, evpi: float, evsi: float, opportunity_cost: float, cost: float, net_value: float, power: float, effect: float, alpha: float, numeraire: str = '', detail: dict[str, str] = {})
A candidate's information, value, cost, and power on one decision.
scores: list[CandidateScore] = [evaluate_candidate(c, decision, prior_mean=mean, prior_sd=sd, economics=economics) for c in designs]
ClusterDesignspec
ClusterDesign(unit: Unit, n_clusters: int, cluster_size: int, icc: float, allocation: float = 0.5)
A cluster-randomized design and its variance inflation.
cd = ClusterDesign(unit=region, n_clusters=24, cluster_size=40, icc=0.05, allocation=0.5)
Combinationspec
Combination(exponents: dict[str, int], kind: Literal['symmetry', 'estimable'], scaling: Literal['log', 'absolute'] = 'log', score: float, label: str = '')
A monomial (or linear combination) of parameters, and how flat it is.
ratio = Combination(exponents={"alpha": 1, "k": -1}, kind="estimable", score=0.0)
CostPerOutcomeIntervalspec
CostPerOutcomeInterval(cost: float, effect: float, effect_se: float, alpha: float, estimate: float, lower: float, upper: float | None, method: Literal['fieller'] = 'fieller', status: Literal['bounded', 'unbounded'], effect_interval: Interval, naive_se: float, naive_interval: Interval)
``cost / effect`` with a Fieller interval and the naive delta-method interval beside it.
ci: CostPerOutcomeInterval = cost_per_outcome_interval(cost=1000.0, effect=25.0, effect_se=6.0)
CostPerOutcomePowerspec
CostPerOutcomePower(cost: float, true_effect: float, effect_se: float, alpha: float, threshold: float | None, effect_required: float, power: float)
Power to bound the cost per outcome unit (below ``threshold`` if one is set).
cp: CostPerOutcomePower = cost_per_outcome_power(1000.0, true_effect=25.0, effect_se=6.0, threshold=60.0)
CrossingProbabilitiesspec
CrossingProbabilities(drift: float, information: tuple[float, ...], per_look: dict[str, tuple[float, ...]], continue_probability: float, binding_only: bool)
Per-look probability that each boundary is the one that stops the study.
assert isinstance(spent, CrossingProbabilities)
DESIGN_RELATIVE_SEvalue
Read-only proxy of a mapping.
print("relative SE by design kind:", dict(DESIGN_RELATIVE_SE))
Decisioncallable
Decision(*args, **kwargs)
decision: Decision = path.decision
DecisionSpecspec
DecisionSpec(name: str = 'act', threshold: float = 0.0, value_per_outcome_unit: float = 1.0, numeraire: str = '')
A two-action decision with value linear in the parameter.
decision = DecisionSpec(name="scale_up", threshold=0.0, value_per_outcome_unit=1000.0, numeraire="USD")
DerivativeMethodcallable
DerivativeMethod(*args, **kwargs)
method: DerivativeMethod = "finite"
DesignCandidatespec
DesignCandidate(name: str, method: str, n_units: int, n_periods: int, holdout_fraction: float, experiment_se: float, cost: float, n_clusters: int | None = None, cooldown_periods: int = 0, detail: dict[str, str] = {})
One concrete experiment design: method, size, horizon, holdout, precision, cost.
designs = [
DesignCandidate(name="small_holdout", method="difference_in_differences", n_units=20, n_periods=6, holdout_fraction=0.2, experiment_se=0.35, cost=200.0, cooldown_periods=2),
DesignCandidate(name="large_holdout", method="cluster_based_regression", n_units=60, n_periods=8, holdout_fraction=0.4, experiment_se=0.12, cost=600.0, n_clusters=12, cooldown_periods=4),
DesignCandidate(name="switchback", method="switchback", n_units=20, n_periods=10, holdout_fraction=0.5, experiment_se=0.2, cost=300.0, cooldown_periods=1),
DesignCandidate(name="ghost", method="ghost", n_units=40, n_periods=6, holdout_fraction=0.3, experiment_se=0.3, cost=450.0),
]
EIGEstimatespec
EIGEstimate(eig: float, se: float, n_sims: int, seed: int | None = None, method: Literal['gaussian', 'nested_monte_carlo'], n_prior_draws: int = 0, experiment_se: float, unit: Literal['nats'] = 'nats')
Expected information gain in nats, with the error of its estimator.
est: EIGEstimate = eig_monte_carlo(prior_draws, 0.3, n_sims=n_sims, seed=1)
EVOIResultspec
EVOIResult(evpi: float, evsi: float, preposterior_sd: float, decision_threshold: float, n_sims: int, seed: int | None = None, method: Literal['gaussian', 'monte_carlo'], evsi_se: float = 0.0, experiment_se: float, prior_mean: float, prior_sd: float, value_per_outcome_unit: float, ... and 1 more field)
EVPI and EVSI of one experiment against one decision, in the numeraire.
res: EVOIResult = evoi_gaussian(decision, mean, sd, se)
EconomicInputsspec
EconomicInputs(value_per_outcome: ValuePerOutcome, dose_per_period: float, discount_rate: float, dose_unit: str = 'dose', dose_cost_per_unit: float = 0.0, marginal_value_ratio: float | None = None)
The economic inputs a candidate's opportunity cost needs, stated once.
economics = EconomicInputs(value_per_outcome=vpo, dose_per_period=20.0, discount_rate=0.02, dose_unit="L", dose_cost_per_unit=1.0, marginal_value_ratio=1.2)
EstimabilityReportspec
EstimabilityReport(parameters: tuple[str, ...], observations: tuple[str, ...] = (), scaling: Literal['log', 'absolute'] = 'log', n_points: int = 1, rank: int, deficiency: int, persistent_deficiency: int, singular_values: tuple[float, ...] = (), condition_number: float, symmetries: tuple[Combination, ...] = (), estimable: tuple[Combination, ...] = (), null_basis: tuple[tuple[float, ...], ...] = (), ... and 4 more fields)
What this design can and cannot separate, at one parameter point and across several.
report: EstimabilityReport = estimable_combinations([low], theta, tolerance=3e-2)
ExperimentValuespec
ExperimentValue(information_value: float, opportunity_cost: float, fixed_cost: float, net: float, numeraire: str, detail: dict[str, str] = {})
``net = information_value − opportunity_cost − fixed_cost``, all in ``numeraire``.
ev: ExperimentValue = experiment_value(info, oc, fixed_cost=250.0)
FisherInformationspec
FisherInformation(parameters: tuple[str, ...], matrix: tuple[tuple[float, ...], ...], noise_sd: float, n_observations: int, det: float, min_eigenvalue: float, singular: bool, method: Literal['jax', 'finite'], detail: dict[str, str] = {})
``J'J / noise_sd²`` for the named parameters at one design and one ``theta``.
assert isinstance(fi, FisherInformation)
HoldoutTradeoffspec
HoldoutTradeoff(design: ClusterDesign, fractions: tuple[float, ...], mdes: tuple[float, ...], relative_mde: tuple[float, ...], n_holdout: tuple[int, ...], best_fraction: float, alpha: float, power: float, two_sided: bool)
MDE as a function of the share of clusters held out of treatment.
ht: HoldoutTradeoff = holdout_tradeoff(cd, sd=2.0, fractions=(0.1, 0.2, 0.3, 0.4, 0.5))
IdentifiabilityRidgespec
IdentifiabilityRidge(parameters: tuple[str, ...], direction: dict[str, float], min_eigenvalue: float, max_eigenvalue: float, condition_number: float, pairs: tuple[tuple[str, str], ...] = (), correlations: tuple[float, ...] = (), detail: dict[str, str] = {})
The flattest direction of the information matrix and the couplings it implies.
assert isinstance(ridge, IdentifiabilityRidge)
IdentifyingDesignspec
IdentifyingDesign(target: str, design: Design, indices: tuple[int, ...], expected_sd: float, expected_sds: dict[str, float], prior_sds: dict[str, float] = {}, noise_sd: float, seed: int | None = None, n_restarts: int, passes: int, detail: dict[str, str] = {})
The ``n`` candidate rows that minimize a target parameter's expected posterior sd.
assert isinstance(out, IdentifyingDesign)
Leaderboardspec
Leaderboard(spec: SimulationSpec, alpha: float, effects: tuple[float, ...], rows: tuple[LeaderboardRow, ...], calibrations: tuple[CalibrationResult, ...], powers: tuple[tuple[SimulatedPower, ...], ...])
Methods ranked by mean power at a calibrated size; uncalibrated methods rank last.
board: Leaderboard = leaderboard(small, effects=(0.8, 1.6), alpha=0.05)
LeaderboardRowspec
LeaderboardRow(method: str, design: Literal['holdout', 'switchback', 'ghost'], status: Literal['stable', 'experimental'], calibrated: bool, false_positive_rate: float, powers: tuple[float, ...], mean_power: float, biases: tuple[float, ...], coverages: tuple[float, ...], rmses: tuple[float, ...], n_unsupported: int, rank: int)
One method's line: its calibrated size and its power, bias and coverage per effect.
assert isinstance(row, LeaderboardRow)
LearningPriorityspec
LearningPriority(treatment: str, eig: float, evoi: float, net_value: float, cost: float, rank: int, numeraire: str = '', detail: dict[str, str] = {})
A candidate's EIG, EVSI, and net value, with its rank (``1`` is first).
assert isinstance(p, LearningPriority)
LookOutcomeclass
LookOutcome(look: int, label: str, information: float, z: float, decision: Decision, crossed: str = '', effect: float | None = None, se: float | None = None) -> None
One interim look: what it saw and what the rule said about it.
assert isinstance(outcome, LookOutcome)
LookSchedulespec
LookSchedule(labels: tuple[str, ...], information: tuple[float, ...])
When the study looks at itself, and how much information it has when it does.
schedule = LookSchedule(labels=tuple(f"look_{k + 1}" for k in range(LOOKS)), information=information)
MDEspec
MDE(design: Literal['difference_in_means', 'coefficient'], effect: float, se: float, alpha: float, power: float, two_sided: bool, n: int | None = None, sd: float | None = None, allocation: float | None = None)
The smallest effect detected with probability ``power`` at level ``alpha``.
m: MDE = mde(200, sd=2.0, power=0.8)
METHODSvalue
Read-only proxy of a mapping.
for name, spec in METHODS.items():
print(f"{name:28s} status={spec.status:12s} pre={spec.requires_pre_period!s:5} controls={spec.requires_controls!s:5} {spec.assumption_names()}")
MatchMetriccallable
MatchMetric(*args, **kwargs)
metric: MatchMetric = "trajectory"
MethodEstimatespec
MethodEstimate(method: str, effect: float, se: float, interval: Interval, n_treated: int, n_control: int, n_pre: int, n_post: int, se_method: str, critical: Literal['normal', 'student_t'] = 'normal', detail: dict[str, float] = {})
One method's estimate: the average effect per treated unit per post period.
assert isinstance(out, MethodEstimate)
MethodNamecallable
MethodName(*args, **kwargs)
name: MethodName = "switchback"
MethodSpecspec
MethodSpec(name: str, assumptions: tuple[Assumption, ...], requires_pre_period: bool, requires_controls: bool, status: Literal['stable', 'experimental'] = 'stable', description: str = '')
A registered experiment method: its assumptions, requirements, and status.
ms: MethodSpec = method_spec("switchback")
MethodStatuscallable
MethodStatus(*args, **kwargs)
status: MethodStatus = ms.status
MonitoringPathclass
MonitoringPath(rule: StoppingRule, looks: tuple[LookOutcome, ...], decision: Decision, stopped_at: int | None = None) -> None
A realized sequence of looks and the decision it produced.
assert isinstance(path, MonitoringPath)
Observationclass
Observation(label: str, expr: Expr, data: Mapping[str, Any] = <factory>, noise_sd: float = 1.0) -> None
One measurable quantity, the data it is measured at, and its noise.
low = Observation("low dose", hill, {"dose": np.linspace(0.01, 0.3, 12)}, noise_sd=0.05)
OperatingCharacteristicsspec
OperatingCharacteristics(rule: str, drift: float, crossings: CrossingProbabilities, expected_information: float, expected_looks: float)
What a rule does at one drift: where it stops, and what it costs to get there.
assert isinstance(oc, OperatingCharacteristics)
OpportunityCostspec
OpportunityCost(value: float, numeraire: str, holdout_fraction: float, n_periods: int, discount_rate: float, dose_per_period: float, dose_unit: str, dose_withheld: float, dose_withheld_discounted: float, dose_cost_per_unit: float, ratio_mean: float, ratio_sd: float, ... and 5 more fields)
What a holdout forgoes, in the numeraire, with every factor that produced it.
oc: OpportunityCost = opportunity_cost(0.25, 8, dose_per_period=20.0, marginal_value_ratio=ratio_draws,
value_per_outcome=vpo, discount_rate=0.02, dose_unit="L", dose_cost_per_unit=1.0)
PanelArraysclass
PanelArrays(outcome: Array, treated: tuple[int, ...], pre: slice, post: slice, assignment: Array | None = None, exposed: Array | None = None) -> None
The arrays every method reads.
arrays: PanelArrays = simulate_panel(spec, rng, design="holdout")
PanelDesigncallable
PanelDesign(*args, **kwargs)
d: PanelDesign = design_for_method(m)
Parametercallable
Parameter(*args, **kwargs)
parameter: Parameter = "value_per_outcome"
Patterncallable
Pattern(*args, **kwargs)
p: Pattern = s.pattern
PowerCurvespec
PowerCurve(design: Literal['difference_in_means', 'coefficient'], effects: tuple[float, ...], powers: tuple[float, ...], se: float, alpha: float, two_sided: bool, n: int | None = None, sd: float | None = None, allocation: float | None = None)
Power as a function of effect size at a fixed design (``n``, ``sd``, ``allocation``).
curve: PowerCurve = power_curve(200, 2.0, effects=np.linspace(0.0, 1.2, 7))
PowerResultspec
PowerResult(design: Literal['difference_in_means', 'coefficient'], power: float, effect: float, se: float, alpha: float, two_sided: bool, n: int | None = None, sd: float | None = None, allocation: float | None = None)
Power to detect ``effect`` at ``alpha`` with standard error ``se``.
pr: PowerResult = power(200, effect=0.6, sd=2.0)
Prescriptionspec
Prescription(added: tuple[str, ...] = (), rank_before: int, rank_after: int, n_parameters: int, broken: tuple[tuple[str, ...], ...] = (), still_flat: tuple[str, ...] = (), considered: tuple[str, ...] = (), complete: bool = False, detail: dict[str, str] = {})
The smallest set of extra measurements that restores rank, and what each one buys.
plan: Prescription = prescribe_measurements([low], [wide], theta, tolerance=3e-2)
ProfileReportspec
ProfileReport(targets: tuple[str, ...], truth: dict[str, float], recovered: dict[str, float], interval: dict[str, tuple[float, float]], level: float, threshold: float, flat: tuple[str, ...] = (), n_observations: int, seed: int | None = None, converged: bool = True, detail: dict[str, str] = {})
Profile-likelihood intervals for named targets, from one simulated experiment.
profile: ProfileReport = simulated_identifiability(
model, {"dose": doses}, truth,
targets=["alpha", "k"], combinations=[ratio],
seed=3, range_factor=30.0, n_grid=11,
)
ProgramSchedulespec
ProgramSchedule(horizon_periods: int, slots: tuple[ScheduledExperiment, ...], skipped: tuple[str, ...], total_net_value: float, numeraire: str = '', detail: dict[str, str] = {})
A non-overlapping sequence of experiments within ``horizon_periods``.
sched: ProgramSchedule = schedule_with_cooldown(scores, horizon_periods=20)
ReExperimentTimingspec
ReExperimentTiming(posterior_sd: float, half_life_periods: float, experiment_se: float, min_eig: float, sd_at_threshold: float, periods: float, eig_now: float, design_kind: str = 'unspecified')
When an experiment with ``experiment_se`` is worth ``min_eig`` nats again.
timing: ReExperimentTiming = time_to_re_experiment(post_sd, 6.0, experiment_se=0.2, min_eig=0.3, design_kind="cluster_holdout")
Recommendationspec
Recommendation(selected: tuple[str, ...], priorities: tuple[LearningPriority, ...], budget: float | None = None, total_cost: float, total_net_value: float, numeraire: str = '', detail: dict[str, str] = {})
The selected treatments and why: the ranked priorities and the budget arithmetic.
assert isinstance(rec, Recommendation)
STOPPED_ESTIMATE_BIASvalue
A named, falsifiable condition that licenses a step.
print("named assumptions:", CANONICAL.name, "|", STOPPED_ESTIMATE_BIAS.name)
SampleSizespec
SampleSize(design: Literal['difference_in_means', 'coefficient'], n: int, effect: float, se: float, alpha: float, power: float, target_power: float, two_sided: bool, sd: float | None = None, allocation: float | None = None, n_treated: int | None = None, n_control: int | None = None)
The smallest ``n`` whose achieved power reaches the target for ``effect``.
assert isinstance(ss, SampleSize)
Scalingcallable
Scaling(*args, **kwargs)
scaling: Scaling = "log"
Schedulespec
Schedule(pattern: Literal['constant', 'pulse', 'ramp', 'random_switchback', 'alternating'], n_periods: int, doses: tuple[float, ...], treatment: str = 'dose', detail: dict[str, float] = {})
One treatment's dose per period: ``doses[t]`` for ``t = 0 … n_periods − 1``.
schedules: list[Schedule] = [
constant(T, 50.0),
pulse(T, 80.0, 20.0, on=2, off=2),
alternating(T, 80.0, 20.0),
ramp(T, 0.0, 100.0),
random_switchback(T, 80.0, 20.0, seed=0),
]
ScheduledExperimentspec
ScheduledExperiment(name: str, start: int, end: int, free_at: int, net_value: float)
One slot of a program: ``[start, end)`` is the run, ``[end, free_at)`` the cooldown.
assert isinstance(slot, ScheduledExperiment)
SensitivityMatrixclass
SensitivityMatrix(matrix: Array, parameters: tuple[str, ...], observations: tuple[str, ...], scaling: Scaling, theta: dict[str, float], zero_columns: tuple[str, ...] = ()) -> None
``d f / d log theta`` (or ``d f / d theta``) stacked over observations.
sensitivity: SensitivityMatrix = sensitivity_matrix([low], theta, scaling=scaling)
SensitivityTablespec
SensitivityTable(parameter: Literal['prior_sd', 'experiment_se', 'value_per_outcome', 'discount_rate', 'holdout_fraction'], mode: Literal['absolute', 'multiplier'], grid: tuple[float, ...], candidates: tuple[str, ...], net_values: tuple[tuple[float, ...], ...], winners: tuple[str, ...], tipping_points: tuple[tuple[float, float], ...], base_value: float, base_winner: str, base_net_values: tuple[float, ...], numeraire: str = '', detail: dict[str, str] = {})
Net value of every candidate at every grid value of one input, and where the winner flips.
table: SensitivityTable = perturb(designs, decision, mean, sd, economics, parameter, grid=(0.5, 1.0, 2.0, 3.0, 5.0, 8.0))
Sidecallable
Side(*args, **kwargs)
side: Side = with_margin.side
SimulatedPowerspec
SimulatedPower(method: str, design: Literal['holdout', 'switchback', 'ghost'], effect: float, truth: float, n_simulations: int, n_unsupported: int, rejections: int, power: float, alpha: float, mean_effect: float, bias: float, coverage: float, ... and 5 more fields)
Realized A/B power and accuracy of a method at one effect size.
sp: SimulatedPower = simulated_power("difference_in_differences", ab, predicted_power=predicted)
SimulationSpecspec
SimulationSpec(n_units: int = 20, n_periods: int = 16, n_pre: int = 8, n_treated: int = 10, unit_sd: float = 1.0, noise_sd: float = 1.0, period_sd: float = 0.5, rho: float = 0.5, effect: float = 0.0, n_simulations: int = 200, seed: int = 0, mass: float = 0.95, ... and 2 more fields)
One simulated panel: its shape, its variance components, its effect, and its budget.
spec = SimulationSpec(n_units=16, n_periods=20, n_pre=10, n_treated=8, effect=1.0, noise_sd=1.0, seed=7)
SpendingFunctioncallable
SpendingFunction(*args, **kwargs)
kind_: SpendingFunction = kind
StoppingRulespec
StoppingRule(name: str, looks: LookSchedule, boundaries: tuple[Boundary, ...])
A look schedule and the boundaries evaluated at every look.
rule = StoppingRule(name=name, looks=schedule, boundaries=(boundary,))
StudySummaryspec
StudySummary(treatment: str, estimate: float, se: float, periods_ago: float, definition: str, name: str = '')
One past study of a treatment: its estimate, standard error, and age.
history = [
StudySummary(treatment="fertilizer", estimate=1.4, se=0.3, periods_ago=12.0, definition="wald"),
StudySummary(treatment="fertilizer", estimate=1.0, se=0.4, periods_ago=2.0, definition="wald"),
]
TreatmentCandidatespec
TreatmentCandidate(name: str, prior_mean: float, prior_sd: float, experiment_se: float, decision: DecisionSpec, opportunity_cost: float = 0.0, fixed_cost: float = 0.0)
A treatment one could run an experiment on, with the inputs its value needs.
candidates = [
TreatmentCandidate(name="fertilizer", prior_mean=mean, prior_sd=sd, experiment_se=0.2, decision=decision, opportunity_cost=oc.value, fixed_cost=250.0),
TreatmentCandidate(name="irrigation", prior_mean=0.8, prior_sd=0.6, experiment_se=0.3, decision=decision, opportunity_cost=100.0, fixed_cost=400.0),
TreatmentCandidate(name="pruning", prior_mean=0.1, prior_sd=0.1, experiment_se=0.3, decision=decision, opportunity_cost=0.0, fixed_cost=60.0),
]
ValueFncallable
ValueFn(*args, **kwargs)
capped: ValueFn = lambda theta: np.minimum(decision.payoff(theta), 200.0)
ValuePerOutcomespec
ValuePerOutcome(value: float, outcome_unit: str, numeraire: str, source: str, assumption: Assumption | None = None)
``value`` numeraire units per ``outcome_unit``, and where that number came from.
vpo = ValuePerOutcome(value=3.0, outcome_unit="kg", numeraire="USD", source="contract price, 2026 season")
alpha_spendingfn
alpha_spending(alpha: float, information: Sequence[float] | LookSchedule, *, family: SpendingFunction = 'obrien_fleming', side: Side = 'two_sided', kind: BoundaryKind = 'efficacy', rho: float = 1.0, n_grid: int = 601, width: float = 8.0) -> Boundary
A Lan–DeMets boundary: solve each threshold to spend exactly what is budgeted.
efficacy = alpha_spending(ALPHA / 2, information, family="obrien_fleming", side="upper")
alternatingfn
alternating(n_periods: int, high: float, low: float, *, treatment: str = 'dose') -> Schedule
``high, low, high, low, …`` — the maximal-contrast pulse with ``on = off = 1``.
schedules: list[Schedule] = [
constant(T, 50.0),
pulse(T, 80.0, 20.0, on=2, off=2),
alternating(T, 80.0, 20.0),
ramp(T, 0.0, 100.0),
random_switchback(T, 80.0, 20.0, seed=0),
]
anchor_drawsfn
anchor_draws(draws: ArrayLike, parameter: str, mde: float, *, credence: float = 0.9, definition: IntervalDefinition = 'hdi', mass: float = 0.9) -> AnchoredEffect | Unsupported
``anchor_effect`` on a flat array of draws of one scalar parameter.
print("from flat draws:", round(anchor_draws(draws.ravel(), "beta", 0.5).anchored_effect, 3))
anchor_effectfn
anchor_effect(posterior: SupportsPosterior, parameter: str, mde: float, *, index: int | None = None, credence: float = 0.9, definition: IntervalDefinition = 'hdi', mass: float = 0.9) -> AnchoredEffect | Unsupported
Anchor an MDE against the posterior of ``parameter``.
ae = anchor_effect(posterior, "beta", mde=0.5, credence=0.9)
bartlett_bandwidthfn
bartlett_bandwidth(n_periods: int) -> int
Newey–West rule of thumb ``max(1, floor(4 · (T/100)^(2/9)))``.
print(f"switchback: {sb.effect:.3f} ± {sb.se:.3f} bandwidth={sb.detail.get('bandwidth')} (rule: {bartlett_bandwidth(spec.n_periods)})")
calibrate_methodfn
calibrate_method(method: str | MethodSpec, spec: SimulationSpec, *, alpha: float = 0.05, registry: Mapping[str, MethodSpec] = ..., design: PanelDesign | None = None) -> CalibrationResult
A/A calibration: ``spec`` with ``effect=0`` and intervals at mass ``1 − alpha``.
cal: CalibrationResult = calibrate_method("difference_in_differences", spec, alpha=0.05)
calibrate_registryfn
calibrate_registry(spec: SimulationSpec, registry: Mapping[str, MethodSpec] = ..., *, alpha: float = 0.05) -> tuple[Mapping[str, MethodSpec], tuple[CalibrationResult, ...]]
Calibrate every method in ``registry`` and return a new registry with statuses set.
registry, results = calibrate_registry(small, alpha=0.05)
cluster_mdefn
cluster_mde(design: ClusterDesign, sd: float, *, alpha: float = 0.05, power: float = 0.8, two_sided: bool = True) -> MDE
Minimum detectable effect under ``design``; see ``cluster_power`` for the scale.
print("cluster MDE:", round(cluster_mde(cd, sd=2.0).effect, 4))
cluster_powerfn
cluster_power(design: ClusterDesign, effect: float, sd: float, *, alpha: float = 0.05, two_sided: bool = True) -> PowerResult
Power to detect ``effect`` on the individual-level scale under ``design``.
print("cluster power for effect 0.5:", round(cluster_power(cd, effect=0.5, sd=2.0).power, 3))
clusters_neededfn
clusters_needed(effect: float, sd: float, cluster_size: int, icc: float, *, alpha: float = 0.05, power: float = 0.8, two_sided: bool = True, allocation: float = 0.5) -> SampleSize | Unsupported
Smallest number of clusters of size ``cluster_size`` reaching ``power`` for ``effect``.
cn = clusters_needed(effect=0.5, sd=2.0, cluster_size=40, icc=0.05)
coefficient_mdefn
coefficient_mde(se: float, *, alpha: float = 0.05, power: float = 0.8, two_sided: bool = True) -> MDE
Minimum detectable coefficient given its design standard error.
print("coefficient_mde:", round(coefficient_mde(se=0.1, power=0.9).effect, 4))
coefficient_powerfn
coefficient_power(effect: float, se: float, *, alpha: float = 0.05, two_sided: bool = True) -> PowerResult
Power to detect a coefficient of size ``effect`` whose design SE is ``se``.
print("coefficient_power:", round(coefficient_power(0.3, se=0.1).power, 3))
coefficient_sample_sizefn
coefficient_sample_size(effect: float, se: float, n: int, *, alpha: float = 0.05, power: float = 0.8, two_sided: bool = True) -> SampleSize | Unsupported
Observations needed for a coefficient whose SE is ``se`` at ``n`` observations.
css = coefficient_sample_size(effect=0.2, se=0.1, n=500)
constantfn
constant(n_periods: int, dose: float, *, treatment: str = 'dose') -> Schedule
The same dose in every period — no temporal contrast.
flat = fisher_information(world.surface, data_with(constant(T, 50.0)), world.theta, NOISE_SD, method=method)
contrast_scorefn
contrast_score(schedule: Schedule) -> float
Mean squared first difference over mean squared dose (module docstring); ``0`` if flat.
print(f"{p:18s} mean={s.mean:5.1f} total={s.total:6.1f} contrast={contrast_score(s):.3f} {s.doses[:6]}...")
cost_per_outcome_intervalfn
cost_per_outcome_interval(cost: float, effect: float, effect_se: float, *, alpha: float = 0.05) -> CostPerOutcomeInterval
Fieller and naive intervals for ``cost / effect`` at confidence ``1 − alpha``.
ci: CostPerOutcomeInterval = cost_per_outcome_interval(cost=1000.0, effect=25.0, effect_se=6.0)
cost_per_outcome_powerfn
cost_per_outcome_power(cost: float, true_effect: float, effect_se: float, *, alpha: float = 0.05, threshold: float | None = None) -> CostPerOutcomePower
Probability the Fieller upper bound is finite (``threshold=None``) or below ``threshold``.
cp: CostPerOutcomePower = cost_per_outcome_power(1000.0, true_effect=25.0, effect_se=6.0, threshold=60.0)
crossing_probabilitiesfn
crossing_probabilities(rule: StoppingRule, drift: float, *, binding_only: bool = True, n_grid: int = 601, width: float = 8.0) -> CrossingProbabilities
Exact first-crossing probabilities for ``rule`` at ``drift``.
spent = crossing_probabilities(harm_only, 0.0)
decayed_sdfn
decayed_sd(posterior_sd: float, periods_elapsed: float, half_life_periods: float) -> float
``posterior_sd · 2^(t / (2 · half_life))``: the posterior variance doubles every half-life.
print("decayed sd after 0, 6, 12 periods (hl=6):", [round(decayed_sd(post_sd, t, 6.0), 4) for t in (0, 6, 12)])
design_effectfn
design_effect(cluster_size: int, icc: float) -> float
``1 + (m − 1) · icc`` — variance inflation of a cluster-randomized mean.
print("design effect:", design_effect(40, 0.05), "| effective n:", round(effective_sample_size(24, 40, 0.05), 1))
design_for_methodfn
design_for_method(method: str | MethodSpec, registry: Mapping[str, MethodSpec] = ...) -> PanelDesign
The panel design a method's data requirements imply.
d: PanelDesign = design_for_method(m)
design_to_identifyfn
design_to_identify(surface: SupportsForward, candidates: Design, theta: Theta, noise_sd: float, target: str, n: int, *, prior_sds: Mapping[str, float] | None = None, seed: int | None = None, n_restarts: int = 3, max_passes: int = 20, method: DerivativeMethod = 'auto', parameter_scales: Mapping[str, float] | None = None) -> IdentifyingDesign | Unsupported
The ``n`` candidate rows (replicates allowed) minimizing ``target``'s expected posterior sd.
print("one row under flat priors ->", type(design_to_identify(steady, candidates, theta, NOISE_SD, target="k_a", n=1, seed=0, method=method)).__name__)
difference_in_differences_sefn
difference_in_differences_se(spec: SimulationSpec) -> float
Exact SE of the DiD estimator under the spec's DGP (derivation in the module docstring).
se = difference_in_differences_se(spec)
difference_sefn
difference_se(n: int, sd: float, allocation: float = 0.5) -> float
``sd / sqrt(n · p · (1 − p))`` — the SE of a two-arm difference in means.
se = difference_se(200, sd=2.0, allocation=0.5)
discount_weightsfn
discount_weights(n_periods: int, rate: float) -> Array
``(1 + rate)^(-t)`` for ``t = 0 … n_periods − 1``: period 0 is undiscounted.
print("discount weights (8 periods, 2%):", np.round(discount_weights(8, 0.02), 4))
effective_sample_sizefn
effective_sample_size(n_clusters: int, cluster_size: int, icc: float) -> float
``k · m / DE`` — the number of independent observations the design is worth.
print("design effect:", design_effect(40, 0.05), "| effective n:", round(effective_sample_size(24, 40, 0.05), 1))
eig_gaussianfn
eig_gaussian(prior_sd: float, experiment_se: float) -> float
``0.5 · ln(1 + prior_sd² / experiment_se²)`` nats.
print(f"experiment_se={se:.2f} EIG = {eig_gaussian(prior_sd, se):.4f} nats")
eig_monte_carlofn
eig_monte_carlo(prior_draws: ArrayLike, experiment_se: float, n_sims: int = 2000, seed: int | None = None, *, chunk: int = 256) -> EIGEstimate
Nested Monte-Carlo EIG of ``y = theta + N(0, experiment_se²)`` under an empirical prior.
est: EIGEstimate = eig_monte_carlo(prior_draws, 0.3, n_sims=n_sims, seed=1)
elasticityfn
elasticity(table: SensitivityTable) -> dict[str, float]
``(Δnet / net_base) / (Δx / x_base)`` per candidate, from the grid values around the base.
print("elasticities:", {k: round(v, 3) for k, v in elasticity(table).items()})
estimable_combinationsfn
estimable_combinations(observations: Sequence[Observation], theta: Theta, *, at: Sequence[Theta] = (), parameters: Sequence[str] | None = None, scaling: Scaling = 'log', tolerance: float = 1e-08, direction_tolerance: float | None = None, max_support: int = 3, max_coefficient: int = 3, max_candidates: int = 200000, step: float = 0.0001, parameter_scales: Mapping[str, float] | None = None) -> EstimabilityReport | Unsupported
Rank, flat directions, and the named combinations this design can estimate.
report = estimable_combinations([low], theta, tolerance=tolerance)
estimatefn
estimate(method: str | MethodSpec, arrays: PanelArrays, *, mass: float = 0.95, registry: Mapping[str, MethodSpec] = ...) -> MethodEstimate | Unsupported
Run a registered method on ``arrays`` with a ``wald`` interval at ``mass``.
out = estimate(method, arrays, mass=0.95)
estimate_cluster_based_regressionfn
estimate_cluster_based_regression(outcome: ArrayLike, treated: Sequence[int], pre: slice, post: slice, *, mass: float = 0.95) -> MethodEstimate | Unsupported
Keyword convenience: build the ``PanelArrays`` and run ``estimate_arrays``.
cbr = estimate_cluster_based_regression(y, tr, pre, post)
estimate_difference_in_differencesfn
estimate_difference_in_differences(outcome: ArrayLike, treated: Sequence[int], pre: slice, post: slice, *, mass: float = 0.95) -> MethodEstimate | Unsupported
Keyword convenience: build the ``PanelArrays`` and run ``estimate_arrays``.
did = estimate_difference_in_differences(y, tr, pre, post)
estimate_ghostfn
estimate_ghost(outcome: ArrayLike, treated: Sequence[int], post: slice, exposed: ArrayLike, *, pre: slice = slice(0, 0, None), mass: float = 0.95) -> MethodEstimate | Unsupported
Keyword convenience: build ``PanelArrays`` with ``exposed`` and run ``estimate_arrays``.
gh = estimate_ghost(gh_arrays.outcome, gh_arrays.treated, gh_arrays.post, gh_arrays.exposed)
estimate_switchbackfn
estimate_switchback(outcome: ArrayLike, assignment: ArrayLike, *, treated: Sequence[int] = (), pre: slice = slice(0, 0, None), post: slice = slice(None, None, None), mass: float = 0.95) -> MethodEstimate | Unsupported
Keyword convenience: build ``PanelArrays`` with a schedule and run ``estimate_arrays``.
sb = estimate_switchback(sb_arrays.outcome, sb_arrays.assignment)
estimate_synthetic_controlfn
estimate_synthetic_control(outcome: ArrayLike, treated: Sequence[int], pre: slice, post: slice, *, mass: float = 0.95, rmspe_ratio_max: float | None = None) -> MethodEstimate | Unsupported
Keyword convenience with the optional RMSPE-ratio placebo filter.
sc = estimate_synthetic_control(y, tr, pre, post, rmspe_ratio_max=5.0)
estimate_time_based_regressionfn
estimate_time_based_regression(outcome: ArrayLike, treated: Sequence[int], pre: slice, post: slice, *, mass: float = 0.95) -> MethodEstimate | Unsupported
Keyword convenience: build the ``PanelArrays`` and run ``estimate_arrays``.
tbr = estimate_time_based_regression(y, tr, pre, post)
evaluate_candidatefn
evaluate_candidate(candidate: DesignCandidate, decision: DecisionSpec, prior_mean: float, prior_sd: float, economics: EconomicInputs, *, alpha: float = 0.05, effect: float | None = None) -> CandidateScore
Score ``candidate`` on ``decision`` under ``theta ~ N(prior_mean, prior_sd²)``.
scores: list[CandidateScore] = [evaluate_candidate(c, decision, prior_mean=mean, prior_sd=sd, economics=economics) for c in designs]
evoi_gaussianfn
evoi_gaussian(decision: DecisionSpec, prior_mean: float, prior_sd: float, experiment_se: float) -> EVOIResult
``evpi_gaussian`` and ``evsi_gaussian`` together, as an ``EVOIResult``.
res: EVOIResult = evoi_gaussian(decision, mean, sd, se)
evpifn
evpi(decision: DecisionSpec, prior_draws: ArrayLike, *, value_fn: ValueFn | None = None) -> float
Monte-Carlo EVPI: ``mean(max(payoff, 0)) − max(mean(payoff), 0)`` over the prior draws.
print("MC EVPI:", round(evpi(decision, draws), 2), "vs closed", round(evpi_gaussian(decision, mean, sd), 2))
evpi_gaussianfn
evpi_gaussian(decision: DecisionSpec, prior_mean: float, prior_sd: float) -> float
Expected value of perfect information under ``theta ~ N(prior_mean, prior_sd²)``.
print("EVPI:", round(evpi_gaussian(decision, mean, sd), 2), decision.numeraire)
evsifn
evsi(decision: DecisionSpec, prior_draws: ArrayLike, experiment_se: float, *, n_sims: int = 1000, seed: int | None = None, value_fn: ValueFn | None = None, chunk: int = 256) -> EVOIResult
Monte-Carlo EVSI of observing ``y = theta + N(0, experiment_se²)``.
mc = evsi(decision, draws, se, n_sims=n_sims, seed=2)
evsi_gaussianfn
evsi_gaussian(decision: DecisionSpec, prior_mean: float, prior_sd: float, experiment_se: float) -> float
Expected value of the sample information an experiment with ``experiment_se`` yields.
print("EVSI:", round(evsi_gaussian(decision, mean, sd, se), 2), decision.numeraire)
expected_posterior_sdfn
expected_posterior_sd(prior_sds: Mapping[str, float] | None, fisher: FisherInformation) -> dict[str, float] | Unsupported
Laplace posterior sds ``sqrt(diag((diag(1/sd²) + FI)⁻¹))`` per parameter coordinate.
print("expected posterior sd (flat prior) ->", type(expected_posterior_sd(None, flat)).__name__)
experiment_se_for_designfn
experiment_se_for_design(design_kind: str, reference_value: float, *, relative_se: float | None = None) -> float | Unsupported
``relative_se · |reference_value|``, with ``relative_se`` looked up by design kind.
se_holdout = experiment_se_for_design("cluster_holdout", reference_value=2.0)
experiment_valuefn
experiment_value(information_value: float, opportunity_cost: OpportunityCost | float, fixed_cost: float, *, numeraire: str = '') -> ExperimentValue
``information_value − opportunity_cost − fixed_cost`` in one numeraire.
ev: ExperimentValue = experiment_value(info, oc, fixed_cost=250.0)
fisher_informationfn
fisher_information(surface: SupportsForward, design_doses: Theta, theta: Theta, noise_sd: float, *, parameters: Sequence[str] | None = None, method: DerivativeMethod = 'auto', parameter_scales: Mapping[str, float] | None = None) -> FisherInformation | Unsupported
``J'J / noise_sd²`` at ``design_doses`` and ``theta`` for the mean's parameters.
fi = fisher_information(world.surface, data_with(s), world.theta, NOISE_SD, method=method)
harm_boundaryfn
harm_boundary(probability: float, information: Sequence[float] | LookSchedule, *, margin: float = 0.0, se_at_full_information: float = 1.0, kind: BoundaryKind = 'harm') -> Boundary
The Z threshold of a posterior-probability harm rule, look by look.
flat = harm_boundary(0.95, information)
holdout_tradeofffn
holdout_tradeoff(design: ClusterDesign, sd: float, fractions: Sequence[float] = (0.1, 0.2, 0.3, 0.4, 0.5), *, alpha: float = 0.05, power: float = 0.8, two_sided: bool = True) -> HoldoutTradeoff
MDE at each holdout share for ``design`` (its own ``allocation`` is ignored).
ht: HoldoutTradeoff = holdout_tradeoff(cd, sd=2.0, fractions=(0.1, 0.2, 0.3, 0.4, 0.5))
identifiability_ridgefn
identifiability_ridge(surface: SupportsForward, design_doses: Theta, theta: Theta, noise_sd: float, *, pairs: Sequence[tuple[str, str]] = (), parameters: Sequence[str] | None = None, method: DerivativeMethod = 'auto', parameter_scales: Mapping[str, float] | None = None) -> IdentifiabilityRidge | Unsupported
The flattest direction of the information matrix at this design, and pairwise couplings.
ridge = identifiability_ridge(world.surface, data_with(pulse(T, 80.0, 20.0, on=2, off=2)), world.theta, NOISE_SD, pairs=(("beta_a", "k_a"),), method=method)
information_fractionsfn
information_fractions(counts: Sequence[float], total: float | None = None) -> tuple[float, ...]
Information fractions from cumulative counts (enrolled, completed, events).
print("from cumulative enrollment:", information_fractions([48, 96, 144, 192, 240]))
information_half_lifefn
information_half_life(prior_sd: float, posterior_sd: float, half_life_periods: float) -> float
Periods until half the information the experiment bought has decayed.
print("information half-life:", round(information_half_life(prior_sd, post_sd, 6.0), 2), "periods")
information_value_offn
information_value_of(decision: DecisionSpec, prior_mean: float, prior_sd: float, experiment_se: float) -> float
EVSI of an experiment measuring the decision parameter to ``experiment_se``.
info = information_value_of(decision, prior_mean=1.2, prior_sd=0.5, experiment_se=0.2)
leaderboardfn
leaderboard(spec: SimulationSpec, effects: Sequence[float], registry: Mapping[str, MethodSpec] = ..., *, alpha: float = 0.05) -> Leaderboard
Rank ``registry``'s methods by mean power over ``effects`` at the calibrated size.
board: Leaderboard = leaderboard(small, effects=(0.8, 1.6), alpha=0.05)
match_clustersfn
match_clusters(pre_outcomes: ArrayLike, *, labels: Sequence[str] | None = None, unit: Unit | None = None, metric: MatchMetric = 'level', seed: int | None = None) -> Assignment
Pair clusters by pre-period similarity and randomize one member of each pair.
asg: Assignment = match_clusters(pre, labels=[f"r{i}" for i in range(9)], unit=region, metric=metric, seed=1)
max_detectable_cost_per_outcomefn
max_detectable_cost_per_outcome(cost: float, effect_mde: float) -> float
``cost / effect_mde``: the largest cost per outcome unit the experiment can bound.
print("largest bounded cost per outcome at the MDE:", round(max_detectable_cost_per_outcome(1000.0, m.effect), 2))
mdefn
mde(n: int, sd: float, *, alpha: float = 0.05, power: float = 0.8, two_sided: bool = True, allocation: float = 0.5) -> MDE
Minimum detectable effect of a two-arm difference in means with ``n`` units in total.
m: MDE = mde(200, sd=2.0, power=0.8)
method_assumptionfn
method_assumption(name: str, statement: str, challenged_by: str) -> Assumption
A method-facet ``Assumption`` in its default ``unverified`` state.
extra = method_assumption("balanced_exposure", "exposure is balanced across arms", "an imbalance test")
method_specfn
method_spec(method: str | MethodSpec, registry: Mapping[str, MethodSpec] = ...) -> MethodSpec
Resolve a name through ``registry`` (a ``MethodSpec`` passes through).
ms: MethodSpec = method_spec("switchback")
mid_horizon_factorfn
mid_horizon_factor(n_periods: int, rate: float) -> float
Mean of ``discount_weights``: undiscounted total × this = discounted total.
print("mid-horizon factor:", round(mid_horizon_factor(8, 0.02), 4))
monitorfn
monitor(rule: StoppingRule, z: Sequence[float], *, effects: Sequence[float] | None = None, ses: Sequence[float] | None = None) -> MonitoringPath
Walk realized statistics against ``rule`` and stop at the first crossing.
path = monitor(rule, observed_z[:3], effects=effects[:3], ses=ses[:3])
obrien_flemingfn
obrien_fleming(alpha: float, information: Sequence[float] | LookSchedule, *, side: Side = 'two_sided', kind: BoundaryKind = 'efficacy', n_grid: int = 601, width: float = 8.0) -> Boundary
Thresholds ``c / sqrt(t)``, solved so the total error is ``alpha``.
shapes = {
"pocock": pocock(ALPHA, information),
"obrien_fleming": obrien_fleming(ALPHA, information),
"lan_demets_obf": alpha_spending(ALPHA, information, family="obrien_fleming"),
"lan_demets_pocock": alpha_spending(ALPHA, information, family="pocock"),
}
observation_from_modelfn
observation_from_model(model: ModelSpec, data: Mapping[str, Any], *, noise_sd: float | None = None, theta: Theta | None = None, label: str = '') -> Observation
The observation a fitted ``ModelSpec`` represents: its mean over the panel's rows.
print(observation_from_model(model, {"dose": np.array([1.0])}, theta=truth).noise_sd)
operating_characteristicsfn
operating_characteristics(rule: StoppingRule, drift: float, *, binding_only: bool = True, n_grid: int = 601, width: float = 8.0) -> OperatingCharacteristics
:func:`crossing_probabilities` plus the expected information and look count.
null = operating_characteristics(rule, 0.0)
opportunity_costfn
opportunity_cost(holdout_fraction: float, n_periods: int, dose_per_period: float, marginal_value_ratio: float | ArrayLike, value_per_outcome: ValuePerOutcome, discount_rate: float, *, dose_unit: str = 'dose', dose_cost_per_unit: float = 0.0) -> OpportunityCost
Signed value forgone by withholding ``holdout_fraction`` of the dose for ``n_periods``.
negative = opportunity_cost(0.25, 8, 20.0, 0.2, vpo, 0.02, dose_cost_per_unit=1.0)
panel_arraysfn
panel_arrays(outcome: ArrayLike, treated: Sequence[int], pre: slice, post: slice, *, assignment: ArrayLike | None = None, exposed: ArrayLike | None = None) -> PanelArrays
Validate and freeze the arrays a method reads; see ``PanelArrays``.
all_treated = panel_arrays(y, treated=range(y.shape[0]), pre=pre, post=post)
pareto_frontfn
pareto_front(scores: Sequence[CandidateScore], objectives: Sequence[str] = ('net_value', '-cost', 'eig')) -> tuple[CandidateScore, ...]
The non-dominated scores: nothing is at least as good on every objective and better on one.
front = pareto_front(scores, objectives=("net_value", "-cost", "eig"))
perturbfn
perturb(candidates: Sequence[DesignCandidate], decision: DecisionSpec, prior_mean: float, prior_sd: float, economics: EconomicInputs, parameter: Parameter, grid: Sequence[float], *, alpha: float = 0.05, effect: float | None = None) -> SensitivityTable
Re-score every candidate at each grid value of ``parameter``; report winners and flips.
table: SensitivityTable = perturb(designs, decision, mean, sd, economics, parameter, grid=(0.5, 1.0, 2.0, 3.0, 5.0, 8.0))
pocockfn
pocock(alpha: float, information: Sequence[float] | LookSchedule, *, side: Side = 'two_sided', kind: BoundaryKind = 'efficacy', n_grid: int = 601, width: float = 8.0) -> Boundary
A constant critical value on the Z scale, solved so the total error is ``alpha``.
shapes = {
"pocock": pocock(ALPHA, information),
"obrien_fleming": obrien_fleming(ALPHA, information),
"lan_demets_obf": alpha_spending(ALPHA, information, family="obrien_fleming"),
"lan_demets_pocock": alpha_spending(ALPHA, information, family="pocock"),
}
powerfn
power(n: int, effect: float, sd: float, *, alpha: float = 0.05, two_sided: bool = True, allocation: float = 0.5) -> PowerResult
Power of a two-arm difference in means with ``n`` units in total.
pr: PowerResult = power(200, effect=0.6, sd=2.0)
power_curvefn
power_curve(n: int, sd: float, effects: Sequence[float], *, alpha: float = 0.05, two_sided: bool = True, allocation: float = 0.5) -> PowerCurve
Power over a grid of effect sizes for a fixed two-arm design.
curve: PowerCurve = power_curve(200, 2.0, effects=np.linspace(0.0, 1.2, 7))
power_from_sefn
power_from_se(effect: float, se: float, *, alpha: float = 0.05, two_sided: bool = True) -> PowerResult
Power for an effect whose estimator has standard error ``se``.
print("power_from_se:", round(power_from_se(0.3, 0.1).power, 3))
preposterior_sdfn
preposterior_sd(prior_sd: float, experiment_se: float) -> float
``prior_sd · preposterior_sd_ratio``: the sd of the posterior mean to come.
print("preposterior ratio:", round(preposterior_sd_ratio(sd, se), 4), "| preposterior sd:", round(preposterior_sd(sd, se), 4))
preposterior_sd_ratiofn
preposterior_sd_ratio(prior_sd: float, experiment_se: float) -> float
``sqrt(prior_sd² / (prior_sd² + experiment_se²))``.
print("preposterior ratio:", round(preposterior_sd_ratio(sd, se), 4), "| preposterior sd:", round(preposterior_sd(sd, se), 4))
prescribe_measurementsfn
prescribe_measurements(observations: Sequence[Observation], candidates: Sequence[Observation], theta: Theta, *, parameters: Sequence[str] | None = None, scaling: Scaling = 'log', tolerance: float = 1e-08, max_additions: int = 4, step: float = 0.0001, parameter_scales: Mapping[str, float] | None = None) -> Prescription | Unsupported
Greedily choose candidates until the design has full rank, or say what is left.
plan: Prescription = prescribe_measurements([low], [wide], theta, tolerance=3e-2)
prior_from_historyfn
prior_from_history(summaries: Sequence[StudySummary], half_life_periods: float) -> tuple[float, float]
``(mean, sd)`` of the inverse-variance combination of the decayed studies.
mean, sd = prior_from_history(history, half_life_periods=8.0)
profile_combinationfn
profile_combination(model: ModelSpec, data: Mapping[str, Any], start: Mapping[str, Any], combination: Combination, *, grid: Sequence[float]) -> tuple[tuple[float, ...], tuple[float, ...]]
The profile of a *combination* of parameters — ``alpha / k`` rather than ``alpha``.
values, drops = profile_combination(model, simulated, truth, ratio, grid=ratio_grid)
profile_likelihoodfn
profile_likelihood(model: ModelSpec, data: Mapping[str, Any], start: Mapping[str, Any], target: str, *, grid: Sequence[float]) -> tuple[tuple[float, ...], tuple[float, ...]]
``(values, drops)``: the profile of one parameter, as the drop in log-likelihood.
values, drops = profile_likelihood(model, simulated, truth, "alpha", grid=grid)
pulsefn
pulse(n_periods: int, high: float, low: float, *, on: int, off: int, start_on: bool = True, treatment: str = 'dose') -> Schedule
``on`` periods at ``high`` then ``off`` at ``low``, repeating.
pulsed = fisher_information(world.surface, data_with(pulse(T, 80.0, 20.0, on=2, off=2)), world.theta, NOISE_SD, method=method)
rampfn
ramp(n_periods: int, start: float, stop: float, *, treatment: str = 'dose') -> Schedule
Linear from ``start`` at period 0 to ``stop`` at the last period (``start`` alone if one).
schedules: list[Schedule] = [
constant(T, 50.0),
pulse(T, 80.0, 20.0, on=2, off=2),
alternating(T, 80.0, 20.0),
ramp(T, 0.0, 100.0),
random_switchback(T, 80.0, 20.0, seed=0),
]
random_switchbackfn
random_switchback(n_periods: int, high: float, low: float, *, seed: int | None, p_high: float = 0.5, treatment: str = 'dose') -> Schedule
Each period independently at ``high`` with probability ``p_high``, else ``low``; seeded.
schedules: list[Schedule] = [
constant(T, 50.0),
pulse(T, 80.0, 20.0, on=2, off=2),
alternating(T, 80.0, 20.0),
ramp(T, 0.0, 100.0),
random_switchback(T, 80.0, 20.0, seed=0),
]
rank_treatmentsfn
rank_treatments(candidates: Sequence[TreatmentCandidate]) -> tuple[LearningPriority, ...]
Every candidate scored and ranked by net value, then EIG, then name.
for p in rank_treatments(candidates):
assert isinstance(p, LearningPriority)
print(f"#{p.rank} {p.treatment:12s} eig={p.eig:.3f} evsi={p.evoi:8.2f} net={p.net_value:8.2f} cost={p.cost:7.2f}")
recommendfn
recommend(candidates: Sequence[TreatmentCandidate], budget: float | None = None) -> Recommendation | Unsupported
Greedy knapsack over ``rank_treatments``: net value per cost, within ``budget``.
rec = recommend(candidates, budget=600.0)
ridge_offn
ridge_of(fisher: FisherInformation, *, pairs: Sequence[tuple[str, str]] = ()) -> IdentifiabilityRidge | Unsupported
``identifiability_ridge`` from an information matrix already computed.
print("from the flat design:", ridge_of(flat).ridge_parameters, "| with pairs ->", type(ridge_of(flat, pairs=(("beta_a", "k_a"),))).__name__)
sample_sizefn
sample_size(effect: float, sd: float, *, alpha: float = 0.05, power: float = 0.8, two_sided: bool = True, allocation: float = 0.5) -> SampleSize | Unsupported
Smallest total ``n`` (integer arms at ``allocation``) reaching ``power`` for ``effect``.
ss = sample_size(effect=0.6, sd=2.0, power=0.8)
schedule_with_cooldownfn
schedule_with_cooldown(scores: Sequence[CandidateScore], horizon_periods: int) -> ProgramSchedule
Greedy end-to-end sequence: best net value first, each run then its cooldown.
sched: ProgramSchedule = schedule_with_cooldown(scores, horizon_periods=20)
sensitivity_matrixfn
sensitivity_matrix(observations: Sequence[Observation], theta: Theta, *, parameters: Sequence[str] | None = None, scaling: Scaling = 'log', step: float = 0.0001, parameter_scales: Mapping[str, float] | None = None) -> SensitivityMatrix | Unsupported
Central differences of every observation with respect to every parameter.
sensitivity: SensitivityMatrix = sensitivity_matrix([low], theta, scaling=scaling)
simplex_weightsfn
simplex_weights(target: Array, donors: Array) -> Array
Non-negative weights summing to one that best reproduce ``target`` from ``donors`` rows.
w = simplex_weights(target, donors)
simulate_panelfn
simulate_panel(spec: SimulationSpec, rng: Generator, *, design: PanelDesign = 'holdout') -> PanelArrays
Draw one panel from ``spec`` and apply ``spec.effect`` under ``design``.
arrays: PanelArrays = simulate_panel(spec, rng, design="holdout")
simulated_identifiabilityfn
simulated_identifiability(model: ModelSpec, data: Mapping[str, Any], truth: Mapping[str, Any], *, targets: Sequence[str] | None = None, combinations: Sequence[Combination] = (), seed: int | None = None, level: float = 0.95, range_factor: float = 20.0, n_grid: int = 21, noise_sd: float | None = None) -> ProfileReport | Unsupported
Run the proposed experiment on simulated data and profile each target.
print(simulated_identifiability(model, {"dose": doses}, {"alpha": 4.0, "k": 10.0}).missing)
simulated_powerfn
simulated_power(method: str | MethodSpec, spec: SimulationSpec, *, registry: Mapping[str, MethodSpec] = ..., design: PanelDesign | None = None, predicted_power: float | None = None) -> SimulatedPower
A/B simulation at ``spec.effect``: power, bias, RMSE and coverage at ``spec.mass``.
sp: SimulatedPower = simulated_power("difference_in_differences", ab, predicted_power=predicted)
spendingfn
spending(kind: SpendingFunction, t: float, alpha: float, *, rho: float = 1.0) -> float
Cumulative error a spending function allows to have been spent by fraction ``t``.
print(f"{'power (rho=3)':16s} " + " ".join(f"{spending('power', t, ALPHA, rho=3.0):7.4f}" for t in grid))
t_criticalfn
t_critical(mass: float, df: float) -> float
Student-t two-sided critical value ``t_{df, (1 + mass) / 2}``.
print(df, round(t_critical(0.95, df), 4))
time_to_re_experimentfn
time_to_re_experiment(posterior_sd: float, half_life_periods: float, experiment_se: float, min_eig: float, *, design_kind: str = 'unspecified') -> ReExperimentTiming
First period at which re-running the experiment is worth ``min_eig`` nats.
timing: ReExperimentTiming = time_to_re_experiment(post_sd, 6.0, experiment_se=0.2, min_eig=0.3, design_kind="cluster_holdout")
wald_tfn
wald_t(effect: float, se: float, mass: float, df: float) -> Interval
``effect ± t_critical(mass, df) · se`` as a ``wald`` interval at ``mass``.
print(wald_t(1.0, 0.5, 0.95, df=10))
Fold a randomized result into an observational model, and log what that assumed.
Agreementspec
Agreement(estimand_name: str, estimand_hash: str, measurement_hash: str, source: str, estimate: float, se: float, posterior_mean: float, posterior_sd: float, interval: Interval, z: float, p: float, inside: bool, ... and 5 more fields)
How a measurement sits against the posterior of its own estimand.
assert isinstance(ok, Agreement)
AgreementVerdictcallable
AgreementVerdict(*args, **kwargs)
verdict: AgreementVerdict = ok.verdict
CalibratedSpecspec
CalibratedSpec(spec: SurfaceSpec, treatment: str, parameter: str, prior: Prior, family: Literal['lognormal', 'gamma'], target: str, sources: tuple[str, ...], plan_statuses: dict[str, str], target_mean: float, target_se: float, design_factor: float, amplitude_mean: float, ... and 2 more fields)
The prior route's output: a new surface spec and how it was reached.
assert isinstance(cal, CalibratedSpec)
Correctionspec
Correction(name: str, kind: Literal['multiplicative', 'additive', 'se_scale'], value: float, counterfactual: float, corrected: float, detail: dict[str, str] = {}, ledger_line: LedgerLine)
One correction operator's output: the number applied and the line that records it.
assert isinstance(c, Correction)
CorrectionKindcallable
CorrectionKind(*args, **kwargs)
kind: CorrectionKind = c.kind
FACET_PREFIXvalue
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
print(FACET_PREFIX, "->", [facet_of(line) for line in baseline.lines])
Ledgerspec
Ledger(lines: tuple[LedgerLine, ...] = ())
An immutable sequence of ``LedgerLine``; see the module docstring for the rule.
pruned = Ledger(lines=tuple(ln for ln in ledger.lines if facet_of(ln) != "level"))
Measurementspec
Measurement(estimand: Estimand, estimate: float, se: float, definition: Literal['wald', 'eti', 'hdi'] = 'wald', mass: float = 0.95, method: str = '', n_units: int | None = None, n_periods: int | None = None, design_factor: float | None = None, source: str, assumptions: tuple[Assumption, ...] = ())
One external estimate of an estimand, with its uncertainty and provenance (D6.1).
m2 = Measurement(estimand=lift, estimate=0.9, se=0.5, definition="eti", mass=0.9, source="study-2")
PriorFamilycallable
PriorFamily(*args, **kwargs)
family: PriorFamily = fam
ResolvedTransferspec
ResolvedTransfer(status: Literal['identified', 'downgraded', 'blocked', 'unsupported', 'unverified'], plan: TransferPlan, factor: float = 1.0, offset: float = 0.0, se_scale: float = 1.0, corrections: tuple[Correction, ...] = (), transport: Verdict | None = None, ledger: Ledger, completeness: Verdict, reason: str = '')
A plan with its corrections applied: the composite operator and the ledger that licenses it.
resolved: ResolvedTransfer = resolve(plan, corrections=[window, level])
UNCORRECTEDvalue
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
print("UNCORRECTED =", repr(UNCORRECTED))
aggregation_levelfn
aggregation_level(level_source: Level, level_target: Level, *, cluster_size: int, icc: float) -> Correction | Unsupported
Individual ↔ cluster ↔ aggregate: point factor 1, SE through ``variance_reweight``.
level = aggregation_level(source.level, target.level, cluster_size=25, icc=0.05)
agreementfn
agreement(producer: SupportsEstimands, measurement: Measurement, *, definition: IntervalDefinition = 'hdi', mass: float = 0.9, seed: int | None = None, tension_at: float = 1.0, disagrees_at: float = 2.0) -> Agreement | Unsupported | Blocked
Realize ``measurement.estimand`` on ``producer`` and compare (module docstring).
ok = agreement(calibrated, experiment, definition="hdi", mass=0.9, seed=0)
amplitude_priorfn
amplitude_prior(mean: float, sd: float, family: PriorFamily = 'lognormal') -> Prior
The moment-matched ``Prior`` for a positive amplitude with the given mean and sd.
print(amplitude_prior(amp_mean, amp_sd, family))
attachfn
attach(measurement: Measurement, surface: Surface, data: Mapping[str, ArrayLike], *, doses: DoseMap | None = None) -> ModelSpec | Unsupported
``surface.model`` with the measurement's constraint appended, or why it cannot be.
model = attach(experiment, surface, data)
carryover_window_factorfn
carryover_window_factor(carryover: CarryoverKernel, theta: Theta, window_periods: int, *, treatment: str) -> Correction | Unsupported
``factor = 1 / Σ_{l < window_periods} w_l``: a short-window total scaled to steady state.
window = carryover_window_factor(geo, {"lam_a": lam}, 2, treatment="a")
chord_to_marginalfn
chord_to_marginal(surface: Surface, theta: Theta, treatment: str, d0: float, d1: float, at: float, *, held: Mapping[str, ArrayLike] | None = None) -> Correction | Unsupported
``factor = f'(at) / ((f(d1) − f(d0)) / (d1 − d0))``: read a chord as a marginal.
c = chord_to_marginal(surface, theta, "a", d0, d1, at)
combine_inverse_variancefn
combine_inverse_variance(targets: Sequence[float], ses: Sequence[float]) -> tuple[float, float]
Fixed-effect pooling of independent estimates of one quantity: ``(mean, se)``.
mean, se = combine_inverse_variance([m1.estimate, m2.estimate], [m1.se, m2.se])
combine_measurementsfn
combine_measurements(measurements: Sequence[Measurement], *, target: Estimand | None = None, corrections: Mapping[str, float] | None = None) -> tuple[float, float, tuple[LedgerLine, ...], dict[str, str]] | Unsupported
Pool measurements on one target estimand: ``(mean, se, ledger_lines, plan_statuses)``.
combined = combine_measurements([experiment, second])
constraint_forfn
constraint_for(measurement: Measurement, surface: Surface, data: Mapping[str, ArrayLike], *, doses: DoseMap | None = None) -> Constraint | Unsupported
The soft constraint that says the surface predicts ``measurement`` at its experiment.
c = constraint_for(experiment, surface, data)
derive_priorfn
derive_prior(measurements: Sequence[Measurement], spec: SurfaceSpec, treatment: str, *, beta_draws: ArrayLike, contribution_draws: ArrayLike, family: PriorFamily = 'lognormal', target: Estimand | None = None, corrections: Mapping[str, float] | None = None) -> CalibratedSpec | Unsupported
Turn measurements of a target estimand into an amplitude prior on ``spec`` (D6.2).
cal = derive_prior([experiment, second], spec, "a", beta_draws=beta_draws,
contribution_draws=contribution_draws, family=family)
design_factorfn
design_factor(beta_samples: ArrayLike, contribution_samples: ArrayLike) -> float
``mean(contribution / beta)`` over paired posterior draws.
f = design_factor(beta_draws, contribution_draws)
dose_path_accumulationfn
dose_path_accumulation(carryover: CarryoverKernel, theta: Theta, dose_path: ArrayLike, *, treatment: str) -> Correction | Unsupported
``factor = Σ_t (w ∗ x)_t / Σ_t x_t``: the effective dose delivered inside the path's window.
acc = dose_path_accumulation(geo, {"lam_a": lam}, path, treatment="a")
facet_offn
facet_of(line: LedgerLine) -> str | None
The facet a line stands in for, or ``None`` for a non-facet line.
print(FACET_PREFIX, "->", [facet_of(line) for line in baseline.lines])
fit_calibratedfn
fit_calibrated(spec: SurfaceSpec, panel: Panel, measurements: Sequence[Measurement], *, backend: Backend | str = 'laplace', draws: int = 1000, tune: int = 1000, chains: int = 4, seed: int | None = None, doses: DoseMap | None = None) -> FitResult | Unsupported
``surface.fit`` with every measurement attached as a soft constraint.
calibrated = fit_calibrated(spec, world.panel, [experiment], backend="laplace", draws=500, seed=0)
lognormal_from_momentsfn
lognormal_from_moments(mean: float, sd: float) -> tuple[float, float]
``(mu, sigma)`` of the lognormal with the given mean and standard deviation.
mu, sigma = lognormal_from_moments(amp_mean, amp_sd)
lognormal_mu_from_momentsfn
lognormal_mu_from_moments(value: float, se: float) -> float
``log(value) - sigma^2 / 2``: the log-scale location whose lognormal has mean ``value``.
mu = lognormal_mu_from_moments(2.0, 0.3)
lognormal_sigma_from_momentsfn
lognormal_sigma_from_moments(value: float, se: float) -> float
``sqrt(log(1 + (se / value)^2))``: the log-scale sigma of a lognormal with that mean and sd.
sigma = lognormal_sigma_from_moments(2.0, 0.3)
mean_sd_to_gammafn
mean_sd_to_gamma(mean: float, sd: float) -> tuple[float, float]
``(shape, rate)`` of the gamma with the given mean and standard deviation.
shape, rate = mean_sd_to_gamma(amp_mean, amp_sd)
resolvefn
resolve(plan: TransferPlan, *, transport: Verdict | None = None, corrections: Sequence[Correction] = ()) -> ResolvedTransfer
Compose the corrections over the plan and write the ledger (module docstring).
resolved: ResolvedTransfer = resolve(plan, corrections=[window, level])
variance_reweightfn
variance_reweight(se: float, n_source: int, n_target: int, *, icc: float | None = None, cluster_size: int | None = None) -> Correction
``se · sqrt(n_source / n_target) · sqrt(1 + (m − 1)·icc)``: the SE at another size.
vr = variance_reweight(0.3, 40, 80, icc=0.05, cluster_size=10)
Pool a corpus of studies; heterogeneity, bias terms, privacy-budgeted release.
BaujatDataspec
BaujatData(k: int, q_contribution: tuple[float, ...], influence: tuple[float, ...], detail: dict[str, str] = {})
Per-study ``Q`` contribution (x) against influence on the pooled estimate (y).
bj: BaujatData = baujat(y_asym, se)
Cellspec
Cell(records: tuple[tuple[str, float], ...], name: str = '')
The ``(contributor, value)`` pairs behind one publishable statistic.
small = Cell(records=(("c0", 0.4), ("c1", 0.5)), name="k-1")
Corpusspec
Corpus(records: tuple[StudyRecord, ...], name: str = 'corpus')
An ordered collection of ``StudyRecord`` with unique study ids.
corpus = Corpus(records=records, name="demo")
CorpusStoreclass
CorpusStore(root: str | Path) -> None
Records and corpora by content hash; corpora also findable by name.
store = CorpusStore(tmp)
DEFAULT_COLUMNSvalue
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
print("default mapping:", DEFAULT_COLUMNS)
EffectShrinkagespec
EffectShrinkage(effect: str, studies: tuple[str, ...], estimate: float, se: float, theta: float, interval: Interval, analytic: float, empirical: float | None)
One study effect's raw and shrunk estimate.
s0: EffectShrinkage = rows[0]
EggerTestspec
EggerTest(k: int, df: int, intercept: float, se: float, t: float, p: float, slope: float, slope_se: float, mass: float, interval: Interval, detail: dict[str, str] = {})
Egger's regression test for funnel asymmetry.
eg: EggerTest = egger(yy, se)
EpsilonChargespec
EpsilonCharge(release_id: str, epsilon: float, mechanism: Literal['laplace', 'gaussian'], delta: float = 0.0)
One ε charge against the ledger, named by the release that made it (timestamp-free).
line: EpsilonCharge = l2.charges[-1]
EpsilonLedgerspec
EpsilonLedger(budget: float, charges: tuple[EpsilonCharge, ...] = ())
An immutable ε budget with its sequence of charges (basic sequential composition).
ledger = EpsilonLedger(budget=policy.epsilon_total)
EpsilonSplitspec
EpsilonSplit(epsilon: float, parts: int, epsilons: tuple[float, ...], composition: str = 'sequential: the parts sum to epsilon')
An ε divided equally among ``parts`` releases under sequential composition.
split: EpsilonSplit = orthogonal_split(1.0, 3)
ForestDataspec
ForestData(rows: tuple[ForestRow, ...], mass: float, pooled_estimate: float, pooled_se: float | None, pooled_interval: Interval | None, prediction_interval: Interval | None, tau2: float | None, detail: dict[str, str] = {})
Per-study rows, the pooled row, and the prediction interval when available.
forest: ForestData = forest_data(corpus, None, re)
ForestRowspec
ForestRow(label: str, estimate: float, se: float, interval: Interval, weight: float | None = None)
One study on a forest plot: its estimate, Wald interval and pooling weight (share).
row: ForestRow = forest.rows[0]
FunnelContourspec
FunnelContour(mass: float, se: tuple[float, ...], lower: tuple[float, ...], upper: tuple[float, ...])
``pooled ± z_mass · se`` evaluated on a grid of standard errors.
c95: FunnelContour = next(c for c in fd.contours if c.mass == 0.95)
FunnelDataspec
FunnelData(k: int, y: tuple[float, ...], se: tuple[float, ...], precision: tuple[float, ...], pooled: float, contours: tuple[FunnelContour, ...], detail: dict[str, str] = {})
Study points (``y`` against ``se``) with pseudo-confidence contours around ``pooled``.
def outside_95(fd: FunnelData) -> int:
c95: FunnelContour = next(c for c in fd.contours if c.mass == 0.95)
z = (c95.upper[-1] - fd.pooled) / c95.se[-1]
return int(np.sum(np.abs(np.asarray(fd.y) - fd.pooled) > z * np.asarray(fd.se)))
Heterogeneityspec
Heterogeneity(q: float, df: int, p_value: float, i2: float, h2: float, k: int)
Cochran's ``Q`` on ``df = k − 1`` with its chi-square ``p``, ``I²`` and ``H²``.
het: Heterogeneity = heterogeneity(y, se)
LeaveOneOutspec
LeaveOneOut(method: Literal['fe', 'dl', 'pm', 'reml'], k: int, mass: float, full_estimate: float, full_se: float, full_interval: Interval, estimates: tuple[float, ...], ses: tuple[float, ...], intervals: tuple[Interval, ...], influence: tuple[float, ...], tau2s: tuple[float, ...], detail: dict[str, str] = {})
Pooled estimates with each study removed, and each study's influence.
loo: LeaveOneOut = leave_one_out(y_sym, se, method=method)
Mechanismcallable
Mechanism(*args, **kwargs)
mech: Mechanism = "laplace"
ModeratorDesignclass
ModeratorDesign(names: tuple[str, ...], columns: Array, means: Array) -> None
Centered moderator columns ``(n_records, n_moderators)``, their names, and the means subtracted — a plain carrier (Specs hold no arrays).
assert isinstance(design, ModeratorDesign)
NO_DUAL_READvalue
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
print("without dual reads:", v_no.status, "|", v_no.reason, "| matches NO_DUAL_READ:", NO_DUAL_READ in v_no.reason)
POOLABLE_QUANTITIESvalue
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
for q in POOLABLE_QUANTITIES.values():
print(f"{q.name:22s} dimensionless={str(q.dimensionless):5s} scale={q.scale:8s} {q.description}")
ParameterSummaryspec
ParameterSummary(name: str, mean: float, sd: float, interval: Interval, identified: bool = True, note: str = '')
Posterior mean, sd, and one provenance-carrying interval for a scalar parameter.
mu: ParameterSummary = res.mu
PoolMethodcallable
PoolMethod(*args, **kwargs)
method: PoolMethod = "reml"
PoolPriorsspec
PoolPriors(mu_scale: float = 1.0, tau_scale: float = 1.0, gamma_scale: float = 1.0, delta_scale: float = 1.0, tau_fixed: float | None = None)
Prior scales of the pool, all explicit.
priors = PoolPriors(mu_scale=2.0, tau_scale=0.5, gamma_scale=0.5)
PoolResultspec
PoolResult(family: str, quantity: str, scale: Literal['natural', 'log'], k: int, n_effects: int, mu: ParameterSummary, tau: ParameterSummary, gamma: tuple[ParameterSummary, ...], delta: ParameterSummary | None, thetas: tuple[ParameterSummary, ...], shrinkage: tuple[EffectShrinkage, ...], delta_verdict: Verdict | None, ... and 7 more fields)
Posterior summaries of one family's pool, every interval at ``mass``/``definition``.
res: PoolResult = out.result
PoolSpecspec
PoolSpec(family: str, moderators: tuple[str, ...] = (), bias_term: bool = False, effect_key: Literal['study', 'contributor'] = 'contributor', priors: PoolPriors = ..., parametrization: Literal['auto', 'centered', 'marginal'] = 'auto', mass: float = 0.95, definition: Literal['eti', 'hdi'] = 'eti')
What to pool and how.
spec = PoolSpec(family="fertilizer", moderators=("follow_up",), priors=priors, mass=0.9, definition="eti")
PoolableQuantityclass
PoolableQuantity(name: str, description: str, dimensionless: bool, scale: Scale) -> None
One entry of the catalog: what the quantity is, whether it is dimensionless by construction, and the scale it is pooled on.
entry: PoolableQuantity = poolable_quantity("elasticity")
Pooledclass
Pooled(result: PoolResult, posterior: Posterior, model: ModelSpec, data: dict[str, Array], spec: PoolSpec) -> None
``pool``'s return: the ``PoolResult`` plus the ``Posterior`` it was summarized from (``theta_<family>`` draws included under either parametrization), the ``ModelSpec`` and the data the backend saw — a plain carrier, so the draws stay out of the Spec.
assert isinstance(out, Pooled) and isinstance(out.posterior, Posterior)
PooledEstimatespec
PooledEstimate(estimate: float, se: float, interval: Interval, model: Literal['fixed', 'random'], tau2: float, tau_method: Literal['none', 'dl', 'pm', 'reml'], k: int, weights: tuple[float, ...], heterogeneity: Heterogeneity, knapp_hartung: bool = False, mass: float, tau_estimate: TauEstimate | None = None, ... and 1 more field)
The pooled mean, its standard error and interval, and everything that produced them.
fe: PooledEstimate = fixed_effect(y, se)
PriorTargetcallable
PriorTarget(*args, **kwargs)
t: PriorTarget = target
PrivacyPolicyspec
PrivacyPolicy(k: int = 3, dominance_p: float = 0.5, dominance_top_n: int = 2, dominance_top_p: float = 0.8, epsilon_total: float = 1.0)
Disclosure thresholds for one release programme.
policy = PrivacyPolicy(k=3, dominance_p=0.5, dominance_top_n=2, dominance_top_p=0.8, epsilon_total=1.0)
ReadKindcallable
ReadKind(*args, **kwargs)
def study(i: int, read: ReadKind) -> StudyRecord:
return StudyRecord(
study=f"s{i}", contributor=f"c{i % 3}", quantity="elasticity", estimate=Y[i], se=SE[i],
read=read, family="fertilizer", n=40 + 10 * i, moderators={"follow_up": float(4 + 2 * i)},
source=f"report-{i}",
)
Releasespec
Release(release_id: str, cell_name: str = '', statistic: Literal['clipped_mean'] = 'clipped_mean', value: float, interval: Interval, mass: float, n: int, clip_lower: float, clip_upper: float, n_clipped: int, sensitivity: float, epsilon: float, ... and 7 more fields)
A published clipped mean with every input that made it reproducible and auditable.
rel_g: Release = g[0]
StudyRecordspec
StudyRecord(study: str, contributor: str, quantity: str, estimate: float, se: float, read: Literal['model', 'experiment'], family: str, estimand_hash: str = '', n: int | None = None, moderators: dict[str, float] = {}, dimension: Dimension = Dimension(1), unit_scale: str = '', ... and 3 more fields)
One study's estimate of one poolable quantity, with its uncertainty and provenance.
StudyRecord(study="bad", contributor="c", quantity="elasticity", estimate=1.0, se=0.1,
read="model", family="fertilizer", dimension=D.outcome)
TauEstimatespec
TauEstimate(tau2: float, method: Literal['dl', 'pm', 'reml'], iterations: int, converged: bool, truncated: bool = False, tolerance: float = 1e-12, detail: dict[str, str] = {})
A between-study variance estimate with the record of how it was found.
t_reml: TauEstimate = estimates["reml"]
TauMethodcallable
TauMethod(*args, **kwargs)
tm: TauMethod = method
baujatfn
baujat(y: ArrayLike, se: ArrayLike) -> BaujatData
Baujat et al. (2002) under fixed-effect pooling.
bj: BaujatData = baujat(y_asym, se)
carry_forwardfn
carry_forward(previous: Release, cell: Cell, policy: PrivacyPolicy, *, churn_threshold: float) -> Release | Blocked
Re-use ``previous`` at no ε cost when contributor churn is below ``churn_threshold``.
carried = carry_forward(rel, same, policy, churn_threshold=0.25)
cell_from_recordsfn
cell_from_records(records: Iterable[Any], *, name: str = '') -> Cell
Build a ``Cell`` from objects exposing ``contributor`` and ``estimate`` (``StudyRecord``).
cell: Cell = cell_from_records(records, name="fertilizer/elasticity")
chargefn
charge(ledger: EpsilonLedger, *, release_id: str, epsilon: float, mechanism: Mechanism, delta: float = 0.0) -> EpsilonLedger | Blocked
A new ledger with the charge appended, or ``Blocked`` (ledger untouched) if over budget.
l1 = charge(ledger, release_id="q1", epsilon=0.3, mechanism=mech)
check_cellfn
check_cell(cell: Cell, policy: PrivacyPolicy) -> Verdict
``identified`` when the cell clears k-anonymity and both dominance rules; else ``blocked``.
ok: Verdict = check_cell(cell, policy)
contributor_totalsfn
contributor_totals(cell: Cell) -> dict[str, float]
Absolute contribution per contributor: the sum of ``|value|`` over its records.
print("totals:", contributor_totals(cell))
delta_identificationfn
delta_identification(corpus: Corpus, family: str | None = None) -> Verdict
``identified`` when at least one contributor in the family reports both a model and an experimental read; otherwise ``blocked`` with the reason ``"no contributor reports both reads"``.
v_yes: Verdict = delta_identification(with_dual, "fertilizer")
eggerfn
egger(y: ArrayLike, se: ArrayLike, *, mass: float = 0.95) -> EggerTest
Egger et al. (1997): OLS of ``z = y/se`` on precision ``1/se`` with an intercept.
eg: EggerTest = egger(yy, se)
fixed_effectfn
fixed_effect(y: ArrayLike, se: ArrayLike, *, mass: float = 0.95) -> PooledEstimate
Inverse-variance fixed-effect pooling; the interval is ``μ̂ ± z·se`` (``wald``).
fe: PooledEstimate = fixed_effect(y, se)
forest_datafn
forest_data(y_or_corpus: Any, se: ArrayLike | None, pooled: Any, *, labels: Sequence[str] | None = None, mass: float = 0.95) -> ForestData
Forest-plot data from ``(y, se)`` arrays or a ``Corpus`` (``se=None``) plus the pooled fit.
forest: ForestData = forest_data(corpus, None, re)
from_framefn
from_frame(df: DataFrame, *, columns: Mapping[str, str] | None = None, moderators: Sequence[str] = ()) -> tuple[StudyRecord, ...]
Build records from a frame under an explicit ``field → column`` mapping.
recs = from_frame(frame, columns={"study": "id", "contributor": "who", "estimate": "est", "se": "stderr"},
moderators=["weeks"])
funnel_datafn
funnel_data(y: ArrayLike, se: ArrayLike, pooled: Any, *, masses: Sequence[float] = (0.9, 0.95, 0.99), n_grid: int = 50) -> FunnelData
Points and closed-form contours ``pooled ± z_m · se`` for ``se`` from 0 to ``max(se)``.
print("bare float pooled also accepted:", funnel_data(y_sym, se, 0.5).pooled)
gaussian_sigmafn
gaussian_sigma(sensitivity: float, epsilon: float, delta: float, *, tol: float = 1e-12) -> float
Analytic Gaussian mechanism (Balle & Wang 2018): the smallest ``σ`` with
print("analytic still defined at ε=2:", round(gaussian_sigma(sens, 2.0, 1e-6), 4))
gaussian_sigma_classicalfn
gaussian_sigma_classical(sensitivity: float, epsilon: float, delta: float) -> float
Classical ``σ = Δ · sqrt(2 ln(1.25/δ)) / ε`` (Dwork & Roth 2014, Thm A.1; needs ε < 1).
print(f"ε={eps}: analytic σ = {gaussian_sigma(sens, eps, 1e-6):.4f} classical σ = {gaussian_sigma_classical(sens, eps, 1e-6):.4f}")
heterogeneityfn
heterogeneity(y: ArrayLike, se: ArrayLike) -> Heterogeneity
Cochran's ``Q`` about the fixed-effect mean, with ``I²`` and ``H²``.
het: Heterogeneity = heterogeneity(y, se)
jaccard_distancefn
jaccard_distance(a: frozenset[str] | set[str], b: frozenset[str] | set[str]) -> float
``1 − |a ∩ b| / |a ∪ b|``; zero for two empty sets.
churn = jaccard_distance(frozenset(rel.contributors), same.contributors)
laplace_scalefn
laplace_scale(sensitivity: float, epsilon: float) -> float
Laplace mechanism scale ``b = sensitivity / epsilon``.
print("laplace scale at ε=0.5:", laplace_scale(sens, 0.5))
leave_one_outfn
leave_one_out(y: ArrayLike, se: ArrayLike, *, method: PoolMethod = 'dl', mass: float = 0.95) -> LeaveOneOut
Re-pool without each study; influence is the shift in pooled-``se`` units.
loo: LeaveOneOut = leave_one_out(y_sym, se, method=method)
moderator_matrixfn
moderator_matrix(corpus: Corpus, names: Sequence[str]) -> ModeratorDesign | Unsupported
The centered moderator design of ``corpus`` for ``names``, in record order.
design = moderator_matrix(corpus, ["follow_up"])
normalizefn
normalize(records_or_frame: Sequence[StudyRecord] | Corpus | DataFrame, *, plans: Mapping[str, TransferPlan] | None = None, name: str = 'corpus', columns: Mapping[str, str] | None = None, moderators: Sequence[str] = ()) -> Corpus | Unsupported
Admit records into a ``Corpus`` or refuse with a reason naming the study.
refused = normalize([per_kg, per_lb])
orthogonal_splitfn
orthogonal_split(epsilon: float, parts: int) -> EpsilonSplit
Equal split ``epsilon / parts`` per release; sequential composition sums to ``epsilon``.
split: EpsilonSplit = orthogonal_split(1.0, 3)
poolfn
pool(spec: PoolSpec, corpus: Corpus, *, backend: str = 'laplace', draws: int = 2000, seed: int | None = 0) -> Pooled | Unverified | Unsupported
Fit the pool and summarize it.
out = pool(spec, corpus, backend="laplace", draws=1000, seed=0)
pool_modelfn
pool_model(spec: PoolSpec, corpus: Corpus) -> tuple[ModelSpec, dict[str, Array]] | Unsupported
The model and its data for ``spec`` on ``corpus``.
built = pool_model(spec, corpus)
poolable_quantityfn
poolable_quantity(name: str) -> PoolableQuantity
The catalog entry for ``name``; ``ValueError`` naming the catalog otherwise.
entry: PoolableQuantity = poolable_quantity("elasticity")
prediction_intervalfn
prediction_interval(pooled: PooledEstimate, mass: float | None = None) -> Interval
``μ̂ ± t_{k−2} · sqrt(τ² + se²)``: where a new study's true effect is expected to lie.
pi = prediction_interval(kh)
prior_from_poolfn
prior_from_pool(result: PoolResult, *, target: PriorTarget = 'predictive', family: PriorFamilyOut = 'normal') -> tuple[Prior, LedgerLine]
Turn a ``PoolResult`` into a ``core.Prior`` plus the ledger line that records it.
prior, line = prior_from_pool(res, target=t, family="normal")
random_effectsfn
random_effects(y: ArrayLike, se: ArrayLike, *, tau_method: TauMethod = 'dl', knapp_hartung: bool = False, mass: float = 0.95) -> PooledEstimate
Random-effects pooling with ``τ²`` by DL, PM or REML; optional Knapp–Hartung interval.
kh = random_effects(y, se, tau_method="reml", knapp_hartung=True)
record_from_resultfn
record_from_result(result: EstimandResult, *, study: str, contributor: str, read: ReadKind, family: str, quantity: str | None = None, moderators: Mapping[str, float] | None = None, period: str = '', source: str = '') -> StudyRecord
A ``StudyRecord`` from a realized estimand.
rec = record_from_result(result, study="fit-1", contributor="c0", read="model", family="fertilizer")
record_from_summaryfn
record_from_summary(*, study: str, contributor: str, quantity: str, estimate: float, se: float, read: ReadKind, family: str, n: int | None = None, moderators: Mapping[str, float] | None = None, dimension: Dimension | None = None, unit_scale: str = '', estimand_hash: str = '', period: str = '', source: str = '', detail: Mapping[str, str] | None = None) -> StudyRecord
A ``StudyRecord`` from hand-entered numbers (a published table, a report).
ratio = record_from_summary(study="neg", contributor="c9", quantity="response_ratio", estimate=-0.2,
se=0.1, read="experiment", family="fertilizer")
releasefn
release(cell: Cell, policy: PrivacyPolicy, ledger: EpsilonLedger, *, release_id: str, epsilon: float, clip: tuple[float, float], seed: int, mechanism: Mechanism = 'laplace', delta: float = 1e-06, mass: float = 0.95) -> tuple[Release, EpsilonLedger] | Blocked
Publish the cell's clipped mean under ``policy`` and ``ledger``, or refuse.
out = release(cell, policy, ledger, release_id="r-laplace", epsilon=0.5, clip=(0.0, 1.0), seed=0, mechanism="laplace")
reml_log_likelihoodfn
reml_log_likelihood(y: ArrayLike, se: ArrayLike, tau2: float) -> float
The restricted log-likelihood (up to a constant) at ``tau2``; for checking ``tau_reml``.
ll = reml_log_likelihood(y, se, t_reml.tau2)
se_from_summaryfn
se_from_summary(summary: Summary) -> tuple[float, str]
``(se, rule)``: interval half-width over ``z`` for ``wald``/``eti``, else the sd.
print(se_from_summary(wald_summary))
tau_dersimonian_lairdfn
tau_dersimonian_laird(y: ArrayLike, se: ArrayLike) -> TauEstimate
DerSimonian–Laird closed form: ``(Q − df) / (Σw − Σw²/Σw)``, truncated at zero.
estimates = {m: f(y, se) for m, f in (("dl", tau_dersimonian_laird), ("pm", tau_paule_mandel), ("reml", tau_reml))}
tau_paule_mandelfn
tau_paule_mandel(y: ArrayLike, se: ArrayLike) -> TauEstimate
Paule–Mandel: the ``τ²`` solving ``Q(τ²) = k − 1``; zero when ``Q(0) ≤ k − 1``.
estimates = {m: f(y, se) for m, f in (("dl", tau_dersimonian_laird), ("pm", tau_paule_mandel), ("reml", tau_reml))}
tau_remlfn
tau_reml(y: ArrayLike, se: ArrayLike) -> TauEstimate
REML: the root of the restricted score ``Σw²(y−μ̂)² − Σw + Σw²/Σw`` on ``τ² ≥ 0``.
estimates = {m: f(y, se) for m, f in (("dl", tau_dersimonian_laird), ("pm", tau_paule_mandel), ("reml", tau_reml))}
SBC, coverage, posterior predictive checks, refutations, specification curves.
Backtestspec
Backtest(spec_hash: str, spec_name: str, panel_hash: str, origins: tuple[int, ...], horizon: int, n_units: int, mass: float, definition: Literal['eti', 'hdi', 'wald'], alpha: float, scores: tuple[HorizonScore, ...], forecasts: tuple[OriginForecast, ...], failures: tuple[OriginFailure, ...] = (), ... and 4 more fields)
A rolling-origin backtest: per-horizon scores, per-origin forecasts, and the failures.
assert isinstance(bt, Backtest)
Benchmarkspec
Benchmark(covariate: str, k_d: float, k_y: float, r2_dxj_x: float, r2_yxj_dx: float, r2_dz_x: float, r2_yz_dx: float, bounds: BiasBounds)
Partial-R² bounds for a confounder ``k_d`` / ``k_y`` times an observed covariate.
assert isinstance(bm, Benchmark)
BiasBoundsspec
BiasBounds(estimate: float, se: float, df: int, r2_yz_dx: float, r2_dz_x: float, reduce: bool, bias: float, adjusted_estimate: float, adjusted_se: float, adjusted_t: float, adjusted_interval: Interval)
The omitted-variable bias a confounder of stated strength would produce.
assert isinstance(b, BiasBounds)
Correctioncallable
Correction(*args, **kwargs)
correction: Correction = "holm"
CoverageResultspec
CoverageResult(n: int, mass: float, definition: Literal['eti', 'hdi', 'wald'], alpha: float, n_fitted: int, n_failed_fits: int, failure_reasons: tuple[str, ...] = (), nominal_region: AcceptanceRegion, parameters: tuple[ParameterCoverage, ...], failed_parameters: tuple[str, ...], passed: bool, provenance: dict[str, Any] = {})
Per-parameter coverage plus the bookkeeping: ``n_fitted + n_failed_fits == n``.
assert isinstance(cov, CoverageResult)
DEFAULT_STATISTICSvalue
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
print("default statistics:", list(DEFAULT_STATISTICS))
EstimandCoveragespec
EstimandCoverage(name: str, estimand_hash: str, n: int, covered: int, mass: float, definition: Literal['eti', 'hdi', 'wald'], region: AcceptanceRegion, passed: bool, true_values: tuple[float, ...] = ())
Coverage of one estimand: ``covered`` of ``n`` realized intervals held the true value (realized at the truth through the same path). ``true_values`` is kept so a plot can show the spread of truths across replications.
assert isinstance(e, EstimandCoverage)
EstimandCoverageResultspec
EstimandCoverageResult(n: int, mass: float, definition: Literal['eti', 'hdi', 'wald'], alpha: float, n_fitted: int, n_failed_fits: int, failure_reasons: tuple[str, ...] = (), nominal_region: AcceptanceRegion, estimands: tuple[EstimandCoverage, ...], failed_estimands: tuple[str, ...], passed: bool, provenance: dict[str, Any] = {})
Per-estimand coverage; ``n_fitted + n_failed_fits == n``. A replication on which an estimand could not be realized (``Unsupported`` / ``Blocked``) counts as a failed fit for that estimand and is named in ``failure_reasons``.
assert isinstance(ec, EstimandCoverageResult)
ExpectedSigncallable
ExpectedSign(*args, **kwargs)
sign: ExpectedSign = "positive"
FitSettingsspec
FitSettings(backend: str = 'laplace', draws: int = 200, tune: int = 200, chains: int = 1)
How each specification is fitted: backend name and sampler sizes.
spec_l, settings_l = apply_option(small.spec, FitSettings(draws=40), axes[0].options[1])
Fittercallable
Fitter(*args, **kwargs)
fit_one: Fitter = fitter
FrozenPredictorclass
FrozenPredictor(spec: SurfaceSpec, posterior: Posterior, conventions: dict[str, dict[str, float]] = <factory>, provenance: dict[str, Any] = <factory>) -> None
A fit's spec, posterior, and nuisance conventions, frozen for replay on new panels.
assert isinstance(fp, FrozenPredictor)
HorizonScorespec
HorizonScore(step: int, n: int, mae: float, rmse: float, crps: float, coverage: float, coverage_region: AcceptanceRegion, passed: bool, bias: float)
Scores for one step ahead, pooled over origins and units (``n`` cells).
assert isinstance(sc, HorizonScore)
ImpliedIndependencespec
ImpliedIndependence(x: str, y: str, given: tuple[str, ...] = ())
``x`` is independent of ``y`` given ``given`` — a claim the graph makes.
assert isinstance(implication, ImpliedIndependence)
IndependenceCheckspec
IndependenceCheck(implication: ImpliedIndependence, result: IndependenceResult, adjusted_p: float, refuted: bool)
One implication, tested: the evidence against it and the verdict on it.
assert isinstance(check, IndependenceCheck)
LearningReportspec
LearningReport(parameters: tuple[ParameterLearning, ...], n_prior: int, n_posterior: int, seed: int | None, threshold: float, prior_dominated: tuple[str, ...], passed: bool)
Per-parameter learning plus the prior-dominated set.
assert isinstance(lr, LearningReport)
OriginFailurespec
OriginFailure(origin: int, reason: str)
An origin whose refit produced no posterior, with the backend's reason.
print("failures:", [OriginFailure.model_validate(f.model_dump()) for f in bt.failures], "| passed:", bt.passed)
OriginForecastspec
OriginForecast(origin: int, periods: tuple[float, ...], units: tuple[str, ...], observed: tuple[tuple[float, ...], ...], mean: tuple[tuple[float, ...], ...], lower: tuple[tuple[float, ...], ...], upper: tuple[tuple[float, ...], ...], converged: bool)
One origin's forecast: the posterior-mean path, its interval, and what was observed.
assert isinstance(f0, OriginForecast)
PPCResultspec
PPCResult(n_draws: int, seed: int | None, mass: float, alpha: float, statistics: tuple[StatisticCheck, ...], skipped: tuple[str, ...] = (), extreme_statistics: tuple[str, ...], provenance: dict[str, Any] = {})
Every statistic's check plus the run's bookkeeping. ``skipped`` names statistics whose observed value was not finite (a lag-1 autocorrelation on a one-period panel, say) — reported, never silently dropped.
assert isinstance(ppc, PPCResult)
ParameterCoveragespec
ParameterCoverage(name: str, n: int, covered: int, mass: float, definition: Literal['eti', 'hdi', 'wald'], region: AcceptanceRegion, passed: bool)
Coverage of one (scalar slice of a) parameter over ``n`` fitted replications.
assert isinstance(p, ParameterCoverage)
ParameterLearningspec
ParameterLearning(name: str, prior_mean: float, prior_sd: float, posterior_mean: float, posterior_sd: float, contraction: float, overlap: float, shift: float, prior_dominated: bool)
Prior and posterior moments of one scalar parameter and what moved.
assert isinstance(p, ParameterLearning)
ParameterRanksspec
ParameterRanks(name: str, n: int, n_ranks: int, bins: int, ranks: tuple[int, ...], histogram: tuple[int, ...], chi2_statistic: float, chi2_p_value: float, ecdf_statistic: float, ecdf_band: float, alpha: float, passed: bool)
The rank histogram of one (scalar slice of a) parameter and its two uniformity checks.
assert isinstance(r, ParameterRanks)
PriorPredictivespec
PriorPredictive(spec_hash: str, panel_hash: str, n: int, seed: int | None, n_units: int, n_periods: int, treatments: tuple[str, ...], outcome_sd: float, expected_sign: Literal['positive', 'negative', 'any'], magnitude_factor: float, tolerance: float, mass: float, ... and 7 more fields)
Prior-implied responses at the observed doses and the share that is implausible.
assert isinstance(pp, PriorPredictive)
Refutationspec
Refutation(kind: Literal['placebo_treatment', 'permutation', 'random_subset', 'added_noise'], estimand_name: str, estimand_hash: str, treatment: str, original: float, original_interval: Interval, refuted_mean: float, refuted_sd: float, refuted_interval: Interval, refuted_estimates: tuple[float, ...] = (), p_value: float, n: int, ... and 7 more fields)
The outcome of one refutation check, with the rule it passed or failed under.
assert isinstance(placebo, Refutation)
RefutationKindcallable
RefutationKind(*args, **kwargs)
kind: RefutationKind = placebo.kind
ResidualReportspec
ResidualReport(n_units: int, n_periods: int, n: int, alpha: float, residual_sd: float, units: tuple[UnitResiduals, ...], tests: tuple[ResidualTest, ...], skipped: tuple[str, ...] = (), flagged: tuple[str, ...], provenance: dict[str, Any] = {})
Per-unit summaries and the pooled tests. ``skipped`` names tests that could not run on this panel (a Ljung–Box lag beyond the series, Shapiro above its size limit), with why.
assert isinstance(rr, ResidualReport)
ResidualTestspec
ResidualTest(name: str, statistic: float, p_value: float | None = None, n: int, lag: int | None = None, note: str = '')
One test: statistic, p-value (``None`` when the test has no reference distribution, as Durbin–Watson), the ``n`` it was computed on, and the lag where one applies.
assert isinstance(t, ResidualTest)
RobustnessValuespec
RobustnessValue(estimate: float, se: float, df: int, q: float, alpha: float, t: float, r2_yd_x: float, rv: float, rv_alpha: float)
Cinelli–Hazlett robustness values for one linear estimate.
assert isinstance(rv, RobustnessValue)
SBCResultspec
SBCResult(spec: SBCSpec, model_hash: str, refit_model_hash: str, alpha_per_parameter: float, n_simulations: int, n_fitted: int, n_failed_fits: int, failure_reasons: tuple[str, ...] = (), parameters: tuple[ParameterRanks, ...], failed_parameters: tuple[str, ...], passed: bool)
Every parameter's rank check plus the bookkeeping of the run.
assert isinstance(out, SBCResult)
SBCSpecspec
SBCSpec(n_simulations: int, draws: int = 200, rank_draws: int = 19, bins: int | None = None, seed: int = 0, backend: str = 'laplace', parameters: tuple[str, ...] = (), alpha: float = 0.05)
How many replications, how big each fit, and how ranks are resolved.
spec = SBCSpec(n_simulations=24, draws=60, rank_draws=19, seed=3, alpha=0.05)
ShiftedPosteriorclass
ShiftedPosterior(posterior: Posterior, estimate: Summary, bias: Summary, shifted: Summary) -> None
Estimate draws with a bias draw subtracted from each — the bias-adjusted posterior.
assert isinstance(s, ShiftedPosterior)
Simulatorcallable
Simulator(*args, **kwargs)
simulate: Simulator = my_simulator
SpecCurvespec
SpecCurve(estimand_name: str, estimand_hash: str, base_spec_hash: str, axes: tuple[SpecificationAxis, ...], rows: tuple[SpecRow, ...], n_total: int, n_dropped: int, max_specs: int, definition: Literal['eti', 'hdi', 'wald'], mass: float, settings: FitSettings, seed: int | None = None)
Every specification's realized estimand, the cap applied, and the estimand realized.
assert isinstance(curve, SpecCurve)
SpecCurveSummaryspec
SpecCurveSummary(n: int, n_failed: int, n_converged: int, median: float | None = None, iqr_lower: float | None = None, iqr_upper: float | None = None, minimum: float | None = None, maximum: float | None = None, share_excluding_zero: float | None = None, share_positive: float | None = None)
Median / IQR of the estimates and the share of specifications excluding zero.
assert isinstance(summary, SpecCurveSummary)
SpecOptionspec
SpecOption(label: str, spec_update: dict[str, Any] = {}, fit_update: dict[str, int | str] = {})
One alternative on an axis: a label plus the spec fields and fit settings it changes.
axes = (
SpecificationAxis(name="kernel", options=(
SpecOption(label="hill"),
SpecOption(label="logistic", spec_update={"kernels": {"a": LogisticKernel(reference_dose=50.0)}}),
)),
SpecificationAxis(name="intercept_scale", options=(
SpecOption(label="tight", spec_update={"intercept_scale": 0.5}),
SpecOption(label="wide", spec_update={"intercept_scale": 5.0}, fit_update={"draws": 50}),
)),
)
SpecRowspec
SpecRow(index: int, labels: dict[str, str], spec_hash: str, spec_name: str, estimate: float | None = None, interval: Interval | None = None, sd: float | None = None, n_draws: int = 0, converged: bool = False, failure: str | None = None)
One fitted specification: its option labels, the realized point, and its fate.
assert isinstance(row, SpecRow)
SpecificationAxisspec
SpecificationAxis(name: str, options: tuple[SpecOption, ...])
One analyst choice: a name and at least one labelled option.
axes = (
SpecificationAxis(name="kernel", options=(
SpecOption(label="hill"),
SpecOption(label="logistic", spec_update={"kernels": {"a": LogisticKernel(reference_dose=50.0)}}),
)),
SpecificationAxis(name="intercept_scale", options=(
SpecOption(label="tight", spec_update={"intercept_scale": 0.5}),
SpecOption(label="wide", spec_update={"intercept_scale": 5.0}, fit_update={"draws": 50}),
)),
)
Statisticcallable
Statistic(*args, **kwargs)
custom: dict[str, Statistic] = {"spread": spread, "nan": lambda y: float("nan")}
StatisticCheckspec
StatisticCheck(name: str, observed: float, interval: Interval, p_value: float, p_two_sided: float, n: int, alpha: float, extreme: bool)
One statistic: observed value, predictive interval, and the tail p-values over ``n`` replicates. ``p_value`` is ``P(T_rep >= T_obs)``; ``p_two_sided`` is ``min(1, 2 · min(P(T_rep >= T_obs), P(T_rep <= T_obs)))``.
assert isinstance(s, StatisticCheck)
StructureRefutationspec
StructureRefutation(graph_hash: str = '', graph_name: str = '', checks: tuple[IndependenceCheck, ...] = (), alpha: float, effect_threshold: float, correction: Literal['none', 'holm', 'benjamini_hochberg'] = 'holm', n_tested: int, n_refuted: int, implicated: tuple[tuple[str, str, float], ...] = (), untested: tuple[tuple[str, str], ...] = (), verdict: Verdict, detail: dict[str, str] = {})
What the data says about the graph's own claims.
assert isinstance(report, StructureRefutation)
SupportsWorldclass
SupportsWorld(*args, **kwargs)
What a replication needs: the spec to fit, the panel to fit it on, the truth to score against. ``axiom.sim.SurfaceWorld`` satisfies it; so does ``WorldView``.
assert isinstance(view, WorldView) and isinstance(view, SupportsWorld)
TippingPointspec
TippingPoint(threshold: float, certainty: float, n_draws: int, bias_grid: tuple[float, ...], probabilities: tuple[float, ...], decision_at_zero: bool, probability_at_zero: float, flipped: bool, bias: float | None, probability: float | None, interval: Interval | None)
The smallest hypothesised bias at which a threshold decision flips.
assert isinstance(tp, TippingPoint)
UnitResidualsspec
UnitResiduals(unit: str, n: int, mean: float, sd: float)
Residual mean and sd for one unit over its periods.
assert isinstance(u, UnitResiduals)
WeakIdReportspec
WeakIdReport(parameters: tuple[str, ...], n_draws: int, correlation: tuple[tuple[float, ...], ...], condition_number: float, rho_threshold: float, condition_threshold: float, ratio_threshold: float, high_pairs: tuple[tuple[str, str, float], ...], prior_sd: dict[str, float], posterior_sd: dict[str, float], sd_ratio: dict[str, float], unlearned: tuple[str, ...], ... and 5 more fields)
Posterior geometry of the structural parameters and what it flags.
assert isinstance(rep, WeakIdReport)
WorldFactorycallable
WorldFactory(*args, **kwargs)
make_world: WorldFactory = factory
WorldViewclass
WorldView(spec: SurfaceSpec, panel: Panel, theta: dict[str, Array]) -> None
A (spec, panel, theta) triple — a world seen through a possibly different spec.
assert isinstance(view, WorldView) and isinstance(view, SupportsWorld)
added_noisefn
added_noise(result: FitResult, sd_fraction: float = 0.5, n: int = 10, seed: int = 0, *, estimand: Estimand | None = None, treatment: str | None = None, alpha: float = 0.1, backend: str = 'laplace', draws: int = 200, chains: int = 1, definition: IntervalDefinition = 'eti', mass: float = 0.9, panel: Panel | None = None) -> Refutation | Unsupported
Refit ``n`` times with Gaussian noise of sd ``sd_fraction · σ̂`` added to the outcome.
noisy = added_noise(res, sd_fraction=0.5, n=3, seed=0, alpha=0.1, draws=DRAWS, panel=world.panel)
apply_optionfn
apply_option(base: SurfaceSpec, settings: FitSettings, option: SpecOption) -> tuple[SurfaceSpec, FitSettings]
``base`` and ``settings`` with one option applied (a fresh, validated ``SurfaceSpec``).
spec_l, settings_l = apply_option(small.spec, FitSettings(draws=40), axes[0].options[1])
benchmarkfn
benchmark(estimate: float, se: float, df: int, *, covariate: str, r2_dxj_x: float, r2_yxj_dx: float, k_d: float = 1.0, k_y: float = 1.0, reduce: bool = True, mass: float = 0.95) -> Benchmark | Unsupported
Bound a confounder by an observed covariate (Cinelli & Hazlett 2020, §4.4; sensemakr).
bm = benchmark(**DARFUR, covariate="female", r2_dxj_x=0.00916, r2_yxj_dx=0.11, k_d=1.0, k_y=1.0)
bhattacharyyafn
bhattacharyya(mean_a: float, sd_a: float, mean_b: float, sd_b: float) -> float
Bhattacharyya coefficient of two normals (1 = identical, 0 = disjoint).
print("same:", bhattacharyya(0, 1, 0, 1), "| far:", round(bhattacharyya(0, 1, 10, 1), 8), "| wider:", round(bhattacharyya(0, 1, 0, 2), 4))
bias_boundsfn
bias_bounds(estimate: float, se: float, df: int, *, r2_yz_dx: float, r2_dz_x: float, reduce: bool = True, mass: float = 0.95) -> BiasBounds
Price a confounder with partial R² ``(r2_yz_dx, r2_dz_x)`` (Cinelli & Hazlett 2020, §4.1).
b = bias_bounds(**DARFUR, r2_yz_dx=0.1246, r2_dz_x=0.0092, mass=0.95)
coveragefn
coverage(world_factory: WorldFactory, *, n: int, mass: float = 0.9, definition: IntervalDefinition = 'eti', parameters: Sequence[str] | None = None, fit: Fitter | None = None, backend: str = 'laplace', draws: int = 200, seed: int = 0, alpha: float = 0.01) -> CoverageResult
Fixed-truth coverage of the ``mass`` ``definition`` interval over ``n`` replications.
cov = coverage(make_world, n=10, mass=0.9, definition="eti", fit=fit_one, seed=5, alpha=0.01)
crpsfn
crps(draws: ArrayLike, observed: ArrayLike) -> Array
Empirical CRPS per cell: ``E|X − y| − ½ E|X − X'|`` from draws along the first axis.
print("crps of an ensemble:", np.round(crps(ensemble, np.zeros(4)), 3), "| point forecast = |error|:", crps(np.full((1, 2), 2.0), np.full(2, 0.5)))
default_binsfn
default_bins(n: int, n_ranks: int, *, min_expected: int = 10) -> int
The largest divisor of ``n_ranks + 1`` that expects at least ``min_expected`` ranks per bin from ``n`` ranks; ``2`` when none does (a two-bin histogram is always defined).
print("default bins for N=60, L=19:", default_bins(60, 19), "| N=200:", default_bins(200, 19))
default_estimandfn
default_estimand(spec: SurfaceSpec, panel: Panel, treatment: str | None = None, *, dose: float | None = None) -> Estimand
The contrast ``E[Y(dose)] − E[Y(0)]`` of one treatment: the per-unit average (``Level(unit="individual")``, uniform unit weights), cumulative over the whole horizon.
est = default_estimand(small.spec, small.panel)
draw_priorfn
draw_prior(model: ModelSpec, rng: Generator, *, n: int = 1) -> dict[str, Array]
``n`` joint draws from the model's prior, constrained, shaped ``(n, *param_shape)``.
theta = draw_prior(model, rng, n=3)
estimand_coveragefn
estimand_coverage(world_factory: WorldFactory, *, estimands: Sequence[Estimand], n: int, mass: float = 0.9, definition: IntervalDefinition = 'eti', fit: Fitter | None = None, backend: str = 'laplace', draws: int = 200, seed: int = 0, alpha: float = 0.01, truth_world: Callable[[SupportsWorld], SupportsWorld] | None = None) -> EstimandCoverageResult
Coverage of realized estimands over ``n`` replications.
ec = estimand_coverage(hill_world, estimands=[contrast], n=6, draws=60, seed=1)
forecastfn
forecast(spec: SurfaceSpec, posterior: Posterior, panel: Panel, *, origin: int, horizon: int, conventions: Conventions | None = None, seed: int | None = None, noise: bool = False) -> PredictiveDraws
Predictive draws for periods ``[origin, origin + horizon)`` with the carryover state carried from the periods before ``origin``.
fc = forecast(carry.spec, truth, carry.panel, origin=8, horizon=3)
freezefn
freeze(result: FitResult, *, panel: Panel | None = None) -> FrozenPredictor | Unsupported
Freeze a ``FitResult``'s spec, posterior, and conventions; ``Unsupported`` without a posterior.
fp = freeze(fitted)
implied_independenciesfn
implied_independencies(graph: CausalGraph, *, max_conditioning: int = 4) -> tuple[tuple[ImpliedIndependence | None, tuple[str, str]], ...]
One testable claim per non-adjacent pair, with a set the graph says separates them.
claims = implied_independencies(proposed)
learningfn
learning(model: ModelSpec, posterior: Posterior | FitResult, *, parameters: Sequence[str] | None = None, n_prior: int = 4000, threshold: float = 0.1, seed: int | None = 0) -> LearningReport | Unsupported
Contraction, overlap and shift of every (or the named) free parameter.
lr = learning(toy, learned, n_prior=4000, seed=1, threshold=0.1)
misspecifyfn
misspecify(world: SupportsWorld, spec: SurfaceSpec) -> WorldView
``world``'s panel and truth, to be fitted with ``spec`` instead of the generating spec.
view = misspecify(carry, naive_spec)
partial_r2fn
partial_r2(t_statistic: float, df: int) -> float
Partial R² of a regressor from its t statistic: ``t² / (t² + df)``.
print("partial R2 of t=4.19 on df=783:", round(partial_r2(0.0973 / 0.0232, 783), 4))
permutationfn
permutation(result: FitResult, n: int = 20, seed: int = 0, *, estimand: Estimand | None = None, treatment: str | None = None, alpha: float = 0.05, backend: str = 'laplace', draws: int = 200, chains: int = 1, definition: IntervalDefinition = 'eti', mass: float = 0.9, panel: Panel | None = None) -> Refutation | Unsupported
Null distribution of the estimand from ``n`` refits with the doses shuffled across cells.
perm = permutation(res, n=5, seed=0, alpha=0.2, draws=DRAWS, panel=world.panel)
placebo_treatmentfn
placebo_treatment(result: FitResult, *, estimand: Estimand | None = None, treatment: str | None = None, seed: int = 0, n: int = 1, backend: str = 'laplace', draws: int = 200, chains: int = 1, definition: IntervalDefinition = 'eti', mass: float = 0.9, panel: Panel | None = None) -> Refutation | Unsupported
Reassign the treatment's dose series across units, refit, and expect no effect.
placebo = placebo_treatment(res, seed=0, draws=DRAWS, panel=world.panel)
posterior_predictivefn
posterior_predictive(result: FitResult, *, statistics: Mapping[str, Statistic] | None = None, n_draws: int = 200, seed: int | None = 0, mass: float = 0.9, alpha: float = 0.05) -> PPCResult | Unsupported
Posterior predictive checks of ``result`` on its own fitted panel.
ppc = posterior_predictive(res, n_draws=80, seed=1)
prior_momentsfn
prior_moments(prior: Prior, resolved: Mapping[str, float] | None = None) -> tuple[float, float]
``(mean, sd)`` of a prior family; a hyperparameter naming a parameter is looked up in ``resolved``. ``KeyError`` when it is not there; ``(value, 0)`` for ``fixed``.
print("normal(1, 3):", prior_moments(Prior(family="normal", hyper={"mu": 1, "sigma": 3})))
prior_predictivefn
prior_predictive(spec: SurfaceSpec, panel: Panel, *, n: int = 200, seed: int | None = 0, expected_sign: ExpectedSign = 'positive', magnitude_factor: float = 10.0, tolerance: float = 0.2, mass: float = 0.9) -> PriorPredictive | Unsupported
Push ``n`` prior draws through ``Surface.forward`` at the panel's doses and summarize.
pp = prior_predictive(world.spec, world.panel, n=60, seed=4, expected_sign=sign, magnitude_factor=10.0)
random_subsetfn
random_subset(result: FitResult, fraction: float = 0.7, n: int = 10, seed: int = 0, *, estimand: Estimand | None = None, treatment: str | None = None, alpha: float = 0.1, backend: str = 'laplace', draws: int = 200, chains: int = 1, definition: IntervalDefinition = 'eti', mass: float = 0.9, panel: Panel | None = None) -> Refutation | Unsupported
Refit on ``n`` random subsets holding ``fraction`` of the units; expect stability.
subset = random_subset(res, fraction=0.6, n=3, seed=0, alpha=0.1, draws=DRAWS, panel=world.panel)
rank_uniformityfn
rank_uniformity(name: str, ranks: ArrayLike, *, n_ranks: int, alpha: float, bins: int | None = None) -> ParameterRanks
Chi-square and ECDF-difference checks of ``ranks`` on ``{0, ..., n_ranks}`` (module docstring). ``ValueError`` for an empty rank vector, a rank off the support, or ``bins`` that do not divide ``n_ranks + 1``.
uniform = rank_uniformity("u", rng.integers(0, 20, size=200), n_ranks=19, alpha=0.05)
realized_pointfn
realized_point(estimand: Estimand, result: FitResult, *, definition: IntervalDefinition, mass: float, seed: int | None = None) -> RealizedDraws | Unsupported
``realize(..., keep_draws=True)`` with every non-result outcome as ``Unsupported``.
pt = realized_point(est, base_fit, definition="eti", mass=0.9)
refute_structurefn
refute_structure(graph: CausalGraph, data: Dataset, *, alpha: float = 0.05, effect_threshold: float = 0.0, correction: Correction = 'holm', max_conditioning: int = 4) -> StructureRefutation | Unsupported
Test every independence the graph implies, and report the edges any failure implicates.
report = refute_structure(proposed, trial, alpha=0.05, correction=correction)
residualsfn
residuals(result: FitResult, *, lags: tuple[int, ...] = (1, 5, 10), alpha: float = 0.05) -> ResidualReport | Unsupported
The residual report of ``result`` on its fitted panel (module docstring).
rr = residuals(res, lags=(1, 4, 20), alpha=0.05)
robustness_valuefn
robustness_value(estimate: float, se: float, df: int, *, q: float = 1.0, alpha: float = 0.05) -> RobustnessValue
``RV_q`` and ``RV_{q,α}`` for a linear estimate (Cinelli & Hazlett 2020, §4.2).
rv = robustness_value(**DARFUR, q=1.0, alpha=0.05)
rolling_originfn
rolling_origin(spec: SurfaceSpec, panel: Panel, *, origins: Sequence[int], horizon: int, backend: str = 'laplace', draws: int = 200, tune: int = 200, chains: int = 1, seed: int | None = 0, mass: float = 0.9, definition: IntervalDefinition = 'eti', alpha: float = 0.01) -> Backtest | Unsupported
Refit at each origin, forecast ``horizon`` periods through the one forward, and score.
bt = rolling_origin(carry.spec, carry.panel, origins=(8, 10), horizon=2, draws=DRAWS, chains=1, seed=0)
sbcfn
sbc(model: ModelSpec, data_template: DataDict, *, spec: SBCSpec, simulate: Simulator | None = None, refit: ModelSpec | None = None) -> SBCResult | Unsupported
Run SBC on ``model`` over ``data_template`` (the covariate columns; the outcome column is overwritten every replication).
out = sbc(model, data, spec=spec, simulate=simulate)
sbc_poolfn
sbc_pool(pool_spec: PoolSpec, corpus: Corpus, *, sbc_spec: SBCSpec, refit: PoolSpec | None = None) -> SBCResult | Unsupported
SBC for the meta-analytic pool: ``meta.pool_model`` gives the model and its data (the records' standard errors, loadings, moderators); the outcome column is re-simulated from the pool's likelihood at each prior draw. ``refit`` pools with a different spec (the negative control). ``Unsupported`` when the corpus cannot be laid out.
pooled = sbc_pool(PoolSpec(family="f", priors=PoolPriors(tau_fixed=0.3)), corpus.build(), sbc_spec=SBCSpec(n_simulations=6, draws=40))
sbc_surfacefn
sbc_surface(spec: SurfaceSpec, panel_template: Panel, *, sbc_spec: SBCSpec, refit: SurfaceSpec | None = None) -> SBCResult | Unsupported
SBC for a response surface: the outcome is simulated through ``Surface.forward`` at the prior draw plus the spec's likelihood noise, then refitted with ``refit`` (default: the same spec). ``panel_template`` supplies the doses and layout; its outcome is ignored.
surf = sbc_surface(world.spec, world.panel, sbc_spec=SBCSpec(n_simulations=6, draws=40))
shift_posteriorfn
shift_posterior(draws: ArrayLike | Posterior, *, name: str | None = None, bias_draws: ArrayLike | None = None, bias_mean: float = 0.0, bias_sd: float = 0.0, definition: IntervalDefinition = 'eti', mass: float = 0.9, seed: int | None = None) -> ShiftedPosterior
Subtract a bias from every estimate draw without refitting.
s = shift_posterior(draws, bias_mean=0.5, bias_sd=0.5, mass=0.9, seed=3)
simulate_outcomefn
simulate_outcome(model: ModelSpec, data: DataDict, theta: Mapping[str, Array], rng: Generator) -> Array
One outcome array from ``model``'s likelihood at ``mean(theta)`` on ``data``.
y = simulate_outcome(model, data, one, rng)
specification_curvefn
specification_curve(base: SurfaceSpec, panel: Panel, axes: Sequence[SpecificationAxis], *, estimand: Estimand | None = None, backend: str = 'laplace', draws: int = 200, tune: int = 200, chains: int = 1, seed: int | None = 0, max_specs: int = 64, definition: IntervalDefinition = 'eti', mass: float = 0.9) -> SpecCurve
Fit every combination of the axes' options and realize ``estimand`` under each.
curve = specification_curve(small.spec, small.panel, axes, draws=40, chains=1, seed=3)
tipping_pointfn
tipping_point(estimate_draws: ArrayLike | Posterior, decision_threshold: float, bias_grid: ArrayLike, *, name: str | None = None, certainty: float = 0.5, definition: IntervalDefinition = 'eti', mass: float = 0.9) -> TippingPoint
Walk ``bias_grid`` (sorted by absolute size) and find where the decision flips.
tp = tipping_point(rng.normal(1.0, 0.1, size=4000), 0.5, np.linspace(-1.0, 1.0, 41), certainty=0.5)
training_panelfn
training_panel(panel: Panel, n_periods: int) -> Panel
The first ``n_periods`` periods of ``panel`` (every unit, roles unchanged).
train = training_panel(carry.panel, 8)
truth_producerfn
truth_producer(world: SupportsWorld) -> FitResult
A ``FitResult`` whose posterior is a two-draw point mass at the world's truth, so an estimand realized on it is the *true* estimand computed through ``estimands.realize``. The generating spec is used, not a mis-specified view's.
producer = truth_producer(w)
weak_identificationfn
weak_identification(result: FitResult | Posterior, model: ModelSpec | None = None, *, parameters: Sequence[str] | None = None, rho_threshold: float = 0.9, condition_threshold: float = 1000.0, ratio_threshold: float = 0.9, saturation_share: float = 0.1, saturation_eps: float = 0.001) -> WeakIdReport | Unsupported
Correlation ridge, condition number, prior-vs-posterior sd ratio, bound saturation.
rep = weak_identification(post, toy, rho_threshold=0.9)
Fluent builders for graphs, priors, and corpora.
BuildErrorerror
A builder was asked to build before it had what it needs, or was given a bad value.
try:
f.require("z", builder="demo")
except BuildError as e:
print("BuildError:", e)
Builderclass
Builder(*args, **kwargs)
Anything that can assemble a ``Spec`` from what it has collected.
print("builders satisfy the protocol:", isinstance(SurfaceBuilder(), Builder), isinstance(PriorBuilder(), Builder))
EntitySpecvalue
Represent a PEP 604 union type
t: EntitySpec = VariableBuilder().kind(kind).name("x").dimension(D.currency).measured_in("USD").build()
Fieldsclass
Fields(values: Mapping[str, Any] = <factory>) -> None
An immutable ``name → value`` mapping with a copy-on-write ``with_``.
f = Fields({"a": 1})
GraphBuilderclass
GraphBuilder(fields: Fields = Fields(values=mappingproxy({}))) -> None
``GraphBuilder().edge("Z", "X").edge("X", "Y").unmeasured("U").build()``.
estimand = GraphBuilder().edge("x", "y").estimand(Treatment(name="x", dimension=D.currency, unit="USD"), Outcome(name="y", dimension=D.outcome), dose=10.0, window=TimeWindow(start=0, stop=4))
MetaBuilderclass
MetaBuilder(fields: Fields = Fields(values=mappingproxy({}))) -> None
Collect study records and pooling choices.
print("from a frame:", [r.study for r in MetaBuilder().from_frame(corpus.to_frame()).build().records])
MomentFamilycallable
MomentFamily(*args, **kwargs)
family: MomentFamily = "lognormal"
PriorBuilderclass
PriorBuilder(fields: Fields = Fields(values=mappingproxy({}))) -> None
Collect a family and hyperparameters; ``build()`` is a ``core.Prior``.
print("builders satisfy the protocol:", isinstance(SurfaceBuilder(), Builder), isinstance(PriorBuilder(), Builder))
SchedulePatterncallable
SchedulePattern(*args, **kwargs)
pattern: SchedulePattern = "pulse"
StudyBuilderclass
StudyBuilder(fields: Fields = Fields(values=mappingproxy({}))) -> None
Collect a study's plan; build the design spec, candidate, schedule, or measurement.
StudyBuilder().size(n_periods=4).schedule("alternating").build_schedule()
SurfaceBuilderclass
SurfaceBuilder(fields: Fields = Fields(values=mappingproxy({}))) -> None
Fluent construction of a ``SurfaceSpec``; every method returns a new builder.
print("builders satisfy the protocol:", isinstance(SurfaceBuilder(), Builder), isinstance(PriorBuilder(), Builder))
VariableBuilderclass
VariableBuilder(fields: Fields = Fields(values=mappingproxy({}))) -> None
``VariableBuilder().treatment("x").dimension(D.currency).unit("USD").build()``.
t: EntitySpec = VariableBuilder().kind(kind).name("x").dimension(D.currency).measured_in("USD").build()
VariableKindcallable
VariableKind(*args, **kwargs)
kind: VariableKind = "treatment"
Domain vocabulary — marketing is one adapter, not the core.
Channelspec
Channel(name: str, dimension: Dimension | None = None, unit: str | None = None, description: str = '')
The thing you can intervene on. Its dimension is the dose's dimension.
print("Channel is Treatment:", Channel is Treatment, "| Geo is Unit:", Geo is Unit, "| KPI is Outcome:", KPI is Outcome)
EXPOSUREvalue
A product of declared base dimensions raised to rational exponents.
print(impressions("tv_impressions"), "| EXPOSURE == D.exposure_count:", EXPOSURE == D.exposure_count)
Geospec
Geo(name: str, dimension: Dimension | None = None, unit: str | None = None, description: str = '', kind: Literal['individual', 'cluster', 'aggregate'] = 'individual')
The randomization / observation unit: a plot, a patient, a region.
print("Channel is Treatment:", Channel is Treatment, "| Geo is Unit:", Geo is Unit, "| KPI is Outcome:", KPI is Outcome)
KPIspec
KPI(name: str, dimension: Dimension | None = None, unit: str | None = None, description: str = '', aggregation: Literal['sum', 'mean', 'rate'] = 'sum')
What you are trying to move.
print("Channel is Treatment:", Channel is Treatment, "| Geo is Unit:", Geo is Unit, "| KPI is Outcome:", KPI is Outcome)
MarketingRolesspec
MarketingRoles(kpi: str, channels: tuple[str, ...], geo: str = 'geo', date: str = 'date', impressions: tuple[str, ...] = (), controls: tuple[str, ...] = (), currency: str = 'USD', kpi_dimension: Literal['currency', 'outcome'] = 'outcome', kpi_unit: str | None = None)
Which columns of a marketing frame play which role.
roles = MarketingRoles(kpi="revenue", channels=("tv", "search"), geo="geo", date="week", impressions=("tv_impressions",), controls=("holiday",), kpi_dimension="currency")
agronomyvalue
The agronomy adapter: plot / rate / yield / optimum over the general core.
print("agronomy's public surface:", len(agronomy.__all__), "names")
contributionfn
contribution(result: FitResult, channel: str, window: TimeWindow | tuple[int, int] | None = None, *, mass: float = 0.9, definition: IntervalDefinition = 'hdi', seed: int | None = None) -> EstimandResult | Unsupported | Blocked
The channel's contribution to the KPI: ``KPI at observed spend − KPI at zero spend``, summed over geos and the window (the ``contrast`` estimand, KPI units).
for fn in (roas, roi, contribution, marginal_roas):
out = fn(res, "tv", mass=0.9, seed=0)
assert isinstance(out, EstimandResult)
print(f"{out.estimand_name:16s} {out.kind:9s} {out.summary.mean:9.3f} {out.summary.interval} unit={out.unit}")
impressionsfn
impressions(name: str) -> Covariate
An impressions / exposures covariate on the ``exposure_count`` base.
print(impressions("tv_impressions"), "| EXPOSURE == D.exposure_count:", EXPOSURE == D.exposure_count)
marginal_roasfn
marginal_roas(result: FitResult, channel: str, window: TimeWindow | tuple[int, int] | None = None, *, mass: float = 0.9, definition: IntervalDefinition = 'hdi', seed: int | None = None) -> EstimandResult | Unsupported | Blocked
The derivative of the window's KPI with respect to a common shift of the channel's observed spend (the ``marginal`` estimand). Needs a currency-valued KPI.
windowed = marginal_roas(res, "tv", window=(2, 8))
marketingvalue
The marketing adapter: channel / spend / geo / KPI over the general core.
print(" ", marketing.roas.__doc__.strip().splitlines()[0])
marketing_specfn
marketing_spec(panel: Panel, channels: Sequence[str] | None = None, *, name: str = 'marketing', kernel: str = 'hill', carryover: str | None = 'geometric', max_lag: int = 4, intercept: InterceptKind = 'hierarchical', seasonality: tuple[float, int] | None = None, trend: bool = False, amplitude_scale: float = 1.0) -> SurfaceSpec
A ``SurfaceSpec`` for a marketing panel through ``build.SurfaceBuilder``.
spec = marketing_spec(panel, seasonality=(4.0, 1), trend=True)
panel_from_marketing_framefn
panel_from_marketing_frame(df: DataFrame, roles: MarketingRoles) -> Panel
A ``Panel`` from a wide geo × date frame; columns with no role are dropped.
panel = panel_from_marketing_frame(wide, roles)
panel_from_mfffn
panel_from_mff(df: DataFrame, roles: MarketingRoles, *, variable: str = 'variable', value: str = 'value') -> Panel
The MFF long format (one row per ``geo × date × variable``) → ``Panel``.
print("MFF long format gives the same panel:", panel_from_mff(long, roles).content_hash() == panel.content_hash())
roasfn
roas(result: FitResult, channel: str, window: TimeWindow | tuple[int, int] | None = None, *, mass: float = 0.9, definition: IntervalDefinition = 'hdi', seed: int | None = None) -> EstimandResult | Unsupported | Blocked
Return on ad spend: ``(KPI at observed spend − KPI at zero) / observed spend``.
bad = roas(count_fit, "tv")
roifn
roi(result: FitResult, channel: str, window: TimeWindow | tuple[int, int] | None = None, *, mass: float = 0.9, definition: IntervalDefinition = 'hdi', seed: int | None = None) -> EstimandResult | Unsupported | Blocked
Return on investment ``ROAS − 1``: the same draws shifted, re-summarized.
for fn in (roas, roi, contribution, marginal_roas):
out = fn(res, "tv", mass=0.9, seed=0)
assert isinstance(out, EstimandResult)
print(f"{out.estimand_name:16s} {out.kind:9s} {out.summary.mean:9.3f} {out.summary.interval} unit={out.unit}")
role_mapfn
role_map(roles: MarketingRoles) -> RoleMap
The general ``RoleMap`` the marketing roles translate to.
rm = role_map(roles)
spendfn
spend(channel: str, *, currency: str = 'USD') -> Dose
The spend dose of ``channel``: currency-dimensioned, costed in its own currency.
print(spend("tv"))
Plot helpers for the diagnostics that have a canonical picture.
availablefn
available() -> bool
True when plotly can be imported.
print("plotly available:", available())
backtest_plotfn
backtest_plot(backtest: Any) -> Any | Unsupported
Per-horizon scores of a rolling-origin backtest — MAE, RMSE, CRPS — and the interval coverage against the nominal mass.
fig = backtest_plot(bt)
boundaryfn
boundary(rule: Any, *, height: float = 360.0) -> Any | Unsupported
The stopping threshold at each look, which is the protocol sentence. (``design``)
boundary(rule)
causal_graphfn
causal_graph(graph: Any, *, height: float = 420.0) -> Any | Unsupported
The graph itself: nodes, directed edges, and the bidirected ones.
figure = causal_graph(graph)
convergencefn
convergence(report: Any, *, height: float = 380.0) -> Any | Unsupported
R-hat per parameter against the threshold that decides the run. (``infer``)
convergence(report)
correctionsfn
corrections(applied: Sequence[Any], *, height: float = 360.0) -> Any | Unsupported
What each correction did to the number, in order. (``calibrate``)
corrections(applied)
coverage_plotfn
coverage_plot(result: Any) -> Any | Unsupported
Per-parameter coverage against the nominal mass with each parameter's exact acceptance region (``region.lower``/``region.upper``).
print("coverage_plot traces:", len(coverage_plot(cov).data))
forestfn
forest(data: Any) -> Any | Unsupported
A forest plot: one row per study with its interval, the pooled row, and the prediction interval when the data carries one.
print("forest traces:", len(forest(forest_data(y, se, pooled, labels=list("abcde"))).data))
funnelfn
funnel(data: Any) -> Any | Unsupported
A funnel plot: study estimates against their standard errors (precision upward) with the pseudo-confidence contours around the pooled estimate.
print("funnel traces:", len(funnel(funnel_data(y, se, pooled)).data))
intervalsfn
intervals(named: Any, *, height: float = 360.0, unit: str = '') -> Any | Unsupported
Several named intervals on one axis. (``core``)
intervals(
{
"40 mg": Interval(lower=-16.7, upper=-8.1, definition="eti", mass=0.9),
"20 mg": Interval(lower=-6.2, upper=-3.4, definition="eti", mass=0.9),
"10 mg": Interval(lower=-2.1, upper=1.4, definition="eti", mass=0.9),
},
unit="mmHg",
)
marginal_curvefn
marginal_curve(result: Any, treatment: str = '', *, n_grid: int = 25, mass: float = 0.9, definition: IntervalDefinition = 'eti', window: TimeWindow | None = None, seed: int | None = None) -> Any | Unsupported
``d outcome / d dose`` against dose, with its posterior band.
slope = marginal_curve(res, "a", n_grid=8, mass=0.8)
panel_coveragefn
panel_coverage(panel: Any, *, height: float = 400.0, max_units: int = 60) -> Any | Unsupported
Which unit was observed in which period. (``data``)
panel_coverage(panel)
recoveryfn
recovery(truth: Any, estimated: Any, *, height: float = 400.0) -> Any | Unsupported
Estimated against true, for a world where the truth is known. (``sim``)
recovery(truth, estimated)
response_curvefn
response_curve(result: Any, treatment: str = '', *, n_grid: int = 25, mass: float = 0.9, definition: IntervalDefinition = 'eti', window: TimeWindow | None = None, seed: int | None = None) -> Any | Unsupported
Expected outcome against the dose of ``treatment``, **with its posterior band**.
fig = response_curve(res, "a", n_grid=8, mass=0.8)
sbc_ranksfn
sbc_ranks(result: Any, *, columns: int = 3) -> Any | Unsupported
Rank histograms per parameter from an SBC run, with the expected count per bin and the binomial band around it; failed parameters are titled as such.
print("sbc_ranks traces:", len(sbc_ranks(sbc_like, columns=2).data))
spec_curve_plotfn
spec_curve_plot(curve: Any) -> Any | Unsupported
The specification curve: estimates sorted with their intervals, coloured by whether the interval excludes zero; failed specifications are omitted and counted in the title.
print("spec_curve_plot traces:", len(spec_curve_plot(curve).data))
stabilityfn
stability(report: Any, *, height: float = 380.0) -> Any | Unsupported
How often each edge came back, split by which way it pointed.
stability(report)
transferfn
transfer(plan: Any, *, height: float = 320.0) -> Any | Unsupported
Which facets differ between source and target, and what that costs. (``estimands``)
transfer(plan)
unrolledfn
unrolled(system: Any, *, height: float = 420.0) -> Any | Unsupported
A dynamic system after unrolling, laid out so depth is time. (``dynamics``)
unrolled(unroll(system, periods=4))
Templated HTML / PPTX / PDF reports over the viz layer.
AnyBlockcallable
AnyBlock(*args, **kwargs)
Runtime representation of an annotated type.
blocks: tuple[AnyBlock, ...] = (
Heading(text="Per-arm detail", level=2),
Paragraph(text="One page per arm, generated in a loop.", emphasis=True),
Metric(source="contrast", label="Effect", unit="units"),
Table(source="arms"),
Divider(),
PageBreak(),
LedgerBlock(source="ledger"),
)
Dividerspec
Divider(block: Literal['divider'] = 'divider')
A horizontal rule.
blocks: tuple[AnyBlock, ...] = (
Heading(text="Per-arm detail", level=2),
Paragraph(text="One page per arm, generated in a loop.", emphasis=True),
Metric(source="contrast", label="Effect", unit="units"),
Table(source="arms"),
Divider(),
PageBreak(),
LedgerBlock(source="ledger"),
)
FORMATSvalue
Built-in immutable sequence.
print("formats:", FORMATS, "| page sizes:", sorted(GEOMETRY))
Figurespec
Figure(block: Literal['figure'] = 'figure', source: str, caption: str = '', height: float | None = None, full_page: bool = False)
A figure, named by the context key that holds it.
broken = Report(name="b", title="b",
sections=(Section(title="s", blocks=(Figure(source="sigma"),)),))
Formatcallable
Format(*args, **kwargs)
chosen: Format = fmt
GEOMETRYvalue
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
print("formats:", FORMATS, "| page sizes:", sorted(GEOMETRY))
Headingspec
Heading(block: Literal['heading'] = 'heading', text: str, level: int = 2)
A section heading. ``level`` 1-3, matching the theme's three heading sizes.
blocks: tuple[AnyBlock, ...] = (
Heading(text="Per-arm detail", level=2),
Paragraph(text="One page per arm, generated in a loop.", emphasis=True),
Metric(source="contrast", label="Effect", unit="units"),
Table(source="arms"),
Divider(),
PageBreak(),
LedgerBlock(source="ledger"),
)
LedgerBlockspec
LedgerBlock(block: Literal['ledger'] = 'ledger', source: str, caption: str = '', show_detail: bool = False)
The assumption trail: a sequence of ``core.LedgerLine``.
blocks: tuple[AnyBlock, ...] = (
Heading(text="Per-arm detail", level=2),
Paragraph(text="One page per arm, generated in a loop.", emphasis=True),
Metric(source="contrast", label="Effect", unit="units"),
Table(source="arms"),
Divider(),
PageBreak(),
LedgerBlock(source="ledger"),
)
Metricspec
Metric(block: Literal['metric'] = 'metric', source: str, label: str, unit: str = '', precision: int | None = None)
One number, with its interval when it has one.
blocks: tuple[AnyBlock, ...] = (
Heading(text="Per-arm detail", level=2),
Paragraph(text="One page per arm, generated in a loop.", emphasis=True),
Metric(source="contrast", label="Effect", unit="units"),
Table(source="arms"),
Divider(),
PageBreak(),
LedgerBlock(source="ledger"),
)
PageBreakspec
PageBreak(block: Literal['page_break'] = 'page_break')
Start a new page or slide here.
blocks: tuple[AnyBlock, ...] = (
Heading(text="Per-arm detail", level=2),
Paragraph(text="One page per arm, generated in a loop.", emphasis=True),
Metric(source="contrast", label="Effect", unit="units"),
Table(source="arms"),
Divider(),
PageBreak(),
LedgerBlock(source="ledger"),
)
PageSizecallable
PageSize(*args, **kwargs)
size: PageSize = house.page
Paragraphspec
Paragraph(block: Literal['paragraph'] = 'paragraph', text: str, emphasis: bool = False)
Body text. Supports ``{placeholders}`` and a little inline markup.
blocks: tuple[AnyBlock, ...] = (
Heading(text="Per-arm detail", level=2),
Paragraph(text="One page per arm, generated in a loop.", emphasis=True),
Metric(source="contrast", label="Effect", unit="units"),
Table(source="arms"),
Divider(),
PageBreak(),
LedgerBlock(source="ledger"),
)
Reportspec
Report(name: str, title: str, subtitle: str = '', theme: Theme = ..., sections: tuple[Section, ...] = (), footer: str = '')
A styled, repeatable report: sections of blocks that name their data.
broken = Report(name="b", title="b",
sections=(Section(title="s", blocks=(Figure(source="sigma"),)),))
ReportBuilderclass
ReportBuilder(name: str, title: str) -> None
A frozen builder for a ``Report``. Every method returns a new one.
template = (
ReportBuilder("dose_readout", "Dose-response readout")
.subtitle("Fitted {as_of}. Every number carries its interval.")
.footer("Generated by axiom.report — template {template_hash}")
.section("Headline", summary="What the fit says, and how sure it is.")
.paragraph("The response **saturates**: the marginal effect at {top_dose:.0f} is "
"already within rounding of zero.")
.metric("contrast", "Expected outcome at 50", unit="units")
.metric("sigma", "Residual sd", unit="units")
.section("Curves", summary="Both figures carry a 90 % band, because they cannot not.")
.figure("response", caption="Expected outcome against dose")
.figure("marginal", caption="Marginal effect — where the band straddles zero, the model "
"does not know whether the next unit helps")
.section("Detail")
.table("arms", caption="Observed means by dose bucket", precision=2)
.divider()
# ...
ResolvedBlockvalue
Represent a PEP 604 union type
block: ResolvedBlock = metrics[0]
ResolvedFigureclass
ResolvedFigure(figure: Any, caption: str = '', height: float | None = None, full_page: bool = False) -> None
A plotly figure, its caption, and how tall it should be drawn.
figures = [b for s in resolved.sections for b in s.blocks if isinstance(b, ResolvedFigure)]
ResolvedLedgerclass
ResolvedLedger(lines: tuple[LedgerLine, ...], caption: str = '', show_detail: bool = False) -> None
The assumption trail, as lines of kind / statement / assumption.
ledgers = [b for s in resolved.sections for b in s.blocks if isinstance(b, ResolvedLedger)]
ResolvedMetricclass
ResolvedMetric(label: str, value: float, text: str, interval: Interval | None = None, unit: str = '') -> None
A number, its label, and the interval it came with — if it had one.
metrics = [b for s in resolved.sections for b in s.blocks if isinstance(b, ResolvedMetric)]
ResolvedReportclass
ResolvedReport(name: str, title: str, subtitle: str, footer: str, theme: Any, sections: tuple[ResolvedSection, ...], source_hash: str) -> None
Everything the renderers need and nothing they do not.
assert isinstance(resolved, ResolvedReport)
ResolvedSectionclass
ResolvedSection(title: str, summary: str, blocks: tuple[ResolvedBlock, ...] = (), breaks: frozenset[int] = <factory>) -> None
One section's title, optional summary, and its blocks in reading order.
assert isinstance(section, ResolvedSection)
ResolvedTableclass
ResolvedTable(columns: tuple[str, ...], rows: tuple[tuple[str, ...], ...], caption: str = '', truncated: int = 0) -> None
Header row, body rows already formatted to strings, and what was truncated.
tables = [b for s in resolved.sections for b in s.blocks if isinstance(b, ResolvedTable)]
ResolvedTextclass
ResolvedText(text: str, level: int = 0, emphasis: bool = False) -> None
A heading or paragraph with its placeholders filled in.
texts = [b for s in resolved.sections for b in s.blocks if isinstance(b, ResolvedText)]
Sectionspec
Section(title: str, blocks: tuple[Heading | Paragraph | Figure | Table | Metric | LedgerBlock | Divider | PageBreak, ...] = (), summary: str = '')
A titled run of blocks. Each section starts a new page or slide.
broken = Report(name="b", title="b",
sections=(Section(title="s", blocks=(Figure(source="sigma"),)),))
Tablespec
Table(block: Literal['table'] = 'table', source: str, caption: str = '', columns: tuple[str, ...] = (), max_rows: int = 20, precision: int = 3)
A table, named by the context key that holds it.
blocks: tuple[AnyBlock, ...] = (
Heading(text="Per-arm detail", level=2),
Paragraph(text="One page per arm, generated in a loop.", emphasis=True),
Metric(source="contrast", label="Effect", unit="units"),
Table(source="arms"),
Divider(),
PageBreak(),
LedgerBlock(source="ledger"),
)
Themespec
Theme(name: str = 'axiom', font: str = 'Helvetica', font_fallback: str = 'system-ui, -apple-system, sans-serif', mono_font: str = 'Courier', base_size: float = 11.0, title_size: float = 24.0, heading_sizes: tuple[float, float, float] = (18.0, 14.0, 12.0), text_color: str = '#1c1c1c', muted_color: str = '#5b6472', accent_color: str = '#2f7fd1', background_color: str = '#ffffff', rule_color: str = '#d8dbe0', ... and 6 more fields)
Fonts, colours and geometry for a rendered report.
house = Theme(
name="house", font="Helvetica", accent_color="#b5453b", muted_color="#6b7280",
palette=("#b5453b", "#2f7fd1", "#3aa17e"), base_size=10.5, title_size=26.0,
page="a4", slide="standard", figure_height=280.0,
)
figure_pngfn
figure_png(figure: Any, theme: Theme, *, height: float, scale: float = 2.0) -> bytes | Unsupported
A themed figure as PNG bytes, or a typed refusal naming the missing extra.
png = figure_png(figures[0].figure, house, height=220.0)
missingfn
missing(report: Report, context: Mapping[str, object]) -> tuple[str, ...]
Context keys the report needs and the context does not have, in reading order.
print("missing now:", missing(template, context))
placeholdersfn
placeholders(text: str) -> tuple[str, ...]
The ``{name}`` fields in a template string, in order, without duplicates.
print("placeholders in the subtitle:", placeholders(template.subtitle))
renderfn
render(report: Report, context: Mapping[str, object], format: Format = 'html', *, inline_plotly: bool = True) -> str | bytes | Unsupported
Fill ``report`` from ``context`` and render it. ``str`` for HTML, ``bytes`` otherwise.
partial = render(template, {"as_of": "x"}, "html")
render_htmlfn
render_html(resolved: ResolvedReport, *, inline_plotly: bool = True) -> str | Unsupported
A complete, self-contained HTML document. Figures stay interactive.
html = render_html(resolved)
render_pdffn
render_pdf(resolved: ResolvedReport) -> bytes | Unsupported
The report as a PDF, as bytes. ``Unsupported`` without the extra.
paged = render_pdf(resolved_generated)
render_pptxfn
render_pptx(resolved: ResolvedReport) -> bytes | Unsupported
The report as a ``.pptx`` file, as bytes. ``Unsupported`` without the extra.
deck = render_pptx(resolved_generated)
resolvefn
resolve(report: Report, context: Mapping[str, object]) -> ResolvedReport | Unsupported
Fill the template from the context, or say exactly which keys are absent.
resolved = resolve(template, context)
writefn
write(report: Report, context: Mapping[str, object], path: str, format: Format | None = None, *, inline_plotly: bool = True) -> str | Unsupported
Render and write to ``path``, returning the path. Format is inferred from the suffix.
written = write(template, context, str(out / f"readout.{fmt}"), chosen)
How to read this
Layers, and why they matter to you
foundation
core, data, io. Specs, dimensions, intervals, panels, and the analysis round-trip. Pure numpy / scipy / pandas / pydantic. If you only ever import these, axiom is a very small dependency.
sampler seam
infer. The one place an optional dependency is allowed to
live. Laplace works with no extras; NumPyro and PyMC sit behind
pip install "axiom[numpyro]" and
pip install "axiom[pymc]", and four NUTS samplers agree to within
0.12 posterior standard deviations over one shared log density.
domain
dynamics, identify, discover, estimands, surface, sim. The causal and functional vocabulary — a language for systems that are simultaneous or unroll over time, graphs and the searches that learn one from data, nine-facet estimands, response kernels, and the synthetic worlds that make every claim in the test suite checkable against a known truth.
the pillars
design, calibrate, meta. Peers, with no cross-imports except design ← surface and calibrate ← estimands. You can use any one of them without dragging in the others.
composition
diagnose, build, adapters, viz, report. Everything that composes the layers below — model checking, fluent builders, the domain adapters, plots, and templated HTML / PPTX / PDF reports.
Imports point down only
A contract test walks the import graph and fails the build if an arrow ever points back up. That is what keeps the foundation usable on its own and lets the pillars be adopted one at a time.
Getting the full documentation
This page is a map with a worked line per symbol, not a
reference manual: no parameter tables, no prose on edge cases. The Sphinx
documentation — full signatures, complete docstrings, and the notebook series for
each subpackage — builds from the repository with make docs, and every
subpackage’s API page links its own notebook series.
git clone https://github.com/redam94/axiom cd axiom uv sync --group dev make docs # Sphinx, builds with zero warnings make notebooks # execute every notebook under nbs/ make gates # the twelve contract tests