pybhatlib 0.3.0__tar.gz → 0.3.2__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 (257) hide show
  1. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/.gitignore +20 -0
  2. pybhatlib-0.3.2/CHANGELOG.md +47 -0
  3. pybhatlib-0.3.2/CONTRIBUTING.md +188 -0
  4. pybhatlib-0.3.2/PKG-INFO +219 -0
  5. pybhatlib-0.3.2/README.md +179 -0
  6. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/data/TRAVELMODE.csv +1126 -1126
  7. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/data/TRAVELMODE_synthetic.csv +1126 -1126
  8. pybhatlib-0.3.2/examples/data/WorkshopData_ToursimExp.csv +1501 -0
  9. pybhatlib-0.3.2/examples/data/Workshop_SCAG_Est.csv +1501 -0
  10. pybhatlib-0.3.2/examples/data/modeData.csv +3594 -0
  11. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/mnp_age45.py +1 -1
  12. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/mnp_flexible_cov.py +1 -1
  13. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/mnp_iid.py +1 -1
  14. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/mnp_mixture.py +1 -1
  15. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/mnp_random_coefficients.py +1 -1
  16. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/morp_example.py +12 -4
  17. pybhatlib-0.3.2/examples/tutorials/_convert_to_ipynb.py +445 -0
  18. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/tutorials/python_scripts/t00_quickstart.py +137 -129
  19. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/tutorials/python_scripts/t01a_vectorization.py +163 -142
  20. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/tutorials/python_scripts/t01b_ldlt.py +135 -130
  21. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/tutorials/python_scripts/t01c_truncated_mvn.py +169 -115
  22. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/tutorials/python_scripts/t02a_gradcovcor.py +227 -151
  23. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/tutorials/python_scripts/t02b_spherical.py +147 -141
  24. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/tutorials/python_scripts/t02c_chain_rules.py +202 -174
  25. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/tutorials/python_scripts/t03a_mvncd_methods.py +199 -132
  26. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/tutorials/python_scripts/t03b_mvncd_gradients.py +166 -150
  27. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/tutorials/python_scripts/t03c_mvncd_rect.py +172 -156
  28. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/tutorials/python_scripts/t03d_univariate_cdfs.py +189 -160
  29. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/tutorials/python_scripts/t03e_bhat2018_table1.py +40 -3
  30. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/tutorials/python_scripts/t04a_mnp_iid.py +107 -17
  31. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/tutorials/python_scripts/t04b_mnp_flexible_cov.py +132 -17
  32. pybhatlib-0.3.2/examples/tutorials/python_scripts/t04c_mnp_heteronly.py +250 -0
  33. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/tutorials/python_scripts/t04d_mnp_random_coefficients.py +93 -15
  34. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/tutorials/python_scripts/t04e_mnp_mixture.py +98 -34
  35. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/tutorials/python_scripts/t04f_mnp_control_options.py +314 -189
  36. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/tutorials/python_scripts/t04g_mnp_forecasting.py +262 -164
  37. pybhatlib-0.3.2/examples/tutorials/python_scripts/t04h_bhatlib_table1.py +565 -0
  38. pybhatlib-0.3.2/examples/tutorials/python_scripts/t04i_bhat2018_table2.py +736 -0
  39. pybhatlib-0.3.2/examples/tutorials/python_scripts/t05b_morp_ate_predict.py +342 -0
  40. pybhatlib-0.3.2/examples/tutorials/python_scripts/t06a_backend_switching.py +353 -0
  41. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/tutorials/python_scripts/t06b_custom_specs.py +228 -222
  42. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/tutorials/python_scripts/t06c_gradient_verification.py +298 -269
  43. pybhatlib-0.3.2/examples/tutorials/python_scripts/t07a_mdcev_trad.py +286 -0
  44. pybhatlib-0.3.2/examples/tutorials/python_scripts/t07b_mdcev_lin.py +254 -0
  45. pybhatlib-0.3.2/examples/tutorials/python_scripts/t08a_mnl.py +38 -0
  46. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/tutorials/t00_quickstart.ipynb +150 -115
  47. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/tutorials/t01a_vectorization.ipynb +173 -131
  48. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/tutorials/t01b_ldlt.ipynb +140 -99
  49. pybhatlib-0.3.2/examples/tutorials/t01c_truncated_mvn.ipynb +194 -0
  50. pybhatlib-0.3.2/examples/tutorials/t02a_gradcovcor.ipynb +232 -0
  51. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/tutorials/t02b_spherical.ipynb +133 -115
  52. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/tutorials/t02c_chain_rules.ipynb +114 -114
  53. pybhatlib-0.3.2/examples/tutorials/t03a_mvncd_methods.ipynb +195 -0
  54. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/tutorials/t03b_mvncd_gradients.ipynb +140 -115
  55. pybhatlib-0.3.2/examples/tutorials/t03c_mvncd_rect.ipynb +180 -0
  56. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/tutorials/t03d_univariate_cdfs.ipynb +130 -115
  57. pybhatlib-0.3.2/examples/tutorials/t03e_bhat2018_table1.ipynb +224 -0
  58. pybhatlib-0.3.2/examples/tutorials/t04a_mnp_iid.ipynb +314 -0
  59. pybhatlib-0.3.2/examples/tutorials/t04b_mnp_flexible_cov.ipynb +331 -0
  60. pybhatlib-0.3.2/examples/tutorials/t04c_mnp_heteronly.ipynb +196 -0
  61. pybhatlib-0.3.2/examples/tutorials/t04d_mnp_random_coefficients.ipynb +340 -0
  62. pybhatlib-0.3.2/examples/tutorials/t04e_mnp_mixture.ipynb +314 -0
  63. pybhatlib-0.3.2/examples/tutorials/t04f_mnp_control_options.ipynb +295 -0
  64. pybhatlib-0.3.2/examples/tutorials/t04g_mnp_forecasting.ipynb +211 -0
  65. pybhatlib-0.3.2/examples/tutorials/t04h_bhatlib_table1.ipynb +580 -0
  66. pybhatlib-0.3.2/examples/tutorials/t04i_bhat2018_table2.ipynb +507 -0
  67. pybhatlib-0.3.2/examples/tutorials/t05b_morp_ate_predict.ipynb +272 -0
  68. pybhatlib-0.3.2/examples/tutorials/t06a_backend_switching.ipynb +307 -0
  69. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/tutorials/t06b_custom_specs.ipynb +130 -130
  70. pybhatlib-0.3.2/examples/tutorials/t06c_gradient_verification.ipynb +315 -0
  71. pybhatlib-0.3.2/examples/tutorials/t07a_mdcev_trad.ipynb +298 -0
  72. pybhatlib-0.3.2/examples/tutorials/t07b_mdcev_lin.ipynb +267 -0
  73. pybhatlib-0.3.2/examples/tutorials/t08a_mnl.ipynb +35 -0
  74. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/pyproject.toml +27 -2
  75. pybhatlib-0.3.2/src/pybhatlib/__init__.py +54 -0
  76. pybhatlib-0.3.2/src/pybhatlib/_version.py +1 -0
  77. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/backend/_numpy_backend.py +6 -4
  78. pybhatlib-0.3.2/src/pybhatlib/gradmvn/__init__.py +116 -0
  79. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/gradmvn/_bivariate_trunc.py +15 -9
  80. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/gradmvn/_cond_trunc_grads.py +11 -5
  81. pybhatlib-0.3.2/src/pybhatlib/gradmvn/_logit_dist.py +1491 -0
  82. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/gradmvn/_mvncd.py +711 -46
  83. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/gradmvn/_mvncd_grad.py +8 -2
  84. pybhatlib-0.3.2/src/pybhatlib/gradmvn/_mvncd_grad_analytic.py +738 -0
  85. pybhatlib-0.3.2/src/pybhatlib/gradmvn/_mvncd_grad_ovus.py +539 -0
  86. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/gradmvn/_mvncd_ssj.py +4 -4
  87. pybhatlib-0.3.2/src/pybhatlib/gradmvn/_mvncd_torch.py +372 -0
  88. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/gradmvn/_ordering.py +10 -3
  89. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/gradmvn/_other_dists.py +10 -4
  90. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/gradmvn/_partial_cdf.py +3 -2
  91. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/gradmvn/_trunc_grads.py +20 -14
  92. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/gradmvn/_truncated.py +15 -9
  93. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/gradmvn/_univariate.py +198 -9
  94. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/matgradient/__init__.py +6 -1
  95. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/matgradient/_spherical.py +73 -0
  96. pybhatlib-0.3.2/src/pybhatlib/models/_ate_common.py +228 -0
  97. pybhatlib-0.3.2/src/pybhatlib/models/_base.py +41 -0
  98. pybhatlib-0.3.2/src/pybhatlib/models/_results_common.py +162 -0
  99. pybhatlib-0.3.2/src/pybhatlib/models/mdcev/__init__.py +34 -0
  100. pybhatlib-0.3.2/src/pybhatlib/models/mdcev/_mdcev_ate.py +282 -0
  101. pybhatlib-0.3.2/src/pybhatlib/models/mdcev/_mdcev_control.py +96 -0
  102. pybhatlib-0.3.2/src/pybhatlib/models/mdcev/_mdcev_forecast.py +429 -0
  103. pybhatlib-0.3.2/src/pybhatlib/models/mdcev/_mdcev_loglik.py +651 -0
  104. pybhatlib-0.3.2/src/pybhatlib/models/mdcev/_mdcev_model.py +593 -0
  105. pybhatlib-0.3.2/src/pybhatlib/models/mdcev/_mdcev_results.py +371 -0
  106. pybhatlib-0.3.2/src/pybhatlib/models/mnl/__init__.py +18 -0
  107. pybhatlib-0.3.2/src/pybhatlib/models/mnl/_mnl_ate.py +316 -0
  108. pybhatlib-0.3.2/src/pybhatlib/models/mnl/_mnl_control.py +65 -0
  109. pybhatlib-0.3.2/src/pybhatlib/models/mnl/_mnl_forecast.py +73 -0
  110. pybhatlib-0.3.2/src/pybhatlib/models/mnl/_mnl_loglik.py +266 -0
  111. pybhatlib-0.3.2/src/pybhatlib/models/mnl/_mnl_model.py +410 -0
  112. pybhatlib-0.3.2/src/pybhatlib/models/mnl/_mnl_results.py +280 -0
  113. pybhatlib-0.3.2/src/pybhatlib/models/mnp/__init__.py +24 -0
  114. pybhatlib-0.3.2/src/pybhatlib/models/mnp/_mnp_ate.py +417 -0
  115. pybhatlib-0.3.2/src/pybhatlib/models/mnp/_mnp_control.py +147 -0
  116. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/models/mnp/_mnp_forecast.py +18 -39
  117. pybhatlib-0.3.2/src/pybhatlib/models/mnp/_mnp_grad_analytic.py +1488 -0
  118. pybhatlib-0.3.2/src/pybhatlib/models/mnp/_mnp_grad_gpu.py +447 -0
  119. pybhatlib-0.3.2/src/pybhatlib/models/mnp/_mnp_loglik.py +1707 -0
  120. pybhatlib-0.3.2/src/pybhatlib/models/mnp/_mnp_model.py +1695 -0
  121. pybhatlib-0.3.2/src/pybhatlib/models/mnp/_mnp_results.py +425 -0
  122. pybhatlib-0.3.2/src/pybhatlib/models/morp/__init__.py +35 -0
  123. pybhatlib-0.3.2/src/pybhatlib/models/morp/_morp_ate.py +404 -0
  124. pybhatlib-0.3.2/src/pybhatlib/models/morp/_morp_control.py +277 -0
  125. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/models/morp/_morp_forecast.py +25 -21
  126. pybhatlib-0.3.2/src/pybhatlib/models/morp/_morp_grad_analytic.py +756 -0
  127. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/models/morp/_morp_loglik.py +169 -40
  128. pybhatlib-0.3.2/src/pybhatlib/models/morp/_morp_model.py +631 -0
  129. pybhatlib-0.3.2/src/pybhatlib/models/morp/_morp_report.py +316 -0
  130. pybhatlib-0.3.2/src/pybhatlib/models/morp/_morp_results.py +468 -0
  131. pybhatlib-0.3.2/src/pybhatlib/optim/_scipy_optim.py +287 -0
  132. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/utils/_qmc.py +2 -2
  133. pybhatlib-0.3.2/src/pybhatlib/vecup/_ldlt.py +297 -0
  134. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/vecup/_truncnorm.py +11 -5
  135. pybhatlib-0.3.2/tests/fixtures/bhatlib_table2_targets.json +102 -0
  136. pybhatlib-0.3.2/tests/test_api_harmonization.py +337 -0
  137. pybhatlib-0.3.2/tests/test_gradmvn/test_mvncd_batch.py +169 -0
  138. pybhatlib-0.3.2/tests/test_gradmvn/test_mvncd_grad_ovus.py +303 -0
  139. pybhatlib-0.3.2/tests/test_gradmvn/test_mvncd_rect_inf_bounds.py +400 -0
  140. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/tests/test_matgradient/test_spherical.py +29 -0
  141. pybhatlib-0.3.2/tests/test_models/test_ate_common.py +99 -0
  142. pybhatlib-0.3.2/tests/test_models/test_ate_from_params.py +189 -0
  143. pybhatlib-0.3.2/tests/test_models/test_mdcev.py +229 -0
  144. pybhatlib-0.3.2/tests/test_models/test_mdcev_scenarios.py +82 -0
  145. pybhatlib-0.3.2/tests/test_models/test_mnl.py +180 -0
  146. pybhatlib-0.3.2/tests/test_models/test_mnl_ate_from_params.py +76 -0
  147. pybhatlib-0.3.2/tests/test_models/test_mnp_active_mask_verbose.py +454 -0
  148. pybhatlib-0.3.2/tests/test_models/test_mnp_ate_scenarios.py +449 -0
  149. pybhatlib-0.3.2/tests/test_models/test_mnp_gauss_parity.py +134 -0
  150. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/tests/test_models/test_mnp_grad.py +9 -7
  151. pybhatlib-0.3.2/tests/test_models/test_mnp_grad_mixture.py +450 -0
  152. pybhatlib-0.3.2/tests/test_models/test_mnp_mixture_ranvars.py +571 -0
  153. pybhatlib-0.3.2/tests/test_models/test_mnp_perobs_scores.py +173 -0
  154. pybhatlib-0.3.2/tests/test_models/test_mnp_se_options.py +140 -0
  155. pybhatlib-0.3.2/tests/test_models/test_mnp_table2_parity.py +371 -0
  156. pybhatlib-0.3.2/tests/test_models/test_mnp_unpar_se.py +556 -0
  157. pybhatlib-0.3.2/tests/test_models/test_model_facade.py +227 -0
  158. pybhatlib-0.3.2/tests/test_models/test_morp.py +631 -0
  159. pybhatlib-0.3.2/tests/test_models/test_morp_analytic_gradient.py +531 -0
  160. pybhatlib-0.3.2/tests/test_models/test_morp_gauss_parity.py +257 -0
  161. pybhatlib-0.3.2/tests/test_models/test_morp_per_outcome_spec.py +451 -0
  162. pybhatlib-0.3.2/tests/test_models/test_morp_scenarios.py +131 -0
  163. pybhatlib-0.3.2/tests/test_models/test_results_harmonization.py +209 -0
  164. pybhatlib-0.3.0/CLAUDE.md +0 -42
  165. pybhatlib-0.3.0/GAUSS_INTEGRATION_PLAN.md +0 -235
  166. pybhatlib-0.3.0/IMPLEMENTATION_PLAN.md +0 -497
  167. pybhatlib-0.3.0/PKG-INFO +0 -124
  168. pybhatlib-0.3.0/README.md +0 -86
  169. pybhatlib-0.3.0/examples/tutorials/_convert_to_ipynb.py +0 -224
  170. pybhatlib-0.3.0/examples/tutorials/python_scripts/t04c_mnp_heteronly.py +0 -154
  171. pybhatlib-0.3.0/examples/tutorials/python_scripts/t05b_morp_ate_predict.py +0 -171
  172. pybhatlib-0.3.0/examples/tutorials/python_scripts/t06a_backend_switching.py +0 -139
  173. pybhatlib-0.3.0/examples/tutorials/t01c_truncated_mvn.ipynb +0 -99
  174. pybhatlib-0.3.0/examples/tutorials/t02a_gradcovcor.ipynb +0 -115
  175. pybhatlib-0.3.0/examples/tutorials/t03a_mvncd_methods.ipynb +0 -99
  176. pybhatlib-0.3.0/examples/tutorials/t03c_mvncd_rect.ipynb +0 -99
  177. pybhatlib-0.3.0/examples/tutorials/t03e_bhat2018_table1.ipynb +0 -44
  178. pybhatlib-0.3.0/examples/tutorials/t04a_mnp_iid.ipynb +0 -99
  179. pybhatlib-0.3.0/examples/tutorials/t04b_mnp_flexible_cov.ipynb +0 -115
  180. pybhatlib-0.3.0/examples/tutorials/t04c_mnp_heteronly.ipynb +0 -115
  181. pybhatlib-0.3.0/examples/tutorials/t04d_mnp_random_coefficients.ipynb +0 -99
  182. pybhatlib-0.3.0/examples/tutorials/t04e_mnp_mixture.ipynb +0 -99
  183. pybhatlib-0.3.0/examples/tutorials/t04f_mnp_control_options.ipynb +0 -131
  184. pybhatlib-0.3.0/examples/tutorials/t04g_mnp_forecasting.ipynb +0 -115
  185. pybhatlib-0.3.0/examples/tutorials/t05b_morp_ate_predict.ipynb +0 -115
  186. pybhatlib-0.3.0/examples/tutorials/t06a_backend_switching.ipynb +0 -115
  187. pybhatlib-0.3.0/examples/tutorials/t06c_gradient_verification.ipynb +0 -131
  188. pybhatlib-0.3.0/src/pybhatlib/__init__.py +0 -5
  189. pybhatlib-0.3.0/src/pybhatlib/_version.py +0 -1
  190. pybhatlib-0.3.0/src/pybhatlib/gradmvn/__init__.py +0 -52
  191. pybhatlib-0.3.0/src/pybhatlib/gradmvn/_mvncd_grad_analytic.py +0 -255
  192. pybhatlib-0.3.0/src/pybhatlib/models/_base.py +0 -14
  193. pybhatlib-0.3.0/src/pybhatlib/models/mnp/__init__.py +0 -14
  194. pybhatlib-0.3.0/src/pybhatlib/models/mnp/_mnp_ate.py +0 -192
  195. pybhatlib-0.3.0/src/pybhatlib/models/mnp/_mnp_control.py +0 -89
  196. pybhatlib-0.3.0/src/pybhatlib/models/mnp/_mnp_grad_analytic.py +0 -386
  197. pybhatlib-0.3.0/src/pybhatlib/models/mnp/_mnp_loglik.py +0 -575
  198. pybhatlib-0.3.0/src/pybhatlib/models/mnp/_mnp_model.py +0 -421
  199. pybhatlib-0.3.0/src/pybhatlib/models/mnp/_mnp_results.py +0 -181
  200. pybhatlib-0.3.0/src/pybhatlib/models/morp/__init__.py +0 -17
  201. pybhatlib-0.3.0/src/pybhatlib/models/morp/_morp_ate.py +0 -119
  202. pybhatlib-0.3.0/src/pybhatlib/models/morp/_morp_control.py +0 -56
  203. pybhatlib-0.3.0/src/pybhatlib/models/morp/_morp_model.py +0 -276
  204. pybhatlib-0.3.0/src/pybhatlib/models/morp/_morp_results.py +0 -154
  205. pybhatlib-0.3.0/src/pybhatlib/optim/_scipy_optim.py +0 -156
  206. pybhatlib-0.3.0/src/pybhatlib/vecup/_ldlt.py +0 -160
  207. pybhatlib-0.3.0/tests/test_models/test_morp.py +0 -210
  208. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/LICENSE +0 -0
  209. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/data/generate_travelmode.py +0 -0
  210. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/examples/mnp_ate_analysis.py +0 -0
  211. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/backend/__init__.py +0 -0
  212. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/backend/_array_api.py +0 -0
  213. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/backend/_torch_backend.py +0 -0
  214. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/io/__init__.py +0 -0
  215. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/io/_data_loader.py +0 -0
  216. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/io/_spec_parser.py +0 -0
  217. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/matgradient/_chain_rules.py +0 -0
  218. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/matgradient/_gomegxomegax.py +0 -0
  219. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/matgradient/_gradcovcor.py +0 -0
  220. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/matgradient/_mat_grad_helpers.py +0 -0
  221. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/matgradient/_radial.py +0 -0
  222. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/models/__init__.py +0 -0
  223. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/optim/__init__.py +0 -0
  224. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/optim/_convergence.py +0 -0
  225. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/optim/_torch_optim.py +0 -0
  226. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/utils/__init__.py +0 -0
  227. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/utils/_seeds.py +0 -0
  228. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/utils/_validation.py +0 -0
  229. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/vecup/__init__.py +0 -0
  230. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/vecup/_mask.py +0 -0
  231. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/vecup/_nondiag.py +0 -0
  232. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/src/pybhatlib/vecup/_vec_ops.py +0 -0
  233. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/tests/__init__.py +0 -0
  234. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/tests/conftest.py +0 -0
  235. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/tests/test_backend/__init__.py +0 -0
  236. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/tests/test_backend/test_array_api.py +0 -0
  237. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/tests/test_gradmvn/__init__.py +0 -0
  238. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/tests/test_gradmvn/test_bivariate_trunc.py +0 -0
  239. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/tests/test_gradmvn/test_cond_trunc_grads.py +0 -0
  240. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/tests/test_gradmvn/test_mvncd.py +0 -0
  241. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/tests/test_gradmvn/test_mvncd_grad_analytic.py +0 -0
  242. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/tests/test_gradmvn/test_mvncd_methods.py +0 -0
  243. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/tests/test_gradmvn/test_mvncd_rect.py +0 -0
  244. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/tests/test_gradmvn/test_ordering.py +0 -0
  245. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/tests/test_gradmvn/test_other_dists.py +0 -0
  246. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/tests/test_gradmvn/test_trunc_grads.py +0 -0
  247. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/tests/test_integration/__init__.py +0 -0
  248. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/tests/test_matgradient/__init__.py +0 -0
  249. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/tests/test_matgradient/test_gradcovcor.py +0 -0
  250. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/tests/test_matgradient/test_mat_grad_helpers.py +0 -0
  251. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/tests/test_matgradient/test_radial.py +0 -0
  252. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/tests/test_models/__init__.py +0 -0
  253. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/tests/test_models/test_mnp_control.py +0 -0
  254. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/tests/test_vecup/__init__.py +0 -0
  255. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/tests/test_vecup/test_ldlt.py +0 -0
  256. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/tests/test_vecup/test_ldlt_rank2.py +0 -0
  257. {pybhatlib-0.3.0 → pybhatlib-0.3.2}/tests/test_vecup/test_vec_ops.py +0 -0
@@ -28,10 +28,23 @@ htmlcov/
28
28
  *.dat
29
29
  *.pkl
30
30
  *.pdf
31
+ *.tar.gz
31
32
  results/
33
+ experiments/
34
+ pr32-*.md
32
35
 
33
36
  # Original GAUSS source
34
37
  chandra_code/
38
+ UTAcode_0402/
39
+ slides/
40
+ docs/
41
+ Gauss Files and Comparison/
42
+ anna0605/
43
+
44
+ # Project-local docs (not for repo)
45
+ CLAUDE.md
46
+ GAUSS_INTEGRATION_PLAN.md
47
+ IMPLEMENTATION_PLAN.md
35
48
 
36
49
  # OS
37
50
  .DS_Store
@@ -50,3 +63,10 @@ nul
50
63
  *.fdb_latexmk
51
64
  *.fls
52
65
  *.synctex.gz
66
+
67
+ # PESOSE proposal working files — internal, NOT for the (soon-public) repo
68
+ PESOSE_*
69
+ PESOSE-related
70
+ .env
71
+ .claude
72
+
@@ -0,0 +1,47 @@
1
+ # Changelog
2
+
3
+ All notable changes to pybhatlib are documented here. The format is based on
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
5
+ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.3.2] - 2026-07-22
8
+
9
+ First version available on PyPI. Identical in code to 0.3.1; released under a new
10
+ patch number only because 0.3.1's filenames had already been reserved on PyPI (an
11
+ earlier 0.3.1 upload was deleted, and PyPI never permits reusing a deleted
12
+ filename). See the 0.3.1 entry below for the full feature list.
13
+
14
+ ## [0.3.1] - 2026-07-22
15
+
16
+ Prepared as the first PyPI release but withdrawn before general availability; never
17
+ installable from PyPI. Superseded by 0.3.2 (same code). Contents:
18
+
19
+ ### Models
20
+ - **Multinomial Probit (MNP)** — IID, flexible covariance, heteroscedastic-only,
21
+ random coefficients, and mixture-of-normals specifications.
22
+ - **Multivariate Ordered Response Probit (MORP)** — multiple ordinal outcomes
23
+ with shared covariance and per-outcome `spec` mapping.
24
+ - **Multiple Discrete-Continuous Extreme Value (MDCEV)** — traditional
25
+ (Bhat 2008) and linear (Bhat 2018) outside-good utility specifications.
26
+ - **Multinomial Logit (MNL)**.
27
+
28
+ ### Numerical core
29
+ - `vecup` — vecdup, matdupfull, LDLT decomposition, truncated MVN moments.
30
+ - `matgradient` — gradcovcor, gomegxomegax, spherical / Cholesky parameterizations.
31
+ - `gradmvn` — Bhat (2018) MVNCD analytic approximation with analytic gradients.
32
+
33
+ ### Estimation & post-estimation
34
+ - BHHH / Hessian / sandwich standard-error estimators, computed at fit time.
35
+ - Average Treatment Effects (ATE) with scenario-matrix support, forecasting,
36
+ per-category MORP probability prediction, and ATEs computed directly from
37
+ supplied coefficients without re-fitting.
38
+ - Optional PyTorch backend (`pip install pybhatlib[torch]`) with GPU support;
39
+ all numerical functions accept an optional `xp` backend kwarg.
40
+
41
+ ### Verification
42
+ - Reproduces Table 1 of Bhat (2018) on the TRAVELMODE dataset: models (a)-(c)
43
+ match published log-likelihoods and BHHH standard errors to =0.001; model (d)
44
+ is documented as multi-modal.
45
+
46
+ [0.3.2]: https://github.com/UMN-Choi-Lab/pybhatlib/releases/tag/v0.3.2
47
+ [0.3.1]: https://github.com/UMN-Choi-Lab/pybhatlib/releases/tag/v0.3.1
@@ -0,0 +1,188 @@
1
+ # Contributing to pybhatlib
2
+
3
+ Thanks for your interest in contributing. This guide covers the pull request
4
+ workflow, local development setup, and the standards we maintain for
5
+ numerical correctness. It is written to be usable even if you are new to
6
+ GitHub — read top to bottom the first time.
7
+
8
+ ## Quick overview
9
+
10
+ - All changes go through a **pull request (PR)** — nobody pushes directly to `main`.
11
+ - Each PR covers **one logical change**, not a batch of unrelated edits.
12
+ - CI runs `pytest` automatically on every PR. **CI must pass before merging.**
13
+ - Because pybhatlib reproduces published log-likelihood values exactly, any
14
+ change that alters numerical results must be justified and verified.
15
+
16
+ ## Setting up locally
17
+
18
+ ```bash
19
+ git clone https://github.com/UMN-Choi-Lab/pybhatlib.git
20
+ cd pybhatlib
21
+ pip install -e ".[dev]"
22
+ ```
23
+
24
+ Verify the install:
25
+
26
+ ```bash
27
+ pytest tests/ -m "not slow"
28
+ ```
29
+
30
+ All tests should pass. If they do not, open an issue before making changes —
31
+ something in your environment is off, and PRs built on a broken baseline
32
+ waste review time.
33
+
34
+ ## The pull request workflow
35
+
36
+ ### 1. Create a branch
37
+
38
+ One branch per logical change. Short, descriptive names — not your username,
39
+ not a date.
40
+
41
+ ```bash
42
+ git checkout main
43
+ git pull origin main
44
+ git checkout -b fix-mixture-shared-coefs
45
+ ```
46
+
47
+ Good branch names: `fix-mixture-shared-coefs`, `add-mvncd-j5-benchmark`,
48
+ `docs-contributing-guide`.
49
+
50
+ Avoid: `dales-changes`, `update`, `wip`, `patch-1`.
51
+
52
+ ### 2. Make changes and commit
53
+
54
+ ```bash
55
+ # Edit files...
56
+ pytest tests/ -m "not slow" # Run tests before committing
57
+ git add <files>
58
+ git commit -m "Short imperative summary of the change"
59
+ ```
60
+
61
+ If you touch a tutorial, edit the `.py` script under
62
+ `examples/tutorials/python_scripts/` (the single source of truth) and
63
+ regenerate the notebooks — the `notebooks` CI job fails if they are stale or
64
+ missing:
65
+
66
+ ```bash
67
+ python examples/tutorials/_convert_to_ipynb.py
68
+ git add examples/tutorials/*.ipynb
69
+ ```
70
+
71
+ Commit message style:
72
+
73
+ - First line: imperative mood, under 72 characters
74
+ (`"Add shared-coefficient support for mixture-of-normals"`, not
75
+ `"Added ..."` or `"Changes to mixture"`).
76
+ - Optional body after a blank line explains *why*, not *what*.
77
+ - One commit per logical step is fine; squash trivial fixup commits before
78
+ pushing (`git rebase -i main`).
79
+
80
+ ### 3. Push and open the PR
81
+
82
+ ```bash
83
+ git push -u origin fix-mixture-shared-coefs
84
+ ```
85
+
86
+ GitHub prints a URL for opening the PR after the push — click it, or visit
87
+ the repo on github.com and look for the "Compare & pull request" banner.
88
+
89
+ PR description should cover:
90
+
91
+ - **What** changed (one or two sentences).
92
+ - **Why** the change is needed (link to an issue or email thread if relevant).
93
+ - **How it was tested** — which tests you ran, which tutorials you re-executed,
94
+ any benchmark numbers.
95
+ - **Verification impact** — if your change could affect log-likelihoods,
96
+ explicitly state whether you re-ran the Table 1 verification (see below).
97
+
98
+ ### 4. Review and merge
99
+
100
+ A maintainer reviews the PR, may request changes, and merges it. While the
101
+ PR is open:
102
+
103
+ - Push new commits to the same branch — they update the PR automatically.
104
+ - Do not force-push unless asked.
105
+ - Do not merge `main` into your branch unless requested; rebase if you must
106
+ update.
107
+
108
+ After merge, delete your branch:
109
+
110
+ ```bash
111
+ git checkout main
112
+ git pull origin main
113
+ git branch -d fix-mixture-shared-coefs
114
+ ```
115
+
116
+ ## Numerical verification (important)
117
+
118
+ pybhatlib reproduces exact log-likelihood values from BHATLIB Table 1:
119
+
120
+ | Model | LL |
121
+ |-------|-----|
122
+ | (a)(i) IID | −670.956 |
123
+ | (a)(ii) Flexible | −661.111 |
124
+ | (b) + AGE45 | −659.285 |
125
+ | (c) Random coefficient | −635.871 |
126
+ | (d) Mixture (nseg=2) | ≈−634.975 |
127
+
128
+ If your change touches any code under `src/pybhatlib/gradmvn/`,
129
+ `src/pybhatlib/matgradient/`, `src/pybhatlib/vecup/`, or
130
+ `src/pybhatlib/models/mnp/`, run the Table 1 tutorial locally before
131
+ requesting review:
132
+
133
+ ```bash
134
+ jupyter nbconvert --to notebook --execute examples/tutorials/t04h_bhatlib_table1.ipynb
135
+ ```
136
+
137
+ Include the resulting LL values in the PR description. If they differ from
138
+ the table, explain why the change is intentional — do not merge a regression
139
+ without discussion.
140
+
141
+ ## Code standards
142
+
143
+ - **Python**: PEP 8, 88-char line length (matches `ruff` config).
144
+ - **Type hints** on all public function signatures.
145
+ - **Docstrings** in NumPy style on all public functions.
146
+ - **Backend-aware numerical code** accepts an optional `xp` keyword for
147
+ NumPy / PyTorch backend selection.
148
+ - **Numba-compatible** in hot paths — use `scipy.special.ndtr`, not
149
+ `scipy.stats.norm.cdf`; avoid Python-level scipy.stats objects inside
150
+ JIT-compiled functions.
151
+ - **No module-level mutable state** — thread safety matters for parallel
152
+ gradient evaluations.
153
+ - **Private modules** are prefixed with an underscore (`_vec_ops.py`).
154
+
155
+ ## Running specific test subsets
156
+
157
+ ```bash
158
+ pytest tests/ # Everything including slow integration tests
159
+ pytest tests/ -m "not slow" # Skip slow tests (what CI runs)
160
+ pytest tests/ -m torch # Only PyTorch-backend tests (needs torch installed)
161
+ pytest tests/test_models/ # Just the model tests
162
+ pytest tests/ -k mnp # Tests matching "mnp" in name
163
+ ```
164
+
165
+ ## Scope rules
166
+
167
+ - **One logical change per PR.** Bundling a bug fix, a feature, and a refactor
168
+ into one PR makes review painful and makes `git bisect` useless if a
169
+ regression shows up later.
170
+ - **Do not reformat unrelated code** in your PR. If you notice style issues
171
+ elsewhere, open a separate PR for cleanup.
172
+ - **Performance changes need before/after numbers.** "Faster" is not
173
+ reviewable; "1.40s → 0.82s on Table 1 Model (a)(ii)" is.
174
+ - **Feature additions that change model semantics** (e.g., allowing
175
+ coefficients to be shared across mixture segments) should include a test
176
+ that pins the new behavior.
177
+
178
+ ## Getting help
179
+
180
+ - **Questions about an existing PR**: comment on the PR directly.
181
+ - **Questions about where to start**: open an issue with the `question` label.
182
+ - **Something is broken**: open an issue with a minimal reproducer and the
183
+ output of `pip freeze`.
184
+
185
+ ## License
186
+
187
+ By contributing, you agree that your contributions are licensed under the
188
+ MIT license (see `LICENSE`).
@@ -0,0 +1,219 @@
1
+ Metadata-Version: 2.4
2
+ Name: pybhatlib
3
+ Version: 0.3.2
4
+ Summary: Python implementation of BHATLIB: matrix-based inference for advanced econometric models
5
+ Project-URL: Homepage, https://github.com/UMN-Choi-Lab/pybhatlib
6
+ Project-URL: Repository, https://github.com/UMN-Choi-Lab/pybhatlib
7
+ Project-URL: Documentation, https://github.com/UMN-Choi-Lab/pybhatlib#readme
8
+ Project-URL: Issues, https://github.com/UMN-Choi-Lab/pybhatlib/issues
9
+ Project-URL: Changelog, https://github.com/UMN-Choi-Lab/pybhatlib/blob/main/CHANGELOG.md
10
+ Author-email: Seongjin Choi <choi@umn.edu>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: MVNCD,covariance matrix,discrete choice,econometrics,multinomial probit,transportation
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: numba>=0.59
23
+ Requires-Dist: numpy>=1.24
24
+ Requires-Dist: pandas>=2.0
25
+ Requires-Dist: scipy>=1.10
26
+ Provides-Extra: all
27
+ Requires-Dist: mypy; extra == 'all'
28
+ Requires-Dist: pytest-cov; extra == 'all'
29
+ Requires-Dist: pytest>=7.0; extra == 'all'
30
+ Requires-Dist: ruff; extra == 'all'
31
+ Requires-Dist: torch>=2.0; extra == 'all'
32
+ Provides-Extra: dev
33
+ Requires-Dist: mypy; extra == 'dev'
34
+ Requires-Dist: pytest-cov; extra == 'dev'
35
+ Requires-Dist: pytest>=7.0; extra == 'dev'
36
+ Requires-Dist: ruff; extra == 'dev'
37
+ Provides-Extra: torch
38
+ Requires-Dist: torch>=2.0; extra == 'torch'
39
+ Description-Content-Type: text/markdown
40
+
41
+ # pybhatlib
42
+
43
+ [![Python](https://img.shields.io/pypi/pyversions/pybhatlib)](https://pypi.org/project/pybhatlib/)
44
+ [![License: MIT](https://img.shields.io/pypi/l/pybhatlib)](https://github.com/UMN-Choi-Lab/pybhatlib/blob/main/LICENSE)
45
+
46
+ Python reimplementation of **BHATLIB** — an open-source library for statistical and
47
+ econometric matrix-based inference methods.
48
+
49
+ BHATLIB (Bhat, Clower, Haddad, Jones; UT Austin / Aptech Systems) provides efficient
50
+ matrix operations, gradient-enabled routines for multivariate distribution evaluation
51
+ (including Bhat's 2018 MVNCD analytic approximation), and pre-built econometric models.
52
+
53
+ ## Installation
54
+
55
+ ```bash
56
+ git clone https://github.com/UMN-Choi-Lab/pybhatlib.git
57
+ cd pybhatlib
58
+ pip install -e . # core (NumPy + SciPy + Numba)
59
+ pip install -e ".[torch]" # add PyTorch backend (optional GPU)
60
+ pip install -e ".[dev]" # add pytest, ruff, mypy
61
+ pip install -e ".[all]" # everything (torch + dev)
62
+ ```
63
+
64
+ ## Quick Start — Multinomial Probit (MNP)
65
+
66
+ Sample data: `examples/data/TRAVELMODE.csv` (3 modes — DA / SR / TR, 1125 observations).
67
+
68
+ ```python
69
+ from pybhatlib.models.mnp import MNPModel, MNPControl
70
+
71
+ model = MNPModel(
72
+ data="examples/data/TRAVELMODE.csv",
73
+ alternatives=["Alt1_ch", "Alt2_ch", "Alt3_ch"],
74
+ availability="none",
75
+ spec={
76
+ "CON_SR": {"Alt1_ch": "sero", "Alt2_ch": "uno", "Alt3_ch": "sero"},
77
+ "CON_TR": {"Alt1_ch": "sero", "Alt2_ch": "sero", "Alt3_ch": "uno"},
78
+ "IVTT": {"Alt1_ch": "IVTT_DA", "Alt2_ch": "IVTT_SR", "Alt3_ch": "IVTT_TR"},
79
+ "OVTT": {"Alt1_ch": "OVTT_DA", "Alt2_ch": "OVTT_SR", "Alt3_ch": "OVTT_TR"},
80
+ "COST": {"Alt1_ch": "COST_DA", "Alt2_ch": "COST_SR", "Alt3_ch": "COST_TR"},
81
+ },
82
+ control=MNPControl(iid=True),
83
+ )
84
+ results = model.fit()
85
+ results.summary()
86
+ ```
87
+
88
+ ## Quick Start — Multivariate Ordered Response Probit (MORP)
89
+
90
+ ```python
91
+ from pybhatlib.models.morp import MORPModel, MORPControl
92
+
93
+ model = MORPModel(
94
+ data=df, # DataFrame or CSV path
95
+ dep_vars=["satisfaction", "recommendation"], # ordinal outcome columns
96
+ spec={
97
+ "income": {"satisfaction": "income", "recommendation": "income"},
98
+ "age": {"satisfaction": "age", "recommendation": "age"},
99
+ "education": {"satisfaction": "education", "recommendation": "education"},
100
+ },
101
+ n_categories=[3, 3],
102
+ control=MORPControl(iid=True, seed=42),
103
+ )
104
+ results = model.fit()
105
+ results.summary()
106
+ ```
107
+
108
+ A runnable end-to-end example (with `morp_ate`, `morp_predict`,
109
+ `morp_predict_category`) is at
110
+ [`examples/tutorials/t05b_morp_ate_predict.ipynb`](https://github.com/UMN-Choi-Lab/pybhatlib/blob/main/examples/tutorials/t05b_morp_ate_predict.ipynb).
111
+
112
+ ## Features
113
+
114
+ **Models**
115
+ - **Multinomial Probit (MNP)** — IID, flexible covariance, heteroscedastic-only,
116
+ random coefficients, mixture-of-normals
117
+ - **Multivariate Ordered Response Probit (MORP)** — multiple ordinal outcomes
118
+ with shared covariance; per-outcome `spec` mapping
119
+ - **Multiple Discrete-Continuous Extreme Value (MDCEV)** — traditional
120
+ (Bhat 2008) and linear (Bhat 2018) outside-good utility specifications,
121
+ selected via `MDCEVControl.utility`
122
+
123
+ **Numerical core**
124
+ - `vecup` — vecdup, matdupfull, LDLT decomposition, truncated MVN moments
125
+ - `matgradient` — gradcovcor, gomegxomegax, spherical / Cholesky parameterizations
126
+ - `gradmvn` — Bhat (2018) MVNCD analytic approximation with analytic gradients
127
+
128
+ **Estimation**
129
+ - Multiple SE estimators (`se_method="bhhh" | "hessian" | "sandwich"`); BHHH is
130
+ the default to match GAUSS BHATLIB's `_max_CovPar=2`. All three are computed
131
+ at fit time and exposed as `se_bhhh` / `se_hessian` / `se_sandwich`;
132
+ `results.summary()` prints a side-by-side diagnostic block (a large
133
+ Hessian/BHHH divergence is a misspecification signal).
134
+ - `MNPControl.active_mask` to freeze a subset of parameters at their starting
135
+ values without recoding the model.
136
+ - `verbose` levels: `0` silent, `1` summary, `2` per-iteration NLL,
137
+ `3` per-iteration parameter / gradient / relative-gradient table.
138
+
139
+ **Backend**
140
+ - NumPy by default; optional PyTorch backend with GPU support (install with
141
+ `[torch]`). All numerical functions take an optional `xp` kwarg.
142
+
143
+ **Post-estimation**
144
+ - Average Treatment Effects (ATE) with scenario-matrix support, forecasting,
145
+ per-category MORP probability prediction.
146
+ - MORP ATEs from supplied coefficients without re-fitting:
147
+ `MORPResults.from_estimates(beta, thresholds, correlation)` rebuilds a results
148
+ object from natural-space estimates, and `morp_ate_from_params` /
149
+ `morp_joint_probs` (mean joint category-combination probabilities, the GAUSS
150
+ `ate1.csv` equivalent) compute effects directly from them.
151
+
152
+ ## Verification
153
+
154
+ pybhatlib reproduces Table 1 from the BHATLIB paper (Bhat 2018) using the
155
+ TRAVELMODE dataset (3 modes — DA, SR, TR; 1125 observations):
156
+
157
+ | Model | Specification | Target LL | Achieved LL | Status |
158
+ |-------|--------------|-----------|-------------|--------|
159
+ | (a)(i) | IID errors | -670.956 | -670.956 | exact match |
160
+ | (a)(ii) | Flexible covariance | -661.111 | -661.111 | exact match |
161
+ | (b) | + AGE45 demographics | -659.285 | -659.284 | exact match |
162
+ | (c) | + Random coeff. OVTT | -635.871 | -635.871 | exact match |
163
+ | (d) | 2-segment mixture | -634.975 | -632.912 | close (multi-modal) |
164
+
165
+ Models (a)–(c) reproduce the published estimates and BHHH standard errors to
166
+ ≤0.001 on every parameter (verified end-to-end against GAUSS 26.1.1 + MaxLik
167
+ 5.0.9). Model (d) is documented as multi-modal — the Python optimum is a
168
+ slightly better local mode than the published one.
169
+
170
+ For MORP, `iid=False` now uses GAUSS BHATLIB's unit-variance identification by
171
+ default (`MORPControl.fix_scales=True`): the latent-utility variances are fixed
172
+ at 1 and only correlations are estimated. The full-covariance `MORP_DINING` /
173
+ `MORP_WALK` models reproduce the GAUSS mean log-likelihoods (−4.6598 / −3.7591)
174
+ and correlation matrices. `summary()` reports the actual threshold cut-points
175
+ (with delta-method standard errors) and a gradient column, matching GAUSS's
176
+ output. See `docs/plans/MORP_BHATLIB_PARITY.md`.
177
+
178
+ The driving notebooks are
179
+ [`t04h_bhatlib_table1.ipynb`](https://github.com/UMN-Choi-Lab/pybhatlib/blob/main/examples/tutorials/t04h_bhatlib_table1.ipynb) and
180
+ [`t04i_bhat2018_table2.ipynb`](https://github.com/UMN-Choi-Lab/pybhatlib/blob/main/examples/tutorials/t04i_bhat2018_table2.ipynb).
181
+
182
+ ## Tutorials
183
+
184
+ A complete tutorial series lives under
185
+ [`examples/tutorials/`](https://github.com/UMN-Choi-Lab/pybhatlib/tree/main/examples/tutorials/) as Jupyter notebooks (each with a
186
+ matching `.py` script under `python_scripts/`):
187
+
188
+ | Track | Notebooks |
189
+ |-------|-----------|
190
+ | Foundations | `t00_quickstart`, `t01a_vectorization`, `t01b_ldlt`, `t01c_truncated_mvn` |
191
+ | Matrix gradients | `t02a_gradcovcor`, `t02b_spherical`, `t02c_chain_rules` |
192
+ | MVNCD | `t03a_mvncd_methods`, `t03b_mvncd_gradients`, `t03c_mvncd_rect`, `t03d_univariate_cdfs`, `t03e_bhat2018_table1` |
193
+ | MNP | `t04a_mnp_iid` … `t04g_mnp_forecasting`, `t04h_bhatlib_table1`, `t04i_bhat2018_table2` |
194
+ | MORP | `t05b_morp_ate_predict` |
195
+ | MDCEV | `t07a_mdcev_trad`, `t07b_mdcev_lin` |
196
+ | Backends & verification | `t06a_backend_switching`, `t06b_custom_specs`, `t06c_gradient_verification` |
197
+
198
+ ## Contributing
199
+
200
+ See [`CONTRIBUTING.md`](https://github.com/UMN-Choi-Lab/pybhatlib/blob/main/CONTRIBUTING.md) for development setup, the PR-test
201
+ workflow, and CODEOWNERS conventions. Key tests:
202
+
203
+ ```bash
204
+ pytest tests/ # full suite
205
+ pytest tests/ -m "not slow" # skip integration tests
206
+ pytest tests/ -m torch # PyTorch backend tests only
207
+ ```
208
+
209
+ ## References
210
+
211
+ 1. Bhat, C. R. (2018). New Matrix-Based Methods for the Analytic Evaluation of the
212
+ Multivariate Cumulative Normal Distribution Function. *Transportation Research
213
+ Part B*, 109: 238–256.
214
+ 2. Bhat, C. R., Clower, E., Haddad, A. J., Jones, J. BHATLIB: An Open-Source
215
+ Library for Statistical and Econometric Matrix-Based Inference Methods in GAUSS.
216
+
217
+ ## License
218
+
219
+ MIT
@@ -0,0 +1,179 @@
1
+ # pybhatlib
2
+
3
+ [![Python](https://img.shields.io/pypi/pyversions/pybhatlib)](https://pypi.org/project/pybhatlib/)
4
+ [![License: MIT](https://img.shields.io/pypi/l/pybhatlib)](https://github.com/UMN-Choi-Lab/pybhatlib/blob/main/LICENSE)
5
+
6
+ Python reimplementation of **BHATLIB** — an open-source library for statistical and
7
+ econometric matrix-based inference methods.
8
+
9
+ BHATLIB (Bhat, Clower, Haddad, Jones; UT Austin / Aptech Systems) provides efficient
10
+ matrix operations, gradient-enabled routines for multivariate distribution evaluation
11
+ (including Bhat's 2018 MVNCD analytic approximation), and pre-built econometric models.
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ git clone https://github.com/UMN-Choi-Lab/pybhatlib.git
17
+ cd pybhatlib
18
+ pip install -e . # core (NumPy + SciPy + Numba)
19
+ pip install -e ".[torch]" # add PyTorch backend (optional GPU)
20
+ pip install -e ".[dev]" # add pytest, ruff, mypy
21
+ pip install -e ".[all]" # everything (torch + dev)
22
+ ```
23
+
24
+ ## Quick Start — Multinomial Probit (MNP)
25
+
26
+ Sample data: `examples/data/TRAVELMODE.csv` (3 modes — DA / SR / TR, 1125 observations).
27
+
28
+ ```python
29
+ from pybhatlib.models.mnp import MNPModel, MNPControl
30
+
31
+ model = MNPModel(
32
+ data="examples/data/TRAVELMODE.csv",
33
+ alternatives=["Alt1_ch", "Alt2_ch", "Alt3_ch"],
34
+ availability="none",
35
+ spec={
36
+ "CON_SR": {"Alt1_ch": "sero", "Alt2_ch": "uno", "Alt3_ch": "sero"},
37
+ "CON_TR": {"Alt1_ch": "sero", "Alt2_ch": "sero", "Alt3_ch": "uno"},
38
+ "IVTT": {"Alt1_ch": "IVTT_DA", "Alt2_ch": "IVTT_SR", "Alt3_ch": "IVTT_TR"},
39
+ "OVTT": {"Alt1_ch": "OVTT_DA", "Alt2_ch": "OVTT_SR", "Alt3_ch": "OVTT_TR"},
40
+ "COST": {"Alt1_ch": "COST_DA", "Alt2_ch": "COST_SR", "Alt3_ch": "COST_TR"},
41
+ },
42
+ control=MNPControl(iid=True),
43
+ )
44
+ results = model.fit()
45
+ results.summary()
46
+ ```
47
+
48
+ ## Quick Start — Multivariate Ordered Response Probit (MORP)
49
+
50
+ ```python
51
+ from pybhatlib.models.morp import MORPModel, MORPControl
52
+
53
+ model = MORPModel(
54
+ data=df, # DataFrame or CSV path
55
+ dep_vars=["satisfaction", "recommendation"], # ordinal outcome columns
56
+ spec={
57
+ "income": {"satisfaction": "income", "recommendation": "income"},
58
+ "age": {"satisfaction": "age", "recommendation": "age"},
59
+ "education": {"satisfaction": "education", "recommendation": "education"},
60
+ },
61
+ n_categories=[3, 3],
62
+ control=MORPControl(iid=True, seed=42),
63
+ )
64
+ results = model.fit()
65
+ results.summary()
66
+ ```
67
+
68
+ A runnable end-to-end example (with `morp_ate`, `morp_predict`,
69
+ `morp_predict_category`) is at
70
+ [`examples/tutorials/t05b_morp_ate_predict.ipynb`](https://github.com/UMN-Choi-Lab/pybhatlib/blob/main/examples/tutorials/t05b_morp_ate_predict.ipynb).
71
+
72
+ ## Features
73
+
74
+ **Models**
75
+ - **Multinomial Probit (MNP)** — IID, flexible covariance, heteroscedastic-only,
76
+ random coefficients, mixture-of-normals
77
+ - **Multivariate Ordered Response Probit (MORP)** — multiple ordinal outcomes
78
+ with shared covariance; per-outcome `spec` mapping
79
+ - **Multiple Discrete-Continuous Extreme Value (MDCEV)** — traditional
80
+ (Bhat 2008) and linear (Bhat 2018) outside-good utility specifications,
81
+ selected via `MDCEVControl.utility`
82
+
83
+ **Numerical core**
84
+ - `vecup` — vecdup, matdupfull, LDLT decomposition, truncated MVN moments
85
+ - `matgradient` — gradcovcor, gomegxomegax, spherical / Cholesky parameterizations
86
+ - `gradmvn` — Bhat (2018) MVNCD analytic approximation with analytic gradients
87
+
88
+ **Estimation**
89
+ - Multiple SE estimators (`se_method="bhhh" | "hessian" | "sandwich"`); BHHH is
90
+ the default to match GAUSS BHATLIB's `_max_CovPar=2`. All three are computed
91
+ at fit time and exposed as `se_bhhh` / `se_hessian` / `se_sandwich`;
92
+ `results.summary()` prints a side-by-side diagnostic block (a large
93
+ Hessian/BHHH divergence is a misspecification signal).
94
+ - `MNPControl.active_mask` to freeze a subset of parameters at their starting
95
+ values without recoding the model.
96
+ - `verbose` levels: `0` silent, `1` summary, `2` per-iteration NLL,
97
+ `3` per-iteration parameter / gradient / relative-gradient table.
98
+
99
+ **Backend**
100
+ - NumPy by default; optional PyTorch backend with GPU support (install with
101
+ `[torch]`). All numerical functions take an optional `xp` kwarg.
102
+
103
+ **Post-estimation**
104
+ - Average Treatment Effects (ATE) with scenario-matrix support, forecasting,
105
+ per-category MORP probability prediction.
106
+ - MORP ATEs from supplied coefficients without re-fitting:
107
+ `MORPResults.from_estimates(beta, thresholds, correlation)` rebuilds a results
108
+ object from natural-space estimates, and `morp_ate_from_params` /
109
+ `morp_joint_probs` (mean joint category-combination probabilities, the GAUSS
110
+ `ate1.csv` equivalent) compute effects directly from them.
111
+
112
+ ## Verification
113
+
114
+ pybhatlib reproduces Table 1 from the BHATLIB paper (Bhat 2018) using the
115
+ TRAVELMODE dataset (3 modes — DA, SR, TR; 1125 observations):
116
+
117
+ | Model | Specification | Target LL | Achieved LL | Status |
118
+ |-------|--------------|-----------|-------------|--------|
119
+ | (a)(i) | IID errors | -670.956 | -670.956 | exact match |
120
+ | (a)(ii) | Flexible covariance | -661.111 | -661.111 | exact match |
121
+ | (b) | + AGE45 demographics | -659.285 | -659.284 | exact match |
122
+ | (c) | + Random coeff. OVTT | -635.871 | -635.871 | exact match |
123
+ | (d) | 2-segment mixture | -634.975 | -632.912 | close (multi-modal) |
124
+
125
+ Models (a)–(c) reproduce the published estimates and BHHH standard errors to
126
+ ≤0.001 on every parameter (verified end-to-end against GAUSS 26.1.1 + MaxLik
127
+ 5.0.9). Model (d) is documented as multi-modal — the Python optimum is a
128
+ slightly better local mode than the published one.
129
+
130
+ For MORP, `iid=False` now uses GAUSS BHATLIB's unit-variance identification by
131
+ default (`MORPControl.fix_scales=True`): the latent-utility variances are fixed
132
+ at 1 and only correlations are estimated. The full-covariance `MORP_DINING` /
133
+ `MORP_WALK` models reproduce the GAUSS mean log-likelihoods (−4.6598 / −3.7591)
134
+ and correlation matrices. `summary()` reports the actual threshold cut-points
135
+ (with delta-method standard errors) and a gradient column, matching GAUSS's
136
+ output. See `docs/plans/MORP_BHATLIB_PARITY.md`.
137
+
138
+ The driving notebooks are
139
+ [`t04h_bhatlib_table1.ipynb`](https://github.com/UMN-Choi-Lab/pybhatlib/blob/main/examples/tutorials/t04h_bhatlib_table1.ipynb) and
140
+ [`t04i_bhat2018_table2.ipynb`](https://github.com/UMN-Choi-Lab/pybhatlib/blob/main/examples/tutorials/t04i_bhat2018_table2.ipynb).
141
+
142
+ ## Tutorials
143
+
144
+ A complete tutorial series lives under
145
+ [`examples/tutorials/`](https://github.com/UMN-Choi-Lab/pybhatlib/tree/main/examples/tutorials/) as Jupyter notebooks (each with a
146
+ matching `.py` script under `python_scripts/`):
147
+
148
+ | Track | Notebooks |
149
+ |-------|-----------|
150
+ | Foundations | `t00_quickstart`, `t01a_vectorization`, `t01b_ldlt`, `t01c_truncated_mvn` |
151
+ | Matrix gradients | `t02a_gradcovcor`, `t02b_spherical`, `t02c_chain_rules` |
152
+ | MVNCD | `t03a_mvncd_methods`, `t03b_mvncd_gradients`, `t03c_mvncd_rect`, `t03d_univariate_cdfs`, `t03e_bhat2018_table1` |
153
+ | MNP | `t04a_mnp_iid` … `t04g_mnp_forecasting`, `t04h_bhatlib_table1`, `t04i_bhat2018_table2` |
154
+ | MORP | `t05b_morp_ate_predict` |
155
+ | MDCEV | `t07a_mdcev_trad`, `t07b_mdcev_lin` |
156
+ | Backends & verification | `t06a_backend_switching`, `t06b_custom_specs`, `t06c_gradient_verification` |
157
+
158
+ ## Contributing
159
+
160
+ See [`CONTRIBUTING.md`](https://github.com/UMN-Choi-Lab/pybhatlib/blob/main/CONTRIBUTING.md) for development setup, the PR-test
161
+ workflow, and CODEOWNERS conventions. Key tests:
162
+
163
+ ```bash
164
+ pytest tests/ # full suite
165
+ pytest tests/ -m "not slow" # skip integration tests
166
+ pytest tests/ -m torch # PyTorch backend tests only
167
+ ```
168
+
169
+ ## References
170
+
171
+ 1. Bhat, C. R. (2018). New Matrix-Based Methods for the Analytic Evaluation of the
172
+ Multivariate Cumulative Normal Distribution Function. *Transportation Research
173
+ Part B*, 109: 238–256.
174
+ 2. Bhat, C. R., Clower, E., Haddad, A. J., Jones, J. BHATLIB: An Open-Source
175
+ Library for Statistical and Econometric Matrix-Based Inference Methods in GAUSS.
176
+
177
+ ## License
178
+
179
+ MIT