Skip to main content

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:

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pytyche-0.2.0.tar.gz (295.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pytyche-0.2.0-py3-none-any.whl (308.1 kB view details)

Uploaded Python 3

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

Hashes for pytyche-0.2.0.tar.gz
Algorithm Hash digest
SHA256 d4fc25b45513e71ec521d420ed2f587fb57a2a176272ba63ef6a63a954487786
MD5 785304e3da10a9ff78a6c6e3a2541f2d
BLAKE2b-256 83f9c4a9ac37f0598b1e03a1670f988cf2c0a355e8ba799434832338bdf8a1e5

See more details on using hashes here.

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

Hashes for pytyche-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 96650dbb47a85c85b56790ad6ee001f359e89de7e97d03815edc01a12526c6ec
MD5 f1a35084f95c2d5807b1cf8d9c939d67
BLAKE2b-256 bb22e528cb8b02a73e0a10b65e7e86fb9a9aa982ff92d98d8817815318b588fc

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page