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.
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/.gitignore +3 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/PKG-INFO +3 -2
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/README.rst +2 -1
- pyrestoolbox-3.7.4/benchmark_batch.py +160 -0
- pyrestoolbox-3.7.4/benchmark_viscosity.py +213 -0
- pyrestoolbox-3.7.4/benchmark_zfactor.py +207 -0
- pyrestoolbox-3.7.4/benchmarks/_bench_worker.py +192 -0
- pyrestoolbox-3.7.4/benchmarks/bench_acceleration.py +103 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyproject.toml +1 -1
- pyrestoolbox-3.7.4/pyresToolbox_rust_acceleration_instructions.md +1415 -0
- pyrestoolbox-3.7.4/pyrestoolbox/brine/__init__.py +18 -0
- pyrestoolbox-3.7.4/pyrestoolbox/brine/_lib_dielectric.py +144 -0
- pyrestoolbox-3.7.4/pyrestoolbox/brine/_lib_maoduan_viscosity.py +37 -0
- pyrestoolbox-3.7.4/pyrestoolbox/brine/_lib_pitzer_params.py +130 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/brine/_lib_vle_engine.py +11 -7
- pyrestoolbox-3.7.4/pyrestoolbox/brine/appelo_volumes.py +243 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/brine/brine.py +109 -48
- pyrestoolbox-3.7.4/pyrestoolbox/brine/iapws_viscosity.py +201 -0
- pyrestoolbox-3.7.4/pyrestoolbox/brine/jones_dole_viscosity.py +260 -0
- pyrestoolbox-3.7.4/pyrestoolbox/brine/kestin_nacl_viscosity.py +112 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/brine/pr_vphi.py +43 -17
- pyrestoolbox-3.7.4/pyrestoolbox/brine/viscosity_route.py +168 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/brine/vphi_route.py +34 -7
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/brine.rst +274 -52
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/changelist.rst +23 -1
- pyrestoolbox-3.7.4/pyrestoolbox-mcp-0.1.0.zip +0 -0
- pyrestoolbox-3.7.4/pyrestoolbox-mcp-email.txt +39 -0
- pyrestoolbox-3.7.4/reviews/CONSOLIDATED.md +101 -0
- pyrestoolbox-3.7.4/reviews/README.md +68 -0
- pyrestoolbox-3.7.4/reviews/api_ux/_summary.md +85 -0
- pyrestoolbox-3.7.4/reviews/api_ux/brine.md +74 -0
- pyrestoolbox-3.7.4/reviews/api_ux/dca.md +83 -0
- pyrestoolbox-3.7.4/reviews/api_ux/gas.md +114 -0
- pyrestoolbox-3.7.4/reviews/api_ux/layer.md +63 -0
- pyrestoolbox-3.7.4/reviews/api_ux/matbal.md +86 -0
- pyrestoolbox-3.7.4/reviews/api_ux/nodal.md +109 -0
- pyrestoolbox-3.7.4/reviews/api_ux/oil.md +71 -0
- pyrestoolbox-3.7.4/reviews/api_ux/plyasunov.md +55 -0
- pyrestoolbox-3.7.4/reviews/api_ux/recommend.md +56 -0
- pyrestoolbox-3.7.4/reviews/api_ux/sensitivity.md +62 -0
- pyrestoolbox-3.7.4/reviews/api_ux/simtools.md +79 -0
- pyrestoolbox-3.7.4/reviews/correctness/_summary.md +49 -0
- pyrestoolbox-3.7.4/reviews/correctness/brine.md +45 -0
- pyrestoolbox-3.7.4/reviews/correctness/dca.md +186 -0
- pyrestoolbox-3.7.4/reviews/correctness/gas.md +18 -0
- pyrestoolbox-3.7.4/reviews/correctness/layer.md +106 -0
- pyrestoolbox-3.7.4/reviews/correctness/matbal.md +193 -0
- pyrestoolbox-3.7.4/reviews/correctness/nodal.md +33 -0
- pyrestoolbox-3.7.4/reviews/correctness/oil.md +32 -0
- pyrestoolbox-3.7.4/reviews/correctness/plyasunov.md +140 -0
- pyrestoolbox-3.7.4/reviews/correctness/recommend.md +73 -0
- pyrestoolbox-3.7.4/reviews/correctness/sensitivity.md +31 -0
- pyrestoolbox-3.7.4/reviews/correctness/simtools.md +43 -0
- pyrestoolbox-3.7.4/reviews/review-2026-06-10.md +138 -0
- pyrestoolbox-3.7.4/reviews/tech_debt/_summary.md +95 -0
- pyrestoolbox-3.7.4/reviews/tech_debt/brine.md +68 -0
- pyrestoolbox-3.7.4/reviews/tech_debt/dca.md +108 -0
- pyrestoolbox-3.7.4/reviews/tech_debt/gas.md +134 -0
- pyrestoolbox-3.7.4/reviews/tech_debt/layer.md +95 -0
- pyrestoolbox-3.7.4/reviews/tech_debt/matbal.md +78 -0
- pyrestoolbox-3.7.4/reviews/tech_debt/nodal.md +95 -0
- pyrestoolbox-3.7.4/reviews/tech_debt/oil.md +80 -0
- pyrestoolbox-3.7.4/reviews/tech_debt/plyasunov.md +103 -0
- pyrestoolbox-3.7.4/reviews/tech_debt/recommend.md +81 -0
- pyrestoolbox-3.7.4/reviews/tech_debt/sensitivity.md +72 -0
- pyrestoolbox-3.7.4/reviews/tech_debt/simtools.md +112 -0
- pyrestoolbox-3.7.4/zstar_residual_plots.py +302 -0
- pyrestoolbox-3.7.4/zstar_vs_z_comparison.zip +0 -0
- pyrestoolbox-3.7.2/pyrestoolbox/brine/__init__.py +0 -5
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/.github/workflows/build-wheels.yml +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/.github/workflows/tests.yml +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/Cargo.lock +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/Cargo.toml +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/LICENSE +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/MANIFEST.in +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/ResToolbox/privacy_policy.md +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/benchmark_rust_vs_python.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/build_pure_python.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/mcp/DEPLOYMENT.md +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/mcp/Dockerfile +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/mcp/README.md +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/mcp/pyproject.toml +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/mcp/pyrestoolbox_mcp/__init__.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/mcp/pyrestoolbox_mcp/serialize.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/mcp/pyrestoolbox_mcp/server.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/mcp/tests/test_server.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/__init__.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/_accelerator.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/brine/_lib_salting_library.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/classes/__init__.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/classes/classes.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/constants/__init__.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/constants/constants.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/dca/__init__.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/dca/dca.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/dca.rst +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/examples.ipynb +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/gas.rst +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/img/bot.png +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/img/bot_PVTO.png +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/img/bot_img.png +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/img/dry_gas.png +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/img/grid_sat_df.png +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/img/influence.png +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/img/properties_df.png +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/img/sgof.png +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/img/swof.png +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/index.rst +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/layer.rst +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/library.rst +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/matbal.rst +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/nodal.rst +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/nodal_examples.ipynb +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/nodal_hydrate_demo.ipynb +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/oil.rst +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/plyasunov.rst +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/recommend.rst +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/sensitivity.rst +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/docs/simtools.rst +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/gas/__init__.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/gas/_hydrate.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/gas/gas.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/layer/__init__.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/layer/layer.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/library/__init__.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/library/component_library.xlsx +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/library/library.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/matbal/__init__.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/matbal/matbal.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/nodal/__init__.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/nodal/nodal.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/oil/__init__.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/oil/_compressibility.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/oil/_constants.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/oil/_correlations.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/oil/_density.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/oil/_harmonize.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/oil/_pvt_class.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/oil/_rate.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/oil/_separator.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/oil/_tables.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/oil/_utils.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/plyasunov/__init__.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/plyasunov/iapws_if97.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/plyasunov/plyasunov_model.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/plyasunov/water_properties.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/recommend/__init__.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/recommend/recommend.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/sensitivity/__init__.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/sensitivity/sensitivity.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/shared_fns/__init__.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/shared_fns/shared_fns.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/simtools/__init__.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/simtools/_aquifer.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/simtools/_decks.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/simtools/_pvt_tables.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/simtools/_relperm.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/simtools/_vfp.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/simtools/simtools.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/validate/__init__.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/pyrestoolbox/validate/validate.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/setup.cfg +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/setup.py +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/bessel.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/critical_properties/mod.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/dca/hyperbolic.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/dca/mod.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/dca/ransac.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/gas_viscosity/mod.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/gwr.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/lib.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/matbal/mod.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/matbal/objective.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/oil/density.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/oil/mod.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/pseudopressure.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/spycher_pruess/mod.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/spycher_pruess/solubility.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vle/alpha.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vle/bip.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vle/components.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vle/eos.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vle/flash.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vle/fugacity.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vle/k_init.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vle/mod.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vle/rachford_rice.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vlp/constants.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vlp/friction.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vlp/holdup_bb.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vlp/holdup_gray.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vlp/holdup_wg.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vlp/ift.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vlp/march.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vlp/mod.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vlp/pvt_helpers.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/vlp/static_column.rs +0 -0
- {pyrestoolbox-3.7.2 → pyrestoolbox-3.7.4}/src/zfactor/mod.rs +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyrestoolbox
|
|
3
|
-
Version: 3.7.
|
|
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}%)")
|