pyrestoolbox 3.0.4__tar.gz → 3.0.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 (93) hide show
  1. pyrestoolbox-3.0.5/MANIFEST.in +8 -0
  2. {pyrestoolbox-3.0.4/pyrestoolbox.egg-info → pyrestoolbox-3.0.5}/PKG-INFO +1 -1
  3. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/brine/brine.py +8 -5
  4. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/docs/brine.rst +14 -12
  5. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/docs/changelist.rst +12 -0
  6. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/docs/matbal.rst +20 -1
  7. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/docs/oil.rst +121 -13
  8. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/docs/simtools.rst +1 -1
  9. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/matbal/matbal.py +19 -1
  10. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/oil/oil.py +201 -61
  11. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/simtools/simtools.py +16 -10
  12. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5/pyrestoolbox.egg-info}/PKG-INFO +1 -1
  13. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox.egg-info/SOURCES.txt +0 -18
  14. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/setup.cfg +5 -2
  15. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/setup.py +2 -2
  16. pyrestoolbox-3.0.4/MANIFEST.in +0 -6
  17. pyrestoolbox-3.0.4/pyrestoolbox/docs/.ipynb_checkpoints/examples-checkpoint.ipynb +0 -1458
  18. pyrestoolbox-3.0.4/pyrestoolbox/docs/.ipynb_checkpoints/nodal_examples-checkpoint.ipynb +0 -825
  19. pyrestoolbox-3.0.4/pyrestoolbox/docs/.ipynb_checkpoints/nodal_hydrate_demo-checkpoint.ipynb +0 -1345
  20. pyrestoolbox-3.0.4/pyrestoolbox/tests/__init__.py +0 -0
  21. pyrestoolbox-3.0.4/pyrestoolbox/tests/run_all_tests.py +0 -98
  22. pyrestoolbox-3.0.4/pyrestoolbox/tests/test_brine.py +0 -266
  23. pyrestoolbox-3.0.4/pyrestoolbox/tests/test_dca.py +0 -675
  24. pyrestoolbox-3.0.4/pyrestoolbox/tests/test_doc_examples.py +0 -1394
  25. pyrestoolbox-3.0.4/pyrestoolbox/tests/test_gas.py +0 -849
  26. pyrestoolbox-3.0.4/pyrestoolbox/tests/test_layer.py +0 -115
  27. pyrestoolbox-3.0.4/pyrestoolbox/tests/test_matbal.py +0 -724
  28. pyrestoolbox-3.0.4/pyrestoolbox/tests/test_nodal.py +0 -836
  29. pyrestoolbox-3.0.4/pyrestoolbox/tests/test_oil.py +0 -499
  30. pyrestoolbox-3.0.4/pyrestoolbox/tests/test_recommend.py +0 -104
  31. pyrestoolbox-3.0.4/pyrestoolbox/tests/test_sensitivity.py +0 -122
  32. pyrestoolbox-3.0.4/pyrestoolbox/tests/test_simtools.py +0 -405
  33. pyrestoolbox-3.0.4/pyrestoolbox/tests/test_unified_brine_design.py +0 -351
  34. pyrestoolbox-3.0.4/pyrestoolbox/tests/test_viscosity_scaling.py +0 -464
  35. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/LICENSE +0 -0
  36. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/README.rst +0 -0
  37. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyproject.toml +0 -0
  38. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/__init__.py +0 -0
  39. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/brine/__init__.py +0 -0
  40. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/brine/_lib_salting_library.py +0 -0
  41. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/brine/_lib_vle_engine.py +0 -0
  42. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/classes/__init__.py +0 -0
  43. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/classes/classes.py +0 -0
  44. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/constants/__init__.py +0 -0
  45. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/constants/constants.py +0 -0
  46. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/dca/__init__.py +0 -0
  47. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/dca/dca.py +0 -0
  48. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/docs/dca.rst +0 -0
  49. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/docs/examples.ipynb +0 -0
  50. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/docs/gas.rst +0 -0
  51. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/docs/img/bot.png +0 -0
  52. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/docs/img/bot_PVTO.png +0 -0
  53. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/docs/img/bot_img.png +0 -0
  54. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/docs/img/dry_gas.png +0 -0
  55. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/docs/img/grid_sat_df.png +0 -0
  56. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/docs/img/influence.png +0 -0
  57. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/docs/img/properties_df.png +0 -0
  58. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/docs/img/sgof.png +0 -0
  59. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/docs/img/swof.png +0 -0
  60. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/docs/layer.rst +0 -0
  61. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/docs/library.rst +0 -0
  62. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/docs/nodal.rst +0 -0
  63. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/docs/nodal_examples.ipynb +0 -0
  64. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/docs/nodal_hydrate_demo.ipynb +0 -0
  65. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/docs/recommend.rst +0 -0
  66. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/docs/sensitivity.rst +0 -0
  67. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/gas/__init__.py +0 -0
  68. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/gas/gas.py +0 -0
  69. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/layer/__init__.py +0 -0
  70. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/layer/layer.py +0 -0
  71. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/library/__init__.py +0 -0
  72. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/library/component_library.xlsx +0 -0
  73. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/library/library.py +0 -0
  74. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/matbal/__init__.py +0 -0
  75. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/nodal/__init__.py +0 -0
  76. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/nodal/nodal.py +0 -0
  77. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/oil/__init__.py +0 -0
  78. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/plyasunov/__init__.py +0 -0
  79. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/plyasunov/iapws_if97.py +0 -0
  80. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/plyasunov/plyasunov_model.py +0 -0
  81. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/plyasunov/water_properties.py +0 -0
  82. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/recommend/__init__.py +0 -0
  83. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/recommend/recommend.py +0 -0
  84. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/sensitivity/__init__.py +0 -0
  85. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/sensitivity/sensitivity.py +0 -0
  86. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/shared_fns/__init__.py +0 -0
  87. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/shared_fns/shared_fns.py +0 -0
  88. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/simtools/__init__.py +0 -0
  89. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/validate/__init__.py +0 -0
  90. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox/validate/validate.py +0 -0
  91. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox.egg-info/dependency_links.txt +0 -0
  92. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox.egg-info/requires.txt +0 -0
  93. {pyrestoolbox-3.0.4 → pyrestoolbox-3.0.5}/pyrestoolbox.egg-info/top_level.txt +0 -0
@@ -0,0 +1,8 @@
1
+ include README.rst
2
+ include LICENSE
3
+ recursive-include pyrestoolbox *.py *.xlsx *.rst *.ipynb *.png
4
+ global-exclude *.pyc
5
+ global-exclude *Zone.Identifier
6
+ prune pyrestoolbox/tests
7
+ prune pyrestoolbox/docs/.ipynb_checkpoints
8
+ recursive-exclude * __pycache__
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyrestoolbox
3
- Version: 3.0.4
3
+ Version: 3.0.5
4
4
  Summary: pyResToolbox - A collection of Reservoir Engineering Utilities
5
5
  Home-page: https://github.com/mwburgoyne/pyResToolbox
6
6
  Author: Mark W. Burgoyne
@@ -24,7 +24,7 @@ Brine property calculations with three models.
24
24
 
25
25
  Functions
26
26
  ---------
27
- brine_props Methane-saturated brine properties (Bw, density, viscosity, Cw, Rs)
27
+ brine_props Methane-saturated brine properties (Bw, density, viscosity, [Cw_usat, Cw_sat], Rs)
28
28
  make_pvtw_table Water PVT table generation (backward-compatible wrapper)
29
29
 
30
30
  Classes
@@ -361,12 +361,15 @@ def brine_props(p: float, degf: float, wt: float=0, ch4_sat: float=0, metric: bo
361
361
  bw = Bw # rb/stb (dimensionless ratio, same in both unit systems)
362
362
  lden = rhobtpbch4 # sg (g/cm3)
363
363
  visw = ub_tpm # cP
364
- cw = cw_new # 1/psi
364
+ cwu_psi = 1 / (145.038 * (1 / cwu)) # Undersaturated compressibility in psi-1
365
+ cws_psi = cw_new # Saturated compressibility in psi-1
365
366
  rsw = rsw_new_oilfield # scf/stb
366
367
 
367
368
  if metric:
368
- cw = cw * INVPSI_TO_INVBAR # 1/psi -> 1/bar
369
+ cw = [cwu_psi * INVPSI_TO_INVBAR, cws_psi * INVPSI_TO_INVBAR] # 1/bar
369
370
  rsw = rsw * SCF_PER_STB_TO_SM3_PER_SM3 # scf/stb -> sm3/sm3
371
+ else:
372
+ cw = [cwu_psi, cws_psi] # 1/psi
370
373
 
371
374
  return (bw, lden, visw, cw, rsw)
372
375
 
@@ -1686,10 +1689,10 @@ class SoreideWhitson:
1686
1689
  # Viscosity: Mao-Duan (2009) via brine_props
1687
1690
  # ================================================================
1688
1691
  # brine_props still needed for viscosity, compressibility, Bw, Rsw
1689
- bw_base, den_base_sg, vis_base_cP, cw_base, rsw_base = brine_props(
1692
+ bw_base, den_base_sg, vis_base_cP, _, rsw_base = brine_props(
1690
1693
  p=psia, degf=degf, wt=wt, ch4_sat=0
1691
1694
  )
1692
- bw_fw, den_fw_sg, vis_fw_cP, cw_fw, rsw_fw = brine_props(
1695
+ bw_fw, den_fw_sg, vis_fw_cP, _, rsw_fw = brine_props(
1693
1696
  p=psia, degf=degf, wt=0, ch4_sat=0
1694
1697
  )
1695
1698
 
@@ -91,8 +91,8 @@ pyrestoolbox.brine.brine_props
91
91
  - float
92
92
  - Viscosity (cP)
93
93
  * - [3]
94
- - float
95
- - Compressibility (1/psi, or 1/barsa if metric=True)
94
+ - list
95
+ - Compressibility [undersaturated, saturated] (1/psi, or 1/barsa if metric=True). The undersaturated value (Cw[0]) is the isothermal compressibility of the brine at constant dissolved gas content. The saturated value (Cw[1]) is a pseudo-compressibility representing the average compressibility of the brine and differentially evolved gas system, accounting for both liquid compression and gas exsolution volume changes
96
96
  * - [4]
97
97
  - float
98
98
  - Rsw — solution gas-water ratio (scf/stb, or sm3/sm3 if metric=True)
@@ -106,17 +106,19 @@ Examples:
106
106
  >>> print('Bw:', bw)
107
107
  >>> print('SGw:', lsg)
108
108
  >>> print('Visw:', visw)
109
- >>> print('Cw:', cw)
109
+ >>> print('Cw_usat:', cw[0])
110
+ >>> print('Cw_sat:', cw[1])
110
111
  >>> print('Rsw:', rsw)
111
112
  Bw: 1.0152007040056148
112
113
  SGw: 0.9950108179684295
113
114
  Visw: 0.4994004662758671
114
- Cw: 0.0001539690974662865
115
+ Cw_usat: 2.969627494768876e-06
116
+ Cw_sat: 0.0001539690974662865
115
117
  Rsw: 1.2540982731813703
116
118
 
117
119
  .. note::
118
120
 
119
- When ``metric=True``, Cw is returned in 1/barsa (instead of 1/psi) and Rsw in sm3/sm3 (instead of scf/stb).
121
+ When ``metric=True``, Cw values are returned in 1/barsa (instead of 1/psi) and Rsw in sm3/sm3 (instead of scf/stb).
120
122
  Bw (rb/stb), density (SG), and viscosity (cP) are unchanged by the unit system.
121
123
 
122
124
  pyrestoolbox.brine.CO2_Brine_Mixture
@@ -187,9 +189,9 @@ pyrestoolbox.brine.CO2_Brine_Mixture
187
189
  * - .Cf_usat
188
190
  - float
189
191
  - Brine undersaturated compressibility (1/Bar or 1/psi)
190
- * - .Cf_ssat
192
+ * - .Cf_sat
191
193
  - float
192
- - Brine saturated compressibility (1/Bar or 1/psi). Requires cw_sat input to be set True to calculate
194
+ - Brine saturated pseudo-compressibility (1/Bar or 1/psi). Represents the average compressibility of the brine and differentially evolved gas system, accounting for both liquid compression and gas exsolution. Requires cw_sat input to be set True to calculate
193
195
 
194
196
 
195
197
  Examples:
@@ -272,7 +274,7 @@ Optionally exports ECLIPSE PVTW keyword file and Excel spreadsheet.
272
274
  - Description
273
275
  * - table
274
276
  - DataFrame
275
- - Pressure, Bw, Density, Viscosity, Cw, Rsw
277
+ - Pressure, Bw, Density, Viscosity, Cw_usat, Cw_sat, Rsw
276
278
  * - pref
277
279
  - float
278
280
  - Reference pressure (psia, or barsa if metric=True)
@@ -280,8 +282,8 @@ Optionally exports ECLIPSE PVTW keyword file and Excel spreadsheet.
280
282
  - float
281
283
  - Bw at reference pressure (rb/stb)
282
284
  * - cw_ref
283
- - float
284
- - Compressibility at reference pressure (1/psi, or 1/bar if metric=True)
285
+ - list
286
+ - Compressibility [undersaturated, saturated] at reference pressure (1/psi, or 1/bar if metric=True)
285
287
  * - visw_ref
286
288
  - float
287
289
  - Viscosity at reference pressure (cP)
@@ -301,7 +303,7 @@ Examples:
301
303
  >>> result['bw_ref']
302
304
  1.027589195773527
303
305
  >>> result['cw_ref']
304
- 3.0887176266534516e-06
306
+ [3.0887176266534516e-06, 3.0887176266534516e-06]
305
307
  >>> result['visw_ref']
306
308
  0.3083544960904146
307
309
 
@@ -421,7 +423,7 @@ based on the gas specific gravity using constrained exponential decay to match t
421
423
  - Brine undersaturated compressibility (1/Bar or 1/psi)
422
424
  * - .Cf_sat
423
425
  - float
424
- - Brine saturated compressibility (1/Bar or 1/psi). Requires cw_sat input to be set True to calculate
426
+ - Brine saturated pseudo-compressibility (1/Bar or 1/psi). Represents the average compressibility of the brine and differentially evolved gas system, accounting for both liquid compression and gas exsolution. Requires cw_sat input to be set True to calculate
425
427
  * - .gas_comp
426
428
  - dict
427
429
  - Normalized gas composition used (including estimated HC split from SG)
@@ -1,3 +1,15 @@
1
+ Changelist in 3.0.5:
2
+
3
+ - **brine_props()**: Compressibility return changed from a scalar (saturated only) to a ``[cw_usat, cw_sat]`` list. The undersaturated value (Spivey Eq 4.32) is the isothermal compressibility at constant dissolved gas content. The saturated value (Spivey Eq 4.35) is a pseudo-compressibility of the brine and differentially evolved gas system. Previously only the saturated value was returned.
4
+ - **oil_co()**: Changed from saturated pseudo-compressibility (``Co = -1/Bo * (dBo/dp - Bg * dRs/dp)``) to undersaturated compressibility (``Co = -1/Bo * dBo/dp`` at constant Rs). Rs is held at the equilibrium value for the specified pressure, yielding the isothermal liquid-phase compressibility without mixing in differentially evolved gas volume. Values below Pb are now smaller and physically consistent with above-Pb values.
5
+ - **oil_co() co_sat parameter**: New ``co_sat=False`` parameter. When ``True``, returns ``[co_usat, co_sat]`` list. Saturated compressibility uses Perrine's definition: ``co_sat = -(1/Bo)*dBo/dp + (Bg/Bo)*dRs/dp``, a pseudo-compressibility including gas evolution effects. Above Pb, both values are equal. Backward compatible — default returns a float.
6
+ - **oil_bt()**: New function returning total two-phase oil FVF: ``Bt = Bo + (Rsi - Rs) * Bg``. Above Pb returns Bo. Useful for material balance and reservoir voidage calculations.
7
+ - **oil_matbal() metric cw/cf fix**: Fixed bug where ``cw`` and ``cf`` in 1/bar were not converted to 1/psi when ``metric=True``, causing the Efw term to be off by ~14.5x. Regression bounds for ``cw``/``cf`` are also now correctly unit-converted.
8
+ - **CO2_Brine_Mixture.Cf_sat** and **SoreideWhitson.Cf_sat**: Documentation clarified that these are pseudo-compressibilities representing the average compressibility of the brine and differentially evolved gas system.
9
+ - **make_pvtw_table()**: Table now includes both ``Cw_usat`` and ``Cw_sat`` columns. ``cw_ref`` is now a ``[usat, sat]`` list. PVTW keyword export uses undersaturated compressibility.
10
+ - **make_bot_og()**: Water compressibility (``cw`` key) now uses undersaturated value from ``brine_props()``.
11
+ - 603 validation tests (up from 588 in 3.0.4).
12
+
1
13
  Changelist in 3.0.4:
2
14
 
3
15
  - **VLP performance**: Eliminated duplicate Z-factor calculations in all 8 VLP method functions. ``_gas_viscosity()`` now accepts a pre-computed Z-factor, avoiding a redundant Hall-Yarborough solve on every segment iteration. Combined with pre-computing Sutton critical properties (Tc/Pc) once per VLP function call instead of recalculating on every segment step. Delivers ~11% speedup on ``operating_point()`` and ``outflow_curve()`` calls.
@@ -257,7 +257,7 @@ Havlena-Odeh oil material balance for OOIP estimation. Computes underground with
257
257
  - Initial water saturation (fraction, default 0)
258
258
  * - cw
259
259
  - float
260
- - Water compressibility (1/psi | 1/bar, default 0)
260
+ - Water compressibility (1/psi | 1/bar, default 0). See compressibility guidance below
261
261
  * - rsmethod
262
262
  - str
263
263
  - Solution GOR method (default 'VELAR')
@@ -370,6 +370,25 @@ Tabulated PVT Example:
370
370
  82793519.84914012
371
371
 
372
372
 
373
+ .. note:: **Compressibility Guidance**
374
+
375
+ The ``cw`` parameter controls how water compressibility is treated in the formation/water expansion term (Efw). Two approaches are common:
376
+
377
+ - **Undersaturated (liquid-phase) compressibility**: Use ``cw_usat`` from ``brine.brine_props()``, which is the isothermal compressibility of brine at constant dissolved gas content. Appropriate when the material balance tracks free gas volumes separately, or when the water does not evolve dissolved gas over the depletion range.
378
+
379
+ - **Saturated (pseudo) compressibility**: Use ``cw_sat`` from ``brine.brine_props()``, which includes the volume of differentially evolved gas. Appropriate when no separate water-gas tracking is performed. Dissolved gas in water can contribute compressibility as large as or larger than the liquid-phase value alone (Ramey, 1964).
380
+
381
+ To compute ``cw`` from brine properties:
382
+
383
+ .. code-block:: python
384
+
385
+ >>> from pyrestoolbox import brine
386
+ >>> props = brine.brine_props(p=4000, degf=220, wt=3)
387
+ >>> cw_usat, cw_sat = props['cw'] # [undersaturated, saturated]
388
+
389
+ Similarly, ``oil.oil_co(co_sat=True)`` returns ``[co_usat, co_sat]`` — the undersaturated value is the liquid-phase compressibility, while the saturated value includes gas evolution effects (Perrine's definition).
390
+
391
+
373
392
  Class Objects
374
393
  ======================
375
394
 
@@ -46,9 +46,9 @@ pyResToolBox uses class objects to track calculation options through the functio
46
46
  + 'VALMC': Valko-McCain Correlation (2003) - Only for oil_rs_bub (Rs at Pb)
47
47
  * - comethod
48
48
  - co_method
49
- - Method for calculating oil compressibility. Defaults to 'EXPLT'.
49
+ - Method for calculating undersaturated oil compressibility. Defaults to 'EXPLT'.
50
50
  Options are:
51
- + 'EXPLT': Explicit calculation with numerical derivatives assessed and calculation via co = -1/bo*(dbodp - bg*drsdp/5.61458).
51
+ + 'EXPLT': Explicit calculation with numerical derivatives via co = -1/bo*dBo/dp at constant Rs.
52
52
  * - denomethod
53
53
  - deno_method
54
54
  - Method for calculating oil density. Defaults to 'SWMH'
@@ -152,7 +152,9 @@ Function List
152
152
  * - Oil GOR at P
153
153
  - `pyrestoolbox.oil.oil_rs`_
154
154
  * - Oil Compressibility
155
- - `pyrestoolbox.oil.oil_co`_
155
+ - `pyrestoolbox.oil.oil_co`_
156
+ * - Total Two-Phase Oil FVF
157
+ - `pyrestoolbox.oil.oil_bt`_
156
158
  * - Oil Density
157
159
  - `pyrestoolbox.oil.oil_deno`_
158
160
  * - Oil Formation Volume Factor
@@ -546,9 +548,14 @@ pyrestoolbox.oil.oil_co
546
548
 
547
549
  .. code-block:: python
548
550
 
549
- oil_co(p, api, degf, sg_sp =0, sg_g =0, pb =0, rsb =0, comethod='EXPLT', zmethod='DAK', rsmethod='VELAR', cmethod='PMC', denomethod='SWMH', bomethod='MCAIN', pbmethod='VALMC', metric = False) -> float
551
+ oil_co(p, api, degf, sg_sp=0, sg_g=0, pb=0, rsb=0, co_sat=False, comethod='EXPLT', zmethod='DAK', rsmethod='VELAR', cmethod='PMC', denomethod='SWMH', bomethod='MCAIN', pbmethod='VALMC', metric=False) -> float or list
552
+
553
+ Returns oil compressibility (1/psi, or 1/barsa if metric=True).
554
+
555
+ By default (``co_sat=False``) returns **undersaturated** compressibility calculated with ``Co = -1/Bo * dBo/dp`` at constant Rs, using correlation values and their numerical derivatives. Rs is held at the equilibrium value for the specified pressure — rsb when above Pb, or the correlation value at p when below Pb. This yields the isothermal compressibility of the liquid oil phase at its current dissolved gas content, without mixing in the volume of differentially evolved gas.
556
+
557
+ When ``co_sat=True``, returns a ``[co_usat, co_sat]`` list. The **saturated** compressibility uses Perrine's definition: ``co_sat = -(1/Bo)*dBo/dp + (Bg/Bo)*dRs/dp``, where both Bo and Rs vary with pressure. This is a pseudo-compressibility representing the average compressibility of the oil and its differentially evolved gas. Above Pb, ``co_sat`` equals ``co_usat`` (no gas evolution).
550
558
 
551
- Returns oil compressibility (1/psi, or 1/barsa if metric=True) calculated with Co = -1/Bo *[dBodp - Bg*dRsdp], using correlation values and their numerical derivatives.
552
559
  At least one of sg_g and sg_sp must be supplied. This function will make simple assumption to estimate missing gas sg if only one is provided.
553
560
  Either pb, rsb or both need to be specified. If one is missing, the other will be calculated from correlation
554
561
 
@@ -581,6 +588,9 @@ Either pb, rsb or both need to be specified. If one is missing, the other will b
581
588
  * - rsb
582
589
  - float
583
590
  - Original solution GOR at original bubble point pressure (scf/stb, or sm3/sm3 if metric=True)
591
+ * - co_sat
592
+ - bool
593
+ - If True, return ``[co_usat, co_sat]`` list. Default False (returns float)
584
594
  * - comethod
585
595
  - string or co_method
586
596
  - The method of Compressibility calculation to be employed. `Calculation Methods and Class Objects`_.
@@ -613,21 +623,119 @@ Either pb, rsb or both need to be specified. If one is missing, the other will b
613
623
  * - Name
614
624
  - Type
615
625
  - Description
616
- * -
626
+ * - co (co_sat=False)
617
627
  - float
618
- - Oil compressibility (1/psi, or 1/barsa if metric=True)
628
+ - Undersaturated oil compressibility (1/psi, or 1/barsa if metric=True)
629
+ * - [co_usat, co_sat] (co_sat=True)
630
+ - list
631
+ - ``[undersaturated, saturated]`` compressibility (1/psi, or 1/barsa if metric=True)
619
632
 
620
633
 
621
634
  Examples:
622
635
 
623
636
  .. code-block:: python
624
637
 
625
- >>> oil.oil_co(p = 4500, api = 47, degf = 180, sg_sp = 0.72, rsb = 2750)
626
- 8.430807614802478e-05
638
+ >>> oil.oil_co(p=6000, api=47, degf=180, sg_sp=0.72, rsb=2750, pb=4945)
639
+ 3.63915926110187e-05
640
+
641
+ >>> oil.oil_co(p=2000, api=47, degf=180, sg_sp=0.72, rsb=2750, pb=4945)
642
+ 1.0122640715155418e-05
643
+
644
+ >>> oil.oil_co(p=2000, api=47, degf=180, sg_sp=0.72, rsb=2750, pb=4945, co_sat=True)
645
+ [1.0122640715155418e-05, 0.0002315283893081275]
646
+
647
+
648
+ pyrestoolbox.oil.oil_bt
649
+ =====================
650
+
651
+ .. code-block:: python
652
+
653
+ oil_bt(p, api, degf, sg_sp=0, sg_g=0, pb=0, rsb=0, rsi=0, zmethod='DAK', rsmethod='VELAR', cmethod='PMC', denomethod='SWMH', bomethod='MCAIN', pbmethod='VALMC', metric=False) -> float
654
+
655
+ Returns total two-phase oil formation volume factor Bt (rb/stb, or rm3/sm3 if metric=True).
656
+
657
+ ``Bt = Bo + (Rsi - Rs) * Bg``
658
+
659
+ Above Pb, Rs = Rsi so Bt = Bo. Below Pb, Bt accounts for the reservoir volume of both the liquid oil and the gas that has evolved from it relative to the original solution GOR.
660
+
661
+ At least one of sg_g and sg_sp must be supplied.
662
+ Either pb, rsb or both need to be specified.
663
+
664
+
665
+ .. list-table:: Inputs
666
+ :widths: 10 15 40
667
+ :header-rows: 1
668
+
669
+ * - Parameter
670
+ - Type
671
+ - Description
672
+ * - p
673
+ - float
674
+ - Pressure (psia, or barsa if metric=True)
675
+ * - api
676
+ - float
677
+ - Density of stock tank liquid (API)
678
+ * - degf
679
+ - float
680
+ - Oil Temperature (deg F, or deg C if metric=True)
681
+ * - sg_sp
682
+ - float
683
+ - Separator gas gravity (relative to air)
684
+ * - sg_g
685
+ - float
686
+ - Weighted average specific gravity of surface gas (relative to air)
687
+ * - pb
688
+ - float
689
+ - Bubble point pressure (psia, or barsa if metric=True)
690
+ * - rsb
691
+ - float
692
+ - Solution GOR at bubble point (scf/stb, or sm3/sm3 if metric=True)
693
+ * - rsi
694
+ - float
695
+ - Initial solution GOR (scf/stb, or sm3/sm3 if metric=True). Default 0 = use rsb
696
+ * - zmethod
697
+ - string or z_method
698
+ - The method of gas z-factor calculation. `Calculation Methods and Class Objects`_.
699
+ * - rsmethod
700
+ - string or rs_method
701
+ - The method of Rs calculation. `Calculation Methods and Class Objects`_.
702
+ * - cmethod
703
+ - string or c_method
704
+ - The method of critical gas property calculation. `Calculation Methods and Class Objects`_.
705
+ * - denomethod
706
+ - string or deno_method
707
+ - The method of live oil density calculation. `Calculation Methods and Class Objects`_.
708
+ * - bomethod
709
+ - string or bo_method
710
+ - The method of Bo calculation. `Calculation Methods and Class Objects`_.
711
+ * - pbmethod
712
+ - string or pb_method
713
+ - The method of Pb calculation. `Calculation Methods and Class Objects`_.
714
+ * - metric
715
+ - bool
716
+ - Use Eclipse METRIC units for inputs/outputs. Default False
717
+
718
+ .. list-table:: Returns
719
+ :widths: 10 15 40
720
+ :header-rows: 1
721
+
722
+ * - Name
723
+ - Type
724
+ - Description
725
+ * -
726
+ - float
727
+ - Total two-phase oil FVF (rb/stb, or rm3/sm3 if metric=True)
728
+
729
+ Examples:
730
+
731
+ .. code-block:: python
732
+
733
+ >>> oil.oil_bt(p=2000, api=47, degf=180, sg_sp=0.72, rsb=2750, pb=4945)
734
+ 4.162163176179142
735
+
736
+ >>> oil.oil_bt(p=6000, api=47, degf=180, sg_sp=0.72, rsb=2750, pb=4945)
737
+ 2.291191400872878
627
738
 
628
- >>> oil.oil_co(p=2000, api=47, degf=180, sg_sp =0.72, rsb =2750, pb=4945)
629
- 0.0002311385007013396
630
-
631
739
 
632
740
  pyrestoolbox.oil.oil_deno
633
741
  ==============================
@@ -1036,7 +1144,7 @@ If user species Pb or Rsb only, the corresponding property will be calculated. I
1036
1144
  - Water density at Pi (lb/cuft)
1037
1145
  * - 'cw'
1038
1146
  - float
1039
- - Water compressibility at Pi (1/psi)
1147
+ - Water undersaturated compressibility at Pi (1/psi)
1040
1148
  * - 'uw'
1041
1149
  - float
1042
1150
  - Water viscosity at Pi (cP)
@@ -1210,7 +1210,7 @@ Creates data required for Oil-Gas-Water black oil tables (PVDO, PVDG, optionally
1210
1210
  - Water density at Pi (lb/cuft, or kg/m3 if metric)
1211
1211
  * - 'cw'
1212
1212
  - float
1213
- - Water compressibility at Pi (1/psi, or 1/bar if metric)
1213
+ - Water undersaturated compressibility at Pi (1/psi, or 1/bar if metric)
1214
1214
  * - 'uw'
1215
1215
  - float
1216
1216
  - Water viscosity at Pi (cP)
@@ -451,6 +451,9 @@ def oil_matbal(p, Np, degf, api=0, sg_sp=0, sg_g=0, pb=0, rsb=0,
451
451
  degf_field = degc_to_degf(degf)
452
452
  pb_field = pb * BAR_TO_PSI if pb > 0 else 0
453
453
  rsb_field = rsb * SM3_PER_SM3_TO_SCF_PER_STB if rsb > 0 else 0
454
+ # Convert compressibilities from 1/bar to 1/psi
455
+ cw = cw / BAR_TO_PSI # 1/bar → 1/psi
456
+ cf = cf / BAR_TO_PSI # 1/bar → 1/psi
454
457
  else:
455
458
  p_field = p
456
459
  degf_field = degf
@@ -567,6 +570,14 @@ def oil_matbal(p, Np, degf, api=0, sg_sp=0, sg_g=0, pb=0, rsb=0,
567
570
 
568
571
  param_names = list(regress.keys())
569
572
  bounds = [regress[k] for k in param_names]
573
+
574
+ # Convert compressibility regression bounds from 1/bar to 1/psi when metric
575
+ if metric:
576
+ bounds = [
577
+ (lo / BAR_TO_PSI, hi / BAR_TO_PSI) if k in ('cw', 'cf') else (lo, hi)
578
+ for k, (lo, hi) in zip(param_names, bounds)
579
+ ]
580
+
570
581
  base_vals = {'m': m, 'cf': cf, 'cw': cw, 'sw_i': sw_i}
571
582
 
572
583
  # Initial guess: use passed value if within bounds, else midpoint
@@ -605,7 +616,14 @@ def oil_matbal(p, Np, degf, api=0, sg_sp=0, sg_g=0, pb=0, rsb=0,
605
616
  cf = base_vals['cf']
606
617
  cw = base_vals['cw']
607
618
  sw_i = base_vals['sw_i']
608
- regressed_result = {k: float(v) for k, v in zip(param_names, opt_result.x)}
619
+
620
+ # Report regressed values back in user's unit system
621
+ regressed_result = {}
622
+ for k, v in zip(param_names, opt_result.x):
623
+ if metric and k in ('cw', 'cf'):
624
+ regressed_result[k] = float(v * BAR_TO_PSI) # 1/psi → 1/bar
625
+ else:
626
+ regressed_result[k] = float(v)
609
627
 
610
628
  # Final computation with (possibly regressed) params
611
629
  ooip, Efw, denom, valid = _compute_result(m, cf, cw, sw_i)