pybhatlib 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pybhatlib-0.1.0/.gitignore +49 -0
- pybhatlib-0.1.0/CLAUDE.md +42 -0
- pybhatlib-0.1.0/IMPLEMENTATION_PLAN.md +497 -0
- pybhatlib-0.1.0/LICENSE +21 -0
- pybhatlib-0.1.0/PKG-INFO +103 -0
- pybhatlib-0.1.0/README.md +65 -0
- pybhatlib-0.1.0/examples/data/TRAVELMODE.csv +1126 -0
- pybhatlib-0.1.0/examples/data/generate_travelmode.py +248 -0
- pybhatlib-0.1.0/examples/mnp_ate_analysis.py +95 -0
- pybhatlib-0.1.0/examples/mnp_flexible_cov.py +46 -0
- pybhatlib-0.1.0/examples/mnp_iid.py +45 -0
- pybhatlib-0.1.0/examples/mnp_random_coefficients.py +54 -0
- pybhatlib-0.1.0/examples/morp_example.py +171 -0
- pybhatlib-0.1.0/examples/tutorials/_convert_to_ipynb.py +224 -0
- pybhatlib-0.1.0/examples/tutorials/python_scripts/t00_quickstart.py +129 -0
- pybhatlib-0.1.0/examples/tutorials/python_scripts/t01a_vectorization.py +142 -0
- pybhatlib-0.1.0/examples/tutorials/python_scripts/t01b_ldlt.py +130 -0
- pybhatlib-0.1.0/examples/tutorials/python_scripts/t01c_truncated_mvn.py +115 -0
- pybhatlib-0.1.0/examples/tutorials/python_scripts/t02a_gradcovcor.py +151 -0
- pybhatlib-0.1.0/examples/tutorials/python_scripts/t02b_spherical.py +141 -0
- pybhatlib-0.1.0/examples/tutorials/python_scripts/t02c_chain_rules.py +174 -0
- pybhatlib-0.1.0/examples/tutorials/python_scripts/t03a_mvncd_methods.py +131 -0
- pybhatlib-0.1.0/examples/tutorials/python_scripts/t03b_mvncd_gradients.py +146 -0
- pybhatlib-0.1.0/examples/tutorials/python_scripts/t03c_mvncd_rect.py +156 -0
- pybhatlib-0.1.0/examples/tutorials/python_scripts/t03d_univariate_cdfs.py +160 -0
- pybhatlib-0.1.0/examples/tutorials/python_scripts/t04c_mnp_heteronly.py +154 -0
- pybhatlib-0.1.0/examples/tutorials/python_scripts/t04f_mnp_control_options.py +189 -0
- pybhatlib-0.1.0/examples/tutorials/python_scripts/t04g_mnp_forecasting.py +164 -0
- pybhatlib-0.1.0/examples/tutorials/python_scripts/t05b_morp_ate_predict.py +171 -0
- pybhatlib-0.1.0/examples/tutorials/python_scripts/t06a_backend_switching.py +139 -0
- pybhatlib-0.1.0/examples/tutorials/python_scripts/t06b_custom_specs.py +222 -0
- pybhatlib-0.1.0/examples/tutorials/python_scripts/t06c_gradient_verification.py +269 -0
- pybhatlib-0.1.0/examples/tutorials/t00_quickstart.ipynb +115 -0
- pybhatlib-0.1.0/examples/tutorials/t01a_vectorization.ipynb +131 -0
- pybhatlib-0.1.0/examples/tutorials/t01b_ldlt.ipynb +99 -0
- pybhatlib-0.1.0/examples/tutorials/t01c_truncated_mvn.ipynb +99 -0
- pybhatlib-0.1.0/examples/tutorials/t02a_gradcovcor.ipynb +115 -0
- pybhatlib-0.1.0/examples/tutorials/t02b_spherical.ipynb +115 -0
- pybhatlib-0.1.0/examples/tutorials/t02c_chain_rules.ipynb +115 -0
- pybhatlib-0.1.0/examples/tutorials/t03a_mvncd_methods.ipynb +99 -0
- pybhatlib-0.1.0/examples/tutorials/t03b_mvncd_gradients.ipynb +115 -0
- pybhatlib-0.1.0/examples/tutorials/t03c_mvncd_rect.ipynb +99 -0
- pybhatlib-0.1.0/examples/tutorials/t03d_univariate_cdfs.ipynb +115 -0
- pybhatlib-0.1.0/examples/tutorials/t04c_mnp_heteronly.ipynb +115 -0
- pybhatlib-0.1.0/examples/tutorials/t04f_mnp_control_options.ipynb +131 -0
- pybhatlib-0.1.0/examples/tutorials/t04g_mnp_forecasting.ipynb +115 -0
- pybhatlib-0.1.0/examples/tutorials/t05b_morp_ate_predict.ipynb +115 -0
- pybhatlib-0.1.0/examples/tutorials/t06a_backend_switching.ipynb +115 -0
- pybhatlib-0.1.0/examples/tutorials/t06b_custom_specs.ipynb +131 -0
- pybhatlib-0.1.0/examples/tutorials/t06c_gradient_verification.ipynb +131 -0
- pybhatlib-0.1.0/pyproject.toml +69 -0
- pybhatlib-0.1.0/src/pybhatlib/__init__.py +5 -0
- pybhatlib-0.1.0/src/pybhatlib/_version.py +1 -0
- pybhatlib-0.1.0/src/pybhatlib/backend/__init__.py +9 -0
- pybhatlib-0.1.0/src/pybhatlib/backend/_array_api.py +84 -0
- pybhatlib-0.1.0/src/pybhatlib/backend/_numpy_backend.py +263 -0
- pybhatlib-0.1.0/src/pybhatlib/backend/_torch_backend.py +257 -0
- pybhatlib-0.1.0/src/pybhatlib/gradmvn/__init__.py +24 -0
- pybhatlib-0.1.0/src/pybhatlib/gradmvn/_bivariate_trunc.py +167 -0
- pybhatlib-0.1.0/src/pybhatlib/gradmvn/_mvncd.py +826 -0
- pybhatlib-0.1.0/src/pybhatlib/gradmvn/_mvncd_grad.py +145 -0
- pybhatlib-0.1.0/src/pybhatlib/gradmvn/_mvncd_ssj.py +117 -0
- pybhatlib-0.1.0/src/pybhatlib/gradmvn/_other_dists.py +169 -0
- pybhatlib-0.1.0/src/pybhatlib/gradmvn/_partial_cdf.py +117 -0
- pybhatlib-0.1.0/src/pybhatlib/gradmvn/_truncated.py +268 -0
- pybhatlib-0.1.0/src/pybhatlib/gradmvn/_univariate.py +193 -0
- pybhatlib-0.1.0/src/pybhatlib/io/__init__.py +6 -0
- pybhatlib-0.1.0/src/pybhatlib/io/_data_loader.py +44 -0
- pybhatlib-0.1.0/src/pybhatlib/io/_spec_parser.py +166 -0
- pybhatlib-0.1.0/src/pybhatlib/matgradient/__init__.py +15 -0
- pybhatlib-0.1.0/src/pybhatlib/matgradient/_chain_rules.py +91 -0
- pybhatlib-0.1.0/src/pybhatlib/matgradient/_gomegxomegax.py +83 -0
- pybhatlib-0.1.0/src/pybhatlib/matgradient/_gradcovcor.py +123 -0
- pybhatlib-0.1.0/src/pybhatlib/matgradient/_spherical.py +190 -0
- pybhatlib-0.1.0/src/pybhatlib/models/__init__.py +1 -0
- pybhatlib-0.1.0/src/pybhatlib/models/_base.py +14 -0
- pybhatlib-0.1.0/src/pybhatlib/models/mnp/__init__.py +14 -0
- pybhatlib-0.1.0/src/pybhatlib/models/mnp/_mnp_ate.py +193 -0
- pybhatlib-0.1.0/src/pybhatlib/models/mnp/_mnp_control.py +88 -0
- pybhatlib-0.1.0/src/pybhatlib/models/mnp/_mnp_forecast.py +119 -0
- pybhatlib-0.1.0/src/pybhatlib/models/mnp/_mnp_loglik.py +565 -0
- pybhatlib-0.1.0/src/pybhatlib/models/mnp/_mnp_model.py +419 -0
- pybhatlib-0.1.0/src/pybhatlib/models/mnp/_mnp_results.py +181 -0
- pybhatlib-0.1.0/src/pybhatlib/models/morp/__init__.py +17 -0
- pybhatlib-0.1.0/src/pybhatlib/models/morp/_morp_ate.py +119 -0
- pybhatlib-0.1.0/src/pybhatlib/models/morp/_morp_control.py +56 -0
- pybhatlib-0.1.0/src/pybhatlib/models/morp/_morp_forecast.py +121 -0
- pybhatlib-0.1.0/src/pybhatlib/models/morp/_morp_loglik.py +256 -0
- pybhatlib-0.1.0/src/pybhatlib/models/morp/_morp_model.py +276 -0
- pybhatlib-0.1.0/src/pybhatlib/models/morp/_morp_results.py +154 -0
- pybhatlib-0.1.0/src/pybhatlib/optim/__init__.py +5 -0
- pybhatlib-0.1.0/src/pybhatlib/optim/_convergence.py +56 -0
- pybhatlib-0.1.0/src/pybhatlib/optim/_scipy_optim.py +156 -0
- pybhatlib-0.1.0/src/pybhatlib/optim/_torch_optim.py +140 -0
- pybhatlib-0.1.0/src/pybhatlib/utils/__init__.py +6 -0
- pybhatlib-0.1.0/src/pybhatlib/utils/_qmc.py +52 -0
- pybhatlib-0.1.0/src/pybhatlib/utils/_seeds.py +23 -0
- pybhatlib-0.1.0/src/pybhatlib/utils/_validation.py +34 -0
- pybhatlib-0.1.0/src/pybhatlib/vecup/__init__.py +24 -0
- pybhatlib-0.1.0/src/pybhatlib/vecup/_ldlt.py +160 -0
- pybhatlib-0.1.0/src/pybhatlib/vecup/_mask.py +74 -0
- pybhatlib-0.1.0/src/pybhatlib/vecup/_nondiag.py +42 -0
- pybhatlib-0.1.0/src/pybhatlib/vecup/_truncnorm.py +153 -0
- pybhatlib-0.1.0/src/pybhatlib/vecup/_vec_ops.py +196 -0
- pybhatlib-0.1.0/tests/__init__.py +1 -0
- pybhatlib-0.1.0/tests/conftest.py +99 -0
- pybhatlib-0.1.0/tests/test_backend/__init__.py +1 -0
- pybhatlib-0.1.0/tests/test_backend/test_array_api.py +75 -0
- pybhatlib-0.1.0/tests/test_gradmvn/__init__.py +1 -0
- pybhatlib-0.1.0/tests/test_gradmvn/test_bivariate_trunc.py +91 -0
- pybhatlib-0.1.0/tests/test_gradmvn/test_mvncd.py +66 -0
- pybhatlib-0.1.0/tests/test_gradmvn/test_mvncd_methods.py +129 -0
- pybhatlib-0.1.0/tests/test_gradmvn/test_mvncd_rect.py +100 -0
- pybhatlib-0.1.0/tests/test_gradmvn/test_other_dists.py +59 -0
- pybhatlib-0.1.0/tests/test_integration/__init__.py +1 -0
- pybhatlib-0.1.0/tests/test_matgradient/__init__.py +1 -0
- pybhatlib-0.1.0/tests/test_matgradient/test_gradcovcor.py +49 -0
- pybhatlib-0.1.0/tests/test_models/__init__.py +1 -0
- pybhatlib-0.1.0/tests/test_models/test_mnp_control.py +39 -0
- pybhatlib-0.1.0/tests/test_models/test_morp.py +210 -0
- pybhatlib-0.1.0/tests/test_vecup/__init__.py +1 -0
- pybhatlib-0.1.0/tests/test_vecup/test_ldlt.py +64 -0
- pybhatlib-0.1.0/tests/test_vecup/test_ldlt_rank2.py +73 -0
- pybhatlib-0.1.0/tests/test_vecup/test_vec_ops.py +100 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
*.egg-info/
|
|
7
|
+
dist/
|
|
8
|
+
build/
|
|
9
|
+
*.egg
|
|
10
|
+
|
|
11
|
+
# Virtual environments
|
|
12
|
+
.venv/
|
|
13
|
+
venv/
|
|
14
|
+
env/
|
|
15
|
+
|
|
16
|
+
# IDE
|
|
17
|
+
.vscode/
|
|
18
|
+
.idea/
|
|
19
|
+
*.swp
|
|
20
|
+
*.swo
|
|
21
|
+
|
|
22
|
+
# Testing
|
|
23
|
+
.pytest_cache/
|
|
24
|
+
.coverage
|
|
25
|
+
htmlcov/
|
|
26
|
+
|
|
27
|
+
# Data & results (large files)
|
|
28
|
+
*.dat
|
|
29
|
+
*.pkl
|
|
30
|
+
*.pdf
|
|
31
|
+
results/
|
|
32
|
+
|
|
33
|
+
# OS
|
|
34
|
+
.DS_Store
|
|
35
|
+
Thumbs.db
|
|
36
|
+
nul
|
|
37
|
+
|
|
38
|
+
# LaTeX build artifacts
|
|
39
|
+
*.aux
|
|
40
|
+
*.log
|
|
41
|
+
*.nav
|
|
42
|
+
*.snm
|
|
43
|
+
*.toc
|
|
44
|
+
*.out
|
|
45
|
+
*.bbl
|
|
46
|
+
*.blg
|
|
47
|
+
*.fdb_latexmk
|
|
48
|
+
*.fls
|
|
49
|
+
*.synctex.gz
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# pybhatlib
|
|
2
|
+
|
|
3
|
+
Python reimplementation of BHATLIB (Bhat, Clower, Haddad, Jones) for statistical
|
|
4
|
+
and econometric matrix-based inference methods.
|
|
5
|
+
|
|
6
|
+
## Build & Test
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install -e ".[dev]" # Install in editable mode with dev deps
|
|
10
|
+
pip install -e ".[torch]" # Optional PyTorch backend
|
|
11
|
+
pytest tests/ # Run all tests
|
|
12
|
+
pytest tests/ -m "not slow" # Skip integration tests
|
|
13
|
+
pytest tests/ -m torch # Only PyTorch backend tests
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Architecture
|
|
17
|
+
|
|
18
|
+
Build dependency chain: `backend → vecup → matgradient → gradmvn → optim/io → models/mnp`
|
|
19
|
+
|
|
20
|
+
- `backend/` — Array backend abstraction (NumPy default, optional PyTorch)
|
|
21
|
+
- `vecup/` — Low-level matrix ops (GAUSS Vecup.src): vecdup, matdupfull, LDLT, truncated MVN
|
|
22
|
+
- `matgradient/` — Matrix gradients (GAUSS Matgradient.src): gradcovcor, gomegxomegax, spherical param
|
|
23
|
+
- `gradmvn/` — Distributions & gradients (GAUSS Gradmvn.src): MVNCD (Bhat 2018), truncated, partial CDF
|
|
24
|
+
- `models/mnp/` — Multinomial Probit: IID, flexible covariance, random coefficients, mixture-of-normals
|
|
25
|
+
- `optim/` — Optimizer wrappers (scipy.optimize + optional PyTorch)
|
|
26
|
+
- `io/` — Data loading and spec parsing
|
|
27
|
+
- `utils/` — QMC sequences, seeds, validation
|
|
28
|
+
|
|
29
|
+
## Key Conventions
|
|
30
|
+
|
|
31
|
+
1. **Row-based arrangement**: All matrix vectorization proceeds row-by-row (matching BHATLIB)
|
|
32
|
+
2. **Upper triangular**: Symmetric matrices stored as vectors of upper diagonal elements
|
|
33
|
+
3. **Covariance parameterization**: Ω = ω Ω* ω (std devs × correlation × std devs)
|
|
34
|
+
4. **`xp` parameter pattern**: Numerical functions accept optional `xp` kwarg for backend selection
|
|
35
|
+
5. **Analytic gradients**: Primary approach; autograd is secondary (PyTorch only)
|
|
36
|
+
|
|
37
|
+
## Coding Standards
|
|
38
|
+
|
|
39
|
+
- PEP 8, type hints on all public functions
|
|
40
|
+
- Docstrings (NumPy style) on all public functions
|
|
41
|
+
- Private modules prefixed with underscore (e.g., `_vec_ops.py`)
|
|
42
|
+
- All tests use numerical gradient verification via finite differences where applicable
|
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
# pybhatlib — Full Implementation Plan
|
|
2
|
+
|
|
3
|
+
## Context
|
|
4
|
+
|
|
5
|
+
**BHATLIB** is an open-source GAUSS library by Bhat, Clower, Haddad, and Jones (UT Austin / Aptech Systems) for statistical and econometric matrix-based inference methods. It provides:
|
|
6
|
+
- Efficient matrix operations and gradient-enabled routines for multivariate distribution evaluation
|
|
7
|
+
- Bhat's (2018) analytic approximation to the Multivariate Normal CDF (MVNCD)
|
|
8
|
+
- Pre-built models: Multinomial Probit (MNP), Multivariate Ordered Probit (MORP), MDCEV
|
|
9
|
+
|
|
10
|
+
**pybhatlib** is a Python reimplementation making these methods accessible beyond the GAUSS ecosystem.
|
|
11
|
+
|
|
12
|
+
### User Decisions
|
|
13
|
+
- **Scope**: MNP model first + all 3 core computational libraries
|
|
14
|
+
- **Optimizer**: Both scipy.optimize AND optional PyTorch backend
|
|
15
|
+
- **Array backend**: NumPy by default, optional PyTorch tensor support
|
|
16
|
+
- **Packaging**: pip-installable from the start (pyproject.toml)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## BHATLIB Technical Summary (from paper)
|
|
21
|
+
|
|
22
|
+
### Core Computational Libraries
|
|
23
|
+
|
|
24
|
+
**1. Vecup.src** — Low-level matrix manipulations and gradient functions:
|
|
25
|
+
- `vecdup(r)`: Extract upper triangular elements (incl. diagonal) → column vector. For 3×3 → 6×1.
|
|
26
|
+
- `vecndup(r)`: Extract upper diagonal elements (excl. diagonal) → column vector. For 3×3 → 3×1.
|
|
27
|
+
- `matdupfull(r)`: Expand column vector of upper diagonal elements → full symmetric matrix (inverse of vecdup).
|
|
28
|
+
- `matdupdiagonefull(r)`: Convert column vector → symmetric matrix with unit diagonal.
|
|
29
|
+
- `nondiag`: Extract non-diagonal elements of a matrix into a vector.
|
|
30
|
+
- `vecsymmetry`: Takes square symmetric matrix, produces matrix where each row unrolls the symmetric elements.
|
|
31
|
+
- Tools for truncated MVN mean/covariance computation
|
|
32
|
+
- LDLT factorization utilities
|
|
33
|
+
- Mask matrix operations for mixed models
|
|
34
|
+
|
|
35
|
+
**2. Matgradient.src** — Higher-level matrix operations and matrix gradients:
|
|
36
|
+
- `gradcovcor(CAPOMEGA)`: For Ω = ω Ω* ω (covariance = stddev × correlation × stddev), computes:
|
|
37
|
+
- `glitomega`: K × [K×(K+1)/2] gradient of Ω elements w.r.t. K std dev elements
|
|
38
|
+
- `gomegastar`: [K×(K-1)/2] × [K×(K+1)/2] gradient of Ω elements w.r.t. correlation elements
|
|
39
|
+
- `gomegxomegax`: Gradient of A = XΩX' w.r.t. symmetric matrix Ω
|
|
40
|
+
- Matrix chain rule operations following row-based arrangement convention
|
|
41
|
+
|
|
42
|
+
**3. Gradmvn.src** — Probability distributions and their gradients:
|
|
43
|
+
- MVNCD using Bhat's (2018) analytic ME approximation with LDLT decomposition
|
|
44
|
+
- Truncated (both-end) density and CDF via combinatorial methods
|
|
45
|
+
- Partial cumulative normal distribution functions (mixed point/interval)
|
|
46
|
+
- Gradient procedures for all distribution functions
|
|
47
|
+
- Additional distributions: logistic, skew-normal, skew-t, Gumbel, reverse-Gumbel
|
|
48
|
+
|
|
49
|
+
### Key Conventions
|
|
50
|
+
1. **Row-based matrix arrangement**: Vectorizing proceeds row by row
|
|
51
|
+
2. **Symmetric matrices**: Only upper diagonal elements stored as vectors
|
|
52
|
+
3. **Covariance parameterization**: Ω = ω Ω* ω (std devs × correlation × std devs)
|
|
53
|
+
4. **Positive-definiteness**: Spherical/radial parameterization Ω* = f(Θ) for unconstrained optimization
|
|
54
|
+
5. **Gradient chain rules**: dA/dω = dA/dΩ × dΩ/dω (row-based ordering)
|
|
55
|
+
|
|
56
|
+
### MNP Model
|
|
57
|
+
- **Theory**: U_qi = V_qi + ξ_qi, V_qi = β'x_qi, ξ_q ~ MVN(0, Λ)
|
|
58
|
+
- **Generalized MNP**: β_q = b + β̃_q, β̃_q ~ MVN(0, Ω), Ω = LL'
|
|
59
|
+
- **Mixture-of-normals**: β_q = Σ π_h β_qh, β_qh ~ MVN(b_h, Ω_qh), π₁ < π₂ < ... < π_H
|
|
60
|
+
- **Key procedures**: mnpFit, mnpATEFit, mnpControlCreate
|
|
61
|
+
- **Control fields**: IID, mix, indep, correst, heteronly, rannddiag, nseg
|
|
62
|
+
- **Output**: coefficients, SE, t-stats, p-values, gradient, log-likelihood, covariance matrix, convergence info
|
|
63
|
+
|
|
64
|
+
### BHATLIB Estimation Workflow
|
|
65
|
+
1. Load library and prepare environment
|
|
66
|
+
2. Specify data file and key variables (dvunordname, davunordname)
|
|
67
|
+
3. Define independent variables matrix (ivunord) with "sero"/"uno" keywords
|
|
68
|
+
4. Set coefficient names (var_unordnames)
|
|
69
|
+
5. Configure control structure (mCtl.IID, mix, ranvars, etc.)
|
|
70
|
+
6. Call fit procedure (mnpFit) → returns results structure
|
|
71
|
+
7. Post-estimation: ATE analysis (mnpATEFit), goodness-of-fit
|
|
72
|
+
|
|
73
|
+
### Validation Data (Table 1 from BHATLIB paper)
|
|
74
|
+
Using TRAVELMODE.csv (1,125 workers, 3 modes: DA 78.22%, SR 7.65%, TR 14.13%):
|
|
75
|
+
|
|
76
|
+
| Model | Description | Log-likelihood |
|
|
77
|
+
|-------|------------|----------------|
|
|
78
|
+
| (a)(i) | IID errors | -670.956 |
|
|
79
|
+
| (a)(ii) | Flexible covariance | -661.111 |
|
|
80
|
+
| (b) | + AGE45 variable | -659.285 |
|
|
81
|
+
| (c) | + Random coeff on OVTT | -635.871 |
|
|
82
|
+
| (d) | 2-segment mixture-of-normals | -634.975 |
|
|
83
|
+
|
|
84
|
+
ATE output (Figure 12): predicted shares at base level = [0.692, 0.141, 0.167]
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Package Structure
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
C:\Users\chois\Gitsrcs\pybhatlib\
|
|
92
|
+
├── pyproject.toml
|
|
93
|
+
├── README.md
|
|
94
|
+
├── LICENSE
|
|
95
|
+
├── CLAUDE.md
|
|
96
|
+
├── .gitignore
|
|
97
|
+
├── examples/
|
|
98
|
+
│ ├── data/TRAVELMODE.csv
|
|
99
|
+
│ ├── mnp_iid.py
|
|
100
|
+
│ ├── mnp_flexible_cov.py
|
|
101
|
+
│ ├── mnp_random_coefficients.py
|
|
102
|
+
│ └── mnp_ate_analysis.py
|
|
103
|
+
├── src/pybhatlib/
|
|
104
|
+
│ ├── __init__.py
|
|
105
|
+
│ ├── _version.py
|
|
106
|
+
│ ├── backend/
|
|
107
|
+
│ │ ├── __init__.py
|
|
108
|
+
│ │ ├── _array_api.py # get_backend(), set_backend(), array_namespace()
|
|
109
|
+
│ │ ├── _numpy_backend.py # NumpyBackend: numpy + scipy.linalg + scipy.stats
|
|
110
|
+
│ │ └── _torch_backend.py # TorchBackend: torch + torch.linalg (optional)
|
|
111
|
+
│ ├── vecup/ # Low-level matrix ops (GAUSS Vecup.src)
|
|
112
|
+
│ │ ├── __init__.py
|
|
113
|
+
│ │ ├── _vec_ops.py # vecdup, vecndup, matdupfull, matdupdiagonefull, vecsymmetry
|
|
114
|
+
│ │ ├── _nondiag.py # nondiag
|
|
115
|
+
│ │ ├── _ldlt.py # ldlt_decompose, ldlt_rank1_update
|
|
116
|
+
│ │ ├── _truncnorm.py # truncated_mvn_moments
|
|
117
|
+
│ │ └── _mask.py # Mask matrix ops for mixed models
|
|
118
|
+
│ ├── matgradient/ # Matrix gradients (GAUSS Matgradient.src)
|
|
119
|
+
│ │ ├── __init__.py
|
|
120
|
+
│ │ ├── _gradcovcor.py # gradcovcor: dΩ/dω, dΩ/dΩ*
|
|
121
|
+
│ │ ├── _gomegxomegax.py # dA/dΩ for A=XΩX'
|
|
122
|
+
│ │ ├── _spherical.py # theta_to_corr, grad_corr_theta (PD parameterization)
|
|
123
|
+
│ │ └── _chain_rules.py # Matrix chain rule helpers
|
|
124
|
+
│ ├── gradmvn/ # Distributions & gradients (GAUSS Gradmvn.src)
|
|
125
|
+
│ │ ├── __init__.py
|
|
126
|
+
│ │ ├── _mvncd.py # Bhat (2018) MVNCD analytic approximation
|
|
127
|
+
│ │ ├── _mvncd_grad.py # Gradients of MVNCD
|
|
128
|
+
│ │ ├── _truncated.py # Truncated MVN density/CDF
|
|
129
|
+
│ │ ├── _partial_cdf.py # Partial cumulative normal (mixed point/interval)
|
|
130
|
+
│ │ ├── _univariate.py # Standard normal PDF/CDF wrappers
|
|
131
|
+
│ │ └── _other_dists.py # Logistic, skew-normal, skew-t, Gumbel
|
|
132
|
+
│ ├── models/
|
|
133
|
+
│ │ ├── __init__.py
|
|
134
|
+
│ │ ├── _base.py # BaseModel ABC
|
|
135
|
+
│ │ └── mnp/
|
|
136
|
+
│ │ ├── __init__.py
|
|
137
|
+
│ │ ├── _mnp_control.py # MNPControl dataclass
|
|
138
|
+
│ │ ├── _mnp_results.py # MNPResults dataclass + summary()
|
|
139
|
+
│ │ ├── _mnp_model.py # MNPModel class with fit()
|
|
140
|
+
│ │ ├── _mnp_loglik.py # Log-likelihood & gradient
|
|
141
|
+
│ │ ├── _mnp_ate.py # ATE post-estimation
|
|
142
|
+
│ │ └── _mnp_forecast.py # Prediction
|
|
143
|
+
│ ├── optim/
|
|
144
|
+
│ │ ├── __init__.py
|
|
145
|
+
│ │ ├── _scipy_optim.py # scipy.optimize.minimize wrapper (BFGS, L-BFGS-B)
|
|
146
|
+
│ │ ├── _torch_optim.py # PyTorch optimizer wrapper (L-BFGS, Adam)
|
|
147
|
+
│ │ └── _convergence.py # Convergence diagnostics
|
|
148
|
+
│ ├── io/
|
|
149
|
+
│ │ ├── __init__.py
|
|
150
|
+
│ │ ├── _data_loader.py # CSV/DAT/XLSX via pandas
|
|
151
|
+
│ │ └── _spec_parser.py # Parse ivunord-style specs ("sero"/"uno")
|
|
152
|
+
│ └── utils/
|
|
153
|
+
│ ├── __init__.py
|
|
154
|
+
│ ├── _qmc.py # Quasi-Monte Carlo sequences
|
|
155
|
+
│ ├── _seeds.py # Random seed management
|
|
156
|
+
│ └── _validation.py # Input validation
|
|
157
|
+
└── tests/
|
|
158
|
+
├── conftest.py # Backend fixtures, test matrices, TRAVELMODE data
|
|
159
|
+
├── test_backend/
|
|
160
|
+
├── test_vecup/
|
|
161
|
+
├── test_matgradient/
|
|
162
|
+
├── test_gradmvn/
|
|
163
|
+
├── test_models/
|
|
164
|
+
└── test_integration/ # End-to-end against BHATLIB paper Table 1
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Detailed Function Signatures
|
|
170
|
+
|
|
171
|
+
### Backend Abstraction (`backend/`)
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
# _array_api.py
|
|
175
|
+
BackendName = Literal["numpy", "torch"]
|
|
176
|
+
|
|
177
|
+
class ArrayNamespace(Protocol):
|
|
178
|
+
"""Protocol: zeros, ones, eye, array, arange, concatenate, stack, diag,
|
|
179
|
+
triu_indices, sqrt, exp, log, abs, sum, dot, matmul, transpose,
|
|
180
|
+
solve, cholesky, det, inv, eigh, normal_pdf, normal_cdf, normal_ppf"""
|
|
181
|
+
|
|
182
|
+
def get_backend(name: BackendName | None = None) -> ArrayNamespace: ...
|
|
183
|
+
def set_backend(name: BackendName) -> None: ...
|
|
184
|
+
def array_namespace(*arrays) -> ArrayNamespace: ...
|
|
185
|
+
|
|
186
|
+
# _numpy_backend.py
|
|
187
|
+
class NumpyBackend:
|
|
188
|
+
"""Wraps numpy + scipy.linalg + scipy.stats."""
|
|
189
|
+
float64 = np.float64
|
|
190
|
+
# All standard ops + solve, cholesky, ldlt, normal_pdf/cdf/ppf
|
|
191
|
+
|
|
192
|
+
# _torch_backend.py
|
|
193
|
+
class TorchBackend:
|
|
194
|
+
"""Wraps torch + torch.linalg. Lazy import. Device support."""
|
|
195
|
+
def __init__(self, device="cpu", dtype=None): ...
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### vecup Module
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
# _vec_ops.py — All accept optional xp kwarg
|
|
202
|
+
def vecdup(r: NDArray, *, xp=None) -> NDArray:
|
|
203
|
+
"""(K,K) → (K*(K+1)//2, 1) upper triangular elements incl diagonal."""
|
|
204
|
+
|
|
205
|
+
def vecndup(r: NDArray, *, xp=None) -> NDArray:
|
|
206
|
+
"""(K,K) → (K*(K-1)//2, 1) upper triangular elements excl diagonal."""
|
|
207
|
+
|
|
208
|
+
def matdupfull(r: NDArray, *, xp=None) -> NDArray:
|
|
209
|
+
"""(K*(K+1)//2,) → (P,P) full symmetric matrix. Inverse of vecdup."""
|
|
210
|
+
|
|
211
|
+
def matdupdiagonefull(r: NDArray, *, xp=None) -> NDArray:
|
|
212
|
+
"""(K*(K-1)//2,) → (P,P) symmetric matrix with unit diagonal."""
|
|
213
|
+
|
|
214
|
+
def vecsymmetry(r: NDArray, *, xp=None) -> NDArray:
|
|
215
|
+
"""(K,K) → (K*(K+1)//2, K*K) position pattern matrix."""
|
|
216
|
+
|
|
217
|
+
# _nondiag.py
|
|
218
|
+
def nondiag(r: NDArray, *, xp=None) -> NDArray:
|
|
219
|
+
"""(K,K) → (K*(K-1),1) non-diagonal elements row-by-row."""
|
|
220
|
+
|
|
221
|
+
# _ldlt.py
|
|
222
|
+
def ldlt_decompose(A: NDArray, *, xp=None) -> tuple[NDArray, NDArray]:
|
|
223
|
+
"""A = LDL^T. Returns (L, D)."""
|
|
224
|
+
|
|
225
|
+
def ldlt_rank1_update(L, D, v, alpha=1.0, *, xp=None) -> tuple[NDArray, NDArray]:
|
|
226
|
+
"""Rank-1 update: LDL^T + alpha*vv^T."""
|
|
227
|
+
|
|
228
|
+
# _truncnorm.py
|
|
229
|
+
def truncated_mvn_moments(mu, sigma, lower, upper, *, xp=None) -> tuple[NDArray, NDArray]:
|
|
230
|
+
"""Returns (mu_trunc, sigma_trunc)."""
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### matgradient Module
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
# _gradcovcor.py
|
|
237
|
+
@dataclass
|
|
238
|
+
class GradCovCorResult:
|
|
239
|
+
glitomega: NDArray # K × [K×(K+1)/2]
|
|
240
|
+
gomegastar: NDArray # [K×(K-1)/2] × [K×(K+1)/2]
|
|
241
|
+
|
|
242
|
+
def gradcovcor(capomega: NDArray, *, xp=None) -> GradCovCorResult:
|
|
243
|
+
"""For Ω = ω Ω* ω, compute dΩ/dω and dΩ/dΩ*."""
|
|
244
|
+
|
|
245
|
+
# _gomegxomegax.py
|
|
246
|
+
def gomegxomegax(X: NDArray, omega: NDArray, *, xp=None) -> NDArray:
|
|
247
|
+
"""dA/dΩ for A=XΩX'. Returns [K(K+1)/2, N(N+1)/2]."""
|
|
248
|
+
|
|
249
|
+
# _spherical.py
|
|
250
|
+
def theta_to_corr(theta: NDArray, K: int, *, xp=None) -> NDArray:
|
|
251
|
+
"""Unconstrained Θ → PD correlation matrix Ω*."""
|
|
252
|
+
|
|
253
|
+
def grad_corr_theta(theta: NDArray, K: int, *, xp=None) -> NDArray:
|
|
254
|
+
"""dΩ*/dΘ Jacobian."""
|
|
255
|
+
|
|
256
|
+
# _chain_rules.py
|
|
257
|
+
def chain_grad(dA_dOmega: NDArray, dOmega_dparam: NDArray, *, xp=None) -> NDArray:
|
|
258
|
+
"""dA/dparam = dA/dOmega @ dOmega/dparam (row-based order)."""
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### gradmvn Module
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
# _mvncd.py
|
|
265
|
+
def mvncd(a: NDArray, sigma: NDArray, *, method="me", xp=None) -> float:
|
|
266
|
+
"""P(X₁≤a₁,...,X_K≤a_K) for X~MVN(0,Σ). Bhat (2018) ME approximation."""
|
|
267
|
+
|
|
268
|
+
def mvncd_batch(a: NDArray, sigma: NDArray, *, method="me", xp=None) -> NDArray:
|
|
269
|
+
"""Vectorized MVNCD for N observations."""
|
|
270
|
+
|
|
271
|
+
# _mvncd_grad.py
|
|
272
|
+
@dataclass
|
|
273
|
+
class MVNCDGradResult:
|
|
274
|
+
prob: float
|
|
275
|
+
grad_a: NDArray # (K,)
|
|
276
|
+
grad_sigma: NDArray # (K*(K-1)//2,)
|
|
277
|
+
|
|
278
|
+
def mvncd_grad(a, sigma, *, method="me", xp=None) -> MVNCDGradResult: ...
|
|
279
|
+
|
|
280
|
+
# _truncated.py
|
|
281
|
+
def truncated_mvn_pdf(x, mu, sigma, lower, upper, *, xp=None) -> float: ...
|
|
282
|
+
def truncated_mvn_cdf(x, mu, sigma, lower, upper, *, xp=None) -> float: ...
|
|
283
|
+
def truncated_mvn_pdf_grad(x, mu, sigma, lower, upper, *, xp=None) -> tuple: ...
|
|
284
|
+
|
|
285
|
+
# _partial_cdf.py
|
|
286
|
+
def partial_mvn_cdf(points, lower, upper, mu, sigma, point_indices=None, range_indices=None, *, xp=None) -> float: ...
|
|
287
|
+
|
|
288
|
+
# _other_dists.py
|
|
289
|
+
def mv_logistic_cdf(x, sigma, *, xp=None) -> float: ...
|
|
290
|
+
def skew_normal_pdf(x, alpha, *, xp=None) -> float: ...
|
|
291
|
+
def skew_normal_cdf(x, alpha, *, xp=None) -> float: ...
|
|
292
|
+
def skew_t_pdf(x, alpha, nu, *, xp=None) -> float: ...
|
|
293
|
+
def gumbel_pdf(x, mu=0.0, beta=1.0, *, xp=None) -> float: ...
|
|
294
|
+
def gumbel_cdf(x, mu=0.0, beta=1.0, *, xp=None) -> float: ...
|
|
295
|
+
def reverse_gumbel_pdf(x, mu=0.0, beta=1.0, *, xp=None) -> float: ...
|
|
296
|
+
def reverse_gumbel_cdf(x, mu=0.0, beta=1.0, *, xp=None) -> float: ...
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### MNP Model
|
|
300
|
+
|
|
301
|
+
```python
|
|
302
|
+
# _mnp_control.py
|
|
303
|
+
@dataclass
|
|
304
|
+
class MNPControl:
|
|
305
|
+
iid: bool = False
|
|
306
|
+
mix: bool = False
|
|
307
|
+
indep: bool = False
|
|
308
|
+
correst: NDArray | None = None
|
|
309
|
+
heteronly: bool = False
|
|
310
|
+
rannddiag: bool = False
|
|
311
|
+
nseg: int = 1
|
|
312
|
+
maxiter: int = 200
|
|
313
|
+
tol: float = 1e-5
|
|
314
|
+
optimizer: Literal["bfgs", "lbfgsb", "torch_adam", "torch_lbfgs"] = "bfgs"
|
|
315
|
+
verbose: int = 1
|
|
316
|
+
seed: int | None = None
|
|
317
|
+
|
|
318
|
+
# _mnp_results.py
|
|
319
|
+
@dataclass
|
|
320
|
+
class MNPResults:
|
|
321
|
+
b: NDArray # Estimated coefficients (parametrized)
|
|
322
|
+
b_original: NDArray # Unparametrized coefficients
|
|
323
|
+
se: NDArray # Standard errors
|
|
324
|
+
t_stat: NDArray # t-statistics
|
|
325
|
+
p_value: NDArray # p-values
|
|
326
|
+
gradient: NDArray # Gradient at convergence
|
|
327
|
+
ll: float # Mean log-likelihood
|
|
328
|
+
ll_total: float # Total log-likelihood
|
|
329
|
+
n_obs: int # Number of observations
|
|
330
|
+
param_names: list[str] # Parameter names
|
|
331
|
+
corr_matrix: NDArray # Correlation matrix of parameters
|
|
332
|
+
cov_matrix: NDArray # Var-cov matrix of parameters
|
|
333
|
+
n_iterations: int
|
|
334
|
+
convergence_time: float
|
|
335
|
+
converged: bool
|
|
336
|
+
return_code: int
|
|
337
|
+
lambda_hat: NDArray | None # Kernel error covariance (if IID=False)
|
|
338
|
+
omega_hat: NDArray | None # Random coeff covariance (if mix=True)
|
|
339
|
+
cholesky_L: NDArray | None # Cholesky of Omega (if mix=True)
|
|
340
|
+
segment_probs: NDArray | None # Mixture probabilities (if nseg>1)
|
|
341
|
+
segment_means: list[NDArray] | None
|
|
342
|
+
segment_covs: list[NDArray] | None
|
|
343
|
+
control: MNPControl
|
|
344
|
+
data_path: str
|
|
345
|
+
|
|
346
|
+
def summary(self) -> str: ...
|
|
347
|
+
def to_dataframe(self) -> pd.DataFrame: ...
|
|
348
|
+
|
|
349
|
+
# _mnp_model.py
|
|
350
|
+
class MNPModel:
|
|
351
|
+
def __init__(self, data, alternatives, availability="none", spec=None,
|
|
352
|
+
var_names=None, mix=False, ranvars=None, control=None): ...
|
|
353
|
+
def fit(self) -> MNPResults: ...
|
|
354
|
+
|
|
355
|
+
# _mnp_loglik.py
|
|
356
|
+
def mnp_loglik(theta, X, y, avail, control, *, return_gradient=False, xp=None) -> float | tuple: ...
|
|
357
|
+
|
|
358
|
+
# _mnp_ate.py
|
|
359
|
+
@dataclass
|
|
360
|
+
class ATEResult:
|
|
361
|
+
n_obs: int
|
|
362
|
+
predicted_shares: NDArray
|
|
363
|
+
base_shares: NDArray | None
|
|
364
|
+
treatment_shares: NDArray | None
|
|
365
|
+
pct_ate: NDArray | None
|
|
366
|
+
|
|
367
|
+
def mnp_ate(results: MNPResults, changevar=None, changeval=None) -> ATEResult: ...
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Optimization
|
|
371
|
+
|
|
372
|
+
```python
|
|
373
|
+
# _scipy_optim.py
|
|
374
|
+
@dataclass
|
|
375
|
+
class OptimResult:
|
|
376
|
+
x: NDArray; fun: float; grad: NDArray; hess_inv: NDArray
|
|
377
|
+
n_iter: int; converged: bool; return_code: int; message: str
|
|
378
|
+
|
|
379
|
+
def minimize_scipy(func, x0, method="BFGS", maxiter=200, tol=1e-5, verbose=1, jac=True) -> OptimResult: ...
|
|
380
|
+
|
|
381
|
+
# _torch_optim.py
|
|
382
|
+
def minimize_torch(func, x0, method="lbfgs", maxiter=200, tol=1e-5, verbose=1, device="cpu") -> OptimResult: ...
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### I/O
|
|
386
|
+
|
|
387
|
+
```python
|
|
388
|
+
# _data_loader.py
|
|
389
|
+
def load_data(path, *, file_type=None) -> pd.DataFrame: ...
|
|
390
|
+
|
|
391
|
+
# _spec_parser.py
|
|
392
|
+
def parse_spec(spec, data, alternatives, nseg=1) -> tuple[NDArray, list[str]]: ...
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## pyproject.toml
|
|
398
|
+
|
|
399
|
+
```toml
|
|
400
|
+
[build-system]
|
|
401
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
402
|
+
build-backend = "hatchling.build"
|
|
403
|
+
|
|
404
|
+
[project]
|
|
405
|
+
name = "pybhatlib"
|
|
406
|
+
dynamic = ["version"]
|
|
407
|
+
description = "Python implementation of BHATLIB: matrix-based inference for advanced econometric models"
|
|
408
|
+
readme = "README.md"
|
|
409
|
+
license = "MIT"
|
|
410
|
+
requires-python = ">=3.10"
|
|
411
|
+
authors = [{ name = "Seongjin Choi", email = "choi@umn.edu" }]
|
|
412
|
+
keywords = ["discrete choice", "multinomial probit", "econometrics", "MVNCD", "covariance matrix"]
|
|
413
|
+
dependencies = ["numpy>=1.24", "scipy>=1.10", "pandas>=2.0"]
|
|
414
|
+
|
|
415
|
+
[project.optional-dependencies]
|
|
416
|
+
torch = ["torch>=2.0"]
|
|
417
|
+
dev = ["pytest>=7.0", "pytest-cov", "ruff", "mypy", "pre-commit"]
|
|
418
|
+
docs = ["sphinx", "sphinx-rtd-theme", "numpydoc"]
|
|
419
|
+
all = ["pybhatlib[torch,dev,docs]"]
|
|
420
|
+
|
|
421
|
+
[tool.hatch.version]
|
|
422
|
+
source = "vcs"
|
|
423
|
+
|
|
424
|
+
[tool.hatch.build.targets.wheel]
|
|
425
|
+
packages = ["src/pybhatlib"]
|
|
426
|
+
|
|
427
|
+
[tool.pytest.ini_options]
|
|
428
|
+
testpaths = ["tests"]
|
|
429
|
+
markers = ["torch: tests requiring PyTorch", "slow: long-running integration tests"]
|
|
430
|
+
|
|
431
|
+
[tool.ruff]
|
|
432
|
+
line-length = 88
|
|
433
|
+
target-version = "py310"
|
|
434
|
+
|
|
435
|
+
[tool.mypy]
|
|
436
|
+
python_version = "3.10"
|
|
437
|
+
warn_return_any = true
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
## Implementation Phases
|
|
443
|
+
|
|
444
|
+
### Phase 0: Project Scaffolding
|
|
445
|
+
- Create pyproject.toml, README.md, LICENSE, .gitignore, CLAUDE.md
|
|
446
|
+
- Create all directories and `__init__.py` files
|
|
447
|
+
- Add TRAVELMODE.csv to examples/data/
|
|
448
|
+
- Initialize git, set up tests/conftest.py
|
|
449
|
+
- Verify `pip install -e .` works
|
|
450
|
+
|
|
451
|
+
### Phase 1: Backend Abstraction
|
|
452
|
+
- _array_api.py, _numpy_backend.py, _torch_backend.py
|
|
453
|
+
- Tests verifying both backends produce identical results
|
|
454
|
+
|
|
455
|
+
### Phase 2: vecup Module
|
|
456
|
+
- _vec_ops.py, _nondiag.py, _ldlt.py, _truncnorm.py, _mask.py
|
|
457
|
+
- Tests against paper examples (p. 6)
|
|
458
|
+
|
|
459
|
+
### Phase 3: matgradient Module
|
|
460
|
+
- _gradcovcor.py, _gomegxomegax.py, _spherical.py, _chain_rules.py
|
|
461
|
+
- Numerical gradient verification via finite differences
|
|
462
|
+
|
|
463
|
+
### Phase 4: gradmvn Module
|
|
464
|
+
- _mvncd.py (Bhat 2018 ME approximation), _mvncd_grad.py
|
|
465
|
+
- _truncated.py, _partial_cdf.py, _univariate.py, _other_dists.py
|
|
466
|
+
- Validate against scipy.stats for K≤3
|
|
467
|
+
|
|
468
|
+
### Phase 5: Optimization & I/O
|
|
469
|
+
- _scipy_optim.py, _torch_optim.py, _convergence.py
|
|
470
|
+
- _data_loader.py, _spec_parser.py
|
|
471
|
+
|
|
472
|
+
### Phase 6: MNP Model
|
|
473
|
+
- _mnp_control.py, _mnp_results.py, _mnp_model.py
|
|
474
|
+
- _mnp_loglik.py (IID, flexible cov, random coefficients, mixture-of-normals)
|
|
475
|
+
- _mnp_ate.py, _mnp_forecast.py
|
|
476
|
+
|
|
477
|
+
### Phase 7: Integration Testing & Validation
|
|
478
|
+
- Replicate Table 1 results from BHATLIB paper
|
|
479
|
+
- ATE validation against Figure 12
|
|
480
|
+
- Cross-backend verification (NumPy ≈ PyTorch)
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
## Build Order (dependency graph)
|
|
485
|
+
|
|
486
|
+
```
|
|
487
|
+
backend → vecup → matgradient → gradmvn → optim/io → models/mnp
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
## Key References
|
|
491
|
+
|
|
492
|
+
1. Bhat, C. R. (2018). "New matrix-based methods for the analytic evaluation of the MVNCD." TR Part B, 109: 238–256.
|
|
493
|
+
2. Bhat, C. R. (2015). "A new GHDM to jointly model mixed types of dependent variables." TR Part B, 79: 50–77.
|
|
494
|
+
3. Bhat, C. R. (2024). "Transformation-based flexible error structures for choice modeling." J. Choice Modelling, 53: 100522.
|
|
495
|
+
4. Higham, N. J. (2009). "Cholesky factorization." WIREs Comp. Stats., 1(2): 251–254.
|
|
496
|
+
5. Bhat, C. R. (2014). "The CML inference approach." Found. Trends Econometrics, 7(1): 1–117.
|
|
497
|
+
6. Saxena, S., Bhat, C. R., Pinjari, A. R. (2023). "Separation-based parameterization strategies." J. Choice Modelling, 47: 100411.
|
pybhatlib-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Seongjin Choi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|