pybhatlib 0.3.2__tar.gz → 0.4.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 (374) hide show
  1. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/.gitignore +9 -0
  2. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/CHANGELOG.md +24 -0
  3. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/PKG-INFO +38 -18
  4. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/README.md +35 -15
  5. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/python_scripts/t04e_mnp_mixture.py +25 -20
  6. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/python_scripts/t04h_bhatlib_table1.py +1 -0
  7. pybhatlib-0.4.0/examples/tutorials/python_scripts/t09a_lr.py +238 -0
  8. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/t04e_mnp_mixture.ipynb +25 -20
  9. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/t04h_bhatlib_table1.ipynb +1 -0
  10. pybhatlib-0.4.0/examples/tutorials/t09a_lr.ipynb +320 -0
  11. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/pyproject.toml +1 -1
  12. pybhatlib-0.4.0/src/pybhatlib/__init__.py +100 -0
  13. pybhatlib-0.4.0/src/pybhatlib/_version.py +1 -0
  14. pybhatlib-0.4.0/src/pybhatlib/gradmvn/_pdfrectn.py +616 -0
  15. pybhatlib-0.4.0/src/pybhatlib/io/_data_loader.py +158 -0
  16. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/matgradient/__init__.py +13 -1
  17. pybhatlib-0.4.0/src/pybhatlib/matgradient/_corr_chol.py +202 -0
  18. pybhatlib-0.4.0/src/pybhatlib/matgradient/_radial.py +425 -0
  19. pybhatlib-0.4.0/src/pybhatlib/mixed/__init__.py +81 -0
  20. pybhatlib-0.4.0/src/pybhatlib/mixed/_copula.py +500 -0
  21. pybhatlib-0.4.0/src/pybhatlib/mixed/_draws.py +281 -0
  22. pybhatlib-0.4.0/src/pybhatlib/mixed/_engine.py +595 -0
  23. pybhatlib-0.4.0/src/pybhatlib/mixed/_kernel.py +182 -0
  24. pybhatlib-0.4.0/src/pybhatlib/mixed/_predict.py +594 -0
  25. pybhatlib-0.4.0/src/pybhatlib/mixed/_rc_pipeline.py +612 -0
  26. pybhatlib-0.4.0/src/pybhatlib/mixed/_reparam.py +886 -0
  27. pybhatlib-0.4.0/src/pybhatlib/mixed/_spec.py +566 -0
  28. pybhatlib-0.4.0/src/pybhatlib/models/lr/__init__.py +17 -0
  29. pybhatlib-0.4.0/src/pybhatlib/models/lr/_lr_ate.py +234 -0
  30. pybhatlib-0.4.0/src/pybhatlib/models/lr/_lr_control.py +47 -0
  31. pybhatlib-0.4.0/src/pybhatlib/models/lr/_lr_forecast.py +29 -0
  32. pybhatlib-0.4.0/src/pybhatlib/models/lr/_lr_loglik.py +101 -0
  33. pybhatlib-0.4.0/src/pybhatlib/models/lr/_lr_model.py +250 -0
  34. pybhatlib-0.4.0/src/pybhatlib/models/lr/_lr_results.py +256 -0
  35. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/mdcev/_mdcev_forecast.py +3 -2
  36. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/mdcev/_mdcev_loglik.py +150 -20
  37. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/mdcev/_mdcev_model.py +2 -1
  38. pybhatlib-0.4.0/src/pybhatlib/models/mdcev_mixed/__init__.py +48 -0
  39. pybhatlib-0.4.0/src/pybhatlib/models/mdcev_mixed/_mdcev_mixed_ate.py +225 -0
  40. pybhatlib-0.4.0/src/pybhatlib/models/mdcev_mixed/_mdcev_mixed_control.py +139 -0
  41. pybhatlib-0.4.0/src/pybhatlib/models/mdcev_mixed/_mdcev_mixed_forecast.py +514 -0
  42. pybhatlib-0.4.0/src/pybhatlib/models/mdcev_mixed/_mdcev_mixed_kernel.py +522 -0
  43. pybhatlib-0.4.0/src/pybhatlib/models/mdcev_mixed/_mdcev_mixed_model.py +750 -0
  44. pybhatlib-0.4.0/src/pybhatlib/models/mdcev_mixed/_mdcev_mixed_results.py +275 -0
  45. pybhatlib-0.4.0/src/pybhatlib/models/mixmnl/__init__.py +32 -0
  46. pybhatlib-0.4.0/src/pybhatlib/models/mixmnl/_mixmnl_ate.py +219 -0
  47. pybhatlib-0.4.0/src/pybhatlib/models/mixmnl/_mixmnl_control.py +125 -0
  48. pybhatlib-0.4.0/src/pybhatlib/models/mixmnl/_mixmnl_forecast.py +135 -0
  49. pybhatlib-0.4.0/src/pybhatlib/models/mixmnl/_mixmnl_kernel.py +149 -0
  50. pybhatlib-0.4.0/src/pybhatlib/models/mixmnl/_mixmnl_model.py +702 -0
  51. pybhatlib-0.4.0/src/pybhatlib/models/mixmnl/_mixmnl_results.py +268 -0
  52. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/mnp/_mnp_grad_analytic.py +78 -21
  53. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/mnp/_mnp_loglik.py +91 -81
  54. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/mnp/_mnp_model.py +123 -89
  55. pybhatlib-0.4.0/src/pybhatlib/models/mnpkercp/__init__.py +41 -0
  56. pybhatlib-0.4.0/src/pybhatlib/models/mnpkercp/_mnpkercp_ate.py +287 -0
  57. pybhatlib-0.4.0/src/pybhatlib/models/mnpkercp/_mnpkercp_control.py +139 -0
  58. pybhatlib-0.4.0/src/pybhatlib/models/mnpkercp/_mnpkercp_diff.py +120 -0
  59. pybhatlib-0.4.0/src/pybhatlib/models/mnpkercp/_mnpkercp_forecast.py +336 -0
  60. pybhatlib-0.4.0/src/pybhatlib/models/mnpkercp/_mnpkercp_kernel.py +566 -0
  61. pybhatlib-0.4.0/src/pybhatlib/models/mnpkercp/_mnpkercp_model.py +700 -0
  62. pybhatlib-0.4.0/src/pybhatlib/models/mnpkercp/_mnpkercp_results.py +272 -0
  63. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/morp/_morp_grad_analytic.py +5 -4
  64. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/morp/_morp_loglik.py +4 -3
  65. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/morp/_morp_report.py +3 -2
  66. pybhatlib-0.4.0/src/pybhatlib/models/morp_flex/__init__.py +35 -0
  67. pybhatlib-0.4.0/src/pybhatlib/models/morp_flex/_morp_flex_ate.py +345 -0
  68. pybhatlib-0.4.0/src/pybhatlib/models/morp_flex/_morp_flex_control.py +146 -0
  69. pybhatlib-0.4.0/src/pybhatlib/models/morp_flex/_morp_flex_forecast.py +263 -0
  70. pybhatlib-0.4.0/src/pybhatlib/models/morp_flex/_morp_flex_kernel.py +864 -0
  71. pybhatlib-0.4.0/src/pybhatlib/models/morp_flex/_morp_flex_model.py +614 -0
  72. pybhatlib-0.4.0/src/pybhatlib/models/morp_flex/_morp_flex_results.py +280 -0
  73. pybhatlib-0.4.0/src/pybhatlib/utils/__init__.py +41 -0
  74. pybhatlib-0.4.0/src/pybhatlib/utils/_logistic.py +239 -0
  75. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/utils/_qmc.py +18 -4
  76. pybhatlib-0.4.0/src/pybhatlib/utils/_quadrature.py +55 -0
  77. pybhatlib-0.4.0/src/pybhatlib/utils/_safe_reparam.py +154 -0
  78. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/vecup/__init__.py +20 -0
  79. pybhatlib-0.4.0/src/pybhatlib/vecup/_panel.py +236 -0
  80. pybhatlib-0.4.0/src/pybhatlib/vecup/_yj.py +519 -0
  81. pybhatlib-0.4.0/tests/fixtures/mixed/mdcev/scag_converged/ass.csv +10 -0
  82. pybhatlib-0.4.0/tests/fixtures/mixed/mdcev/scag_converged/b.csv +27 -0
  83. pybhatlib-0.4.0/tests/fixtures/mixed/mdcev/scag_converged/draws_r1.csv +1500 -0
  84. pybhatlib-0.4.0/tests/fixtures/mixed/mdcev/scag_converged/draws_r10.csv +1500 -0
  85. pybhatlib-0.4.0/tests/fixtures/mixed/mdcev/scag_converged/draws_r2.csv +1500 -0
  86. pybhatlib-0.4.0/tests/fixtures/mixed/mdcev/scag_converged/draws_r3.csv +1500 -0
  87. pybhatlib-0.4.0/tests/fixtures/mixed/mdcev/scag_converged/draws_r4.csv +1500 -0
  88. pybhatlib-0.4.0/tests/fixtures/mixed/mdcev/scag_converged/draws_r5.csv +1500 -0
  89. pybhatlib-0.4.0/tests/fixtures/mixed/mdcev/scag_converged/draws_r6.csv +1500 -0
  90. pybhatlib-0.4.0/tests/fixtures/mixed/mdcev/scag_converged/draws_r7.csv +1500 -0
  91. pybhatlib-0.4.0/tests/fixtures/mixed/mdcev/scag_converged/draws_r8.csv +1500 -0
  92. pybhatlib-0.4.0/tests/fixtures/mixed/mdcev/scag_converged/draws_r9.csv +1500 -0
  93. pybhatlib-0.4.0/tests/fixtures/mixed/mdcev/scag_converged/ll_obs.csv +1500 -0
  94. pybhatlib-0.4.0/tests/fixtures/mixed/mnl/travelmode_converged/ass.csv +15 -0
  95. pybhatlib-0.4.0/tests/fixtures/mixed/mnl/travelmode_converged/b.csv +12 -0
  96. pybhatlib-0.4.0/tests/fixtures/mixed/mnl/travelmode_converged/draws_r1.csv +1125 -0
  97. pybhatlib-0.4.0/tests/fixtures/mixed/mnl/travelmode_converged/draws_r10.csv +1125 -0
  98. pybhatlib-0.4.0/tests/fixtures/mixed/mnl/travelmode_converged/draws_r11.csv +1125 -0
  99. pybhatlib-0.4.0/tests/fixtures/mixed/mnl/travelmode_converged/draws_r12.csv +1125 -0
  100. pybhatlib-0.4.0/tests/fixtures/mixed/mnl/travelmode_converged/draws_r13.csv +1125 -0
  101. pybhatlib-0.4.0/tests/fixtures/mixed/mnl/travelmode_converged/draws_r14.csv +1125 -0
  102. pybhatlib-0.4.0/tests/fixtures/mixed/mnl/travelmode_converged/draws_r15.csv +1125 -0
  103. pybhatlib-0.4.0/tests/fixtures/mixed/mnl/travelmode_converged/draws_r2.csv +1125 -0
  104. pybhatlib-0.4.0/tests/fixtures/mixed/mnl/travelmode_converged/draws_r3.csv +1125 -0
  105. pybhatlib-0.4.0/tests/fixtures/mixed/mnl/travelmode_converged/draws_r4.csv +1125 -0
  106. pybhatlib-0.4.0/tests/fixtures/mixed/mnl/travelmode_converged/draws_r5.csv +1125 -0
  107. pybhatlib-0.4.0/tests/fixtures/mixed/mnl/travelmode_converged/draws_r6.csv +1125 -0
  108. pybhatlib-0.4.0/tests/fixtures/mixed/mnl/travelmode_converged/draws_r7.csv +1125 -0
  109. pybhatlib-0.4.0/tests/fixtures/mixed/mnl/travelmode_converged/draws_r8.csv +1125 -0
  110. pybhatlib-0.4.0/tests/fixtures/mixed/mnl/travelmode_converged/draws_r9.csv +1125 -0
  111. pybhatlib-0.4.0/tests/fixtures/mixed/mnl/travelmode_converged/grad_obs.csv +1125 -0
  112. pybhatlib-0.4.0/tests/fixtures/mixed/mnl/travelmode_converged/ll_obs.csv +1125 -0
  113. pybhatlib-0.4.0/tests/fixtures/mixed/mnl/travelmode_converged/meta.json +46 -0
  114. pybhatlib-0.4.0/tests/fixtures/mixed/mnp/travelmode_copula/ass.csv +5 -0
  115. pybhatlib-0.4.0/tests/fixtures/mixed/mnp/travelmode_copula/b.csv +18 -0
  116. pybhatlib-0.4.0/tests/fixtures/mixed/mnp/travelmode_copula/draws_r1.csv +1125 -0
  117. pybhatlib-0.4.0/tests/fixtures/mixed/mnp/travelmode_copula/draws_r2.csv +1125 -0
  118. pybhatlib-0.4.0/tests/fixtures/mixed/mnp/travelmode_copula/draws_r3.csv +1125 -0
  119. pybhatlib-0.4.0/tests/fixtures/mixed/mnp/travelmode_copula/draws_r4.csv +1125 -0
  120. pybhatlib-0.4.0/tests/fixtures/mixed/mnp/travelmode_copula/draws_r5.csv +1125 -0
  121. pybhatlib-0.4.0/tests/fixtures/mixed/mnp/travelmode_copula/ll_obs.csv +1125 -0
  122. pybhatlib-0.4.0/tests/fixtures/mixed/mnp/travelmode_nocopula/ass.csv +5 -0
  123. pybhatlib-0.4.0/tests/fixtures/mixed/mnp/travelmode_nocopula/b.csv +18 -0
  124. pybhatlib-0.4.0/tests/fixtures/mixed/mnp/travelmode_nocopula/draws_r1.csv +1125 -0
  125. pybhatlib-0.4.0/tests/fixtures/mixed/mnp/travelmode_nocopula/draws_r2.csv +1125 -0
  126. pybhatlib-0.4.0/tests/fixtures/mixed/mnp/travelmode_nocopula/draws_r3.csv +1125 -0
  127. pybhatlib-0.4.0/tests/fixtures/mixed/mnp/travelmode_nocopula/draws_r4.csv +1125 -0
  128. pybhatlib-0.4.0/tests/fixtures/mixed/mnp/travelmode_nocopula/draws_r5.csv +1125 -0
  129. pybhatlib-0.4.0/tests/fixtures/mixed/mnp/travelmode_nocopula/ll_obs.csv +1125 -0
  130. pybhatlib-0.4.0/tests/fixtures/mixed/morp/panelcommute_converged/ass.csv +10 -0
  131. pybhatlib-0.4.0/tests/fixtures/mixed/morp/panelcommute_converged/b.csv +21 -0
  132. pybhatlib-0.4.0/tests/fixtures/mixed/morp/panelcommute_converged/draws_r1.csv +2323 -0
  133. pybhatlib-0.4.0/tests/fixtures/mixed/morp/panelcommute_converged/draws_r10.csv +2323 -0
  134. pybhatlib-0.4.0/tests/fixtures/mixed/morp/panelcommute_converged/draws_r2.csv +2323 -0
  135. pybhatlib-0.4.0/tests/fixtures/mixed/morp/panelcommute_converged/draws_r3.csv +2323 -0
  136. pybhatlib-0.4.0/tests/fixtures/mixed/morp/panelcommute_converged/draws_r4.csv +2323 -0
  137. pybhatlib-0.4.0/tests/fixtures/mixed/morp/panelcommute_converged/draws_r5.csv +2323 -0
  138. pybhatlib-0.4.0/tests/fixtures/mixed/morp/panelcommute_converged/draws_r6.csv +2323 -0
  139. pybhatlib-0.4.0/tests/fixtures/mixed/morp/panelcommute_converged/draws_r7.csv +2323 -0
  140. pybhatlib-0.4.0/tests/fixtures/mixed/morp/panelcommute_converged/draws_r8.csv +2323 -0
  141. pybhatlib-0.4.0/tests/fixtures/mixed/morp/panelcommute_converged/draws_r9.csv +2323 -0
  142. pybhatlib-0.4.0/tests/fixtures/mixed/morp/panelcommute_converged/ll_obs.csv +2323 -0
  143. pybhatlib-0.4.0/tests/fixtures/mixed/morp/panelcommute_converged/meta.json +25 -0
  144. pybhatlib-0.4.0/tests/test_mixed/__init__.py +0 -0
  145. pybhatlib-0.4.0/tests/test_mixed/test_copula.py +301 -0
  146. pybhatlib-0.4.0/tests/test_mixed/test_corr_chol.py +173 -0
  147. pybhatlib-0.4.0/tests/test_mixed/test_draws.py +182 -0
  148. pybhatlib-0.4.0/tests/test_mixed/test_engine_master.py +162 -0
  149. pybhatlib-0.4.0/tests/test_mixed/test_logistic.py +72 -0
  150. pybhatlib-0.4.0/tests/test_mixed/test_logitmod.py +108 -0
  151. pybhatlib-0.4.0/tests/test_mixed/test_mdcev_mixed_ate.py +456 -0
  152. pybhatlib-0.4.0/tests/test_mixed/test_mdcev_mixed_collapse.py +172 -0
  153. pybhatlib-0.4.0/tests/test_mixed/test_mdcev_mixed_gauss_parity.py +158 -0
  154. pybhatlib-0.4.0/tests/test_mixed/test_mdcev_mixed_kernel.py +206 -0
  155. pybhatlib-0.4.0/tests/test_mixed/test_mdcev_mixed_master.py +161 -0
  156. pybhatlib-0.4.0/tests/test_mixed/test_mixed_predict.py +215 -0
  157. pybhatlib-0.4.0/tests/test_mixed/test_mixmnl_ate.py +383 -0
  158. pybhatlib-0.4.0/tests/test_mixed/test_mixmnl_collapse.py +161 -0
  159. pybhatlib-0.4.0/tests/test_mixed/test_mixmnl_facade.py +330 -0
  160. pybhatlib-0.4.0/tests/test_mixed/test_mixmnl_gauss_parity.py +112 -0
  161. pybhatlib-0.4.0/tests/test_mixed/test_mixmnl_kernel.py +136 -0
  162. pybhatlib-0.4.0/tests/test_mixed/test_mnp_diff.py +120 -0
  163. pybhatlib-0.4.0/tests/test_mixed/test_mnp_spec.py +250 -0
  164. pybhatlib-0.4.0/tests/test_mixed/test_mnpkercp_ate.py +443 -0
  165. pybhatlib-0.4.0/tests/test_mixed/test_mnpkercp_corr_grad.py +134 -0
  166. pybhatlib-0.4.0/tests/test_mixed/test_mnpkercp_gauss_parity.py +197 -0
  167. pybhatlib-0.4.0/tests/test_mixed/test_mnpkercp_kernel.py +167 -0
  168. pybhatlib-0.4.0/tests/test_mixed/test_mnpkercp_master.py +132 -0
  169. pybhatlib-0.4.0/tests/test_mixed/test_mnpkercp_master_corr.py +132 -0
  170. pybhatlib-0.4.0/tests/test_mixed/test_mnpkercp_master_sparse_corr.py +131 -0
  171. pybhatlib-0.4.0/tests/test_mixed/test_morp_flex_ate.py +387 -0
  172. pybhatlib-0.4.0/tests/test_mixed/test_morp_flex_gauss_parity.py +107 -0
  173. pybhatlib-0.4.0/tests/test_mixed/test_morp_flex_kernel.py +207 -0
  174. pybhatlib-0.4.0/tests/test_mixed/test_morp_flex_master.py +112 -0
  175. pybhatlib-0.4.0/tests/test_mixed/test_morp_spec.py +320 -0
  176. pybhatlib-0.4.0/tests/test_mixed/test_panel.py +174 -0
  177. pybhatlib-0.4.0/tests/test_mixed/test_pdfrectn.py +144 -0
  178. pybhatlib-0.4.0/tests/test_mixed/test_rc_pipeline.py +230 -0
  179. pybhatlib-0.4.0/tests/test_mixed/test_spec.py +398 -0
  180. pybhatlib-0.4.0/tests/test_mixed/test_yj.py +210 -0
  181. pybhatlib-0.4.0/tests/test_models/test_lr.py +152 -0
  182. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_models/test_mdcev.py +15 -0
  183. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_models/test_mnp_grad_mixture.py +38 -44
  184. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_models/test_mnp_mixture_ranvars.py +74 -128
  185. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_models/test_mnp_table2_parity.py +7 -29
  186. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_models/test_mnp_unpar_se.py +10 -0
  187. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_models/test_model_facade.py +45 -9
  188. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_models/test_morp.py +8 -0
  189. pybhatlib-0.4.0/tests/test_reparam_extreme_inputs.py +84 -0
  190. pybhatlib-0.3.2/src/pybhatlib/__init__.py +0 -54
  191. pybhatlib-0.3.2/src/pybhatlib/_version.py +0 -1
  192. pybhatlib-0.3.2/src/pybhatlib/io/_data_loader.py +0 -44
  193. pybhatlib-0.3.2/src/pybhatlib/matgradient/_radial.py +0 -181
  194. pybhatlib-0.3.2/src/pybhatlib/utils/__init__.py +0 -6
  195. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/CONTRIBUTING.md +0 -0
  196. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/LICENSE +0 -0
  197. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/data/TRAVELMODE.csv +0 -0
  198. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/data/TRAVELMODE_synthetic.csv +0 -0
  199. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/data/WorkshopData_ToursimExp.csv +0 -0
  200. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/data/Workshop_SCAG_Est.csv +0 -0
  201. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/data/generate_travelmode.py +0 -0
  202. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/data/modeData.csv +0 -0
  203. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/mnp_age45.py +0 -0
  204. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/mnp_ate_analysis.py +0 -0
  205. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/mnp_flexible_cov.py +0 -0
  206. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/mnp_iid.py +0 -0
  207. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/mnp_mixture.py +0 -0
  208. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/mnp_random_coefficients.py +0 -0
  209. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/morp_example.py +0 -0
  210. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/_convert_to_ipynb.py +0 -0
  211. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/python_scripts/t00_quickstart.py +0 -0
  212. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/python_scripts/t01a_vectorization.py +0 -0
  213. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/python_scripts/t01b_ldlt.py +0 -0
  214. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/python_scripts/t01c_truncated_mvn.py +0 -0
  215. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/python_scripts/t02a_gradcovcor.py +0 -0
  216. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/python_scripts/t02b_spherical.py +0 -0
  217. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/python_scripts/t02c_chain_rules.py +0 -0
  218. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/python_scripts/t03a_mvncd_methods.py +0 -0
  219. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/python_scripts/t03b_mvncd_gradients.py +0 -0
  220. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/python_scripts/t03c_mvncd_rect.py +0 -0
  221. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/python_scripts/t03d_univariate_cdfs.py +0 -0
  222. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/python_scripts/t03e_bhat2018_table1.py +0 -0
  223. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/python_scripts/t04a_mnp_iid.py +0 -0
  224. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/python_scripts/t04b_mnp_flexible_cov.py +0 -0
  225. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/python_scripts/t04c_mnp_heteronly.py +0 -0
  226. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/python_scripts/t04d_mnp_random_coefficients.py +0 -0
  227. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/python_scripts/t04f_mnp_control_options.py +0 -0
  228. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/python_scripts/t04g_mnp_forecasting.py +0 -0
  229. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/python_scripts/t04i_bhat2018_table2.py +0 -0
  230. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/python_scripts/t05b_morp_ate_predict.py +0 -0
  231. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/python_scripts/t06a_backend_switching.py +0 -0
  232. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/python_scripts/t06b_custom_specs.py +0 -0
  233. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/python_scripts/t06c_gradient_verification.py +0 -0
  234. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/python_scripts/t07a_mdcev_trad.py +0 -0
  235. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/python_scripts/t07b_mdcev_lin.py +0 -0
  236. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/python_scripts/t08a_mnl.py +0 -0
  237. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/t00_quickstart.ipynb +0 -0
  238. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/t01a_vectorization.ipynb +0 -0
  239. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/t01b_ldlt.ipynb +0 -0
  240. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/t01c_truncated_mvn.ipynb +0 -0
  241. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/t02a_gradcovcor.ipynb +0 -0
  242. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/t02b_spherical.ipynb +0 -0
  243. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/t02c_chain_rules.ipynb +0 -0
  244. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/t03a_mvncd_methods.ipynb +0 -0
  245. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/t03b_mvncd_gradients.ipynb +0 -0
  246. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/t03c_mvncd_rect.ipynb +0 -0
  247. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/t03d_univariate_cdfs.ipynb +0 -0
  248. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/t03e_bhat2018_table1.ipynb +0 -0
  249. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/t04a_mnp_iid.ipynb +0 -0
  250. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/t04b_mnp_flexible_cov.ipynb +0 -0
  251. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/t04c_mnp_heteronly.ipynb +0 -0
  252. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/t04d_mnp_random_coefficients.ipynb +0 -0
  253. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/t04f_mnp_control_options.ipynb +0 -0
  254. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/t04g_mnp_forecasting.ipynb +0 -0
  255. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/t04i_bhat2018_table2.ipynb +0 -0
  256. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/t05b_morp_ate_predict.ipynb +0 -0
  257. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/t06a_backend_switching.ipynb +0 -0
  258. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/t06b_custom_specs.ipynb +0 -0
  259. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/t06c_gradient_verification.ipynb +0 -0
  260. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/t07a_mdcev_trad.ipynb +0 -0
  261. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/t07b_mdcev_lin.ipynb +0 -0
  262. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/examples/tutorials/t08a_mnl.ipynb +0 -0
  263. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/backend/__init__.py +0 -0
  264. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/backend/_array_api.py +0 -0
  265. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/backend/_numpy_backend.py +0 -0
  266. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/backend/_torch_backend.py +0 -0
  267. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/gradmvn/__init__.py +0 -0
  268. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/gradmvn/_bivariate_trunc.py +0 -0
  269. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/gradmvn/_cond_trunc_grads.py +0 -0
  270. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/gradmvn/_logit_dist.py +0 -0
  271. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/gradmvn/_mvncd.py +0 -0
  272. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/gradmvn/_mvncd_grad.py +0 -0
  273. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/gradmvn/_mvncd_grad_analytic.py +0 -0
  274. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/gradmvn/_mvncd_grad_ovus.py +0 -0
  275. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/gradmvn/_mvncd_ssj.py +0 -0
  276. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/gradmvn/_mvncd_torch.py +0 -0
  277. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/gradmvn/_ordering.py +0 -0
  278. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/gradmvn/_other_dists.py +0 -0
  279. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/gradmvn/_partial_cdf.py +0 -0
  280. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/gradmvn/_trunc_grads.py +0 -0
  281. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/gradmvn/_truncated.py +0 -0
  282. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/gradmvn/_univariate.py +0 -0
  283. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/io/__init__.py +0 -0
  284. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/io/_spec_parser.py +0 -0
  285. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/matgradient/_chain_rules.py +0 -0
  286. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/matgradient/_gomegxomegax.py +0 -0
  287. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/matgradient/_gradcovcor.py +0 -0
  288. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/matgradient/_mat_grad_helpers.py +0 -0
  289. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/matgradient/_spherical.py +0 -0
  290. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/__init__.py +0 -0
  291. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/_ate_common.py +0 -0
  292. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/_base.py +0 -0
  293. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/_results_common.py +0 -0
  294. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/mdcev/__init__.py +0 -0
  295. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/mdcev/_mdcev_ate.py +0 -0
  296. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/mdcev/_mdcev_control.py +0 -0
  297. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/mdcev/_mdcev_results.py +0 -0
  298. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/mnl/__init__.py +0 -0
  299. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/mnl/_mnl_ate.py +0 -0
  300. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/mnl/_mnl_control.py +0 -0
  301. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/mnl/_mnl_forecast.py +0 -0
  302. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/mnl/_mnl_loglik.py +0 -0
  303. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/mnl/_mnl_model.py +0 -0
  304. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/mnl/_mnl_results.py +0 -0
  305. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/mnp/__init__.py +0 -0
  306. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/mnp/_mnp_ate.py +0 -0
  307. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/mnp/_mnp_control.py +0 -0
  308. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/mnp/_mnp_forecast.py +0 -0
  309. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/mnp/_mnp_grad_gpu.py +0 -0
  310. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/mnp/_mnp_results.py +0 -0
  311. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/morp/__init__.py +0 -0
  312. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/morp/_morp_ate.py +0 -0
  313. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/morp/_morp_control.py +0 -0
  314. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/morp/_morp_forecast.py +0 -0
  315. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/morp/_morp_model.py +0 -0
  316. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/models/morp/_morp_results.py +0 -0
  317. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/optim/__init__.py +0 -0
  318. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/optim/_convergence.py +0 -0
  319. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/optim/_scipy_optim.py +0 -0
  320. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/optim/_torch_optim.py +0 -0
  321. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/utils/_seeds.py +0 -0
  322. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/utils/_validation.py +0 -0
  323. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/vecup/_ldlt.py +0 -0
  324. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/vecup/_mask.py +0 -0
  325. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/vecup/_nondiag.py +0 -0
  326. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/vecup/_truncnorm.py +0 -0
  327. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/src/pybhatlib/vecup/_vec_ops.py +0 -0
  328. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/__init__.py +0 -0
  329. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/conftest.py +0 -0
  330. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/fixtures/bhatlib_table2_targets.json +0 -0
  331. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_api_harmonization.py +0 -0
  332. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_backend/__init__.py +0 -0
  333. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_backend/test_array_api.py +0 -0
  334. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_gradmvn/__init__.py +0 -0
  335. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_gradmvn/test_bivariate_trunc.py +0 -0
  336. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_gradmvn/test_cond_trunc_grads.py +0 -0
  337. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_gradmvn/test_mvncd.py +0 -0
  338. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_gradmvn/test_mvncd_batch.py +0 -0
  339. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_gradmvn/test_mvncd_grad_analytic.py +0 -0
  340. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_gradmvn/test_mvncd_grad_ovus.py +0 -0
  341. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_gradmvn/test_mvncd_methods.py +0 -0
  342. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_gradmvn/test_mvncd_rect.py +0 -0
  343. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_gradmvn/test_mvncd_rect_inf_bounds.py +0 -0
  344. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_gradmvn/test_ordering.py +0 -0
  345. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_gradmvn/test_other_dists.py +0 -0
  346. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_gradmvn/test_trunc_grads.py +0 -0
  347. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_integration/__init__.py +0 -0
  348. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_matgradient/__init__.py +0 -0
  349. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_matgradient/test_gradcovcor.py +0 -0
  350. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_matgradient/test_mat_grad_helpers.py +0 -0
  351. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_matgradient/test_radial.py +0 -0
  352. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_matgradient/test_spherical.py +0 -0
  353. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_models/__init__.py +0 -0
  354. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_models/test_ate_common.py +0 -0
  355. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_models/test_ate_from_params.py +0 -0
  356. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_models/test_mdcev_scenarios.py +0 -0
  357. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_models/test_mnl.py +0 -0
  358. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_models/test_mnl_ate_from_params.py +0 -0
  359. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_models/test_mnp_active_mask_verbose.py +0 -0
  360. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_models/test_mnp_ate_scenarios.py +0 -0
  361. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_models/test_mnp_control.py +0 -0
  362. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_models/test_mnp_gauss_parity.py +0 -0
  363. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_models/test_mnp_grad.py +0 -0
  364. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_models/test_mnp_perobs_scores.py +0 -0
  365. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_models/test_mnp_se_options.py +0 -0
  366. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_models/test_morp_analytic_gradient.py +0 -0
  367. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_models/test_morp_gauss_parity.py +0 -0
  368. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_models/test_morp_per_outcome_spec.py +0 -0
  369. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_models/test_morp_scenarios.py +0 -0
  370. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_models/test_results_harmonization.py +0 -0
  371. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_vecup/__init__.py +0 -0
  372. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_vecup/test_ldlt.py +0 -0
  373. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_vecup/test_ldlt_rank2.py +0 -0
  374. {pybhatlib-0.3.2 → pybhatlib-0.4.0}/tests/test_vecup/test_vec_ops.py +0 -0
@@ -70,3 +70,12 @@ PESOSE-related
70
70
  .env
71
71
  .claude
72
72
 
73
+ pesose*
74
+
75
+ # Local scratch / harness output (mixed-model work)
76
+ issue*
77
+ PR32*
78
+ 0714*
79
+ 0717*
80
+ gauss_harness/
81
+ bench/*_log.csv
@@ -4,6 +4,30 @@ All notable changes to pybhatlib are documented here. The format is based on
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
5
5
  adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.4.0] - 2026-09-16
8
+
9
+ ### Added
10
+ - **Linear regression (LR)** — `LRModel` / `LRControl` / `LRResults`: one
11
+ continuous outcome solved in closed form (SVD least squares) with classical,
12
+ White-robust, and BHHH standard errors; the residual standard deviation
13
+ `sigma` is reported as the trailing parameter (MDCEV convention);
14
+ `lr_predict`, `lr_ate` / `lr_ate_from_params` on the shared `scenarios=`
15
+ API; tutorial `t09a_lr`.
16
+ - **Mixed (simulation-based) model family** — a shared maximum-simulated-
17
+ likelihood engine (Halton draws, panel broadcast, normal / log-normal /
18
+ Yeo-Johnson coefficient transforms, analytic scores through the
19
+ reparameterisation Jacobians), plus four models built on it:
20
+ `MixMNLModel` (mixed multinomial logit), `MNPKerCPModel` (mixed MNP
21
+ coupling random coefficients to the kernel error through a copula),
22
+ `MORPFlexModel` (mixed MORP with a flexible Yeo-Johnson
23
+ kernel and rectangle MVNCD), and `MDCEVMixedModel` (mixed MDCEV with a
24
+ vectorized per-draw kernel). These are new and still under active
25
+ refinement; the closed-form models above are the stable API.
26
+
27
+ ### Fixed
28
+ - **MNP mixture-of-normals** — coefficients that are not random now stay shared
29
+ across segments instead of being re-estimated per segment.
30
+
7
31
  ## [0.3.2] - 2026-07-22
8
32
 
9
33
  First version available on PyPI. Identical in code to 0.3.1; released under a new
@@ -1,7 +1,7 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: pybhatlib
3
- Version: 0.3.2
4
- Summary: Python implementation of BHATLIB: matrix-based inference for advanced econometric models
3
+ Version: 0.4.0
4
+ Summary: Matrix-based econometric inference in Python: analytic MVNCD evaluation and discrete-choice 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
7
7
  Project-URL: Documentation, https://github.com/UMN-Choi-Lab/pybhatlib#readme
@@ -40,18 +40,28 @@ Description-Content-Type: text/markdown
40
40
 
41
41
  # pybhatlib
42
42
 
43
+ [![PyPI](https://img.shields.io/pypi/v/pybhatlib)](https://pypi.org/project/pybhatlib/)
43
44
  [![Python](https://img.shields.io/pypi/pyversions/pybhatlib)](https://pypi.org/project/pybhatlib/)
45
+ [![Tests](https://img.shields.io/github/actions/workflow/status/UMN-Choi-Lab/pybhatlib/tests.yml?branch=main&label=tests)](https://github.com/UMN-Choi-Lab/pybhatlib/actions/workflows/tests.yml)
44
46
  [![License: MIT](https://img.shields.io/pypi/l/pybhatlib)](https://github.com/UMN-Choi-Lab/pybhatlib/blob/main/LICENSE)
45
47
 
46
- Python reimplementation of **BHATLIB** — an open-source library for statistical and
47
- econometric matrix-based inference methods.
48
+ Open-source Python library for matrix-based econometric inference: multivariate
49
+ normal CDF evaluation with analytic gradients, and maximum-likelihood estimation of
50
+ discrete-choice models (MNP, MORP, MDCEV, MNL).
48
51
 
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
+ The numerical core follows the GAUSS **BHATLIB** library by Chandra Bhat and
53
+ colleagues (UT Austin / Aptech Systems) and implements Bhat's (2018) analytic
54
+ MVNCD approximation.
52
55
 
53
56
  ## Installation
54
57
 
58
+ ```bash
59
+ pip install pybhatlib # core (NumPy + SciPy + Numba)
60
+ pip install "pybhatlib[torch]" # add PyTorch backend (optional GPU)
61
+ ```
62
+
63
+ From source (for development, or to run the bundled examples and tutorials):
64
+
55
65
  ```bash
56
66
  git clone https://github.com/UMN-Choi-Lab/pybhatlib.git
57
67
  cd pybhatlib
@@ -117,8 +127,15 @@ A runnable end-to-end example (with `morp_ate`, `morp_predict`,
117
127
  - **Multivariate Ordered Response Probit (MORP)** — multiple ordinal outcomes
118
128
  with shared covariance; per-outcome `spec` mapping
119
129
  - **Multiple Discrete-Continuous Extreme Value (MDCEV)** — traditional
120
- (Bhat 2008) and linear (Bhat 2018) outside-good utility specifications,
121
- selected via `MDCEVControl.utility`
130
+ (Bhat 2008) and linear outside-good utility specifications, selected via
131
+ `MDCEVControl.utility`
132
+ - **Multinomial Logit (MNL)**
133
+ - **Linear regression (LR)** — single continuous outcome, closed-form OLS with
134
+ classical / White-robust / BHHH standard errors
135
+ - **Mixed / simulation-based models** — `MixMNLModel`, `MNPKerCPModel`,
136
+ `MORPFlexModel`, `MDCEVMixedModel` on a shared maximum-simulated-likelihood
137
+ engine (Halton draws, panel data, normal / log-normal / Yeo-Johnson
138
+ coefficient transforms). New and under active refinement.
122
139
 
123
140
  **Numerical core**
124
141
  - `vecup` — vecdup, matdupfull, LDLT decomposition, truncated MVN moments
@@ -151,8 +168,8 @@ A runnable end-to-end example (with `morp_ate`, `morp_predict`,
151
168
 
152
169
  ## Verification
153
170
 
154
- pybhatlib reproduces Table 1 from the BHATLIB paper (Bhat 2018) using the
155
- TRAVELMODE dataset (3 modes — DA, SR, TR; 1125 observations):
171
+ pybhatlib reproduces Table 1 of the BHATLIB GAUSS reference implementation using
172
+ the TRAVELMODE dataset (3 modes — DA, SR, TR; 1125 observations):
156
173
 
157
174
  | Model | Specification | Target LL | Achieved LL | Status |
158
175
  |-------|--------------|-----------|-------------|--------|
@@ -162,10 +179,10 @@ TRAVELMODE dataset (3 modes — DA, SR, TR; 1125 observations):
162
179
  | (c) | + Random coeff. OVTT | -635.871 | -635.871 | exact match |
163
180
  | (d) | 2-segment mixture | -634.975 | -632.912 | close (multi-modal) |
164
181
 
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.
182
+ Models (a)–(c) reproduce the GAUSS estimates and BHHH standard errors to ≤0.001
183
+ on every parameter (verified end-to-end against GAUSS 26.1.1 + MaxLik 5.0.9).
184
+ Model (d) is multi-modal — the Python optimum is a slightly better local mode
185
+ than the GAUSS one.
169
186
 
170
187
  For MORP, `iid=False` now uses GAUSS BHATLIB's unit-variance identification by
171
188
  default (`MORPControl.fix_scales=True`): the latent-utility variances are fixed
@@ -193,6 +210,8 @@ matching `.py` script under `python_scripts/`):
193
210
  | MNP | `t04a_mnp_iid` … `t04g_mnp_forecasting`, `t04h_bhatlib_table1`, `t04i_bhat2018_table2` |
194
211
  | MORP | `t05b_morp_ate_predict` |
195
212
  | MDCEV | `t07a_mdcev_trad`, `t07b_mdcev_lin` |
213
+ | MNL | `t08a_mnl` |
214
+ | LR | `t09a_lr` |
196
215
  | Backends & verification | `t06a_backend_switching`, `t06b_custom_specs`, `t06c_gradient_verification` |
197
216
 
198
217
  ## Contributing
@@ -211,8 +230,9 @@ pytest tests/ -m torch # PyTorch backend tests only
211
230
  1. Bhat, C. R. (2018). New Matrix-Based Methods for the Analytic Evaluation of the
212
231
  Multivariate Cumulative Normal Distribution Function. *Transportation Research
213
232
  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.
233
+ 2. Bhat, C. R. (2008). The Multiple Discrete-Continuous Extreme Value (MDCEV)
234
+ Model: Role of Utility Function Parameters, Identification Considerations, and
235
+ Model Extensions. *Transportation Research Part B*, 42(3): 274–303.
216
236
 
217
237
  ## License
218
238
 
@@ -1,17 +1,27 @@
1
1
  # pybhatlib
2
2
 
3
+ [![PyPI](https://img.shields.io/pypi/v/pybhatlib)](https://pypi.org/project/pybhatlib/)
3
4
  [![Python](https://img.shields.io/pypi/pyversions/pybhatlib)](https://pypi.org/project/pybhatlib/)
5
+ [![Tests](https://img.shields.io/github/actions/workflow/status/UMN-Choi-Lab/pybhatlib/tests.yml?branch=main&label=tests)](https://github.com/UMN-Choi-Lab/pybhatlib/actions/workflows/tests.yml)
4
6
  [![License: MIT](https://img.shields.io/pypi/l/pybhatlib)](https://github.com/UMN-Choi-Lab/pybhatlib/blob/main/LICENSE)
5
7
 
6
- Python reimplementation of **BHATLIB** — an open-source library for statistical and
7
- econometric matrix-based inference methods.
8
+ Open-source Python library for matrix-based econometric inference: multivariate
9
+ normal CDF evaluation with analytic gradients, and maximum-likelihood estimation of
10
+ discrete-choice models (MNP, MORP, MDCEV, MNL).
8
11
 
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
+ The numerical core follows the GAUSS **BHATLIB** library by Chandra Bhat and
13
+ colleagues (UT Austin / Aptech Systems) and implements Bhat's (2018) analytic
14
+ MVNCD approximation.
12
15
 
13
16
  ## Installation
14
17
 
18
+ ```bash
19
+ pip install pybhatlib # core (NumPy + SciPy + Numba)
20
+ pip install "pybhatlib[torch]" # add PyTorch backend (optional GPU)
21
+ ```
22
+
23
+ From source (for development, or to run the bundled examples and tutorials):
24
+
15
25
  ```bash
16
26
  git clone https://github.com/UMN-Choi-Lab/pybhatlib.git
17
27
  cd pybhatlib
@@ -77,8 +87,15 @@ A runnable end-to-end example (with `morp_ate`, `morp_predict`,
77
87
  - **Multivariate Ordered Response Probit (MORP)** — multiple ordinal outcomes
78
88
  with shared covariance; per-outcome `spec` mapping
79
89
  - **Multiple Discrete-Continuous Extreme Value (MDCEV)** — traditional
80
- (Bhat 2008) and linear (Bhat 2018) outside-good utility specifications,
81
- selected via `MDCEVControl.utility`
90
+ (Bhat 2008) and linear outside-good utility specifications, selected via
91
+ `MDCEVControl.utility`
92
+ - **Multinomial Logit (MNL)**
93
+ - **Linear regression (LR)** — single continuous outcome, closed-form OLS with
94
+ classical / White-robust / BHHH standard errors
95
+ - **Mixed / simulation-based models** — `MixMNLModel`, `MNPKerCPModel`,
96
+ `MORPFlexModel`, `MDCEVMixedModel` on a shared maximum-simulated-likelihood
97
+ engine (Halton draws, panel data, normal / log-normal / Yeo-Johnson
98
+ coefficient transforms). New and under active refinement.
82
99
 
83
100
  **Numerical core**
84
101
  - `vecup` — vecdup, matdupfull, LDLT decomposition, truncated MVN moments
@@ -111,8 +128,8 @@ A runnable end-to-end example (with `morp_ate`, `morp_predict`,
111
128
 
112
129
  ## Verification
113
130
 
114
- pybhatlib reproduces Table 1 from the BHATLIB paper (Bhat 2018) using the
115
- TRAVELMODE dataset (3 modes — DA, SR, TR; 1125 observations):
131
+ pybhatlib reproduces Table 1 of the BHATLIB GAUSS reference implementation using
132
+ the TRAVELMODE dataset (3 modes — DA, SR, TR; 1125 observations):
116
133
 
117
134
  | Model | Specification | Target LL | Achieved LL | Status |
118
135
  |-------|--------------|-----------|-------------|--------|
@@ -122,10 +139,10 @@ TRAVELMODE dataset (3 modes — DA, SR, TR; 1125 observations):
122
139
  | (c) | + Random coeff. OVTT | -635.871 | -635.871 | exact match |
123
140
  | (d) | 2-segment mixture | -634.975 | -632.912 | close (multi-modal) |
124
141
 
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.
142
+ Models (a)–(c) reproduce the GAUSS estimates and BHHH standard errors to ≤0.001
143
+ on every parameter (verified end-to-end against GAUSS 26.1.1 + MaxLik 5.0.9).
144
+ Model (d) is multi-modal — the Python optimum is a slightly better local mode
145
+ than the GAUSS one.
129
146
 
130
147
  For MORP, `iid=False` now uses GAUSS BHATLIB's unit-variance identification by
131
148
  default (`MORPControl.fix_scales=True`): the latent-utility variances are fixed
@@ -153,6 +170,8 @@ matching `.py` script under `python_scripts/`):
153
170
  | MNP | `t04a_mnp_iid` … `t04g_mnp_forecasting`, `t04h_bhatlib_table1`, `t04i_bhat2018_table2` |
154
171
  | MORP | `t05b_morp_ate_predict` |
155
172
  | MDCEV | `t07a_mdcev_trad`, `t07b_mdcev_lin` |
173
+ | MNL | `t08a_mnl` |
174
+ | LR | `t09a_lr` |
156
175
  | Backends & verification | `t06a_backend_switching`, `t06b_custom_specs`, `t06c_gradient_verification` |
157
176
 
158
177
  ## Contributing
@@ -171,8 +190,9 @@ pytest tests/ -m torch # PyTorch backend tests only
171
190
  1. Bhat, C. R. (2018). New Matrix-Based Methods for the Analytic Evaluation of the
172
191
  Multivariate Cumulative Normal Distribution Function. *Transportation Research
173
192
  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.
193
+ 2. Bhat, C. R. (2008). The Multiple Discrete-Continuous Extreme Value (MDCEV)
194
+ Model: Role of Utility Function Parameters, Identification Considerations, and
195
+ Model Extensions. *Transportation Research Part B*, 42(3): 274–303.
176
196
 
177
197
  ## License
178
198
 
@@ -80,7 +80,8 @@ print("""
80
80
  Example (S=2, n_beta=7, flexible covariance with 1 free param):
81
81
  2 * 7 + 1 + 1 = 16 parameters
82
82
 
83
- In pybhatlib: set nseg=2 in MNPControl to enable a 2-segment mixture.
83
+ In pybhatlib: set nseg=2 in MNPControl and name the coefficient(s) that
84
+ vary by segment in ranvars=[...]; every other coefficient is shared.
84
85
  """)
85
86
 
86
87
  # ============================================================
@@ -110,6 +111,7 @@ model = MNPModel(
110
111
  alternatives=alternatives,
111
112
  spec=spec,
112
113
  control=MNPControl(iid=False, nseg=2, maxiter=200, verbose=1, seed=42),
114
+ ranvars=["OVTT"], # OVTT varies by segment; all other betas are shared
113
115
  )
114
116
  results = model.fit()
115
117
  t_elapsed = time.perf_counter() - t0
@@ -149,15 +151,13 @@ print("""
149
151
  "MNP Table2 d") fits a 2-segment mixture WITH a random coefficient
150
152
  on OVTT (ranvars=["OVTT"], nrand=1), reaching LL = -634.975.
151
153
 
152
- This tutorial uses the simpler segment-specific-means form (nseg=2
153
- with no random coefficient inside each segment). On the supplied
154
- TRAVELMODE data this reaches LL = -632.912 slightly HIGHER (better)
155
- than the paper target because it has a different parameterization and
156
- one fewer covariance restriction. This is expected: mixture models
157
- have many local optima, and a higher LL simply means the optimizer
158
- found a better mode of this likelihood surface. To reproduce the
159
- exact paper model, add ranvars=["OVTT"] to the MNPModel(...) call
160
- (see Step 4).
154
+ That is exactly the model fitted above: only OVTT gets a
155
+ segment-specific coefficient (and its own variance) in each segment,
156
+ every other coefficient is shared across the two segments, and the
157
+ kernel covariance is common. On the supplied TRAVELMODE data
158
+ pybhatlib reaches LL = -634.94, matching the paper to 0.04. Mixture
159
+ likelihoods have several local optima, so a different seed may stop
160
+ at a slightly different mode with a similar LL.
161
161
  """)
162
162
 
163
163
  # Print segment probabilities if available
@@ -180,10 +180,12 @@ print("""
180
180
  two segments' coefficient vectors side by side and weight them by the
181
181
  estimated mixing probabilities pi_s.
182
182
 
183
- In this 7-variable specification, the segment-1 betas are the first 7
184
- entries of b_original (CON_SR..COST) and the segment-2 betas are the
185
- *_s2 entries near the end. Reading them together tells us how the two
186
- groups differ in their sensitivity to travel time and cost.
183
+ In this 7-variable specification only OVTT was allowed to vary by
184
+ segment (ranvars=["OVTT"]), so segment 1 owns the first 7 entries of
185
+ b_original (CON_SR..COST) and segment 2 differs from it only in the
186
+ OVTT_s2 entry near the end; the other six coefficients are shared and
187
+ identical in both columns below. Reading OVTT across the two groups
188
+ tells us how they differ in out-of-vehicle time sensitivity.
187
189
  """)
188
190
 
189
191
  # Pair up segment-1 and segment-2 betas for the 7 utility variables.
@@ -196,7 +198,7 @@ print(f" {'Variable':<12s}{'Seg1 beta':>12s}{'Seg2 beta':>12s}"
196
198
  print(" " + "-" * 49)
197
199
  for nm in beta_names:
198
200
  b1 = name_to_val.get(nm, float("nan"))
199
- b2 = name_to_val.get(nm + "_s2", float("nan"))
201
+ b2 = name_to_val.get(nm + "_s2", b1) # shared coefficient -> same value
200
202
  mixed = pi[0] * b1 + pi[1] * b2
201
203
  print(f" {nm:<12s}{b1:>12.4f}{b2:>12.4f}{mixed:>13.4f}")
202
204
 
@@ -247,12 +249,15 @@ print("""
247
249
 
248
250
  for seed in [42, 123, 456, 789]:
249
251
  ctrl = MNPControl(iid=False, nseg=2, seed=seed, maxiter=200)
250
- res = MNPModel(..., control=ctrl).fit()
252
+ res = MNPModel(..., control=ctrl, ranvars=["OVTT"]).fit()
251
253
  print(seed, res.loglik * res.n_obs)
252
254
 
253
- Parameter count scaling:
254
- - nseg=2: 2 * n_beta + 1 segment param + n_cov (doubles the betas)
255
- - nseg=3: 3 * n_beta + 2 segment params + n_cov (triples the betas)
255
+ Parameter count scaling (n_rand = number of names in ranvars):
256
+ - nseg=1: n_beta + n_cov + n_omega(n_rand)
257
+ - nseg=2: + 1 segment param + n_rand betas + n_omega(n_rand)
258
+ - nseg=3: + 2 segment params + 2 * (n_rand betas + n_omega(n_rand))
259
+ Coefficients not listed in ranvars are shared across segments and
260
+ cost nothing extra.
256
261
  - Identification requires sufficient variation in the data; too many
257
262
  segments on a small dataset will produce near-flat likelihood
258
263
 
@@ -263,7 +268,7 @@ print("""
263
268
  4. Use LR test or BIC to decide between nseg=1 and nseg=2
264
269
 
265
270
  Rule of thumb:
266
- - Each additional segment adds n_beta + 1 free parameters
271
+ - Each additional segment adds 1 + n_rand + n_omega(n_rand) free parameters
267
272
  - Worthwhile only when delta-LL > (n_beta + 1) / 2 (BIC criterion)
268
273
  - For n_beta=7: need delta-LL > 4 to justify a second segment
269
274
  """)
@@ -320,6 +320,7 @@ model_d = MNPModel(
320
320
  alternatives=alternatives,
321
321
  spec=spec_7var,
322
322
  control=MNPControl(iid=False, nseg=2, maxiter=200, verbose=0, seed=42),
323
+ ranvars=["OVTT"], # paper Model (d): OVTT varies by segment
323
324
  )
324
325
  res_d = model_d.fit()
325
326
  t_d = time.perf_counter() - t0
@@ -0,0 +1,238 @@
1
+ """Tutorial T09a: Linear Regression (Single Continuous Outcome).
2
+
3
+ This tutorial fits an ordinary least-squares linear regression with
4
+ pybhatlib's ``LRModel`` on the tourism-expenditure data used in t07a/t07b,
5
+ explaining log total trip expenditure with traveller and trip attributes.
6
+
7
+ What you will learn:
8
+ - How the ``spec`` convention shared by all pybhatlib models maps
9
+ coefficient names to data columns (``"uno"`` = constant)
10
+ - That estimation is analytical (SVD least squares): no optimizer,
11
+ starting values, or convergence settings
12
+ - How to read ``results.summary()``: coefficient table with the
13
+ residual standard deviation ``sigma`` as the trailing parameter,
14
+ R-squared, F-test, residual variance
15
+ - How ``LRControl(se_method=...)`` switches between classical,
16
+ heteroscedasticity-robust (White), and BHHH standard errors
17
+ - Prediction on the training data or on a new DataFrame
18
+ - Average treatment effects via the ``scenarios=`` API shared with
19
+ MNP / MNL / MORP / MDCEV, and ATEs from externally supplied coefficients
20
+
21
+ Prerequisites: t00 (quickstart).
22
+
23
+ Expected runtime: < 1 sec
24
+ """
25
+ import os, sys
26
+ import numpy as np
27
+ import pandas as pd
28
+
29
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "src"))
30
+
31
+ from pybhatlib.models.lr import LRControl, LRModel, lr_ate_from_params
32
+
33
+ data_path = os.path.join(os.path.dirname(__file__), "..", "..", "data", "WorkshopData_ToursimExp.csv")
34
+
35
+ # ============================================================
36
+ # Step 1: Data and Specification
37
+ # ============================================================
38
+ print("=" * 60)
39
+ print(" Step 1: Data and Specification")
40
+ print("=" * 60)
41
+
42
+ data = pd.read_csv(data_path)
43
+
44
+ # Outcome: log of total trip expenditure across the five spending categories
45
+ # (accommodation, food & beverage, transport, shopping, recreation).
46
+ data["total_exp"] = data[["Accomod", "FandB", "Transp", "Shp", "Recr"]].sum(axis=1)
47
+ data["ln_exp"] = np.log(data["total_exp"])
48
+
49
+ print(f" n = {len(data)} trips")
50
+ print(f" total expenditure: mean = {data['total_exp'].mean():,.0f}, "
51
+ f"median = {data['total_exp'].median():,.0f}")
52
+ print(f" ln_exp: mean = {data['ln_exp'].mean():.3f}, sd = {data['ln_exp'].std():.3f}")
53
+
54
+ print("""
55
+ The spec grammar is the one every pybhatlib model uses: a coefficient
56
+ name maps to a data column, to "uno" (a column of ones, i.e. the
57
+ constant), or to a numeric constant. Nothing is added implicitly, so
58
+ the intercept is declared explicitly. The outcome-keyed form used by
59
+ the multi-outcome models, e.g. {"B_RURAL": {"ln_exp": "rural"}}, is
60
+ also accepted.
61
+ """)
62
+
63
+ spec = {
64
+ "CON": "uno", # constant
65
+ "B_RURAL": "rural", # rural residence (urban = base)
66
+ "B_FEM": "fr_fem", # fraction of the travel party that is female
67
+ "B_HINC": "hinc20k", # household income above $20k
68
+ "B_ST410": "st410", # stay of 4-10 days (< 3 days = base)
69
+ "B_STGT10": "stgt10", # stay longer than 10 days
70
+ }
71
+
72
+ for name, col in spec.items():
73
+ print(f" {name:<10s} <- {col}")
74
+
75
+ # ============================================================
76
+ # Step 2: Estimate and Summarize
77
+ # ============================================================
78
+ print("\n" + "=" * 60)
79
+ print(" Step 2: Estimate and Summarize")
80
+ print("=" * 60)
81
+
82
+ print("""
83
+ LRModel follows the same object surface as the other models
84
+ (fit / predict / ate / results.summary()), but the least-squares
85
+ solution is computed in closed form from the SVD of the design
86
+ matrix. There is no optimizer, so LRControl carries only covariance
87
+ and verbosity options; results.n_iter is 0 and results.converged is
88
+ True by construction.
89
+ """)
90
+
91
+ model = LRModel(
92
+ data=data,
93
+ dep_var="ln_exp",
94
+ spec=spec,
95
+ control=LRControl(verbose=1),
96
+ )
97
+ results = model.fit()
98
+ print()
99
+ results.summary()
100
+
101
+ print("""
102
+ Reading the summary:
103
+ - The last row, sigma, is the residual standard deviation: the
104
+ Gaussian MLE sqrt(SSE / N), reported as a parameter with its own
105
+ standard error exactly as MDCEV reports its scale (results.params
106
+ is [coefficients..., sigma]). Its t-statistic tests sigma = 0 and
107
+ carries no information.
108
+ - Mean log-likelihood is the Gaussian log-likelihood per observation
109
+ at sigma^2 = SSE / N (results.sigma2).
110
+ - Residual variance is the unbiased SSE / (N - K)
111
+ (results.residual_variance); the default se_method="hessian" with
112
+ df_correction=True gives the textbook OLS covariance
113
+ SSE / (N - K) * (X'X)^-1 for the coefficients. p-values use
114
+ Student's t with N - K degrees of freedom for every se_method.
115
+ - F(df_model, df_resid) jointly tests all non-constant slopes.
116
+ - R-squared is centered because the design spans a constant.
117
+ """)
118
+
119
+ print(" Coefficient table as a DataFrame:")
120
+ print(results.to_dataframe().round(4).to_string())
121
+
122
+ # ============================================================
123
+ # Step 3: Standard-Error Options
124
+ # ============================================================
125
+ print("\n" + "=" * 60)
126
+ print(" Step 3: Standard-Error Options")
127
+ print("=" * 60)
128
+
129
+ print("""
130
+ Expenditure data are typically heteroscedastic, so compare the
131
+ classical covariance with the White sandwich. With df_correction=True
132
+ (the default) the sandwich is the HC1 form; "bhhh" inverts the score
133
+ cross-product of the Gaussian likelihood over all parameters including
134
+ sigma. Estimates are identical across methods; only the standard
135
+ errors change.
136
+ """)
137
+
138
+ se_table = {}
139
+ for method in ["hessian", "sandwich", "bhhh"]:
140
+ ctrl = LRControl(se_method=method, verbose=0)
141
+ se_table[method] = LRModel(data, "ln_exp", spec, control=ctrl).fit().se
142
+
143
+ print(f" {'coef':<10s} {'estimate':>10s} {'se hessian':>12s} {'se sandwich':>12s} {'se bhhh':>10s}")
144
+ print(" " + "-" * 58)
145
+ for k, name in enumerate(results.param_names):
146
+ print(f" {name:<10s} {results.params[k]:>10.4f} {se_table['hessian'][k]:>12.4f} "
147
+ f"{se_table['sandwich'][k]:>12.4f} {se_table['bhhh'][k]:>10.4f}")
148
+
149
+ # ============================================================
150
+ # Step 4: Prediction
151
+ # ============================================================
152
+ print("\n" + "=" * 60)
153
+ print(" Step 4: Prediction")
154
+ print("=" * 60)
155
+
156
+ print("""
157
+ model.predict() with no argument returns the in-sample fitted values.
158
+ Passing a DataFrame rebuilds the design through the model's spec, so
159
+ new observations only need the columns the spec refers to. (Passing a
160
+ ready-made (N, K) array is also accepted.)
161
+ """)
162
+
163
+ y_hat = model.predict()
164
+ rmse = np.sqrt(np.mean((data["ln_exp"] - y_hat) ** 2))
165
+ print(f" In-sample RMSE of ln_exp: {rmse:.4f}")
166
+ print(f" R-squared recomputed from fitted values: "
167
+ f"{1 - np.sum((data['ln_exp'] - y_hat) ** 2) / np.sum((data['ln_exp'] - data['ln_exp'].mean()) ** 2):.6f}")
168
+
169
+ new_trips = pd.DataFrame({
170
+ "rural": [0, 1],
171
+ "fr_fem": [0.5, 0.5],
172
+ "hinc20k": [1, 0],
173
+ "st410": [1, 0],
174
+ "stgt10": [0, 1],
175
+ })
176
+ pred_ln = model.predict(new_trips)
177
+ print("\n Two hypothetical trips:")
178
+ print(f" {'trip':<6s} {'rural':>6s} {'hinc':>5s} {'stay':>8s} {'ln_exp':>8s} {'exp(ln_exp)':>12s}")
179
+ for i in range(len(new_trips)):
180
+ stay = "4-10 d" if new_trips.st410[i] else (">10 d" if new_trips.stgt10[i] else "<3 d")
181
+ print(f" {i + 1:<6d} {new_trips.rural[i]:>6d} {new_trips.hinc20k[i]:>5d} {stay:>8s} "
182
+ f"{pred_ln[i]:>8.3f} {np.exp(pred_ln[i]):>12,.0f}")
183
+
184
+ print("""
185
+ Note: exp(predicted ln_exp) is the conditional median-type prediction
186
+ of expenditure, not its conditional mean (Jensen's inequality).
187
+ """)
188
+
189
+ # ============================================================
190
+ # Step 5: Average Treatment Effects (scenarios= API)
191
+ # ============================================================
192
+ print("\n" + "=" * 60)
193
+ print(" Step 5: Average Treatment Effects (scenarios= API)")
194
+ print("=" * 60)
195
+
196
+ print("""
197
+ model.ate(scenarios=...) uses the scenario grammar shared with the other
198
+ models: each scenario overrides data columns with a scalar or with
199
+ another column, the design is rebuilt through the spec, and the mean
200
+ prediction is reported per scenario. For a linear model the effect in
201
+ outcome units of switching a dummy from 0 to 1 is exactly its
202
+ coefficient, which makes this a good check of the machinery.
203
+ """)
204
+
205
+ ate = model.ate(scenarios={
206
+ "urban": {"rural": 0},
207
+ "rural": {"rural": 1},
208
+ "long_stay_hinc": {"st410": 0, "stgt10": 1, "hinc20k": 1},
209
+ })
210
+ ate.summary()
211
+
212
+ effect_units = ate.comparison("urban", "rural")
213
+ print(f"\n ATE rural vs urban, in ln_exp units: {effect_units:+.4f}")
214
+ print(f" B_RURAL coefficient: {results.params[1]:+.4f}")
215
+ print(f" Implied expenditure ratio exp(B): {np.exp(results.params[1]):.3f}")
216
+
217
+ print("""
218
+ comparison() reports the effect in outcome units (here ln_exp).
219
+ percent=True gives the percentage change of the *predicted mean of
220
+ ln_exp*, the share-model convention; for a log outcome that is not the
221
+ percentage change in expenditure. Use exp(effect) - 1 for that, as
222
+ shown above.
223
+ """)
224
+
225
+ print(" Scenario table:")
226
+ print(ate.to_dataframe().round(4).to_string())
227
+
228
+ # ATEs from externally supplied estimates (e.g. GAUSS output) without
229
+ # re-fitting: pass the reported vector [coefficients..., sigma] exactly as
230
+ # printed by summary(); same scenario API, same numbers.
231
+ external = lr_ate_from_params(
232
+ results.params,
233
+ param_names=results.param_names,
234
+ data=data, spec=spec, dep_var="ln_exp",
235
+ scenarios={"urban": {"rural": 0}, "rural": {"rural": 1}},
236
+ )
237
+ print(f"\n lr_ate_from_params reproduces the fitted ATE: "
238
+ f"{external.comparison('urban', 'rural'):+.4f}")