pyrestoolbox 3.1.3__tar.gz → 3.1.5__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.3 → pyrestoolbox-3.1.5}/PKG-INFO +1 -1
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/build_pure_python.py +22 -4
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyproject.toml +1 -1
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/_accelerator.py +43 -1
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/brine/_lib_vle_engine.py +44 -6
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/brine/brine.py +149 -106
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/classes/classes.py +1 -1
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/dca/dca.py +47 -14
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/changelist.rst +33 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/gas/gas.py +266 -174
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/matbal/matbal.py +24 -37
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/nodal/nodal.py +627 -946
- pyrestoolbox-3.1.5/pyrestoolbox/oil/__init__.py +76 -0
- pyrestoolbox-3.1.5/pyrestoolbox/oil/_compressibility.py +375 -0
- pyrestoolbox-3.1.5/pyrestoolbox/oil/_constants.py +125 -0
- pyrestoolbox-3.1.5/pyrestoolbox/oil/_correlations.py +494 -0
- pyrestoolbox-3.1.5/pyrestoolbox/oil/_density.py +227 -0
- pyrestoolbox-3.1.5/pyrestoolbox/oil/_harmonize.py +139 -0
- pyrestoolbox-3.1.5/pyrestoolbox/oil/_pvt_class.py +172 -0
- pyrestoolbox-3.1.5/pyrestoolbox/oil/_rate.py +192 -0
- pyrestoolbox-3.1.5/pyrestoolbox/oil/_separator.py +112 -0
- pyrestoolbox-3.1.5/pyrestoolbox/oil/_tables.py +272 -0
- pyrestoolbox-3.1.5/pyrestoolbox/oil/_utils.py +226 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/plyasunov/iapws_if97.py +9 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/plyasunov/plyasunov_model.py +12 -1
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/simtools/simtools.py +39 -20
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/validate/validate.py +21 -5
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/setup.cfg +1 -1
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/setup.py +6 -2
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/lib.rs +3 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/pseudopressure.rs +161 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vle/flash.rs +13 -5
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vlp/segment_gas.rs +16 -40
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vlp/segment_oil.rs +12 -36
- pyrestoolbox-3.1.3/pyrestoolbox/oil/__init__.py +0 -1
- pyrestoolbox-3.1.3/pyrestoolbox/oil/oil.py +0 -2146
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/.github/workflows/build-wheels.yml +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/.gitignore +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/Cargo.lock +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/Cargo.toml +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/LICENSE +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/MANIFEST.in +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/README.rst +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/ResToolbox/privacy_policy.md +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/benchmark_rust_vs_python.py +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/__init__.py +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/brine/__init__.py +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/brine/_lib_salting_library.py +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/classes/__init__.py +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/constants/__init__.py +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/constants/constants.py +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/dca/__init__.py +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/brine.rst +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/dca.rst +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/examples.ipynb +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/gas.rst +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/img/bot.png +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/img/bot_PVTO.png +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/img/bot_img.png +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/img/dry_gas.png +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/img/grid_sat_df.png +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/img/influence.png +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/img/properties_df.png +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/img/sgof.png +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/img/swof.png +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/layer.rst +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/library.rst +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/matbal.rst +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/nodal.rst +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/nodal_examples.ipynb +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/nodal_hydrate_demo.ipynb +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/oil.rst +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/recommend.rst +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/sensitivity.rst +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/simtools.rst +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/gas/__init__.py +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/layer/__init__.py +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/layer/layer.py +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/library/__init__.py +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/library/component_library.xlsx +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/library/library.py +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/matbal/__init__.py +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/nodal/__init__.py +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/plyasunov/__init__.py +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/plyasunov/water_properties.py +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/recommend/__init__.py +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/recommend/recommend.py +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/sensitivity/__init__.py +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/sensitivity/sensitivity.py +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/shared_fns/__init__.py +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/shared_fns/shared_fns.py +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/simtools/__init__.py +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/validate/__init__.py +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/bessel.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/critical_properties/mod.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/dca/hyperbolic.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/dca/mod.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/dca/ransac.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/gas_viscosity/mod.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/gwr.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/matbal/mod.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/matbal/objective.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/oil/density.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/oil/mod.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/spycher_pruess/mod.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/spycher_pruess/solubility.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vle/alpha.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vle/bip.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vle/components.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vle/eos.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vle/fugacity.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vle/k_init.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vle/mod.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vle/rachford_rice.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vlp/friction.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vlp/holdup_bb.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vlp/holdup_gray.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vlp/holdup_hb.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vlp/holdup_wg.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vlp/ift.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vlp/mod.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vlp/pvt_helpers.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vlp/static_column.rs +0 -0
- {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/zfactor/mod.rs +0 -0
|
@@ -19,17 +19,35 @@ ROOT = Path(__file__).parent
|
|
|
19
19
|
|
|
20
20
|
def main():
|
|
21
21
|
# Write a temporary pyproject.toml for setuptools (pure Python)
|
|
22
|
+
# Strategy: keep the full [project] section from the real pyproject.toml
|
|
23
|
+
# (so modern setuptools gets name, version, dependencies, etc.) but swap
|
|
24
|
+
# [build-system] from maturin to setuptools.
|
|
25
|
+
import re
|
|
22
26
|
with tempfile.TemporaryDirectory() as tmp:
|
|
23
27
|
tmp = Path(tmp)
|
|
24
28
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
real_content = (ROOT / "pyproject.toml").read_text()
|
|
30
|
+
|
|
31
|
+
# Replace [build-system] section (everything up to next [section] or EOF)
|
|
32
|
+
pp_content = re.sub(
|
|
33
|
+
r'^\[build-system\].*?(?=^\[(?!build-system)|\Z)',
|
|
28
34
|
'[build-system]\n'
|
|
29
35
|
'requires = ["setuptools", "wheel"]\n'
|
|
30
|
-
'build-backend = "setuptools.build_meta"\n'
|
|
36
|
+
'build-backend = "setuptools.build_meta"\n\n',
|
|
37
|
+
real_content,
|
|
38
|
+
flags=re.MULTILINE | re.DOTALL,
|
|
39
|
+
)
|
|
40
|
+
# Remove [tool.maturin] section (not relevant for setuptools)
|
|
41
|
+
pp_content = re.sub(
|
|
42
|
+
r'^\[tool\.maturin\].*?(?=^\[(?!tool\.maturin)|\Z)',
|
|
43
|
+
'',
|
|
44
|
+
pp_content,
|
|
45
|
+
flags=re.MULTILINE | re.DOTALL,
|
|
31
46
|
)
|
|
32
47
|
|
|
48
|
+
pp_toml = tmp / "pyproject.toml"
|
|
49
|
+
pp_toml.write_text(pp_content)
|
|
50
|
+
|
|
33
51
|
# Build using setup.py + the temporary pyproject.toml
|
|
34
52
|
# We swap pyproject.toml temporarily
|
|
35
53
|
real_toml = ROOT / "pyproject.toml"
|
|
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pyrestoolbox"
|
|
7
|
-
version = "3.1.
|
|
7
|
+
version = "3.1.5"
|
|
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"}]
|
|
@@ -156,13 +156,23 @@ else:
|
|
|
156
156
|
|
|
157
157
|
|
|
158
158
|
def get_status():
|
|
159
|
-
|
|
159
|
+
status = {
|
|
160
160
|
"rust_available": RUST_AVAILABLE,
|
|
161
161
|
"failure_reason": _failure_reason if not RUST_AVAILABLE else "",
|
|
162
162
|
"forced_python": _force_python,
|
|
163
163
|
"sentinel_path": str(_sentinel_path()),
|
|
164
164
|
"sentinel_exists": _sentinel_path().exists(),
|
|
165
165
|
}
|
|
166
|
+
if RUST_AVAILABLE:
|
|
167
|
+
status["rust_version"] = get_rust_version()
|
|
168
|
+
return status
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def get_rust_version():
|
|
172
|
+
"""Return Rust extension version string, or None if unavailable."""
|
|
173
|
+
if not RUST_AVAILABLE or _rust_module is None:
|
|
174
|
+
return None
|
|
175
|
+
return getattr(_rust_module, '__version__', getattr(_rust_module, 'version', None))
|
|
166
176
|
|
|
167
177
|
|
|
168
178
|
def clear_block():
|
|
@@ -172,3 +182,35 @@ def clear_block():
|
|
|
172
182
|
"note": "Restart Python process to retry Rust extension loading",
|
|
173
183
|
**get_status(),
|
|
174
184
|
}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def rust_accelerated(rust_fn_name):
|
|
188
|
+
"""Decorator that dispatches to a Rust implementation when available.
|
|
189
|
+
|
|
190
|
+
The decorated function is the pure-Python fallback. When Rust is available,
|
|
191
|
+
the decorator calls ``_rust_module.<rust_fn_name>`` with the same positional
|
|
192
|
+
and keyword arguments. On ImportError or AttributeError (missing function),
|
|
193
|
+
it falls back to the Python implementation transparently.
|
|
194
|
+
|
|
195
|
+
Usage::
|
|
196
|
+
|
|
197
|
+
@rust_accelerated('hb_fbhp_gas_rust')
|
|
198
|
+
def _hb_fbhp_gas(thp, api, gsg, ...):
|
|
199
|
+
return _segment_march_gas(...) # pure-Python path
|
|
200
|
+
"""
|
|
201
|
+
import functools
|
|
202
|
+
|
|
203
|
+
def decorator(fn):
|
|
204
|
+
@functools.wraps(fn)
|
|
205
|
+
def wrapper(*args, **kwargs):
|
|
206
|
+
if RUST_AVAILABLE:
|
|
207
|
+
try:
|
|
208
|
+
rust_fn = getattr(_rust_module, rust_fn_name)
|
|
209
|
+
return rust_fn(*args, **kwargs)
|
|
210
|
+
except (ImportError, AttributeError):
|
|
211
|
+
pass
|
|
212
|
+
return fn(*args, **kwargs)
|
|
213
|
+
wrapper._rust_fn_name = rust_fn_name
|
|
214
|
+
wrapper._python_fn = fn
|
|
215
|
+
return wrapper
|
|
216
|
+
return decorator
|
|
@@ -46,6 +46,7 @@ Author: Mark Burgoyne, Markus H. Nielsen
|
|
|
46
46
|
Date: 2025-2026
|
|
47
47
|
"""
|
|
48
48
|
|
|
49
|
+
import warnings
|
|
49
50
|
import numpy as np
|
|
50
51
|
from typing import Dict, Tuple, Optional, List, Callable
|
|
51
52
|
from dataclasses import dataclass
|
|
@@ -1081,6 +1082,11 @@ def rr_solver(
|
|
|
1081
1082
|
b = (b_min + b_max) / 2.0
|
|
1082
1083
|
|
|
1083
1084
|
if N_it > max_iter:
|
|
1085
|
+
warnings.warn(
|
|
1086
|
+
f"Rachford-Rice solver did not converge in {max_iter} iterations "
|
|
1087
|
+
f"(residual={abs(h_b):.2e}, tol={tol:.1e}). Results may be inaccurate.",
|
|
1088
|
+
RuntimeWarning, stacklevel=3
|
|
1089
|
+
)
|
|
1084
1090
|
break
|
|
1085
1091
|
|
|
1086
1092
|
# Recover compositions from transformed variables
|
|
@@ -1590,6 +1596,8 @@ class SWBinaryVLE:
|
|
|
1590
1596
|
x = np.array([0.999, 0.001])
|
|
1591
1597
|
y = np.array([0.02, 0.98])
|
|
1592
1598
|
|
|
1599
|
+
damp = 0.3
|
|
1600
|
+
prev_error = np.inf
|
|
1593
1601
|
for iteration in range(max_iter):
|
|
1594
1602
|
x = np.clip(x, 1e-14, 1.0 - 1e-14)
|
|
1595
1603
|
x = x / np.sum(x)
|
|
@@ -1606,7 +1614,13 @@ class SWBinaryVLE:
|
|
|
1606
1614
|
if error < 1e-10:
|
|
1607
1615
|
return y[0]
|
|
1608
1616
|
|
|
1609
|
-
|
|
1617
|
+
# Adaptive damping
|
|
1618
|
+
if error < prev_error:
|
|
1619
|
+
damp = min(damp * 1.15, 0.8)
|
|
1620
|
+
else:
|
|
1621
|
+
damp = max(damp * 0.5, 0.1)
|
|
1622
|
+
prev_error = error
|
|
1623
|
+
|
|
1610
1624
|
y = y + damp * (y_new - y)
|
|
1611
1625
|
y = y / np.sum(y)
|
|
1612
1626
|
|
|
@@ -1895,7 +1909,20 @@ class SWMultiComponentFlash:
|
|
|
1895
1909
|
return ai, bi
|
|
1896
1910
|
|
|
1897
1911
|
def build_kij_matrix(self, T_K: float, mode: str = 'AQ') -> np.ndarray:
|
|
1898
|
-
"""Build N×N kij matrix. mode='AQ' or 'NA' for gas-water pairs.
|
|
1912
|
+
"""Build N×N kij matrix. mode='AQ' or 'NA' for gas-water pairs.
|
|
1913
|
+
|
|
1914
|
+
Results are cached per (T_K, mode) since the matrix only depends on
|
|
1915
|
+
temperature, mode, and instance-level constants (salinity, framework,
|
|
1916
|
+
component list) that don't change between calls.
|
|
1917
|
+
"""
|
|
1918
|
+
cache_key = (T_K, mode)
|
|
1919
|
+
if hasattr(self, '_kij_cache'):
|
|
1920
|
+
cached = self._kij_cache.get(cache_key)
|
|
1921
|
+
if cached is not None:
|
|
1922
|
+
return cached.copy()
|
|
1923
|
+
else:
|
|
1924
|
+
self._kij_cache = {}
|
|
1925
|
+
|
|
1899
1926
|
kij = np.zeros((self.nc, self.nc))
|
|
1900
1927
|
for i in range(self.nc):
|
|
1901
1928
|
for j in range(i + 1, self.nc):
|
|
@@ -1911,6 +1938,8 @@ class SWMultiComponentFlash:
|
|
|
1911
1938
|
val = get_gas_gas_bip(ni, nj)
|
|
1912
1939
|
kij[i, j] = val
|
|
1913
1940
|
kij[j, i] = val
|
|
1941
|
+
|
|
1942
|
+
self._kij_cache[cache_key] = kij.copy()
|
|
1914
1943
|
return kij
|
|
1915
1944
|
|
|
1916
1945
|
def calc_fugacity_coefficients(self, T_K: float, P_Pa: float, comp: np.ndarray,
|
|
@@ -2083,7 +2112,7 @@ class SWMultiComponentFlash:
|
|
|
2083
2112
|
0.0, mode, gamma_arr.tolist(),
|
|
2084
2113
|
)
|
|
2085
2114
|
return V, np.array(x_r), np.array(y_r), True
|
|
2086
|
-
except
|
|
2115
|
+
except (ImportError, AttributeError):
|
|
2087
2116
|
pass
|
|
2088
2117
|
|
|
2089
2118
|
kij_matrix = self.build_kij_matrix(T_K, mode)
|
|
@@ -2107,6 +2136,8 @@ class SWMultiComponentFlash:
|
|
|
2107
2136
|
K[self.iw] = min(K[self.iw], 0.01)
|
|
2108
2137
|
|
|
2109
2138
|
converged = False
|
|
2139
|
+
damp = 0.5
|
|
2140
|
+
prev_err = np.inf
|
|
2110
2141
|
for it in range(max_iter):
|
|
2111
2142
|
# Robust RR solver (Nielsen & Lia 2022)
|
|
2112
2143
|
V, x, y = solve_rachford_rice(z, K)
|
|
@@ -2121,12 +2152,19 @@ class SWMultiComponentFlash:
|
|
|
2121
2152
|
# Gamma-phi K-value: K_i = γ_i × φ_i^L / φ_i^V
|
|
2122
2153
|
K_new = np.clip(gamma_eff * phi_L / (phi_V + 1e-30), 1e-10, 1e10)
|
|
2123
2154
|
|
|
2124
|
-
|
|
2155
|
+
err = np.max(np.abs(K_new / K - 1.0))
|
|
2156
|
+
if err < tol:
|
|
2125
2157
|
converged = True
|
|
2126
2158
|
K = K_new
|
|
2127
2159
|
break
|
|
2128
2160
|
|
|
2129
|
-
|
|
2161
|
+
# Adaptive damping: accelerate when converging, brake when stalling
|
|
2162
|
+
if err < prev_err:
|
|
2163
|
+
damp = min(damp * 1.1, 0.95)
|
|
2164
|
+
else:
|
|
2165
|
+
damp = max(damp * 0.5, 0.1)
|
|
2166
|
+
prev_err = err
|
|
2167
|
+
|
|
2130
2168
|
K = K * (K_new / K)**damp
|
|
2131
2169
|
|
|
2132
2170
|
# Final compositions with converged K
|
|
@@ -2263,7 +2301,7 @@ class SWMultiComponentFlash:
|
|
|
2263
2301
|
if gamma_aq is not None:
|
|
2264
2302
|
result['gamma'] = gamma_aq
|
|
2265
2303
|
return result
|
|
2266
|
-
except
|
|
2304
|
+
except (ImportError, AttributeError):
|
|
2267
2305
|
pass
|
|
2268
2306
|
|
|
2269
2307
|
# Calculate gamma for gamma-phi method
|