pyrestoolbox 3.7.6__tar.gz → 3.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/.github/complexity-baseline.json +5 -8
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/.github/workflows/build-wheels.yml +4 -4
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/.github/workflows/tests.yml +4 -1
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/.gitignore +3 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/Cargo.lock +1 -1
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/Cargo.toml +1 -1
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/PKG-INFO +17 -20
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/README.rst +15 -17
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/mcp/README.md +12 -5
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/mcp/pyproject.toml +1 -1
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/mcp/pyrestoolbox_mcp/server.py +103 -3
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/mcp/tests/test_server.py +51 -2
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyproject.toml +2 -3
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/__init__.py +0 -2
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/_accelerator.py +20 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/brine/_lib_vle_engine.py +42 -23
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/brine/brine.py +147 -58
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/brine/pr_vphi.py +14 -12
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/brine/vphi_route.py +4 -2
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/dca/dca.py +36 -22
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/docs/brine.rst +25 -20
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/docs/changelist.rst +75 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/docs/dca.rst +1 -1
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/docs/gas.rst +96 -33
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/docs/layer.rst +2 -2
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/docs/nodal.rst +51 -48
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/docs/nodal_examples.ipynb +17 -19
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/docs/nodal_hydrate_demo.ipynb +23 -26
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/docs/oil.rst +7 -7
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/docs/sensitivity.rst +1 -1
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/docs/simtools.rst +7 -1
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/gas/_hydrate.py +48 -30
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/gas/gas.py +137 -84
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/layer/layer.py +1 -2
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/matbal/matbal.py +2 -1
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/nodal/nodal.py +295 -149
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/oil/_compressibility.py +9 -16
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/oil/_constants.py +12 -1
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/oil/_correlations.py +39 -20
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/oil/_density.py +2 -1
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/oil/_harmonize.py +15 -29
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/oil/_rate.py +2 -2
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/oil/_tables.py +7 -6
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/oil/_utils.py +17 -1
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/plyasunov/plyasunov_model.py +12 -8
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/shared_fns/shared_fns.py +62 -24
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/simtools/_decks.py +16 -13
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/simtools/_relperm.py +8 -1
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/simtools/_vfp.py +41 -15
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/setup.cfg +0 -1
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/setup.py +0 -1
- pyrestoolbox-3.8.0/src/brine_visc/mod.rs +61 -0
- pyrestoolbox-3.8.0/src/brine_visc/salt.rs +149 -0
- pyrestoolbox-3.8.0/src/brine_visc/water.rs +157 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/gas_viscosity/mod.rs +6 -2
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/lib.rs +5 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/oil/density.rs +29 -23
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/oil/mod.rs +1 -1
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/spycher_pruess/solubility.rs +70 -37
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/vlp/constants.rs +5 -1
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/vlp/holdup_bb.rs +8 -3
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/vlp/holdup_gray.rs +2 -2
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/vlp/march.rs +44 -42
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/vlp/mod.rs +19 -4
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/vlp/pvt_helpers.rs +57 -13
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/vlp/static_column.rs +2 -1
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/zfactor/mod.rs +16 -0
- pyrestoolbox-3.7.6/mcp/DEPLOYMENT.md +0 -53
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/.github/scripts/check_complexity.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/LICENSE +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/MANIFEST.in +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/ResToolbox/privacy_policy.md +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/build_pure_python.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/clippy.toml +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/mcp/Dockerfile +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/mcp/pyrestoolbox_mcp/__init__.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/mcp/pyrestoolbox_mcp/serialize.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/brine/__init__.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/brine/_lib_dielectric.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/brine/_lib_maoduan_viscosity.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/brine/_lib_pitzer_params.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/brine/_lib_salting_library.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/brine/appelo_volumes.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/brine/iapws_viscosity.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/brine/jones_dole_viscosity.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/brine/kestin_nacl_viscosity.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/brine/viscosity_route.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/classes/__init__.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/classes/classes.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/constants/__init__.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/constants/constants.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/dca/__init__.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/docs/examples.ipynb +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/docs/img/bot.png +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/docs/img/bot_PVTO.png +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/docs/img/bot_img.png +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/docs/img/dry_gas.png +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/docs/img/grid_sat_df.png +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/docs/img/influence.png +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/docs/img/properties_df.png +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/docs/img/sgof.png +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/docs/img/swof.png +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/docs/index.rst +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/docs/library.rst +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/docs/matbal.rst +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/docs/plyasunov.rst +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/docs/recommend.rst +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/gas/__init__.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/layer/__init__.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/library/__init__.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/library/component_library.xlsx +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/library/library.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/matbal/__init__.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/nodal/__init__.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/oil/__init__.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/oil/_pvt_class.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/oil/_separator.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/plyasunov/__init__.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/plyasunov/iapws_if97.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/plyasunov/water_properties.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/recommend/__init__.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/recommend/recommend.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/sensitivity/__init__.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/sensitivity/sensitivity.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/shared_fns/__init__.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/simtools/__init__.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/simtools/_aquifer.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/simtools/_pvt_tables.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/simtools/simtools.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/validate/__init__.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/pyrestoolbox/validate/validate.py +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/bessel.rs +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/critical_properties/mod.rs +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/dca/hyperbolic.rs +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/dca/mod.rs +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/dca/ransac.rs +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/gwr.rs +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/matbal/mod.rs +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/matbal/objective.rs +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/pseudopressure.rs +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/spycher_pruess/mod.rs +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/vle/alpha.rs +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/vle/bip.rs +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/vle/components.rs +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/vle/eos.rs +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/vle/flash.rs +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/vle/fugacity.rs +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/vle/k_init.rs +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/vle/mod.rs +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/vle/rachford_rice.rs +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/vlp/friction.rs +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/vlp/holdup_wg.rs +0 -0
- {pyrestoolbox-3.7.6 → pyrestoolbox-3.8.0}/src/vlp/ift.rs +0 -0
|
@@ -2,17 +2,15 @@
|
|
|
2
2
|
"pyrestoolbox/brine/_lib_vle_engine.py::SWBinaryVLE.calc_gas_solubility": 16,
|
|
3
3
|
"pyrestoolbox/brine/_lib_vle_engine.py::SWMultiComponentFlash.build_kij_matrix": 18,
|
|
4
4
|
"pyrestoolbox/brine/_lib_vle_engine.py::SWMultiComponentFlash.calc_equilibrium": 24,
|
|
5
|
-
"pyrestoolbox/brine/_lib_vle_engine.py::calc_gas_brine_equilibrium":
|
|
5
|
+
"pyrestoolbox/brine/_lib_vle_engine.py::calc_gas_brine_equilibrium": 23,
|
|
6
6
|
"pyrestoolbox/brine/brine.py::CO2_Brine_Mixture.__init__": 18,
|
|
7
|
-
"pyrestoolbox/brine/brine.py::SoreideWhitson.__init__":
|
|
7
|
+
"pyrestoolbox/brine/brine.py::SoreideWhitson.__init__": 27,
|
|
8
8
|
"pyrestoolbox/brine/brine.py::SoreideWhitson._calc_properties": 19,
|
|
9
|
-
"pyrestoolbox/brine/brine.py::brine_props":
|
|
10
|
-
"pyrestoolbox/dca/dca.py::
|
|
11
|
-
"pyrestoolbox/dca/dca.py::fit_decline_cum": 22,
|
|
9
|
+
"pyrestoolbox/brine/brine.py::brine_props": 17,
|
|
10
|
+
"pyrestoolbox/dca/dca.py::fit_decline_cum": 20,
|
|
12
11
|
"pyrestoolbox/dca/dca.py::forecast": 16,
|
|
13
12
|
"pyrestoolbox/dca/dca.py::hyp2_from_eur": 18,
|
|
14
13
|
"pyrestoolbox/gas/_hydrate.py::gas_hydrate": 26,
|
|
15
|
-
"pyrestoolbox/gas/gas.py::gas_ponz2p": 16,
|
|
16
14
|
"pyrestoolbox/gas/gas.py::gas_tc_pc": 16,
|
|
17
15
|
"pyrestoolbox/gas/gas.py::gas_ug": 16,
|
|
18
16
|
"pyrestoolbox/matbal/matbal.py::gas_matbal": 26,
|
|
@@ -20,11 +18,10 @@
|
|
|
20
18
|
"pyrestoolbox/nodal/nodal.py::Completion.__init__": 21,
|
|
21
19
|
"pyrestoolbox/nodal/nodal.py::_bb_flow_pattern": 18,
|
|
22
20
|
"pyrestoolbox/nodal/nodal.py::_prepare_flow_inputs": 16,
|
|
23
|
-
"pyrestoolbox/nodal/nodal.py::ipr_curve":
|
|
21
|
+
"pyrestoolbox/nodal/nodal.py::ipr_curve": 22,
|
|
24
22
|
"pyrestoolbox/nodal/nodal.py::operating_point": 18,
|
|
25
23
|
"pyrestoolbox/oil/_compressibility.py::oil_co": 16,
|
|
26
24
|
"pyrestoolbox/oil/_correlations.py::oil_pbub": 16,
|
|
27
|
-
"pyrestoolbox/oil/_harmonize.py::oil_harmonize": 16,
|
|
28
25
|
"pyrestoolbox/shared_fns/shared_fns.py::validate_pe_inputs": 43,
|
|
29
26
|
"pyrestoolbox/simtools/_decks.py::_crawl_deck_includes": 16,
|
|
30
27
|
"pyrestoolbox/simtools/_decks.py::ix_extract_problem_cells": 41,
|
|
@@ -45,7 +45,7 @@ jobs:
|
|
|
45
45
|
# Use maturin as the build frontend
|
|
46
46
|
CIBW_BUILD_FRONTEND: "build"
|
|
47
47
|
# Build for CPython 3.8-3.13 (last 5+ years of Python releases)
|
|
48
|
-
CIBW_BUILD: "
|
|
48
|
+
CIBW_BUILD: "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
|
|
@@ -80,9 +80,9 @@ jobs:
|
|
|
80
80
|
# missing OpenBLAS (this broke the v3.6.0 first build attempt - the
|
|
81
81
|
# skip is load-bearing, do not remove it). Linux import + full-suite
|
|
82
82
|
# coverage lives in .github/workflows/tests.yml instead.
|
|
83
|
-
# Also skip macOS (MPFR symbol issue) and
|
|
83
|
+
# Also skip macOS (MPFR symbol issue) and cp39 (dependency
|
|
84
84
|
# ilt-inversion may not have matching wheels on PyPI yet).
|
|
85
|
-
CIBW_TEST_SKIP: "*-manylinux* *-macosx*
|
|
85
|
+
CIBW_TEST_SKIP: "*-manylinux* *-macosx* cp39-*"
|
|
86
86
|
|
|
87
87
|
- uses: actions/upload-artifact@v6
|
|
88
88
|
with:
|
|
@@ -108,7 +108,7 @@ jobs:
|
|
|
108
108
|
uses: pypa/cibuildwheel@v2.23
|
|
109
109
|
env:
|
|
110
110
|
CIBW_BUILD_FRONTEND: "build"
|
|
111
|
-
CIBW_BUILD: "
|
|
111
|
+
CIBW_BUILD: "cp39-* cp310-* cp311-* cp312-* cp313-*"
|
|
112
112
|
CIBW_ARCHS_MACOS: "x86_64"
|
|
113
113
|
CIBW_TEST_COMMAND: >
|
|
114
114
|
python -c "from pyrestoolbox._accelerator import get_status; s = get_status(); print(s); assert s['rust_available'], f'Rust not loaded: {s}'"
|
|
@@ -80,7 +80,7 @@ jobs:
|
|
|
80
80
|
- name: Check cyclomatic complexity against baseline
|
|
81
81
|
run: python3 .github/scripts/check_complexity.py
|
|
82
82
|
|
|
83
|
-
# ── Rust lint ratchet (clippy clean; any new warning fails)
|
|
83
|
+
# ── Rust lint ratchet (clippy clean; any new warning fails) and unit tests ─
|
|
84
84
|
rust-lint:
|
|
85
85
|
name: Clippy
|
|
86
86
|
runs-on: ubuntu-latest
|
|
@@ -96,3 +96,6 @@ jobs:
|
|
|
96
96
|
|
|
97
97
|
- name: Clippy
|
|
98
98
|
run: cargo clippy --all-targets -- -D warnings
|
|
99
|
+
|
|
100
|
+
- name: Rust unit tests
|
|
101
|
+
run: cargo test --release
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyrestoolbox
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.8.0
|
|
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+)
|
|
@@ -12,7 +12,6 @@ Requires-Dist: tabulate
|
|
|
12
12
|
Requires-Dist: ilt-inversion
|
|
13
13
|
Requires-Dist: mpmath
|
|
14
14
|
Requires-Dist: gmpy2
|
|
15
|
-
Requires-Dist: python-flint
|
|
16
15
|
Requires-Dist: openpyxl
|
|
17
16
|
License-File: LICENSE
|
|
18
17
|
Summary: pyResToolbox - A collection of Reservoir Engineering Utilities
|
|
@@ -20,7 +19,7 @@ Keywords: restoolbox,petroleum,reservoir
|
|
|
20
19
|
Home-Page: https://github.com/mwburgoyne/pyResToolbox
|
|
21
20
|
Author-email: "Mark W. Burgoyne" <mark.w.burgoyne@gmail.com>
|
|
22
21
|
License: GPL-3.0-or-later
|
|
23
|
-
Requires-Python: >=3.
|
|
22
|
+
Requires-Python: >=3.9
|
|
24
23
|
Description-Content-Type: text/x-rst; charset=UTF-8
|
|
25
24
|
Project-URL: Homepage, https://github.com/mwburgoyne/pyResToolbox
|
|
26
25
|
|
|
@@ -35,7 +34,7 @@ A collection of Reservoir Engineering Utilities
|
|
|
35
34
|
This set of functions focuses on those that the author uses often while crafting programming solutions.
|
|
36
35
|
These are the scripts that are often copy/pasted from previous work - sometimes slightly modified - resulting in a trail of slightly different versions over the years. Some attempt has been made here to make this implementation flexible enough such that it can be relied on as-is going forward.
|
|
37
36
|
|
|
38
|
-
|
|
37
|
+
Modules: gas, oil, brine, nodal (VLP/IPR, VFP tables), simtools (black-oil, PVTW, rel-perm, aquifer and deck tools), dca (decline curves), matbal (material balance), layer, library, recommend and sensitivity. Every module accepts Eclipse METRIC units with ``metric=True``; the full documentation ships inside the package, and ``pyrestoolbox.docs_dir()`` returns its location for offline reading.
|
|
39
38
|
|
|
40
39
|
Includes functions to perform calculations including;
|
|
41
40
|
|
|
@@ -289,13 +288,11 @@ Or creating black oil table information for oil
|
|
|
289
288
|
>>>
|
|
290
289
|
>>> plt.tight_layout()
|
|
291
290
|
>>> plt.show()
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
Stock Tank Gas Density: 0.052046870460837856 lb/cuft
|
|
296
|
-
Reservoir Water Density: 61.40223160167964 lb/cuft
|
|
291
|
+
Stock Tank Oil Density: 52.06448672566371 lb/cuft
|
|
292
|
+
Stock Tank Gas Density: 0.05204687045843109 lb/cuft
|
|
293
|
+
Reservoir Water Density: 61.37100825498743 lb/cuft
|
|
297
294
|
Reservoir Water Compressibility: 2.930237693350768e-06 1/psi
|
|
298
|
-
Reservoir Water Viscosity: 0.
|
|
295
|
+
Reservoir Water Viscosity: 0.36386395924173587 cP
|
|
299
296
|
|
|
300
297
|
.. image:: https://raw.githubusercontent.com/mwburgoyne/pyResToolbox/main/pyrestoolbox/docs/img/bot.png
|
|
301
298
|
:alt: Black Oil Properties
|
|
@@ -374,17 +371,17 @@ With ability to generate Live Oil PVTO style table data as well
|
|
|
374
371
|
>>> ..
|
|
375
372
|
>>> plt.show()
|
|
376
373
|
Pb (psia): 4500
|
|
377
|
-
Bob (rb/stb): 1.
|
|
378
|
-
Rsb (mscf/stb): 1.
|
|
379
|
-
Rsb Scaling Required:
|
|
380
|
-
Visob (cP): 0.
|
|
381
|
-
Cob (1/psi):
|
|
382
|
-
|
|
383
|
-
Stock Tank Oil Density: 52.
|
|
384
|
-
Stock Tank Gas Density: 0.
|
|
385
|
-
Reservoir Water Density: 61.
|
|
374
|
+
Bob (rb/stb): 1.5372158966466267
|
|
375
|
+
Rsb (mscf/stb): 1.137900924344287
|
|
376
|
+
Rsb Scaling Required: 1.0362710951888936
|
|
377
|
+
Visob (cP): 0.36663121233374113
|
|
378
|
+
Cob (1/psi): 2.0042955593519084e-05
|
|
379
|
+
|
|
380
|
+
Stock Tank Oil Density: 52.06448672566371 lb/cuft
|
|
381
|
+
Stock Tank Gas Density: 0.05204687045843109 lb/cuft
|
|
382
|
+
Reservoir Water Density: 61.37100825498743 lb/cuft
|
|
386
383
|
Reservoir Water Compressibility: 2.930237693350768e-06 1/psi
|
|
387
|
-
Reservoir Water Viscosity: 0.
|
|
384
|
+
Reservoir Water Viscosity: 0.36386395924173587 cP
|
|
388
385
|
|
|
389
386
|
.. image:: https://raw.githubusercontent.com/mwburgoyne/pyResToolbox/main/pyrestoolbox/docs/img/bot_PVTO.png
|
|
390
387
|
:alt: Live Oil Properties
|
|
@@ -9,7 +9,7 @@ A collection of Reservoir Engineering Utilities
|
|
|
9
9
|
This set of functions focuses on those that the author uses often while crafting programming solutions.
|
|
10
10
|
These are the scripts that are often copy/pasted from previous work - sometimes slightly modified - resulting in a trail of slightly different versions over the years. Some attempt has been made here to make this implementation flexible enough such that it can be relied on as-is going forward.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Modules: gas, oil, brine, nodal (VLP/IPR, VFP tables), simtools (black-oil, PVTW, rel-perm, aquifer and deck tools), dca (decline curves), matbal (material balance), layer, library, recommend and sensitivity. Every module accepts Eclipse METRIC units with ``metric=True``; the full documentation ships inside the package, and ``pyrestoolbox.docs_dir()`` returns its location for offline reading.
|
|
13
13
|
|
|
14
14
|
Includes functions to perform calculations including;
|
|
15
15
|
|
|
@@ -263,13 +263,11 @@ Or creating black oil table information for oil
|
|
|
263
263
|
>>>
|
|
264
264
|
>>> plt.tight_layout()
|
|
265
265
|
>>> plt.show()
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
Stock Tank Gas Density: 0.052046870460837856 lb/cuft
|
|
270
|
-
Reservoir Water Density: 61.40223160167964 lb/cuft
|
|
266
|
+
Stock Tank Oil Density: 52.06448672566371 lb/cuft
|
|
267
|
+
Stock Tank Gas Density: 0.05204687045843109 lb/cuft
|
|
268
|
+
Reservoir Water Density: 61.37100825498743 lb/cuft
|
|
271
269
|
Reservoir Water Compressibility: 2.930237693350768e-06 1/psi
|
|
272
|
-
Reservoir Water Viscosity: 0.
|
|
270
|
+
Reservoir Water Viscosity: 0.36386395924173587 cP
|
|
273
271
|
|
|
274
272
|
.. image:: https://raw.githubusercontent.com/mwburgoyne/pyResToolbox/main/pyrestoolbox/docs/img/bot.png
|
|
275
273
|
:alt: Black Oil Properties
|
|
@@ -348,17 +346,17 @@ With ability to generate Live Oil PVTO style table data as well
|
|
|
348
346
|
>>> ..
|
|
349
347
|
>>> plt.show()
|
|
350
348
|
Pb (psia): 4500
|
|
351
|
-
Bob (rb/stb): 1.
|
|
352
|
-
Rsb (mscf/stb): 1.
|
|
353
|
-
Rsb Scaling Required:
|
|
354
|
-
Visob (cP): 0.
|
|
355
|
-
Cob (1/psi):
|
|
356
|
-
|
|
357
|
-
Stock Tank Oil Density: 52.
|
|
358
|
-
Stock Tank Gas Density: 0.
|
|
359
|
-
Reservoir Water Density: 61.
|
|
349
|
+
Bob (rb/stb): 1.5372158966466267
|
|
350
|
+
Rsb (mscf/stb): 1.137900924344287
|
|
351
|
+
Rsb Scaling Required: 1.0362710951888936
|
|
352
|
+
Visob (cP): 0.36663121233374113
|
|
353
|
+
Cob (1/psi): 2.0042955593519084e-05
|
|
354
|
+
|
|
355
|
+
Stock Tank Oil Density: 52.06448672566371 lb/cuft
|
|
356
|
+
Stock Tank Gas Density: 0.05204687045843109 lb/cuft
|
|
357
|
+
Reservoir Water Density: 61.37100825498743 lb/cuft
|
|
360
358
|
Reservoir Water Compressibility: 2.930237693350768e-06 1/psi
|
|
361
|
-
Reservoir Water Viscosity: 0.
|
|
359
|
+
Reservoir Water Viscosity: 0.36386395924173587 cP
|
|
362
360
|
|
|
363
361
|
.. image:: https://raw.githubusercontent.com/mwburgoyne/pyResToolbox/main/pyrestoolbox/docs/img/bot_PVTO.png
|
|
364
362
|
:alt: Live Oil Properties
|
|
@@ -66,8 +66,7 @@ pyrestoolbox-mcp --transport streamable-http --host 0.0.0.0 --port 8000
|
|
|
66
66
|
|
|
67
67
|
It is stateless (no session affinity needed behind a load balancer) and
|
|
68
68
|
serves the MCP endpoint at `/mcp`. A `Dockerfile` is included; put an API
|
|
69
|
-
gateway in front for authentication.
|
|
70
|
-
team one-pager.
|
|
69
|
+
gateway in front for authentication.
|
|
71
70
|
|
|
72
71
|
## Tests
|
|
73
72
|
|
|
@@ -80,8 +79,16 @@ PYTHONPATH=<repo>:<repo>/mcp python3 -m pytest mcp/tests/ -q
|
|
|
80
79
|
- Units default to oilfield (psia, deg F, ft, mD, cP); pass `metric: true`
|
|
81
80
|
where the function supports it. The plyasunov module is SI (K, MPa).
|
|
82
81
|
- The oil module is scalar-only; gas and brine functions accept lists.
|
|
83
|
-
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
- Gas rates are Mscf/d everywhere (sm3/d with `metric: true`).
|
|
83
|
+
- Functions that take objects are callable through `call` with those objects
|
|
84
|
+
written as JSON objects of their constructor keywords: `completion`
|
|
85
|
+
(`nodal.Completion`, with an optional `segments` list of `WellSegment`
|
|
86
|
+
objects), `reservoir`, `gas_pvt`, `oil_pvt`, `result` (the object a
|
|
87
|
+
`fit_decline` or `fit_ratio` call returned), `ratios`, and `func` as a
|
|
88
|
+
`'module.function'` name for `sensitivity.sweep`/`tornado`. For example
|
|
89
|
+
`call('nodal.fbhp', {"thp": 500, "well_type": "gas", "qg_mscfd": 5000,
|
|
90
|
+
"completion": {"tid": 2.441, "length": 10000, "tht": 100, "bht": 200}})`.
|
|
91
|
+
Classes themselves are not called directly; the one-shot wrapper tools
|
|
92
|
+
cover the common stateful workflows.
|
|
86
93
|
- File-writing simtools functions keep their `export=False` defaults, so
|
|
87
94
|
calls return table text rather than touching disk.
|
|
@@ -11,7 +11,7 @@ authors = [{name = "Mark W. Burgoyne", email = "mark.w.burgoyne@gmail.com"}]
|
|
|
11
11
|
keywords = ["mcp", "petroleum", "reservoir", "pyrestoolbox"]
|
|
12
12
|
requires-python = ">=3.10"
|
|
13
13
|
dependencies = [
|
|
14
|
-
"mcp>=1.2",
|
|
14
|
+
"mcp>=1.2,<2",
|
|
15
15
|
"pyrestoolbox>=3.7",
|
|
16
16
|
]
|
|
17
17
|
readme = "README.md"
|
|
@@ -46,6 +46,17 @@ mcp = FastMCP(
|
|
|
46
46
|
"recommend_methods(...) if unsure which correlation method suits a "
|
|
47
47
|
"fluid. All inputs default to oilfield units (psia, deg F, ft, mD, "
|
|
48
48
|
"cP); pass metric=True for Eclipse METRIC units where supported. "
|
|
49
|
+
"IMPORTANT: parameter NAMES keep their oilfield spelling under "
|
|
50
|
+
"metric=True. `degf` then takes deg C, `p`/`pr`/`pwf` take barsa, and "
|
|
51
|
+
"lengths take m; the docstring unit notation `(deg F | deg C)` gives "
|
|
52
|
+
"the field value first and the metric value second. Passing a "
|
|
53
|
+
"Fahrenheit value to `degf` with metric=True converts it twice and "
|
|
54
|
+
"returns a wrong answer silently. Standard volumes are always at "
|
|
55
|
+
"60 deg F and 14.696 psia, so metric sm3 is on that basis. "
|
|
56
|
+
"Gas rates are Mscf/d everywhere (sm3/d with metric=True). "
|
|
57
|
+
"Functions that take objects (Completion, Reservoir, GasPVT, OilPVT, "
|
|
58
|
+
"decline results) accept them through call() as JSON objects of their "
|
|
59
|
+
"constructor keywords; see the call tool description. "
|
|
49
60
|
"Full module documentation is available as docs:// resources."
|
|
50
61
|
),
|
|
51
62
|
)
|
|
@@ -104,6 +115,11 @@ def describe(name: str) -> str:
|
|
|
104
115
|
|
|
105
116
|
name is 'module.function', e.g. 'gas.gas_z' or 'dca.fit_decline'.
|
|
106
117
|
Also see the docs://<module> resources for worked examples.
|
|
118
|
+
|
|
119
|
+
Units live in the docstring, not the signature, which is why annotations
|
|
120
|
+
are stripped below. A parameter documented `(deg F | deg C)` takes the
|
|
121
|
+
first under the default and the second when metric=True; the parameter
|
|
122
|
+
name itself never changes.
|
|
107
123
|
"""
|
|
108
124
|
_, obj = _resolve(name)
|
|
109
125
|
target = obj.__init__ if inspect.isclass(obj) else obj
|
|
@@ -128,11 +144,30 @@ def call(name: str, arguments: dict = {}) -> dict:
|
|
|
128
144
|
|
|
129
145
|
name is 'module.function', e.g. 'gas.gas_z'. arguments is a JSON object
|
|
130
146
|
of keyword arguments, e.g. {"p": 2000, "sg": 0.75, "degf": 200}.
|
|
147
|
+
That example is oilfield units. With {"metric": true} the same keys take
|
|
148
|
+
metric values, so `degf` would be deg C and `p` barsa; the names do not
|
|
149
|
+
change. Call describe(name) first and read its unit notation: `(deg F |
|
|
150
|
+
deg C)` means field first, metric second.
|
|
131
151
|
Correlation methods are strings (e.g. "zmethod": "DAK"); an invalid
|
|
132
152
|
method string returns an error listing the valid options. Gas and brine
|
|
133
153
|
functions accept lists for pressure-like inputs; oil functions are
|
|
134
|
-
scalar-only.
|
|
135
|
-
|
|
154
|
+
scalar-only. Gas rates are Mscf/d (sm3/d with metric=True).
|
|
155
|
+
|
|
156
|
+
Object arguments are passed as JSON objects of their constructor keywords
|
|
157
|
+
and built here (describe the class for its keywords):
|
|
158
|
+
completion -> nodal.Completion (a "segments" list of objects -> WellSegment)
|
|
159
|
+
reservoir -> nodal.Reservoir
|
|
160
|
+
gas_pvt -> gas.GasPVT
|
|
161
|
+
oil_pvt -> oil.OilPVT
|
|
162
|
+
result -> dca.DeclineResult (forecast) or dca.RatioResult (ratio_forecast);
|
|
163
|
+
pass the object a fit_decline / fit_ratio call returned
|
|
164
|
+
ratios -> {name: RatioResult object}
|
|
165
|
+
func -> a 'module.function' name (sensitivity.sweep / tornado); objects
|
|
166
|
+
inside base_kwargs are built by the same rules
|
|
167
|
+
e.g. call('nodal.fbhp', {"thp": 500, "well_type": "gas", "qg_mscfd": 5000,
|
|
168
|
+
"completion": {"tid": 2.441, "length": 10000, "tht": 100, "bht": 200}}).
|
|
169
|
+
Classes themselves cannot be called here; one-shot wrappers
|
|
170
|
+
(co2_brine_props, sw_brine_props, fit_and_forecast) cover the common cases.
|
|
136
171
|
"""
|
|
137
172
|
_, obj = _resolve(name)
|
|
138
173
|
if inspect.isclass(obj):
|
|
@@ -143,10 +178,75 @@ def call(name: str, arguments: dict = {}) -> dict:
|
|
|
143
178
|
)
|
|
144
179
|
if not callable(obj):
|
|
145
180
|
raise ValueError(f"{name} is not callable (it is {type(obj).__name__})")
|
|
146
|
-
result = obj(**arguments)
|
|
181
|
+
result = obj(**_build_objects(obj, arguments))
|
|
147
182
|
return {"result": to_jsonable(result)}
|
|
148
183
|
|
|
149
184
|
|
|
185
|
+
def _library_class(dotted: str):
|
|
186
|
+
module, cls = dotted.split(".")
|
|
187
|
+
return getattr(_get_module(module), cls)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# Keyword -> library class built from a JSON object. The library names these
|
|
191
|
+
# parameters consistently, so one table covers every function that takes them.
|
|
192
|
+
_OBJECT_PARAMS = {
|
|
193
|
+
"completion": "nodal.Completion",
|
|
194
|
+
"reservoir": "nodal.Reservoir",
|
|
195
|
+
"gas_pvt": "gas.GasPVT",
|
|
196
|
+
"oil_pvt": "oil.OilPVT",
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _build_objects(fn, arguments: dict) -> dict:
|
|
201
|
+
"""Turn JSON objects in `arguments` into the library objects fn expects.
|
|
202
|
+
|
|
203
|
+
Raises ValueError naming the parameter and class when construction fails,
|
|
204
|
+
so an agent learns which keywords to fix instead of an AttributeError from
|
|
205
|
+
deep inside the library.
|
|
206
|
+
"""
|
|
207
|
+
try:
|
|
208
|
+
annotations = {k: str(v.annotation) for k, v in inspect.signature(fn).parameters.items()}
|
|
209
|
+
except (TypeError, ValueError):
|
|
210
|
+
annotations = {}
|
|
211
|
+
out = {}
|
|
212
|
+
for key, value in arguments.items():
|
|
213
|
+
out[key] = _build_one(key, value, annotations.get(key, ""))
|
|
214
|
+
return out
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _build_one(key, value, annotation):
|
|
218
|
+
if key == "func" and isinstance(value, str):
|
|
219
|
+
_, fn = _resolve(value)
|
|
220
|
+
return fn
|
|
221
|
+
if key == "base_kwargs" and isinstance(value, dict):
|
|
222
|
+
return {k: _build_one(k, v, "") for k, v in value.items()}
|
|
223
|
+
if not isinstance(value, dict):
|
|
224
|
+
return value
|
|
225
|
+
if key == "ratios":
|
|
226
|
+
return {name: _construct("dca.RatioResult", key, spec) for name, spec in value.items()}
|
|
227
|
+
if key == "result":
|
|
228
|
+
dotted = "dca.RatioResult" if "RatioResult" in annotation else "dca.DeclineResult"
|
|
229
|
+
return _construct(dotted, key, value)
|
|
230
|
+
if key in _OBJECT_PARAMS:
|
|
231
|
+
if key == "completion" and isinstance(value.get("segments"), list):
|
|
232
|
+
value = dict(value, segments=[
|
|
233
|
+
_construct("nodal.WellSegment", "completion.segments", seg)
|
|
234
|
+
if isinstance(seg, dict) else seg for seg in value["segments"]])
|
|
235
|
+
return _construct(_OBJECT_PARAMS[key], key, value)
|
|
236
|
+
return value
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _construct(dotted, key, spec):
|
|
240
|
+
cls = _library_class(dotted)
|
|
241
|
+
try:
|
|
242
|
+
return cls(**spec)
|
|
243
|
+
except TypeError as exc:
|
|
244
|
+
raise ValueError(
|
|
245
|
+
f"Could not build {dotted} for '{key}' from {sorted(spec)}: {exc}. "
|
|
246
|
+
f"Use describe({dotted!r}) for its keywords."
|
|
247
|
+
) from exc
|
|
248
|
+
|
|
249
|
+
|
|
150
250
|
@mcp.tool()
|
|
151
251
|
def recommend_methods(sg: float = 0.65, co2: float = 0, h2s: float = 0,
|
|
152
252
|
n2: float = 0, h2: float = 0, api: float | None = None,
|
|
@@ -106,6 +106,55 @@ def test_call_rejects_private():
|
|
|
106
106
|
srv.call('gas._resolve_methods', {})
|
|
107
107
|
|
|
108
108
|
|
|
109
|
+
_COMPLETION = {'tid': 2.441, 'length': 10000, 'tht': 100, 'bht': 200}
|
|
110
|
+
_RESERVOIR = {'pr': 3000, 'degf': 200, 'k': 10, 'h': 50, 're': 1500, 'rw': 0.35, 'S': 2, 'D': 0.001}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_call_builds_completion_and_matches_doc_example():
|
|
114
|
+
r = srv.call('nodal.fbhp', {'thp': 500, 'completion': _COMPLETION, 'vlpmethod': 'HB',
|
|
115
|
+
'well_type': 'gas', 'qg_mscfd': 5000, 'gsg': 0.65,
|
|
116
|
+
'cgr': 10, 'qw_bwpd': 10, 'api': 45, 'oil_vis': 1.0})
|
|
117
|
+
assert abs(r['result'] - 961.594453485463) / 961.594453485463 < RTOL
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def test_call_builds_segments_reservoir_and_gas_pvt():
|
|
121
|
+
segs = {'segments': [{'md': 5000, 'id': 2.441}, {'md': 5000, 'id': 2.441, 'deviation': 45}],
|
|
122
|
+
'tht': 100, 'bht': 200}
|
|
123
|
+
r = srv.call('nodal.fbhp', {'thp': 500, 'completion': segs, 'vlpmethod': 'HB', 'well_type': 'gas',
|
|
124
|
+
'qg_mscfd': 5000, 'gsg': 0.65, 'cgr': 10, 'qw_bwpd': 10,
|
|
125
|
+
'api': 45, 'oil_vis': 1.0})
|
|
126
|
+
assert abs(r['result'] - 932.8973511781188) < 0.01
|
|
127
|
+
op = srv.call('nodal.operating_point', {'thp': 500, 'completion': _COMPLETION,
|
|
128
|
+
'reservoir': _RESERVOIR, 'vlpmethod': 'HB',
|
|
129
|
+
'well_type': 'gas', 'gsg': 0.65})['result']
|
|
130
|
+
assert abs(op['rate'] - 10578.1) < 0.2
|
|
131
|
+
assert srv.call('nodal.fthp', {'bhp': 1000, 'completion': _COMPLETION, 'well_type': 'gas',
|
|
132
|
+
'qg_mscfd': 5000, 'gas_pvt': {'sg': 0.7, 'co2': 0.1}})['result'] > 14.7
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_call_forecast_from_fit_result_and_ratio():
|
|
136
|
+
t = list(range(1, 25))
|
|
137
|
+
q = [1000 * 2.718281828 ** (-0.05 * x) for x in t]
|
|
138
|
+
fit = srv.call('dca.fit_decline', {'t': t, 'q': q, 'method': 'exponential'})['result']
|
|
139
|
+
ratio = {'method': 'linear', 'a': 2.0, 'b': 0.0, 'domain': 'time'}
|
|
140
|
+
fc = srv.call('dca.forecast', {'result': fit, 't_end': 60, 'dt': 30, 'ratios': {'gor': ratio}})['result']
|
|
141
|
+
assert all(abs(c - 2 * Q) < 1e-6 * Q for c, Q in zip(fc['secondary']['gor']['cum'], fc['Qcum']))
|
|
142
|
+
assert srv.call('dca.ratio_forecast', {'result': ratio, 'x': 10})['result'] == 2.0
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def test_call_sensitivity_with_function_name_and_nested_objects():
|
|
146
|
+
r = srv.call('sensitivity.sweep', {'func': 'nodal.fbhp', 'vary_param': 'thp', 'vary_values': [300, 500],
|
|
147
|
+
'base_kwargs': {'thp': 500, 'completion': _COMPLETION,
|
|
148
|
+
'well_type': 'gas', 'qg_mscfd': 5000}})['result']
|
|
149
|
+
assert r['results'][1] > r['results'][0]
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def test_call_bad_object_keyword_names_the_class():
|
|
153
|
+
with pytest.raises(ValueError, match="Could not build nodal.Completion for 'completion'"):
|
|
154
|
+
srv.call('nodal.fbhp', {'thp': 500, 'completion': {'tid': 2.441, 'lenght': 10000},
|
|
155
|
+
'well_type': 'gas', 'qg_mscfd': 5000})
|
|
156
|
+
|
|
157
|
+
|
|
109
158
|
# ----------------------------------------------------------- composite tools
|
|
110
159
|
|
|
111
160
|
def test_recommend_methods_h2_mandatory_bns():
|
|
@@ -118,8 +167,8 @@ def test_recommend_methods_h2_mandatory_bns():
|
|
|
118
167
|
def test_co2_brine_props_matches_doc_example():
|
|
119
168
|
out = srv.co2_brine_props(pres=5000, temp=275, ppm=30000)
|
|
120
169
|
# brine.rst worked example values
|
|
121
|
-
assert abs(out['bw'][0] - 1.
|
|
122
|
-
assert abs(out['x'][0] - 0.
|
|
170
|
+
assert abs(out['bw'][0] - 1.1090966489093108) < 1e-6
|
|
171
|
+
assert abs(out['x'][0] - 0.02431474274865895) < 1e-6
|
|
123
172
|
assert type(out['bw'][0]) is float # json-clean
|
|
124
173
|
|
|
125
174
|
|
|
@@ -4,12 +4,12 @@ build-backend = "maturin"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pyrestoolbox"
|
|
7
|
-
version = "3.
|
|
7
|
+
version = "3.8.0"
|
|
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.9"
|
|
13
13
|
classifiers = [
|
|
14
14
|
"Programming Language :: Python :: 3",
|
|
15
15
|
"Programming Language :: Rust",
|
|
@@ -24,7 +24,6 @@ dependencies = [
|
|
|
24
24
|
"ilt-inversion",
|
|
25
25
|
"mpmath",
|
|
26
26
|
"gmpy2",
|
|
27
|
-
"python-flint",
|
|
28
27
|
"openpyxl",
|
|
29
28
|
]
|
|
30
29
|
readme = "README.rst"
|
|
@@ -11,8 +11,6 @@ These are the scripts that are often copy/pasted from previous work - sometimes
|
|
|
11
11
|
in a trail of slightly different versions over the years. Some attempt has been made here to make this
|
|
12
12
|
implementation flexible enough such that it can be relied on as-is going forward.
|
|
13
13
|
|
|
14
|
-
Note: Version 2.x refactors functions into different modules, requiring separate imports.
|
|
15
|
-
|
|
16
14
|
Usage: ``from pyrestoolbox import <module>`` then ``<module>.function(...)``.
|
|
17
15
|
All inputs use oilfield units (psia, degF, ft, mD, cP) unless otherwise noted.
|
|
18
16
|
|
|
@@ -14,6 +14,25 @@ from pathlib import Path
|
|
|
14
14
|
|
|
15
15
|
logger = logging.getLogger(__name__)
|
|
16
16
|
|
|
17
|
+
|
|
18
|
+
def _warn_if_extension_stale(native):
|
|
19
|
+
"""Warn when the compiled extension's version differs from the package's.
|
|
20
|
+
|
|
21
|
+
A stale local build otherwise runs old numerics silently (3.7.6 under a
|
|
22
|
+
3.7.7 package was found on 25 September 2026).
|
|
23
|
+
"""
|
|
24
|
+
try:
|
|
25
|
+
from pyrestoolbox import __version__ as pkg_version
|
|
26
|
+
except ImportError:
|
|
27
|
+
return
|
|
28
|
+
ext_version = getattr(native, '__version__', None)
|
|
29
|
+
if ext_version and pkg_version and ext_version != pkg_version:
|
|
30
|
+
import warnings
|
|
31
|
+
warnings.warn(
|
|
32
|
+
f"pyrestoolbox Rust extension is version {ext_version} but the package is "
|
|
33
|
+
f"{pkg_version}; rebuild the extension or set PYRESTOOLBOX_NO_RUST=1.",
|
|
34
|
+
RuntimeWarning, stacklevel=2)
|
|
35
|
+
|
|
17
36
|
RUST_AVAILABLE: bool = False
|
|
18
37
|
_rust_module = None
|
|
19
38
|
_failure_reason: str = ""
|
|
@@ -139,6 +158,7 @@ else:
|
|
|
139
158
|
RUST_AVAILABLE = True
|
|
140
159
|
logger.debug("pyResToolbox Rust acceleration loaded successfully")
|
|
141
160
|
_clear_sentinel()
|
|
161
|
+
_warn_if_extension_stale(_native)
|
|
142
162
|
|
|
143
163
|
except ImportError as e:
|
|
144
164
|
_failure_reason = f"ImportError: {e}"
|
|
@@ -2304,6 +2304,47 @@ class SWMultiComponentFlash:
|
|
|
2304
2304
|
# =============================================================================
|
|
2305
2305
|
# Main Multi-Component API
|
|
2306
2306
|
# =============================================================================
|
|
2307
|
+
def _flash_with_feed_retries(flash, T_K, P_Pa, gas_vec, salinity_method):
|
|
2308
|
+
"""Two-phase gas-brine flash, retrying with increasing water in the feed.
|
|
2309
|
+
|
|
2310
|
+
Returns the first result that is two-phase and converged on both sides;
|
|
2311
|
+
if none is, returns the last one with two_phase=False and a warning.
|
|
2312
|
+
"""
|
|
2313
|
+
# The y_* inputs are the dry EQUILIBRIUM VAPOR composition. A
|
|
2314
|
+
# water-dominated feed strips the more-soluble gases into the liquid,
|
|
2315
|
+
# so the equilibrium vapor drifts off the requested composition (a
|
|
2316
|
+
# 95/5 water/gas feed depletes dry-basis CO2 from 0.73 to ~0.65 for
|
|
2317
|
+
# CH4+CO2 at 50 MPa, biasing x_CO2 ~10% low). A gas-excess feed pins
|
|
2318
|
+
# the vapor at the requested composition; single-gas results are
|
|
2319
|
+
# feed-independent (unique tie-line). Near the water boiling curve a
|
|
2320
|
+
# two-phase solution only exists for water-rich feeds (feed water must
|
|
2321
|
+
# lie between the liquid and vapor water mole fractions), so retry
|
|
2322
|
+
# with increasing water fraction until both flashes are two-phase.
|
|
2323
|
+
result = None
|
|
2324
|
+
for w_frac in (0.5, 0.9, 0.95, 0.98):
|
|
2325
|
+
z = np.concatenate(([w_frac], (1.0 - w_frac) * gas_vec))
|
|
2326
|
+
result = flash.calc_equilibrium(T_K, P_Pa, z,
|
|
2327
|
+
salinity_method=salinity_method)
|
|
2328
|
+
V_aq = result.get('V_aq', np.nan)
|
|
2329
|
+
V_na = result.get('V_na', np.nan)
|
|
2330
|
+
if (result.get('converged_aq', False)
|
|
2331
|
+
and result.get('converged_na', False)
|
|
2332
|
+
and 1e-6 < V_aq < 1 - 1e-6
|
|
2333
|
+
and 1e-6 < V_na < 1 - 1e-6
|
|
2334
|
+
and result['x_aq'][0] > 0.5):
|
|
2335
|
+
break
|
|
2336
|
+
else:
|
|
2337
|
+
# Every feed came back single-phase or unconverged, so x_aq/y_na are
|
|
2338
|
+
# the flash feed itself, not an equilibrium. Say so.
|
|
2339
|
+
warnings.warn(
|
|
2340
|
+
f"SoreideWhitson: no two-phase gas-brine split found at {P_Pa / 1e5:.1f} bar, "
|
|
2341
|
+
f"{T_K - 273.15:.1f} degC; the returned compositions are the flash feed, "
|
|
2342
|
+
f"not an equilibrium (outside the model's two-phase region).",
|
|
2343
|
+
RuntimeWarning, stacklevel=3)
|
|
2344
|
+
result['two_phase'] = False
|
|
2345
|
+
return result
|
|
2346
|
+
|
|
2347
|
+
|
|
2307
2348
|
def calc_gas_brine_equilibrium(
|
|
2308
2349
|
salinity_wt_pct: float,
|
|
2309
2350
|
temperature_F: float,
|
|
@@ -2406,29 +2447,7 @@ def calc_gas_brine_equilibrium(
|
|
|
2406
2447
|
framework=framework,
|
|
2407
2448
|
salinity_method=salinity_method)
|
|
2408
2449
|
|
|
2409
|
-
|
|
2410
|
-
# water-dominated feed strips the more-soluble gases into the liquid,
|
|
2411
|
-
# so the equilibrium vapor drifts off the requested composition (a
|
|
2412
|
-
# 95/5 water/gas feed depletes dry-basis CO2 from 0.73 to ~0.65 for
|
|
2413
|
-
# CH4+CO2 at 50 MPa, biasing x_CO2 ~10% low). A gas-excess feed pins
|
|
2414
|
-
# the vapor at the requested composition; single-gas results are
|
|
2415
|
-
# feed-independent (unique tie-line). Near the water boiling curve a
|
|
2416
|
-
# two-phase solution only exists for water-rich feeds (feed water must
|
|
2417
|
-
# lie between the liquid and vapor water mole fractions), so retry
|
|
2418
|
-
# with increasing water fraction until both flashes are two-phase.
|
|
2419
|
-
result = None
|
|
2420
|
-
for w_frac in (0.5, 0.9, 0.95, 0.98):
|
|
2421
|
-
z = np.concatenate(([w_frac], (1.0 - w_frac) * gas_vec))
|
|
2422
|
-
result = flash.calc_equilibrium(T_K, P_Pa, z,
|
|
2423
|
-
salinity_method=salinity_method)
|
|
2424
|
-
V_aq = result.get('V_aq', np.nan)
|
|
2425
|
-
V_na = result.get('V_na', np.nan)
|
|
2426
|
-
if (result.get('converged_aq', False)
|
|
2427
|
-
and result.get('converged_na', False)
|
|
2428
|
-
and 1e-6 < V_aq < 1 - 1e-6
|
|
2429
|
-
and 1e-6 < V_na < 1 - 1e-6
|
|
2430
|
-
and result['x_aq'][0] > 0.5):
|
|
2431
|
-
break
|
|
2450
|
+
result = _flash_with_feed_retries(flash, T_K, P_Pa, gas_vec, salinity_method)
|
|
2432
2451
|
|
|
2433
2452
|
x_gas = {}
|
|
2434
2453
|
for i, name in enumerate(comp_names):
|