pybhatlib 0.2.0__tar.gz → 0.3.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.2.0 → pybhatlib-0.3.0}/.gitignore +3 -0
- pybhatlib-0.3.0/GAUSS_INTEGRATION_PLAN.md +235 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/PKG-INFO +17 -1
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/README.md +16 -0
- pybhatlib-0.3.0/examples/data/TRAVELMODE.csv +1126 -0
- pybhatlib-0.3.0/examples/mnp_age45.py +45 -0
- pybhatlib-0.3.0/examples/mnp_mixture.py +51 -0
- pybhatlib-0.3.0/examples/tutorials/python_scripts/t04a_mnp_iid.py +181 -0
- pybhatlib-0.3.0/examples/tutorials/python_scripts/t04b_mnp_flexible_cov.py +192 -0
- pybhatlib-0.3.0/examples/tutorials/python_scripts/t04d_mnp_random_coefficients.py +230 -0
- pybhatlib-0.3.0/examples/tutorials/python_scripts/t04e_mnp_mixture.py +207 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t03a_mvncd_methods.ipynb +1 -1
- pybhatlib-0.3.0/examples/tutorials/t03e_bhat2018_table1.ipynb +44 -0
- pybhatlib-0.3.0/examples/tutorials/t04a_mnp_iid.ipynb +99 -0
- pybhatlib-0.3.0/examples/tutorials/t04b_mnp_flexible_cov.ipynb +115 -0
- pybhatlib-0.3.0/examples/tutorials/t04d_mnp_random_coefficients.ipynb +99 -0
- pybhatlib-0.3.0/examples/tutorials/t04e_mnp_mixture.ipynb +99 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/pyproject.toml +1 -1
- pybhatlib-0.3.0/src/pybhatlib/gradmvn/__init__.py +52 -0
- pybhatlib-0.3.0/src/pybhatlib/gradmvn/_cond_trunc_grads.py +471 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/gradmvn/_mvncd.py +194 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/gradmvn/_mvncd_grad.py +28 -15
- pybhatlib-0.3.0/src/pybhatlib/gradmvn/_mvncd_grad_analytic.py +255 -0
- pybhatlib-0.3.0/src/pybhatlib/gradmvn/_ordering.py +143 -0
- pybhatlib-0.3.0/src/pybhatlib/gradmvn/_trunc_grads.py +562 -0
- pybhatlib-0.3.0/src/pybhatlib/gradmvn/_univariate.py +335 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/matgradient/__init__.py +3 -0
- pybhatlib-0.3.0/src/pybhatlib/matgradient/_mat_grad_helpers.py +325 -0
- pybhatlib-0.3.0/src/pybhatlib/matgradient/_radial.py +181 -0
- pybhatlib-0.3.0/src/pybhatlib/matgradient/_spherical.py +252 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/mnp/_mnp_ate.py +3 -4
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/mnp/_mnp_control.py +1 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/mnp/_mnp_forecast.py +1 -1
- pybhatlib-0.3.0/src/pybhatlib/models/mnp/_mnp_grad_analytic.py +386 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/mnp/_mnp_loglik.py +19 -9
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/mnp/_mnp_model.py +5 -3
- pybhatlib-0.3.0/tests/test_gradmvn/test_cond_trunc_grads.py +492 -0
- pybhatlib-0.3.0/tests/test_gradmvn/test_mvncd_grad_analytic.py +302 -0
- pybhatlib-0.3.0/tests/test_gradmvn/test_ordering.py +277 -0
- pybhatlib-0.3.0/tests/test_gradmvn/test_trunc_grads.py +473 -0
- pybhatlib-0.3.0/tests/test_matgradient/test_mat_grad_helpers.py +359 -0
- pybhatlib-0.3.0/tests/test_matgradient/test_radial.py +218 -0
- pybhatlib-0.3.0/tests/test_matgradient/test_spherical.py +244 -0
- pybhatlib-0.3.0/tests/test_models/test_mnp_grad.py +586 -0
- pybhatlib-0.2.0/src/pybhatlib/gradmvn/__init__.py +0 -24
- pybhatlib-0.2.0/src/pybhatlib/gradmvn/_univariate.py +0 -193
- pybhatlib-0.2.0/src/pybhatlib/matgradient/_spherical.py +0 -190
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/CLAUDE.md +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/IMPLEMENTATION_PLAN.md +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/LICENSE +0 -0
- /pybhatlib-0.2.0/examples/data/TRAVELMODE.csv → /pybhatlib-0.3.0/examples/data/TRAVELMODE_synthetic.csv +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/data/generate_travelmode.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/mnp_ate_analysis.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/mnp_flexible_cov.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/mnp_iid.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/mnp_random_coefficients.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/morp_example.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/_convert_to_ipynb.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t00_quickstart.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t01a_vectorization.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t01b_ldlt.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t01c_truncated_mvn.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t02a_gradcovcor.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t02b_spherical.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t02c_chain_rules.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t03a_mvncd_methods.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t03b_mvncd_gradients.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t03c_mvncd_rect.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t03d_univariate_cdfs.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t03e_bhat2018_table1.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t04c_mnp_heteronly.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t04f_mnp_control_options.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t04g_mnp_forecasting.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t05b_morp_ate_predict.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t06a_backend_switching.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t06b_custom_specs.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t06c_gradient_verification.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t00_quickstart.ipynb +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t01a_vectorization.ipynb +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t01b_ldlt.ipynb +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t01c_truncated_mvn.ipynb +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t02a_gradcovcor.ipynb +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t02b_spherical.ipynb +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t02c_chain_rules.ipynb +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t03b_mvncd_gradients.ipynb +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t03c_mvncd_rect.ipynb +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t03d_univariate_cdfs.ipynb +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t04c_mnp_heteronly.ipynb +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t04f_mnp_control_options.ipynb +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t04g_mnp_forecasting.ipynb +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t05b_morp_ate_predict.ipynb +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t06a_backend_switching.ipynb +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t06b_custom_specs.ipynb +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t06c_gradient_verification.ipynb +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/__init__.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/_version.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/backend/__init__.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/backend/_array_api.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/backend/_numpy_backend.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/backend/_torch_backend.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/gradmvn/_bivariate_trunc.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/gradmvn/_mvncd_ssj.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/gradmvn/_other_dists.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/gradmvn/_partial_cdf.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/gradmvn/_truncated.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/io/__init__.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/io/_data_loader.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/io/_spec_parser.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/matgradient/_chain_rules.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/matgradient/_gomegxomegax.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/matgradient/_gradcovcor.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/__init__.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/_base.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/mnp/__init__.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/mnp/_mnp_results.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/morp/__init__.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/morp/_morp_ate.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/morp/_morp_control.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/morp/_morp_forecast.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/morp/_morp_loglik.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/morp/_morp_model.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/morp/_morp_results.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/optim/__init__.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/optim/_convergence.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/optim/_scipy_optim.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/optim/_torch_optim.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/utils/__init__.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/utils/_qmc.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/utils/_seeds.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/utils/_validation.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/vecup/__init__.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/vecup/_ldlt.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/vecup/_mask.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/vecup/_nondiag.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/vecup/_truncnorm.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/vecup/_vec_ops.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/__init__.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/conftest.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_backend/__init__.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_backend/test_array_api.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_gradmvn/__init__.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_gradmvn/test_bivariate_trunc.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_gradmvn/test_mvncd.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_gradmvn/test_mvncd_methods.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_gradmvn/test_mvncd_rect.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_gradmvn/test_other_dists.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_integration/__init__.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_matgradient/__init__.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_matgradient/test_gradcovcor.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_models/__init__.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_models/test_mnp_control.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_models/test_morp.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_vecup/__init__.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_vecup/test_ldlt.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_vecup/test_ldlt_rank2.py +0 -0
- {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_vecup/test_vec_ops.py +0 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# Refine pybhatlib from Chandra's GAUSS Code
|
|
2
|
+
|
|
3
|
+
## Context
|
|
4
|
+
|
|
5
|
+
We compared the authoritative GAUSS implementation (`chandra_code/mnp_lat_TRAVELMODE.gss`) against pybhatlib and found 10+ gaps. The biggest performance bottleneck is **numerical gradients everywhere** — the GAUSS code has full analytic gradient chains via sequential LDLT conditioning with truncated moments, while Python uses finite differences (3N+1 function evaluations per iteration).
|
|
6
|
+
|
|
7
|
+
This plan addresses refinements in priority order, grouped into independently testable phases.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Phase A: Quick Wins (Robustness Improvements)
|
|
12
|
+
|
|
13
|
+
**Files to modify:**
|
|
14
|
+
- `src/pybhatlib/matgradient/_spherical.py` — add π·logistic constraint + numerical safeguards
|
|
15
|
+
- `src/pybhatlib/models/mnp/_mnp_model.py` — better starting values
|
|
16
|
+
|
|
17
|
+
### A1. Spherical angle constraint: π·logistic mapping
|
|
18
|
+
|
|
19
|
+
GAUSS `cholspherparmunconst` (line 3726) maps unconstrained θ** → [0,π] via:
|
|
20
|
+
```
|
|
21
|
+
θ* = π / (1 + exp(-θ**))
|
|
22
|
+
```
|
|
23
|
+
Our `theta_to_corr()` uses raw angles without this constraint. Add the logistic mapping at the entry point of `theta_to_corr()` and inverse at `corr_to_theta()` (if exists).
|
|
24
|
+
|
|
25
|
+
### A2. Numerical safeguards in spherical Cholesky
|
|
26
|
+
|
|
27
|
+
GAUSS (lines 4202-4205) thresholds tiny trig values:
|
|
28
|
+
```
|
|
29
|
+
if |cos(θ)| < 6.12e-17 → cos = 0
|
|
30
|
+
if |sin(θ)| < 1.22e-16 → sin = 0
|
|
31
|
+
```
|
|
32
|
+
Add to `_build_cholesky_from_angles()`.
|
|
33
|
+
|
|
34
|
+
### A3. Better starting values
|
|
35
|
+
|
|
36
|
+
GAUSS uses `0.5*I + 0.5*ones(K,K)` as initial correlation structure. Our MNPModel uses zeros for correlation params. Change starting values for lambda_params correlation portion to produce a moderate correlation structure.
|
|
37
|
+
|
|
38
|
+
### Verification
|
|
39
|
+
- All 126 existing tests must still pass
|
|
40
|
+
- `theta_to_corr()` should produce identical results for angles already in [0,π]
|
|
41
|
+
- New starting values should not worsen convergence on existing examples
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Phase B: GGE Ordering + TG/TGBME Methods
|
|
46
|
+
|
|
47
|
+
**Files to create:**
|
|
48
|
+
- `src/pybhatlib/gradmvn/_ordering.py` — GGE/Gibson ordering algorithm
|
|
49
|
+
- `tests/test_gradmvn/test_ordering.py`
|
|
50
|
+
|
|
51
|
+
**Files to modify:**
|
|
52
|
+
- `src/pybhatlib/gradmvn/_mvncd.py` — add TG, TGBME methods + ordering dispatch
|
|
53
|
+
- `src/pybhatlib/gradmvn/__init__.py` — export ordering
|
|
54
|
+
|
|
55
|
+
### B1. GGE ordering (GAUSS `ordering`, line 2039)
|
|
56
|
+
|
|
57
|
+
Algorithm: at each step h, compute standardized deviations z_i = (a_i - μ_trunc) / σ_trunc for remaining variables, reorder by ascending z_i (bring most constrained variable forward).
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
def gge_ordering(a: NDArray, sigma: NDArray) -> NDArray:
|
|
61
|
+
"""Return permutation indices via Gibson-Geman-Escobar adaptive ordering."""
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### B2. TG CDF method (GAUSS `cdfmvnaTG`, line 837)
|
|
65
|
+
|
|
66
|
+
Sequential univariate conditioning without rank-1 updates — just strips LDLT rows/columns:
|
|
67
|
+
```
|
|
68
|
+
P = ∏ₕ Φ((aₕ - μ̃ₕ) / √Dₕ)
|
|
69
|
+
```
|
|
70
|
+
Simpler than ME. Uses GGE ordering for accuracy.
|
|
71
|
+
|
|
72
|
+
### B3. TGBME CDF method (GAUSS `cdfmvnaTGBME`, line 1297)
|
|
73
|
+
|
|
74
|
+
Bivariate conditioning variant: pairs variables, uses `ldltblock(Σ, 2)` and bivariate truncated moments for rank-2 updates. Handles odd K by using univariate for the last variable.
|
|
75
|
+
|
|
76
|
+
### Verification
|
|
77
|
+
- FD comparison: TG and TGBME should match scipy.stats.multivariate_normal.cdf within ~2% MAPE for K=5
|
|
78
|
+
- Compare GGE ordering against ascending ordering accuracy (GGE should be equal or better)
|
|
79
|
+
- Existing MVNCD tests must still pass
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Phase C: Analytic MVNCD Gradients (Core)
|
|
84
|
+
|
|
85
|
+
This is the highest-impact change. The GAUSS gradient chain for MVNCD:
|
|
86
|
+
```
|
|
87
|
+
pdfmvnaME/OVUS → gcondmeantrunc/gcondcovtrunc → grad_univariate_normal_trunc → gradnoncdfn
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### C1. Atomic gradient helpers
|
|
91
|
+
|
|
92
|
+
**Create:** `src/pybhatlib/gradmvn/_trunc_grads.py`
|
|
93
|
+
**Test:** `tests/test_gradmvn/test_trunc_grads.py`
|
|
94
|
+
|
|
95
|
+
Functions (all verified against FD):
|
|
96
|
+
|
|
97
|
+
| Function | GAUSS ref | Purpose |
|
|
98
|
+
|----------|-----------|---------|
|
|
99
|
+
| `grad_univariate_normal_trunc` | line 2120 | ∂(μ_trunc, σ²_trunc)/∂(μ, σ², w) |
|
|
100
|
+
| `grad_bivariate_normal_trunc` | line 2138 | ∂(μ_trunc, Σ_trunc)/∂(μ, Σ, w) for bivariate |
|
|
101
|
+
| `grad_cdf_bvn` | line 2512 | ∂BVN(w₁,w₂,ρ)/∂(w₁,w₂,ρ) |
|
|
102
|
+
| `grad_noncdfn` | line 2489 | ∂Φ((x-μ)/σ)/∂(μ,σ²,x) |
|
|
103
|
+
| `grad_noncdfbvn_by_cdfn` | line 2561 | ∂[BVN/Φ]/∂(μ,Σ,x) for OVUS screening ratio |
|
|
104
|
+
|
|
105
|
+
### C2. Compound gradient helpers
|
|
106
|
+
|
|
107
|
+
**Create:** `src/pybhatlib/gradmvn/_cond_trunc_grads.py`
|
|
108
|
+
**Create:** `src/pybhatlib/matgradient/_mat_grad_helpers.py`
|
|
109
|
+
**Test:** `tests/test_gradmvn/test_cond_trunc_grads.py`
|
|
110
|
+
|
|
111
|
+
| Function | GAUSS ref | Purpose |
|
|
112
|
+
|----------|-----------|---------|
|
|
113
|
+
| `gcondmeantrunc` | line 2224 | Gradient of conditional mean through truncation |
|
|
114
|
+
| `gcondcovtrunc` | line 2389 | Gradient of conditional covariance through truncation |
|
|
115
|
+
| `ginverse` | — | ∂X⁻¹/∂X (matrix inverse gradient) |
|
|
116
|
+
| `gcondcov` | line 2323 | ∂(X₂₂ - X₁₂X₁₁⁻¹X₁₂ᵀ)/∂X |
|
|
117
|
+
|
|
118
|
+
### C3. ME and OVUS analytic gradients
|
|
119
|
+
|
|
120
|
+
**Create:** `src/pybhatlib/gradmvn/_mvncd_grad_analytic.py`
|
|
121
|
+
**Modify:** `src/pybhatlib/gradmvn/_mvncd_grad.py` — add `analytic` parameter
|
|
122
|
+
**Test:** `tests/test_gradmvn/test_mvncd_grad_analytic.py`
|
|
123
|
+
|
|
124
|
+
Core algorithm (ME, from GAUSS `pdfmvnaME` line 981):
|
|
125
|
+
```
|
|
126
|
+
For h = 1..m-1:
|
|
127
|
+
1. Truncate: {μ̃, ω} = univariate_normal_trunc(...)
|
|
128
|
+
2. Gradients: {gY, gmu, gX, gC} = gcondmeantrunc(...)
|
|
129
|
+
3. Cumulative Jacobian: gcumul = gcumul @ [[gmumean|gmucov], [gXmean|gXcov]]
|
|
130
|
+
4. Update LDLT via rank-1
|
|
131
|
+
5. Factor: p[h+1] = Φ((a[h+1]-μ̃)/√D)
|
|
132
|
+
6. Accumulate: grho += (1/p[h+1]) * gcumul @ grad_factor
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Returns `(prob, gw, grho)` in standardized space.
|
|
136
|
+
|
|
137
|
+
OVUS differs: uses bivariate screening (kkk=2), `grad_noncdfbvn_by_cdfn` instead of `grad_noncdfn`, initial BVN instead of univariate Φ.
|
|
138
|
+
|
|
139
|
+
### C4. Standardization wrapper
|
|
140
|
+
|
|
141
|
+
Update `mvncd_grad()` to dispatch to analytic when available:
|
|
142
|
+
```python
|
|
143
|
+
def mvncd_grad(a, sigma, *, method="me", analytic=True, xp=None):
|
|
144
|
+
if analytic and method in ("me", "ovus"):
|
|
145
|
+
# Standardize: w = a/√diag(Σ), ρ = corr(Σ)
|
|
146
|
+
# Call _mvncd_grad_me/ovus(w, ρ) → (prob, gw, grho)
|
|
147
|
+
# Destandardize via gradcorcov: gcov = f(gw, grho)
|
|
148
|
+
...
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Verification
|
|
152
|
+
- Every atomic helper: FD verification at multiple parameter regimes (moderate, extreme truncation, high/low ρ)
|
|
153
|
+
- Analytic `gw` vs numerical: `|(gw_analytic - gw_numerical)| / max(|gw|, 1e-8) < 1e-3` for K=3,4,5
|
|
154
|
+
- Analytic `grho` vs numerical: same tolerance
|
|
155
|
+
- Cross-method: ME and OVUS analytic should agree within approximation tolerance
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Phase D: MNP Analytic Gradient Chain
|
|
160
|
+
|
|
161
|
+
**Create:** `src/pybhatlib/models/mnp/_mnp_grad.py`
|
|
162
|
+
**Modify:** `src/pybhatlib/models/mnp/_mnp_loglik.py` — wire in analytic gradient
|
|
163
|
+
**Modify:** `src/pybhatlib/models/mnp/_mnp_control.py` — add `analytic_grad` field
|
|
164
|
+
**Test:** `tests/test_models/test_mnp_grad.py`
|
|
165
|
+
|
|
166
|
+
### D1. Analytic gradient function
|
|
167
|
+
|
|
168
|
+
Translates GAUSS `lgd` (line 297). The chain rule:
|
|
169
|
+
```
|
|
170
|
+
∂(-logL)/∂θ = -(1/N) Σ_q (1/P_q) ∂P_q/∂θ
|
|
171
|
+
|
|
172
|
+
where ∂P_q/∂θ decomposes as:
|
|
173
|
+
∂P/∂β = (∂P/∂B) × (∂B/∂β) [mean gradient via design matrix]
|
|
174
|
+
∂P/∂λ = (∂P/∂Σ) × gomegxomegax(M) × gradcovcor × gcholspher [cov gradient]
|
|
175
|
+
∂P/∂ω = (∂P/∂Σ) × gomegxomegax(M) × [scale gradient]
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Uses existing `gradcovcor()`, `gomegxomegax()` (already implemented, just not integrated).
|
|
179
|
+
|
|
180
|
+
### D2. Integration into mnp_loglik
|
|
181
|
+
|
|
182
|
+
Add `analytic_grad: bool = True` to `MNPControl`. When True and `return_gradient=True`, call `mnp_analytic_gradient()` instead of `_numerical_gradient()`.
|
|
183
|
+
|
|
184
|
+
### Verification
|
|
185
|
+
- Component FD: For small problem (N=10, I=3), compare analytic `∂nll/∂θ_k` vs FD at `1e-4` tolerance
|
|
186
|
+
- Convergence: BFGS with analytic grads converges to same optimum as numerical
|
|
187
|
+
- Performance: time comparison (expect ~7-14x speedup depending on parameter count)
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Phase E: Additional Parameterizations
|
|
192
|
+
|
|
193
|
+
### E1. Radial parameterization (Van Oest 2019)
|
|
194
|
+
|
|
195
|
+
**Create:** `src/pybhatlib/matgradient/_radial.py`
|
|
196
|
+
**Test:** `tests/test_matgradient/test_radial.py`
|
|
197
|
+
|
|
198
|
+
GAUSS `newcholparm` (line 3752): `S = tanh(θ)`, accumulative sine products.
|
|
199
|
+
Include analytic Jacobian from `gnewcholparm` (line 4048).
|
|
200
|
+
|
|
201
|
+
### E2. Analytic spherical Jacobian
|
|
202
|
+
|
|
203
|
+
**Modify:** `src/pybhatlib/matgradient/_spherical.py` — replace numerical FD in `grad_corr_theta()`
|
|
204
|
+
**Create:** `src/pybhatlib/matgradient/_cholesky_grad.py` — `gcholeskycov`
|
|
205
|
+
|
|
206
|
+
GAUSS `gcholspherunconstcorscaled` (line 3871) provides fully analytic Jacobian.
|
|
207
|
+
|
|
208
|
+
### Verification
|
|
209
|
+
- Radial: `radial_to_corr()` must produce valid PD correlation for any input
|
|
210
|
+
- Radial Jacobian vs FD: `1e-5` tolerance
|
|
211
|
+
- Analytic spherical Jacobian vs current numerical: `1e-5` tolerance
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Implementation Order & Dependencies
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
Phase A (quick wins) ← independent, do first
|
|
219
|
+
Phase B (TG/TGBME) ← independent of C/D
|
|
220
|
+
Phase C1 (atomic grads) ← foundation
|
|
221
|
+
Phase C2 (compound grads) ← depends on C1
|
|
222
|
+
Phase C3 (MVNCD grads) ← depends on C1, C2
|
|
223
|
+
Phase C4 (wrapper) ← depends on C3
|
|
224
|
+
Phase D (MNP grad chain) ← depends on C4
|
|
225
|
+
Phase E (parameterizations) ← independent, enhances D
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Expected Performance Impact
|
|
229
|
+
|
|
230
|
+
For K=4 (I=5 alternatives), P=20 parameters:
|
|
231
|
+
- **Current**: (2×20+1)=41 likelihood evaluations per optimizer iteration
|
|
232
|
+
- **With Phase C+D**: ~3 equivalent evaluations per iteration
|
|
233
|
+
- **Speedup**: ~14x
|
|
234
|
+
|
|
235
|
+
For larger models (P=50): ~33x speedup.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pybhatlib
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Python implementation of BHATLIB: matrix-based inference for advanced econometric models
|
|
5
5
|
Project-URL: Homepage, https://github.com/UMN-Choi-Lab/pybhatlib
|
|
6
6
|
Project-URL: Repository, https://github.com/UMN-Choi-Lab/pybhatlib
|
|
@@ -96,6 +96,22 @@ results.summary()
|
|
|
96
96
|
- **Dual backend**: NumPy (default) + optional PyTorch with GPU support
|
|
97
97
|
- **Post-estimation**: Average Treatment Effects (ATE), forecasting
|
|
98
98
|
|
|
99
|
+
## Verification
|
|
100
|
+
|
|
101
|
+
pybhatlib reproduces Table 1 from the BHATLIB paper (Bhat 2018) using synthetic
|
|
102
|
+
TRAVELMODE data with 3 travel modes (DA, SR, TR) and 210 observations:
|
|
103
|
+
|
|
104
|
+
| Model | Specification | Target LL | Achieved LL | Status |
|
|
105
|
+
|-------|--------------|-----------|-------------|--------|
|
|
106
|
+
| (a)(i) | IID errors | -670.956 | -670.956 | Exact match |
|
|
107
|
+
| (a)(ii) | Flexible covariance | -661.111 | -661.111 | Exact match |
|
|
108
|
+
| (b) | + AGE45 demographics | -659.285 | -659.284 | Exact match |
|
|
109
|
+
| (c) | + Random coeff. OVTT | -635.871 | -635.871 | Exact match |
|
|
110
|
+
| (d) | 2-segment mixture | -634.975 | -632.912 | Close (synthetic data) |
|
|
111
|
+
|
|
112
|
+
Models (a)–(c) match the published results exactly. Model (d) achieves a slightly
|
|
113
|
+
better log-likelihood due to multiple local optima in mixture models with synthetic data.
|
|
114
|
+
|
|
99
115
|
## References
|
|
100
116
|
|
|
101
117
|
1. Bhat, C. R. (2018). New Matrix-Based Methods for the Analytic Evaluation of the
|
|
@@ -58,6 +58,22 @@ results.summary()
|
|
|
58
58
|
- **Dual backend**: NumPy (default) + optional PyTorch with GPU support
|
|
59
59
|
- **Post-estimation**: Average Treatment Effects (ATE), forecasting
|
|
60
60
|
|
|
61
|
+
## Verification
|
|
62
|
+
|
|
63
|
+
pybhatlib reproduces Table 1 from the BHATLIB paper (Bhat 2018) using synthetic
|
|
64
|
+
TRAVELMODE data with 3 travel modes (DA, SR, TR) and 210 observations:
|
|
65
|
+
|
|
66
|
+
| Model | Specification | Target LL | Achieved LL | Status |
|
|
67
|
+
|-------|--------------|-----------|-------------|--------|
|
|
68
|
+
| (a)(i) | IID errors | -670.956 | -670.956 | Exact match |
|
|
69
|
+
| (a)(ii) | Flexible covariance | -661.111 | -661.111 | Exact match |
|
|
70
|
+
| (b) | + AGE45 demographics | -659.285 | -659.284 | Exact match |
|
|
71
|
+
| (c) | + Random coeff. OVTT | -635.871 | -635.871 | Exact match |
|
|
72
|
+
| (d) | 2-segment mixture | -634.975 | -632.912 | Close (synthetic data) |
|
|
73
|
+
|
|
74
|
+
Models (a)–(c) match the published results exactly. Model (d) achieves a slightly
|
|
75
|
+
better log-likelihood due to multiple local optima in mixture models with synthetic data.
|
|
76
|
+
|
|
61
77
|
## References
|
|
62
78
|
|
|
63
79
|
1. Bhat, C. R. (2018). New Matrix-Based Methods for the Analytic Evaluation of the
|