pyrestoolbox 3.8.4__tar.gz → 3.8.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 (152) hide show
  1. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/Cargo.lock +1 -1
  2. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/Cargo.toml +1 -1
  3. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/PKG-INFO +1 -1
  4. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyproject.toml +1 -1
  5. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/docs/changelist.rst +6 -0
  6. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/docs/nodal.rst +1 -1
  7. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/nodal/nodal.py +78 -41
  8. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/pseudopressure.rs +72 -113
  9. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/vlp/constants.rs +6 -0
  10. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/vlp/march.rs +46 -15
  11. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/vlp/mod.rs +12 -12
  12. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/vlp/static_column.rs +20 -4
  13. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/.github/complexity-baseline.json +0 -0
  14. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/.github/scripts/check_complexity.py +0 -0
  15. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/.github/workflows/build-wheels.yml +0 -0
  16. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/.github/workflows/tests.yml +0 -0
  17. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/.gitignore +0 -0
  18. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/LICENSE +0 -0
  19. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/MANIFEST.in +0 -0
  20. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/README.rst +0 -0
  21. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/ResToolbox/privacy_policy.md +0 -0
  22. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/build_pure_python.py +0 -0
  23. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/clippy.toml +0 -0
  24. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/mcp/Dockerfile +0 -0
  25. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/mcp/README.md +0 -0
  26. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/mcp/pyproject.toml +0 -0
  27. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/mcp/pyrestoolbox_mcp/__init__.py +0 -0
  28. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/mcp/pyrestoolbox_mcp/serialize.py +0 -0
  29. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/mcp/pyrestoolbox_mcp/server.py +0 -0
  30. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/mcp/tests/test_server.py +0 -0
  31. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/__init__.py +0 -0
  32. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/_accelerator.py +0 -0
  33. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/brine/__init__.py +0 -0
  34. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/brine/_lib_dielectric.py +0 -0
  35. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/brine/_lib_maoduan_viscosity.py +0 -0
  36. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/brine/_lib_pitzer_params.py +0 -0
  37. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/brine/_lib_salting_library.py +0 -0
  38. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/brine/_lib_vle_engine.py +0 -0
  39. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/brine/appelo_volumes.py +0 -0
  40. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/brine/brine.py +0 -0
  41. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/brine/iapws_viscosity.py +0 -0
  42. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/brine/jones_dole_viscosity.py +0 -0
  43. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/brine/kestin_nacl_viscosity.py +0 -0
  44. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/brine/pr_vphi.py +0 -0
  45. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/brine/viscosity_route.py +0 -0
  46. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/brine/vphi_route.py +0 -0
  47. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/classes/__init__.py +0 -0
  48. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/classes/classes.py +0 -0
  49. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/constants/__init__.py +0 -0
  50. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/constants/constants.py +0 -0
  51. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/dca/__init__.py +0 -0
  52. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/dca/dca.py +0 -0
  53. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/docs/brine.rst +0 -0
  54. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/docs/dca.rst +0 -0
  55. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/docs/examples.ipynb +0 -0
  56. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/docs/gas.rst +0 -0
  57. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/docs/img/bot.png +0 -0
  58. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/docs/img/bot_PVTO.png +0 -0
  59. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/docs/img/bot_img.png +0 -0
  60. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/docs/img/dry_gas.png +0 -0
  61. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/docs/img/grid_sat_df.png +0 -0
  62. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/docs/img/influence.png +0 -0
  63. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/docs/img/properties_df.png +0 -0
  64. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/docs/img/sgof.png +0 -0
  65. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/docs/img/swof.png +0 -0
  66. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/docs/index.rst +0 -0
  67. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/docs/layer.rst +0 -0
  68. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/docs/library.rst +0 -0
  69. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/docs/matbal.rst +0 -0
  70. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/docs/nodal_examples.ipynb +0 -0
  71. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/docs/nodal_hydrate_demo.ipynb +0 -0
  72. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/docs/oil.rst +0 -0
  73. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/docs/plyasunov.rst +0 -0
  74. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/docs/recommend.rst +0 -0
  75. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/docs/sensitivity.rst +0 -0
  76. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/docs/simtools.rst +0 -0
  77. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/gas/__init__.py +0 -0
  78. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/gas/_hydrate.py +0 -0
  79. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/gas/gas.py +0 -0
  80. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/layer/__init__.py +0 -0
  81. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/layer/layer.py +0 -0
  82. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/library/__init__.py +0 -0
  83. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/library/component_library.xlsx +0 -0
  84. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/library/library.py +0 -0
  85. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/matbal/__init__.py +0 -0
  86. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/matbal/matbal.py +0 -0
  87. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/nodal/__init__.py +0 -0
  88. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/oil/__init__.py +0 -0
  89. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/oil/_compressibility.py +0 -0
  90. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/oil/_constants.py +0 -0
  91. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/oil/_correlations.py +0 -0
  92. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/oil/_density.py +0 -0
  93. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/oil/_harmonize.py +0 -0
  94. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/oil/_pvt_class.py +0 -0
  95. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/oil/_rate.py +0 -0
  96. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/oil/_separator.py +0 -0
  97. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/oil/_tables.py +0 -0
  98. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/oil/_utils.py +0 -0
  99. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/plyasunov/__init__.py +0 -0
  100. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/plyasunov/iapws_if97.py +0 -0
  101. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/plyasunov/plyasunov_model.py +0 -0
  102. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/plyasunov/water_properties.py +0 -0
  103. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/recommend/__init__.py +0 -0
  104. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/recommend/recommend.py +0 -0
  105. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/sensitivity/__init__.py +0 -0
  106. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/sensitivity/sensitivity.py +0 -0
  107. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/shared_fns/__init__.py +0 -0
  108. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/shared_fns/shared_fns.py +0 -0
  109. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/simtools/__init__.py +0 -0
  110. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/simtools/_aquifer.py +0 -0
  111. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/simtools/_decks.py +0 -0
  112. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/simtools/_pvt_tables.py +0 -0
  113. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/simtools/_relperm.py +0 -0
  114. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/simtools/_vfp.py +0 -0
  115. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/simtools/simtools.py +0 -0
  116. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/validate/__init__.py +0 -0
  117. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/pyrestoolbox/validate/validate.py +0 -0
  118. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/setup.cfg +0 -0
  119. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/setup.py +0 -0
  120. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/bessel.rs +0 -0
  121. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/brine_visc/mod.rs +0 -0
  122. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/brine_visc/salt.rs +0 -0
  123. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/brine_visc/water.rs +0 -0
  124. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/critical_properties/mod.rs +0 -0
  125. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/dca/hyperbolic.rs +0 -0
  126. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/dca/mod.rs +0 -0
  127. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/dca/ransac.rs +0 -0
  128. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/gas_viscosity/mod.rs +0 -0
  129. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/gwr.rs +0 -0
  130. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/lib.rs +0 -0
  131. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/matbal/mod.rs +0 -0
  132. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/matbal/objective.rs +0 -0
  133. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/oil/density.rs +0 -0
  134. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/oil/mod.rs +0 -0
  135. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/spycher_pruess/mod.rs +0 -0
  136. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/spycher_pruess/solubility.rs +0 -0
  137. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/vle/alpha.rs +0 -0
  138. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/vle/bip.rs +0 -0
  139. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/vle/components.rs +0 -0
  140. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/vle/eos.rs +0 -0
  141. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/vle/flash.rs +0 -0
  142. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/vle/fugacity.rs +0 -0
  143. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/vle/k_init.rs +0 -0
  144. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/vle/mod.rs +0 -0
  145. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/vle/rachford_rice.rs +0 -0
  146. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/vlp/friction.rs +0 -0
  147. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/vlp/holdup_bb.rs +0 -0
  148. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/vlp/holdup_gray.rs +0 -0
  149. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/vlp/holdup_wg.rs +0 -0
  150. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/vlp/ift.rs +0 -0
  151. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/vlp/pvt_helpers.rs +0 -0
  152. {pyrestoolbox-3.8.4 → pyrestoolbox-3.8.5}/src/zfactor/mod.rs +0 -0
@@ -152,7 +152,7 @@ dependencies = [
152
152
 
153
153
  [[package]]
154
154
  name = "pyrestoolbox-native"
155
- version = "3.8.4"
155
+ version = "3.8.5"
156
156
  dependencies = [
157
157
  "pyo3",
158
158
  "rug",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "pyrestoolbox-native"
3
- version = "3.8.4"
3
+ version = "3.8.5"
4
4
  edition = "2021"
5
5
  license = "GPL-3.0-or-later"
6
6
  description = "Rust acceleration extensions for pyResToolbox"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyrestoolbox
3
- Version: 3.8.4
3
+ Version: 3.8.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+)
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "pyrestoolbox"
7
- version = "3.8.4"
7
+ version = "3.8.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"}]
@@ -1,3 +1,9 @@
1
+ Changelist in 3.8.5:
2
+
3
+ - **NODAL: gas VLP uses the ``gas_pvt`` Z-factor and viscosity methods (results move when a GasPVT is passed).** ``fbhp``, ``fthp``, ``outflow_curve``, ``operating_point`` and the simtools VFP tables took only the gas SG and Sutton/Wichert-Aziz pseudo-criticals from a ``GasPVT``; the march always ran Hall-Yarborough Z with Lee-Gonzalez-Eakin viscosity, so ``zmethod='BNS'`` (or the GasPVT default, DAK with PMC) was silently ignored, as were H2 and He (which raised a warning). The march now evaluates Z and viscosity exactly as ``gas_pvt.z()`` and ``gas_pvt.viscosity()`` do, at every step and in the zero-rate static column, in Python and Rust (the Rust march builds the same Z/viscosity evaluator as ``gas_dmp``, so every method stays accelerated; the two paths agree to 1e-9 relative). Calls without ``gas_pvt`` are bit-identical. Example (THP 1000 psia, 5 MMscf/d, 100 bbl/d water, 5000 ft of 2.441 in tubing, 80-200 degF, SG 0.65), Beggs-Brill BHP: 1475.3 psia before, 1459.3 with ``GasPVT(sg=0.65)`` (DAK/PMC), 1508.2 with ``zmethod='BNS'``; WG 1444.8, 1444.1, 1446.2. Beggs-Brill moves most because the distributed-to-segregated boundary sits mid-well here and the gradient steps 27% across it. The pure-Python fallback is about 15 times slower with a ``gas_pvt`` (about 40 ms per ``fbhp``), since each step calls ``gas_z`` and ``gas_ug``. No documented example passes a ``gas_pvt`` to the VLP functions, so none moves; one nodal test that asserted a sweet ``GasPVT`` equals the ``gsg``-only answer now compares an HY/SUT ``GasPVT`` (within 4e-5).
4
+
5
+ - **NODAL: gas VLP results now approach the dry-gas answer continuously as the liquid rate goes to zero.** With no liquid every method returns the single-phase gas BHP, but a vanishing liquid rate did not: in a 5000 ft, 2.441 in well at 1000 psia THP and 5 MMscf/d (dry gas 1232 psia for all four methods), 1e-6 bbl/d of water gave WG 1294, GRAY 1243 and BB 1429 psia, and BB BHP fell as water rose from 0.001 to 1 bbl/d. The causes are in the correlations' low-liquid limits: the Woldesemayat-Ghajar drift-flux void fraction tends to u_sg/(u_sg + u_gm) because the drift velocity does not depend on the amount of liquid, leaving a stagnant liquid holdup u_gm/(u_sg + u_gm) at any liquid rate; Gray's holdup tends to exp(A); and Beggs-Brill holdup scales as lambda^0.58, so its friction parameter y = lambda/HL^2 grows without bound and the two-phase friction multiplier peaks near lambda = 1e-9. Hagedorn-Brown, whose holdup is floored at lambda, was already continuous. The gas segment march now blends each segment's gradient linearly between the method's own zero-liquid gradient and its two-phase gradient while the no-slip liquid fraction is below 1e-3 (``_LOW_LIQ_LAMBDA``, Rust ``LOW_LIQ_LAMBDA``). At zero liquid and at lambda >= 1e-3 results are unchanged; in between they are continuous and BHP rises monotonically with liquid rate. In the example, 1 bbl/d of water now gives WG 1237.5 (was 1298.5), GRAY 1233.3 (1245.8), BB 1241.2 (1335.7) and HB 1232.0 (1232.3) psia; 10 bbl/d gives WG 1300.8 (1312.7), GRAY 1263.3 (1269.2), BB 1324.9 (1338.9), HB 1234.7 (1235.3). Gas wells with a liquid-to-gas ratio above roughly 3 bbl/MMscf at flowing conditions, and every oil-well march, are unaffected. No frozen baseline or documented example moves.
6
+
1
7
  Changelist in 3.8.4:
2
8
 
3
9
  - **GAS: associated-gas hydrocarbon pseudo-criticals for BNS (``ag``).** Burgoyne, Nielsen & Stanko (2025) fitted two hydrocarbon Tc/Pc correlations, for gas condensates and for associated gas; only the condensate one was exposed. Every gas function that takes ``he`` (except ``gas_sg``, ``gas_hydrate`` and the recommenders, which use no critical properties) now also takes ``ag`` (keyword, appended last): ``gas_tc_pc``, ``gas_z``, ``gas_ug``, ``gas_cg``, ``gas_bg``, ``gas_den``, ``gas_thermal``, ``gas_ponz2p``, ``gas_grad2sg``, ``gas_dmp``, ``gas_rate_radial``, ``gas_rate_linear``, ``GasPVT`` and ``gas_matbal``. ``ag=True`` makes ``cmethod='BNS'`` return the associated-gas values (SG 0.75: Tc 397.7 against 401.2 degR, Pc 653.9 against 663.4 psia), and the hydrocarbon-inert BIPs follow the selected Tc, as in the regression. Other cmethods ignore it and a user ``tc``/``pc`` takes precedence. The resolved values reach the Rust paths through the existing hydrocarbon Tc/Pc override, so the Rust code is unchanged. With ``ag=True``, results match the reference BNS implementation (``AG=True``) to 5e-15 in Z and viscosity and 7e-13 Btu/lb-mol in enthalpy; with ``ag=False`` (the default) every result is bit-identical to 3.8.3 on both paths.
@@ -426,7 +426,7 @@ Returns flowing bottom hole pressure (psia, or barsa if metric=True) using the s
426
426
  - 'gas' or 'oil'
427
427
  * - gas_pvt
428
428
  - GasPVT
429
- - Gas PVT object. If provided, extracts gas composition (sg, co2, h2s, n2, h2, he) and method selections for IPR calculations
429
+ - Gas PVT object. If provided, the gas-well VLP march evaluates Z and gas viscosity with its methods and composition (as ``gas_pvt.z()`` and ``gas_pvt.viscosity()``: DAK with PMC by default, or HY or BNS as configured, impurities and H2/He included), and IPR calculations use the same. Without it the march uses Hall-Yarborough Z on Sutton pseudo-criticals of ``gsg`` with Lee-Gonzalez-Eakin viscosity
430
430
  * - oil_pvt
431
431
  - OilPVT
432
432
  - Oil PVT object. If provided for oil wells, extracts api, sgsp, pb, rsb
@@ -62,6 +62,7 @@ from pyrestoolbox.constants import (BAR_TO_PSI, PSI_TO_BAR, degc_to_degf, degf_t
62
62
  D_PER_SM3_TO_D_PER_MSCF, D_PER_MSCF_TO_D_PER_SM3,
63
63
  STB_TO_SM3, SM3_TO_STB)
64
64
  import pyrestoolbox.gas as gas
65
+ from pyrestoolbox.gas.gas import _rust_cmethod
65
66
  import pyrestoolbox.oil as oil
66
67
  from pyrestoolbox.brine.viscosity_route import brine_viscosity as _brine_viscosity
67
68
  from pyrestoolbox._accelerator import RUST_AVAILABLE as _RUST_AVAILABLE, rust_accelerated
@@ -273,6 +274,12 @@ _GRAY_ND_COEF = 205.0 # Diameter number coefficient in A
273
274
  _GRAY_ROUGH_K = 28.5 # Effective roughness coefficient
274
275
  _GRAY_R_THRESH = 0.007 # R threshold for roughness interpolation
275
276
  _GRAY_SP_LIQ_FRAC = 1e-6 # v_sg/v_m below this is single-phase liquid (dry roughness)
277
+ # Below this no-slip liquid fraction the two-phase gradient is blended
278
+ # linearly toward the method's own zero-liquid gradient (weight lambda/_LOW_LIQ_LAMBDA).
279
+ # WG drift-flux and Gray holdup tend to finite values as liquid vanishes and
280
+ # Beggs-Brill's friction parameter y = lambda/HL^2 grows without bound, so
281
+ # without the blend a trace of liquid moved dry-gas BHP by up to 16%.
282
+ _LOW_LIQ_LAMBDA = 1e-3
276
283
  _GRAY_ROUGH_FLOOR = 2.77e-5 # Minimum effective roughness (ft)
277
284
 
278
285
  # ============================================================================
@@ -372,31 +379,25 @@ class _FlowInputs:
372
379
  gsg: float = 0.65
373
380
  vis_frac: float = 1.0
374
381
  rsb_frac: float = 1.0
375
- tc: Optional[float] = None # pseudo-critical T (deg R); None -> Sutton from gsg in the march
376
- pc: Optional[float] = None # pseudo-critical P (psia); None -> Sutton from gsg in the march
382
+ # Gas Z/viscosity spec from GasPVT (_gas_pvt_zspec); None -> Hall-Yarborough
383
+ # on Sutton pseudo-criticals of gsg with Lee-Gonzalez-Eakin viscosity
384
+ zspec: Optional[tuple] = None
377
385
 
378
386
 
379
- def _gas_pvt_tc_pc(gas_pvt):
380
- """Mixture pseudo-criticals for the VLP march from a GasPVT object.
387
+ def _gas_pvt_zspec(gas_pvt):
388
+ """Z-factor and viscosity specification for the VLP march from a GasPVT.
381
389
 
382
- The march runs Hall-Yarborough on Sutton pseudo-criticals of the total-gas
383
- sg. User-supplied tc/pc are honoured as given; otherwise, when the object
384
- carries CO2/H2S/N2, Sutton + Wichert-Aziz is applied so impurities reach the
385
- Z-factor as they already do in ipr_curve. Sweet gas returns (None, None) so
386
- the march path is untouched. BNS-style tc/pc are hydrocarbon-only and are
387
- not usable with a single-pseudo-component Z-factor, hence the SUT call here.
390
+ Returns (zmethod, cmethod, sg, [co2, h2s, n2, h2, he], tc, pc) with the
391
+ object's resolved methods and effective critical properties, so the march
392
+ evaluates Z and viscosity exactly as gas_pvt.z() and gas_pvt.viscosity()
393
+ do (Python via gas_z/gas_ug, Rust via the same GasState as gas_dmp). PMC
394
+ travels as 'SUT' with its already-resolved tc/pc, which gas_z treats as a
395
+ mixture override, as the Rust batch paths do.
388
396
  """
389
- if getattr(gas_pvt, '_user_tc_pc', False):
390
- return gas_pvt.tc, gas_pvt.pc
391
- if gas_pvt.h2 > 0 or getattr(gas_pvt, 'he', 0) > 0:
392
- warnings.warn(
393
- "VLP correlations use a Hall-Yarborough Z-factor with Sutton + Wichert-Aziz "
394
- "pseudo-criticals; the H2 and He fractions on gas_pvt are ignored in the wellbore march.",
395
- RuntimeWarning, stacklevel=3)
396
- if gas_pvt.co2 > 0 or gas_pvt.h2s > 0 or gas_pvt.n2 > 0:
397
- return gas.gas_tc_pc(gas_pvt.sg, co2=gas_pvt.co2, h2s=gas_pvt.h2s,
398
- n2=gas_pvt.n2, cmethod='SUT')
399
- return None, None
397
+ return (gas_pvt.zmethod.name, _rust_cmethod(gas_pvt.cmethod), float(gas_pvt.sg),
398
+ [float(gas_pvt.co2), float(gas_pvt.h2s), float(gas_pvt.n2),
399
+ float(gas_pvt.h2), float(getattr(gas_pvt, 'he', 0))],
400
+ float(gas_pvt.tc), float(gas_pvt.pc))
400
401
 
401
402
 
402
403
  def _prepare_flow_inputs(well_type, metric, gas_pvt=None, oil_pvt=None,
@@ -435,7 +436,7 @@ def _prepare_flow_inputs(well_type, metric, gas_pvt=None, oil_pvt=None,
435
436
  if gas_pvt is not None and well_type == 'gas':
436
437
  if f.gsg == 0.65:
437
438
  f.gsg = gas_pvt.sg
438
- f.tc, f.pc = _gas_pvt_tc_pc(gas_pvt)
439
+ f.zspec = _gas_pvt_zspec(gas_pvt)
439
440
 
440
441
  # OilPVT already holds oilfield units
441
442
  if oil_pvt is not None and well_type == 'oil':
@@ -1063,7 +1064,25 @@ def _condensate_vis(pr, cgr_local, gsg, api, temp_f, p_avg, oil_vis):
1063
1064
  return oil_vis
1064
1065
 
1065
1066
 
1066
- def _static_gas_column_pressure(thp, length, tht, bht, gsg, theta=math.pi / 2.0):
1067
+ def _march_gas_z_mu(gsg, temp_f, press_psia, tc, pc, zspec, viscosity=True):
1068
+ """(Z, gas viscosity cP) for the gas march.
1069
+
1070
+ zspec None: Hall-Yarborough on (tc, pc) and Lee-Gonzalez-Eakin. Otherwise
1071
+ the GasPVT methods carried by zspec (see _gas_pvt_zspec). viscosity=False
1072
+ returns (Z, None).
1073
+ """
1074
+ if zspec is None:
1075
+ zee = _z_factor(gsg, temp_f, press_psia, tc=tc, pc=pc)
1076
+ mu = _gas_viscosity(gsg, temp_f, press_psia, z=zee, tc=tc, pc=pc) if viscosity else None
1077
+ return zee, mu
1078
+ zmethod, cmethod, sg, (co2, h2s, n2, h2, he), tc_z, pc_z = zspec
1079
+ kw = dict(p=press_psia, sg=sg, degf=temp_f, zmethod=zmethod, cmethod=cmethod,
1080
+ co2=co2, h2s=h2s, n2=n2, h2=h2, he=he, tc=tc_z, pc=pc_z)
1081
+ zee = float(gas.gas_z(**kw))
1082
+ return zee, (float(gas.gas_ug(**kw)) if viscosity else None)
1083
+
1084
+
1085
+ def _static_gas_column_pressure(thp, length, tht, bht, gsg, theta=math.pi / 2.0, zspec=None):
1067
1086
  tc, pc = _sutton_tc_pc(gsg)
1068
1087
  n_seg = 50
1069
1088
  d_len = length / n_seg
@@ -1073,7 +1092,7 @@ def _static_gas_column_pressure(thp, length, tht, bht, gsg, theta=math.pi / 2.0)
1073
1092
  frac = (i + 0.5) / n_seg
1074
1093
  temp_local = tht + (bht - tht) * frac
1075
1094
  temp_r = temp_local + 459.67
1076
- zee = _z_factor(gsg, temp_local, max(p, 14.7), tc=tc, pc=pc)
1095
+ zee, _ = _march_gas_z_mu(gsg, temp_local, max(p, 14.7), tc, pc, zspec, viscosity=False)
1077
1096
  rho_gas = _MW_AIR * gsg * p / (zee * _R_GAS * temp_r)
1078
1097
  p += rho_gas / _IN2_PER_FT2 * d_len * sin_theta
1079
1098
  return p
@@ -1186,11 +1205,11 @@ def _hb_gradient_gas(s):
1186
1205
  def _hb_fbhp_gas(thp, api, gsg, tid, rough, length, tht, bht,
1187
1206
  wsg, qg_mmscfd, cgr, qw_bwpd, oil_vis,
1188
1207
  injection=False, pr=0.0, theta=math.pi / 2.0,
1189
- tc=None, pc=None):
1208
+ zspec=None):
1190
1209
  return _segment_march_gas(thp, api, gsg, tid, rough, length, tht, bht,
1191
1210
  wsg, qg_mmscfd, cgr, qw_bwpd, oil_vis,
1192
1211
  injection, pr, theta, _hb_gradient_gas,
1193
- tc=tc, pc=pc)
1212
+ zspec=zspec)
1194
1213
 
1195
1214
 
1196
1215
  @rust_accelerated('hb_fbhp_oil_rust')
@@ -1312,11 +1331,11 @@ def _wg_gradient_gas(s):
1312
1331
  def _wg_fbhp_gas(thp, api, gsg, tid, rough, length, tht, bht,
1313
1332
  wsg, qg_mmscfd, cgr, qw_bwpd, oil_vis,
1314
1333
  injection=False, pr=0.0, theta=math.pi / 2.0,
1315
- tc=None, pc=None):
1334
+ zspec=None):
1316
1335
  return _segment_march_gas(thp, api, gsg, tid, rough, length, tht, bht,
1317
1336
  wsg, qg_mmscfd, cgr, qw_bwpd, oil_vis,
1318
1337
  injection, pr, theta, _wg_gradient_gas,
1319
- tc=tc, pc=pc)
1338
+ zspec=zspec)
1320
1339
 
1321
1340
 
1322
1341
  @rust_accelerated('wg_fbhp_oil_rust')
@@ -1404,16 +1423,16 @@ def _gray_effective_roughness(rough_dry, sigma, rho_ns, v_sl, v_sg):
1404
1423
 
1405
1424
  def _segment_march_gas(thp, api, gsg, tid, rough, length, tht, bht,
1406
1425
  wsg, qg_mmscfd, cgr, qw_bwpd, oil_vis,
1407
- injection, pr, theta, gradient_fn, tc=None, pc=None):
1426
+ injection, pr, theta, gradient_fn, zspec=None):
1408
1427
  """Shared segment march for all gas VLP methods.
1409
1428
 
1410
1429
  gradient_fn(s) -> dpdz (psi/ft)
1411
1430
  Called at each pressure iteration with a dict containing all
1412
1431
  computed PVT/flow properties for the current segment step.
1413
- tc, pc: mixture pseudo-criticals (deg R, psia); None -> Sutton from gsg.
1432
+ zspec: GasPVT Z/viscosity spec (_gas_pvt_zspec); None -> Hall-Yarborough
1433
+ on Sutton pseudo-criticals of gsg with Lee-Gonzalez-Eakin viscosity.
1414
1434
  """
1415
- if tc is None or pc is None:
1416
- tc, pc = _sutton_tc_pc(gsg)
1435
+ tc, pc = _sutton_tc_pc(gsg)
1417
1436
  osg = 141.5 / (api + 131.5)
1418
1437
  total_mass = (_RHO_AIR_STC * gsg * qg_mmscfd * 1e6 +
1419
1438
  osg * _RHO_FW * cgr * qg_mmscfd * _FT3_PER_BBL +
@@ -1432,7 +1451,7 @@ def _segment_march_gas(thp, api, gsg, tid, rough, length, tht, bht,
1432
1451
  "the specified liquid rates (liquid loading not modelled).",
1433
1452
  RuntimeWarning, stacklevel=3
1434
1453
  )
1435
- return _static_gas_column_pressure(thp, length, tht, bht, gsg, theta=theta)
1454
+ return _static_gas_column_pressure(thp, length, tht, bht, gsg, theta=theta, zspec=zspec)
1436
1455
 
1437
1456
  diam_ft = tid / 12.0
1438
1457
  rough_ft = rough / 12.0
@@ -1466,8 +1485,7 @@ def _segment_march_gas(thp, api, gsg, tid, rough, length, tht, bht,
1466
1485
  oil_vis_loc = _condensate_vis(pr, cgr_loc, gsg, api,
1467
1486
  temp_f, p_avg, oil_vis)
1468
1487
 
1469
- zee = _z_factor(gsg, temp_f, p_avg, tc=tc, pc=pc)
1470
- mu_g = _gas_viscosity(gsg, temp_f, p_avg, z=zee, tc=tc, pc=pc)
1488
+ zee, mu_g = _march_gas_z_mu(gsg, temp_f, p_avg, tc, pc, zspec)
1471
1489
 
1472
1490
  rho_g = _MW_AIR * gsg * p_avg / (zee * _R_GAS * temp_r)
1473
1491
  rho_l = lsg_loc * _RHO_FW
@@ -1506,7 +1524,7 @@ def _segment_march_gas(thp, api, gsg, tid, rough, length, tht, bht,
1506
1524
  'qg_mmscfd': qg_mmscfd, 'api': api,
1507
1525
  }
1508
1526
 
1509
- dpdz = gradient_fn(s)
1527
+ dpdz = _low_liquid_gradient(gradient_fn, s)
1510
1528
  p_est = p_psia + dpdz * seg_len
1511
1529
 
1512
1530
  p_psia = p_est
@@ -1518,6 +1536,25 @@ def _segment_march_gas(thp, api, gsg, tid, rough, length, tht, bht,
1518
1536
  return p_psia
1519
1537
 
1520
1538
 
1539
+ def _low_liquid_gradient(gradient_fn, s):
1540
+ """Gradient with a continuous approach to the dry-gas limit.
1541
+
1542
+ For 0 < lambda_l < _LOW_LIQ_LAMBDA returns g0 + (lambda_l/_LOW_LIQ_LAMBDA)(g - g0),
1543
+ where g is the method's two-phase gradient and g0 the same method with the
1544
+ liquid removed (its existing lambda_l = 0 path). At lambda_l = 0 and at or
1545
+ above _LOW_LIQ_LAMBDA this is exactly gradient_fn(s).
1546
+ """
1547
+ g = gradient_fn(s)
1548
+ lam = s['lambda_l']
1549
+ if not 0.0 < lam < _LOW_LIQ_LAMBDA:
1550
+ return g
1551
+ s0 = dict(s, mflow_l=0.0, mflow_o=0.0, mflow_w=0.0, v_sl=0.0,
1552
+ v_m=s['v_sg'], lambda_l=0.0, rho_ns=s['rho_g'],
1553
+ mflow_total=s['mflow_g'], qo_loc=0.0, ql_loc=0.0, qw_bwpd=0.0)
1554
+ g0 = gradient_fn(s0)
1555
+ return g0 + (lam / _LOW_LIQ_LAMBDA) * (g - g0)
1556
+
1557
+
1521
1558
  # ============================================================================
1522
1559
  # Shared Oil Segment March
1523
1560
  # ============================================================================
@@ -1673,11 +1710,11 @@ def _gray_gradient_gas(s):
1673
1710
  def _gray_fbhp_gas(thp, api, gsg, tid, rough, length, tht, bht,
1674
1711
  wsg, qg_mmscfd, cgr, qw_bwpd, oil_vis,
1675
1712
  injection=False, pr=0.0, theta=math.pi / 2.0,
1676
- tc=None, pc=None):
1713
+ zspec=None):
1677
1714
  return _segment_march_gas(thp, api, gsg, tid, rough, length, tht, bht,
1678
1715
  wsg, qg_mmscfd, cgr, qw_bwpd, oil_vis,
1679
1716
  injection, pr, theta, _gray_gradient_gas,
1680
- tc=tc, pc=pc)
1717
+ zspec=zspec)
1681
1718
 
1682
1719
 
1683
1720
  @rust_accelerated('gray_fbhp_oil_rust')
@@ -1857,12 +1894,12 @@ def _bb_gradient_gas(s):
1857
1894
  def _bb_core_gas(thp, api, gsg, tid, rough, length, tht, bht,
1858
1895
  wsg, qg_mmscfd, cgr, qw_bwpd, oil_vis,
1859
1896
  injection=False, pr=0.0, theta=math.pi / 2.0,
1860
- tc=None, pc=None):
1897
+ zspec=None):
1861
1898
  """Beggs & Brill core for gas wells."""
1862
1899
  return _segment_march_gas(thp, api, gsg, tid, rough, length, tht, bht,
1863
1900
  wsg, qg_mmscfd, cgr, qw_bwpd, oil_vis,
1864
1901
  injection, pr, theta, _bb_gradient_gas,
1865
- tc=tc, pc=pc)
1902
+ zspec=zspec)
1866
1903
 
1867
1904
 
1868
1905
  @rust_accelerated('bb_fbhp_oil_rust')
@@ -1975,7 +2012,7 @@ def fbhp(thp: float, completion: 'Completion', vlpmethod: str = 'WG', well_type:
1975
2012
  length=length, tht=tht_seg, bht=bht_seg, wsg=wsg,
1976
2013
  qg_mmscfd=f.qg_mmscfd, cgr=f.cgr, qw_bwpd=f.qw_bwpd,
1977
2014
  oil_vis=oil_vis, injection=injection, pr=f.pr, theta=theta,
1978
- tc=f.tc, pc=f.pc)
2015
+ zspec=f.zspec)
1979
2016
  else:
1980
2017
  return _OIL_METHOD_DIC[vlpmethod.name](
1981
2018
  thp=thp_in, api=f.api, gsg=f.gsg, tid=tid, rough=rough,
@@ -65,8 +65,9 @@ enum ZMethod {
65
65
  ///
66
66
  /// These eleven values used to be threaded through eval_z, gl_integrate,
67
67
  /// p_over_z and solve_ponz2p_single one argument at a time, and repeated
68
- /// verbatim at every call site.
69
- struct GasState<'a> {
68
+ /// verbatim at every call site. Built with `GasState::new`, shared by the
69
+ /// pseudopressure entry points and the VLP gas march.
70
+ pub(crate) struct GasState {
70
71
  method: ZMethod,
71
72
  deg_r: f64,
72
73
  sg: f64,
@@ -81,13 +82,72 @@ struct GasState<'a> {
81
82
  n2: f64,
82
83
  h2: f64,
83
84
  he: f64,
84
- lbc_params: &'a Option<gas_viscosity::LbcParams>,
85
+ lbc_params: Option<gas_viscosity::LbcParams>,
85
86
  }
86
87
 
87
- impl GasState<'_> {
88
+ impl GasState {
89
+ /// Resolve the method and critical properties once for a temperature.
90
+ ///
91
+ /// zmethod "DAK", "HY" (cmethod "SUT") or "BNS"/"BUR"; anything else is
92
+ /// an Err so the caller can fall back to Python. User tc/pc (both > 0)
93
+ /// replace the mixture Tc/Pc for DAK/HY and only the hydrocarbon
94
+ /// pseudo-component Tc/Pc for BNS (inert constants and LBC MW unchanged).
95
+ /// `with_viscosity` precomputes the BNS LBC parameters.
96
+ #[allow(clippy::too_many_arguments)]
97
+ pub(crate) fn new(
98
+ degf: f64, sg: f64, zmethod: &str, cmethod: &str,
99
+ inerts: [f64; 5], tc: f64, pc: f64, with_viscosity: bool,
100
+ ) -> Result<Self, String> {
101
+ let [co2, h2s, n2, h2, he] = inerts;
102
+ let method = match (zmethod, cmethod) {
103
+ ("DAK", "SUT") => ZMethod::DakSut,
104
+ ("HY", "SUT") => ZMethod::HySut,
105
+ ("BNS", _) | ("BUR", _) => ZMethod::Bns,
106
+ _ => {
107
+ return Err(format!(
108
+ "Rust acceleration not available for zmethod={}, cmethod={}", zmethod, cmethod
109
+ ));
110
+ }
111
+ };
112
+ let user_tc_pc = tc > 0.0 && pc > 0.0;
113
+ let (tpc_sut, ppc_sut) = match method {
114
+ ZMethod::DakSut | ZMethod::HySut => {
115
+ if user_tc_pc {
116
+ (tc, pc)
117
+ } else {
118
+ critical_properties::sutton_wa_internal(sg, co2, h2s, n2)?
119
+ }
120
+ }
121
+ ZMethod::Bns => (0.0, 0.0), // Not used
122
+ };
123
+ let (tpc_bns, ppc_bns) = match method {
124
+ ZMethod::Bns => {
125
+ if user_tc_pc {
126
+ (tc, pc)
127
+ } else {
128
+ let (t, p, _) = critical_properties::bns_pseudocritical_internal(sg, co2, h2s, n2, h2, he);
129
+ (t, p)
130
+ }
131
+ }
132
+ _ => (0.0, 0.0),
133
+ };
134
+ // LBC parameters honour the HC Tc/Pc override
135
+ let lbc_params = match method {
136
+ ZMethod::Bns if with_viscosity => {
137
+ let (tc_lbc, pc_lbc) = if user_tc_pc { (tc, pc) } else { (0.0, 0.0) };
138
+ Some(gas_viscosity::lbc_params(degf, sg, co2, h2s, n2, h2, he, tc_lbc, pc_lbc))
139
+ }
140
+ _ => None,
141
+ };
142
+ Ok(GasState {
143
+ method, deg_r: degf + DEGF2R, sg, tpc_sut, ppc_sut, tpc_bns, ppc_bns,
144
+ co2, h2s, n2, h2, he, lbc_params,
145
+ })
146
+ }
147
+
88
148
  /// Z-factor at a single pressure. All critical property computation is
89
149
  /// internal — no Python round-trips.
90
- fn eval_z(&self, p_psia: f64) -> f64 {
150
+ pub(crate) fn eval_z(&self, p_psia: f64) -> f64 {
91
151
  match self.method {
92
152
  ZMethod::DakSut => {
93
153
  zfactor::dak_core_pub(p_psia / self.ppc_sut, self.deg_r / self.tpc_sut)
@@ -103,9 +163,9 @@ impl GasState<'_> {
103
163
  }
104
164
 
105
165
  /// Viscosity at a single pressure, using LBC only on the BNS path.
106
- fn eval_ug(&self, p_psia: f64, zee: f64) -> f64 {
166
+ pub(crate) fn eval_ug(&self, p_psia: f64, zee: f64) -> f64 {
107
167
  match self.method {
108
- ZMethod::Bns => match self.lbc_params {
168
+ ZMethod::Bns => match &self.lbc_params {
109
169
  Some(params) => {
110
170
  gas_viscosity::lbc_viscosity_with_params(p_psia, self.deg_r, zee, params)
111
171
  }
@@ -219,67 +279,11 @@ pub fn gas_dmp_rust(
219
279
  ) -> PyResult<f64> {
220
280
  // inerts = [co2, h2s, n2, h2, he] mole fractions (one record keeps the
221
281
  // signature under clippy's argument threshold)
222
- let [co2, h2s, n2, h2, he] = inerts;
223
282
  if p1 == p2 {
224
283
  return Ok(0.0);
225
284
  }
226
-
227
- let deg_r = degf + DEGF2R;
228
-
229
- // Determine method
230
- let method = match (zmethod, cmethod) {
231
- ("DAK", "SUT") | ("DAK", _) if cmethod == "SUT" => ZMethod::DakSut,
232
- ("HY", "SUT") | ("HY", _) if cmethod == "SUT" => ZMethod::HySut,
233
- ("BNS", _) | ("BUR", _) => ZMethod::Bns,
234
- _ => {
235
- return Err(pyo3::exceptions::PyValueError::new_err(
236
- format!("Rust acceleration not available for zmethod={}, cmethod={}", zmethod, cmethod)
237
- ));
238
- }
239
- };
240
-
241
- // Precompute critical properties — user tc/pc override is method-dependent:
242
- // DAK/HY+SUT: (tc, pc) replace the mixture Tc/Pc.
243
- // BNS: (tc, pc) replace only the HC pseudo-component Tc/Pc; inert Tc/Pc
244
- // remain at BNS internal constants. LBC params also honor the HC override.
245
- let user_tc_pc = tc > 0.0 && pc > 0.0;
246
- let (tpc_sut, ppc_sut) = match method {
247
- ZMethod::DakSut | ZMethod::HySut => {
248
- if user_tc_pc {
249
- (tc, pc)
250
- } else {
251
- critical_properties::sutton_wa_internal(sg, co2, h2s, n2)
252
- .map_err(pyo3::exceptions::PyValueError::new_err)?
253
- }
254
- }
255
- ZMethod::Bns => (0.0, 0.0), // Not used
256
- };
257
-
258
- let (tpc_bns, ppc_bns) = match method {
259
- ZMethod::Bns => {
260
- if user_tc_pc {
261
- (tc, pc)
262
- } else {
263
- let (t, p, _) = critical_properties::bns_pseudocritical_internal(sg, co2, h2s, n2, h2, he);
264
- (t, p)
265
- }
266
- }
267
- _ => (0.0, 0.0),
268
- };
269
-
270
- // Precompute LBC params for BNS method (propagates HC Tc/Pc override)
271
- let lbc_p = match method {
272
- ZMethod::Bns => {
273
- let (tc_lbc, pc_lbc) = if user_tc_pc { (tc, pc) } else { (0.0, 0.0) };
274
- Some(gas_viscosity::lbc_params(degf, sg, co2, h2s, n2, h2, he, tc_lbc, pc_lbc))
275
- }
276
- _ => None,
277
- };
278
-
279
- let state = GasState {
280
- method, deg_r, sg, tpc_sut, ppc_sut, tpc_bns, ppc_bns,
281
- co2, h2s, n2, h2, he, lbc_params: &lbc_p,
282
- };
285
+ let state = GasState::new(degf, sg, zmethod, cmethod, inerts, tc, pc, true)
286
+ .map_err(pyo3::exceptions::PyValueError::new_err)?;
283
287
 
284
288
  // Two-tier Gauss-Legendre integration
285
289
  let result_7 = state.gl_integrate(p1, p2, &GL7_NODES, &GL7_WEIGHTS);
@@ -310,54 +314,9 @@ pub fn gas_ponz2p_rust(
310
314
  pc: f64,
311
315
  rtol: f64,
312
316
  ) -> PyResult<Vec<f64>> {
313
- // inerts = [co2, h2s, n2, h2, he] mole fractions
314
- let [co2, h2s, n2, h2, he] = inerts;
315
- let deg_r = degf + DEGF2R;
316
-
317
- // Determine method
318
- let method = match (zmethod, cmethod) {
319
- ("DAK", "SUT") | ("DAK", _) if cmethod == "SUT" => ZMethod::DakSut,
320
- ("HY", "SUT") | ("HY", _) if cmethod == "SUT" => ZMethod::HySut,
321
- ("BNS", _) | ("BUR", _) => ZMethod::Bns,
322
- _ => {
323
- return Err(pyo3::exceptions::PyValueError::new_err(
324
- format!("Rust acceleration not available for zmethod={}, cmethod={}", zmethod, cmethod)
325
- ));
326
- }
327
- };
328
-
329
- // Precompute critical properties — see gas_dmp_rust for override semantics.
330
- let user_tc_pc = tc > 0.0 && pc > 0.0;
331
- let (tpc_sut, ppc_sut) = match method {
332
- ZMethod::DakSut | ZMethod::HySut => {
333
- if user_tc_pc {
334
- (tc, pc)
335
- } else {
336
- critical_properties::sutton_wa_internal(sg, co2, h2s, n2)
337
- .map_err(pyo3::exceptions::PyValueError::new_err)?
338
- }
339
- }
340
- ZMethod::Bns => (0.0, 0.0),
341
- };
342
-
343
- let (tpc_bns, ppc_bns) = match method {
344
- ZMethod::Bns => {
345
- if user_tc_pc {
346
- (tc, pc)
347
- } else {
348
- let (t, p, _) = critical_properties::bns_pseudocritical_internal(sg, co2, h2s, n2, h2, he);
349
- (t, p)
350
- }
351
- }
352
- _ => (0.0, 0.0),
353
- };
354
-
355
- // P/Z needs no viscosity, so the LBC parameters stay unset here.
356
- let no_lbc = None;
357
- let state = GasState {
358
- method, deg_r, sg, tpc_sut, ppc_sut, tpc_bns, ppc_bns,
359
- co2, h2s, n2, h2, he, lbc_params: &no_lbc,
360
- };
317
+ // inerts = [co2, h2s, n2, h2, he] mole fractions; P/Z needs no viscosity
318
+ let state = GasState::new(degf, sg, zmethod, cmethod, inerts, tc, pc, false)
319
+ .map_err(pyo3::exceptions::PyValueError::new_err)?;
361
320
 
362
321
  let mut results = Vec::with_capacity(poverz.len());
363
322
  for &target in &poverz {
@@ -149,6 +149,12 @@ pub const GRAY_ND_COEF: f64 = 205.0; // Diameter number coefficient in A
149
149
  pub const GRAY_ROUGH_K: f64 = 28.5; // Effective roughness coefficient
150
150
  pub const GRAY_R_THRESH: f64 = 0.007; // R threshold for roughness interpolation
151
151
  pub const GRAY_SP_LIQ_FRAC: f64 = 1e-6; // v_sg/v_m below this is single-phase liquid (dry roughness)
152
+ /// Below this no-slip liquid fraction the gas march blends each segment's
153
+ /// gradient linearly toward the method's own zero-liquid gradient (weight
154
+ /// lambda / LOW_LIQ_LAMBDA): WG and Gray holdup tend to finite values as liquid
155
+ /// vanishes and Beggs-Brill y = lambda/HL^2 grows without bound (Python
156
+ /// _LOW_LIQ_LAMBDA, nodal.py).
157
+ pub const LOW_LIQ_LAMBDA: f64 = 1e-3;
152
158
  pub const GRAY_ROUGH_FLOOR: f64 = 2.77e-5; // Minimum effective roughness (ft)
153
159
 
154
160
  // ============================================================================