Bayesian causal inference for zero-inflated outcomes — GPU-accelerated joint hurdle BCF with SBC calibration
Project description
Pytyche
Faster, calibrated heterogeneous-treatment-effect (HTE) discovery in a sequential / adaptive targeting loop — built for real-world online experiments where most users don't convert and the conversion signal is noisy.
Pytyche fits a Bayesian Causal Forest (BCF) over hurdle-distributed outcomes (revenue, conversion, click-through, churn), recalibrates its posterior intervals against simulation-based ground truth, and feeds the calibrated posterior into a sequential allocator that drives the next round of a multi-round experiment. The end-to-end loop runs on a single GPU. The same primitives also work for a single-shot RCT or observational analysis, but the API is shaped for multi-round growth experiments where you narrow allocation toward responder segments over time while keeping controls in every segment so measurement stays honest.
Documentation: https://tradcliffe2.gitlab.io/tyche/ · First fit: tutorial · Overview: concept doc · Status: alpha (0.1.0.dev) — MIT
What's inside
| Why this is here | |
|---|---|
| GPU joint hurdle BCF on top of bartz | Roughly 5× to 60× faster than the StochTree CPU backend at production scales on real workloads — the canonical joint hurdle fit lands 4.5–8.6× at n=750k (the heaviest case: two coupled forests with shared topology), while the single-channel continuous and binary paths hit 17–63× from n=250k through n=2M. Full grid in the bench publication snapshot. What used to be a CPU-week of simulation-based calibration (SBC) sweeps now runs overnight. |
| Shared-tree topology with dual leaves | A single tree structure jointly fits both channels of a hurdle outcome (probit conversion + log-severity), instead of training two independent forests — following Linero et al.'s shared Bayesian forests. The structural prior carries information across channels and stabilizes per-segment group-average treatment effect (GATE) estimates at low conversion rates — exactly the regime online experiments live in. |
| Perturb + rotation MCMC proposals (research / alpha) | Augments bartz's grow/prune chain with structural tree moves following Pratola et al., adapted to the GPU kernels. Aimed at reducing autocorrelation in deep-tree posteriors. |
| Programmatic DGP generators built for tailored SBC recalibration | A small typed grammar (pytyche.generators.scenarios) parameterizes the data-generating process (DGP); sweeps over it produce the SBC tables that recalibrate the posterior at your operating regime. Agentic DGP generation is in development. |
| Sequential targeting as a first-class API citizen | The sequential experiment loop (pt.sequential_experiment) runs Thompson allocation with controls retention; built-in power simulations sweep over the same DGP classes used for calibration. See sequential-targeting. |
| Honest-uncertainty contracts | pytyche.contracts separates observed data from ground truth at the type level, so analysis code structurally cannot peek at what it shouldn't see. See statistical-honesty. |
| Interpretable policy segmentations alongside the posterior | Each round of sequential targeting compresses the BCF CATE posterior into a shallow policy tree (cf. Hitsch / Misra / Zhang 2024) — a stakeholder-facing decision surface, not just a model object, that drives the next round's allocation and gives reviewers an auditable handle on the model's segment-level decisions. First-class public API + custom-criterion + robustness layer on the roadmap. |
Why it exists
BCF posteriors at production scale are structurally miscalibrated — the regularization prior of Bayesian Additive Regression Trees (BART, the model family BCF builds on) narrows credible intervals for better point estimation at the cost of honest uncertainty. The standard fixes (more chains, better sampler) don't help. The actual fix is empirical: SBC across realistic DGPs, then isotonic recalibration. That requires hundreds of full posterior fits, which on CPU is a multi-week research project per design surface.
GPU BCF makes that loop cheap enough to run routinely. Speed isn't the contribution — speed is what unlocks the contribution. With the loop cheap, calibration becomes something you do per-deployment instead of per-publication; with calibrated posteriors, sequential targeting becomes safe enough to actually deploy.
Install
# Recommended — GPU JAX (CUDA 12)
uv add 'pytyche[gpu]'
# CPU-only (fully functional; the first fit warns once when no CUDA device is found)
uv add pytyche
Verify the runtime with python -c "import pytyche as pt; pt.check_setup()".
For a development checkout (editable, all extras, bartz fork as a submodule):
git clone --recurse-submodules https://gitlab.com/tradcliffe2/tyche
cd tyche
uv sync --all-extras
Quick start
Fit the canonical model on an 800-visitor synthetic dataset in ~20 seconds on JAX-CPU:
import os; os.environ["JAX_PLATFORMS"] = "cpu" # omit for GPU
import pytyche as pt
bundle = pt.generate(n_visitors=800, segments={
"responders": {"pct": 0.4, "base_conv": 0.08, "treatment_effect": 0.10,
"aov_mu": 3.5, "aov_sigma": 0.5, "treatment_aov_mu_shift": 0.15},
"non_responders": {"pct": 0.6, "base_conv": 0.06, "treatment_effect": 0.0,
"aov_mu": 3.3, "aov_sigma": 0.5, "treatment_aov_mu_shift": 0.0},
}, metric="revenue_per_visitor", seed=0)
result = pt.fit(bundle.observed, num_burnin=40, num_mcmc=80, num_trees_mu=30,
num_trees_tau=15, max_depth=4, num_gfr_sweeps=2,
diagnostic_interval=20, seed=0)
result.analyze() # comparisons, discovered segments, recommendation
# result.rpv_cate_samples → (n_visitors, 80) posterior draws of the per-visitor CATE
The full product is the adaptive loop on top of that fit — pt.sequential_experiment(...) runs a realistically-sized multi-round experiment (350,000 visitors) in about fifteen minutes on a consumer GPU; the adaptive-experiment tutorial walks it end to end. For the single-fit on-ramp (posterior interpretation at segment level, ground-truth comparison) see the first-fit tutorial.
What pytyche is and isn't
Pytyche is a designed-experiment library for round-based online experiments with a handful of treatments: assignment rules are explicit, propensities are recorded exactly, and the loop pauses between rounds. Out of scope: marketplaces and anything else with cross-visitor interference (SUTVA violations), regulated decision contexts needing preregistration-grade governance, large-catalog per-item recommendation, real-time/streaming adaptation, and heavily-confounded observational inference (use econml / DoubleML). Extremely heavy-tailed revenue and calibration applied far from the scale it was fitted at both warrant a domain-specific re-check. The full enumeration lives in the overview's scope section.
Documentation
The published site follows the Diátaxis information architecture:
- Tutorials — hands-on walkthroughs. Start with your first hurdle BCF fit.
- How-to guides — recipes for specific tasks.
- Concepts — design rationale. Start with overview.
- Reference — curated Public API for users, full Internal tree for extenders.
Build locally:
uv sync --all-extras
uv run sphinx-build -W -b html docs/ docs/_build/html
Documentation coverage
Public-API docstring coverage: 98% (72/73 symbols on the curated public surface — baseline in docs/coverage-baseline.json).
CI enforces stable-or-increasing coverage on every MR via tests/test_docs/test_public_api_coverage.py. When an MR raises coverage, bump the baseline in the same MR so the new floor sticks. The doc-health dashboard renders the current polish-state breakdown (drafting / reviewed / polished) and the drift list — docs whose declared depends-on source files have moved since the doc's last-human-review. Polished-tier drift fails CI; lower-tier drift is reported but doesn't block.
Development
See CLAUDE.md for project principles (development discipline, the SDLC-citizen contract for docs, the OpenSpec workflow), CLAUDE-AGENT.md for the implementation-subagent guide, and docs/concepts/testing-philosophy.md for the rationale behind pytyche's testing practices. Spec-driven changes live under openspec/.
License
MIT — see LICENSE. Built on bartz (MIT) by Giacomo Petrillo; the GPU BART kernels pytyche fits on top of are bartz's. Hurdle / shared-tree extensions and the calibration / targeting / generator stack are pytyche's.
Source: https://gitlab.com/tradcliffe2/tyche (PyPI package name: pytyche; GitLab repo name: tyche for URL brevity).
Citation
Methodology paper in preparation. Cite as pytyche, v0.1.0.dev (alpha), https://gitlab.com/tradcliffe2/tyche until a citable DOI is up.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pytyche-0.2.0.tar.gz.
File metadata
- Download URL: pytyche-0.2.0.tar.gz
- Upload date:
- Size: 295.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d4fc25b45513e71ec521d420ed2f587fb57a2a176272ba63ef6a63a954487786
|
|
| MD5 |
785304e3da10a9ff78a6c6e3a2541f2d
|
|
| BLAKE2b-256 |
83f9c4a9ac37f0598b1e03a1670f988cf2c0a355e8ba799434832338bdf8a1e5
|
File details
Details for the file pytyche-0.2.0-py3-none-any.whl.
File metadata
- Download URL: pytyche-0.2.0-py3-none-any.whl
- Upload date:
- Size: 308.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
96650dbb47a85c85b56790ad6ee001f359e89de7e97d03815edc01a12526c6ec
|
|
| MD5 |
f1a35084f95c2d5807b1cf8d9c939d67
|
|
| BLAKE2b-256 |
bb22e528cb8b02a73e0a10b65e7e86fb9a9aa982ff92d98d8817815318b588fc
|