pybhatlib
Open-source Python library for matrix-based econometric inference: multivariate normal CDF evaluation with analytic gradients, and maximum-likelihood estimation of discrete-choice models (MNP, MORP, MDCEV, MNL).
The numerical core follows the GAUSS BHATLIB library by Chandra Bhat and colleagues (UT Austin / Aptech Systems) and implements Bhat's (2018) analytic MVNCD approximation.
Installation
pip install pybhatlib # core (NumPy + SciPy + Numba)
pip install "pybhatlib[torch]" # add PyTorch backend (optional GPU)
From source (for development, or to run the bundled examples and tutorials):
git clone https://github.com/UMN-Choi-Lab/pybhatlib.git
cd pybhatlib
pip install -e . # core (NumPy + SciPy + Numba)
pip install -e ".[torch]" # add PyTorch backend (optional GPU)
pip install -e ".[dev]" # add pytest, ruff, mypy
pip install -e ".[all]" # everything (torch + dev)
Quick Start — Multinomial Probit (MNP)
Sample data: examples/data/TRAVELMODE.csv (3 modes — DA / SR / TR, 1125 observations).
from pybhatlib.models.mnp import MNPModel, MNPControl
model = MNPModel(
data="examples/data/TRAVELMODE.csv",
alternatives=["Alt1_ch", "Alt2_ch", "Alt3_ch"],
availability="none",
spec={
"CON_SR": {"Alt1_ch": "sero", "Alt2_ch": "uno", "Alt3_ch": "sero"},
"CON_TR": {"Alt1_ch": "sero", "Alt2_ch": "sero", "Alt3_ch": "uno"},
"IVTT": {"Alt1_ch": "IVTT_DA", "Alt2_ch": "IVTT_SR", "Alt3_ch": "IVTT_TR"},
"OVTT": {"Alt1_ch": "OVTT_DA", "Alt2_ch": "OVTT_SR", "Alt3_ch": "OVTT_TR"},
"COST": {"Alt1_ch": "COST_DA", "Alt2_ch": "COST_SR", "Alt3_ch": "COST_TR"},
},
control=MNPControl(iid=True),
)
results = model.fit()
results.summary()
Quick Start — Multivariate Ordered Response Probit (MORP)
from pybhatlib.models.morp import MORPModel, MORPControl
model = MORPModel(
data=df, # DataFrame or CSV path
dep_vars=["satisfaction", "recommendation"], # ordinal outcome columns
spec={
"income": {"satisfaction": "income", "recommendation": "income"},
"age": {"satisfaction": "age", "recommendation": "age"},
"education": {"satisfaction": "education", "recommendation": "education"},
},
n_categories=[3, 3],
control=MORPControl(iid=True, seed=42),
)
results = model.fit()
results.summary()
A runnable end-to-end example (with morp_ate, morp_predict,
morp_predict_category) is at
examples/tutorials/t05b_morp_ate_predict.ipynb.
Features
Models
- Multinomial Probit (MNP) — IID, flexible covariance, heteroscedastic-only, random coefficients, mixture-of-normals
- Multivariate Ordered Response Probit (MORP) — multiple ordinal outcomes
with shared covariance; per-outcome
specmapping - Multiple Discrete-Continuous Extreme Value (MDCEV) — traditional
(Bhat 2008) and linear outside-good utility specifications, selected via
MDCEVControl.utility - Multinomial Logit (MNL)
- Linear regression (LR) — single continuous outcome, closed-form OLS with classical / White-robust / BHHH standard errors
- Mixed / simulation-based models —
MixMNLModel,MNPKerCPModel,MORPFlexModel,MDCEVMixedModelon a shared maximum-simulated-likelihood engine (Halton draws, panel data, normal / log-normal / Yeo-Johnson coefficient transforms). New and under active refinement.
Numerical core
vecup— vecdup, matdupfull, LDLT decomposition, truncated MVN momentsmatgradient— gradcovcor, gomegxomegax, spherical / Cholesky parameterizationsgradmvn— Bhat (2018) MVNCD analytic approximation with analytic gradients
Estimation
- Multiple SE estimators (
se_method="bhhh" | "hessian" | "sandwich"); BHHH is the default to match GAUSS BHATLIB's_max_CovPar=2. All three are computed at fit time and exposed asse_bhhh/se_hessian/se_sandwich;results.summary()prints a side-by-side diagnostic block (a large Hessian/BHHH divergence is a misspecification signal). MNPControl.active_maskto freeze a subset of parameters at their starting values without recoding the model.verboselevels:0silent,1summary,2per-iteration NLL,3per-iteration parameter / gradient / relative-gradient table.
Backend
- NumPy by default; optional PyTorch backend with GPU support (install with
[torch]). All numerical functions take an optionalxpkwarg.
Post-estimation
- Average Treatment Effects (ATE) with scenario-matrix support, forecasting, per-category MORP probability prediction.
- MORP ATEs from supplied coefficients without re-fitting:
MORPResults.from_estimates(beta, thresholds, correlation)rebuilds a results object from natural-space estimates, andmorp_ate_from_params/morp_joint_probs(mean joint category-combination probabilities, the GAUSSate1.csvequivalent) compute effects directly from them.
Verification
pybhatlib reproduces Table 1 of the BHATLIB GAUSS reference implementation using the TRAVELMODE dataset (3 modes — DA, SR, TR; 1125 observations):
| Model | Specification | Target LL | Achieved LL | Status |
|---|---|---|---|---|
| (a)(i) | IID errors | -670.956 | -670.956 | exact match |
| (a)(ii) | Flexible covariance | -661.111 | -661.111 | exact match |
| (b) | + AGE45 demographics | -659.285 | -659.284 | exact match |
| (c) | + Random coeff. OVTT | -635.871 | -635.871 | exact match |
| (d) | 2-segment mixture | -634.975 | -632.912 | close (multi-modal) |
Models (a)–(c) reproduce the GAUSS estimates and BHHH standard errors to ≤0.001 on every parameter (verified end-to-end against GAUSS 26.1.1 + MaxLik 5.0.9). Model (d) is multi-modal — the Python optimum is a slightly better local mode than the GAUSS one.
For MORP, iid=False now uses GAUSS BHATLIB's unit-variance identification by
default (MORPControl.fix_scales=True): the latent-utility variances are fixed
at 1 and only correlations are estimated. The full-covariance MORP_DINING /
MORP_WALK models reproduce the GAUSS mean log-likelihoods (−4.6598 / −3.7591)
and correlation matrices. summary() reports the actual threshold cut-points
(with delta-method standard errors) and a gradient column, matching GAUSS's
output. See docs/plans/MORP_BHATLIB_PARITY.md.
The driving notebooks are
t04h_bhatlib_table1.ipynb and
t04i_bhat2018_table2.ipynb.
Tutorials
A complete tutorial series lives under
examples/tutorials/ as Jupyter notebooks (each with a
matching .py script under python_scripts/):
| Track | Notebooks |
|---|---|
| Foundations | t00_quickstart, t01a_vectorization, t01b_ldlt, t01c_truncated_mvn |
| Matrix gradients | t02a_gradcovcor, t02b_spherical, t02c_chain_rules |
| MVNCD | t03a_mvncd_methods, t03b_mvncd_gradients, t03c_mvncd_rect, t03d_univariate_cdfs, t03e_bhat2018_table1 |
| MNP | t04a_mnp_iid … t04g_mnp_forecasting, t04h_bhatlib_table1, t04i_bhat2018_table2 |
| MORP | t05b_morp_ate_predict |
| MDCEV | t07a_mdcev_trad, t07b_mdcev_lin |
| MNL | t08a_mnl |
| LR | t09a_lr |
| Backends & verification | t06a_backend_switching, t06b_custom_specs, t06c_gradient_verification |
Contributing
See CONTRIBUTING.md for development setup, the PR-test
workflow, and CODEOWNERS conventions. Key tests:
pytest tests/ # full suite
pytest tests/ -m "not slow" # skip integration tests
pytest tests/ -m torch # PyTorch backend tests only
References
- Bhat, C. R. (2018). New Matrix-Based Methods for the Analytic Evaluation of the Multivariate Cumulative Normal Distribution Function. Transportation Research Part B, 109: 238–256.
- Bhat, C. R. (2008). The Multiple Discrete-Continuous Extreme Value (MDCEV) Model: Role of Utility Function Parameters, Identification Considerations, and Model Extensions. Transportation Research Part B, 42(3): 274–303.
License
MIT
Metadata
Release files for pybhatlib 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pybhatlib-0.4.0.tar.gz | 3.3 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pybhatlib-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 3.7 MB
Release files / pybhatlib-0.4.0.tar.gz
| Download URL | pybhatlib-0.4.0.tar.gz |
|---|---|
| Size | 3.3 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
21a4b6e1e168f59e25a83053cce9a7aa3b2d1d7c1a43737c0c6672308f79b311
|
|
BLAKE2b-256 checksum How to use checksums |
9db18338669f8784df21d05c6cbbd0ef0bdd8595a06495b43816e8299db3c9bb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.
Transparency logRelease files / pybhatlib-0.4.0-py3-none-any.whl
| Download URL | pybhatlib-0.4.0-py3-none-any.whl |
|---|---|
| Size | 472.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6bdc0a59dfeb4fda216343337951977169f3b34c4366bd90e45dfa820c664af3
|
|
BLAKE2b-256 checksum How to use checksums |
1a40aaa9a21717b78b49820595a4493c156f32b5c3f1c905d2e21e27b46ccacb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 16, 2026.
Transparency log