pyrestoolbox 3.1.0__tar.gz → 3.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/.github/workflows/build-wheels.yml +8 -7
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/PKG-INFO +8 -8
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/README.rst +6 -6
- pyrestoolbox-3.1.2/benchmark_rust_vs_python.py +220 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyproject.toml +2 -2
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/docs/simtools.rst +2 -2
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/gas/gas.py +50 -22
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/simtools/simtools.py +23 -18
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/setup.cfg +1 -1
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/setup.py +1 -1
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/gwr.rs +1 -1
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/zfactor/mod.rs +42 -32
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/.gitignore +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/Cargo.lock +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/Cargo.toml +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/LICENSE +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/MANIFEST.in +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/ResToolbox/privacy_policy.md +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/build_pure_python.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/__init__.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/_accelerator.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/brine/__init__.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/brine/_lib_salting_library.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/brine/_lib_vle_engine.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/brine/brine.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/classes/__init__.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/classes/classes.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/constants/__init__.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/constants/constants.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/dca/__init__.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/dca/dca.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/docs/brine.rst +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/docs/changelist.rst +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/docs/dca.rst +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/docs/examples.ipynb +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/docs/gas.rst +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/docs/img/bot.png +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/docs/img/bot_PVTO.png +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/docs/img/bot_img.png +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/docs/img/dry_gas.png +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/docs/img/grid_sat_df.png +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/docs/img/influence.png +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/docs/img/properties_df.png +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/docs/img/sgof.png +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/docs/img/swof.png +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/docs/layer.rst +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/docs/library.rst +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/docs/matbal.rst +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/docs/nodal.rst +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/docs/nodal_examples.ipynb +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/docs/nodal_hydrate_demo.ipynb +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/docs/oil.rst +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/docs/recommend.rst +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/docs/sensitivity.rst +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/gas/__init__.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/layer/__init__.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/layer/layer.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/library/__init__.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/library/component_library.xlsx +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/library/library.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/matbal/__init__.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/matbal/matbal.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/nodal/__init__.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/nodal/nodal.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/oil/__init__.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/oil/oil.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/plyasunov/__init__.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/plyasunov/iapws_if97.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/plyasunov/plyasunov_model.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/plyasunov/water_properties.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/recommend/__init__.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/recommend/recommend.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/sensitivity/__init__.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/sensitivity/sensitivity.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/shared_fns/__init__.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/shared_fns/shared_fns.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/simtools/__init__.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/validate/__init__.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/pyrestoolbox/validate/validate.py +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/bessel.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/critical_properties/mod.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/dca/hyperbolic.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/dca/mod.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/dca/ransac.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/gas_viscosity/mod.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/lib.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/matbal/mod.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/matbal/objective.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/oil/density.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/oil/mod.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/pseudopressure.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/spycher_pruess/mod.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/spycher_pruess/solubility.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/vle/alpha.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/vle/bip.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/vle/components.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/vle/eos.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/vle/flash.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/vle/fugacity.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/vle/k_init.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/vle/mod.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/vle/rachford_rice.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/vlp/friction.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/vlp/holdup_bb.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/vlp/holdup_gray.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/vlp/holdup_hb.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/vlp/holdup_wg.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/vlp/ift.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/vlp/mod.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/vlp/pvt_helpers.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/vlp/segment_gas.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/vlp/segment_oil.rs +0 -0
- {pyrestoolbox-3.1.0 → pyrestoolbox-3.1.2}/src/vlp/static_column.rs +0 -0
|
@@ -40,12 +40,12 @@ jobs:
|
|
|
40
40
|
shell: bash
|
|
41
41
|
|
|
42
42
|
- name: Build wheels via cibuildwheel
|
|
43
|
-
uses: pypa/cibuildwheel@v2.
|
|
43
|
+
uses: pypa/cibuildwheel@v2.23
|
|
44
44
|
env:
|
|
45
45
|
# Use maturin as the build frontend
|
|
46
46
|
CIBW_BUILD_FRONTEND: "build"
|
|
47
|
-
# Build for CPython 3.
|
|
48
|
-
CIBW_BUILD: "cp310-* cp311-* cp312-* cp313-*"
|
|
47
|
+
# Build for CPython 3.8-3.13 (last 5+ years of Python releases)
|
|
48
|
+
CIBW_BUILD: "cp38-* cp39-* cp310-* cp311-* cp312-* cp313-*"
|
|
49
49
|
# Skip 32-bit and musl (uncommon for scientific Python)
|
|
50
50
|
CIBW_SKIP: "*-win32 *-manylinux_i686 *-musllinux*"
|
|
51
51
|
# maturin needs Rust in the build container
|
|
@@ -74,8 +74,9 @@ jobs:
|
|
|
74
74
|
CIBW_TEST_COMMAND: >
|
|
75
75
|
python -c "from pyrestoolbox._accelerator import get_status; s = get_status(); print(s); assert s['rust_available'], f'Rust not loaded: {s}'"
|
|
76
76
|
CIBW_TEST_REQUIRES: "numpy"
|
|
77
|
-
# Skip tests on Linux (scipy needs OpenBLAS absent in manylinux)
|
|
78
|
-
|
|
77
|
+
# Skip tests on Linux (scipy needs OpenBLAS absent in manylinux), macOS (MPFR symbol issue),
|
|
78
|
+
# and cp38/cp39 (dependency ilt-inversion may not have matching wheels on PyPI yet)
|
|
79
|
+
CIBW_TEST_SKIP: "*-manylinux* *-macosx* cp38-* cp39-*"
|
|
79
80
|
|
|
80
81
|
- uses: actions/upload-artifact@v4
|
|
81
82
|
with:
|
|
@@ -98,10 +99,10 @@ jobs:
|
|
|
98
99
|
uses: dtolnay/rust-toolchain@stable
|
|
99
100
|
|
|
100
101
|
- name: Build wheels via cibuildwheel
|
|
101
|
-
uses: pypa/cibuildwheel@v2.
|
|
102
|
+
uses: pypa/cibuildwheel@v2.23
|
|
102
103
|
env:
|
|
103
104
|
CIBW_BUILD_FRONTEND: "build"
|
|
104
|
-
CIBW_BUILD: "cp310-* cp311-* cp312-* cp313-*"
|
|
105
|
+
CIBW_BUILD: "cp38-* cp39-* cp310-* cp311-* cp312-* cp313-*"
|
|
105
106
|
CIBW_ARCHS_MACOS: "x86_64"
|
|
106
107
|
CIBW_TEST_COMMAND: >
|
|
107
108
|
python -c "from pyrestoolbox._accelerator import get_status; s = get_status(); print(s); assert s['rust_available'], f'Rust not loaded: {s}'"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyrestoolbox
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.2
|
|
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+)
|
|
@@ -20,7 +20,7 @@ Keywords: restoolbox,petroleum,reservoir
|
|
|
20
20
|
Home-Page: https://github.com/mwburgoyne/pyResToolbox
|
|
21
21
|
Author-email: "Mark W. Burgoyne" <mark.w.burgoyne@gmail.com>
|
|
22
22
|
License: GPL-3.0-or-later
|
|
23
|
-
Requires-Python: >=3.
|
|
23
|
+
Requires-Python: >=3.8
|
|
24
24
|
Description-Content-Type: text/x-rst; charset=UTF-8
|
|
25
25
|
Project-URL: Homepage, https://github.com/mwburgoyne/pyResToolbox
|
|
26
26
|
|
|
@@ -203,7 +203,7 @@ A set of Gas-Oil relative permeability curves with the LET method
|
|
|
203
203
|
>>> plt.grid('both')
|
|
204
204
|
>>> plt.plot()
|
|
205
205
|
|
|
206
|
-
.. image:: https://
|
|
206
|
+
.. image:: https://raw.githubusercontent.com/mwburgoyne/pyResToolbox/main/pyrestoolbox/docs/img/sgof.png
|
|
207
207
|
:alt: SGOF Relative Permeability Curves
|
|
208
208
|
|
|
209
209
|
Or a set of Water-Oil relative permeability curves with the Corey method
|
|
@@ -220,7 +220,7 @@ Or a set of Water-Oil relative permeability curves with the Corey method
|
|
|
220
220
|
>>> plt.grid('both')
|
|
221
221
|
>>> plt.plot()
|
|
222
222
|
|
|
223
|
-
.. image:: https://
|
|
223
|
+
.. image:: https://raw.githubusercontent.com/mwburgoyne/pyResToolbox/main/pyrestoolbox/docs/img/swof.png
|
|
224
224
|
:alt: SWOF Relative Permeability Curves
|
|
225
225
|
|
|
226
226
|
A set of dimensionless pressures for the constant terminal rate Van Everdingin & Hurst aquifer, along with an AQUTAB.INC export for use in ECLIPSE.
|
|
@@ -242,7 +242,7 @@ A set of dimensionless pressures for the constant terminal rate Van Everdingin &
|
|
|
242
242
|
>>> plt.title('Constant Terminal Rate Solution')
|
|
243
243
|
>>> plt.show()
|
|
244
244
|
|
|
245
|
-
.. image:: https://
|
|
245
|
+
.. image:: https://raw.githubusercontent.com/mwburgoyne/pyResToolbox/main/pyrestoolbox/docs/img/influence.png
|
|
246
246
|
:alt: Constant Terminal Rate influence tables
|
|
247
247
|
|
|
248
248
|
Or creating black oil table information for oil
|
|
@@ -295,7 +295,7 @@ Or creating black oil table information for oil
|
|
|
295
295
|
Reservoir Water Compressibility: 2.930237693350768e-06 1/psi
|
|
296
296
|
Reservoir Water Viscosity: 0.3640686136171888 cP
|
|
297
297
|
|
|
298
|
-
.. image:: https://
|
|
298
|
+
.. image:: https://raw.githubusercontent.com/mwburgoyne/pyResToolbox/main/pyrestoolbox/docs/img/bot.png
|
|
299
299
|
:alt: Black Oil Properties
|
|
300
300
|
|
|
301
301
|
And gas
|
|
@@ -310,7 +310,7 @@ And gas
|
|
|
310
310
|
>>> ...
|
|
311
311
|
>>> plt.show()
|
|
312
312
|
|
|
313
|
-
.. image:: https://
|
|
313
|
+
.. image:: https://raw.githubusercontent.com/mwburgoyne/pyResToolbox/main/pyrestoolbox/docs/img/dry_gas.png
|
|
314
314
|
:alt: Dry Gas Properties
|
|
315
315
|
|
|
316
316
|
With ability to generate Live Oil PVTO style table data as well
|
|
@@ -384,7 +384,7 @@ With ability to generate Live Oil PVTO style table data as well
|
|
|
384
384
|
Reservoir Water Compressibility: 2.930237693350768e-06 1/psi
|
|
385
385
|
Reservoir Water Viscosity: 0.3640686136171888 cP
|
|
386
386
|
|
|
387
|
-
.. image:: https://
|
|
387
|
+
.. image:: https://raw.githubusercontent.com/mwburgoyne/pyResToolbox/main/pyrestoolbox/docs/img/bot_PVTO.png
|
|
388
388
|
:alt: Live Oil Properties
|
|
389
389
|
|
|
390
390
|
|
|
@@ -177,7 +177,7 @@ A set of Gas-Oil relative permeability curves with the LET method
|
|
|
177
177
|
>>> plt.grid('both')
|
|
178
178
|
>>> plt.plot()
|
|
179
179
|
|
|
180
|
-
.. image:: https://
|
|
180
|
+
.. image:: https://raw.githubusercontent.com/mwburgoyne/pyResToolbox/main/pyrestoolbox/docs/img/sgof.png
|
|
181
181
|
:alt: SGOF Relative Permeability Curves
|
|
182
182
|
|
|
183
183
|
Or a set of Water-Oil relative permeability curves with the Corey method
|
|
@@ -194,7 +194,7 @@ Or a set of Water-Oil relative permeability curves with the Corey method
|
|
|
194
194
|
>>> plt.grid('both')
|
|
195
195
|
>>> plt.plot()
|
|
196
196
|
|
|
197
|
-
.. image:: https://
|
|
197
|
+
.. image:: https://raw.githubusercontent.com/mwburgoyne/pyResToolbox/main/pyrestoolbox/docs/img/swof.png
|
|
198
198
|
:alt: SWOF Relative Permeability Curves
|
|
199
199
|
|
|
200
200
|
A set of dimensionless pressures for the constant terminal rate Van Everdingin & Hurst aquifer, along with an AQUTAB.INC export for use in ECLIPSE.
|
|
@@ -216,7 +216,7 @@ A set of dimensionless pressures for the constant terminal rate Van Everdingin &
|
|
|
216
216
|
>>> plt.title('Constant Terminal Rate Solution')
|
|
217
217
|
>>> plt.show()
|
|
218
218
|
|
|
219
|
-
.. image:: https://
|
|
219
|
+
.. image:: https://raw.githubusercontent.com/mwburgoyne/pyResToolbox/main/pyrestoolbox/docs/img/influence.png
|
|
220
220
|
:alt: Constant Terminal Rate influence tables
|
|
221
221
|
|
|
222
222
|
Or creating black oil table information for oil
|
|
@@ -269,7 +269,7 @@ Or creating black oil table information for oil
|
|
|
269
269
|
Reservoir Water Compressibility: 2.930237693350768e-06 1/psi
|
|
270
270
|
Reservoir Water Viscosity: 0.3640686136171888 cP
|
|
271
271
|
|
|
272
|
-
.. image:: https://
|
|
272
|
+
.. image:: https://raw.githubusercontent.com/mwburgoyne/pyResToolbox/main/pyrestoolbox/docs/img/bot.png
|
|
273
273
|
:alt: Black Oil Properties
|
|
274
274
|
|
|
275
275
|
And gas
|
|
@@ -284,7 +284,7 @@ And gas
|
|
|
284
284
|
>>> ...
|
|
285
285
|
>>> plt.show()
|
|
286
286
|
|
|
287
|
-
.. image:: https://
|
|
287
|
+
.. image:: https://raw.githubusercontent.com/mwburgoyne/pyResToolbox/main/pyrestoolbox/docs/img/dry_gas.png
|
|
288
288
|
:alt: Dry Gas Properties
|
|
289
289
|
|
|
290
290
|
With ability to generate Live Oil PVTO style table data as well
|
|
@@ -358,7 +358,7 @@ With ability to generate Live Oil PVTO style table data as well
|
|
|
358
358
|
Reservoir Water Compressibility: 2.930237693350768e-06 1/psi
|
|
359
359
|
Reservoir Water Viscosity: 0.3640686136171888 cP
|
|
360
360
|
|
|
361
|
-
.. image:: https://
|
|
361
|
+
.. image:: https://raw.githubusercontent.com/mwburgoyne/pyResToolbox/main/pyrestoolbox/docs/img/bot_PVTO.png
|
|
362
362
|
:alt: Live Oil Properties
|
|
363
363
|
|
|
364
364
|
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Benchmark: Rust-accelerated vs pure-Python pyResToolbox functions.
|
|
4
|
+
|
|
5
|
+
Runs 8 test cases with BNS method, comparing results, accuracy and speedup.
|
|
6
|
+
Pure-Python runs in a subprocess with PYRESTOOLBOX_NO_RUST=1 (set before
|
|
7
|
+
import since the accelerator probes at import time).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
import time
|
|
15
|
+
|
|
16
|
+
import numpy as np
|
|
17
|
+
from tabulate import tabulate
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# ── Helper: run a test case in a subprocess ──────────────────────────────
|
|
21
|
+
def _run_in_subprocess(test_code: str, env_override: dict | None = None) -> dict:
|
|
22
|
+
"""Execute test_code in a fresh Python process and return JSON result."""
|
|
23
|
+
env = os.environ.copy()
|
|
24
|
+
if env_override:
|
|
25
|
+
env.update(env_override)
|
|
26
|
+
|
|
27
|
+
wrapper = f"""
|
|
28
|
+
import json, time, sys, os
|
|
29
|
+
sys.path.insert(0, {os.getcwd()!r})
|
|
30
|
+
{test_code}
|
|
31
|
+
"""
|
|
32
|
+
result = subprocess.run(
|
|
33
|
+
[sys.executable, "-c", wrapper],
|
|
34
|
+
capture_output=True, text=True, env=env, timeout=300
|
|
35
|
+
)
|
|
36
|
+
if result.returncode != 0:
|
|
37
|
+
return {"error": result.stderr.strip(), "elapsed": 0, "value": None}
|
|
38
|
+
try:
|
|
39
|
+
return json.loads(result.stdout.strip().split("\n")[-1])
|
|
40
|
+
except (json.JSONDecodeError, IndexError):
|
|
41
|
+
return {"error": f"Bad output: {result.stdout[:200]}", "elapsed": 0, "value": None}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ── Test case definitions ────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
PREAMBLE = """
|
|
47
|
+
from pyrestoolbox import gas, oil
|
|
48
|
+
from pyrestoolbox.nodal import Completion, fbhp
|
|
49
|
+
from pyrestoolbox import simtools
|
|
50
|
+
from pyrestoolbox.classes import z_method, c_method
|
|
51
|
+
import numpy as np
|
|
52
|
+
import time
|
|
53
|
+
import json
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
# (label, function_call_string, test_code)
|
|
57
|
+
TESTS = [
|
|
58
|
+
(
|
|
59
|
+
"(a) Single Z-factor",
|
|
60
|
+
"gas.gas_z(p=3000, sg=0.7, degf=200, zmethod=z_method.BNS, cmethod=c_method.BNS)",
|
|
61
|
+
"""
|
|
62
|
+
z = gas.gas_z(p=3000, sg=0.7, degf=200, zmethod=z_method.BNS, cmethod=c_method.BNS)
|
|
63
|
+
print(json.dumps({"elapsed": _elapsed, "value": float(z)}))
|
|
64
|
+
""",
|
|
65
|
+
),
|
|
66
|
+
(
|
|
67
|
+
"(b) 1000x Z-factors",
|
|
68
|
+
"[gas.gas_z(p=p, sg=0.7, degf=200, zmethod=z_method.BNS, cmethod=c_method.BNS)\n for p in np.linspace(500, 5000, 1000)]",
|
|
69
|
+
"""
|
|
70
|
+
pressures = np.linspace(500, 5000, 1000).tolist()
|
|
71
|
+
results = [gas.gas_z(p=p, sg=0.7, degf=200, zmethod=z_method.BNS, cmethod=c_method.BNS) for p in pressures]
|
|
72
|
+
print(json.dumps({"elapsed": _elapsed, "value": float(np.mean(results))}))
|
|
73
|
+
""",
|
|
74
|
+
),
|
|
75
|
+
(
|
|
76
|
+
"(c) Single viscosity",
|
|
77
|
+
"gas.gas_ug(p=3000, sg=0.7, degf=200, zmethod=z_method.BNS, cmethod=c_method.BNS)",
|
|
78
|
+
"""
|
|
79
|
+
ug = gas.gas_ug(p=3000, sg=0.7, degf=200, zmethod=z_method.BNS, cmethod=c_method.BNS)
|
|
80
|
+
print(json.dumps({"elapsed": _elapsed, "value": float(ug)}))
|
|
81
|
+
""",
|
|
82
|
+
),
|
|
83
|
+
(
|
|
84
|
+
"(d) 1000x viscosities",
|
|
85
|
+
"[gas.gas_ug(p=p, sg=0.7, degf=200, zmethod=z_method.BNS, cmethod=c_method.BNS)\n for p in np.linspace(500, 5000, 1000)]",
|
|
86
|
+
"""
|
|
87
|
+
pressures = np.linspace(500, 5000, 1000).tolist()
|
|
88
|
+
results = [gas.gas_ug(p=p, sg=0.7, degf=200, zmethod=z_method.BNS, cmethod=c_method.BNS) for p in pressures]
|
|
89
|
+
print(json.dumps({"elapsed": _elapsed, "value": float(np.mean(results))}))
|
|
90
|
+
""",
|
|
91
|
+
),
|
|
92
|
+
(
|
|
93
|
+
"(e) Pseudopressure",
|
|
94
|
+
"gas.gas_dmp(p1=500, p2=4000, degf=200, sg=0.7, zmethod=z_method.BNS, cmethod=c_method.BNS)",
|
|
95
|
+
"""
|
|
96
|
+
pp = gas.gas_dmp(p1=500, p2=4000, degf=200, sg=0.7, zmethod=z_method.BNS, cmethod=c_method.BNS)
|
|
97
|
+
print(json.dumps({"elapsed": _elapsed, "value": float(pp)}))
|
|
98
|
+
""",
|
|
99
|
+
),
|
|
100
|
+
(
|
|
101
|
+
"(f) Gas outflow",
|
|
102
|
+
"fbhp(thp=500, completion=Completion(2.441, 10000, 100, 200),\n vlpmethod='WG', well_type='gas', qg_mmscfd=5, cgr=10,\n qw_bwpd=10, api=45, gsg=0.65)",
|
|
103
|
+
"""
|
|
104
|
+
c = Completion(tid=2.441, length=10000, tht=100, bht=200)
|
|
105
|
+
bhp = fbhp(thp=500, completion=c, vlpmethod='WG', well_type='gas',
|
|
106
|
+
qg_mmscfd=5.0, cgr=10, qw_bwpd=10, api=45, gsg=0.65, wsg=1.07)
|
|
107
|
+
print(json.dumps({"elapsed": _elapsed, "value": float(bhp)}))
|
|
108
|
+
""",
|
|
109
|
+
),
|
|
110
|
+
(
|
|
111
|
+
"(g) Oil outflow",
|
|
112
|
+
"fbhp(thp=200, completion=Completion(2.441, 8000, 100, 180),\n vlpmethod='HB', well_type='oil', qt_stbpd=2000, gor=800,\n wc=0.3, pb=2500, rsb=500, api=35, sgsp=0.65, gsg=0.65)",
|
|
113
|
+
"""
|
|
114
|
+
c = Completion(tid=2.441, length=8000, tht=100, bht=180)
|
|
115
|
+
bhp = fbhp(thp=200, completion=c, vlpmethod='HB', well_type='oil',
|
|
116
|
+
qt_stbpd=2000, gor=800, wc=0.3, pb=2500, rsb=500,
|
|
117
|
+
api=35, sgsp=0.65, gsg=0.65)
|
|
118
|
+
print(json.dumps({"elapsed": _elapsed, "value": float(bhp)}))
|
|
119
|
+
""",
|
|
120
|
+
),
|
|
121
|
+
(
|
|
122
|
+
"(h) CT Influence table",
|
|
123
|
+
"simtools.influence_tables(ReDs=[2, 5, 10],\n min_td=0.01, max_td=10, n_incr=10, M=7)",
|
|
124
|
+
"""
|
|
125
|
+
tds, pds = simtools.influence_tables(ReDs=[2, 5, 10], min_td=0.01, max_td=10, n_incr=10, M=7, export=False)
|
|
126
|
+
val = sum(sum(pd) for pd in pds)
|
|
127
|
+
print(json.dumps({"elapsed": _elapsed, "value": float(val)}))
|
|
128
|
+
""",
|
|
129
|
+
),
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _wrap_timed(test_code: str) -> str:
|
|
134
|
+
"""Wrap test code with timing. Replaces _elapsed placeholder."""
|
|
135
|
+
lines = test_code.strip().split("\n")
|
|
136
|
+
print_line = lines[-1]
|
|
137
|
+
setup_lines = "\n".join(lines[:-1])
|
|
138
|
+
return f"""
|
|
139
|
+
{PREAMBLE}
|
|
140
|
+
t0 = time.perf_counter()
|
|
141
|
+
{setup_lines}
|
|
142
|
+
_elapsed = time.perf_counter() - t0
|
|
143
|
+
{print_line}
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def main():
|
|
148
|
+
print("=" * 78)
|
|
149
|
+
print(" pyResToolbox Benchmark: Rust-accelerated vs Pure Python (BNS)")
|
|
150
|
+
print("=" * 78)
|
|
151
|
+
print()
|
|
152
|
+
|
|
153
|
+
# Confirm Rust status
|
|
154
|
+
rust_check = _run_in_subprocess(
|
|
155
|
+
PREAMBLE + """
|
|
156
|
+
from pyrestoolbox._accelerator import get_status
|
|
157
|
+
s = get_status()
|
|
158
|
+
print(json.dumps({"elapsed": 0, "value": 1 if s['rust_available'] else 0}))
|
|
159
|
+
"""
|
|
160
|
+
)
|
|
161
|
+
rust_avail = rust_check.get("value", 0) == 1
|
|
162
|
+
print(f" Rust acceleration available: {rust_avail}")
|
|
163
|
+
if not rust_avail:
|
|
164
|
+
print(" WARNING: Rust not available - benchmark will show Python vs Python")
|
|
165
|
+
print()
|
|
166
|
+
|
|
167
|
+
rows = []
|
|
168
|
+
for label, call_str, test_code in TESTS:
|
|
169
|
+
full_code = _wrap_timed(test_code)
|
|
170
|
+
|
|
171
|
+
# Run with Rust (default)
|
|
172
|
+
print(f" Running {label} (Rust)...", end="", flush=True)
|
|
173
|
+
r_rust = _run_in_subprocess(full_code)
|
|
174
|
+
if r_rust.get("error"):
|
|
175
|
+
print(f" ERROR: {r_rust['error'][:80]}")
|
|
176
|
+
rows.append([label, call_str, "ERROR", "", "", "", "", ""])
|
|
177
|
+
continue
|
|
178
|
+
print(f" {r_rust['elapsed']:.4f}s", flush=True)
|
|
179
|
+
|
|
180
|
+
# Run pure Python
|
|
181
|
+
print(f" Running {label} (Python)...", end="", flush=True)
|
|
182
|
+
r_py = _run_in_subprocess(full_code, env_override={"PYRESTOOLBOX_NO_RUST": "1"})
|
|
183
|
+
if r_py.get("error"):
|
|
184
|
+
print(f" ERROR: {r_py['error'][:80]}")
|
|
185
|
+
rows.append([label, call_str, f"{r_rust['elapsed']:.4f}", "ERROR",
|
|
186
|
+
f"{r_rust['value']:.6g}", "", "", ""])
|
|
187
|
+
continue
|
|
188
|
+
print(f" {r_py['elapsed']:.4f}s", flush=True)
|
|
189
|
+
|
|
190
|
+
speedup = r_py["elapsed"] / r_rust["elapsed"] if r_rust["elapsed"] > 1e-9 else float("inf")
|
|
191
|
+
v_rust, v_py = r_rust["value"], r_py["value"]
|
|
192
|
+
if v_rust is not None and v_py is not None and abs(v_py) > 1e-30:
|
|
193
|
+
rel_err = abs(v_rust - v_py) / abs(v_py)
|
|
194
|
+
accuracy = "Exact" if rel_err < 1e-12 else f"{rel_err:.2e}"
|
|
195
|
+
else:
|
|
196
|
+
accuracy = "N/A"
|
|
197
|
+
|
|
198
|
+
rows.append([
|
|
199
|
+
label,
|
|
200
|
+
call_str,
|
|
201
|
+
f"{r_rust['elapsed']:.4f}",
|
|
202
|
+
f"{r_py['elapsed']:.4f}",
|
|
203
|
+
f"{v_rust:.6g}",
|
|
204
|
+
f"{v_py:.6g}",
|
|
205
|
+
f"{speedup:.1f}x",
|
|
206
|
+
accuracy,
|
|
207
|
+
])
|
|
208
|
+
|
|
209
|
+
print()
|
|
210
|
+
print(tabulate(
|
|
211
|
+
rows,
|
|
212
|
+
headers=["Test", "Function Call", "Rust (s)", "Python (s)",
|
|
213
|
+
"Rust Result", "Python Result", "Speedup", "Rel Error"],
|
|
214
|
+
tablefmt="pretty",
|
|
215
|
+
colalign=("left", "left", "right", "right", "right", "right", "right", "center"),
|
|
216
|
+
))
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
if __name__ == "__main__":
|
|
220
|
+
main()
|
|
@@ -4,12 +4,12 @@ build-backend = "maturin"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pyrestoolbox"
|
|
7
|
-
version = "3.1.
|
|
7
|
+
version = "3.1.2"
|
|
8
8
|
description = "pyResToolbox - A collection of Reservoir Engineering Utilities"
|
|
9
9
|
license = {text = "GPL-3.0-or-later"}
|
|
10
10
|
authors = [{name = "Mark W. Burgoyne", email = "mark.w.burgoyne@gmail.com"}]
|
|
11
11
|
keywords = ["restoolbox", "petroleum", "reservoir"]
|
|
12
|
-
requires-python = ">=3.
|
|
12
|
+
requires-python = ">=3.8"
|
|
13
13
|
classifiers = [
|
|
14
14
|
"Programming Language :: Python :: 3",
|
|
15
15
|
"Programming Language :: Rust",
|
|
@@ -146,7 +146,7 @@ pyrestoolbox.simtools.influence_tables
|
|
|
146
146
|
|
|
147
147
|
.. code-block:: python
|
|
148
148
|
|
|
149
|
-
influence_tables(ReDs, min_td = 0.01, max_td = 200, n_incr = 20, M =
|
|
149
|
+
influence_tables(ReDs, min_td = 0.01, max_td = 200, n_incr = 20, M = 7, export = False)-> tuple
|
|
150
150
|
|
|
151
151
|
Solves Van Everdingin & Hurst Constant Terminal Rate solution via inverse Laplace transform and optionally writes out ECLIPSE styled AQUTAB include file.
|
|
152
152
|
|
|
@@ -175,7 +175,7 @@ Returns a tuple of;
|
|
|
175
175
|
- Number of log transformed increments to split dimensionless time into. Default = 20
|
|
176
176
|
* - M
|
|
177
177
|
- int
|
|
178
|
-
- Laplace
|
|
178
|
+
- Laplace inversion accuracy. Higher = more accurate, but more time. Generally 6-12 is good range. Default = 7
|
|
179
179
|
* - export
|
|
180
180
|
- bool
|
|
181
181
|
- Boolean value that controls whether an include file with 'INFLUENCE.INC' name is created. Default: False
|
|
@@ -536,32 +536,60 @@ def _cardano_cubic(c2, c1, c0, flag=0):
|
|
|
536
536
|
return max(Zs)
|
|
537
537
|
return Zs
|
|
538
538
|
|
|
539
|
-
def _halley_cubic_vec(c2, c1, c0, max_iter=50, tol=1e-12):
|
|
539
|
+
def _halley_cubic_vec(c2, c1, c0, A=None, B=None, max_iter=50, tol=1e-12):
|
|
540
540
|
"""Vectorized Halley solver: solve Z^3+c2*Z^2+c1*Z+c0=0 for max root (vapor Z).
|
|
541
541
|
c2, c1, c0 are 1D arrays of length N. Returns 1D array of Z values.
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
542
|
+
When A and B are provided, solves in Z* = Z - B space (per Aaron Zick's
|
|
543
|
+
reformulation) where all physical roots lie in (0, 1], giving more
|
|
544
|
+
robust convergence. Falls back to _cardano_cubic for any bad elements."""
|
|
545
|
+
|
|
546
|
+
if A is not None and B is not None:
|
|
547
|
+
# Z* = Z - B reformulation: Z*³ + d2·Z*² + d1·Z* + d0 = 0
|
|
548
|
+
# f*(0) = -2B² < 0, f*(1) = A >= 0, so largest root in (0, 1]
|
|
549
|
+
d2 = 4.0 * B - 1.0
|
|
550
|
+
d1 = A + 2.0 * B * (B - 2.0)
|
|
551
|
+
d0 = -2.0 * B * B
|
|
552
|
+
|
|
553
|
+
# Start at Z* = 1 (above largest root since f*(1) = A >= 0)
|
|
554
|
+
Zs = np.ones_like(c2)
|
|
555
|
+
|
|
556
|
+
for _ in range(max_iter):
|
|
557
|
+
f = Zs**3 + d2 * Zs**2 + d1 * Zs + d0
|
|
558
|
+
fp = 3.0 * Zs**2 + 2.0 * d2 * Zs + d1
|
|
559
|
+
fpp = 6.0 * Zs + 2.0 * d2
|
|
560
|
+
safe_fp = np.where(np.abs(fp) < 1e-30, 1e-30, fp)
|
|
561
|
+
dZ = f / safe_fp
|
|
562
|
+
denom = safe_fp - 0.5 * dZ * fpp
|
|
563
|
+
denom = np.where(np.abs(denom) < 1e-30, 1e-30, denom)
|
|
564
|
+
dZ = f / denom
|
|
565
|
+
Zs -= dZ
|
|
566
|
+
if np.max(np.abs(dZ)) < tol:
|
|
567
|
+
break
|
|
568
|
+
|
|
569
|
+
# Convert Z* -> Z; fallback to Cardano in Z space for bad elements
|
|
570
|
+
Z = Zs + B
|
|
571
|
+
f = Zs**3 + d2 * Zs**2 + d1 * Zs + d0
|
|
572
|
+
bad = (np.abs(f) > 1e-6) | (Zs <= 0.0)
|
|
573
|
+
else:
|
|
574
|
+
# Legacy path: solve directly in Z space with Cauchy upper bound
|
|
575
|
+
Z = 1.0 + np.maximum(np.abs(c2), np.maximum(np.abs(c1), np.abs(c0)))
|
|
576
|
+
|
|
577
|
+
for _ in range(max_iter):
|
|
578
|
+
f = Z**3 + c2 * Z**2 + c1 * Z + c0
|
|
579
|
+
fp = 3.0 * Z**2 + 2.0 * c2 * Z + c1
|
|
580
|
+
fpp = 6.0 * Z + 2.0 * c2
|
|
581
|
+
safe_fp = np.where(np.abs(fp) < 1e-30, 1e-30, fp)
|
|
582
|
+
dZ = f / safe_fp
|
|
583
|
+
denom = safe_fp - 0.5 * dZ * fpp
|
|
584
|
+
denom = np.where(np.abs(denom) < 1e-30, 1e-30, denom)
|
|
585
|
+
dZ = f / denom
|
|
586
|
+
Z -= dZ
|
|
587
|
+
if np.max(np.abs(dZ)) < tol:
|
|
588
|
+
break
|
|
547
589
|
|
|
548
|
-
for _ in range(max_iter):
|
|
549
590
|
f = Z**3 + c2 * Z**2 + c1 * Z + c0
|
|
550
|
-
|
|
551
|
-
fpp = 6.0 * Z + 2.0 * c2
|
|
552
|
-
# Protect against zero derivatives
|
|
553
|
-
safe_fp = np.where(np.abs(fp) < 1e-30, 1e-30, fp)
|
|
554
|
-
dZ = f / safe_fp
|
|
555
|
-
denom = safe_fp - 0.5 * dZ * fpp
|
|
556
|
-
denom = np.where(np.abs(denom) < 1e-30, 1e-30, denom)
|
|
557
|
-
dZ = f / denom
|
|
558
|
-
Z -= dZ
|
|
559
|
-
if np.max(np.abs(dZ)) < tol:
|
|
560
|
-
break
|
|
591
|
+
bad = (np.abs(f) > 1e-6) | (Z < 0.0)
|
|
561
592
|
|
|
562
|
-
# Check residuals and fall back to Cardano for any bad elements
|
|
563
|
-
f = Z**3 + c2 * Z**2 + c1 * Z + c0
|
|
564
|
-
bad = np.abs(f) > 1e-6
|
|
565
593
|
if np.any(bad):
|
|
566
594
|
bad_idx = np.where(bad)[0]
|
|
567
595
|
for idx in bad_idx:
|
|
@@ -766,7 +794,7 @@ def gas_z(
|
|
|
766
794
|
c0 = -(A * B - B**2 - B**3)
|
|
767
795
|
|
|
768
796
|
# Solve all cubics at once - get max (vapor) root
|
|
769
|
-
Z_raw = _halley_cubic_vec(c2, c1_coeff, c0) # (N,)
|
|
797
|
+
Z_raw = _halley_cubic_vec(c2, c1_coeff, c0, A=A, B=B) # (N,)
|
|
770
798
|
|
|
771
799
|
# Fugacity-based root selection for sub-critical conditions
|
|
772
800
|
# When 3 real roots exist, the thermodynamically stable phase
|
|
@@ -64,6 +64,7 @@ import pandas as pd
|
|
|
64
64
|
from tabulate import tabulate
|
|
65
65
|
from ilt import gwr
|
|
66
66
|
from mpmath import mp
|
|
67
|
+
from pyrestoolbox._accelerator import RUST_AVAILABLE, _rust_module
|
|
67
68
|
|
|
68
69
|
from pyrestoolbox.classes import (kr_family, kr_table, vlp_method,
|
|
69
70
|
z_method, c_method, pb_method, rs_method,
|
|
@@ -720,7 +721,7 @@ def influence_tables(
|
|
|
720
721
|
min_td: float = 0.01,
|
|
721
722
|
max_td: float = 200,
|
|
722
723
|
n_incr: int = 20,
|
|
723
|
-
M: int =
|
|
724
|
+
M: int = 7,
|
|
724
725
|
export: bool = False,
|
|
725
726
|
) -> Tuple:
|
|
726
727
|
""" Returns a Tuple of;
|
|
@@ -734,7 +735,7 @@ def influence_tables(
|
|
|
734
735
|
min_td: Minimum dimensionless time. Default = 0.01
|
|
735
736
|
max_td: Maximum dimensionless time. Dfeault = 200
|
|
736
737
|
n_incr: Number of increments to split dimensionless time into (log transformed), Default = 20
|
|
737
|
-
M: Laplace
|
|
738
|
+
M: Laplace inversion accuracy. Higher = more accurate, but more time. Generally 6-12 is good range. Default = 7
|
|
738
739
|
export: Boolean value that controls whether an include file with 'INFLUENCE.INC' name is created. Default: False
|
|
739
740
|
"""
|
|
740
741
|
if len(ReDs) == 0:
|
|
@@ -742,26 +743,30 @@ def influence_tables(
|
|
|
742
743
|
if min(ReDs) <=1:
|
|
743
744
|
raise ValueError("ReDs must all be strictly greater than 1.0")
|
|
744
745
|
|
|
745
|
-
# Eq 23 from SPE 81428
|
|
746
|
-
def laplace_Ps(s: float, ReD: float):
|
|
747
|
-
x = mp.sqrt(s)
|
|
748
|
-
# pre-calculate duplicated bessel function for greater efficiency
|
|
749
|
-
i1sReD = mp.besseli(1, x * ReD)
|
|
750
|
-
k1sReD = mp.besselk(1, x * ReD)
|
|
751
|
-
numerator = k1sReD * mp.besseli(0, x) + i1sReD * mp.besselk(0, x)
|
|
752
|
-
denominator = s ** 1.5 * (
|
|
753
|
-
i1sReD * mp.besselk(1, x) - k1sReD * mp.besseli(1, x)
|
|
754
|
-
)
|
|
755
|
-
return numerator / denominator
|
|
756
|
-
|
|
757
746
|
dtD = np.log(max_td / min_td) / n_incr
|
|
758
747
|
tD = [np.exp(x * dtD + np.log(min_td)) for x in range(n_incr + 1)]
|
|
759
748
|
tD = np.array(tD)
|
|
760
749
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
750
|
+
# Rust fast path: Bessel + GWR entirely in Rust (no Python callbacks)
|
|
751
|
+
if RUST_AVAILABLE:
|
|
752
|
+
pDs = _rust_module.influence_tables_rust(tD.tolist(), list(ReDs), M)
|
|
753
|
+
else:
|
|
754
|
+
# Python fallback via ilt library
|
|
755
|
+
def laplace_Ps(s: float, ReD: float):
|
|
756
|
+
x = mp.sqrt(s)
|
|
757
|
+
# pre-calculate duplicated bessel function for greater efficiency
|
|
758
|
+
i1sReD = mp.besseli(1, x * ReD)
|
|
759
|
+
k1sReD = mp.besselk(1, x * ReD)
|
|
760
|
+
numerator = k1sReD * mp.besseli(0, x) + i1sReD * mp.besselk(0, x)
|
|
761
|
+
denominator = s ** 1.5 * (
|
|
762
|
+
i1sReD * mp.besselk(1, x) - k1sReD * mp.besseli(1, x)
|
|
763
|
+
)
|
|
764
|
+
return numerator / denominator
|
|
765
|
+
|
|
766
|
+
pDs = []
|
|
767
|
+
for ReD in ReDs:
|
|
768
|
+
print("Calculating ReD = " + str(ReD))
|
|
769
|
+
pDs.append(gwr(lambda s: laplace_Ps(s, ReD), tD, M))
|
|
765
770
|
|
|
766
771
|
if export:
|
|
767
772
|
inc_out = "---------------------------------------\n"
|
|
@@ -12,7 +12,7 @@ with open(os.path.join(ROOT, 'README.rst'), 'r', encoding='utf-8') as f:
|
|
|
12
12
|
setup(
|
|
13
13
|
name='pyrestoolbox',
|
|
14
14
|
include_package_data=True,
|
|
15
|
-
version='3.1.
|
|
15
|
+
version='3.1.2', # Ideally should be same as your GitHub release tag version
|
|
16
16
|
packages=find_packages(exclude=['pyrestoolbox.tests', 'pyrestoolbox.tests.*']),
|
|
17
17
|
description='pyResToolbox - A collection of Reservoir Engineering Utilities',
|
|
18
18
|
license="GNU General Public License v3 or later (GPLv3+)",
|
|
@@ -511,7 +511,7 @@ fn gwr_single(
|
|
|
511
511
|
// =========================================================================
|
|
512
512
|
|
|
513
513
|
#[pyfunction]
|
|
514
|
-
#[pyo3(signature = (td_array, red_array, m=
|
|
514
|
+
#[pyo3(signature = (td_array, red_array, m=7))]
|
|
515
515
|
pub fn influence_tables_rust(
|
|
516
516
|
td_array: Vec<f64>,
|
|
517
517
|
red_array: Vec<f64>,
|
|
@@ -288,37 +288,46 @@ fn cardano_cubic(c2: f64, c1: f64, c0: f64, flag: i32) -> f64 {
|
|
|
288
288
|
}
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
-
/// Halley's method for finding max root of cubic z^3 + c2*z^2 + c1*z + c0 = 0
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
291
|
+
/// Halley's method for finding max root of cubic z^3 + c2*z^2 + c1*z + c0 = 0,
|
|
292
|
+
/// using Z* = Z - B transformation (per Aaron Zick's reformulation).
|
|
293
|
+
/// For any standard cubic EOS with non-negative A, all physical Z* roots
|
|
294
|
+
/// lie in (0, 1], eliminating negative/huge roots and guaranteeing convergence.
|
|
295
|
+
/// Returns Z (not Z*), i.e. the B offset is added back before returning.
|
|
296
|
+
fn halley_cubic_zstar(a_mix: f64, b_mix: f64) -> f64 {
|
|
297
|
+
// Z* cubic: Z*³ + d2·Z*² + d1·Z* + d0 = 0
|
|
298
|
+
// where f*(0) = d0 = -2B² < 0 and f*(1) = A >= 0
|
|
299
|
+
let d2 = 4.0 * b_mix - 1.0;
|
|
300
|
+
let d1 = a_mix + 2.0 * b_mix * (b_mix - 2.0);
|
|
301
|
+
let d0 = -2.0 * b_mix * b_mix;
|
|
302
|
+
|
|
303
|
+
// Start at Z* = 1 where f* = A >= 0 (above largest root).
|
|
304
|
+
let mut zs = 1.0_f64;
|
|
298
305
|
|
|
299
306
|
for _ in 0..50 {
|
|
300
|
-
let f =
|
|
301
|
-
let fp = 3.0 *
|
|
302
|
-
let fpp = 6.0 *
|
|
307
|
+
let f = zs * zs * zs + d2 * zs * zs + d1 * zs + d0;
|
|
308
|
+
let fp = 3.0 * zs * zs + 2.0 * d2 * zs + d1;
|
|
309
|
+
let fpp = 6.0 * zs + 2.0 * d2;
|
|
303
310
|
|
|
304
311
|
let fp_safe = if fp.abs() < 1e-30 { 1e-30 } else { fp };
|
|
305
312
|
let dz = f / fp_safe;
|
|
306
313
|
let denom = fp_safe - 0.5 * dz * fpp;
|
|
307
314
|
let denom_safe = if denom.abs() < 1e-30 { 1e-30 } else { denom };
|
|
308
315
|
let dz_final = f / denom_safe;
|
|
309
|
-
|
|
316
|
+
zs -= dz_final;
|
|
310
317
|
if dz_final.abs() < 1e-12 {
|
|
311
318
|
break;
|
|
312
319
|
}
|
|
313
320
|
}
|
|
314
321
|
|
|
315
|
-
// Verify convergence
|
|
316
|
-
let f =
|
|
317
|
-
if f.abs() > 1e-6 {
|
|
318
|
-
|
|
322
|
+
// Verify convergence; fallback to Cardano in Z space if needed
|
|
323
|
+
let f = zs * zs * zs + d2 * zs * zs + d1 * zs + d0;
|
|
324
|
+
if f.abs() > 1e-6 || zs <= 0.0 {
|
|
325
|
+
let c2 = -(1.0 - b_mix);
|
|
326
|
+
let c1 = a_mix - 3.0 * b_mix * b_mix - 2.0 * b_mix;
|
|
327
|
+
let c0 = -(a_mix * b_mix - b_mix * b_mix - b_mix * b_mix * b_mix);
|
|
319
328
|
return cardano_cubic(c2, c1, c0, 1);
|
|
320
329
|
}
|
|
321
|
-
|
|
330
|
+
zs + b_mix
|
|
322
331
|
}
|
|
323
332
|
|
|
324
333
|
fn bns_zfactor_core(
|
|
@@ -381,28 +390,29 @@ fn bns_zfactor_core(
|
|
|
381
390
|
b_mix += zi[i] * bi[i];
|
|
382
391
|
}
|
|
383
392
|
|
|
384
|
-
//
|
|
385
|
-
let
|
|
386
|
-
let c1 = a_mix - 3.0 * b_mix * b_mix - 2.0 * b_mix;
|
|
387
|
-
let c0 = -(a_mix * b_mix - b_mix * b_mix - b_mix * b_mix * b_mix);
|
|
388
|
-
|
|
389
|
-
// Solve cubic - get max (vapor) root
|
|
390
|
-
let z_max = halley_cubic(c2, c1, c0);
|
|
393
|
+
// Solve cubic in Z* = Z - B space (Aaron Zick reformulation)
|
|
394
|
+
let z_max = halley_cubic_zstar(a_mix, b_mix);
|
|
391
395
|
|
|
392
|
-
//
|
|
393
|
-
|
|
394
|
-
let
|
|
396
|
+
// Fugacity-based root selection for sub-critical conditions.
|
|
397
|
+
// Use Z* cubic discriminant to check for 3-root case.
|
|
398
|
+
let d2 = 4.0 * b_mix - 1.0;
|
|
399
|
+
let d1 = a_mix + 2.0 * b_mix * (b_mix - 2.0);
|
|
400
|
+
let d0 = -2.0 * b_mix * b_mix;
|
|
401
|
+
let p_d = (3.0 * d1 - d2 * d2) / 3.0;
|
|
402
|
+
let q_d = (2.0 * d2 * d2 * d2 - 9.0 * d2 * d1 + 27.0 * d0) / 27.0;
|
|
395
403
|
let disc = q_d * q_d / 4.0 + p_d * p_d * p_d / 27.0;
|
|
396
404
|
|
|
397
405
|
let z_selected = if disc < -1e-15 {
|
|
398
|
-
// 3 real roots
|
|
399
|
-
let
|
|
400
|
-
let
|
|
406
|
+
// 3 real roots — find min root via deflation in Z* space
|
|
407
|
+
let zs_max = z_max - b_mix; // convert back to Z*
|
|
408
|
+
let b_q = d2 + zs_max;
|
|
409
|
+
let c_q = d1 + zs_max * b_q;
|
|
401
410
|
let det = (b_q * b_q - 4.0 * c_q).max(0.0);
|
|
402
|
-
let
|
|
411
|
+
let zs_min = (-b_q - det.sqrt()) / 2.0;
|
|
412
|
+
let z_min = zs_min + b_mix; // convert to Z
|
|
403
413
|
|
|
404
|
-
// Fugacity comparison (Gibbs criterion)
|
|
405
|
-
if
|
|
414
|
+
// Fugacity comparison (Gibbs criterion); Z > B ↔ Z* > 0
|
|
415
|
+
if zs_min > 0.0 {
|
|
406
416
|
let sqrt2: f64 = std::f64::consts::SQRT_2;
|
|
407
417
|
let s2p1 = 1.0 + sqrt2;
|
|
408
418
|
let s2m1 = sqrt2 - 1.0;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|