pyrestoolbox 3.8.0__tar.gz → 3.8.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/Cargo.lock +1 -1
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/Cargo.toml +1 -1
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/PKG-INFO +1 -1
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyproject.toml +1 -1
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/brine/_lib_vle_engine.py +133 -9
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/docs/changelist.rst +8 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/nodal/nodal.py +10 -1
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/vle/flash.rs +178 -9
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/vlp/march.rs +4 -1
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/.github/complexity-baseline.json +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/.github/scripts/check_complexity.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/.github/workflows/build-wheels.yml +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/.github/workflows/tests.yml +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/.gitignore +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/LICENSE +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/MANIFEST.in +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/README.rst +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/ResToolbox/privacy_policy.md +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/build_pure_python.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/clippy.toml +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/mcp/Dockerfile +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/mcp/README.md +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/mcp/pyproject.toml +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/mcp/pyrestoolbox_mcp/__init__.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/mcp/pyrestoolbox_mcp/serialize.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/mcp/pyrestoolbox_mcp/server.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/mcp/tests/test_server.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/__init__.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/_accelerator.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/brine/__init__.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/brine/_lib_dielectric.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/brine/_lib_maoduan_viscosity.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/brine/_lib_pitzer_params.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/brine/_lib_salting_library.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/brine/appelo_volumes.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/brine/brine.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/brine/iapws_viscosity.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/brine/jones_dole_viscosity.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/brine/kestin_nacl_viscosity.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/brine/pr_vphi.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/brine/viscosity_route.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/brine/vphi_route.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/classes/__init__.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/classes/classes.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/constants/__init__.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/constants/constants.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/dca/__init__.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/dca/dca.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/docs/brine.rst +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/docs/dca.rst +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/docs/examples.ipynb +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/docs/gas.rst +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/docs/img/bot.png +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/docs/img/bot_PVTO.png +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/docs/img/bot_img.png +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/docs/img/dry_gas.png +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/docs/img/grid_sat_df.png +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/docs/img/influence.png +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/docs/img/properties_df.png +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/docs/img/sgof.png +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/docs/img/swof.png +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/docs/index.rst +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/docs/layer.rst +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/docs/library.rst +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/docs/matbal.rst +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/docs/nodal.rst +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/docs/nodal_examples.ipynb +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/docs/nodal_hydrate_demo.ipynb +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/docs/oil.rst +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/docs/plyasunov.rst +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/docs/recommend.rst +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/docs/sensitivity.rst +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/docs/simtools.rst +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/gas/__init__.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/gas/_hydrate.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/gas/gas.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/layer/__init__.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/layer/layer.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/library/__init__.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/library/component_library.xlsx +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/library/library.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/matbal/__init__.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/matbal/matbal.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/nodal/__init__.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/oil/__init__.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/oil/_compressibility.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/oil/_constants.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/oil/_correlations.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/oil/_density.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/oil/_harmonize.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/oil/_pvt_class.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/oil/_rate.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/oil/_separator.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/oil/_tables.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/oil/_utils.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/plyasunov/__init__.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/plyasunov/iapws_if97.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/plyasunov/plyasunov_model.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/plyasunov/water_properties.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/recommend/__init__.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/recommend/recommend.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/sensitivity/__init__.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/sensitivity/sensitivity.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/shared_fns/__init__.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/shared_fns/shared_fns.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/simtools/__init__.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/simtools/_aquifer.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/simtools/_decks.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/simtools/_pvt_tables.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/simtools/_relperm.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/simtools/_vfp.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/simtools/simtools.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/validate/__init__.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/pyrestoolbox/validate/validate.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/setup.cfg +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/setup.py +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/bessel.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/brine_visc/mod.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/brine_visc/salt.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/brine_visc/water.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/critical_properties/mod.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/dca/hyperbolic.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/dca/mod.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/dca/ransac.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/gas_viscosity/mod.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/gwr.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/lib.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/matbal/mod.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/matbal/objective.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/oil/density.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/oil/mod.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/pseudopressure.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/spycher_pruess/mod.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/spycher_pruess/solubility.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/vle/alpha.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/vle/bip.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/vle/components.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/vle/eos.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/vle/fugacity.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/vle/k_init.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/vle/mod.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/vle/rachford_rice.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/vlp/constants.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/vlp/friction.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/vlp/holdup_bb.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/vlp/holdup_gray.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/vlp/holdup_wg.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/vlp/ift.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/vlp/mod.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/vlp/pvt_helpers.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/src/vlp/static_column.rs +0 -0
- {pyrestoolbox-3.8.0 → pyrestoolbox-3.8.2}/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.8.
|
|
7
|
+
version = "3.8.2"
|
|
8
8
|
description = "pyResToolbox - A collection of Reservoir Engineering Utilities"
|
|
9
9
|
license = {text = "GPL-3.0-or-later"}
|
|
10
10
|
authors = [{name = "Mark W. Burgoyne", email = "mark.w.burgoyne@gmail.com"}]
|
|
@@ -67,6 +67,14 @@ R_GAS = 8.314462 # J/(mol·K)
|
|
|
67
67
|
OMEGA_A = 0.45724
|
|
68
68
|
OMEGA_B = 0.07780
|
|
69
69
|
MW_NACL = 58.4428 # NaCl molar weight (matches brine.MWSAL)
|
|
70
|
+
# flash_tp trivial-solution test: max|ln K| below this means both labelled
|
|
71
|
+
# phases collapsed onto one composition (K -> 1). Gas-water K-values sit
|
|
72
|
+
# orders of magnitude from 1, so a genuine split never comes near it.
|
|
73
|
+
TRIVIAL_LNK_TOL = 1e-6
|
|
74
|
+
# Tangent-plane stability (Michelsen 1982, Fluid Phase Equilib. 9:1-19): a
|
|
75
|
+
# non-trivial stationary point with sum(Y) above 1 + this makes the feed
|
|
76
|
+
# unstable. The margin keeps round-off at sum(Y) = 1 from reading as a split.
|
|
77
|
+
STABILITY_TM_TOL = 1e-8
|
|
70
78
|
MW_H2O = 18.015
|
|
71
79
|
|
|
72
80
|
# H2 critical temperature for BIP correlations (manuscript value, NIST)
|
|
@@ -1130,6 +1138,11 @@ def rr_solver(
|
|
|
1130
1138
|
return N_it, yi, xi, V, L
|
|
1131
1139
|
|
|
1132
1140
|
|
|
1141
|
+
def _is_trivial_k(K: np.ndarray) -> bool:
|
|
1142
|
+
"""True when every K is within TRIVIAL_LNK_TOL of 1 in log terms."""
|
|
1143
|
+
return bool(np.max(np.abs(np.log(K))) < TRIVIAL_LNK_TOL)
|
|
1144
|
+
|
|
1145
|
+
|
|
1133
1146
|
def solve_rachford_rice(z: np.ndarray, K: np.ndarray) -> Tuple[float, np.ndarray, np.ndarray]:
|
|
1134
1147
|
"""
|
|
1135
1148
|
Convenience wrapper around rr_solver for flash calculations.
|
|
@@ -2059,6 +2072,16 @@ class SWMultiComponentFlash:
|
|
|
2059
2072
|
tol: Convergence tolerance on K-values
|
|
2060
2073
|
|
|
2061
2074
|
Returns: V (vapor fraction), x (liquid), y (vapor), converged (bool)
|
|
2075
|
+
|
|
2076
|
+
When successive substitution collapses onto the trivial solution
|
|
2077
|
+
(all K -> 1; e.g. a feed so dilute in gas that the trial vapor stays
|
|
2078
|
+
water-dominated and both phases take the liquid root), a tangent-plane
|
|
2079
|
+
stability test decides. A stable feed returns single phase, converged:
|
|
2080
|
+
V = 0 when water is the majority component (x = z), else V = 1 (y = z).
|
|
2081
|
+
The other composition is the incipient-phase stationary point, or the
|
|
2082
|
+
feed itself when no such phase exists on the cubic. An unstable feed
|
|
2083
|
+
is re-flashed from K seeded by the stability test. converged is False
|
|
2084
|
+
only if the result is still trivial or the test is inconclusive.
|
|
2062
2085
|
"""
|
|
2063
2086
|
z = np.asarray(z, dtype=float)
|
|
2064
2087
|
z = z / np.sum(z)
|
|
@@ -2107,9 +2130,41 @@ class SWMultiComponentFlash:
|
|
|
2107
2130
|
gamma_eff = np.asarray(gamma, dtype=float)
|
|
2108
2131
|
|
|
2109
2132
|
# Initialize K-values (Wilson + gamma for initial estimate)
|
|
2110
|
-
|
|
2111
|
-
|
|
2133
|
+
K0 = self._wilson_k_values(T_K, P_Pa) * gamma_eff
|
|
2134
|
+
K0[self.iw] = min(K0[self.iw], 0.01)
|
|
2135
|
+
|
|
2136
|
+
eos = (Ai, Bi, sqrt_Ai, onemk)
|
|
2137
|
+
K, converged = self._ss_iterate(z, K0, eos, gamma_eff, max_iter, tol)
|
|
2138
|
+
|
|
2139
|
+
# SS collapsed onto K = 1: both labelled phases took one composition,
|
|
2140
|
+
# so V says nothing. Settle it with a stability test instead.
|
|
2141
|
+
if _is_trivial_k(K):
|
|
2142
|
+
verdict = self._resolve_trivial(z, K0, eos, gamma_eff, max_iter, tol)
|
|
2143
|
+
if verdict is not None and verdict[0] == 'stable':
|
|
2144
|
+
_, feed_liquid, incipient = verdict
|
|
2145
|
+
if feed_liquid:
|
|
2146
|
+
return 0.0, z.copy(), incipient, True
|
|
2147
|
+
return 1.0, incipient, z.copy(), True
|
|
2148
|
+
if verdict is not None:
|
|
2149
|
+
K, converged = self._ss_iterate(z, verdict[1], eos, gamma_eff,
|
|
2150
|
+
max_iter, tol)
|
|
2151
|
+
|
|
2152
|
+
# Final compositions with converged K
|
|
2153
|
+
V, x, y = solve_rachford_rice(z, K)
|
|
2154
|
+
x = np.clip(x, 1e-15, None)
|
|
2155
|
+
y = np.clip(y, 1e-15, None)
|
|
2156
|
+
x = x / np.sum(x)
|
|
2157
|
+
y = y / np.sum(y)
|
|
2158
|
+
if _is_trivial_k(K):
|
|
2159
|
+
converged = False
|
|
2160
|
+
return V, x, y, converged
|
|
2112
2161
|
|
|
2162
|
+
def _ss_iterate(self, z: np.ndarray, K: np.ndarray, eos: tuple,
|
|
2163
|
+
gamma_eff: np.ndarray, max_iter: int,
|
|
2164
|
+
tol: float) -> Tuple[np.ndarray, bool]:
|
|
2165
|
+
"""Damped successive substitution from K. Returns (K, converged)."""
|
|
2166
|
+
Ai, Bi, sqrt_Ai, onemk = eos
|
|
2167
|
+
K = K.copy()
|
|
2113
2168
|
converged = False
|
|
2114
2169
|
damp = 0.5
|
|
2115
2170
|
prev_err = np.inf
|
|
@@ -2145,15 +2200,84 @@ class SWMultiComponentFlash:
|
|
|
2145
2200
|
prev_err = err
|
|
2146
2201
|
|
|
2147
2202
|
K = K * (K_new / K)**damp
|
|
2203
|
+
return K, converged
|
|
2148
2204
|
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
return V, x, y, converged
|
|
2205
|
+
def _stability_trial(self, lnf_feed: np.ndarray, seed: np.ndarray,
|
|
2206
|
+
trial_phase: str, eos: tuple, ln_gamma: np.ndarray,
|
|
2207
|
+
max_iter: int, tol: float) -> Optional[Tuple[float, np.ndarray]]:
|
|
2208
|
+
"""
|
|
2209
|
+
Tangent-plane stationary point by direct substitution (Michelsen 1982):
|
|
2210
|
+
ln Y_i = ln f_i(z) - ln phi_i(y), less ln gamma_i for a liquid trial.
|
|
2156
2211
|
|
|
2212
|
+
Returns (sum Y, normalised y), or None when the cubic fails or the
|
|
2213
|
+
search does not settle (inconclusive).
|
|
2214
|
+
"""
|
|
2215
|
+
Ai, Bi, sqrt_Ai, onemk = eos
|
|
2216
|
+
lnY = np.log(np.clip(seed / np.sum(seed), 1e-300, None))
|
|
2217
|
+
for _ in range(max_iter):
|
|
2218
|
+
Y = np.exp(lnY)
|
|
2219
|
+
phi = self._calc_fugacity_fast(Y / np.sum(Y), Ai, Bi, sqrt_Ai, onemk,
|
|
2220
|
+
trial_phase)
|
|
2221
|
+
if not np.all(np.isfinite(phi)):
|
|
2222
|
+
return None
|
|
2223
|
+
lnY_new = lnf_feed - np.log(phi)
|
|
2224
|
+
if trial_phase == 'liquid':
|
|
2225
|
+
lnY_new = lnY_new - ln_gamma
|
|
2226
|
+
step = np.max(np.abs(lnY_new - lnY))
|
|
2227
|
+
lnY = lnY_new
|
|
2228
|
+
if step < tol:
|
|
2229
|
+
Y = np.exp(lnY)
|
|
2230
|
+
return float(np.sum(Y)), Y / np.sum(Y)
|
|
2231
|
+
return None
|
|
2232
|
+
|
|
2233
|
+
def _resolve_trivial(self, z: np.ndarray, K0: np.ndarray, eos: tuple,
|
|
2234
|
+
gamma_eff: np.ndarray, max_iter: int, tol: float):
|
|
2235
|
+
"""
|
|
2236
|
+
Stability test on the feed after SS collapsed onto K = 1.
|
|
2237
|
+
|
|
2238
|
+
The feed is taken as aqueous (liquid root, with gamma) when water is
|
|
2239
|
+
the majority component, else as the non-aqueous phase (vapor root).
|
|
2240
|
+
Vapor-like trials seeded from K0*z and from the gas components, and
|
|
2241
|
+
liquid-like trials seeded from z/K0 and from pure water, search for a
|
|
2242
|
+
second phase; any unstable trial wins.
|
|
2243
|
+
|
|
2244
|
+
Returns ('stable', feed_liquid, incipient) where incipient is the
|
|
2245
|
+
opposite-phase stationary point (the feed itself when that trial
|
|
2246
|
+
collapsed, i.e. no such phase exists on the cubic), ('split', K) with
|
|
2247
|
+
K seeded from an unstable trial, or None when inconclusive.
|
|
2248
|
+
"""
|
|
2249
|
+
Ai, Bi, sqrt_Ai, onemk = eos
|
|
2250
|
+
feed_liquid = bool(z[self.iw] > 0.5)
|
|
2251
|
+
ln_gamma = np.log(gamma_eff)
|
|
2252
|
+
zc = np.clip(z, 1e-300, None)
|
|
2253
|
+
phi_z = self._calc_fugacity_fast(zc, Ai, Bi, sqrt_Ai, onemk,
|
|
2254
|
+
'liquid' if feed_liquid else 'vapor')
|
|
2255
|
+
if not np.all(np.isfinite(phi_z)):
|
|
2256
|
+
return None
|
|
2257
|
+
lnf = np.log(zc) + np.log(phi_z) + (ln_gamma if feed_liquid else 0.0)
|
|
2258
|
+
|
|
2259
|
+
# Wilson-type seeds alone can start water-dominated and collapse onto
|
|
2260
|
+
# the feed even when a split exists, so each trial phase also gets a
|
|
2261
|
+
# near-pure seed: gas-only for the vapor, water-only for the liquid.
|
|
2262
|
+
not_w = np.arange(len(z)) != self.iw
|
|
2263
|
+
gas_seed = np.where(not_w, zc, 0.0) + 1e-10
|
|
2264
|
+
water_seed = np.where(not_w, 1e-10, 1.0)
|
|
2265
|
+
incipient = z.copy()
|
|
2266
|
+
seeds = (('vapor', K0 * zc), ('vapor', gas_seed),
|
|
2267
|
+
('liquid', zc / K0), ('liquid', water_seed))
|
|
2268
|
+
for phase, seed in seeds:
|
|
2269
|
+
res = self._stability_trial(lnf, seed, phase, eos, ln_gamma,
|
|
2270
|
+
max_iter, tol)
|
|
2271
|
+
if res is None:
|
|
2272
|
+
return None
|
|
2273
|
+
sum_Y, y = res
|
|
2274
|
+
if _is_trivial_k(y / zc):
|
|
2275
|
+
continue
|
|
2276
|
+
if sum_Y > 1.0 + STABILITY_TM_TOL:
|
|
2277
|
+
return 'split', (y / zc if phase == 'vapor' else zc / y)
|
|
2278
|
+
if (phase == 'vapor') == feed_liquid:
|
|
2279
|
+
incipient = y
|
|
2280
|
+
return 'stable', feed_liquid, incipient
|
|
2157
2281
|
|
|
2158
2282
|
def calc_equilibrium(self, T_K: float, P_Pa: float, z: np.ndarray,
|
|
2159
2283
|
salinity_method: str = 'gamma_phi',
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
Changelist in 3.8.2:
|
|
2
|
+
|
|
3
|
+
- **BRINE (internal VLE flash): feeds far from saturation now return a proper single-phase result instead of an arbitrary phase label (GitHub issue #5).** The Soreide-Whitson flash behind ``calc_gas_brine_equilibrium`` (``SWMultiComponentFlash.flash_tp``) used successive substitution with no stability test. When the trial phase started on the feed's side of the cubic, both phases took the same root, the K-values collapsed to 1 and Rachford-Rice reported V = 0 or V = 1 from a rounding-level residual, with ``converged=True``; the Python and Rust paths could disagree on which. This hit about a third of a broad sweep of feeds (very water-rich or very gas-rich). The reported case, 1 kg water with 5e-7 mol each of H2 and CH4 at 1 mol/kg NaCl, 45 degC and 100 bar, came back all vapour. A collapsed iteration is now settled by a Michelsen (1982) tangent-plane stability test with Wilson and near-pure trial phases, in Python and Rust (parity 1.5e-14). A stable feed returns single phase and converged, as aqueous (V = 0, x = feed) when water is the majority component and as the non-aqueous phase (V = 1) otherwise; the other composition is the incipient phase where one exists. An unstable feed is re-flashed from the stability test's K-values. ``converged=False`` is returned only if that still fails. Converged two-phase results are unchanged, so ``SoreideWhitson`` and every documented value are unaffected: the public path flashes gas-excess feeds only. ``SWMultiComponentFlash`` remains internal; there is no public interface for splitting a finite inventory into phase amounts.
|
|
4
|
+
|
|
5
|
+
Changelist in 3.8.1:
|
|
6
|
+
|
|
7
|
+
- **NODAL: condensate that drops out in the tubing is treated as a saturated light liquid, not a dead oil.** The gas VLP marches gave dropped-out condensate the Beggs-Robinson dead-oil viscosity (Rs = 0), which is 2-5 times too viscous for a liquid in equilibrium with gas (up to about 16 cP at low temperature) and made the Woldesemayat-Ghajar BHP with dropout exceed the no-dropout value. The dropped liquid now takes the Beggs-Robinson live-oil viscosity at the Standing (1947) solution GOR at local pressure and temperature, with the bubble point at the local pressure (0.25-1.6 cP for 45-55 API condensate), in Python and Rust. Only gas wells with ``pr`` supplied (condensate dropout on) are affected; for 5 MMscf/d with CGR 10, 10 bbl/d water and 3000 psia reservoir pressure (6000 ft tubing), BHP moves from 877.6 to 850.4 psia for WG and from 635.6 to 652.9 psia for HB (its holdup correlation runs through the liquid viscosity number), with Gray and BB within 0.4%. With dropout on, WG BHP is now below the no-dropout value, as it should be with less liquid in the tubing.
|
|
8
|
+
|
|
1
9
|
Changelist in 3.8.0:
|
|
2
10
|
|
|
3
11
|
- **NODAL: water viscosity in the VLP marches comes from the library's brine model (documented values move).** The marches used a private water-viscosity formula that evaluated a natural-log fit in degC as a base-10 fit in degF, so water viscosity was 12-30x too low (0.054 cP at 100 degF against 0.68; floored at 0.01 cP above about 200 degF), understating liquid friction and holdup in every method. Both the Python and Rust marches now use the same gas-free brine viscosity ``brine_props`` reports (IAPWS-2008 water with IF97 density, Jones-Dole NaCl ratio, Kestin pressure factor), with the salinity taken from ``wsg`` via McCain's gamma_w = 1 + 0.695e-6 ppm; Rust carries a port of that chain matching Python to 1e-14. Wells without water are unchanged. With water the change is modest for small water rates (the documented gas examples with 10 bbl/d move by -0.1 to -45.6 psi; WG moves most because realistic viscosity puts the liquid Reynolds number below Chisholm's laminar threshold) and larger with more water (5 MMscf/d with 500 bbl/d: HB +9%, WG +5%). The pure-Python march is slower (about 15 ms per ``fbhp`` against 3); the Rust path is about 0.2-0.3 ms.
|
|
@@ -1048,8 +1048,17 @@ def _condensate_dropout(cgr, qg_mmscfd, p_avg, pr, osg, qw_bwpd, wsg):
|
|
|
1048
1048
|
|
|
1049
1049
|
|
|
1050
1050
|
def _condensate_vis(pr, cgr_local, gsg, api, temp_f, p_avg, oil_vis):
|
|
1051
|
+
"""Viscosity (cP) of condensate dropped out in the tubing.
|
|
1052
|
+
|
|
1053
|
+
The dropped liquid is in equilibrium with the gas at the local pressure, so
|
|
1054
|
+
it is a saturated live liquid: Beggs-Robinson live-oil viscosity at the
|
|
1055
|
+
Standing (1947) solution GOR at local p and T, with Pb = p. Before 3.8.1 it
|
|
1056
|
+
was given the dead-oil viscosity (Rs = 0), 2-5x too viscous for a light
|
|
1057
|
+
condensate (up to ~16 cP at low temperature).
|
|
1058
|
+
"""
|
|
1051
1059
|
if pr > 14.7 and cgr_local > 0.01:
|
|
1052
|
-
|
|
1060
|
+
rs_sat = _standing_rs(gsg, p_avg, temp_f, api)
|
|
1061
|
+
return _oil_viscosity_full(gsg, api, temp_f, rsb=rs_sat, pb=p_avg,
|
|
1053
1062
|
press_psia=p_avg)
|
|
1054
1063
|
return oil_vis
|
|
1055
1064
|
|
|
@@ -12,6 +12,17 @@ use crate::vle::fugacity::calc_fugacity_fast;
|
|
|
12
12
|
use crate::vle::k_init::sw_kvalue_init;
|
|
13
13
|
use crate::vle::rachford_rice::solve_rachford_rice;
|
|
14
14
|
|
|
15
|
+
/// Trivial-solution test for `flash_tp`: max|ln K| below this means both
|
|
16
|
+
/// labelled phases collapsed onto one composition (K -> 1). Gas-water K-values
|
|
17
|
+
/// sit orders of magnitude from 1, so a genuine split never comes near it.
|
|
18
|
+
/// Same value as Python `_lib_vle_engine.TRIVIAL_LNK_TOL`.
|
|
19
|
+
const TRIVIAL_LNK_TOL: f64 = 1e-6;
|
|
20
|
+
|
|
21
|
+
/// Tangent-plane stability (Michelsen 1982, Fluid Phase Equilib. 9:1-19): a
|
|
22
|
+
/// non-trivial stationary point with sum(Y) above 1 + this makes the feed
|
|
23
|
+
/// unstable. Same value as Python `_lib_vle_engine.STABILITY_TM_TOL`.
|
|
24
|
+
const STABILITY_TM_TOL: f64 = 1e-8;
|
|
25
|
+
|
|
15
26
|
/// Precomputed EOS quantities that are constant across SS iterations (T,P fixed).
|
|
16
27
|
struct EosPrecomputed {
|
|
17
28
|
ai_dim: Vec<f64>, // Dimensionless Ai = ai*P/(RT)^2
|
|
@@ -110,7 +121,14 @@ fn precompute_eos(
|
|
|
110
121
|
/// * `tol` - Convergence tolerance on K-values
|
|
111
122
|
///
|
|
112
123
|
/// # Returns
|
|
113
|
-
/// (V, x, y, converged) - vapor fraction, liquid comp, vapor comp, convergence flag
|
|
124
|
+
/// (V, x, y, converged) - vapor fraction, liquid comp, vapor comp, convergence flag.
|
|
125
|
+
/// When successive substitution collapses onto the trivial solution (all
|
|
126
|
+
/// K -> 1), a tangent-plane stability test decides: a stable feed returns
|
|
127
|
+
/// single phase, converged (V = 0 with x = z when water is the majority
|
|
128
|
+
/// component, else V = 1 with y = z; the other composition is the incipient
|
|
129
|
+
/// stationary point, or the feed when none exists); an unstable feed is
|
|
130
|
+
/// re-flashed from the trial's K. `converged` is false only if the result is
|
|
131
|
+
/// still trivial or the test is inconclusive. Port of Python `flash_tp`.
|
|
114
132
|
pub fn flash_tp(
|
|
115
133
|
t_k: f64,
|
|
116
134
|
p_pa: f64,
|
|
@@ -149,22 +167,68 @@ pub fn flash_tp(
|
|
|
149
167
|
|
|
150
168
|
// Initialize K-values (SW-specific + gamma for initial estimate)
|
|
151
169
|
let k_init = sw_kvalue_init(comp_indices, &tc, &pc, &omega, t_k, p_pa);
|
|
152
|
-
let mut
|
|
170
|
+
let mut k0: Vec<f64> = k_init
|
|
153
171
|
.iter()
|
|
154
172
|
.zip(gamma.iter())
|
|
155
173
|
.map(|(&ki, &gi)| ki * gi)
|
|
156
174
|
.collect();
|
|
157
175
|
|
|
158
176
|
// Water K should be << 1 in gas-water systems
|
|
159
|
-
|
|
177
|
+
k0[iw] = k0[iw].min(0.01);
|
|
178
|
+
|
|
179
|
+
let (mut k_vals, mut converged) = ss_iterate(&z_norm, &k0, &eos, gamma, nc, max_iter, tol);
|
|
180
|
+
|
|
181
|
+
// SS collapsed onto K = 1: both labelled phases took one composition, so
|
|
182
|
+
// V says nothing. Settle it with a stability test instead.
|
|
183
|
+
if is_trivial_k(&k_vals) {
|
|
184
|
+
match resolve_trivial(&z_norm, &k0, &eos, gamma, iw, max_iter, tol) {
|
|
185
|
+
Some(Verdict::Stable { feed_liquid, incipient }) => {
|
|
186
|
+
return if feed_liquid {
|
|
187
|
+
(0.0, z_norm, incipient, true)
|
|
188
|
+
} else {
|
|
189
|
+
(1.0, incipient, z_norm, true)
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
Some(Verdict::Split(k_seed)) => {
|
|
193
|
+
(k_vals, converged) = ss_iterate(&z_norm, &k_seed, &eos, gamma, nc, max_iter, tol);
|
|
194
|
+
}
|
|
195
|
+
None => {}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Final compositions with converged K
|
|
200
|
+
let (v, mut x, mut y) = solve_rachford_rice(&z_norm, &k_vals);
|
|
201
|
+
clip_and_normalize(&mut x);
|
|
202
|
+
clip_and_normalize(&mut y);
|
|
160
203
|
|
|
204
|
+
let converged = converged && !is_trivial_k(&k_vals);
|
|
205
|
+
|
|
206
|
+
(v, x, y, converged)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/// True when every K is within `TRIVIAL_LNK_TOL` of 1 in log terms.
|
|
210
|
+
fn is_trivial_k(k: &[f64]) -> bool {
|
|
211
|
+
k.iter().map(|k| k.ln().abs()).fold(0.0_f64, f64::max) < TRIVIAL_LNK_TOL
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/// Damped successive substitution from `k_start`. Returns (K, converged).
|
|
215
|
+
fn ss_iterate(
|
|
216
|
+
z: &[f64],
|
|
217
|
+
k_start: &[f64],
|
|
218
|
+
eos: &EosPrecomputed,
|
|
219
|
+
gamma: &[f64],
|
|
220
|
+
nc: usize,
|
|
221
|
+
max_iter: usize,
|
|
222
|
+
tol: f64,
|
|
223
|
+
) -> (Vec<f64>, bool) {
|
|
224
|
+
let mut k_vals = k_start.to_vec();
|
|
161
225
|
let mut converged = false;
|
|
162
226
|
let mut damp: f64 = 0.5;
|
|
163
227
|
let mut prev_err: f64 = f64::INFINITY;
|
|
164
228
|
|
|
165
229
|
for _it in 0..max_iter {
|
|
166
230
|
// Robust RR solver (Nielsen & Lia 2022)
|
|
167
|
-
let (_v, mut x, mut y) = solve_rachford_rice(
|
|
231
|
+
let (_v, mut x, mut y) = solve_rachford_rice(z, &k_vals);
|
|
168
232
|
|
|
169
233
|
// Clip and normalize
|
|
170
234
|
clip_and_normalize(&mut x);
|
|
@@ -206,13 +270,118 @@ pub fn flash_tp(
|
|
|
206
270
|
k_vals[i] *= (k_new[i] / k_vals[i]).powf(damp);
|
|
207
271
|
}
|
|
208
272
|
}
|
|
273
|
+
(k_vals, converged)
|
|
274
|
+
}
|
|
209
275
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
276
|
+
/// Outcome of the stability test after a trivial SS exit.
|
|
277
|
+
enum Verdict {
|
|
278
|
+
/// Feed is one phase; `incipient` is the opposite-phase stationary point
|
|
279
|
+
/// (the feed itself when that trial collapsed).
|
|
280
|
+
Stable { feed_liquid: bool, incipient: Vec<f64> },
|
|
281
|
+
/// Feed splits; K seeded from the unstable trial.
|
|
282
|
+
Split(Vec<f64>),
|
|
283
|
+
}
|
|
214
284
|
|
|
215
|
-
|
|
285
|
+
/// Tangent-plane stationary point by direct substitution (Michelsen 1982):
|
|
286
|
+
/// ln Y_i = ln f_i(z) - ln phi_i(y), less ln gamma_i for a liquid trial.
|
|
287
|
+
/// Returns (sum Y, normalised y), or None when phi is non-finite or the
|
|
288
|
+
/// search does not settle. Port of Python `_stability_trial`.
|
|
289
|
+
#[allow(clippy::too_many_arguments)]
|
|
290
|
+
fn stability_trial(
|
|
291
|
+
lnf_feed: &[f64],
|
|
292
|
+
seed: &[f64],
|
|
293
|
+
liquid: bool,
|
|
294
|
+
eos: &EosPrecomputed,
|
|
295
|
+
ln_gamma: &[f64],
|
|
296
|
+
nc: usize,
|
|
297
|
+
max_iter: usize,
|
|
298
|
+
tol: f64,
|
|
299
|
+
) -> Option<(f64, Vec<f64>)> {
|
|
300
|
+
let seed_sum: f64 = seed.iter().sum();
|
|
301
|
+
let mut ln_y: Vec<f64> = seed.iter().map(|&s| (s / seed_sum).max(1e-300).ln()).collect();
|
|
302
|
+
for _ in 0..max_iter {
|
|
303
|
+
let big_y: Vec<f64> = ln_y.iter().map(|v| v.exp()).collect();
|
|
304
|
+
let y_sum: f64 = big_y.iter().sum();
|
|
305
|
+
let y: Vec<f64> = big_y.iter().map(|v| v / y_sum).collect();
|
|
306
|
+
let phi = calc_fugacity_fast(&y, &eos.ai_dim, &eos.bi_dim, &eos.sqrt_ai, &eos.onemk, nc, liquid);
|
|
307
|
+
if !phi.iter().all(|p| p.is_finite()) {
|
|
308
|
+
return None;
|
|
309
|
+
}
|
|
310
|
+
let mut step = 0.0_f64;
|
|
311
|
+
for i in 0..nc {
|
|
312
|
+
let mut v = lnf_feed[i] - phi[i].ln();
|
|
313
|
+
if liquid {
|
|
314
|
+
v -= ln_gamma[i];
|
|
315
|
+
}
|
|
316
|
+
step = step.max((v - ln_y[i]).abs());
|
|
317
|
+
ln_y[i] = v;
|
|
318
|
+
}
|
|
319
|
+
if step < tol {
|
|
320
|
+
let big_y: Vec<f64> = ln_y.iter().map(|v| v.exp()).collect();
|
|
321
|
+
let y_sum: f64 = big_y.iter().sum();
|
|
322
|
+
return Some((y_sum, big_y.iter().map(|v| v / y_sum).collect()));
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
None
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/// Stability test on the feed after SS collapsed onto K = 1. The feed is
|
|
329
|
+
/// taken as aqueous (liquid root, with gamma) when water is the majority
|
|
330
|
+
/// component, else as the non-aqueous phase (vapor root). Vapor-like trials
|
|
331
|
+
/// seeded from K0*z and from the gas components, and liquid-like trials seeded
|
|
332
|
+
/// from z/K0 and from pure water, search for a second phase; any unstable
|
|
333
|
+
/// trial wins. Port of Python `_resolve_trivial`.
|
|
334
|
+
fn resolve_trivial(
|
|
335
|
+
z: &[f64],
|
|
336
|
+
k0: &[f64],
|
|
337
|
+
eos: &EosPrecomputed,
|
|
338
|
+
gamma: &[f64],
|
|
339
|
+
iw: usize,
|
|
340
|
+
max_iter: usize,
|
|
341
|
+
tol: f64,
|
|
342
|
+
) -> Option<Verdict> {
|
|
343
|
+
let nc = z.len();
|
|
344
|
+
let feed_liquid = z[iw] > 0.5;
|
|
345
|
+
let ln_gamma: Vec<f64> = gamma.iter().map(|g| g.ln()).collect();
|
|
346
|
+
let zc: Vec<f64> = z.iter().map(|&v| v.max(1e-300)).collect();
|
|
347
|
+
let phi_z = calc_fugacity_fast(&zc, &eos.ai_dim, &eos.bi_dim, &eos.sqrt_ai, &eos.onemk, nc, feed_liquid);
|
|
348
|
+
if !phi_z.iter().all(|p| p.is_finite()) {
|
|
349
|
+
return None;
|
|
350
|
+
}
|
|
351
|
+
let lnf: Vec<f64> = (0..nc)
|
|
352
|
+
.map(|i| zc[i].ln() + phi_z[i].ln() + if feed_liquid { ln_gamma[i] } else { 0.0 })
|
|
353
|
+
.collect();
|
|
354
|
+
|
|
355
|
+
// Wilson-type seeds alone can start water-dominated and collapse onto the
|
|
356
|
+
// feed even when a split exists, so each trial phase also gets a near-pure
|
|
357
|
+
// seed: gas-only for the vapor, water-only for the liquid.
|
|
358
|
+
let gas_seed: Vec<f64> = (0..nc).map(|i| if i == iw { 0.0 } else { zc[i] } + 1e-10).collect();
|
|
359
|
+
let water_seed: Vec<f64> = (0..nc).map(|i| if i == iw { 1.0 } else { 1e-10 }).collect();
|
|
360
|
+
let wilson_vap: Vec<f64> = (0..nc).map(|i| k0[i] * zc[i]).collect();
|
|
361
|
+
let wilson_liq: Vec<f64> = (0..nc).map(|i| zc[i] / k0[i]).collect();
|
|
362
|
+
let seeds: [(bool, &[f64]); 4] = [
|
|
363
|
+
(false, &wilson_vap),
|
|
364
|
+
(false, &gas_seed),
|
|
365
|
+
(true, &wilson_liq),
|
|
366
|
+
(true, &water_seed),
|
|
367
|
+
];
|
|
368
|
+
|
|
369
|
+
let mut incipient = z.to_vec();
|
|
370
|
+
for (liquid, seed) in seeds {
|
|
371
|
+
let (sum_y, y) = stability_trial(&lnf, seed, liquid, eos, &ln_gamma, nc, max_iter, tol)?;
|
|
372
|
+
let k_trial: Vec<f64> = (0..nc).map(|i| y[i] / zc[i]).collect();
|
|
373
|
+
if is_trivial_k(&k_trial) {
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
if sum_y > 1.0 + STABILITY_TM_TOL {
|
|
377
|
+
let k_seed = if liquid { k_trial.iter().map(|k| 1.0 / k).collect() } else { k_trial };
|
|
378
|
+
return Some(Verdict::Split(k_seed));
|
|
379
|
+
}
|
|
380
|
+
if liquid != feed_liquid {
|
|
381
|
+
incipient = y;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
Some(Verdict::Stable { feed_liquid, incipient })
|
|
216
385
|
}
|
|
217
386
|
|
|
218
387
|
/// Clip compositions to [1e-15, inf) and normalize.
|
|
@@ -86,8 +86,11 @@ fn condensate_vis(
|
|
|
86
86
|
pr: f64, cgr_local: f64, gsg: f64, api: f64, temp_f: f64,
|
|
87
87
|
p_avg: f64, oil_vis: f64,
|
|
88
88
|
) -> f64 {
|
|
89
|
+
// Dropped condensate is a saturated live liquid: Beggs-Robinson at the
|
|
90
|
+
// Standing solution GOR at local p, T with Pb = p (mirrors Python _condensate_vis)
|
|
89
91
|
if pr > 14.7 && cgr_local > 0.01 {
|
|
90
|
-
|
|
92
|
+
let rs_sat = standing_rs(gsg, p_avg, temp_f, api);
|
|
93
|
+
oil_viscosity_full(gsg, api, temp_f, rs_sat, p_avg, p_avg, 1.0, 1.0)
|
|
91
94
|
} else {
|
|
92
95
|
oil_vis
|
|
93
96
|
}
|
|
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
|
|
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
|