pyrestoolbox 3.1.5__tar.gz → 3.2.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.1.5 → pyrestoolbox-3.2.0}/PKG-INFO +1 -1
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyproject.toml +1 -1
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/brine/_lib_vle_engine.py +6 -3
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/brine/brine.py +3 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/dca/dca.py +11 -2
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/docs/changelist.rst +21 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/layer/layer.py +48 -63
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/recommend/recommend.py +8 -2
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/sensitivity/sensitivity.py +11 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/simtools/simtools.py +6 -80
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/vle/mod.rs +23 -14
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/.github/workflows/build-wheels.yml +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/.gitignore +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/Cargo.lock +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/Cargo.toml +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/LICENSE +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/MANIFEST.in +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/README.rst +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/ResToolbox/privacy_policy.md +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/benchmark_rust_vs_python.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/build_pure_python.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/__init__.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/_accelerator.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/brine/__init__.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/brine/_lib_salting_library.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/classes/__init__.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/classes/classes.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/constants/__init__.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/constants/constants.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/dca/__init__.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/docs/brine.rst +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/docs/dca.rst +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/docs/examples.ipynb +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/docs/gas.rst +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/docs/img/bot.png +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/docs/img/bot_PVTO.png +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/docs/img/bot_img.png +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/docs/img/dry_gas.png +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/docs/img/grid_sat_df.png +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/docs/img/influence.png +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/docs/img/properties_df.png +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/docs/img/sgof.png +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/docs/img/swof.png +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/docs/layer.rst +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/docs/library.rst +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/docs/matbal.rst +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/docs/nodal.rst +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/docs/nodal_examples.ipynb +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/docs/nodal_hydrate_demo.ipynb +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/docs/oil.rst +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/docs/recommend.rst +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/docs/sensitivity.rst +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/docs/simtools.rst +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/gas/__init__.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/gas/gas.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/layer/__init__.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/library/__init__.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/library/component_library.xlsx +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/library/library.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/matbal/__init__.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/matbal/matbal.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/nodal/__init__.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/nodal/nodal.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/oil/__init__.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/oil/_compressibility.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/oil/_constants.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/oil/_correlations.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/oil/_density.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/oil/_harmonize.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/oil/_pvt_class.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/oil/_rate.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/oil/_separator.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/oil/_tables.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/oil/_utils.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/plyasunov/__init__.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/plyasunov/iapws_if97.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/plyasunov/plyasunov_model.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/plyasunov/water_properties.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/recommend/__init__.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/sensitivity/__init__.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/shared_fns/__init__.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/shared_fns/shared_fns.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/simtools/__init__.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/validate/__init__.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/pyrestoolbox/validate/validate.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/setup.cfg +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/setup.py +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/bessel.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/critical_properties/mod.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/dca/hyperbolic.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/dca/mod.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/dca/ransac.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/gas_viscosity/mod.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/gwr.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/lib.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/matbal/mod.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/matbal/objective.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/oil/density.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/oil/mod.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/pseudopressure.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/spycher_pruess/mod.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/spycher_pruess/solubility.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/vle/alpha.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/vle/bip.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/vle/components.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/vle/eos.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/vle/flash.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/vle/fugacity.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/vle/k_init.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/vle/rachford_rice.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/vlp/friction.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/vlp/holdup_bb.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/vlp/holdup_gray.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/vlp/holdup_hb.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/vlp/holdup_wg.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/vlp/ift.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/vlp/mod.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/vlp/pvt_helpers.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/vlp/segment_gas.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/vlp/segment_oil.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/vlp/static_column.rs +0 -0
- {pyrestoolbox-3.1.5 → pyrestoolbox-3.2.0}/src/zfactor/mod.rs +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pyrestoolbox"
|
|
7
|
-
version = "3.
|
|
7
|
+
version = "3.2.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"}]
|
|
@@ -2100,9 +2100,12 @@ class SWMultiComponentFlash:
|
|
|
2100
2100
|
z = np.asarray(z, dtype=float)
|
|
2101
2101
|
z = z / np.sum(z)
|
|
2102
2102
|
|
|
2103
|
-
# Rust acceleration path — gamma is always passed explicitly
|
|
2104
|
-
#
|
|
2105
|
-
#
|
|
2103
|
+
# Rust acceleration path — gamma is always passed explicitly.
|
|
2104
|
+
# The Rust entry point trusts the caller-supplied gamma and never
|
|
2105
|
+
# substitutes its own S&W Eq 8 ks fallback. framework='proposed'
|
|
2106
|
+
# specialised ks models (Dubessy CO2, Akinfiev H2S, Li HC,
|
|
2107
|
+
# Mao-Duan N2, Duan-Sun CO2) are applied in self.calc_gamma()
|
|
2108
|
+
# on the Python side.
|
|
2106
2109
|
if _RUST_AVAILABLE:
|
|
2107
2110
|
try:
|
|
2108
2111
|
gamma_arr = np.asarray(gamma, dtype=float) if gamma is not None \
|
|
@@ -456,6 +456,7 @@ class CO2_Brine_Mixture():
|
|
|
456
456
|
.Rs : CO2 Saturated Brine solution gas ratio (sm3/sm3 or scf/stb), relative to standard conditions
|
|
457
457
|
.Cf_usat : Brine undersaturated compressibility (1/Bar or 1/psi). The compressibility with constant Rs
|
|
458
458
|
.Cf_sat : Brine saturated compressibility (1/Bar or 1/psi). The compressibility with reducing Rs under depletion
|
|
459
|
+
.converged: True if the Spycher-Pruess fugacity iteration reached tolerance, False otherwise
|
|
459
460
|
|
|
460
461
|
Usage example for 5000 psia x 275 deg F and 3% NaCl brine:
|
|
461
462
|
mix = brine.CO2_Brine_Mixture(pres = 5000, temp = 275, ppm = 30000, metric = False)
|
|
@@ -523,6 +524,7 @@ class CO2_Brine_Mixture():
|
|
|
523
524
|
self.Cf_sat = None # Saturated brine compressibility (1/Bar or 1/psi). Compressibility with changing Rs
|
|
524
525
|
self.CO2_sat = False # Flag to determine if in P-T range for saturated liquid CO2 K values
|
|
525
526
|
self.repeat = False # Flag to trigger repeat of calculations depending on whether CO2 is saturated liquid phase
|
|
527
|
+
self.converged = True # Spycher-Pruess fugacity iteration convergence flag
|
|
526
528
|
#self.EzrokhiDenA = None # Ezrokhi coefficient array for density (to be calculated with ezrokhi() function)
|
|
527
529
|
#self.EzrokhiVisB = None # Ezrokhi coefficient array for viscosity (to be calculated with ezrokhi() function)
|
|
528
530
|
self.Rs_STD = None # Dissolved CO2 remaining at standard conditions (sm3/sm3)
|
|
@@ -1125,6 +1127,7 @@ class CO2_Brine_Mixture():
|
|
|
1125
1127
|
iternum += 1
|
|
1126
1128
|
|
|
1127
1129
|
if err > EPS:
|
|
1130
|
+
self.converged = False
|
|
1128
1131
|
import warnings
|
|
1129
1132
|
warnings.warn(
|
|
1130
1133
|
f"Spycher CO2-brine iteration did not converge in {iternum} iterations "
|
|
@@ -323,10 +323,13 @@ def duong_cum(qi, a, m, t):
|
|
|
323
323
|
if np.any(t <= 0):
|
|
324
324
|
raise ValueError("Time must be positive for Duong model")
|
|
325
325
|
|
|
326
|
-
# Generate fine time grid for integration
|
|
326
|
+
# Generate fine time grid for integration. Lower bound must be strictly
|
|
327
|
+
# less than ti to give np.linspace an ascending range, otherwise trapezoid
|
|
328
|
+
# integrates over a descending axis and returns negative cumulative.
|
|
327
329
|
results = np.zeros_like(t, dtype=float)
|
|
328
330
|
for i, ti in enumerate(t):
|
|
329
|
-
|
|
331
|
+
lower = min(0.001, ti * 0.001)
|
|
332
|
+
t_fine = np.linspace(lower, ti, max(500, int(ti * 10)))
|
|
330
333
|
q_fine = qi * t_fine ** (-m) * np.exp(a / (1.0 - m) * (t_fine ** (1.0 - m) - 1.0))
|
|
331
334
|
results[i] = np.trapezoid(q_fine, t_fine)
|
|
332
335
|
|
|
@@ -958,6 +961,12 @@ def forecast(result, t_end, dt=1.0, q_min=0.0, uptime=1.0, ratios=None):
|
|
|
958
961
|
-------
|
|
959
962
|
ForecastResult
|
|
960
963
|
"""
|
|
964
|
+
if dt <= 0:
|
|
965
|
+
raise ValueError(f"dt must be positive, got {dt}")
|
|
966
|
+
if t_end <= 0:
|
|
967
|
+
raise ValueError(f"t_end must be positive, got {t_end}")
|
|
968
|
+
if not (0.0 < uptime <= 1.0):
|
|
969
|
+
raise ValueError(f"uptime must be in (0, 1], got {uptime}")
|
|
961
970
|
t = np.arange(dt, t_end + dt / 2, dt)
|
|
962
971
|
|
|
963
972
|
if result.method == 'duong':
|
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
Changelist in 3.2.0:
|
|
2
|
+
|
|
3
|
+
- **DCA bug fixes**:
|
|
4
|
+
|
|
5
|
+
- ``dca.duong_cum`` — fixed linspace-inversion bug where cumulative volume integrated over a descending axis for ``t < 0.001`` (small-t inputs) and returned a negative value. Lower bound of the integration is now ``min(0.001, t * 0.001)`` so the grid is always ascending.
|
|
6
|
+
- ``dca.forecast`` — now validates ``dt > 0``, ``t_end > 0`` and ``uptime`` in ``(0, 1]`` at entry with clear ``ValueError`` messages. Previously ``dt = 0`` raised an opaque ``ZeroDivisionError`` and out-of-range ``uptime`` was silently accepted.
|
|
7
|
+
|
|
8
|
+
- **Rachford-Rice solver consolidation**: ``simtools.rr_solver`` is now a thin wrapper that delegates to the canonical ``pyrestoolbox.brine._lib_vle_engine.rr_solver`` (Nielsen & Lia 2022). Removes ~100 lines of duplicated iterative code plus the dead ``ensure_numpy_array`` helper. Inputs validated for length/sum before delegation; behaviour is preserved (``EPS_T=1e-15``, ``max_iter=100``).
|
|
9
|
+
|
|
10
|
+
- **Rust Sechenov fallback guard**: ``src/vle/mod.rs::flash_tp_rust`` no longer silently substitutes its S&W Eq 8 ``ks`` fallback when a caller passes all-ones ``gamma`` with ``salinity > 0``. The caller-supplied ``gamma`` is now always trusted, eliminating any risk that Python ``framework='proposed'`` calls bypass the specialised Dubessy/Akinfiev/Li/Mao-Duan/Duan-Sun ``ks`` models. ``calc_equilibrium_rust`` retains the S&W Eq 8 path but now carries a prominent doc warning. Python path unchanged (Python always passes the correct ``gamma``).
|
|
11
|
+
|
|
12
|
+
- **Convergence flag on ``CO2_Brine_Mixture``**: New ``.converged`` attribute on the class. ``True`` after a successful Spycher-Pruess fugacity iteration, ``False`` when the 100-iteration limit is hit (matching the existing ``RuntimeWarning``). Lets downstream callers detect non-convergence programmatically.
|
|
13
|
+
|
|
14
|
+
- **``sensitivity.tornado`` robustness**: Raises ``ValueError`` if ``base_result`` is not finite (NaN/Inf), and if any ``ranges[param]`` has ``lo > hi``. Previously returned ``nan`` or ``inf`` sensitivities that silently corrupted tornado plots.
|
|
15
|
+
|
|
16
|
+
- **``layer`` module dedup**: Five copies of the EXP/LANG dispatch (B-clamp, flow-fraction evaluation) consolidated into three private helpers (``_clamp_b``, ``_b_max``, ``_flow_fraction_at_x``). No behaviour change.
|
|
17
|
+
|
|
18
|
+
- **``recommend`` module docstrings**: ``sg`` on ``recommend_gas_methods`` and ``well_type`` on ``recommend_vlp_method`` are currently unused by the decision logic. Docstrings now flag them as reserved for future rules. Signatures preserved for backward compatibility.
|
|
19
|
+
|
|
20
|
+
- 701 validation tests (up from 696 in 3.1.5).
|
|
21
|
+
|
|
1
22
|
Changelist in 3.1.5:
|
|
2
23
|
|
|
3
24
|
- **Agent-friendly UX**:
|
|
@@ -41,6 +41,35 @@ import numpy.typing as npt
|
|
|
41
41
|
|
|
42
42
|
from pyrestoolbox.shared_fns import bisect_solve
|
|
43
43
|
|
|
44
|
+
|
|
45
|
+
_B_MIN = 1e-6
|
|
46
|
+
_B_MAX_EXP = 709
|
|
47
|
+
_B_MAX_LANG = 25000
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _b_max(method: str) -> float:
|
|
51
|
+
"""Upper bookend for B in bisection for the given Lorenz method."""
|
|
52
|
+
return _B_MAX_LANG if method == "LANG" else _B_MAX_EXP
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _clamp_b(B: float, method: str) -> float:
|
|
56
|
+
"""Clamp B to [_B_MIN, _b_max(method)]."""
|
|
57
|
+
return min(max(B, _B_MIN), _b_max(method))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _flow_fraction_at_x(B: float, x: float, method: str) -> float:
|
|
61
|
+
"""Flow fraction from best phi_h fraction x, given B and method.
|
|
62
|
+
|
|
63
|
+
EXP: (1 - exp(-B*x)) / (1 - exp(-B))
|
|
64
|
+
LANG: (VL * x) / (PL + x) with PL = 1/B, VL = PL + 1
|
|
65
|
+
"""
|
|
66
|
+
if method == "LANG":
|
|
67
|
+
PL = 1 / B
|
|
68
|
+
VL = PL + 1
|
|
69
|
+
return (VL * x) / (PL + x)
|
|
70
|
+
return (1 - np.exp(-B * x)) / (1 - np.exp(-B))
|
|
71
|
+
|
|
72
|
+
|
|
44
73
|
def lorenz2b(lorenz: float, lrnz_method: str = "EXP") -> float:
|
|
45
74
|
""" Returns B-factor that characterizes the Lorenz function
|
|
46
75
|
Lorenz: Lorenz coefficient (0-1)
|
|
@@ -63,37 +92,22 @@ def lorenz2b(lorenz: float, lrnz_method: str = "EXP") -> float:
|
|
|
63
92
|
return 0.0 # Homogeneous — no heterogeneity
|
|
64
93
|
|
|
65
94
|
if lorenz < 0.000333:
|
|
66
|
-
|
|
67
|
-
if method == "LANG":
|
|
68
|
-
B = 1 / 1000
|
|
69
|
-
return B
|
|
95
|
+
return 1 / 1000 if method == "LANG" else 2 / 1000
|
|
70
96
|
if lorenz > 0.997179125528914:
|
|
71
|
-
|
|
72
|
-
if method == "LANG":
|
|
73
|
-
B = 25000
|
|
74
|
-
return B
|
|
97
|
+
return _b_max(method)
|
|
75
98
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if method == "LANG":
|
|
79
|
-
hi = 25000
|
|
80
|
-
lo = 0.000001
|
|
99
|
+
hi = _b_max(method)
|
|
100
|
+
lo = _B_MIN
|
|
81
101
|
args = (lorenz, method)
|
|
82
102
|
|
|
83
103
|
def LorenzErr(args, B):
|
|
84
104
|
lorenz, method = args
|
|
85
|
-
B =
|
|
86
|
-
if method == "
|
|
87
|
-
B = min(B, 709)
|
|
88
|
-
err = 2 * ((1 / (np.exp(B) - 1)) - (1 / B)) + 1 - lorenz
|
|
89
|
-
else:
|
|
90
|
-
B = min(B, 25000)
|
|
105
|
+
B = _clamp_b(B, method)
|
|
106
|
+
if method == "LANG":
|
|
91
107
|
PL = 1 / B
|
|
92
108
|
VL = PL + 1
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
) * 2 - lorenz
|
|
96
|
-
return err
|
|
109
|
+
return (VL - PL * VL * np.log(VL) + PL * VL * np.log(PL) - 0.5) * 2 - lorenz
|
|
110
|
+
return 2 * ((1 / (np.exp(B) - 1)) - (1 / B)) + 1 - lorenz
|
|
97
111
|
|
|
98
112
|
rtol = 0.0000001
|
|
99
113
|
return bisect_solve(args, LorenzErr, lo, hi, rtol)
|
|
@@ -113,16 +127,12 @@ def lorenzfromb(B: float, lrnz_method: str = "EXP") -> float:
|
|
|
113
127
|
method = lrnz_method.upper()
|
|
114
128
|
if B <= 0:
|
|
115
129
|
return 0.0 # B=0 means homogeneous (Lc=0)
|
|
116
|
-
B =
|
|
130
|
+
B = _clamp_b(B, method)
|
|
117
131
|
if method == "LANG":
|
|
118
|
-
B = min(B, 25000)
|
|
119
132
|
PL = 1 / B
|
|
120
133
|
VL = PL + 1
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
B = min(B, 709)
|
|
124
|
-
L = 2 * (1 / (np.exp(B) - 1) - (1 / B)) + 1
|
|
125
|
-
return L
|
|
134
|
+
return (VL - PL * VL * np.log(VL) + PL * VL * np.log(PL) - 0.5) * 2
|
|
135
|
+
return 2 * (1 / (np.exp(B) - 1) - (1 / B)) + 1
|
|
126
136
|
|
|
127
137
|
|
|
128
138
|
def lorenz_from_flow_fraction(
|
|
@@ -155,23 +165,15 @@ def lorenz_from_flow_fraction(
|
|
|
155
165
|
return lorenzfromb(B, method)
|
|
156
166
|
|
|
157
167
|
# Set bookends and first guess of B
|
|
158
|
-
hi =
|
|
159
|
-
lo =
|
|
168
|
+
hi = _B_MAX_EXP
|
|
169
|
+
lo = _B_MIN
|
|
160
170
|
args = (kh_frac, phih_frac, method)
|
|
161
171
|
|
|
162
172
|
def BErr(args, B):
|
|
163
173
|
kh_frac, phih_frac, method = args
|
|
164
174
|
method = method.upper()
|
|
165
|
-
B =
|
|
166
|
-
|
|
167
|
-
B = min(B, 709)
|
|
168
|
-
err = (1 - np.exp(-B * phih_frac)) / (1 - np.exp(-B)) - kh_frac
|
|
169
|
-
else:
|
|
170
|
-
B = min(B, 25000)
|
|
171
|
-
PL = 1 / B
|
|
172
|
-
VL = PL + 1
|
|
173
|
-
err = (VL * phih_frac) / (PL + phih_frac) - kh_frac
|
|
174
|
-
return err
|
|
175
|
+
B = _clamp_b(B, method)
|
|
176
|
+
return _flow_fraction_at_x(B, phih_frac, method) - kh_frac
|
|
175
177
|
|
|
176
178
|
rtol = 0.0000001
|
|
177
179
|
B = bisect_solve(args, BErr, lo, hi, rtol)
|
|
@@ -203,16 +205,8 @@ def lorenz_2_flow_frac(
|
|
|
203
205
|
if B < 0: # Need to calculate B
|
|
204
206
|
B = lorenz2b(lorenz=lorenz, lrnz_method=lrnz_method)
|
|
205
207
|
|
|
206
|
-
B =
|
|
207
|
-
|
|
208
|
-
B = min(B, 709)
|
|
209
|
-
fraction = (1 - np.exp(-B * phih_frac)) / (1 - np.exp(-B))
|
|
210
|
-
else:
|
|
211
|
-
B = min(B, 25000)
|
|
212
|
-
PL = 1 / B
|
|
213
|
-
VL = PL + 1
|
|
214
|
-
fraction = (VL * phih_frac) / (PL + phih_frac)
|
|
215
|
-
return fraction
|
|
208
|
+
B = _clamp_b(B, method)
|
|
209
|
+
return _flow_fraction_at_x(B, phih_frac, method)
|
|
216
210
|
|
|
217
211
|
|
|
218
212
|
def lorenz_2_layers(
|
|
@@ -255,11 +249,7 @@ def lorenz_2_layers(
|
|
|
255
249
|
if B < 0: # Need to calculate B
|
|
256
250
|
B = lorenz2b(lorenz=lorenz, lrnz_method=lrnz_method)
|
|
257
251
|
|
|
258
|
-
B =
|
|
259
|
-
if method == "EXP":
|
|
260
|
-
B = min(B, 709)
|
|
261
|
-
else:
|
|
262
|
-
B = min(B, 25000)
|
|
252
|
+
B = _clamp_b(B, method)
|
|
263
253
|
|
|
264
254
|
user_layers = False
|
|
265
255
|
if len(phi_h_fracs) > 1:
|
|
@@ -280,12 +270,7 @@ def lorenz_2_layers(
|
|
|
280
270
|
sumkh = []
|
|
281
271
|
|
|
282
272
|
for layer in phih:
|
|
283
|
-
|
|
284
|
-
sumkh.append((1 - np.exp(-B * layer)) / (1 - np.exp(-B)))
|
|
285
|
-
else:
|
|
286
|
-
PL = 1 / B
|
|
287
|
-
VL = PL + 1
|
|
288
|
-
sumkh.append((VL * layer) / (PL + layer))
|
|
273
|
+
sumkh.append(_flow_fraction_at_x(B, layer, method))
|
|
289
274
|
|
|
290
275
|
kh = (
|
|
291
276
|
np.array([sumkh[i] - sumkh[i - 1] for i in range(1, len(sumkh))])
|
|
@@ -75,7 +75,9 @@ def recommend_gas_methods(sg: float = 0.65, co2: float = 0, h2s: float = 0,
|
|
|
75
75
|
Parameters
|
|
76
76
|
----------
|
|
77
77
|
sg : float
|
|
78
|
-
Gas specific gravity (default 0.65).
|
|
78
|
+
Gas specific gravity (default 0.65). Currently unused by the decision
|
|
79
|
+
logic — accepted for API consistency and reserved for future rules
|
|
80
|
+
(e.g. heavy gas / condensate-laden streams). Breaking removal deferred.
|
|
79
81
|
co2 : float
|
|
80
82
|
CO2 mole fraction (default 0).
|
|
81
83
|
h2s : float
|
|
@@ -90,6 +92,7 @@ def recommend_gas_methods(sg: float = 0.65, co2: float = 0, h2s: float = 0,
|
|
|
90
92
|
dict of str to MethodRecommendation
|
|
91
93
|
Keys: 'zmethod', 'cmethod'.
|
|
92
94
|
"""
|
|
95
|
+
_ = sg # reserved, see docstring
|
|
93
96
|
inerts = co2 + h2s + n2 + h2
|
|
94
97
|
recs = {}
|
|
95
98
|
|
|
@@ -204,13 +207,16 @@ def recommend_vlp_method(deviation: float = 0,
|
|
|
204
207
|
deviation : float
|
|
205
208
|
Maximum wellbore deviation from vertical in degrees (default 0).
|
|
206
209
|
well_type : str
|
|
207
|
-
'gas' or 'oil' (default 'gas').
|
|
210
|
+
'gas' or 'oil' (default 'gas'). Currently unused by the decision logic
|
|
211
|
+
— accepted for API consistency and reserved for future fluid-specific
|
|
212
|
+
recommendations. Breaking removal deferred.
|
|
208
213
|
|
|
209
214
|
Returns
|
|
210
215
|
-------
|
|
211
216
|
dict of str to MethodRecommendation
|
|
212
217
|
Key: 'vlp_method'.
|
|
213
218
|
"""
|
|
219
|
+
_ = well_type # reserved, see docstring
|
|
214
220
|
recs = {}
|
|
215
221
|
|
|
216
222
|
if deviation <= 30:
|
|
@@ -39,6 +39,7 @@ __all__ = [
|
|
|
39
39
|
'SweepResult', 'TornadoEntry', 'TornadoResult',
|
|
40
40
|
]
|
|
41
41
|
|
|
42
|
+
import math
|
|
42
43
|
from dataclasses import dataclass, field
|
|
43
44
|
from typing import Callable, Dict, Any, List, Optional
|
|
44
45
|
|
|
@@ -172,11 +173,21 @@ def tornado(func: Callable, base_kwargs: Dict[str, Any],
|
|
|
172
173
|
base_raw = func(**base_kwargs)
|
|
173
174
|
base_val = _extract_result(base_raw, result_key)
|
|
174
175
|
base_result = float(base_val)
|
|
176
|
+
if not math.isfinite(base_result):
|
|
177
|
+
raise ValueError(
|
|
178
|
+
f"base_result is not finite ({base_result}); "
|
|
179
|
+
f"tornado sensitivities would be undefined. "
|
|
180
|
+
f"Check base_kwargs and result_key."
|
|
181
|
+
)
|
|
175
182
|
|
|
176
183
|
entries = []
|
|
177
184
|
for param, (lo, hi) in ranges.items():
|
|
178
185
|
if param not in base_kwargs:
|
|
179
186
|
raise ValueError(f"Parameter '{param}' not found in base_kwargs")
|
|
187
|
+
if lo > hi:
|
|
188
|
+
raise ValueError(
|
|
189
|
+
f"ranges['{param}'] = ({lo}, {hi}): low must be <= high"
|
|
190
|
+
)
|
|
180
191
|
|
|
181
192
|
kwargs_lo = dict(base_kwargs)
|
|
182
193
|
kwargs_lo[param] = lo
|
|
@@ -1025,19 +1025,11 @@ def zip_check_sim_deck(files2scrape = [], tozip = True, console_summary = True,
|
|
|
1025
1025
|
else:
|
|
1026
1026
|
return
|
|
1027
1027
|
|
|
1028
|
-
def ensure_numpy_array(data):
|
|
1029
|
-
if isinstance(data, list):
|
|
1030
|
-
return np.array(data)
|
|
1031
|
-
elif isinstance(data, np.ndarray):
|
|
1032
|
-
return data
|
|
1033
|
-
else:
|
|
1034
|
-
raise ValueError("Input is neither a list nor a numpy array.")
|
|
1035
|
-
|
|
1036
1028
|
def rr_solver(
|
|
1037
1029
|
zi: Union[npt.ArrayLike, list], ki: Union[npt.ArrayLike, list]
|
|
1038
1030
|
) -> Tuple[int, np.ndarray, np.ndarray, float, float]:
|
|
1039
1031
|
"""
|
|
1040
|
-
Solves for the root of the Rachford-Rice equation using a method that
|
|
1032
|
+
Solves for the root of the Rachford-Rice equation using a method that
|
|
1041
1033
|
gracefully handles catastrophic roundoff errors.
|
|
1042
1034
|
The method is outlined in 2022 'Fluid Phase Equilibria' paper by M. Nielsen & H. Lia
|
|
1043
1035
|
|
|
@@ -1052,8 +1044,10 @@ def rr_solver(
|
|
|
1052
1044
|
V: Vapor molar fraction (float)
|
|
1053
1045
|
L: Liquid molar fraction (float)
|
|
1054
1046
|
"""
|
|
1055
|
-
|
|
1056
|
-
|
|
1047
|
+
from pyrestoolbox.brine._lib_vle_engine import rr_solver as _brine_rr_solver
|
|
1048
|
+
|
|
1049
|
+
zi = np.asarray(zi, dtype=float)
|
|
1050
|
+
ki = np.asarray(ki, dtype=float)
|
|
1057
1051
|
|
|
1058
1052
|
if len(zi) == 0 or len(ki) == 0:
|
|
1059
1053
|
raise ValueError("zi and ki arrays must not be empty")
|
|
@@ -1063,76 +1057,8 @@ def rr_solver(
|
|
|
1063
1057
|
zi_sum = np.sum(zi)
|
|
1064
1058
|
if zi_sum <= 0:
|
|
1065
1059
|
raise ValueError("Sum of zi must be positive")
|
|
1066
|
-
zi = zi / zi_sum # Normalize feed compositions
|
|
1067
|
-
|
|
1068
|
-
def rr(V: float) -> float:
|
|
1069
|
-
return np.dot(zi, (ki - 1) / (1 + V * (ki - 1)))
|
|
1070
|
-
|
|
1071
|
-
def h(b: float) -> float: # Eq 12b
|
|
1072
|
-
return np.sum(zi*b/(1+b*(phi_min - ci)))
|
|
1073
|
-
|
|
1074
|
-
def dh(b: float) -> float: # Eq 16b
|
|
1075
|
-
return np.sum(zi/(1 + b*(phi_min - ci)) ** 2)
|
|
1076
|
-
|
|
1077
|
-
N_it = 0
|
|
1078
|
-
|
|
1079
|
-
# Assess if solution nearest vapor or liquid solutions
|
|
1080
|
-
# and appropriately assign pseudo phase and ki's
|
|
1081
|
-
near_vapor = False
|
|
1082
|
-
if rr(0.5) > 0:
|
|
1083
|
-
near_vapor = True
|
|
1084
|
-
|
|
1085
|
-
ki_hat = ki
|
|
1086
|
-
if near_vapor:
|
|
1087
|
-
ki_hat = 1/ki
|
|
1088
|
-
|
|
1089
|
-
ci = np.where(np.abs(1 - ki_hat) > 1e-10, 1/(1-ki_hat), 0.0) # Eq 10
|
|
1090
|
-
|
|
1091
|
-
# Will use the transformed solution in terms of b (Eq 14b)
|
|
1092
|
-
phi_max = min(1/(1-min(ki_hat)), 0.5) # Eq 11a
|
|
1093
|
-
phi_min = 1/(1-max(ki_hat)) # Eq 11b with upper 0.5 limit given we have chosen this phase
|
|
1094
|
-
b_min = 1/(phi_max - phi_min) # Eq 15
|
|
1095
|
-
b_max = np.inf
|
|
1096
|
-
|
|
1097
|
-
b = 1/(0.25 - phi_min)
|
|
1098
|
-
N_it = 0
|
|
1099
|
-
h_b = np.inf
|
|
1100
|
-
|
|
1101
|
-
while abs(h_b) > EPS_T:
|
|
1102
|
-
N_it += 1
|
|
1103
1060
|
|
|
1104
|
-
|
|
1105
|
-
dh_b = dh(b)
|
|
1106
|
-
|
|
1107
|
-
if h_b > 0:
|
|
1108
|
-
b_max = b
|
|
1109
|
-
else:
|
|
1110
|
-
b_min = b
|
|
1111
|
-
|
|
1112
|
-
b = b - h_b / dh_b
|
|
1113
|
-
|
|
1114
|
-
if b < b_min or b > b_max:
|
|
1115
|
-
b = (b_min + b_max) / 2
|
|
1116
|
-
|
|
1117
|
-
if N_it > MAX_ITR:
|
|
1118
|
-
break
|
|
1119
|
-
|
|
1120
|
-
ui = -zi*ci*b/(1+b*(phi_min-ci)) # Eq 27b
|
|
1121
|
-
phi = (1+b*phi_min)/b # Rearranged Eq 14b
|
|
1122
|
-
|
|
1123
|
-
if near_vapor:
|
|
1124
|
-
L = phi
|
|
1125
|
-
V = 1 - L
|
|
1126
|
-
yi = ui
|
|
1127
|
-
xi = ki_hat * ui # Eq 28
|
|
1128
|
-
|
|
1129
|
-
else:
|
|
1130
|
-
V = phi
|
|
1131
|
-
L = 1 - V
|
|
1132
|
-
xi = ui
|
|
1133
|
-
yi = ki_hat * ui # Eq 28
|
|
1134
|
-
|
|
1135
|
-
return N_it, yi, xi, V, L
|
|
1061
|
+
return _brine_rr_solver(zi, ki, tol=EPS_T, max_iter=MAX_ITR)
|
|
1136
1062
|
|
|
1137
1063
|
|
|
1138
1064
|
# =============================================================================
|
|
@@ -57,10 +57,14 @@ fn resolve_comp_names(comp_names: &[String]) -> PyResult<Vec<usize>> {
|
|
|
57
57
|
/// * `p_pa` - Pressure in Pascal
|
|
58
58
|
/// * `z` - Feed composition (will be normalized)
|
|
59
59
|
/// * `comp_names` - Component names (e.g., ["H2O", "CH4", "CO2"])
|
|
60
|
-
/// * `salinity` - NaCl molality (mol/kg water)
|
|
60
|
+
/// * `salinity` - NaCl molality (mol/kg water). Informational only; the caller
|
|
61
|
+
/// is responsible for baking salinity into `gamma`.
|
|
61
62
|
/// * `mode` - "AQ" for aqueous phase BIPs, "NA" for non-aqueous
|
|
62
|
-
/// * `gamma` - Activity coefficient array (same length as z).
|
|
63
|
-
///
|
|
63
|
+
/// * `gamma` - Activity coefficient array (same length as z). Must be computed
|
|
64
|
+
/// by the caller. For framework='proposed', specialized ks models
|
|
65
|
+
/// (Dubessy/Akinfiev/Li/Mao-Duan/Duan-Sun) live on the Python side
|
|
66
|
+
/// — this Rust entry point must not substitute its own S&W Eq 8
|
|
67
|
+
/// fallback silently. Use `1.0` for gases in freshwater.
|
|
64
68
|
///
|
|
65
69
|
/// # Returns
|
|
66
70
|
/// Tuple (V, x_vec, y_vec) where V is vapor fraction, x is liquid comp, y is vapor comp.
|
|
@@ -74,6 +78,10 @@ pub fn flash_tp_rust(
|
|
|
74
78
|
mode: String,
|
|
75
79
|
gamma: Vec<f64>,
|
|
76
80
|
) -> PyResult<(f64, Vec<f64>, Vec<f64>)> {
|
|
81
|
+
// Salinity arg is retained for signature stability / future diagnostics;
|
|
82
|
+
// currently unused because gamma is always caller-supplied.
|
|
83
|
+
let _ = salinity;
|
|
84
|
+
|
|
77
85
|
// Validate inputs
|
|
78
86
|
if !t_k.is_finite() || t_k <= 0.0 {
|
|
79
87
|
return Err(PyValueError::new_err("Temperature must be positive and finite"));
|
|
@@ -104,23 +112,13 @@ pub fn flash_tp_rust(
|
|
|
104
112
|
|
|
105
113
|
let comp_indices = resolve_comp_names(&comp_names)?;
|
|
106
114
|
|
|
107
|
-
// If salinity > 0 and mode is AQ and gamma is all-ones, compute gamma internally
|
|
108
|
-
let gamma_eff = if salinity > 0.0
|
|
109
|
-
&& mode_aq
|
|
110
|
-
&& gamma.iter().all(|&g| (g - 1.0).abs() < 1e-15)
|
|
111
|
-
{
|
|
112
|
-
flash::calc_gamma(&comp_indices, t_k, salinity)
|
|
113
|
-
} else {
|
|
114
|
-
gamma
|
|
115
|
-
};
|
|
116
|
-
|
|
117
115
|
let (v, x, y, _converged) = flash::flash_tp(
|
|
118
116
|
t_k,
|
|
119
117
|
p_pa,
|
|
120
118
|
&z,
|
|
121
119
|
&comp_indices,
|
|
122
120
|
mode_aq,
|
|
123
|
-
&
|
|
121
|
+
&gamma,
|
|
124
122
|
200,
|
|
125
123
|
1e-10,
|
|
126
124
|
);
|
|
@@ -134,6 +132,17 @@ pub fn flash_tp_rust(
|
|
|
134
132
|
/// Flash 2 (kij_NA) -> non-aqueous phase y
|
|
135
133
|
/// True K-values: K_i = y_i(Flash 2) / x_i(Flash 1)
|
|
136
134
|
///
|
|
135
|
+
/// # Warning
|
|
136
|
+
/// This entry point computes Sechenov activity coefficients internally using
|
|
137
|
+
/// **S&W Equation 8 only**. It does NOT route CO2/H2S through the specialized
|
|
138
|
+
/// Dubessy/Akinfiev/Li/Mao-Duan/Duan-Sun models used by the Python
|
|
139
|
+
/// framework='proposed' path. Callers who need the specialized ks models must
|
|
140
|
+
/// compute gamma Python-side and call `flash_tp_rust` twice (AQ then NA) with
|
|
141
|
+
/// the pre-computed gamma, rather than using this convenience entry point.
|
|
142
|
+
/// Not currently called from Python `SWFlashEngine` — Python computes gamma
|
|
143
|
+
/// via `self.calc_gamma(...)` with the correct framework dispatch and calls
|
|
144
|
+
/// `flash_tp_rust` directly.
|
|
145
|
+
///
|
|
137
146
|
/// # Arguments
|
|
138
147
|
/// * `t_k` - Temperature in Kelvin
|
|
139
148
|
/// * `p_pa` - Pressure in Pascal
|
|
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
|
|
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
|