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.
Files changed (156) hide show
  1. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/.gitignore +3 -0
  2. pybhatlib-0.3.0/GAUSS_INTEGRATION_PLAN.md +235 -0
  3. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/PKG-INFO +17 -1
  4. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/README.md +16 -0
  5. pybhatlib-0.3.0/examples/data/TRAVELMODE.csv +1126 -0
  6. pybhatlib-0.3.0/examples/mnp_age45.py +45 -0
  7. pybhatlib-0.3.0/examples/mnp_mixture.py +51 -0
  8. pybhatlib-0.3.0/examples/tutorials/python_scripts/t04a_mnp_iid.py +181 -0
  9. pybhatlib-0.3.0/examples/tutorials/python_scripts/t04b_mnp_flexible_cov.py +192 -0
  10. pybhatlib-0.3.0/examples/tutorials/python_scripts/t04d_mnp_random_coefficients.py +230 -0
  11. pybhatlib-0.3.0/examples/tutorials/python_scripts/t04e_mnp_mixture.py +207 -0
  12. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t03a_mvncd_methods.ipynb +1 -1
  13. pybhatlib-0.3.0/examples/tutorials/t03e_bhat2018_table1.ipynb +44 -0
  14. pybhatlib-0.3.0/examples/tutorials/t04a_mnp_iid.ipynb +99 -0
  15. pybhatlib-0.3.0/examples/tutorials/t04b_mnp_flexible_cov.ipynb +115 -0
  16. pybhatlib-0.3.0/examples/tutorials/t04d_mnp_random_coefficients.ipynb +99 -0
  17. pybhatlib-0.3.0/examples/tutorials/t04e_mnp_mixture.ipynb +99 -0
  18. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/pyproject.toml +1 -1
  19. pybhatlib-0.3.0/src/pybhatlib/gradmvn/__init__.py +52 -0
  20. pybhatlib-0.3.0/src/pybhatlib/gradmvn/_cond_trunc_grads.py +471 -0
  21. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/gradmvn/_mvncd.py +194 -0
  22. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/gradmvn/_mvncd_grad.py +28 -15
  23. pybhatlib-0.3.0/src/pybhatlib/gradmvn/_mvncd_grad_analytic.py +255 -0
  24. pybhatlib-0.3.0/src/pybhatlib/gradmvn/_ordering.py +143 -0
  25. pybhatlib-0.3.0/src/pybhatlib/gradmvn/_trunc_grads.py +562 -0
  26. pybhatlib-0.3.0/src/pybhatlib/gradmvn/_univariate.py +335 -0
  27. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/matgradient/__init__.py +3 -0
  28. pybhatlib-0.3.0/src/pybhatlib/matgradient/_mat_grad_helpers.py +325 -0
  29. pybhatlib-0.3.0/src/pybhatlib/matgradient/_radial.py +181 -0
  30. pybhatlib-0.3.0/src/pybhatlib/matgradient/_spherical.py +252 -0
  31. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/mnp/_mnp_ate.py +3 -4
  32. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/mnp/_mnp_control.py +1 -0
  33. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/mnp/_mnp_forecast.py +1 -1
  34. pybhatlib-0.3.0/src/pybhatlib/models/mnp/_mnp_grad_analytic.py +386 -0
  35. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/mnp/_mnp_loglik.py +19 -9
  36. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/mnp/_mnp_model.py +5 -3
  37. pybhatlib-0.3.0/tests/test_gradmvn/test_cond_trunc_grads.py +492 -0
  38. pybhatlib-0.3.0/tests/test_gradmvn/test_mvncd_grad_analytic.py +302 -0
  39. pybhatlib-0.3.0/tests/test_gradmvn/test_ordering.py +277 -0
  40. pybhatlib-0.3.0/tests/test_gradmvn/test_trunc_grads.py +473 -0
  41. pybhatlib-0.3.0/tests/test_matgradient/test_mat_grad_helpers.py +359 -0
  42. pybhatlib-0.3.0/tests/test_matgradient/test_radial.py +218 -0
  43. pybhatlib-0.3.0/tests/test_matgradient/test_spherical.py +244 -0
  44. pybhatlib-0.3.0/tests/test_models/test_mnp_grad.py +586 -0
  45. pybhatlib-0.2.0/src/pybhatlib/gradmvn/__init__.py +0 -24
  46. pybhatlib-0.2.0/src/pybhatlib/gradmvn/_univariate.py +0 -193
  47. pybhatlib-0.2.0/src/pybhatlib/matgradient/_spherical.py +0 -190
  48. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/CLAUDE.md +0 -0
  49. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/IMPLEMENTATION_PLAN.md +0 -0
  50. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/LICENSE +0 -0
  51. /pybhatlib-0.2.0/examples/data/TRAVELMODE.csv → /pybhatlib-0.3.0/examples/data/TRAVELMODE_synthetic.csv +0 -0
  52. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/data/generate_travelmode.py +0 -0
  53. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/mnp_ate_analysis.py +0 -0
  54. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/mnp_flexible_cov.py +0 -0
  55. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/mnp_iid.py +0 -0
  56. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/mnp_random_coefficients.py +0 -0
  57. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/morp_example.py +0 -0
  58. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/_convert_to_ipynb.py +0 -0
  59. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t00_quickstart.py +0 -0
  60. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t01a_vectorization.py +0 -0
  61. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t01b_ldlt.py +0 -0
  62. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t01c_truncated_mvn.py +0 -0
  63. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t02a_gradcovcor.py +0 -0
  64. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t02b_spherical.py +0 -0
  65. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t02c_chain_rules.py +0 -0
  66. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t03a_mvncd_methods.py +0 -0
  67. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t03b_mvncd_gradients.py +0 -0
  68. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t03c_mvncd_rect.py +0 -0
  69. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t03d_univariate_cdfs.py +0 -0
  70. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t03e_bhat2018_table1.py +0 -0
  71. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t04c_mnp_heteronly.py +0 -0
  72. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t04f_mnp_control_options.py +0 -0
  73. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t04g_mnp_forecasting.py +0 -0
  74. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t05b_morp_ate_predict.py +0 -0
  75. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t06a_backend_switching.py +0 -0
  76. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t06b_custom_specs.py +0 -0
  77. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/python_scripts/t06c_gradient_verification.py +0 -0
  78. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t00_quickstart.ipynb +0 -0
  79. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t01a_vectorization.ipynb +0 -0
  80. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t01b_ldlt.ipynb +0 -0
  81. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t01c_truncated_mvn.ipynb +0 -0
  82. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t02a_gradcovcor.ipynb +0 -0
  83. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t02b_spherical.ipynb +0 -0
  84. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t02c_chain_rules.ipynb +0 -0
  85. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t03b_mvncd_gradients.ipynb +0 -0
  86. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t03c_mvncd_rect.ipynb +0 -0
  87. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t03d_univariate_cdfs.ipynb +0 -0
  88. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t04c_mnp_heteronly.ipynb +0 -0
  89. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t04f_mnp_control_options.ipynb +0 -0
  90. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t04g_mnp_forecasting.ipynb +0 -0
  91. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t05b_morp_ate_predict.ipynb +0 -0
  92. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t06a_backend_switching.ipynb +0 -0
  93. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t06b_custom_specs.ipynb +0 -0
  94. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/examples/tutorials/t06c_gradient_verification.ipynb +0 -0
  95. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/__init__.py +0 -0
  96. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/_version.py +0 -0
  97. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/backend/__init__.py +0 -0
  98. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/backend/_array_api.py +0 -0
  99. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/backend/_numpy_backend.py +0 -0
  100. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/backend/_torch_backend.py +0 -0
  101. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/gradmvn/_bivariate_trunc.py +0 -0
  102. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/gradmvn/_mvncd_ssj.py +0 -0
  103. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/gradmvn/_other_dists.py +0 -0
  104. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/gradmvn/_partial_cdf.py +0 -0
  105. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/gradmvn/_truncated.py +0 -0
  106. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/io/__init__.py +0 -0
  107. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/io/_data_loader.py +0 -0
  108. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/io/_spec_parser.py +0 -0
  109. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/matgradient/_chain_rules.py +0 -0
  110. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/matgradient/_gomegxomegax.py +0 -0
  111. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/matgradient/_gradcovcor.py +0 -0
  112. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/__init__.py +0 -0
  113. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/_base.py +0 -0
  114. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/mnp/__init__.py +0 -0
  115. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/mnp/_mnp_results.py +0 -0
  116. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/morp/__init__.py +0 -0
  117. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/morp/_morp_ate.py +0 -0
  118. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/morp/_morp_control.py +0 -0
  119. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/morp/_morp_forecast.py +0 -0
  120. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/morp/_morp_loglik.py +0 -0
  121. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/morp/_morp_model.py +0 -0
  122. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/models/morp/_morp_results.py +0 -0
  123. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/optim/__init__.py +0 -0
  124. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/optim/_convergence.py +0 -0
  125. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/optim/_scipy_optim.py +0 -0
  126. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/optim/_torch_optim.py +0 -0
  127. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/utils/__init__.py +0 -0
  128. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/utils/_qmc.py +0 -0
  129. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/utils/_seeds.py +0 -0
  130. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/utils/_validation.py +0 -0
  131. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/vecup/__init__.py +0 -0
  132. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/vecup/_ldlt.py +0 -0
  133. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/vecup/_mask.py +0 -0
  134. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/vecup/_nondiag.py +0 -0
  135. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/vecup/_truncnorm.py +0 -0
  136. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/src/pybhatlib/vecup/_vec_ops.py +0 -0
  137. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/__init__.py +0 -0
  138. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/conftest.py +0 -0
  139. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_backend/__init__.py +0 -0
  140. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_backend/test_array_api.py +0 -0
  141. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_gradmvn/__init__.py +0 -0
  142. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_gradmvn/test_bivariate_trunc.py +0 -0
  143. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_gradmvn/test_mvncd.py +0 -0
  144. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_gradmvn/test_mvncd_methods.py +0 -0
  145. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_gradmvn/test_mvncd_rect.py +0 -0
  146. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_gradmvn/test_other_dists.py +0 -0
  147. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_integration/__init__.py +0 -0
  148. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_matgradient/__init__.py +0 -0
  149. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_matgradient/test_gradcovcor.py +0 -0
  150. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_models/__init__.py +0 -0
  151. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_models/test_mnp_control.py +0 -0
  152. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_models/test_morp.py +0 -0
  153. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_vecup/__init__.py +0 -0
  154. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_vecup/test_ldlt.py +0 -0
  155. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_vecup/test_ldlt_rank2.py +0 -0
  156. {pybhatlib-0.2.0 → pybhatlib-0.3.0}/tests/test_vecup/test_vec_ops.py +0 -0
@@ -30,6 +30,9 @@ htmlcov/
30
30
  *.pdf
31
31
  results/
32
32
 
33
+ # Original GAUSS source
34
+ chandra_code/
35
+
33
36
  # OS
34
37
  .DS_Store
35
38
  Thumbs.db
@@ -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.2.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