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.
Files changed (124) hide show
  1. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/PKG-INFO +1 -1
  2. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/build_pure_python.py +22 -4
  3. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyproject.toml +1 -1
  4. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/_accelerator.py +43 -1
  5. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/brine/_lib_vle_engine.py +44 -6
  6. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/brine/brine.py +149 -106
  7. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/classes/classes.py +1 -1
  8. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/dca/dca.py +47 -14
  9. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/changelist.rst +33 -0
  10. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/gas/gas.py +266 -174
  11. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/matbal/matbal.py +24 -37
  12. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/nodal/nodal.py +627 -946
  13. pyrestoolbox-3.1.5/pyrestoolbox/oil/__init__.py +76 -0
  14. pyrestoolbox-3.1.5/pyrestoolbox/oil/_compressibility.py +375 -0
  15. pyrestoolbox-3.1.5/pyrestoolbox/oil/_constants.py +125 -0
  16. pyrestoolbox-3.1.5/pyrestoolbox/oil/_correlations.py +494 -0
  17. pyrestoolbox-3.1.5/pyrestoolbox/oil/_density.py +227 -0
  18. pyrestoolbox-3.1.5/pyrestoolbox/oil/_harmonize.py +139 -0
  19. pyrestoolbox-3.1.5/pyrestoolbox/oil/_pvt_class.py +172 -0
  20. pyrestoolbox-3.1.5/pyrestoolbox/oil/_rate.py +192 -0
  21. pyrestoolbox-3.1.5/pyrestoolbox/oil/_separator.py +112 -0
  22. pyrestoolbox-3.1.5/pyrestoolbox/oil/_tables.py +272 -0
  23. pyrestoolbox-3.1.5/pyrestoolbox/oil/_utils.py +226 -0
  24. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/plyasunov/iapws_if97.py +9 -0
  25. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/plyasunov/plyasunov_model.py +12 -1
  26. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/simtools/simtools.py +39 -20
  27. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/validate/validate.py +21 -5
  28. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/setup.cfg +1 -1
  29. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/setup.py +6 -2
  30. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/lib.rs +3 -0
  31. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/pseudopressure.rs +161 -0
  32. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vle/flash.rs +13 -5
  33. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vlp/segment_gas.rs +16 -40
  34. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vlp/segment_oil.rs +12 -36
  35. pyrestoolbox-3.1.3/pyrestoolbox/oil/__init__.py +0 -1
  36. pyrestoolbox-3.1.3/pyrestoolbox/oil/oil.py +0 -2146
  37. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/.github/workflows/build-wheels.yml +0 -0
  38. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/.gitignore +0 -0
  39. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/Cargo.lock +0 -0
  40. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/Cargo.toml +0 -0
  41. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/LICENSE +0 -0
  42. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/MANIFEST.in +0 -0
  43. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/README.rst +0 -0
  44. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/ResToolbox/privacy_policy.md +0 -0
  45. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/benchmark_rust_vs_python.py +0 -0
  46. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/__init__.py +0 -0
  47. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/brine/__init__.py +0 -0
  48. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/brine/_lib_salting_library.py +0 -0
  49. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/classes/__init__.py +0 -0
  50. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/constants/__init__.py +0 -0
  51. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/constants/constants.py +0 -0
  52. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/dca/__init__.py +0 -0
  53. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/brine.rst +0 -0
  54. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/dca.rst +0 -0
  55. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/examples.ipynb +0 -0
  56. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/gas.rst +0 -0
  57. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/img/bot.png +0 -0
  58. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/img/bot_PVTO.png +0 -0
  59. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/img/bot_img.png +0 -0
  60. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/img/dry_gas.png +0 -0
  61. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/img/grid_sat_df.png +0 -0
  62. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/img/influence.png +0 -0
  63. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/img/properties_df.png +0 -0
  64. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/img/sgof.png +0 -0
  65. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/img/swof.png +0 -0
  66. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/layer.rst +0 -0
  67. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/library.rst +0 -0
  68. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/matbal.rst +0 -0
  69. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/nodal.rst +0 -0
  70. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/nodal_examples.ipynb +0 -0
  71. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/nodal_hydrate_demo.ipynb +0 -0
  72. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/oil.rst +0 -0
  73. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/recommend.rst +0 -0
  74. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/sensitivity.rst +0 -0
  75. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/docs/simtools.rst +0 -0
  76. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/gas/__init__.py +0 -0
  77. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/layer/__init__.py +0 -0
  78. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/layer/layer.py +0 -0
  79. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/library/__init__.py +0 -0
  80. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/library/component_library.xlsx +0 -0
  81. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/library/library.py +0 -0
  82. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/matbal/__init__.py +0 -0
  83. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/nodal/__init__.py +0 -0
  84. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/plyasunov/__init__.py +0 -0
  85. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/plyasunov/water_properties.py +0 -0
  86. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/recommend/__init__.py +0 -0
  87. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/recommend/recommend.py +0 -0
  88. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/sensitivity/__init__.py +0 -0
  89. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/sensitivity/sensitivity.py +0 -0
  90. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/shared_fns/__init__.py +0 -0
  91. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/shared_fns/shared_fns.py +0 -0
  92. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/simtools/__init__.py +0 -0
  93. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/pyrestoolbox/validate/__init__.py +0 -0
  94. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/bessel.rs +0 -0
  95. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/critical_properties/mod.rs +0 -0
  96. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/dca/hyperbolic.rs +0 -0
  97. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/dca/mod.rs +0 -0
  98. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/dca/ransac.rs +0 -0
  99. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/gas_viscosity/mod.rs +0 -0
  100. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/gwr.rs +0 -0
  101. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/matbal/mod.rs +0 -0
  102. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/matbal/objective.rs +0 -0
  103. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/oil/density.rs +0 -0
  104. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/oil/mod.rs +0 -0
  105. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/spycher_pruess/mod.rs +0 -0
  106. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/spycher_pruess/solubility.rs +0 -0
  107. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vle/alpha.rs +0 -0
  108. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vle/bip.rs +0 -0
  109. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vle/components.rs +0 -0
  110. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vle/eos.rs +0 -0
  111. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vle/fugacity.rs +0 -0
  112. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vle/k_init.rs +0 -0
  113. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vle/mod.rs +0 -0
  114. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vle/rachford_rice.rs +0 -0
  115. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vlp/friction.rs +0 -0
  116. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vlp/holdup_bb.rs +0 -0
  117. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vlp/holdup_gray.rs +0 -0
  118. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vlp/holdup_hb.rs +0 -0
  119. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vlp/holdup_wg.rs +0 -0
  120. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vlp/ift.rs +0 -0
  121. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vlp/mod.rs +0 -0
  122. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vlp/pvt_helpers.rs +0 -0
  123. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/vlp/static_column.rs +0 -0
  124. {pyrestoolbox-3.1.3 → pyrestoolbox-3.1.5}/src/zfactor/mod.rs +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyrestoolbox
3
- Version: 3.1.3
3
+ Version: 3.1.5
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Rust
6
6
  Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
@@ -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
- # Copy the pure-Python pyproject.toml
26
- pp_toml = tmp / "pyproject.toml"
27
- pp_toml.write_text(
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.3"
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
- return {
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
- damp = 0.4
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 Exception:
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
- if np.max(np.abs(K_new / K - 1.0)) < tol:
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
- damp = 0.7 if it < 20 else 0.9
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 Exception:
2304
+ except (ImportError, AttributeError):
2267
2305
  pass
2268
2306
 
2269
2307
  # Calculate gamma for gamma-phi method