pyrestoolbox 3.7.2__tar.gz → 3.7.4__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 (198) hide show
  1. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/.gitignore +3 -0
  2. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/PKG-INFO +3 -2
  3. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/README.rst +2 -1
  4. pyrestoolbox-3.7.4/benchmark_batch.py +160 -0
  5. pyrestoolbox-3.7.4/benchmark_viscosity.py +213 -0
  6. pyrestoolbox-3.7.4/benchmark_zfactor.py +207 -0
  7. pyrestoolbox-3.7.4/benchmarks/_bench_worker.py +192 -0
  8. pyrestoolbox-3.7.4/benchmarks/bench_acceleration.py +103 -0
  9. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyproject.toml +1 -1
  10. pyrestoolbox-3.7.4/pyresToolbox_rust_acceleration_instructions.md +1415 -0
  11. pyrestoolbox-3.7.4/pyrestoolbox/brine/__init__.py +18 -0
  12. pyrestoolbox-3.7.4/pyrestoolbox/brine/_lib_dielectric.py +144 -0
  13. pyrestoolbox-3.7.4/pyrestoolbox/brine/_lib_maoduan_viscosity.py +37 -0
  14. pyrestoolbox-3.7.4/pyrestoolbox/brine/_lib_pitzer_params.py +130 -0
  15. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/brine/_lib_vle_engine.py +11 -7
  16. pyrestoolbox-3.7.4/pyrestoolbox/brine/appelo_volumes.py +243 -0
  17. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/brine/brine.py +109 -48
  18. pyrestoolbox-3.7.4/pyrestoolbox/brine/iapws_viscosity.py +201 -0
  19. pyrestoolbox-3.7.4/pyrestoolbox/brine/jones_dole_viscosity.py +260 -0
  20. pyrestoolbox-3.7.4/pyrestoolbox/brine/kestin_nacl_viscosity.py +112 -0
  21. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/brine/pr_vphi.py +43 -17
  22. pyrestoolbox-3.7.4/pyrestoolbox/brine/viscosity_route.py +168 -0
  23. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/brine/vphi_route.py +34 -7
  24. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/brine.rst +274 -52
  25. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/changelist.rst +23 -1
  26. pyrestoolbox-3.7.4/pyrestoolbox-mcp-0.1.0.zip +0 -0
  27. pyrestoolbox-3.7.4/pyrestoolbox-mcp-email.txt +39 -0
  28. pyrestoolbox-3.7.4/reviews/CONSOLIDATED.md +101 -0
  29. pyrestoolbox-3.7.4/reviews/README.md +68 -0
  30. pyrestoolbox-3.7.4/reviews/api_ux/_summary.md +85 -0
  31. pyrestoolbox-3.7.4/reviews/api_ux/brine.md +74 -0
  32. pyrestoolbox-3.7.4/reviews/api_ux/dca.md +83 -0
  33. pyrestoolbox-3.7.4/reviews/api_ux/gas.md +114 -0
  34. pyrestoolbox-3.7.4/reviews/api_ux/layer.md +63 -0
  35. pyrestoolbox-3.7.4/reviews/api_ux/matbal.md +86 -0
  36. pyrestoolbox-3.7.4/reviews/api_ux/nodal.md +109 -0
  37. pyrestoolbox-3.7.4/reviews/api_ux/oil.md +71 -0
  38. pyrestoolbox-3.7.4/reviews/api_ux/plyasunov.md +55 -0
  39. pyrestoolbox-3.7.4/reviews/api_ux/recommend.md +56 -0
  40. pyrestoolbox-3.7.4/reviews/api_ux/sensitivity.md +62 -0
  41. pyrestoolbox-3.7.4/reviews/api_ux/simtools.md +79 -0
  42. pyrestoolbox-3.7.4/reviews/correctness/_summary.md +49 -0
  43. pyrestoolbox-3.7.4/reviews/correctness/brine.md +45 -0
  44. pyrestoolbox-3.7.4/reviews/correctness/dca.md +186 -0
  45. pyrestoolbox-3.7.4/reviews/correctness/gas.md +18 -0
  46. pyrestoolbox-3.7.4/reviews/correctness/layer.md +106 -0
  47. pyrestoolbox-3.7.4/reviews/correctness/matbal.md +193 -0
  48. pyrestoolbox-3.7.4/reviews/correctness/nodal.md +33 -0
  49. pyrestoolbox-3.7.4/reviews/correctness/oil.md +32 -0
  50. pyrestoolbox-3.7.4/reviews/correctness/plyasunov.md +140 -0
  51. pyrestoolbox-3.7.4/reviews/correctness/recommend.md +73 -0
  52. pyrestoolbox-3.7.4/reviews/correctness/sensitivity.md +31 -0
  53. pyrestoolbox-3.7.4/reviews/correctness/simtools.md +43 -0
  54. pyrestoolbox-3.7.4/reviews/review-2026-06-10.md +138 -0
  55. pyrestoolbox-3.7.4/reviews/tech_debt/_summary.md +95 -0
  56. pyrestoolbox-3.7.4/reviews/tech_debt/brine.md +68 -0
  57. pyrestoolbox-3.7.4/reviews/tech_debt/dca.md +108 -0
  58. pyrestoolbox-3.7.4/reviews/tech_debt/gas.md +134 -0
  59. pyrestoolbox-3.7.4/reviews/tech_debt/layer.md +95 -0
  60. pyrestoolbox-3.7.4/reviews/tech_debt/matbal.md +78 -0
  61. pyrestoolbox-3.7.4/reviews/tech_debt/nodal.md +95 -0
  62. pyrestoolbox-3.7.4/reviews/tech_debt/oil.md +80 -0
  63. pyrestoolbox-3.7.4/reviews/tech_debt/plyasunov.md +103 -0
  64. pyrestoolbox-3.7.4/reviews/tech_debt/recommend.md +81 -0
  65. pyrestoolbox-3.7.4/reviews/tech_debt/sensitivity.md +72 -0
  66. pyrestoolbox-3.7.4/reviews/tech_debt/simtools.md +112 -0
  67. pyrestoolbox-3.7.4/zstar_residual_plots.py +302 -0
  68. pyrestoolbox-3.7.4/zstar_vs_z_comparison.zip +0 -0
  69. pyrestoolbox-3.7.2/pyrestoolbox/brine/__init__.py +0 -5
  70. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/.github/workflows/build-wheels.yml +0 -0
  71. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/.github/workflows/tests.yml +0 -0
  72. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/Cargo.lock +0 -0
  73. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/Cargo.toml +0 -0
  74. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/LICENSE +0 -0
  75. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/MANIFEST.in +0 -0
  76. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/ResToolbox/privacy_policy.md +0 -0
  77. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/benchmark_rust_vs_python.py +0 -0
  78. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/build_pure_python.py +0 -0
  79. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/mcp/DEPLOYMENT.md +0 -0
  80. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/mcp/Dockerfile +0 -0
  81. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/mcp/README.md +0 -0
  82. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/mcp/pyproject.toml +0 -0
  83. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/mcp/pyrestoolbox_mcp/__init__.py +0 -0
  84. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/mcp/pyrestoolbox_mcp/serialize.py +0 -0
  85. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/mcp/pyrestoolbox_mcp/server.py +0 -0
  86. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/mcp/tests/test_server.py +0 -0
  87. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/__init__.py +0 -0
  88. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/_accelerator.py +0 -0
  89. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/brine/_lib_salting_library.py +0 -0
  90. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/classes/__init__.py +0 -0
  91. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/classes/classes.py +0 -0
  92. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/constants/__init__.py +0 -0
  93. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/constants/constants.py +0 -0
  94. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/dca/__init__.py +0 -0
  95. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/dca/dca.py +0 -0
  96. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/dca.rst +0 -0
  97. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/examples.ipynb +0 -0
  98. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/gas.rst +0 -0
  99. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/img/bot.png +0 -0
  100. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/img/bot_PVTO.png +0 -0
  101. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/img/bot_img.png +0 -0
  102. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/img/dry_gas.png +0 -0
  103. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/img/grid_sat_df.png +0 -0
  104. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/img/influence.png +0 -0
  105. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/img/properties_df.png +0 -0
  106. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/img/sgof.png +0 -0
  107. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/img/swof.png +0 -0
  108. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/index.rst +0 -0
  109. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/layer.rst +0 -0
  110. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/library.rst +0 -0
  111. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/matbal.rst +0 -0
  112. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/nodal.rst +0 -0
  113. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/nodal_examples.ipynb +0 -0
  114. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/nodal_hydrate_demo.ipynb +0 -0
  115. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/oil.rst +0 -0
  116. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/plyasunov.rst +0 -0
  117. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/recommend.rst +0 -0
  118. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/sensitivity.rst +0 -0
  119. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/simtools.rst +0 -0
  120. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/gas/__init__.py +0 -0
  121. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/gas/_hydrate.py +0 -0
  122. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/gas/gas.py +0 -0
  123. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/layer/__init__.py +0 -0
  124. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/layer/layer.py +0 -0
  125. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/library/__init__.py +0 -0
  126. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/library/component_library.xlsx +0 -0
  127. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/library/library.py +0 -0
  128. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/matbal/__init__.py +0 -0
  129. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/matbal/matbal.py +0 -0
  130. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/nodal/__init__.py +0 -0
  131. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/nodal/nodal.py +0 -0
  132. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/oil/__init__.py +0 -0
  133. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/oil/_compressibility.py +0 -0
  134. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/oil/_constants.py +0 -0
  135. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/oil/_correlations.py +0 -0
  136. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/oil/_density.py +0 -0
  137. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/oil/_harmonize.py +0 -0
  138. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/oil/_pvt_class.py +0 -0
  139. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/oil/_rate.py +0 -0
  140. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/oil/_separator.py +0 -0
  141. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/oil/_tables.py +0 -0
  142. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/oil/_utils.py +0 -0
  143. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/plyasunov/__init__.py +0 -0
  144. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/plyasunov/iapws_if97.py +0 -0
  145. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/plyasunov/plyasunov_model.py +0 -0
  146. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/plyasunov/water_properties.py +0 -0
  147. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/recommend/__init__.py +0 -0
  148. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/recommend/recommend.py +0 -0
  149. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/sensitivity/__init__.py +0 -0
  150. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/sensitivity/sensitivity.py +0 -0
  151. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/shared_fns/__init__.py +0 -0
  152. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/shared_fns/shared_fns.py +0 -0
  153. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/simtools/__init__.py +0 -0
  154. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/simtools/_aquifer.py +0 -0
  155. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/simtools/_decks.py +0 -0
  156. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/simtools/_pvt_tables.py +0 -0
  157. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/simtools/_relperm.py +0 -0
  158. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/simtools/_vfp.py +0 -0
  159. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/simtools/simtools.py +0 -0
  160. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/validate/__init__.py +0 -0
  161. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/validate/validate.py +0 -0
  162. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/setup.cfg +0 -0
  163. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/setup.py +0 -0
  164. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/bessel.rs +0 -0
  165. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/critical_properties/mod.rs +0 -0
  166. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/dca/hyperbolic.rs +0 -0
  167. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/dca/mod.rs +0 -0
  168. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/dca/ransac.rs +0 -0
  169. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/gas_viscosity/mod.rs +0 -0
  170. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/gwr.rs +0 -0
  171. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/lib.rs +0 -0
  172. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/matbal/mod.rs +0 -0
  173. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/matbal/objective.rs +0 -0
  174. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/oil/density.rs +0 -0
  175. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/oil/mod.rs +0 -0
  176. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/pseudopressure.rs +0 -0
  177. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/spycher_pruess/mod.rs +0 -0
  178. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/spycher_pruess/solubility.rs +0 -0
  179. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vle/alpha.rs +0 -0
  180. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vle/bip.rs +0 -0
  181. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vle/components.rs +0 -0
  182. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vle/eos.rs +0 -0
  183. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vle/flash.rs +0 -0
  184. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vle/fugacity.rs +0 -0
  185. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vle/k_init.rs +0 -0
  186. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vle/mod.rs +0 -0
  187. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vle/rachford_rice.rs +0 -0
  188. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vlp/constants.rs +0 -0
  189. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vlp/friction.rs +0 -0
  190. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vlp/holdup_bb.rs +0 -0
  191. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vlp/holdup_gray.rs +0 -0
  192. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vlp/holdup_wg.rs +0 -0
  193. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vlp/ift.rs +0 -0
  194. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vlp/march.rs +0 -0
  195. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vlp/mod.rs +0 -0
  196. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vlp/pvt_helpers.rs +0 -0
  197. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vlp/static_column.rs +0 -0
  198. {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/zfactor/mod.rs +0 -0
@@ -43,3 +43,6 @@ ilt/
43
43
  benchmark_ilt.py
44
44
  benchmark_rust.py
45
45
  BUILD_INSTRUCTIONS.txt
46
+
47
+ # local virtualenv (389 MB, never committed)
48
+ .venv/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyrestoolbox
3
- Version: 3.7.2
3
+ Version: 3.7.4
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Rust
6
6
  Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
@@ -55,6 +55,7 @@ Includes functions to perform calculations including;
55
55
  - Generation of AQUTAB include file influence functions for use in ECLIPSE
56
56
  - Creation of Corey, LET and Jerauld relative permeability tables in Eclipse format, with curve fitting support
57
57
  - Calculation of Methane, CO2 and multicomponent gas saturated brine properties (Soreide-Whitson VLE)
58
+ - Multi-salt gas-free brine viscosity: IAPWS-2008 water, an ion-additive salt ratio over any combination of Na, K, Mg, Ca, Sr, Ba, Cl, Br, sulfate and carbonate, and a measured pressure factor
58
59
  - Method recommendation engine for selecting appropriate correlations based on fluid composition
59
60
  - Sensitivity analysis with parameter sweeps and tornado charts
60
61
 
@@ -127,7 +128,7 @@ Module List
127
128
  * - `library <https://github.com/mwburgoyne/pyResToolbox/blob/main/pyrestoolbox/docs/library.rst>`_
128
129
  - Return critical parameters for typical single components
129
130
  * - `brine <https://github.com/mwburgoyne/pyResToolbox/blob/main/pyrestoolbox/docs/brine.rst>`_
130
- - Calculate suite of brine properties with variable methane, Calculate suite of CO2 saturated brine properties, Multicomponent gas-saturated brine (Soreide-Whitson VLE)
131
+ - Calculate suite of brine properties with variable methane, Calculate suite of CO2 saturated brine properties, Multicomponent gas-saturated brine (Soreide-Whitson VLE), multi-salt gas-free brine viscosity
131
132
  * - `layer <https://github.com/mwburgoyne/pyResToolbox/blob/main/pyrestoolbox/docs/layer.rst>`_
132
133
  - Lorenz coefficient from Beta value, Lorenz coefficient from flow fraction, Lorenz coefficient to flow fraction, Lorenz coefficient to permeability array
133
134
  * - `simtools <https://github.com/mwburgoyne/pyResToolbox/blob/main/pyrestoolbox/docs/simtools.rst>`_
@@ -29,6 +29,7 @@ Includes functions to perform calculations including;
29
29
  - Generation of AQUTAB include file influence functions for use in ECLIPSE
30
30
  - Creation of Corey, LET and Jerauld relative permeability tables in Eclipse format, with curve fitting support
31
31
  - Calculation of Methane, CO2 and multicomponent gas saturated brine properties (Soreide-Whitson VLE)
32
+ - Multi-salt gas-free brine viscosity: IAPWS-2008 water, an ion-additive salt ratio over any combination of Na, K, Mg, Ca, Sr, Ba, Cl, Br, sulfate and carbonate, and a measured pressure factor
32
33
  - Method recommendation engine for selecting appropriate correlations based on fluid composition
33
34
  - Sensitivity analysis with parameter sweeps and tornado charts
34
35
 
@@ -101,7 +102,7 @@ Module List
101
102
  * - `library <https://github.com/mwburgoyne/pyResToolbox/blob/main/pyrestoolbox/docs/library.rst>`_
102
103
  - Return critical parameters for typical single components
103
104
  * - `brine <https://github.com/mwburgoyne/pyResToolbox/blob/main/pyrestoolbox/docs/brine.rst>`_
104
- - Calculate suite of brine properties with variable methane, Calculate suite of CO2 saturated brine properties, Multicomponent gas-saturated brine (Soreide-Whitson VLE)
105
+ - Calculate suite of brine properties with variable methane, Calculate suite of CO2 saturated brine properties, Multicomponent gas-saturated brine (Soreide-Whitson VLE), multi-salt gas-free brine viscosity
105
106
  * - `layer <https://github.com/mwburgoyne/pyResToolbox/blob/main/pyrestoolbox/docs/layer.rst>`_
106
107
  - Lorenz coefficient from Beta value, Lorenz coefficient from flow fraction, Lorenz coefficient to flow fraction, Lorenz coefficient to permeability array
107
108
  * - `simtools <https://github.com/mwburgoyne/pyResToolbox/blob/main/pyrestoolbox/docs/simtools.rst>`_
@@ -0,0 +1,160 @@
1
+ """Benchmark: Batch vs scalar Rust dispatch for gas_z and gas_ug."""
2
+
3
+ import time
4
+ import numpy as np
5
+ import sys
6
+ sys.path.insert(0, '/home/mark/projects')
7
+
8
+ from pyrestoolbox._native import (
9
+ bns_zfactor_full, bns_zfactor_batch,
10
+ dak_zfactor_full, dak_zfactor_batch,
11
+ hy_zfactor_batch, hall_yarborough_zfactor_full,
12
+ gas_ug_lge, gas_ug_lge_batch,
13
+ gas_ug_lbc, gas_ug_lbc_batch,
14
+ )
15
+ from pyrestoolbox import gas as gmod
16
+ import pyrestoolbox.gas.gas as gas_mod
17
+
18
+ N_POINTS = 10_000
19
+ N_REPEATS = 20
20
+ pressures = np.linspace(14.7, 15000, N_POINTS)
21
+ p_list = pressures.tolist()
22
+
23
+ # Common parameters
24
+ sg, degf = 0.7, 200.0
25
+ co2, h2s, n2, h2 = 0.01, 0.0, 0.02, 0.0
26
+
27
+ print(f"Batch vs Scalar Rust dispatch: {N_POINTS} pressure points, {N_REPEATS} repeats")
28
+ print("=" * 80)
29
+
30
+ def bench(name, fn, repeats=N_REPEATS):
31
+ times = []
32
+ result = None
33
+ for _ in range(repeats):
34
+ t0 = time.perf_counter()
35
+ result = fn()
36
+ t1 = time.perf_counter()
37
+ times.append(t1 - t0)
38
+ return np.median(times) * 1000, np.min(times) * 1000, result
39
+
40
+ # ==================== Z-Factor ====================
41
+ print("\n--- Z-Factor: BNS Method ---")
42
+ _, t_scalar, z_scalar = bench("BNS scalar", lambda: np.array([
43
+ bns_zfactor_full(float(pi), degf, sg, co2, h2s, n2, h2) for pi in pressures
44
+ ]))
45
+ _, t_batch, z_batch = bench("BNS batch", lambda: np.array(
46
+ bns_zfactor_batch(p_list, degf, sg, co2, h2s, n2, h2)
47
+ ))
48
+
49
+ # Also benchmark pure Python (Rust disabled)
50
+ orig_rust = gas_mod.RUST_AVAILABLE
51
+ gas_mod.RUST_AVAILABLE = False
52
+ _, t_python, z_python = bench("BNS Python", lambda: gmod.gas_z(
53
+ pressures, sg, degf, zmethod='BNS', co2=co2, h2s=h2s, n2=n2, h2=h2
54
+ ))
55
+ gas_mod.RUST_AVAILABLE = orig_rust
56
+
57
+ print(f" {'Method':<30} {'Min (ms)':>10} {'Speedup':>10}")
58
+ print(f" {'-'*55}")
59
+ print(f" {'Python (NumPy vectorized)':<30} {t_python:>10.2f} {'baseline':>10}")
60
+ print(f" {'Rust scalar (N FFI calls)':<30} {t_scalar:>10.2f} {t_python/t_scalar:>10.1f}x")
61
+ print(f" {'Rust batch (1 FFI call)':<30} {t_batch:>10.2f} {t_python/t_batch:>10.1f}x")
62
+ print(f" Batch vs scalar speedup: {t_scalar/t_batch:.1f}x")
63
+ print(f" Max |batch - scalar|: {np.max(np.abs(z_batch - z_scalar)):.2e}")
64
+
65
+ # DAK
66
+ print("\n--- Z-Factor: DAK Method (SUT critical props) ---")
67
+ _, t_dak_scalar, z_dak_s = bench("DAK scalar", lambda: np.array([
68
+ dak_zfactor_full(float(pi), degf, sg, co2, h2s, n2) for pi in pressures
69
+ ]))
70
+ _, t_dak_batch, z_dak_b = bench("DAK batch", lambda: np.array(
71
+ dak_zfactor_batch(p_list, degf, sg, co2, h2s, n2)
72
+ ))
73
+ gas_mod.RUST_AVAILABLE = False
74
+ _, t_dak_py, _ = bench("DAK Python", lambda: gmod.gas_z(
75
+ pressures, sg, degf, zmethod='DAK', cmethod='SUT', co2=co2, h2s=h2s, n2=n2
76
+ ))
77
+ gas_mod.RUST_AVAILABLE = orig_rust
78
+
79
+ print(f" {'Method':<30} {'Min (ms)':>10} {'Speedup':>10}")
80
+ print(f" {'-'*55}")
81
+ print(f" {'Python (NumPy vectorized)':<30} {t_dak_py:>10.2f} {'baseline':>10}")
82
+ print(f" {'Rust scalar (N FFI calls)':<30} {t_dak_scalar:>10.2f} {t_dak_py/t_dak_scalar:>10.1f}x")
83
+ print(f" {'Rust batch (1 FFI call)':<30} {t_dak_batch:>10.2f} {t_dak_py/t_dak_batch:>10.1f}x")
84
+ print(f" Batch vs scalar speedup: {t_dak_scalar/t_dak_batch:.1f}x")
85
+ print(f" Max |batch - scalar|: {np.max(np.abs(z_dak_b - z_dak_s)):.2e}")
86
+
87
+ # HY
88
+ print("\n--- Z-Factor: HY Method (SUT critical props) ---")
89
+ _, t_hy_scalar, z_hy_s = bench("HY scalar", lambda: np.array([
90
+ hall_yarborough_zfactor_full(float(pi), degf, sg, co2, h2s, n2) for pi in pressures
91
+ ]))
92
+ _, t_hy_batch, z_hy_b = bench("HY batch", lambda: np.array(
93
+ hy_zfactor_batch(p_list, degf, sg, co2, h2s, n2)
94
+ ))
95
+ gas_mod.RUST_AVAILABLE = False
96
+ _, t_hy_py, _ = bench("HY Python", lambda: gmod.gas_z(
97
+ pressures, sg, degf, zmethod='HY', cmethod='SUT', co2=co2, h2s=h2s, n2=n2
98
+ ))
99
+ gas_mod.RUST_AVAILABLE = orig_rust
100
+
101
+ print(f" {'Method':<30} {'Min (ms)':>10} {'Speedup':>10}")
102
+ print(f" {'-'*55}")
103
+ print(f" {'Python (NumPy vectorized)':<30} {t_hy_py:>10.2f} {'baseline':>10}")
104
+ print(f" {'Rust scalar (N FFI calls)':<30} {t_hy_scalar:>10.2f} {t_hy_py/t_hy_scalar:>10.1f}x")
105
+ print(f" {'Rust batch (1 FFI call)':<30} {t_hy_batch:>10.2f} {t_hy_py/t_hy_batch:>10.1f}x")
106
+ print(f" Batch vs scalar speedup: {t_hy_scalar/t_hy_batch:.1f}x")
107
+ print(f" Max |batch - scalar|: {np.max(np.abs(z_hy_b - z_hy_s)):.2e}")
108
+
109
+ # ==================== Viscosity ====================
110
+ print("\n--- Viscosity: LGE Method ---")
111
+ z_dak = np.array(dak_zfactor_batch(p_list, degf, sg, co2, h2s, n2))
112
+ _, t_lge_scalar, ug_lge_s = bench("LGE scalar", lambda: np.array([
113
+ gas_ug_lge(float(pi), sg, degf, float(zi)) for pi, zi in zip(pressures, z_dak)
114
+ ]))
115
+ _, t_lge_batch, ug_lge_b = bench("LGE batch", lambda: np.array(
116
+ gas_ug_lge_batch(p_list, z_dak.tolist(), sg, degf)
117
+ ))
118
+ print(f" {'Method':<30} {'Min (ms)':>10} {'Speedup':>10}")
119
+ print(f" {'-'*55}")
120
+ print(f" {'Rust scalar (N FFI calls)':<30} {t_lge_scalar:>10.2f} {'baseline':>10}")
121
+ print(f" {'Rust batch (1 FFI call)':<30} {t_lge_batch:>10.2f} {t_lge_scalar/t_lge_batch:>10.1f}x")
122
+ print(f" Max |batch - scalar|: {np.max(np.abs(ug_lge_b - ug_lge_s)):.2e}")
123
+
124
+ print("\n--- Viscosity: LBC Method (BNS) ---")
125
+ z_bns = np.array(bns_zfactor_batch(p_list, degf, sg, co2, h2s, n2, h2))
126
+ _, t_lbc_scalar, ug_lbc_s = bench("LBC scalar", lambda: np.array([
127
+ gas_ug_lbc(float(pi), sg, degf, co2, h2s, n2, h2, float(zi)) for pi, zi in zip(pressures, z_bns)
128
+ ]))
129
+ _, t_lbc_batch, ug_lbc_b = bench("LBC batch", lambda: np.array(
130
+ gas_ug_lbc_batch(p_list, z_bns.tolist(), sg, degf, co2, h2s, n2, h2)
131
+ ))
132
+ print(f" {'Method':<30} {'Min (ms)':>10} {'Speedup':>10}")
133
+ print(f" {'-'*55}")
134
+ print(f" {'Rust scalar (N FFI calls)':<30} {t_lbc_scalar:>10.2f} {'baseline':>10}")
135
+ print(f" {'Rust batch (1 FFI call)':<30} {t_lbc_batch:>10.2f} {t_lbc_scalar/t_lbc_batch:>10.1f}x")
136
+ print(f" Max |batch - scalar|: {np.max(np.abs(ug_lbc_b - ug_lbc_s)):.2e}")
137
+
138
+ # ==================== Full Pipeline (gas_z + gas_ug via public API) ====================
139
+ print("\n" + "=" * 80)
140
+ print("--- Full Pipeline: gas_z() + gas_ug() via public API ---")
141
+
142
+ # With batch Rust (current)
143
+ gas_mod.RUST_AVAILABLE = True
144
+ _, t_full_batch, _ = bench("Batch Rust", lambda: (
145
+ gmod.gas_z(pressures, sg, degf, zmethod='BNS', co2=co2, h2s=h2s, n2=n2, h2=h2),
146
+ gmod.gas_ug(pressures, sg, degf, zmethod='BNS', co2=co2, h2s=h2s, n2=n2, h2=h2),
147
+ ))
148
+
149
+ # Pure Python
150
+ gas_mod.RUST_AVAILABLE = False
151
+ _, t_full_python, _ = bench("Python", lambda: (
152
+ gmod.gas_z(pressures, sg, degf, zmethod='BNS', co2=co2, h2s=h2s, n2=n2, h2=h2),
153
+ gmod.gas_ug(pressures, sg, degf, zmethod='BNS', co2=co2, h2s=h2s, n2=n2, h2=h2),
154
+ ))
155
+ gas_mod.RUST_AVAILABLE = orig_rust
156
+
157
+ print(f" {'Method':<30} {'Min (ms)':>10} {'Speedup':>10}")
158
+ print(f" {'-'*55}")
159
+ print(f" {'Python (NumPy vectorized)':<30} {t_full_python:>10.2f} {'baseline':>10}")
160
+ print(f" {'Rust batch (2 FFI calls)':<30} {t_full_batch:>10.2f} {t_full_python/t_full_batch:>10.1f}x")
@@ -0,0 +1,213 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ BNS Viscosity Benchmark: Rust-accelerated vs Pure Python
4
+
5
+ Replicates the benchmarking approach from Peter Kirkham's analysis at
6
+ https://pkirkham.github.io/analysis/viscosity-bns/
7
+
8
+ Runs randomised BNS viscosity calculations in 5-second windows, comparing
9
+ Rust-accelerated pyResToolbox against the pure-Python fallback. Reports
10
+ calculations per second for single and batch (1000-pressure) modes, and
11
+ produces a logarithmic bar chart.
12
+ """
13
+
14
+ import json
15
+ import os
16
+ import subprocess
17
+ import sys
18
+ import time
19
+
20
+ # ── Benchmark code executed inside subprocesses ──────────────────────────
21
+
22
+ BENCHMARK_CODE = r'''
23
+ import time
24
+ import random
25
+ import numpy as np
26
+ from pyrestoolbox import gas
27
+ from pyrestoolbox.classes import z_method, c_method
28
+
29
+ P_SC = 14.7 # psia
30
+ T_SC = 60.0 # degF
31
+ DURATION = 5.0 # seconds per benchmark window
32
+
33
+ def random_between(a, b):
34
+ return a + (b - a) * random.random()
35
+
36
+ def run_single(duration=DURATION):
37
+ """Single-pressure calculations, one at a time."""
38
+ count = 0
39
+ start = time.time()
40
+ while time.time() - start < duration:
41
+ p = random_between(P_SC, 15000.0)
42
+ t = random_between(T_SC, 300.0)
43
+ sg = random_between(0.65, 0.9)
44
+ if random.random() < 0.3:
45
+ inert_frac = random.random()
46
+ co2 = random_between(0.0, inert_frac)
47
+ h2s = random_between(0.0, 1.0 - co2)
48
+ n2 = random_between(0.0, 1.0 - co2 - h2s)
49
+ h2 = random_between(0.0, 1.0 - co2 - h2s - n2)
50
+ else:
51
+ co2 = h2s = n2 = h2 = 0.0
52
+
53
+ mu = gas.gas_ug(p=p, sg=sg, degf=t,
54
+ zmethod=z_method.BNS, cmethod=c_method.BNS,
55
+ co2=co2, h2s=h2s, n2=n2, h2=h2)
56
+ assert np.all(np.isfinite(mu)) and np.all(mu > 0)
57
+ count += 1
58
+
59
+ elapsed = time.time() - start
60
+ return count / elapsed
61
+
62
+ def run_batch(n_samples=1000, duration=DURATION):
63
+ """Batch-pressure calculations, n_samples pressures per iteration."""
64
+ rng = np.random.default_rng()
65
+ count = 0
66
+ start = time.time()
67
+ while time.time() - start < duration:
68
+ p = rng.uniform(P_SC, 15000.0, size=n_samples)
69
+ t = random_between(T_SC, 300.0)
70
+ sg = random_between(0.65, 0.9)
71
+ if random.random() < 0.3:
72
+ inert_frac = random.random()
73
+ co2 = random_between(0.0, inert_frac)
74
+ h2s = random_between(0.0, 1.0 - co2)
75
+ n2 = random_between(0.0, 1.0 - co2 - h2s)
76
+ h2 = random_between(0.0, 1.0 - co2 - h2s - n2)
77
+ else:
78
+ co2 = h2s = n2 = h2 = 0.0
79
+
80
+ mu = gas.gas_ug(p=p, sg=sg, degf=t,
81
+ zmethod=z_method.BNS, cmethod=c_method.BNS,
82
+ co2=co2, h2s=h2s, n2=n2, h2=h2)
83
+ assert np.all(np.isfinite(mu)) and np.all(mu > 0)
84
+ count += n_samples
85
+
86
+ elapsed = time.time() - start
87
+ return count / elapsed
88
+
89
+ import json
90
+ single_rate = run_single()
91
+ batch_rate = run_batch()
92
+ print(json.dumps({"single": single_rate, "batch": batch_rate}))
93
+ '''
94
+
95
+
96
+ def run_subprocess(env_override=None):
97
+ """Run the benchmark in a subprocess, return dict with single/batch rates."""
98
+ env = os.environ.copy()
99
+ if env_override:
100
+ env.update(env_override)
101
+
102
+ code = f"import sys, os\nsys.path.insert(0, {os.getcwd()!r})\n{BENCHMARK_CODE}"
103
+ result = subprocess.run(
104
+ [sys.executable, "-c", code],
105
+ capture_output=True, text=True, env=env, timeout=120
106
+ )
107
+ if result.returncode != 0:
108
+ print(f" ERROR: {result.stderr.strip()[:200]}")
109
+ return None
110
+ try:
111
+ return json.loads(result.stdout.strip().split("\n")[-1])
112
+ except (json.JSONDecodeError, IndexError):
113
+ print(f" Bad output: {result.stdout[:200]}")
114
+ return None
115
+
116
+
117
+ def main():
118
+ print("=" * 70)
119
+ print(" BNS Viscosity Benchmark: Rust vs Pure Python")
120
+ print(" (after P. Kirkham, https://pkirkham.github.io/analysis/viscosity-bns/)")
121
+ print("=" * 70)
122
+ print()
123
+ print(" Parameters:")
124
+ print(" Pressure: 14.7 – 15,000 psia (uniform random)")
125
+ print(" Temperature: 60 – 300 °F (uniform random)")
126
+ print(" SG: 0.65 – 0.9 (uniform random)")
127
+ print(" Inerts: 30% chance of CO2/H2S/N2/H2")
128
+ print(" Duration: 5 s per test window")
129
+ print(" Batch size: 1,000 pressures per call")
130
+ print()
131
+
132
+ # ── Rust-accelerated run ─────────────────────────────────────────────
133
+ print(" Running Rust-accelerated benchmark...", flush=True)
134
+ rust = run_subprocess()
135
+ if rust is None:
136
+ print(" Rust benchmark failed!")
137
+ return
138
+
139
+ print(f" Single: {rust['single']:>10,.0f} calc/s")
140
+ print(f" Batch: {rust['batch']:>10,.0f} calc/s")
141
+ print()
142
+
143
+ # ── Pure Python run ──────────────────────────────────────────────────
144
+ print(" Running pure-Python benchmark...", flush=True)
145
+ python = run_subprocess(env_override={"PYRESTOOLBOX_NO_RUST": "1"})
146
+ if python is None:
147
+ print(" Python benchmark failed!")
148
+ return
149
+
150
+ print(f" Single: {python['single']:>10,.0f} calc/s")
151
+ print(f" Batch: {python['batch']:>10,.0f} calc/s")
152
+ print()
153
+
154
+ # ── Summary table ────────────────────────────────────────────────────
155
+ speedup_single = rust["single"] / python["single"] if python["single"] > 0 else 0
156
+ speedup_batch = rust["batch"] / python["batch"] if python["batch"] > 0 else 0
157
+
158
+ print(" ┌─────────────────┬──────────────┬──────────────┬──────────┐")
159
+ print(" │ Mode │ Rust (calc/s)│Python (calc/s)│ Speedup │")
160
+ print(" ├─────────────────┼──────────────┼──────────────┼──────────┤")
161
+ print(f" │ Single pressure │ {rust['single']:>12,.0f}│{python['single']:>14,.0f}│ {speedup_single:>7.1f}x │")
162
+ print(f" │ 1000x batch │ {rust['batch']:>12,.0f}│{python['batch']:>14,.0f}│ {speedup_batch:>7.1f}x │")
163
+ print(" └─────────────────┴──────────────┴──────────────┴──────────┘")
164
+ print()
165
+
166
+ # ── Bar chart ────────────────────────────────────────────────────────
167
+ try:
168
+ import matplotlib
169
+ matplotlib.use("Agg")
170
+ import matplotlib.pyplot as plt
171
+ import numpy as np
172
+
173
+ labels = ["Python\nSingle", "Rust\nSingle", "Python\nBatch (1000)", "Rust\nBatch (1000)"]
174
+ rates = [python["single"], rust["single"], python["batch"], rust["batch"]]
175
+ colors = ["#4878CF", "#D65F5F", "#4878CF", "#D65F5F"]
176
+ hatches = ["", "", "//", "//"]
177
+
178
+ fig, ax = plt.subplots(figsize=(8, 5))
179
+ bars = ax.bar(labels, rates, color=colors, edgecolor="black", linewidth=0.8)
180
+ for bar, h in zip(bars, hatches):
181
+ bar.set_hatch(h)
182
+
183
+ ax.set_yscale("log")
184
+ ax.set_ylabel("BNS Viscosity Calculations per Second")
185
+ ax.set_title("BNS Viscosity Benchmark: Rust-accelerated vs Pure Python\n"
186
+ "(P: 14.7–15,000 psia, T: 60–300 °F, SG: 0.65–0.9, 30% inerts)")
187
+ ax.grid(axis="y", which="both", alpha=0.3)
188
+
189
+ # Annotate bars with values
190
+ for bar, rate in zip(bars, rates):
191
+ ax.text(bar.get_x() + bar.get_width() / 2, rate * 1.3,
192
+ f"{rate:,.0f}/s", ha="center", va="bottom", fontsize=10, fontweight="bold")
193
+
194
+ # Add speedup annotations
195
+ ax.annotate(f"{speedup_single:.1f}x", xy=(1, rust["single"]),
196
+ xytext=(0.5, rust["single"] * 3),
197
+ arrowprops=dict(arrowstyle="->", color="green", lw=1.5),
198
+ fontsize=11, color="green", fontweight="bold", ha="center")
199
+ ax.annotate(f"{speedup_batch:.1f}x", xy=(3, rust["batch"]),
200
+ xytext=(2.5, rust["batch"] * 3),
201
+ arrowprops=dict(arrowstyle="->", color="green", lw=1.5),
202
+ fontsize=11, color="green", fontweight="bold", ha="center")
203
+
204
+ plt.tight_layout()
205
+ outpath = os.path.join(os.path.dirname(__file__) or ".", "benchmark_viscosity.png")
206
+ plt.savefig(outpath, dpi=150)
207
+ print(f" Chart saved to: {outpath}")
208
+ except ImportError:
209
+ print(" (matplotlib not available — skipping chart)")
210
+
211
+
212
+ if __name__ == "__main__":
213
+ main()
@@ -0,0 +1,207 @@
1
+ """Benchmark: Halley (Z*=Z-B) vs Cardano analytical solver for PR-EOS cubic.
2
+
3
+ Tests both pure Python and Rust-accelerated paths.
4
+ """
5
+
6
+ import time
7
+ import numpy as np
8
+ import sys
9
+ sys.path.insert(0, '/home/mark/projects')
10
+
11
+ from pyrestoolbox.gas.gas import _cardano_cubic, _halley_cubic_vec
12
+
13
+ # ---------- Setup: generate realistic cubic coefficients ----------
14
+ # Use BNS-style PR-EOS coefficients for a range of pressures
15
+ # Simulate a typical gas: sg=0.7, T=200F, pressures 14.7 to 15000 psia
16
+ np.random.seed(42)
17
+
18
+ N_POINTS = 10_000 # Number of pressure points to solve
19
+
20
+ # Realistic ranges for A and B from PR-EOS mixing rules
21
+ # A ~ 0.01 to 5.0, B ~ 0.001 to 0.5
22
+ A = np.random.uniform(0.01, 5.0, N_POINTS)
23
+ B = np.random.uniform(0.005, 0.5, N_POINTS)
24
+
25
+ # PR cubic coefficients: Z^3 - (1-B)*Z^2 + (A-3B^2-2B)*Z - (AB-B^2-B^3) = 0
26
+ c2 = -(1.0 - B)
27
+ c1 = A - 3.0 * B**2 - 2.0 * B
28
+ c0 = -(A * B - B**2 - B**3)
29
+
30
+ # ---------- Pure Python benchmarks ----------
31
+ N_REPEATS = 20
32
+
33
+ print(f"Benchmarking Z-factor solvers: {N_POINTS} cubic equations, {N_REPEATS} repeats")
34
+ print("=" * 70)
35
+
36
+ # --- Halley (vectorized, Z* reformulation) ---
37
+ times_halley = []
38
+ for _ in range(N_REPEATS):
39
+ t0 = time.perf_counter()
40
+ Z_halley = _halley_cubic_vec(c2.copy(), c1.copy(), c0.copy(), A=A, B=B)
41
+ t1 = time.perf_counter()
42
+ times_halley.append(t1 - t0)
43
+
44
+ halley_median = np.median(times_halley) * 1000
45
+ halley_min = np.min(times_halley) * 1000
46
+
47
+ # --- Cardano (element-by-element, analytical) ---
48
+ times_cardano = []
49
+ for _ in range(N_REPEATS):
50
+ t0 = time.perf_counter()
51
+ Z_cardano = np.array([_cardano_cubic(c2[i], c1[i], c0[i], flag=1) for i in range(N_POINTS)])
52
+ t1 = time.perf_counter()
53
+ times_cardano.append(t1 - t0)
54
+
55
+ cardano_median = np.median(times_cardano) * 1000
56
+ cardano_min = np.min(times_cardano) * 1000
57
+
58
+ # --- Halley without Z* (legacy path, for comparison) ---
59
+ times_halley_legacy = []
60
+ for _ in range(N_REPEATS):
61
+ t0 = time.perf_counter()
62
+ Z_halley_leg = _halley_cubic_vec(c2.copy(), c1.copy(), c0.copy())
63
+ t1 = time.perf_counter()
64
+ times_halley_legacy.append(t1 - t0)
65
+
66
+ halley_leg_median = np.median(times_halley_legacy) * 1000
67
+ halley_leg_min = np.min(times_halley_legacy) * 1000
68
+
69
+ print("\n--- Pure Python Results ---")
70
+ print(f"{'Method':<30} {'Median (ms)':>12} {'Min (ms)':>12} {'Speedup':>10}")
71
+ print("-" * 70)
72
+ print(f"{'Halley Z* (vectorized)':<30} {halley_median:>12.2f} {halley_min:>12.2f} {'baseline':>10}")
73
+ print(f"{'Halley legacy (vectorized)':<30} {halley_leg_median:>12.2f} {halley_leg_min:>12.2f} {halley_min/halley_leg_min:>10.2f}x")
74
+ print(f"{'Cardano (scalar loop)':<30} {cardano_median:>12.2f} {cardano_min:>12.2f} {halley_min/cardano_min:>10.2f}x")
75
+
76
+ # Verify agreement
77
+ max_diff = np.max(np.abs(Z_halley - Z_cardano))
78
+ print(f"\nMax |Z_halley - Z_cardano|: {max_diff:.2e}")
79
+
80
+ # Count Halley fallbacks (where Halley needed Cardano backup)
81
+ d2 = 4.0 * B - 1.0
82
+ d1_star = A + 2.0 * B * (B - 2.0)
83
+ d0_star = -2.0 * B * B
84
+ Zs_check = Z_halley - B
85
+ f_check = Zs_check**3 + d2 * Zs_check**2 + d1_star * Zs_check + d0_star
86
+ n_fallback = np.sum((np.abs(f_check) > 1e-6) | (Zs_check <= 0))
87
+ print(f"Halley Z* fallbacks to Cardano: {n_fallback}/{N_POINTS}")
88
+
89
+ # ---------- Rust benchmarks ----------
90
+ print("\n" + "=" * 70)
91
+ try:
92
+ from pyrestoolbox.gas.gas import _rust_module, RUST_AVAILABLE
93
+ if not RUST_AVAILABLE:
94
+ raise ImportError("Rust module not available")
95
+ print("Rust module loaded successfully")
96
+
97
+ # For Rust, we need to go through the full BNS path which includes
98
+ # mixing rules. Let's benchmark the full gas_z call instead.
99
+ from pyrestoolbox import gas as gmod
100
+
101
+ # Test parameters
102
+ sg = 0.7
103
+ degf = 200.0
104
+ co2, h2s, n2, h2 = 0.01, 0.0, 0.02, 0.0
105
+ pressures = np.linspace(14.7, 15000, N_POINTS).tolist()
106
+
107
+ # --- Rust BNS path ---
108
+ # Force Rust
109
+ times_rust = []
110
+ for _ in range(N_REPEATS):
111
+ t0 = time.perf_counter()
112
+ z_rust = np.array([_rust_module.bns_zfactor_full(
113
+ float(pi), float(degf), float(sg),
114
+ float(co2), float(h2s), float(n2), float(h2)
115
+ ) for pi in pressures])
116
+ t1 = time.perf_counter()
117
+ times_rust.append(t1 - t0)
118
+
119
+ rust_median = np.median(times_rust) * 1000
120
+ rust_min = np.min(times_rust) * 1000
121
+
122
+ # --- Python BNS path (force by temporarily hiding Rust) ---
123
+ import pyrestoolbox.gas.gas as gas_mod
124
+ orig_rust = gas_mod.RUST_AVAILABLE
125
+ gas_mod.RUST_AVAILABLE = False
126
+
127
+ times_python_full = []
128
+ for _ in range(N_REPEATS):
129
+ t0 = time.perf_counter()
130
+ z_python = gmod.gas_z(pressures, sg, degf, zmethod='BNS',
131
+ co2=co2, h2s=h2s, n2=n2, h2=h2)
132
+ t1 = time.perf_counter()
133
+ times_python_full.append(t1 - t0)
134
+
135
+ gas_mod.RUST_AVAILABLE = orig_rust
136
+
137
+ python_full_median = np.median(times_python_full) * 1000
138
+ python_full_min = np.min(times_python_full) * 1000
139
+
140
+ # --- Rust with Cardano-only path ---
141
+ # Check if Rust module has a cardano-only entry point
142
+ has_cardano_only = hasattr(_rust_module, 'bns_zfactor_full_cardano')
143
+
144
+ print("\n--- Full BNS Z-Factor Pipeline (mixing rules + solve + fugacity) ---")
145
+ print(f"{'Method':<30} {'Median (ms)':>12} {'Min (ms)':>12} {'Speedup':>10}")
146
+ print("-" * 70)
147
+ print(f"{'Python Halley Z*':<30} {python_full_median:>12.2f} {python_full_min:>12.2f} {'baseline':>10}")
148
+ print(f"{'Rust Halley Z* (scalar loop)':<30} {rust_median:>12.2f} {rust_min:>12.2f} {python_full_min/rust_min:>10.1f}x")
149
+
150
+ max_diff_full = np.max(np.abs(np.array(z_rust) - np.array(z_python)))
151
+ print(f"\nMax |Z_rust - Z_python|: {max_diff_full:.2e}")
152
+
153
+ if has_cardano_only:
154
+ times_rust_cardano = []
155
+ for _ in range(N_REPEATS):
156
+ t0 = time.perf_counter()
157
+ z_rc = np.array([_rust_module.bns_zfactor_full_cardano(
158
+ float(pi), float(degf), float(sg),
159
+ float(co2), float(h2s), float(n2), float(h2)
160
+ ) for pi in pressures])
161
+ t1 = time.perf_counter()
162
+ times_rust_cardano.append(t1 - t0)
163
+ rc_median = np.median(times_rust_cardano) * 1000
164
+ rc_min = np.min(times_rust_cardano) * 1000
165
+ print(f"{'Rust Cardano (scalar loop)':<30} {rc_median:>12.2f} {rc_min:>12.2f} {python_full_min/rc_min:>10.1f}x")
166
+
167
+ except ImportError as e:
168
+ print(f"Rust module not available: {e}")
169
+ print("Skipping Rust benchmarks.")
170
+
171
+ # ---------- Iteration count analysis ----------
172
+ print("\n" + "=" * 70)
173
+ print("--- Halley Z* Iteration Count Analysis ---")
174
+
175
+ # Count how many iterations Halley needs
176
+ iter_counts = np.zeros(N_POINTS, dtype=int)
177
+ d2 = 4.0 * B - 1.0
178
+ d1_star = A + 2.0 * B * (B - 2.0)
179
+ d0_star = -2.0 * B * B
180
+
181
+ Zs = np.ones(N_POINTS)
182
+ for it in range(50):
183
+ f = Zs**3 + d2 * Zs**2 + d1_star * Zs + d0_star
184
+ fp = 3.0 * Zs**2 + 2.0 * d2 * Zs + d1_star
185
+ fpp = 6.0 * Zs + 2.0 * d2
186
+ safe_fp = np.where(np.abs(fp) < 1e-30, 1e-30, fp)
187
+ dZ = f / safe_fp
188
+ denom = safe_fp - 0.5 * dZ * fpp
189
+ denom = np.where(np.abs(denom) < 1e-30, 1e-30, denom)
190
+ dZ = f / denom
191
+ Zs -= dZ
192
+
193
+ # Mark converged
194
+ not_converged = np.abs(dZ) >= 1e-12
195
+ iter_counts[not_converged] = it + 1
196
+
197
+ # The ones that converged at iteration X have iter_counts = X
198
+ # The ones still at 0 converged in 1 iteration
199
+ iter_counts[iter_counts == 0] = 1
200
+
201
+ print(f"Mean iterations: {np.mean(iter_counts):.1f}")
202
+ print(f"Max iterations: {np.max(iter_counts)}")
203
+ print(f"Distribution:")
204
+ for i in range(1, min(np.max(iter_counts) + 1, 15)):
205
+ count = np.sum(iter_counts == i)
206
+ if count > 0:
207
+ print(f" {i} iterations: {count:>6} ({100*count/N_POINTS:.1f}%)")