yaeos 4.0.0__cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
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.
- yaeos/__init__.py +56 -0
- yaeos/constants.py +11 -0
- yaeos/core.py +2874 -0
- yaeos/envelopes.py +510 -0
- yaeos/fitting/__init__.py +12 -0
- yaeos/fitting/core.py +199 -0
- yaeos/fitting/model_setters.py +76 -0
- yaeos/fitting/solvers.py +87 -0
- yaeos/gpec.py +225 -0
- yaeos/lib/__init__.py +9 -0
- yaeos/lib/yaeos_python.cpython-312-x86_64-linux-gnu.so +0 -0
- yaeos/models/__init__.py +26 -0
- yaeos/models/excess_gibbs/__init__.py +20 -0
- yaeos/models/excess_gibbs/dortmund.py +45 -0
- yaeos/models/excess_gibbs/nrtl.py +49 -0
- yaeos/models/excess_gibbs/psrk_unifac.py +45 -0
- yaeos/models/excess_gibbs/unifac.py +45 -0
- yaeos/models/excess_gibbs/uniquac.py +117 -0
- yaeos/models/groups.py +49 -0
- yaeos/models/residual_helmholtz/__init__.py +21 -0
- yaeos/models/residual_helmholtz/cubic_eos/__init__.py +38 -0
- yaeos/models/residual_helmholtz/cubic_eos/cubic_eos.py +449 -0
- yaeos/models/residual_helmholtz/cubic_eos/mixing_rules.py +253 -0
- yaeos/models/residual_helmholtz/multifluid/__init__.py +12 -0
- yaeos/models/residual_helmholtz/multifluid/gerg2008.py +73 -0
- yaeos-4.0.0.dist-info/METADATA +87 -0
- yaeos-4.0.0.dist-info/RECORD +34 -0
- yaeos-4.0.0.dist-info/WHEEL +6 -0
- yaeos.libs/libblas-fe34f726.so.3.8.0 +0 -0
- yaeos.libs/libgfortran-8f1e9814.so.5.0.0 +0 -0
- yaeos.libs/libgomp-870cb1d0.so.1.0.0 +0 -0
- yaeos.libs/liblapack-31d7d384.so.3.8.0 +0 -0
- yaeos.libs/libmvec-2-8eb5c230.28.so +0 -0
- yaeos.libs/libquadmath-828275a7.so.0.0.0 +0 -0
yaeos/core.py
ADDED
@@ -0,0 +1,2874 @@
|
|
1
|
+
"""yaeos Python API core module.
|
2
|
+
|
3
|
+
ArModel and GeModel abstract classes definition. Also, the implementation of
|
4
|
+
the models' thermoprops methods.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from abc import ABC
|
8
|
+
from typing import Union
|
9
|
+
|
10
|
+
from intersect import intersection
|
11
|
+
|
12
|
+
import numpy as np
|
13
|
+
|
14
|
+
from yaeos.lib import yaeos_c
|
15
|
+
|
16
|
+
from yaeos.envelopes import PTEnvelope, PXEnvelope, TXEnvelope
|
17
|
+
|
18
|
+
from yaeos.constants import root_kinds
|
19
|
+
|
20
|
+
from warnings import warn
|
21
|
+
|
22
|
+
|
23
|
+
def adjust_root_kind(number_of_phases, kinds_x=None, kind_w=None):
|
24
|
+
"""Convert the the kinds of each phase to the corresponding value.
|
25
|
+
|
26
|
+
The C interface of `yaeos` expects the kinds of each phase to be defined
|
27
|
+
as integer values, so this function converts the kinds of each phase
|
28
|
+
to the corresponding integer value. If the kind is not specified, it
|
29
|
+
defaults to "stable" for all phases.
|
30
|
+
|
31
|
+
Parameters
|
32
|
+
----------
|
33
|
+
number_of_phases : int
|
34
|
+
Number of phases in the system, besides de reference phase
|
35
|
+
kinds_x : list, optional
|
36
|
+
Kinds of the phases in the system, by default None
|
37
|
+
kind_w : str, optional
|
38
|
+
Kind of the test phase, by default None
|
39
|
+
Returns
|
40
|
+
-------
|
41
|
+
tuple
|
42
|
+
kinds_x_out : list
|
43
|
+
List of kinds for each phase in the system
|
44
|
+
kind_w_out : str
|
45
|
+
Kind of the test phase
|
46
|
+
"""
|
47
|
+
if kinds_x:
|
48
|
+
kinds_x_out = [root_kinds[kind] for kind in kinds_x]
|
49
|
+
else:
|
50
|
+
kinds_x_out = [root_kinds["stable"] for _ in range(number_of_phases)]
|
51
|
+
|
52
|
+
if kind_w:
|
53
|
+
kind_w_out = root_kinds[kind_w]
|
54
|
+
else:
|
55
|
+
kind_w_out = root_kinds["stable"]
|
56
|
+
|
57
|
+
return kinds_x_out, kind_w_out
|
58
|
+
|
59
|
+
|
60
|
+
class GeModel(ABC):
|
61
|
+
"""Excess Gibbs (Ge) model abstract class."""
|
62
|
+
|
63
|
+
def ln_gamma(
|
64
|
+
self, moles, temperature: float, dt: bool = False, dn: bool = False
|
65
|
+
) -> Union[np.ndarray, tuple[np.ndarray, dict]]:
|
66
|
+
r"""Calculate natural logarithm of activity coefficients.
|
67
|
+
|
68
|
+
Calculate :math:`\ln \gamma_i(n,T)` vector.
|
69
|
+
|
70
|
+
Parameters
|
71
|
+
----------
|
72
|
+
moles : array_like
|
73
|
+
Moles number vector [mol]
|
74
|
+
temperature : float
|
75
|
+
Temperature [K]
|
76
|
+
|
77
|
+
Returns
|
78
|
+
-------
|
79
|
+
np.ndarray
|
80
|
+
:math:`ln \gamma_i(n,T)` vector
|
81
|
+
|
82
|
+
Example
|
83
|
+
-------
|
84
|
+
.. code-block:: python
|
85
|
+
|
86
|
+
import numpy as np
|
87
|
+
|
88
|
+
from yaeos import NRTL
|
89
|
+
|
90
|
+
|
91
|
+
a = np.array([[0, 0.3], [0.3, 0]])
|
92
|
+
b = np.array([[0, 0.4], [0.4, 0]])
|
93
|
+
c = np.array([[0, 0.5], [0.5, 0]])
|
94
|
+
|
95
|
+
nrtl = NRTL(a, b, c)
|
96
|
+
|
97
|
+
# Evaluating ln_gamma only
|
98
|
+
print(nrtl.ln_gamma([5.0, 5.6], 300.0))
|
99
|
+
|
100
|
+
# Asking for derivatives
|
101
|
+
|
102
|
+
print(nrtl.ln_gamma([5.0, 5.6], 300.0, dt=True, dn=True))
|
103
|
+
"""
|
104
|
+
nc = len(moles)
|
105
|
+
|
106
|
+
dt = np.empty(nc, order="F") if dt else None
|
107
|
+
dn = np.empty((nc, nc), order="F") if dn else None
|
108
|
+
|
109
|
+
res = yaeos_c.ln_gamma_ge(
|
110
|
+
self.id,
|
111
|
+
moles,
|
112
|
+
temperature,
|
113
|
+
dlngamma_dt=dt,
|
114
|
+
dlngamma_dn=dn,
|
115
|
+
)
|
116
|
+
|
117
|
+
if dt is None and dn is None:
|
118
|
+
...
|
119
|
+
else:
|
120
|
+
res = (res, {"dt": dt, "dn": dn})
|
121
|
+
return res
|
122
|
+
|
123
|
+
def excess_gibbs(
|
124
|
+
self,
|
125
|
+
moles,
|
126
|
+
temperature: float,
|
127
|
+
dt: bool = False,
|
128
|
+
dt2: bool = False,
|
129
|
+
dn: bool = False,
|
130
|
+
dtn: bool = False,
|
131
|
+
dn2: bool = False,
|
132
|
+
) -> Union[np.ndarray, tuple[np.ndarray, dict]]:
|
133
|
+
"""Calculate excess Gibbs energy [bar L].
|
134
|
+
|
135
|
+
Parameters
|
136
|
+
----------
|
137
|
+
moles : array_like
|
138
|
+
Moles number vector [mol]
|
139
|
+
temperature : float
|
140
|
+
Temperature [K]
|
141
|
+
dt : bool, optional
|
142
|
+
Calculate temperature derivative, by default False
|
143
|
+
dt2 : bool, optional
|
144
|
+
Calculate temperature second derivative, by default False
|
145
|
+
dn : bool, optional
|
146
|
+
Calculate moles derivative, by default False
|
147
|
+
dtn : bool, optional
|
148
|
+
Calculate cross temperature and moles derivative, by default False
|
149
|
+
dn2 : bool, optional
|
150
|
+
Calculate moles second derivative, by default False
|
151
|
+
|
152
|
+
Returns
|
153
|
+
-------
|
154
|
+
Union[np.ndarray, tuple[np.ndarray, dict]]
|
155
|
+
Excess Gibbs energy or tuple with excess Gibbs energy and
|
156
|
+
derivatives dictionary if any derivative is asked [bar L]
|
157
|
+
|
158
|
+
Example
|
159
|
+
-------
|
160
|
+
.. code-block:: python
|
161
|
+
|
162
|
+
from yaeos import UNIFACVLE
|
163
|
+
|
164
|
+
# Ethanol - water system
|
165
|
+
groups = [{1: 2, 2: 1, 14: 1}, {16: 1}]
|
166
|
+
|
167
|
+
model = UNIFACVLE(groups)
|
168
|
+
|
169
|
+
# Evaluating excess Gibbs energy only
|
170
|
+
print(model.excess_gibbs(model.excess_gibbs([0.5,0.5], 303.15))
|
171
|
+
|
172
|
+
# Asking for derivatives
|
173
|
+
print(
|
174
|
+
model.excess_gibbs(
|
175
|
+
[0.5,0.5],
|
176
|
+
303.15,
|
177
|
+
dt=True,
|
178
|
+
dt2=True,
|
179
|
+
dn=True,
|
180
|
+
dtn=True,
|
181
|
+
dn2=True
|
182
|
+
)
|
183
|
+
"""
|
184
|
+
nc = len(moles)
|
185
|
+
|
186
|
+
dt = np.empty(1, order="F") if dt else None
|
187
|
+
dt2 = np.empty(1, order="F") if dt2 else None
|
188
|
+
dn = np.empty(nc, order="F") if dn else None
|
189
|
+
dtn = np.empty(nc, order="F") if dtn else None
|
190
|
+
dn2 = np.empty((nc, nc), order="F") if dn2 else None
|
191
|
+
|
192
|
+
possible_derivatives = [dt, dt2, dn, dtn]
|
193
|
+
all_none = all([d is None for d in possible_derivatives])
|
194
|
+
|
195
|
+
res = yaeos_c.excess_gibbs_ge(
|
196
|
+
self.id,
|
197
|
+
moles,
|
198
|
+
temperature,
|
199
|
+
get=dt,
|
200
|
+
get2=dt2,
|
201
|
+
gen=dn,
|
202
|
+
getn=dtn,
|
203
|
+
gen2=dn2,
|
204
|
+
)
|
205
|
+
|
206
|
+
if all_none:
|
207
|
+
...
|
208
|
+
else:
|
209
|
+
res = (
|
210
|
+
res,
|
211
|
+
{
|
212
|
+
"dt": dt if dt is None else dt[0],
|
213
|
+
"dt2": dt2 if dt2 is None else dt2[0],
|
214
|
+
"dn": dn,
|
215
|
+
"dtn": dtn,
|
216
|
+
"dn2": dn2,
|
217
|
+
},
|
218
|
+
)
|
219
|
+
|
220
|
+
return res
|
221
|
+
|
222
|
+
def excess_enthalpy(
|
223
|
+
self, moles, temperature: float, dt: bool = False, dn: bool = False
|
224
|
+
) -> Union[np.ndarray, tuple[np.ndarray, dict]]:
|
225
|
+
"""Calculate excess enthalpy [bar L].
|
226
|
+
|
227
|
+
Parameters
|
228
|
+
----------
|
229
|
+
moles : array_like
|
230
|
+
Moles number vector [mol]
|
231
|
+
temperature : float
|
232
|
+
Temperature [K]
|
233
|
+
dt : bool, optional
|
234
|
+
Calculate temperature derivative, by default False
|
235
|
+
dn : bool, optional
|
236
|
+
Calculate moles derivative, by default False
|
237
|
+
|
238
|
+
Returns
|
239
|
+
-------
|
240
|
+
Union[np.ndarray, tuple[np.ndarray, dict]]
|
241
|
+
Excess enthalpy or tuple with excess enthalpy and derivatives
|
242
|
+
dictionary if any derivative is asked [bar L]
|
243
|
+
|
244
|
+
Example
|
245
|
+
-------
|
246
|
+
.. code-block:: python
|
247
|
+
|
248
|
+
from yaeos import UNIFACVLE
|
249
|
+
|
250
|
+
# Ethanol - water system
|
251
|
+
groups = [{1: 2, 2: 1, 14: 1}, {16: 1}]
|
252
|
+
|
253
|
+
model = UNIFACVLE(groups)
|
254
|
+
|
255
|
+
# Evaluating excess enthalpy only
|
256
|
+
print(model.excess_enthalpy([0.5, 0.5], 303.15))
|
257
|
+
|
258
|
+
# Asking for derivatives
|
259
|
+
print(model.excess_enthalpy([0.5, 0.5], 303.15, dt=True, dn=True))
|
260
|
+
"""
|
261
|
+
nc = len(moles)
|
262
|
+
|
263
|
+
dt = np.empty(1, order="F") if dt else None
|
264
|
+
dn = np.empty(nc, order="F") if dn else None
|
265
|
+
|
266
|
+
res = yaeos_c.excess_enthalpy_ge(
|
267
|
+
self.id,
|
268
|
+
moles,
|
269
|
+
temperature,
|
270
|
+
het=dt,
|
271
|
+
hen=dn,
|
272
|
+
)
|
273
|
+
|
274
|
+
if dt is None and dn is None:
|
275
|
+
...
|
276
|
+
else:
|
277
|
+
res = (res, {"dt": dt if dt is None else dt[0], "dn": dn})
|
278
|
+
|
279
|
+
return res
|
280
|
+
|
281
|
+
def excess_entropy(
|
282
|
+
self, moles, temperature: float, dt: bool = False, dn: bool = False
|
283
|
+
) -> Union[np.ndarray, tuple[np.ndarray, dict]]:
|
284
|
+
"""Calculate excess entropy [bar L / K].
|
285
|
+
|
286
|
+
Parameters
|
287
|
+
----------
|
288
|
+
moles : array_like
|
289
|
+
Moles number vector [mol]
|
290
|
+
temperature : float
|
291
|
+
Temperature [K]
|
292
|
+
dt : bool, optional
|
293
|
+
Calculate temperature derivative, by default False
|
294
|
+
dn : bool, optional
|
295
|
+
Calculate moles derivative, by default False
|
296
|
+
|
297
|
+
Returns
|
298
|
+
-------
|
299
|
+
Union[np.ndarray, tuple[np.ndarray, dict]]
|
300
|
+
Excess entropy or tuple with excess entropy and derivatives
|
301
|
+
dictionary if any derivative is asked [bar L / K]
|
302
|
+
|
303
|
+
Example
|
304
|
+
-------
|
305
|
+
.. code-block:: python
|
306
|
+
|
307
|
+
from yaeos import UNIFACVLE
|
308
|
+
|
309
|
+
# Ethanol - water system
|
310
|
+
groups = [{1: 2, 2: 1, 14: 1}, {16: 1}]
|
311
|
+
|
312
|
+
model = UNIFACVLE(groups)
|
313
|
+
|
314
|
+
# Evaluating excess entropy only
|
315
|
+
print(model.excess_entropy([0.5, 0.5], 303.15))
|
316
|
+
|
317
|
+
# Asking for derivatives
|
318
|
+
print(model.excess_entropy([0.5, 0.5], 303.15, dt=True, dn=True))
|
319
|
+
"""
|
320
|
+
nc = len(moles)
|
321
|
+
|
322
|
+
dt = np.empty(1, order="F") if dt else None
|
323
|
+
dn = np.empty(nc, order="F") if dn else None
|
324
|
+
|
325
|
+
res = yaeos_c.excess_entropy_ge(
|
326
|
+
self.id,
|
327
|
+
moles,
|
328
|
+
temperature,
|
329
|
+
set=dt,
|
330
|
+
sen=dn,
|
331
|
+
)
|
332
|
+
|
333
|
+
if dt is None and dn is None:
|
334
|
+
...
|
335
|
+
else:
|
336
|
+
res = (res, {"dt": dt if dt is None else dt[0], "dn": dn})
|
337
|
+
|
338
|
+
return res
|
339
|
+
|
340
|
+
def stability_analysis(self, z, temperature):
|
341
|
+
"""Perform stability analysis.
|
342
|
+
|
343
|
+
Find all the possible minima values that the :math:`tm` function,
|
344
|
+
defined by Michelsen and Mollerup.
|
345
|
+
|
346
|
+
Parameters
|
347
|
+
----------
|
348
|
+
z : array_like
|
349
|
+
Global mole fractions
|
350
|
+
temperature : float
|
351
|
+
Temperature [K]
|
352
|
+
|
353
|
+
Returns
|
354
|
+
-------
|
355
|
+
dict
|
356
|
+
Stability analysis result dictionary with keys:
|
357
|
+
- w: value of the test phase that minimizes the :math:`tm` function
|
358
|
+
- tm: minimum value of the :math:`tm` function.
|
359
|
+
dict
|
360
|
+
All found minimum values of the :math:`tm` function and the
|
361
|
+
corresponding test phase mole fractions.
|
362
|
+
- w: all values of :math:`w` that minimize the :math:`tm` function
|
363
|
+
- tm: all values found minima of the :math:`tm` function"""
|
364
|
+
(w_min, tm_min, all_mins) = yaeos_c.stability_zt_ge(
|
365
|
+
id=self.id, z=z, t=temperature
|
366
|
+
)
|
367
|
+
|
368
|
+
all_mins_w = all_mins[:, : len(z)]
|
369
|
+
all_mins = all_mins[:, -1]
|
370
|
+
|
371
|
+
return {"w": w_min, "tm": tm_min}, {"tm": all_mins, "w": all_mins_w}
|
372
|
+
|
373
|
+
def flash_t(self, z, temperature: float, k0=None) -> dict:
|
374
|
+
"""Two-phase split with specification of temperature and pressure.
|
375
|
+
|
376
|
+
Parameters
|
377
|
+
----------
|
378
|
+
z : array_like
|
379
|
+
Global mole fractions
|
380
|
+
temperature : float
|
381
|
+
Temperature [K]
|
382
|
+
k0 : array_like, optional
|
383
|
+
Initial guess for the split, by default None (will use k_wilson)
|
384
|
+
|
385
|
+
Returns
|
386
|
+
-------
|
387
|
+
dict
|
388
|
+
Flash result dictionary with keys:
|
389
|
+
- x: heavy phase mole fractions
|
390
|
+
- y: light phase mole fractions
|
391
|
+
- Vx: heavy phase volume [L]
|
392
|
+
- Vy: light phase volume [L]
|
393
|
+
- T: temperature [K]
|
394
|
+
- beta: light phase fraction
|
395
|
+
"""
|
396
|
+
if k0 is None:
|
397
|
+
mintpd, _ = self.stability_analysis(z, temperature)
|
398
|
+
k0 = mintpd["w"] / np.array(z)
|
399
|
+
|
400
|
+
x, y, pressure, temperature, volume_x, volume_y, beta = (
|
401
|
+
yaeos_c.flash_ge(self.id, z, t=temperature, k0=k0)
|
402
|
+
)
|
403
|
+
|
404
|
+
flash_result = {
|
405
|
+
"x": x,
|
406
|
+
"y": y,
|
407
|
+
"Vx": volume_x,
|
408
|
+
"Vy": volume_y,
|
409
|
+
"T": temperature,
|
410
|
+
"beta": beta,
|
411
|
+
}
|
412
|
+
|
413
|
+
return flash_result
|
414
|
+
|
415
|
+
def __del__(self) -> None:
|
416
|
+
"""Delete the model from the available models list (Fortran side)."""
|
417
|
+
yaeos_c.make_available_ge_models_list(self.id)
|
418
|
+
|
419
|
+
|
420
|
+
class ArModel(ABC):
|
421
|
+
"""Residual Helmholtz (Ar) model abstract class."""
|
422
|
+
|
423
|
+
def lnphi_vt(
|
424
|
+
self,
|
425
|
+
moles,
|
426
|
+
volume: float,
|
427
|
+
temperature: float,
|
428
|
+
dt: bool = False,
|
429
|
+
dp: bool = False,
|
430
|
+
dn: bool = False,
|
431
|
+
) -> Union[np.ndarray, tuple[np.ndarray, dict]]:
|
432
|
+
r"""Calculate fugacity coefficent given volume and temperature.
|
433
|
+
|
434
|
+
Calculate :math:`ln \phi_i(n,V,T)` and its derivatives with respect to
|
435
|
+
temperature, pressure and moles number.
|
436
|
+
|
437
|
+
Parameters
|
438
|
+
----------
|
439
|
+
moles : array_like
|
440
|
+
Moles number vector [mol]
|
441
|
+
volume : float
|
442
|
+
Volume [L]
|
443
|
+
temperature : float
|
444
|
+
Temperature [K]
|
445
|
+
dt : bool, optional
|
446
|
+
Calculate temperature derivative, by default False
|
447
|
+
dp : bool, optional
|
448
|
+
Calculate pressure derivative, by default False
|
449
|
+
dn : bool, optional
|
450
|
+
Calculate moles derivative, by default False
|
451
|
+
|
452
|
+
Returns
|
453
|
+
-------
|
454
|
+
Union[np.ndarray, tuple[np.ndarray, dict]]
|
455
|
+
:math:`ln \phi_i(n,V,T)` vector or tuple with
|
456
|
+
:math:`ln \phi_i(n,V,T)` vector and derivatives dictionary if any
|
457
|
+
derivative is asked
|
458
|
+
|
459
|
+
Example
|
460
|
+
-------
|
461
|
+
.. code-block:: python
|
462
|
+
|
463
|
+
import numpy as np
|
464
|
+
|
465
|
+
from yaeos import PengRobinson76
|
466
|
+
|
467
|
+
|
468
|
+
tc = np.array([320.0, 375.0]) # critical temperatures [K]
|
469
|
+
pc = np.array([45.0, 60.0]) # critical pressures [bar]
|
470
|
+
w = np.array([0.0123, 0.045]) # acentric factors
|
471
|
+
|
472
|
+
model = PengRobinson76(tc, pc, w)
|
473
|
+
|
474
|
+
# Evaluating ln_phi only
|
475
|
+
# will print: [-1.45216274 -2.01044828]
|
476
|
+
|
477
|
+
print(model.lnphi_vt([5.0, 5.6], 1.0, 300.0))
|
478
|
+
|
479
|
+
# Asking for derivatives
|
480
|
+
# will print:
|
481
|
+
# (
|
482
|
+
# array([-1.45216274, -2.01044828]),
|
483
|
+
# {'dt': array([0.01400063, 0.01923493]), 'dp': None, 'dn': None}
|
484
|
+
# )
|
485
|
+
|
486
|
+
print(model.lnphi_vt([5.0, 5.6], 1.0, 300.0, dt=True)
|
487
|
+
"""
|
488
|
+
nc = len(moles)
|
489
|
+
|
490
|
+
dt = np.empty(nc, order="F") if dt else None
|
491
|
+
dp = np.empty(nc, order="F") if dp else None
|
492
|
+
dn = np.empty((nc, nc), order="F") if dn else None
|
493
|
+
|
494
|
+
res = yaeos_c.lnphi_vt(
|
495
|
+
self.id,
|
496
|
+
moles,
|
497
|
+
volume,
|
498
|
+
temperature,
|
499
|
+
dlnphidt=dt,
|
500
|
+
dlnphidp=dp,
|
501
|
+
dlnphidn=dn,
|
502
|
+
)
|
503
|
+
|
504
|
+
if dt is None and dp is None and dn is None:
|
505
|
+
...
|
506
|
+
else:
|
507
|
+
res = (res, {"dt": dt, "dp": dp, "dn": dn})
|
508
|
+
return res
|
509
|
+
|
510
|
+
def lnphi_pt(
|
511
|
+
self,
|
512
|
+
moles,
|
513
|
+
pressure: float,
|
514
|
+
temperature: float,
|
515
|
+
root: str = "stable",
|
516
|
+
dt: bool = False,
|
517
|
+
dp: bool = False,
|
518
|
+
dn: bool = False,
|
519
|
+
) -> Union[np.ndarray, tuple[np.ndarray, dict]]:
|
520
|
+
r"""Calculate fugacity coefficent given pressure and temperature.
|
521
|
+
|
522
|
+
Calculate :math:`ln \phi_i(n,P,T)` and its derivatives with respect to
|
523
|
+
temperature, pressure and moles number.
|
524
|
+
|
525
|
+
Parameters
|
526
|
+
----------
|
527
|
+
moles : array_like
|
528
|
+
Moles number vector [mol]
|
529
|
+
pressure : float
|
530
|
+
Pressure [bar]
|
531
|
+
temperature : float
|
532
|
+
Temperature [K]
|
533
|
+
root : str, optional
|
534
|
+
Volume root, use: "liquid", "vapor" or "stable", by default
|
535
|
+
"stable"
|
536
|
+
dt : bool, optional
|
537
|
+
Calculate temperature derivative, by default False
|
538
|
+
dp : bool, optional
|
539
|
+
Calculate pressure derivative, by default False
|
540
|
+
dn : bool, optional
|
541
|
+
Calculate moles derivative, by default False
|
542
|
+
|
543
|
+
Returns
|
544
|
+
-------
|
545
|
+
Union[np.ndarray, tuple[np.ndarray, dict]]
|
546
|
+
:math:`ln \phi_i(n,P,T)` vector or tuple with
|
547
|
+
:math:`ln \phi_i(n,P,T)` vector and derivatives dictionary if any
|
548
|
+
derivative is asked
|
549
|
+
|
550
|
+
Example
|
551
|
+
-------
|
552
|
+
.. code-block:: python
|
553
|
+
|
554
|
+
import numpy as np
|
555
|
+
|
556
|
+
from yaeos import PengRobinson76
|
557
|
+
|
558
|
+
|
559
|
+
tc = np.array([320.0, 375.0]) # critical temperatures [K]
|
560
|
+
pc = np.array([45.0, 60.0]) # critical pressures [bar]
|
561
|
+
w = np.array([0.0123, 0.045]) # acentric factors
|
562
|
+
|
563
|
+
model = PengRobinson76(tc, pc, w)
|
564
|
+
|
565
|
+
# Evaluating ln_phi only
|
566
|
+
# will print: [-0.10288733 -0.11909807]
|
567
|
+
|
568
|
+
print(model.lnphi_pt([5.0, 5.6], 10.0, 300.0))
|
569
|
+
|
570
|
+
# Asking for derivatives
|
571
|
+
# will print:
|
572
|
+
# (
|
573
|
+
# array([-0.10288733, -0.11909807]),
|
574
|
+
# {'dt': array([0.00094892, 0.00108809]), 'dp': None, 'dn': None}
|
575
|
+
# )
|
576
|
+
|
577
|
+
print(model.lnphi_pt([5.0, 5.6], 10.0, 300.0, dt=True)
|
578
|
+
"""
|
579
|
+
nc = len(moles)
|
580
|
+
|
581
|
+
dt = np.empty(nc, order="F") if dt else None
|
582
|
+
dp = np.empty(nc, order="F") if dp else None
|
583
|
+
dn = np.empty((nc, nc), order="F") if dn else None
|
584
|
+
|
585
|
+
res = yaeos_c.lnphi_pt(
|
586
|
+
self.id,
|
587
|
+
moles,
|
588
|
+
pressure,
|
589
|
+
temperature,
|
590
|
+
root,
|
591
|
+
dlnphidt=dt,
|
592
|
+
dlnphidp=dp,
|
593
|
+
dlnphidn=dn,
|
594
|
+
)
|
595
|
+
|
596
|
+
if dt is None and dp is None and dn is None:
|
597
|
+
...
|
598
|
+
else:
|
599
|
+
res = (res, {"dt": dt, "dp": dp, "dn": dn})
|
600
|
+
return res
|
601
|
+
|
602
|
+
def pressure(
|
603
|
+
self,
|
604
|
+
moles,
|
605
|
+
volume: float,
|
606
|
+
temperature: float,
|
607
|
+
dv: bool = False,
|
608
|
+
dt: bool = False,
|
609
|
+
dn: bool = False,
|
610
|
+
) -> Union[float, tuple[float, dict]]:
|
611
|
+
"""Calculate pressure given volume and temperature [bar].
|
612
|
+
|
613
|
+
Calculate :math:`P(n,V,T)` and its derivatives with respect to
|
614
|
+
volume, temperature and moles number.
|
615
|
+
|
616
|
+
Parameters
|
617
|
+
----------
|
618
|
+
moles : array_like
|
619
|
+
Moles number vector [mol]
|
620
|
+
volume : float
|
621
|
+
Volume [L]
|
622
|
+
temperature : float
|
623
|
+
Temperature [K]
|
624
|
+
dv : bool, optional
|
625
|
+
Calculate volume derivative, by default False
|
626
|
+
dt : bool, optional
|
627
|
+
Calculate temperature derivative, by default False
|
628
|
+
dn : bool, optional
|
629
|
+
Calculate moles derivative, by default False
|
630
|
+
|
631
|
+
Returns
|
632
|
+
-------
|
633
|
+
Union[float, tuple[float, dict]]
|
634
|
+
Pressure or tuple with Presure and derivatives dictionary if any
|
635
|
+
derivative is asked [bar]
|
636
|
+
|
637
|
+
Example
|
638
|
+
-------
|
639
|
+
.. code-block:: python
|
640
|
+
|
641
|
+
import numpy as np
|
642
|
+
|
643
|
+
from yaeos import PengRobinson76
|
644
|
+
|
645
|
+
|
646
|
+
tc = np.array([320.0, 375.0]) # critical temperatures [K]
|
647
|
+
pc = np.array([45.0, 60.0]) # critical pressures [bar]
|
648
|
+
w = np.array([0.0123, 0.045]) # acentric factors
|
649
|
+
|
650
|
+
model = PengRobinson76(tc, pc, w)
|
651
|
+
|
652
|
+
# Evaluating pressure only
|
653
|
+
# will print: 16.011985733846956
|
654
|
+
|
655
|
+
print(model.pressure(np.array([5.0, 5.6]), 2.0, 300.0))
|
656
|
+
|
657
|
+
# Asking for derivatives
|
658
|
+
# will print:
|
659
|
+
# (
|
660
|
+
# 16.011985733846956,
|
661
|
+
# {'dv': None, 'dt': np.float64(0.7664672352866752), 'dn': None}
|
662
|
+
# )
|
663
|
+
|
664
|
+
print(model.pressure(np.array([5.0, 5.6]), 2.0, 300.0, dt=True))
|
665
|
+
"""
|
666
|
+
nc = len(moles)
|
667
|
+
|
668
|
+
dv = np.empty(1, order="F") if dv else None
|
669
|
+
dt = np.empty(1, order="F") if dt else None
|
670
|
+
dn = np.empty(nc, order="F") if dn else None
|
671
|
+
|
672
|
+
res = yaeos_c.pressure(
|
673
|
+
self.id, moles, volume, temperature, dpdv=dv, dpdt=dt, dpdn=dn
|
674
|
+
)
|
675
|
+
|
676
|
+
if dt is None and dv is None and dn is None:
|
677
|
+
...
|
678
|
+
else:
|
679
|
+
res = (
|
680
|
+
res,
|
681
|
+
{
|
682
|
+
"dv": dv if dv is None else dv[0],
|
683
|
+
"dt": dt if dt is None else dt[0],
|
684
|
+
"dn": dn,
|
685
|
+
},
|
686
|
+
)
|
687
|
+
return res
|
688
|
+
|
689
|
+
def volume(
|
690
|
+
self, moles, pressure: float, temperature: float, root: str = "stable"
|
691
|
+
) -> float:
|
692
|
+
"""Calculate volume given pressure and temperature [L].
|
693
|
+
|
694
|
+
Parameters
|
695
|
+
----------
|
696
|
+
moles : array_like
|
697
|
+
Moles number vector [mol]
|
698
|
+
pressure : float
|
699
|
+
Pressure [bar]
|
700
|
+
temperature : float
|
701
|
+
Temperature [K]
|
702
|
+
root : str, optional
|
703
|
+
Volume root, use: "liquid", "vapor" or "stable", by default
|
704
|
+
"stable"
|
705
|
+
|
706
|
+
Returns
|
707
|
+
-------
|
708
|
+
float
|
709
|
+
Volume [L]
|
710
|
+
|
711
|
+
Example
|
712
|
+
-------
|
713
|
+
.. code-block:: python
|
714
|
+
|
715
|
+
import numpy as np
|
716
|
+
|
717
|
+
from yaeos import PengRobinson76
|
718
|
+
|
719
|
+
|
720
|
+
tc = np.array([320.0, 375.0]) # critical temperatures [K]
|
721
|
+
pc = np.array([45.0, 60.0]) # critical pressures [bar]
|
722
|
+
w = np.array([0.0123, 0.045]) # acentric factors
|
723
|
+
|
724
|
+
model = PengRobinson76(tc, pc, w)
|
725
|
+
|
726
|
+
# Evaluating stable root volume
|
727
|
+
# will print: 23.373902973572587
|
728
|
+
|
729
|
+
print(model.volume(np.array([5.0, 5.6]), 10.0, 300.0))
|
730
|
+
|
731
|
+
# Liquid root volume (not stable)
|
732
|
+
# will print: 0.8156388756398074
|
733
|
+
|
734
|
+
print(model.volume(np.array([5.0, 5.6]), 10.0, 300.0, "liquid"))
|
735
|
+
"""
|
736
|
+
res = yaeos_c.volume(self.id, moles, pressure, temperature, root)
|
737
|
+
return res
|
738
|
+
|
739
|
+
def enthalpy_residual_vt(
|
740
|
+
self,
|
741
|
+
moles,
|
742
|
+
volume: float,
|
743
|
+
temperature: float,
|
744
|
+
dt: bool = False,
|
745
|
+
dv: bool = False,
|
746
|
+
dn: bool = False,
|
747
|
+
) -> Union[float, tuple[float, dict]]:
|
748
|
+
"""Calculate residual enthalpy given volume and temperature [bar L].
|
749
|
+
|
750
|
+
Parameters
|
751
|
+
----------
|
752
|
+
moles : array_like
|
753
|
+
Moles number vector [mol]
|
754
|
+
volume : float
|
755
|
+
Volume [L]
|
756
|
+
temperature : float
|
757
|
+
Temperature [K]
|
758
|
+
|
759
|
+
Returns
|
760
|
+
-------
|
761
|
+
Union[float, tuple[float, dict]]
|
762
|
+
Residual enthalpy or tuple with Residual enthalpy and derivatives
|
763
|
+
dictionary if any derivative is asked [bar L]
|
764
|
+
|
765
|
+
Example
|
766
|
+
-------
|
767
|
+
.. code-block:: python
|
768
|
+
|
769
|
+
import numpy as np
|
770
|
+
|
771
|
+
from yaeos import PengRobinson76
|
772
|
+
|
773
|
+
|
774
|
+
tc = np.array([320.0, 375.0]) # critical temperatures [K]
|
775
|
+
pc = np.array([45.0, 60.0]) # critical pressures [bar]
|
776
|
+
w = np.array([0.0123, 0.045]) # acentric factors
|
777
|
+
|
778
|
+
model = PengRobinson76(tc, pc, w)
|
779
|
+
|
780
|
+
# Evaluating residual enthalpy only
|
781
|
+
# will print: -182.50424367123696
|
782
|
+
|
783
|
+
print(
|
784
|
+
model.enthalpy_residual_vt(np.array([5.0, 5.6]), 10.0, 300.0)
|
785
|
+
)
|
786
|
+
|
787
|
+
# Asking for derivatives
|
788
|
+
# will print:
|
789
|
+
# (
|
790
|
+
# -182.50424367123696,
|
791
|
+
# {'dt': 0.21542452742588686, 'dv': None, 'dn': None}
|
792
|
+
# )
|
793
|
+
|
794
|
+
print(
|
795
|
+
model.enthalpy_residual_vt(
|
796
|
+
np.array([5.0, 5.6]),
|
797
|
+
10.0,
|
798
|
+
300.0,
|
799
|
+
dt=True)
|
800
|
+
)
|
801
|
+
)
|
802
|
+
"""
|
803
|
+
nc = len(moles)
|
804
|
+
|
805
|
+
dt = np.empty(1, order="F") if dt else None
|
806
|
+
dv = np.empty(1, order="F") if dv else None
|
807
|
+
dn = np.empty(nc, order="F") if dn else None
|
808
|
+
|
809
|
+
res = yaeos_c.enthalpy_residual_vt(
|
810
|
+
self.id,
|
811
|
+
moles,
|
812
|
+
volume,
|
813
|
+
temperature,
|
814
|
+
hrt=dt,
|
815
|
+
hrv=dv,
|
816
|
+
hrn=dn,
|
817
|
+
)
|
818
|
+
|
819
|
+
if dt is None and dv is None and dn is None:
|
820
|
+
...
|
821
|
+
else:
|
822
|
+
res = (
|
823
|
+
res,
|
824
|
+
{
|
825
|
+
"dt": dt if dt is None else dt[0],
|
826
|
+
"dv": dv if dv is None else dv[0],
|
827
|
+
"dn": dn,
|
828
|
+
},
|
829
|
+
)
|
830
|
+
return res
|
831
|
+
|
832
|
+
def gibbs_residual_vt(
|
833
|
+
self,
|
834
|
+
moles,
|
835
|
+
volume: float,
|
836
|
+
temperature: float,
|
837
|
+
dt: bool = False,
|
838
|
+
dv: bool = False,
|
839
|
+
dn: bool = False,
|
840
|
+
) -> Union[float, tuple[float, dict]]:
|
841
|
+
"""Calculate residual Gibbs energy at volume and temperature [bar L].
|
842
|
+
|
843
|
+
Parameters
|
844
|
+
----------
|
845
|
+
moles : array_like
|
846
|
+
Moles number vector [mol]
|
847
|
+
volume : float
|
848
|
+
Volume [L]
|
849
|
+
temperature : float
|
850
|
+
Temperature [K]
|
851
|
+
|
852
|
+
Returns
|
853
|
+
-------
|
854
|
+
Union[float, tuple[float, dict]]
|
855
|
+
Residual Gibbs energy or tuple with Residual Gibbs energy and
|
856
|
+
derivatives dictionary if any derivative is asked [bar L]
|
857
|
+
|
858
|
+
Example
|
859
|
+
-------
|
860
|
+
.. code-block:: python
|
861
|
+
|
862
|
+
import numpy as np
|
863
|
+
|
864
|
+
from yaeos import PengRobinson76
|
865
|
+
|
866
|
+
|
867
|
+
tc = np.array([320.0, 375.0]) # critical temperatures [K]
|
868
|
+
pc = np.array([45.0, 60.0]) # critical pressures [bar]
|
869
|
+
w = np.array([0.0123, 0.045]) # acentric factors
|
870
|
+
|
871
|
+
model = PengRobinson76(tc, pc, w)
|
872
|
+
|
873
|
+
# Evaluating residual gibbs energy only
|
874
|
+
# will print: -138.60374582274
|
875
|
+
|
876
|
+
print(model.gibbs_residual_vt(np.array([5.0, 5.6]), 10.0, 300.0))
|
877
|
+
|
878
|
+
# Asking for derivatives
|
879
|
+
# will print:
|
880
|
+
# (
|
881
|
+
# -138.60374582274,
|
882
|
+
# {'dt': 0.289312908265414, 'dv': None, 'dn': None}
|
883
|
+
# )
|
884
|
+
|
885
|
+
print(
|
886
|
+
model.gibbs_residual_vt(
|
887
|
+
np.array([5.0, 5.6]),
|
888
|
+
10.0,
|
889
|
+
300.0,
|
890
|
+
dt=True
|
891
|
+
)
|
892
|
+
)
|
893
|
+
"""
|
894
|
+
nc = len(moles)
|
895
|
+
|
896
|
+
dt = np.empty(1, order="F") if dt else None
|
897
|
+
dv = np.empty(1, order="F") if dv else None
|
898
|
+
dn = np.empty(nc, order="F") if dn else None
|
899
|
+
|
900
|
+
res = yaeos_c.gibbs_residual_vt(
|
901
|
+
self.id,
|
902
|
+
moles,
|
903
|
+
volume,
|
904
|
+
temperature,
|
905
|
+
grt=dt,
|
906
|
+
grv=dv,
|
907
|
+
grn=dn,
|
908
|
+
)
|
909
|
+
|
910
|
+
if dt is None and dv is None and dn is None:
|
911
|
+
...
|
912
|
+
else:
|
913
|
+
res = (
|
914
|
+
res,
|
915
|
+
{
|
916
|
+
"dt": dt if dt is None else dt[0],
|
917
|
+
"dv": dv if dv is None else dv[0],
|
918
|
+
"dn": dn,
|
919
|
+
},
|
920
|
+
)
|
921
|
+
return res
|
922
|
+
|
923
|
+
def entropy_residual_vt(
|
924
|
+
self,
|
925
|
+
moles,
|
926
|
+
volume: float,
|
927
|
+
temperature: float,
|
928
|
+
dt: bool = False,
|
929
|
+
dv: bool = False,
|
930
|
+
dn: bool = False,
|
931
|
+
) -> Union[float, tuple[float, dict]]:
|
932
|
+
"""Calculate residual entropy given volume and temperature [bar L / K].
|
933
|
+
|
934
|
+
Parameters
|
935
|
+
----------
|
936
|
+
moles : array_like
|
937
|
+
Moles number vector [mol]
|
938
|
+
volume : float
|
939
|
+
Volume [L]
|
940
|
+
temperature : float
|
941
|
+
Temperature [K]
|
942
|
+
|
943
|
+
Returns
|
944
|
+
-------
|
945
|
+
Union[float, tuple[float, dict]]
|
946
|
+
Residual entropy or tuple with Residual entropy and derivatives
|
947
|
+
dictionary if any derivative is asked [bar L]
|
948
|
+
|
949
|
+
Example
|
950
|
+
-------
|
951
|
+
.. code-block:: python
|
952
|
+
|
953
|
+
import numpy as np
|
954
|
+
|
955
|
+
from yaeos import PengRobinson76
|
956
|
+
|
957
|
+
|
958
|
+
tc = np.array([320.0, 375.0]) # critical temperatures [K]
|
959
|
+
pc = np.array([45.0, 60.0]) # critical pressures [bar]
|
960
|
+
w = np.array([0.0123, 0.045]) # acentric factors
|
961
|
+
|
962
|
+
model = PengRobinson76(tc, pc, w)
|
963
|
+
|
964
|
+
# Evaluating residual entropy only
|
965
|
+
# will print: -0.1463349928283233
|
966
|
+
|
967
|
+
print(model.entropy_residual_vt(np.array([5.0, 5.6]), 10.0, 300.0))
|
968
|
+
|
969
|
+
# Asking for derivatives
|
970
|
+
# will print:
|
971
|
+
# (
|
972
|
+
# (-0.1463349928283233,
|
973
|
+
# {'dt': 0.00024148870662932045, 'dv': None, 'dn': None})
|
974
|
+
# )
|
975
|
+
|
976
|
+
print(
|
977
|
+
model.entropy_residual_vt(
|
978
|
+
np.array([5.0, 5.6]),
|
979
|
+
10.0,
|
980
|
+
300.0,
|
981
|
+
dt=True
|
982
|
+
)
|
983
|
+
)
|
984
|
+
"""
|
985
|
+
nc = len(moles)
|
986
|
+
|
987
|
+
dt = np.empty(1, order="F") if dt else None
|
988
|
+
dv = np.empty(1, order="F") if dv else None
|
989
|
+
dn = np.empty(nc, order="F") if dn else None
|
990
|
+
|
991
|
+
res = yaeos_c.entropy_residual_vt(
|
992
|
+
self.id,
|
993
|
+
moles,
|
994
|
+
volume,
|
995
|
+
temperature,
|
996
|
+
srt=dt,
|
997
|
+
srv=dv,
|
998
|
+
srn=dn,
|
999
|
+
)
|
1000
|
+
|
1001
|
+
if dt is None and dv is None and dn is None:
|
1002
|
+
...
|
1003
|
+
else:
|
1004
|
+
res = (
|
1005
|
+
res,
|
1006
|
+
{
|
1007
|
+
"dt": dt if dt is None else dt[0],
|
1008
|
+
"dv": dv if dv is None else dv[0],
|
1009
|
+
"dn": dn,
|
1010
|
+
},
|
1011
|
+
)
|
1012
|
+
return res
|
1013
|
+
|
1014
|
+
def cv_residual_vt(
|
1015
|
+
self, moles, volume: float, temperature: float
|
1016
|
+
) -> float:
|
1017
|
+
"""Residual isochoric heat capacity given V and T [bar L / K].
|
1018
|
+
|
1019
|
+
Parameters
|
1020
|
+
----------
|
1021
|
+
moles : array_like
|
1022
|
+
Moles number vector [mol]
|
1023
|
+
volume : float
|
1024
|
+
Volume [L]
|
1025
|
+
temperature : float
|
1026
|
+
Temperature [K]
|
1027
|
+
|
1028
|
+
Returns
|
1029
|
+
-------
|
1030
|
+
float
|
1031
|
+
Residual isochoric heat capacity [bar L / K]
|
1032
|
+
|
1033
|
+
Example
|
1034
|
+
-------
|
1035
|
+
.. code-block:: python
|
1036
|
+
|
1037
|
+
import numpy as np
|
1038
|
+
|
1039
|
+
from yaeos import PengRobinson76
|
1040
|
+
|
1041
|
+
|
1042
|
+
tc = np.array([320.0, 375.0]) # critical temperatures [K]
|
1043
|
+
pc = np.array([45.0, 60.0]) # critical pressures [bar]
|
1044
|
+
w = np.array([0.0123, 0.045]) # acentric factors
|
1045
|
+
|
1046
|
+
model = PengRobinson76(tc, pc, w)
|
1047
|
+
|
1048
|
+
# Evaluating residual isochoric heat capacity only
|
1049
|
+
# will print: 0.07244661198879614
|
1050
|
+
|
1051
|
+
print(model.cv_residual_vt(np.array([5.0, 5.6]), 10.0, 300.0))
|
1052
|
+
"""
|
1053
|
+
return yaeos_c.cv_residual_vt(self.id, moles, volume, temperature)
|
1054
|
+
|
1055
|
+
def cp_residual_vt(
|
1056
|
+
self, moles, volume: float, temperature: float
|
1057
|
+
) -> float:
|
1058
|
+
"""Calculate residual isobaric heat capacity given V and T [bar L / K].
|
1059
|
+
|
1060
|
+
Parameters
|
1061
|
+
----------
|
1062
|
+
moles : array_like
|
1063
|
+
Moles number vector [mol]
|
1064
|
+
volume : float
|
1065
|
+
Volume [L]
|
1066
|
+
temperature : float
|
1067
|
+
Temperature [K]
|
1068
|
+
|
1069
|
+
Returns
|
1070
|
+
-------
|
1071
|
+
float
|
1072
|
+
Residual isochoric heat capacity [bar L / K]
|
1073
|
+
|
1074
|
+
Example
|
1075
|
+
-------
|
1076
|
+
.. code-block:: python
|
1077
|
+
|
1078
|
+
import numpy as np
|
1079
|
+
|
1080
|
+
from yaeos import PengRobinson76
|
1081
|
+
|
1082
|
+
|
1083
|
+
tc = np.array([320.0, 375.0]) # critical temperatures [K]
|
1084
|
+
pc = np.array([45.0, 60.0]) # critical pressures [bar]
|
1085
|
+
w = np.array([0.0123, 0.045]) # acentric factors
|
1086
|
+
|
1087
|
+
model = PengRobinson76(tc, pc, w)
|
1088
|
+
|
1089
|
+
# Evaluating residual isobaric heat capacity only
|
1090
|
+
# will print: 1.4964025088916886
|
1091
|
+
|
1092
|
+
print(model.cp_residual_vt(np.array([5.0, 5.6]), 10.0, 300.0))
|
1093
|
+
"""
|
1094
|
+
return yaeos_c.cp_residual_vt(self.id, moles, volume, temperature)
|
1095
|
+
|
1096
|
+
def pure_saturation_pressures(
|
1097
|
+
self, component, stop_pressure=0.01, stop_temperature=100
|
1098
|
+
):
|
1099
|
+
"""Calculate pure component saturation pressures [bar].
|
1100
|
+
|
1101
|
+
Calculation starts from the critical point and goes down to the
|
1102
|
+
stop pressure or stop temperature.
|
1103
|
+
|
1104
|
+
Parameters
|
1105
|
+
----------
|
1106
|
+
component : int
|
1107
|
+
Component index (starting from 1)
|
1108
|
+
stop_pressure : float, optional
|
1109
|
+
Stop pressure [bar], by default 0.01
|
1110
|
+
stop_temperature : float, optional
|
1111
|
+
Stop temperature [K], by default 100
|
1112
|
+
|
1113
|
+
Returns
|
1114
|
+
-------
|
1115
|
+
dict
|
1116
|
+
Pure component saturation points dictionary with keys:
|
1117
|
+
- T: Temperature [K]
|
1118
|
+
- P: Pressure [bar]
|
1119
|
+
- Vx: Liquid Phase Volume [L/mole]
|
1120
|
+
- Vy: Vapor Phase Volume [L/mole]
|
1121
|
+
|
1122
|
+
Example
|
1123
|
+
-------
|
1124
|
+
.. code-block:: python
|
1125
|
+
|
1126
|
+
import numpy as np
|
1127
|
+
|
1128
|
+
from yaeos import PengRobinson76
|
1129
|
+
|
1130
|
+
tc = np.array([320.0, 375.0])
|
1131
|
+
pc = np.array([45.0, 60.0])
|
1132
|
+
w = np.array([0.0123, 0.045])
|
1133
|
+
|
1134
|
+
model = PengRobinson76(tc, pc, w)
|
1135
|
+
"""
|
1136
|
+
p, t, vx, vy = yaeos_c.pure_saturation_line(
|
1137
|
+
self.id, component, stop_p=stop_pressure, stop_t=stop_temperature
|
1138
|
+
)
|
1139
|
+
|
1140
|
+
msk = ~np.isnan(t)
|
1141
|
+
|
1142
|
+
return {"T": t[msk], "P": p[msk], "Vx": vx[msk], "Vy": vy[msk]}
|
1143
|
+
|
1144
|
+
def flash_pt(
|
1145
|
+
self, z, pressure: float, temperature: float, k0=None
|
1146
|
+
) -> dict:
|
1147
|
+
"""Two-phase split with specification of temperature and pressure.
|
1148
|
+
|
1149
|
+
Parameters
|
1150
|
+
----------
|
1151
|
+
z : array_like
|
1152
|
+
Global mole fractions
|
1153
|
+
pressure : float
|
1154
|
+
Pressure [bar]
|
1155
|
+
temperature : float
|
1156
|
+
Temperature [K]
|
1157
|
+
k0 : array_like, optional
|
1158
|
+
Initial guess for the split, by default None (will use k_wilson)
|
1159
|
+
|
1160
|
+
Returns
|
1161
|
+
-------
|
1162
|
+
dict
|
1163
|
+
Flash result dictionary with keys:
|
1164
|
+
- x: heavy phase mole fractions
|
1165
|
+
- y: light phase mole fractions
|
1166
|
+
- Vx: heavy phase volume [L]
|
1167
|
+
- Vy: light phase volume [L]
|
1168
|
+
- P: pressure [bar]
|
1169
|
+
- T: temperature [K]
|
1170
|
+
- beta: light phase fraction. If beta is -1 flash was not
|
1171
|
+
successful.
|
1172
|
+
|
1173
|
+
Example
|
1174
|
+
-------
|
1175
|
+
.. code-block:: python
|
1176
|
+
|
1177
|
+
import numpy as np
|
1178
|
+
|
1179
|
+
from yaeos import PengRobinson76
|
1180
|
+
|
1181
|
+
|
1182
|
+
tc = np.array([369.83, 507.6]) # critical temperatures [K]
|
1183
|
+
pc = np.array([42.48, 30.25]) # critical pressures [bar]
|
1184
|
+
w = np.array([0.152291, 0.301261]) # acentric factors
|
1185
|
+
|
1186
|
+
model = PengRobinson76(tc, pc, w)
|
1187
|
+
|
1188
|
+
# Flash calculation
|
1189
|
+
# will print:
|
1190
|
+
# {
|
1191
|
+
# 'x': array([0.3008742, 0.6991258]),
|
1192
|
+
# 'y': array([0.85437317, 0.14562683]),
|
1193
|
+
# 'Vx': 0.12742569165483714,
|
1194
|
+
# 'Vy': 3.218831515959867,
|
1195
|
+
# 'P': 8.0,
|
1196
|
+
# 'T': 350.0,
|
1197
|
+
# 'beta': 0.35975821044266726
|
1198
|
+
# }
|
1199
|
+
|
1200
|
+
print(model.flash_pt([0.5, 0.5], 8.0, 350.0))
|
1201
|
+
"""
|
1202
|
+
if k0 is None:
|
1203
|
+
k0 = [0 for i in range(len(z))]
|
1204
|
+
|
1205
|
+
x, y, pressure, temperature, volume_x, volume_y, beta = yaeos_c.flash(
|
1206
|
+
self.id, z, p=pressure, t=temperature, k0=k0
|
1207
|
+
)
|
1208
|
+
|
1209
|
+
flash_result = {
|
1210
|
+
"x": x,
|
1211
|
+
"y": y,
|
1212
|
+
"Vx": volume_x,
|
1213
|
+
"Vy": volume_y,
|
1214
|
+
"P": pressure,
|
1215
|
+
"T": temperature,
|
1216
|
+
"beta": beta,
|
1217
|
+
}
|
1218
|
+
|
1219
|
+
return flash_result
|
1220
|
+
|
1221
|
+
def flash_vt(self, z, volume: float, temperature: float, k0=None) -> dict:
|
1222
|
+
"""Two-phase split with specification of temperature and volume.
|
1223
|
+
|
1224
|
+
Parameters
|
1225
|
+
----------
|
1226
|
+
z : array_like
|
1227
|
+
Global mole fractions
|
1228
|
+
volume : float
|
1229
|
+
Molar volume [L/mol]
|
1230
|
+
temperature : float
|
1231
|
+
Temperature [K]
|
1232
|
+
k0 : array_like, optional
|
1233
|
+
Initial guess for the split, by default None (will use k_wilson)
|
1234
|
+
|
1235
|
+
Returns
|
1236
|
+
-------
|
1237
|
+
dict
|
1238
|
+
Flash result dictionary with keys:
|
1239
|
+
- x: heavy phase mole fractions
|
1240
|
+
- y: light phase mole fractions
|
1241
|
+
- Vx: heavy phase molar volume [L/mol]
|
1242
|
+
- Vy: light phase molar volume [L/mol]
|
1243
|
+
- P: pressure [bar]
|
1244
|
+
- T: temperature [K]
|
1245
|
+
- beta: light phase fraction. If beta is -1 flash was not
|
1246
|
+
successful.
|
1247
|
+
|
1248
|
+
Example
|
1249
|
+
-------
|
1250
|
+
.. code-block:: python
|
1251
|
+
|
1252
|
+
import numpy as np
|
1253
|
+
|
1254
|
+
from yaeos import PengRobinson76
|
1255
|
+
|
1256
|
+
|
1257
|
+
tc = np.array([507.6, 658.0]) # critical temperatures [K]
|
1258
|
+
pc = np.array([30.25, 18.20]) # critical pressures [bar]
|
1259
|
+
w = np.array([0.301261, 0.576385]) # acentric factors
|
1260
|
+
|
1261
|
+
model = PengRobinson76(tc, pc, w)
|
1262
|
+
|
1263
|
+
# Flash calculation
|
1264
|
+
# will print:
|
1265
|
+
# {
|
1266
|
+
# 'x': array([0.26308567, 0.73691433]),
|
1267
|
+
# 'y': array([0.95858707, 0.04141293]),
|
1268
|
+
# 'Vx': 0.2417828483590114,
|
1269
|
+
# 'Vy': 31.706890870110417,
|
1270
|
+
# 'P': 1.0001131874567775,
|
1271
|
+
# 'T': 393.15,
|
1272
|
+
# 'beta': 0.34063818069513246
|
1273
|
+
# }
|
1274
|
+
|
1275
|
+
print(model.flash_vt([0.5, 0.5], 10.96, 393.15))
|
1276
|
+
"""
|
1277
|
+
if k0 is None:
|
1278
|
+
k0 = [0 for i in range(len(z))]
|
1279
|
+
|
1280
|
+
x, y, pressure, temperature, volume_x, volume_y, beta = (
|
1281
|
+
yaeos_c.flash_vt(self.id, z, v=volume, t=temperature, k0=k0)
|
1282
|
+
)
|
1283
|
+
|
1284
|
+
flash_result = {
|
1285
|
+
"x": x,
|
1286
|
+
"y": y,
|
1287
|
+
"Vx": volume_x,
|
1288
|
+
"Vy": volume_y,
|
1289
|
+
"P": pressure,
|
1290
|
+
"T": temperature,
|
1291
|
+
"beta": beta,
|
1292
|
+
}
|
1293
|
+
|
1294
|
+
return flash_result
|
1295
|
+
|
1296
|
+
def flash_pt_grid(
|
1297
|
+
self, z, pressures, temperatures, parallel=False
|
1298
|
+
) -> dict:
|
1299
|
+
"""Two-phase split with specification of temperature and pressure grid.
|
1300
|
+
|
1301
|
+
Parameters
|
1302
|
+
----------
|
1303
|
+
z : array_like
|
1304
|
+
Global mole fractions
|
1305
|
+
pressures : array_like
|
1306
|
+
Pressures grid [bar]
|
1307
|
+
temperatures : array_like
|
1308
|
+
Temperatures grid [K]
|
1309
|
+
parallel : bool, optional
|
1310
|
+
Use parallel processing, by default False
|
1311
|
+
|
1312
|
+
Returns
|
1313
|
+
-------
|
1314
|
+
dict
|
1315
|
+
Flash grid result dictionary with keys:
|
1316
|
+
- x: heavy phase mole fractions
|
1317
|
+
- y: light phase mole fractions
|
1318
|
+
- Vx: heavy phase volume [L]
|
1319
|
+
- Vy: light phase volume [L]
|
1320
|
+
- P: pressure [bar]
|
1321
|
+
- T: temperature [K]
|
1322
|
+
- beta: light phase fraction
|
1323
|
+
|
1324
|
+
Example
|
1325
|
+
-------
|
1326
|
+
.. code-block:: python
|
1327
|
+
|
1328
|
+
import numpy as np
|
1329
|
+
|
1330
|
+
from yaeos import PengRobinson76
|
1331
|
+
|
1332
|
+
tc = np.array([369.83, 507.6]) # critical temperatures [K]
|
1333
|
+
pc = np.array([42.48, 30.25]) # critical pressures [bar]
|
1334
|
+
w = np.array([0.152291, 0.301261])
|
1335
|
+
|
1336
|
+
temperatures = [350.0, 360.0, 400.0]
|
1337
|
+
pressures = [10, 20, 30]
|
1338
|
+
"""
|
1339
|
+
xs, ys, vxs, vys, betas = yaeos_c.flash_grid(
|
1340
|
+
self.id, z, pressures, temperatures, parallel=parallel
|
1341
|
+
)
|
1342
|
+
|
1343
|
+
flash = {
|
1344
|
+
"x": xs,
|
1345
|
+
"y": ys,
|
1346
|
+
"Vx": vxs,
|
1347
|
+
"Vy": vys,
|
1348
|
+
"P": pressures,
|
1349
|
+
"T": temperatures,
|
1350
|
+
"beta": betas,
|
1351
|
+
}
|
1352
|
+
|
1353
|
+
return flash
|
1354
|
+
|
1355
|
+
def saturation_pressure(
|
1356
|
+
self,
|
1357
|
+
z,
|
1358
|
+
temperature: float,
|
1359
|
+
kind: str = "bubble",
|
1360
|
+
p0: float = 0,
|
1361
|
+
y0=None,
|
1362
|
+
) -> dict:
|
1363
|
+
"""Saturation pressure at specified temperature.
|
1364
|
+
|
1365
|
+
Arguments
|
1366
|
+
---------
|
1367
|
+
z: array_like
|
1368
|
+
Global molar fractions
|
1369
|
+
temperature: float
|
1370
|
+
Temperature [K]
|
1371
|
+
kind: str, optional
|
1372
|
+
Kind of saturation point, defaults to "bubble". Options are
|
1373
|
+
- "bubble"
|
1374
|
+
- "dew"
|
1375
|
+
- "liquid-liquid"
|
1376
|
+
p0: float, optional
|
1377
|
+
Initial guess for pressure [bar]
|
1378
|
+
y0: array_like, optional
|
1379
|
+
Initial guess for the incipient phase, by default None
|
1380
|
+
(will use k_wilson correlation)
|
1381
|
+
|
1382
|
+
Returns
|
1383
|
+
-------
|
1384
|
+
dict
|
1385
|
+
Saturation pressure calculation result dictionary with keys:
|
1386
|
+
- x: heavy phase mole fractions
|
1387
|
+
- y: light phase mole fractions
|
1388
|
+
- Vx: heavy phase volume [L]
|
1389
|
+
- Vy: light phase volume [L]
|
1390
|
+
- P: pressure [bar]
|
1391
|
+
- T: temperature [K]
|
1392
|
+
- beta: light phase fraction
|
1393
|
+
|
1394
|
+
Example
|
1395
|
+
-------
|
1396
|
+
.. code-block:: python
|
1397
|
+
|
1398
|
+
import numpy as np
|
1399
|
+
|
1400
|
+
from yaeos import PengRobinson76
|
1401
|
+
|
1402
|
+
|
1403
|
+
tc = np.array([369.83, 507.6]) # critical temperatures [K]
|
1404
|
+
pc = np.array([42.48, 30.25]) # critical pressures [bar]
|
1405
|
+
w = np.array([0.152291, 0.301261]) # acentric factors
|
1406
|
+
|
1407
|
+
model = PengRobinson76(tc, pc, w)
|
1408
|
+
|
1409
|
+
# Saturation pressure calculation
|
1410
|
+
# will print:
|
1411
|
+
# {
|
1412
|
+
# 'x': array([0.5, 0.5]),
|
1413
|
+
# 'y': array([0.9210035 , 0.07899651]),
|
1414
|
+
# 'Vx': 0.11974125553488875,
|
1415
|
+
# 'Vy': 1.849650524323853,
|
1416
|
+
# 'T': 350.0,
|
1417
|
+
# 'P': 12.990142036059941,
|
1418
|
+
# 'beta': 0.0
|
1419
|
+
# }
|
1420
|
+
|
1421
|
+
print(model.saturation_pressure(np.array([0.5, 0.5]), 350.0))
|
1422
|
+
"""
|
1423
|
+
if y0 is None:
|
1424
|
+
y0 = np.zeros_like(z)
|
1425
|
+
|
1426
|
+
p, x, y, volume_x, volume_y, beta = yaeos_c.saturation_pressure(
|
1427
|
+
id=self.id, z=z, t=temperature, kind=kind, p0=p0, y0=y0
|
1428
|
+
)
|
1429
|
+
|
1430
|
+
return {
|
1431
|
+
"x": x,
|
1432
|
+
"y": y,
|
1433
|
+
"Vx": volume_x,
|
1434
|
+
"Vy": volume_y,
|
1435
|
+
"T": temperature,
|
1436
|
+
"P": p,
|
1437
|
+
"beta": beta,
|
1438
|
+
}
|
1439
|
+
|
1440
|
+
def saturation_temperature(
|
1441
|
+
self, z, pressure: float, kind: str = "bubble", t0: float = 0, y0=None
|
1442
|
+
) -> dict:
|
1443
|
+
"""Saturation temperature at specified pressure.
|
1444
|
+
|
1445
|
+
Arguments
|
1446
|
+
---------
|
1447
|
+
z: array_like
|
1448
|
+
Global molar fractions
|
1449
|
+
pressure: float
|
1450
|
+
Pressure [bar]
|
1451
|
+
kind: str, optional
|
1452
|
+
Kind of saturation point, defaults to "bubble". Options are
|
1453
|
+
- "bubble"
|
1454
|
+
- "dew"
|
1455
|
+
- "liquid-liquid"
|
1456
|
+
t0: float, optional
|
1457
|
+
Initial guess for temperature [K]
|
1458
|
+
y0: array_like, optional
|
1459
|
+
Initial guess for the incipient phase, by default None
|
1460
|
+
(will use k_wilson correlation)
|
1461
|
+
|
1462
|
+
Returns
|
1463
|
+
-------
|
1464
|
+
dict
|
1465
|
+
Saturation temperature calculation result dictionary with keys:
|
1466
|
+
- x: heavy phase mole fractions
|
1467
|
+
- y: light phase mole fractions
|
1468
|
+
- Vx: heavy phase volume [L]
|
1469
|
+
- Vy: light phase volume [L]
|
1470
|
+
- P: pressure [bar]
|
1471
|
+
- T: temperature [K]
|
1472
|
+
- beta: light phase fraction
|
1473
|
+
|
1474
|
+
Example
|
1475
|
+
-------
|
1476
|
+
.. code-block:: python
|
1477
|
+
|
1478
|
+
import numpy as np
|
1479
|
+
|
1480
|
+
from yaeos import PengRobinson76
|
1481
|
+
|
1482
|
+
|
1483
|
+
tc = np.array([369.83, 507.6]) # critical temperatures [K]
|
1484
|
+
pc = np.array([42.48, 30.25]) # critical pressures [bar]
|
1485
|
+
w = np.array([0.152291, 0.301261]) # acentric factors
|
1486
|
+
|
1487
|
+
model = PengRobinson76(tc, pc, w)
|
1488
|
+
|
1489
|
+
# Saturation pressure calculation
|
1490
|
+
# will print:
|
1491
|
+
# {
|
1492
|
+
# 'x': array([0.5, 0.5]),
|
1493
|
+
# 'y': array([0.9210035 , 0.07899651]),
|
1494
|
+
# 'Vx': 0.11974125553488875,
|
1495
|
+
# 'Vy': 1.849650524323853,
|
1496
|
+
# 'T': 350.0,
|
1497
|
+
# 'P': 12.99,
|
1498
|
+
# 'beta': 0.0
|
1499
|
+
# }
|
1500
|
+
|
1501
|
+
print(model.saturation_temperature(np.array([0.5, 0.5]), 12.99))
|
1502
|
+
"""
|
1503
|
+
if y0 is None:
|
1504
|
+
y0 = np.zeros_like(z)
|
1505
|
+
|
1506
|
+
t, x, y, volume_x, volume_y, beta = yaeos_c.saturation_temperature(
|
1507
|
+
id=self.id, z=z, p=pressure, kind=kind, t0=t0, y0=y0
|
1508
|
+
)
|
1509
|
+
|
1510
|
+
return {
|
1511
|
+
"x": x,
|
1512
|
+
"y": y,
|
1513
|
+
"Vx": volume_x,
|
1514
|
+
"Vy": volume_y,
|
1515
|
+
"T": t,
|
1516
|
+
"P": pressure,
|
1517
|
+
"beta": beta,
|
1518
|
+
}
|
1519
|
+
|
1520
|
+
# =========================================================================
|
1521
|
+
# Phase envelopes
|
1522
|
+
# -------------------------------------------------------------------------
|
1523
|
+
def phase_envelope_pt(
|
1524
|
+
self,
|
1525
|
+
z,
|
1526
|
+
kind: str = "bubble",
|
1527
|
+
max_points: int = 700,
|
1528
|
+
t0: float = 150.0,
|
1529
|
+
p0: float = 1.0,
|
1530
|
+
w0=None,
|
1531
|
+
stop_pressure: float = 2500,
|
1532
|
+
ds0: float = 0.001,
|
1533
|
+
) -> PTEnvelope:
|
1534
|
+
"""Two phase envelope calculation (PT).
|
1535
|
+
|
1536
|
+
Parameters
|
1537
|
+
----------
|
1538
|
+
z : array_like
|
1539
|
+
Global mole fractions
|
1540
|
+
kind : str, optional
|
1541
|
+
Kind of saturation point to start the envelope calculation,
|
1542
|
+
defaults to "bubble". Options are
|
1543
|
+
- "bubble"
|
1544
|
+
- "dew"
|
1545
|
+
- "liquid-liquid"
|
1546
|
+
max_points : int, optional
|
1547
|
+
Envelope's maximum points to calculate (T, P), by default 700
|
1548
|
+
t0 : float, optional
|
1549
|
+
Initial guess for temperature [K] for the saturation point of kind:
|
1550
|
+
`kind`, by default 150.0
|
1551
|
+
p0 : float, optional
|
1552
|
+
Initial guess for pressure [bar] for the saturation point of kind:
|
1553
|
+
`kind`, by default 1.0
|
1554
|
+
w0 : array_like, optional
|
1555
|
+
Initial guess for the incipient phase mole fractions,
|
1556
|
+
by default None In the case of bubble and dew line calculations, it
|
1557
|
+
will use the k_wilson correlation. In the case of liquid-liquid
|
1558
|
+
envelope it will make a search for the first unstable component
|
1559
|
+
when decreasing temperature at the given pressure.
|
1560
|
+
stop_pressure : float, optional
|
1561
|
+
Stop on pressures above stop_pressure [bar], by default 2500.0.
|
1562
|
+
If the the initial guess pressure is above this value, the
|
1563
|
+
calculation will stop immediately.
|
1564
|
+
ds0: float, optional
|
1565
|
+
Step for the first specified variable, by default 0.001. The
|
1566
|
+
specified variable is the temperature for bubble and dew lines, and
|
1567
|
+
pressure for liquid-liquid lines. For bubble and dew lines, the
|
1568
|
+
step is positive, while for liquid-liquid lines it is negative.
|
1569
|
+
|
1570
|
+
Returns
|
1571
|
+
-------
|
1572
|
+
PTEnvelope
|
1573
|
+
PTEnvelope object with the phase envelope information.
|
1574
|
+
|
1575
|
+
Example
|
1576
|
+
-------
|
1577
|
+
.. code-block:: python
|
1578
|
+
|
1579
|
+
import numpy as np
|
1580
|
+
|
1581
|
+
import matplotlib.pyplot as plt
|
1582
|
+
|
1583
|
+
from yaeos import PengRobinson76
|
1584
|
+
|
1585
|
+
|
1586
|
+
tc = np.array([369.83, 507.6]) # critical temperatures [K]
|
1587
|
+
pc = np.array([42.48, 30.25]) # critical pressures [bar]
|
1588
|
+
w = np.array([0.152291, 0.301261]) # acentric factors
|
1589
|
+
|
1590
|
+
model = PengRobinson76(tc, pc, w)
|
1591
|
+
|
1592
|
+
# Two phase envelope calculation and plot
|
1593
|
+
env = model.phase_envelope_pt(
|
1594
|
+
np.array([0.5, 0.5]),
|
1595
|
+
t0=150.0,
|
1596
|
+
p0=1.0
|
1597
|
+
)
|
1598
|
+
|
1599
|
+
plt.plot(env["T"], env["P"])
|
1600
|
+
plt.scatter(env["Tc"], env["Pc"])
|
1601
|
+
"""
|
1602
|
+
|
1603
|
+
ds0 = 0.001
|
1604
|
+
|
1605
|
+
if kind == "bubble":
|
1606
|
+
sat = self.saturation_pressure(z, t0, kind=kind, p0=p0)
|
1607
|
+
w0 = sat["y"]
|
1608
|
+
ns0 = len(z) + 3
|
1609
|
+
p0 = sat["P"]
|
1610
|
+
kinds_x = ["liquid"]
|
1611
|
+
kind_w = "vapor"
|
1612
|
+
elif kind == "dew":
|
1613
|
+
sat = self.saturation_temperature(z, p0, kind=kind, t0=t0)
|
1614
|
+
w0 = sat["x"]
|
1615
|
+
ns0 = len(z) + 3
|
1616
|
+
t0 = sat["T"]
|
1617
|
+
kinds_x = ["vapor"]
|
1618
|
+
kind_w = "liquid"
|
1619
|
+
elif kind == "liquid-liquid":
|
1620
|
+
ns0 = len(z) + 2
|
1621
|
+
if w0 is None:
|
1622
|
+
# =============================================================
|
1623
|
+
# Find an initialization for the liquid-liquid envelope
|
1624
|
+
# -------------------------------------------------------------
|
1625
|
+
ts = []
|
1626
|
+
for i in range(len(z)):
|
1627
|
+
w0 = np.zeros_like(z)
|
1628
|
+
w0 += 1e-5
|
1629
|
+
w0[i] = 1 - np.sum(w0[1:])
|
1630
|
+
for t in np.linspace(1000, 100, 25):
|
1631
|
+
tm = self.stability_tm(z, w0, p0, t)
|
1632
|
+
if tm < -0.01:
|
1633
|
+
ts.append(t)
|
1634
|
+
break
|
1635
|
+
if len(ts) == 0:
|
1636
|
+
warn("No liquid-liquid region found.")
|
1637
|
+
return None
|
1638
|
+
i = np.argmin(ts)
|
1639
|
+
t0 = ts[i]
|
1640
|
+
w0 = np.zeros_like(z)
|
1641
|
+
w0 += 1e-5
|
1642
|
+
w0[i] = 1 - np.sum(w0[1:])
|
1643
|
+
|
1644
|
+
kinds_x = ["liquid"]
|
1645
|
+
kind_w = "liquid"
|
1646
|
+
ds0 = -ds0
|
1647
|
+
|
1648
|
+
envelope = self.phase_envelope_pt_mp(
|
1649
|
+
z,
|
1650
|
+
x_l0=[z],
|
1651
|
+
w0=w0,
|
1652
|
+
betas0=[1],
|
1653
|
+
t0=t0,
|
1654
|
+
p0=p0,
|
1655
|
+
ns0=ns0,
|
1656
|
+
ds0=ds0,
|
1657
|
+
max_points=max_points,
|
1658
|
+
stop_pressure=stop_pressure,
|
1659
|
+
kinds_x=kinds_x,
|
1660
|
+
kind_w=kind_w,
|
1661
|
+
)
|
1662
|
+
|
1663
|
+
return envelope
|
1664
|
+
|
1665
|
+
def phase_envelope_px(
|
1666
|
+
self,
|
1667
|
+
z0,
|
1668
|
+
zi,
|
1669
|
+
temperature,
|
1670
|
+
kind="bubble",
|
1671
|
+
max_points=500,
|
1672
|
+
p0=10.0,
|
1673
|
+
w0=None,
|
1674
|
+
a0=1e-2,
|
1675
|
+
ns0=None,
|
1676
|
+
ds0=1e-5,
|
1677
|
+
) -> PXEnvelope:
|
1678
|
+
"""Two phase envelope calculation (PX).
|
1679
|
+
|
1680
|
+
Calculation of a phase envelope that starts at a given composition and
|
1681
|
+
its related to another composition with some proportion.
|
1682
|
+
|
1683
|
+
Parameters
|
1684
|
+
----------
|
1685
|
+
z0 : array_like
|
1686
|
+
Initial global mole fractions
|
1687
|
+
zi : array_like
|
1688
|
+
Final global mole fractions
|
1689
|
+
temperature : float
|
1690
|
+
Temperature [K]
|
1691
|
+
kind : str, optional
|
1692
|
+
Kind of saturation point to start the envelope calculation,
|
1693
|
+
defaults to "bubble". Options are
|
1694
|
+
- "bubble"
|
1695
|
+
- "dew"
|
1696
|
+
max_points : int, optional
|
1697
|
+
Envelope's maximum points to calculate, by default 500
|
1698
|
+
p0 : float, optional
|
1699
|
+
Initial guess for pressure [bar] for the saturation point of kind:
|
1700
|
+
`kind`, by default 10.0
|
1701
|
+
a0 : float, optional
|
1702
|
+
Initial molar fraction of composition `zi`, by default 0.001
|
1703
|
+
ns0 : int, optional
|
1704
|
+
Initial specified variable number, by default None.
|
1705
|
+
The the first `n=len(z)` values correspond to the K-values,
|
1706
|
+
`len(z)+1` is the main phase molar fraction (ussually one) and
|
1707
|
+
the last two values are the pressure and alpha.
|
1708
|
+
ds0 : float, optional
|
1709
|
+
Step for the first specified variable, by default 0.01
|
1710
|
+
"""
|
1711
|
+
if ns0 is None:
|
1712
|
+
ns0 = len(z0) + 3
|
1713
|
+
|
1714
|
+
zi = np.array(zi)
|
1715
|
+
z0 = np.array(z0)
|
1716
|
+
|
1717
|
+
if w0 is None:
|
1718
|
+
w0 = np.zeros_like(z0)
|
1719
|
+
|
1720
|
+
z = a0 * zi + (1 - a0) * z0
|
1721
|
+
sat = self.saturation_pressure(
|
1722
|
+
z, temperature=temperature, kind=kind, p0=p0, y0=w0
|
1723
|
+
)
|
1724
|
+
|
1725
|
+
if kind == "dew":
|
1726
|
+
w0 = sat["x"]
|
1727
|
+
kind_x = ["vapor"]
|
1728
|
+
kind_w = "liquid"
|
1729
|
+
else:
|
1730
|
+
w0 = sat["y"]
|
1731
|
+
kind_x = ["liquid"]
|
1732
|
+
kind_w = "vapor"
|
1733
|
+
|
1734
|
+
p0 = sat["P"]
|
1735
|
+
|
1736
|
+
envelope = self.phase_envelope_px_mp(
|
1737
|
+
z0,
|
1738
|
+
zi,
|
1739
|
+
temperature,
|
1740
|
+
x_l0=[z],
|
1741
|
+
w0=w0,
|
1742
|
+
betas0=[1],
|
1743
|
+
p0=p0,
|
1744
|
+
alpha0=a0,
|
1745
|
+
ns0=ns0,
|
1746
|
+
ds0=ds0,
|
1747
|
+
max_points=max_points,
|
1748
|
+
kinds_x=kind_x,
|
1749
|
+
kind_w=kind_w,
|
1750
|
+
)
|
1751
|
+
|
1752
|
+
return envelope
|
1753
|
+
|
1754
|
+
def phase_envelope_tx(
|
1755
|
+
self,
|
1756
|
+
z0,
|
1757
|
+
zi,
|
1758
|
+
pressure,
|
1759
|
+
kind="bubble",
|
1760
|
+
max_points=300,
|
1761
|
+
t0=150.0,
|
1762
|
+
a0=0.001,
|
1763
|
+
ns0=None,
|
1764
|
+
ds0=0.1,
|
1765
|
+
w0=None,
|
1766
|
+
) -> TXEnvelope:
|
1767
|
+
"""Two phase envelope calculation (TX).
|
1768
|
+
|
1769
|
+
Calculation of a phase envelope that starts at a given composition and
|
1770
|
+
its related to another composition with some proportion.
|
1771
|
+
|
1772
|
+
Parameters
|
1773
|
+
----------
|
1774
|
+
z0 : array_like
|
1775
|
+
Initial global mole fractions
|
1776
|
+
zi : array_like
|
1777
|
+
Final global mole fractions
|
1778
|
+
pressure : float
|
1779
|
+
Pressure [bar]
|
1780
|
+
kind : str, optional
|
1781
|
+
Kind of saturation point to start the envelope calculation,
|
1782
|
+
defaults to "bubble". Options are
|
1783
|
+
- "bubble"
|
1784
|
+
- "dew"
|
1785
|
+
max_points : int, optional
|
1786
|
+
Envelope's maximum points to calculate (P, X), by default 300
|
1787
|
+
t0 : float, optional
|
1788
|
+
Initial guess for temperature [K] for the saturation point of kind:
|
1789
|
+
`kind`, by default 150.0
|
1790
|
+
a0 : float, optional
|
1791
|
+
Initial molar fraction of composition `zi`, by default 0.001
|
1792
|
+
ns0 : int, optional
|
1793
|
+
Initial specified variable number, by default None.
|
1794
|
+
The the first `n=len(z)` values correspond to the K-values, where
|
1795
|
+
the last two values are the temperature and alpha.
|
1796
|
+
ds0 : float, optional
|
1797
|
+
Step for a, by default 0.1
|
1798
|
+
w0 : array_like, optional
|
1799
|
+
Initial guess for the incipient phase, by default None
|
1800
|
+
(will use k_wilson correlation)
|
1801
|
+
"""
|
1802
|
+
zi = np.array(zi)
|
1803
|
+
z0 = np.array(z0)
|
1804
|
+
|
1805
|
+
if not ns0:
|
1806
|
+
ns0 = len(z0) + 3
|
1807
|
+
|
1808
|
+
if w0 is None:
|
1809
|
+
w0 = np.zeros_like(z0)
|
1810
|
+
|
1811
|
+
z = a0 * zi + (1 - a0) * z0
|
1812
|
+
sat = self.saturation_temperature(
|
1813
|
+
z, pressure=pressure, kind=kind, t0=t0, y0=w0
|
1814
|
+
)
|
1815
|
+
|
1816
|
+
if kind == "dew":
|
1817
|
+
w0 = sat["x"]
|
1818
|
+
kind_x = ["vapor"]
|
1819
|
+
kind_w = "liquid"
|
1820
|
+
else:
|
1821
|
+
w0 = sat["y"]
|
1822
|
+
kind_x = ["liquid"]
|
1823
|
+
kind_w = "vapor"
|
1824
|
+
|
1825
|
+
envelope = self.phase_envelope_tx_mp(
|
1826
|
+
z0=z0,
|
1827
|
+
zi=zi,
|
1828
|
+
p=pressure,
|
1829
|
+
x_l0=[z],
|
1830
|
+
w0=w0,
|
1831
|
+
betas0=[1],
|
1832
|
+
t0=t0,
|
1833
|
+
alpha0=a0,
|
1834
|
+
ns0=ns0,
|
1835
|
+
ds0=ds0,
|
1836
|
+
max_points=max_points,
|
1837
|
+
kinds_x=kind_x,
|
1838
|
+
kind_w=kind_w,
|
1839
|
+
)
|
1840
|
+
|
1841
|
+
return envelope
|
1842
|
+
|
1843
|
+
def phase_envelope_pt3(
|
1844
|
+
self,
|
1845
|
+
z,
|
1846
|
+
x0,
|
1847
|
+
y0,
|
1848
|
+
w0,
|
1849
|
+
beta0,
|
1850
|
+
t0,
|
1851
|
+
p0,
|
1852
|
+
specified_variable=None,
|
1853
|
+
first_step=None,
|
1854
|
+
kinds_x=None,
|
1855
|
+
kind_w=None,
|
1856
|
+
max_points=1000,
|
1857
|
+
stop_pressure=2500,
|
1858
|
+
) -> PTEnvelope:
|
1859
|
+
"""
|
1860
|
+
Three-phase envelope tracing method.
|
1861
|
+
|
1862
|
+
Calculation of a three-phase envelope that starts with an estimated
|
1863
|
+
compositions, pressure, temperature and phase fractions.
|
1864
|
+
|
1865
|
+
Parameters
|
1866
|
+
----------
|
1867
|
+
z : array_like
|
1868
|
+
Global mole fractions
|
1869
|
+
x0 : array_like
|
1870
|
+
Initial phase x mole fractions
|
1871
|
+
y0 : array_like
|
1872
|
+
Initial phase y mole fractions
|
1873
|
+
w0 : array_like
|
1874
|
+
Initial incipient phase w mole fractions
|
1875
|
+
beta0 : float
|
1876
|
+
Initial phase fraction between x and y
|
1877
|
+
t0 : float
|
1878
|
+
Initial temperature [K]
|
1879
|
+
p0 : float
|
1880
|
+
Initial pressure [bar]
|
1881
|
+
specified_variable : int, optional
|
1882
|
+
Initial specified variable number, by default 2*len(z)+2
|
1883
|
+
(temperature). The the first `n=(1,len(z))` values correspond to
|
1884
|
+
the K-values between phase x and w, the next `n=(len(z)+1,
|
1885
|
+
2*len(z))` are the K-values between phase y and w. The last three
|
1886
|
+
values are pressure, temperature and beta.
|
1887
|
+
first_step : float, optional
|
1888
|
+
Step for the specified variable, by default 0.1
|
1889
|
+
kinds_x : list, optional
|
1890
|
+
Kinds of the main phases, by default None (will use "stable")
|
1891
|
+
kind_w : str, optional
|
1892
|
+
Kind of the reference phase, by default None (will use "stable")
|
1893
|
+
max_points : int, optional
|
1894
|
+
Maximum number of points to calculate, by default 1000
|
1895
|
+
stop_pressure : float, optional
|
1896
|
+
Stop at pressure above stop_pressure [bar], default 2500
|
1897
|
+
"""
|
1898
|
+
|
1899
|
+
np = 2
|
1900
|
+
if specified_variable is None:
|
1901
|
+
specified_variable = 2 * len(z) + np + 2
|
1902
|
+
|
1903
|
+
if first_step is None:
|
1904
|
+
first_step = 0.1
|
1905
|
+
|
1906
|
+
envelope = self.phase_envelope_pt_mp(
|
1907
|
+
z=z,
|
1908
|
+
x_l0=[x0, y0],
|
1909
|
+
w0=w0,
|
1910
|
+
betas0=[1 - beta0, beta0],
|
1911
|
+
t0=t0,
|
1912
|
+
p0=p0,
|
1913
|
+
ns0=specified_variable,
|
1914
|
+
ds0=first_step,
|
1915
|
+
beta_w=0,
|
1916
|
+
kinds_x=kinds_x,
|
1917
|
+
kind_w=kind_w,
|
1918
|
+
max_points=max_points,
|
1919
|
+
stop_pressure=stop_pressure,
|
1920
|
+
)
|
1921
|
+
|
1922
|
+
return envelope
|
1923
|
+
|
1924
|
+
def phase_envelope_px3(
|
1925
|
+
self,
|
1926
|
+
z0,
|
1927
|
+
zi,
|
1928
|
+
T,
|
1929
|
+
x0,
|
1930
|
+
y0,
|
1931
|
+
w0,
|
1932
|
+
beta0,
|
1933
|
+
a0,
|
1934
|
+
p0,
|
1935
|
+
specified_variable=None,
|
1936
|
+
first_step=None,
|
1937
|
+
max_points=1000,
|
1938
|
+
kinds_x=None,
|
1939
|
+
kind_w=None,
|
1940
|
+
) -> PXEnvelope:
|
1941
|
+
"""
|
1942
|
+
Three-phase envelope tracing method.
|
1943
|
+
|
1944
|
+
Calculation of a three-phase envelope that starts with an estimated
|
1945
|
+
compositions, pressure, temperature and phase fractions.
|
1946
|
+
|
1947
|
+
Parameters
|
1948
|
+
----------
|
1949
|
+
z0 : array_like
|
1950
|
+
Global mole fractions of the original fluid
|
1951
|
+
zi : array_like
|
1952
|
+
Global mole fractions of the other fluid
|
1953
|
+
x0 : array_like
|
1954
|
+
Initial phase x mole fractions
|
1955
|
+
y0 : array_like
|
1956
|
+
Initial phase y mole fractions
|
1957
|
+
w0 : array_like
|
1958
|
+
Initial incipient phase w mole fractions
|
1959
|
+
beta0 : float
|
1960
|
+
Initial phase fraction between x and y
|
1961
|
+
a0 : float
|
1962
|
+
Initial molar fraction of the other fluid
|
1963
|
+
p0 : float
|
1964
|
+
Initial pressure [bar]
|
1965
|
+
specified_variable : int, optional
|
1966
|
+
Initial specified variable number, by default 2*len(z)+2
|
1967
|
+
(temperature). The the first `n=(1,len(z))` values correspond to
|
1968
|
+
the K-values between phase x and w, the next `n=(len(z)+1,
|
1969
|
+
2*len(z))` are the K-values between phase y and w. The last three
|
1970
|
+
values are pressure, a and beta.
|
1971
|
+
first_step : float, optional
|
1972
|
+
Step for the specified variable, by default 0.1
|
1973
|
+
max_points : int, optional
|
1974
|
+
Maximum number of points to calculate, by default 1000
|
1975
|
+
kinds_x : list, optional
|
1976
|
+
Kinds of the main phases, by default None (will use "stable")
|
1977
|
+
options can be - "stable", "liquid", "vapor"
|
1978
|
+
kind_w : str, optional
|
1979
|
+
Kind of the reference phase, by default None (will use "stable")
|
1980
|
+
options can be - "stable", "liquid", "vapor"
|
1981
|
+
"""
|
1982
|
+
if specified_variable is None:
|
1983
|
+
specified_variable = 2 * len(z0) + 2
|
1984
|
+
|
1985
|
+
if first_step is None:
|
1986
|
+
first_step = 0.1
|
1987
|
+
|
1988
|
+
kinds_x, kind_w = adjust_root_kind(
|
1989
|
+
number_of_phases=2, kinds_x=kinds_x, kind_w=kind_w
|
1990
|
+
)
|
1991
|
+
|
1992
|
+
envelope = self.phase_envelope_px_mp(
|
1993
|
+
z0=z0,
|
1994
|
+
zi=zi,
|
1995
|
+
t=T,
|
1996
|
+
x_l0=[x0, y0],
|
1997
|
+
w0=w0,
|
1998
|
+
betas0=[1 - beta0, beta0],
|
1999
|
+
p0=p0,
|
2000
|
+
alpha0=a0,
|
2001
|
+
ns0=specified_variable,
|
2002
|
+
ds0=first_step,
|
2003
|
+
max_points=max_points,
|
2004
|
+
kinds_x=kinds_x,
|
2005
|
+
kind_w=kind_w,
|
2006
|
+
)
|
2007
|
+
return envelope
|
2008
|
+
|
2009
|
+
def phase_envelope_pt_mp(
|
2010
|
+
self,
|
2011
|
+
z,
|
2012
|
+
x_l0,
|
2013
|
+
w0,
|
2014
|
+
betas0,
|
2015
|
+
p0,
|
2016
|
+
t0,
|
2017
|
+
ns0,
|
2018
|
+
ds0,
|
2019
|
+
beta_w=0,
|
2020
|
+
kinds_x=None,
|
2021
|
+
kind_w=None,
|
2022
|
+
max_points=1000,
|
2023
|
+
stop_pressure=1000,
|
2024
|
+
) -> PTEnvelope:
|
2025
|
+
"""Multi-phase envelope."""
|
2026
|
+
x_l0 = np.array(x_l0)
|
2027
|
+
|
2028
|
+
number_of_phases = x_l0.shape[0]
|
2029
|
+
|
2030
|
+
kinds_x, kind_w = adjust_root_kind(
|
2031
|
+
number_of_phases=number_of_phases, kinds_x=kinds_x, kind_w=kind_w
|
2032
|
+
)
|
2033
|
+
|
2034
|
+
x_ls, ws, betas, ps, ts, iters, ns, x_kinds, w_kinds, pcs, tcs = (
|
2035
|
+
yaeos_c.pt_mp_phase_envelope(
|
2036
|
+
id=self.id,
|
2037
|
+
np=number_of_phases,
|
2038
|
+
z=z,
|
2039
|
+
x_l0=x_l0,
|
2040
|
+
w0=w0,
|
2041
|
+
betas0=betas0,
|
2042
|
+
t0=t0,
|
2043
|
+
p0=p0,
|
2044
|
+
ns0=ns0,
|
2045
|
+
ds0=ds0,
|
2046
|
+
beta_w=beta_w,
|
2047
|
+
kinds_x=kinds_x,
|
2048
|
+
kind_w=kind_w,
|
2049
|
+
max_points=max_points,
|
2050
|
+
stop_pressure=stop_pressure,
|
2051
|
+
)
|
2052
|
+
)
|
2053
|
+
|
2054
|
+
x_kinds = x_kinds.astype(str)
|
2055
|
+
w_kinds = w_kinds.astype(str)
|
2056
|
+
x_kinds = np.char.strip(x_kinds)
|
2057
|
+
w_kinds = np.char.strip(w_kinds)
|
2058
|
+
|
2059
|
+
envelope = PTEnvelope(
|
2060
|
+
global_composition=z,
|
2061
|
+
main_phases_compositions=x_ls,
|
2062
|
+
reference_phase_compositions=ws,
|
2063
|
+
reference_phase_kinds=w_kinds,
|
2064
|
+
main_phases_kinds=x_kinds,
|
2065
|
+
main_phases_molar_fractions=betas,
|
2066
|
+
pressures=ps,
|
2067
|
+
temperatures=ts,
|
2068
|
+
iterations=iters,
|
2069
|
+
specified_variable=ns,
|
2070
|
+
critical_pressures=pcs,
|
2071
|
+
critical_temperatures=tcs,
|
2072
|
+
)
|
2073
|
+
|
2074
|
+
return envelope
|
2075
|
+
|
2076
|
+
def phase_envelope_px_mp(
|
2077
|
+
self,
|
2078
|
+
z0,
|
2079
|
+
zi,
|
2080
|
+
t,
|
2081
|
+
x_l0,
|
2082
|
+
w0,
|
2083
|
+
betas0,
|
2084
|
+
p0,
|
2085
|
+
ns0,
|
2086
|
+
ds0,
|
2087
|
+
alpha0=0,
|
2088
|
+
beta_w=0,
|
2089
|
+
max_points=1000,
|
2090
|
+
kinds_x=None,
|
2091
|
+
kind_w=None,
|
2092
|
+
) -> PXEnvelope:
|
2093
|
+
"""Multi-phase PX envelope.
|
2094
|
+
|
2095
|
+
Calculate a phase envelope with a preselected ammount of phases.
|
2096
|
+
|
2097
|
+
Parameters
|
2098
|
+
----------
|
2099
|
+
z0: float, array_like
|
2100
|
+
Original Fluid.
|
2101
|
+
zi: float, array_like
|
2102
|
+
Other fluid.
|
2103
|
+
t: float
|
2104
|
+
Temperature [K]
|
2105
|
+
x_l0: float, matrix [number of phases, number of components]
|
2106
|
+
A matrix where each row is the composition of a main phase.
|
2107
|
+
Guess for first point.
|
2108
|
+
w0: flot, array_like
|
2109
|
+
Composition of the reference (ussually incipient) phase.
|
2110
|
+
Guess for first point
|
2111
|
+
betas0: float, array_like
|
2112
|
+
Molar fraction of each main phase. Guess for first point
|
2113
|
+
p0: float
|
2114
|
+
Pressure guess for first point [bar]
|
2115
|
+
ns0: int
|
2116
|
+
Initial variable to specifiy.
|
2117
|
+
From 1 to `(number_of_phases*number_of_components)` corresponds to
|
2118
|
+
each composition.
|
2119
|
+
From `number_of_phases*number_of_components` to
|
2120
|
+
`number_of_phases*number_of_components + number_of_phases`
|
2121
|
+
corresponds to each beta value of the main phases.
|
2122
|
+
The last two posibilities are the pressure and molar relation
|
2123
|
+
between the two fluids, respectively.
|
2124
|
+
kinds_x: list(str), optional
|
2125
|
+
List of kinds of main phases, defaults to stable. options are:
|
2126
|
+
- "stable"
|
2127
|
+
- "liquid"
|
2128
|
+
- "vapor"
|
2129
|
+
kinds_w: list(str), optional
|
2130
|
+
Kind of reference phase, defaults to stable. options are:
|
2131
|
+
- "stable"
|
2132
|
+
- "liquid"
|
2133
|
+
- "vapor"
|
2134
|
+
"""
|
2135
|
+
x_l0 = np.array(x_l0, order="F")
|
2136
|
+
|
2137
|
+
number_of_phases = x_l0.shape[0]
|
2138
|
+
|
2139
|
+
kinds_x, kind_w = adjust_root_kind(
|
2140
|
+
number_of_phases=number_of_phases, kinds_x=kinds_x, kind_w=kind_w
|
2141
|
+
)
|
2142
|
+
|
2143
|
+
x_ls, ws, betas, ps, alphas, iters, ns, x_kinds, w_kinds, pcs, acs = (
|
2144
|
+
yaeos_c.px_mp_phase_envelope(
|
2145
|
+
id=self.id,
|
2146
|
+
np=number_of_phases,
|
2147
|
+
z0=z0,
|
2148
|
+
zi=zi,
|
2149
|
+
x_l0=x_l0,
|
2150
|
+
w0=w0,
|
2151
|
+
betas0=betas0,
|
2152
|
+
t=t,
|
2153
|
+
beta_w=beta_w,
|
2154
|
+
kinds_x=kinds_x,
|
2155
|
+
kind_w=kind_w,
|
2156
|
+
alpha0=alpha0,
|
2157
|
+
p0=p0,
|
2158
|
+
ns0=ns0,
|
2159
|
+
ds0=ds0,
|
2160
|
+
max_points=max_points,
|
2161
|
+
)
|
2162
|
+
)
|
2163
|
+
|
2164
|
+
return PXEnvelope(
|
2165
|
+
temperature=t,
|
2166
|
+
global_composition_0=z0,
|
2167
|
+
global_composition_i=zi,
|
2168
|
+
main_phases_compositions=x_ls,
|
2169
|
+
reference_phase_compositions=ws,
|
2170
|
+
main_phases_molar_fractions=betas,
|
2171
|
+
pressures=ps,
|
2172
|
+
alphas=alphas,
|
2173
|
+
iterations=iters,
|
2174
|
+
specified_variable=ns,
|
2175
|
+
critical_pressures=pcs,
|
2176
|
+
critical_alphas=acs,
|
2177
|
+
main_phases_kinds=x_kinds,
|
2178
|
+
reference_phase_kinds=w_kinds,
|
2179
|
+
)
|
2180
|
+
|
2181
|
+
def phase_envelope_tx_mp(
|
2182
|
+
self,
|
2183
|
+
z0,
|
2184
|
+
zi,
|
2185
|
+
p,
|
2186
|
+
x_l0,
|
2187
|
+
w0,
|
2188
|
+
betas0,
|
2189
|
+
t0,
|
2190
|
+
ns0,
|
2191
|
+
ds0,
|
2192
|
+
alpha0=0,
|
2193
|
+
beta_w=0,
|
2194
|
+
max_points=1000,
|
2195
|
+
kinds_x=None,
|
2196
|
+
kind_w=None,
|
2197
|
+
) -> TXEnvelope:
|
2198
|
+
"""Multi-phase envelope."""
|
2199
|
+
|
2200
|
+
x_l0 = np.array(x_l0, order="F")
|
2201
|
+
|
2202
|
+
number_of_phases = x_l0.shape[0]
|
2203
|
+
|
2204
|
+
kinds_x, kind_w = adjust_root_kind(
|
2205
|
+
number_of_phases=number_of_phases, kinds_x=kinds_x, kind_w=kind_w
|
2206
|
+
)
|
2207
|
+
|
2208
|
+
(
|
2209
|
+
x_ls,
|
2210
|
+
ws,
|
2211
|
+
betas,
|
2212
|
+
ts,
|
2213
|
+
alphas,
|
2214
|
+
iters,
|
2215
|
+
ns,
|
2216
|
+
main_kinds,
|
2217
|
+
ref_kinds,
|
2218
|
+
tcs,
|
2219
|
+
acs,
|
2220
|
+
) = yaeos_c.tx_mp_phase_envelope(
|
2221
|
+
id=self.id,
|
2222
|
+
np=number_of_phases,
|
2223
|
+
z0=z0,
|
2224
|
+
zi=zi,
|
2225
|
+
p=p,
|
2226
|
+
beta_w=beta_w,
|
2227
|
+
kinds_x=kinds_x,
|
2228
|
+
kind_w=kind_w,
|
2229
|
+
x_l0=x_l0,
|
2230
|
+
w0=w0,
|
2231
|
+
betas0=betas0,
|
2232
|
+
alpha0=alpha0,
|
2233
|
+
t0=t0,
|
2234
|
+
ns0=ns0,
|
2235
|
+
ds0=ds0,
|
2236
|
+
max_points=max_points,
|
2237
|
+
)
|
2238
|
+
|
2239
|
+
return TXEnvelope(
|
2240
|
+
pressure=p,
|
2241
|
+
global_composition_0=z0,
|
2242
|
+
global_composition_i=zi,
|
2243
|
+
main_phases_compositions=x_ls,
|
2244
|
+
reference_phase_compositions=ws,
|
2245
|
+
main_phases_molar_fractions=betas,
|
2246
|
+
temperatures=ts,
|
2247
|
+
alphas=alphas,
|
2248
|
+
iterations=iters,
|
2249
|
+
specified_variable=ns,
|
2250
|
+
critical_temperatures=tcs,
|
2251
|
+
critical_alphas=acs,
|
2252
|
+
main_phases_kinds=main_kinds,
|
2253
|
+
reference_phase_kinds=ref_kinds,
|
2254
|
+
)
|
2255
|
+
|
2256
|
+
def phase_envelope_pt_from_dsp(
|
2257
|
+
self,
|
2258
|
+
z,
|
2259
|
+
env1: PTEnvelope,
|
2260
|
+
env2: PTEnvelope,
|
2261
|
+
dbeta0=1e-5,
|
2262
|
+
max_points=1000,
|
2263
|
+
) -> list:
|
2264
|
+
"""Calculate PT phase envelopes from a DSP.
|
2265
|
+
|
2266
|
+
This method calculates the phase envelope at the intersection of two
|
2267
|
+
PT envelopes, `env1` and `env2`.
|
2268
|
+
|
2269
|
+
Parameters
|
2270
|
+
----------
|
2271
|
+
z : array_like
|
2272
|
+
Global mole fractions
|
2273
|
+
env1 : PTEnvelope
|
2274
|
+
First PT envelope object
|
2275
|
+
env2 : PTEnvelope
|
2276
|
+
Second PT envelope object
|
2277
|
+
dbeta0 : float, optional
|
2278
|
+
initial step for the beta values, by default 1e-5
|
2279
|
+
max_points : int, optional
|
2280
|
+
Maximum number of points to calculate, by default 1000
|
2281
|
+
"""
|
2282
|
+
nc = env1.number_of_components
|
2283
|
+
phases = env1.number_of_phases + 1
|
2284
|
+
|
2285
|
+
Ts, Ps = intersection(env1["T"], env1["P"], env2["T"], env2["P"])
|
2286
|
+
|
2287
|
+
dsps = []
|
2288
|
+
for Tdsp, Pdsp in zip(Ts, Ps):
|
2289
|
+
env1_loc = np.argmin(
|
2290
|
+
np.abs(env1["T"] - Tdsp) + np.abs(env1["P"] - Pdsp)
|
2291
|
+
)
|
2292
|
+
env2_loc = np.argmin(
|
2293
|
+
np.abs(env2["T"] - Tdsp) + np.abs(env2["P"] - Pdsp)
|
2294
|
+
)
|
2295
|
+
|
2296
|
+
betas_1 = env1.main_phases_molar_fractions[env1_loc, :]
|
2297
|
+
betas_2 = env2.main_phases_molar_fractions[env2_loc, :]
|
2298
|
+
|
2299
|
+
w0 = env1.reference_phase_compositions[env1_loc]
|
2300
|
+
y0 = env2.reference_phase_compositions[env2_loc]
|
2301
|
+
|
2302
|
+
x_l1 = np.vstack(
|
2303
|
+
(env1.main_phases_compositions[env1_loc, :, :], y0)
|
2304
|
+
)
|
2305
|
+
x_l2 = np.vstack(
|
2306
|
+
(env1.main_phases_compositions[env1_loc, :, :], w0)
|
2307
|
+
)
|
2308
|
+
|
2309
|
+
# Convert the kinds to the correct format. Saving first as *_ to
|
2310
|
+
# avoid issues.
|
2311
|
+
kinds_x_1 = env1.main_phases_kinds[env1_loc, :]
|
2312
|
+
kinds_x_2 = env2.main_phases_kinds[env2_loc, :]
|
2313
|
+
kind_w_1 = env1.reference_phase_kinds[env1_loc]
|
2314
|
+
kind_w_2 = env2.reference_phase_kinds[env2_loc]
|
2315
|
+
dsp_1 = self.phase_envelope_pt_mp(
|
2316
|
+
z=z,
|
2317
|
+
x_l0=x_l1,
|
2318
|
+
w0=w0,
|
2319
|
+
betas0=[*betas_1, 0],
|
2320
|
+
p0=Pdsp,
|
2321
|
+
t0=Tdsp,
|
2322
|
+
ns0=phases * nc + phases,
|
2323
|
+
ds0=dbeta0,
|
2324
|
+
beta_w=0,
|
2325
|
+
kinds_x=[*kinds_x_1, kind_w_1],
|
2326
|
+
kind_w=kind_w_2,
|
2327
|
+
max_points=max_points,
|
2328
|
+
)
|
2329
|
+
|
2330
|
+
dsp_2 = self.phase_envelope_pt_mp(
|
2331
|
+
z=z,
|
2332
|
+
x_l0=x_l2,
|
2333
|
+
w0=y0,
|
2334
|
+
betas0=[*betas_2, 0],
|
2335
|
+
p0=Pdsp,
|
2336
|
+
t0=Tdsp,
|
2337
|
+
ns0=phases * nc + phases,
|
2338
|
+
ds0=dbeta0,
|
2339
|
+
beta_w=0,
|
2340
|
+
kinds_x=[*kinds_x_2, kind_w_2],
|
2341
|
+
kind_w=kind_w_1,
|
2342
|
+
max_points=max_points,
|
2343
|
+
)
|
2344
|
+
|
2345
|
+
dsps.append([dsp_1, dsp_2])
|
2346
|
+
|
2347
|
+
return dsps
|
2348
|
+
|
2349
|
+
def phase_envelope_px_from_dsp(
|
2350
|
+
self, z0, zi, env1: PXEnvelope, env2: PXEnvelope, dbeta0=1e-5
|
2351
|
+
) -> list:
|
2352
|
+
"""Calculate PX phase envelopes from a DSP.
|
2353
|
+
|
2354
|
+
This method calculates the phase envelope at the intersection of two
|
2355
|
+
PX envelopes, `env1` and `env2`.
|
2356
|
+
Parameters
|
2357
|
+
----------
|
2358
|
+
z0 : array_like
|
2359
|
+
Global mole fractions of the original fluid
|
2360
|
+
zi : array_like
|
2361
|
+
Global mole fractions of the other fluid
|
2362
|
+
env1 : PXEnvelope
|
2363
|
+
First PX envelope object
|
2364
|
+
env2 : PXEnvelope
|
2365
|
+
Second PX envelope object
|
2366
|
+
dbeta0 : float, optional
|
2367
|
+
Initial step for the beta values, by default 1e-5
|
2368
|
+
Returns
|
2369
|
+
-------
|
2370
|
+
list
|
2371
|
+
List of lists of two PXEnvelope objects, one for each intersection
|
2372
|
+
point.
|
2373
|
+
"""
|
2374
|
+
|
2375
|
+
nc = env1.number_of_components
|
2376
|
+
phases = env1.number_of_phases + 1
|
2377
|
+
dsps = intersection(env1["a"], env1["P"], env2["a"], env2["P"])
|
2378
|
+
dsps = []
|
2379
|
+
|
2380
|
+
for adsp, Pdsp in zip(dsps[0], dsps[1]):
|
2381
|
+
env1_loc = np.argmin(abs(env1["a"] - adsp) + abs(env1["P"]) - Pdsp)
|
2382
|
+
env2_loc = np.argmin(abs(env2["a"] - adsp) + abs(env2["P"]) - Pdsp)
|
2383
|
+
|
2384
|
+
betas_1 = env1.main_phases_molar_fractions[env1_loc, :]
|
2385
|
+
betas_2 = env2.main_phases_molar_fractions[env2_loc, :]
|
2386
|
+
|
2387
|
+
w0 = env1.reference_phase_compositions[env1_loc]
|
2388
|
+
y0 = env2.reference_phase_compositions[env2_loc]
|
2389
|
+
|
2390
|
+
x_l1 = np.vstack(
|
2391
|
+
(env1.main_phases_compositions[env1_loc, :, :], y0)
|
2392
|
+
)
|
2393
|
+
x_l2 = np.vstack(
|
2394
|
+
(env1.main_phases_compositions[env1_loc, :, :], w0)
|
2395
|
+
)
|
2396
|
+
|
2397
|
+
dsp_1 = self.phase_envelope_px_mp(
|
2398
|
+
z0=z0,
|
2399
|
+
zi=zi,
|
2400
|
+
t=T,
|
2401
|
+
x_l0=x_l1,
|
2402
|
+
w0=w0,
|
2403
|
+
betas0=[*betas_1, 0],
|
2404
|
+
p0=Pdsp,
|
2405
|
+
alpha0=adsp,
|
2406
|
+
ns0=phases * nc + phases,
|
2407
|
+
ds0=dbeta0,
|
2408
|
+
beta_w=0,
|
2409
|
+
max_points=500,
|
2410
|
+
)
|
2411
|
+
|
2412
|
+
dsp_2 = self.phase_envelope_px_mp(
|
2413
|
+
z0=z0,
|
2414
|
+
zi=zi,
|
2415
|
+
t=T,
|
2416
|
+
x_l0=x_l2,
|
2417
|
+
w0=y0,
|
2418
|
+
betas0=[*betas_2, 0],
|
2419
|
+
p0=Pdsp,
|
2420
|
+
alpha0=adsp,
|
2421
|
+
ns0=phases * nc + phases,
|
2422
|
+
ds0=dbeta0,
|
2423
|
+
beta_w=0,
|
2424
|
+
max_points=800,
|
2425
|
+
)
|
2426
|
+
|
2427
|
+
dsps.append([dsp_1, dsp_2])
|
2428
|
+
|
2429
|
+
return dsps
|
2430
|
+
|
2431
|
+
def isopleth(
|
2432
|
+
self,
|
2433
|
+
z,
|
2434
|
+
three_phase=True,
|
2435
|
+
dew_start=(500, 0.01),
|
2436
|
+
bubble_start=(200, 10),
|
2437
|
+
max_points=1000,
|
2438
|
+
delta_dew_2ph=0.01,
|
2439
|
+
delta_bub_2ph=0.01,
|
2440
|
+
delta_dsp_3ph=0.01,
|
2441
|
+
stop_pressure=2500,
|
2442
|
+
):
|
2443
|
+
|
2444
|
+
dew_point = self.saturation_temperature(
|
2445
|
+
z, pressure=dew_start[1], kind="dew", t0=dew_start[0]
|
2446
|
+
)
|
2447
|
+
bub_point = self.saturation_pressure(
|
2448
|
+
z, temperature=bubble_start[0], kind="bubble", p0=bubble_start[1]
|
2449
|
+
)
|
2450
|
+
|
2451
|
+
dew_line = self.phase_envelope_pt_mp(
|
2452
|
+
z=z,
|
2453
|
+
x_l0=[z],
|
2454
|
+
w0=dew_point["x"],
|
2455
|
+
betas0=[1],
|
2456
|
+
p0=dew_point["P"],
|
2457
|
+
t0=dew_point["T"],
|
2458
|
+
ns0=len(z) + 2,
|
2459
|
+
ds0=delta_dew_2ph,
|
2460
|
+
max_points=max_points,
|
2461
|
+
stop_pressure=stop_pressure,
|
2462
|
+
)
|
2463
|
+
|
2464
|
+
bub_line = self.phase_envelope_pt_mp(
|
2465
|
+
z=z,
|
2466
|
+
x_l0=[z],
|
2467
|
+
w0=bub_point["y"],
|
2468
|
+
betas0=[1],
|
2469
|
+
p0=bub_point["P"],
|
2470
|
+
t0=bub_point["T"],
|
2471
|
+
ns0=len(z) + 2,
|
2472
|
+
ds0=delta_bub_2ph,
|
2473
|
+
max_points=max_points,
|
2474
|
+
stop_pressure=stop_pressure,
|
2475
|
+
)
|
2476
|
+
|
2477
|
+
liq = self.phase_envelope_pt(
|
2478
|
+
z, kind="liquid-liquid", t0=500, p0=2000, max_points=2
|
2479
|
+
)
|
2480
|
+
|
2481
|
+
if len(liq["T"]) > 0:
|
2482
|
+
liq_line = self.phase_envelope_pt_mp(
|
2483
|
+
z=z,
|
2484
|
+
x_l0=[z],
|
2485
|
+
w0=liq.reference_phase_compositions[0],
|
2486
|
+
betas0=[1],
|
2487
|
+
p0=liq["P"][0],
|
2488
|
+
t0=liq["T"][0],
|
2489
|
+
ns0=len(z) + 2,
|
2490
|
+
ds0=-0.01,
|
2491
|
+
max_points=max_points,
|
2492
|
+
stop_pressure=1e10,
|
2493
|
+
)
|
2494
|
+
else:
|
2495
|
+
liq_line = None
|
2496
|
+
|
2497
|
+
dsps_db = intersection(
|
2498
|
+
dew_line["T"],
|
2499
|
+
dew_line["P"],
|
2500
|
+
bub_line["T"],
|
2501
|
+
bub_line["P"],
|
2502
|
+
)
|
2503
|
+
|
2504
|
+
if liq_line:
|
2505
|
+
dsps_dl = intersection(
|
2506
|
+
dew_line["T"],
|
2507
|
+
dew_line["P"],
|
2508
|
+
liq_line["T"],
|
2509
|
+
liq_line["P"],
|
2510
|
+
)
|
2511
|
+
|
2512
|
+
dsps_bl = intersection(
|
2513
|
+
bub_line["T"],
|
2514
|
+
bub_line["P"],
|
2515
|
+
liq_line["T"],
|
2516
|
+
liq_line["P"],
|
2517
|
+
)
|
2518
|
+
|
2519
|
+
dsps_set = {
|
2520
|
+
"dl": [dsps_dl, dew_line, liq_line],
|
2521
|
+
"db": [dsps_db, dew_line, bub_line],
|
2522
|
+
"bl": [dsps_bl, bub_line, liq_line],
|
2523
|
+
}
|
2524
|
+
else:
|
2525
|
+
dsps_set = {"db": [dsps_db, dew_line, bub_line]}
|
2526
|
+
|
2527
|
+
dew_locs = []
|
2528
|
+
bub_locs = []
|
2529
|
+
liq_locs = []
|
2530
|
+
|
2531
|
+
three_phase_envs = []
|
2532
|
+
stable_lines = {"3ph": [], "2ph": []}
|
2533
|
+
|
2534
|
+
if three_phase:
|
2535
|
+
dew_line_stable = dew_line
|
2536
|
+
bub_line_stable = bub_line
|
2537
|
+
liq_line_stable = liq_line
|
2538
|
+
|
2539
|
+
for dsp_name in dsps_set:
|
2540
|
+
# Order the DSPs wrt temperature
|
2541
|
+
dsps = dsps_set[dsp_name][0]
|
2542
|
+
env_1, env_2 = dsps_set[dsp_name][1], dsps_set[dsp_name][2]
|
2543
|
+
|
2544
|
+
idx = dsps[0].argsort()
|
2545
|
+
|
2546
|
+
dsps = (dsps[0][idx], dsps[1][idx])
|
2547
|
+
|
2548
|
+
if 0 < len(dsps[0]) <= 2:
|
2549
|
+
for temperature, pressure in zip(dsps[0], dsps[1]):
|
2550
|
+
env_1_loc = np.argmin(
|
2551
|
+
np.abs(env_1["T"] - temperature)
|
2552
|
+
+ np.abs(env_1["P"] - pressure)
|
2553
|
+
)
|
2554
|
+
env_2_loc = np.argmin(
|
2555
|
+
np.abs(env_2["T"] - temperature)
|
2556
|
+
+ np.abs(env_2["P"] - pressure)
|
2557
|
+
)
|
2558
|
+
|
2559
|
+
dew_locs.append(env_1_loc)
|
2560
|
+
bub_locs.append(env_2_loc)
|
2561
|
+
|
2562
|
+
w_env_1 = env_1.reference_phase_compositions[env_1_loc]
|
2563
|
+
w_env_2 = env_2.reference_phase_compositions[env_2_loc]
|
2564
|
+
|
2565
|
+
env1 = self.phase_envelope_pt_mp(
|
2566
|
+
z,
|
2567
|
+
x_l0=np.array([z, w_env_1]),
|
2568
|
+
w0=w_env_2,
|
2569
|
+
betas0=[1, 0],
|
2570
|
+
t0=temperature,
|
2571
|
+
p0=pressure,
|
2572
|
+
ns0=2 * len(z) + 2,
|
2573
|
+
ds0=delta_dsp_3ph,
|
2574
|
+
max_points=1000,
|
2575
|
+
stop_pressure=max([pressure * 2, stop_pressure]),
|
2576
|
+
)
|
2577
|
+
|
2578
|
+
env2 = self.phase_envelope_pt_mp(
|
2579
|
+
z,
|
2580
|
+
x_l0=np.array([z, w_env_2]),
|
2581
|
+
w0=w_env_1,
|
2582
|
+
betas0=[1, 0],
|
2583
|
+
t0=temperature,
|
2584
|
+
p0=pressure,
|
2585
|
+
ns0=2 * len(z) + 2,
|
2586
|
+
ds0=delta_dsp_3ph,
|
2587
|
+
max_points=1000,
|
2588
|
+
stop_pressure=max([pressure * 2, stop_pressure]),
|
2589
|
+
)
|
2590
|
+
|
2591
|
+
three_phase_envs.append((env1, env2))
|
2592
|
+
# if len(dew_locs) == 2:
|
2593
|
+
# msk = np.array([False] * len(dew_line))
|
2594
|
+
# msk[: dew_locs[1] + 1] = True
|
2595
|
+
# msk[dew_locs[0] :] = True
|
2596
|
+
# dew_line_stable *= np.nan
|
2597
|
+
# dew_line_stable[msk] = dew_line[msk]
|
2598
|
+
|
2599
|
+
# msk = np.array([False] * len(bub_line))
|
2600
|
+
# msk[bub_locs[0] : bub_locs[1] + 1] = True
|
2601
|
+
# bub_line_stable *= np.nan
|
2602
|
+
# bub_line_stable[msk] = bub_line[msk]
|
2603
|
+
|
2604
|
+
elif len(dsps[0]) == 0:
|
2605
|
+
|
2606
|
+
k0 = dew_line.reference_phase_compositions[0, :] / z
|
2607
|
+
flash = self.flash_pt(
|
2608
|
+
z,
|
2609
|
+
pressure=bub_line["P"][0],
|
2610
|
+
temperature=bub_line["T"][0],
|
2611
|
+
k0=k0,
|
2612
|
+
)
|
2613
|
+
|
2614
|
+
x_l0 = [z, flash["y"]]
|
2615
|
+
w0 = bub_line.reference_phase_compositions[0, :]
|
2616
|
+
betas = [1 - flash["beta"], flash["beta"]]
|
2617
|
+
|
2618
|
+
bubble_isolated = self.phase_envelope_pt_mp(
|
2619
|
+
z=z,
|
2620
|
+
x_l0=x_l0,
|
2621
|
+
w0=w0,
|
2622
|
+
betas0=betas,
|
2623
|
+
p0=flash["P"],
|
2624
|
+
t0=flash["T"],
|
2625
|
+
ns0=2 * len(z) + 4,
|
2626
|
+
ds0=delta_bub_2ph,
|
2627
|
+
max_points=max_points,
|
2628
|
+
)
|
2629
|
+
|
2630
|
+
x_l0 = [z, bub_line.reference_phase_compositions[0, :]]
|
2631
|
+
w0 = flash["y"]
|
2632
|
+
idx = np.argmax(w0)
|
2633
|
+
|
2634
|
+
flash = self.flash_pt(z, flash["P"], flash["T"])
|
2635
|
+
betas = [1 - flash["beta"], flash["beta"]]
|
2636
|
+
|
2637
|
+
dew_isolated = self.phase_envelope_pt_mp(
|
2638
|
+
z=z,
|
2639
|
+
x_l0=x_l0,
|
2640
|
+
w0=w0,
|
2641
|
+
betas0=betas,
|
2642
|
+
p0=flash["P"] - 5,
|
2643
|
+
t0=flash["T"] + 5,
|
2644
|
+
ns0=2 * len(z) + 3,
|
2645
|
+
ds0=delta_bub_2ph,
|
2646
|
+
max_points=max_points,
|
2647
|
+
)
|
2648
|
+
|
2649
|
+
three_phase_envs.append((bubble_isolated, dew_isolated))
|
2650
|
+
|
2651
|
+
stable_lines["2ph"] = {
|
2652
|
+
"dew": dew_line_stable,
|
2653
|
+
"bub": bub_line_stable,
|
2654
|
+
"liq": liq_line_stable,
|
2655
|
+
}
|
2656
|
+
|
2657
|
+
return {
|
2658
|
+
"2ph": (dew_line, bub_line, liq_line),
|
2659
|
+
"DSP": dsps,
|
2660
|
+
"3ph": three_phase_envs,
|
2661
|
+
"2ph_stable": stable_lines["2ph"],
|
2662
|
+
}
|
2663
|
+
else:
|
2664
|
+
return {
|
2665
|
+
"2ph": {"dew": dew_line, "bub": bub_line},
|
2666
|
+
}
|
2667
|
+
|
2668
|
+
# =========================================================================
|
2669
|
+
# Stability analysis
|
2670
|
+
# -------------------------------------------------------------------------
|
2671
|
+
def stability_analysis(self, z, pressure, temperature):
|
2672
|
+
"""Perform stability analysis.
|
2673
|
+
|
2674
|
+
Find all the possible minima values that the :math:`tm` function,
|
2675
|
+
defined by Michelsen and Mollerup.
|
2676
|
+
|
2677
|
+
Parameters
|
2678
|
+
----------
|
2679
|
+
z : array_like
|
2680
|
+
Global mole fractions
|
2681
|
+
pressure : float
|
2682
|
+
Pressure [bar]
|
2683
|
+
temperature : float
|
2684
|
+
Temperature [K]
|
2685
|
+
|
2686
|
+
Returns
|
2687
|
+
-------
|
2688
|
+
dict
|
2689
|
+
Stability analysis result dictionary with keys:
|
2690
|
+
- w: value of the test phase that minimizes the :math:`tm` function
|
2691
|
+
- tm: minimum value of the :math:`tm` function.
|
2692
|
+
dict
|
2693
|
+
All found minimum values of the :math:`tm` function and the
|
2694
|
+
corresponding test phase mole fractions.
|
2695
|
+
- w: all values of :math:`w` that minimize the :math:`tm` function
|
2696
|
+
- tm: all values found minima of the :math:`tm` function
|
2697
|
+
"""
|
2698
|
+
(w_min, tm_min, all_mins) = yaeos_c.stability_zpt(
|
2699
|
+
id=self.id, z=z, p=pressure, t=temperature
|
2700
|
+
)
|
2701
|
+
|
2702
|
+
all_mins_w = all_mins[:, : len(z)]
|
2703
|
+
all_mins = all_mins[:, -1]
|
2704
|
+
|
2705
|
+
return {"w": w_min, "tm": tm_min}, {"tm": all_mins, "w": all_mins_w}
|
2706
|
+
|
2707
|
+
def stability_tm(self, z, w, pressure, temperature):
|
2708
|
+
"""Calculate the :math:`tm` function.
|
2709
|
+
|
2710
|
+
Calculate the :math:`tm` function, defined by Michelsen and Mollerup.
|
2711
|
+
If this value is negative, it means that the feed with composition `z`
|
2712
|
+
is unstable.
|
2713
|
+
|
2714
|
+
Parameters
|
2715
|
+
----------
|
2716
|
+
z : array_like
|
2717
|
+
Global mole fractions
|
2718
|
+
w : array_like
|
2719
|
+
Test Phase mole fractions
|
2720
|
+
pressure : float
|
2721
|
+
Pressure [bar]
|
2722
|
+
temperature : float
|
2723
|
+
Temperature [K]
|
2724
|
+
|
2725
|
+
Returns
|
2726
|
+
-------
|
2727
|
+
float
|
2728
|
+
Value of the :math:`tm` function
|
2729
|
+
"""
|
2730
|
+
return yaeos_c.tm(id=self.id, z=z, w=w, p=pressure, t=temperature)
|
2731
|
+
|
2732
|
+
# =========================================================================
|
2733
|
+
# Critical points and lines
|
2734
|
+
# -------------------------------------------------------------------------
|
2735
|
+
def critical_point(self, z0, zi=[0, 0], ns=1, s=0, max_iters=100) -> dict:
|
2736
|
+
"""Critical point calculation.
|
2737
|
+
|
2738
|
+
Calculate the critical point of a mixture. At a given composition.
|
2739
|
+
|
2740
|
+
Parameters
|
2741
|
+
----------
|
2742
|
+
z0: array_like
|
2743
|
+
Mole fractions of original fluid
|
2744
|
+
zi: array_like
|
2745
|
+
Mole fractinos of other fluid
|
2746
|
+
ns: int
|
2747
|
+
Number of specification
|
2748
|
+
S: float
|
2749
|
+
Specification value
|
2750
|
+
max_iters: int, optional
|
2751
|
+
|
2752
|
+
Returns
|
2753
|
+
-------
|
2754
|
+
dict
|
2755
|
+
Critical point calculation result dictionary with keys:
|
2756
|
+
- Tc: critical temperature [K]
|
2757
|
+
- Pc: critical pressure [bar]
|
2758
|
+
- Vc: critical volume [L]
|
2759
|
+
"""
|
2760
|
+
*x, t, p, v = yaeos_c.critical_point(
|
2761
|
+
self.id, z0=z0, zi=zi, spec=ns, s=s, max_iters=max_iters
|
2762
|
+
)
|
2763
|
+
|
2764
|
+
return {"x": x, "Tc": t, "Pc": p, "Vc": v}
|
2765
|
+
|
2766
|
+
def critical_line(
|
2767
|
+
self,
|
2768
|
+
z0,
|
2769
|
+
zi,
|
2770
|
+
ns=1,
|
2771
|
+
s=1e-5,
|
2772
|
+
ds0=1e-2,
|
2773
|
+
a0=1e-5,
|
2774
|
+
v0=0,
|
2775
|
+
t0=0,
|
2776
|
+
p0=0,
|
2777
|
+
stability_analysis=False,
|
2778
|
+
max_points=1000,
|
2779
|
+
stop_pressure=2500,
|
2780
|
+
):
|
2781
|
+
"""Critical Line calculation.
|
2782
|
+
|
2783
|
+
Calculate the critical line between two compositions
|
2784
|
+
|
2785
|
+
Parameters
|
2786
|
+
----------
|
2787
|
+
z0: array_like
|
2788
|
+
Initial global mole fractions
|
2789
|
+
zi: array_like
|
2790
|
+
Final global mole fractions
|
2791
|
+
ns: int, optional
|
2792
|
+
Specified variable number, by default 1
|
2793
|
+
s: float, optional
|
2794
|
+
Specified value, by default 1e-5
|
2795
|
+
ds0: float, optional
|
2796
|
+
Step for molar fraction of composition `i`
|
2797
|
+
a0: float, optional
|
2798
|
+
Initial molar fraction of composition `i`
|
2799
|
+
v0: float, optional
|
2800
|
+
Initial guess for volume [L/mol]
|
2801
|
+
t0: float, optional
|
2802
|
+
Initial guess for temperature [K]
|
2803
|
+
p0: float, optional
|
2804
|
+
Initial guess for pressure [bar]
|
2805
|
+
max_points: int, optional
|
2806
|
+
Maximum number of points to calculate
|
2807
|
+
stop_pressure: float, optional
|
2808
|
+
Stop when reaching this pressure value
|
2809
|
+
"""
|
2810
|
+
alphas, vs, ts, ps, *cep = yaeos_c.critical_line(
|
2811
|
+
self.id,
|
2812
|
+
ns=ns,
|
2813
|
+
ds0=ds0,
|
2814
|
+
a0=a0,
|
2815
|
+
v0=v0,
|
2816
|
+
t0=t0,
|
2817
|
+
p0=p0,
|
2818
|
+
s=s,
|
2819
|
+
stability_analysis=stability_analysis,
|
2820
|
+
z0=z0,
|
2821
|
+
zi=zi,
|
2822
|
+
max_points=max_points,
|
2823
|
+
stop_pressure=stop_pressure,
|
2824
|
+
)
|
2825
|
+
|
2826
|
+
msk = ~np.isnan(ts)
|
2827
|
+
|
2828
|
+
if stability_analysis:
|
2829
|
+
return {
|
2830
|
+
"a": alphas[msk],
|
2831
|
+
"T": ts[msk],
|
2832
|
+
"P": ps[msk],
|
2833
|
+
"V": vs[msk],
|
2834
|
+
}, {
|
2835
|
+
"x": cep[0],
|
2836
|
+
"y": cep[1],
|
2837
|
+
"P": cep[2],
|
2838
|
+
"Vx": cep[3],
|
2839
|
+
"Vy": cep[4],
|
2840
|
+
"T": cep[5],
|
2841
|
+
}
|
2842
|
+
else:
|
2843
|
+
return {
|
2844
|
+
"a": alphas[msk],
|
2845
|
+
"T": ts[msk],
|
2846
|
+
"P": ps[msk],
|
2847
|
+
"V": vs[msk],
|
2848
|
+
}
|
2849
|
+
|
2850
|
+
def critical_line_liquid_liquid(
|
2851
|
+
self, z0=[0, 1], zi=[1, 0], pressure=2000, t0=500
|
2852
|
+
):
|
2853
|
+
"""Find the start of the Liquid-Liquid critical line of a binary.
|
2854
|
+
|
2855
|
+
Parameters
|
2856
|
+
----------
|
2857
|
+
z0: array_like
|
2858
|
+
Initial global mole fractions
|
2859
|
+
zi: array_like
|
2860
|
+
Final global mole fractions
|
2861
|
+
pressure: float
|
2862
|
+
Pressure [bar]
|
2863
|
+
t0: float
|
2864
|
+
Initial guess for temperature [K]
|
2865
|
+
"""
|
2866
|
+
a, t, v = yaeos_c.find_llcl(
|
2867
|
+
self.id, z0=z0, zi=zi, p=pressure, tstart=t0
|
2868
|
+
)
|
2869
|
+
|
2870
|
+
return a, t, v
|
2871
|
+
|
2872
|
+
def __del__(self) -> None:
|
2873
|
+
"""Delete the model from the available models list (Fortran side)."""
|
2874
|
+
yaeos_c.make_available_ar_models_list(self.id)
|