#!/usr/bin/python3
# -*- coding: utf-8 -*-
r"""Pychemqt, Chemical Engineering Process simulator
Copyright (C) 2009-2025, Juan José Gómez Romera <jjgomera@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Library with the implementantion of a generic cubic equation of state with the
form
.. math::
P = \frac{RT}{V-b}-\frac{\alpha(T)}{V^2+\delta V+\epsilon}
Expressing as a cubic polynomy in compressibility factor easy to solve
.. math::
Z^3 + \left(\delta'-B'-1\right)Z^2 +
\left(a'+\epsilon'-\delta'\left(b'+1\right)\right)Z -
\left(\epsilon'\left(b'+1\right)+a'b'\right) = 0
using the adimensional parameters
.. math::
\begin{array}[t]{l}
a' = \frac{aP}{RT}\\
b' = \frac{bP}{RT}\\
\delta' = \frac{\delta P}{RT}\\
\epsilon' = \frac{\epsilon P}{RT}\\
\end{array}
Each cubic EoS implemented here would a specific form of this general
expression changing the values of δ, ε and the expresion of α(T)
Each equation is specially suitable for different compounds, for example, the
Schmidt-Wenzel (SW) equation (1980) and the Adachi-Lu-Sugie (ALS) equation
(1983) are good for methane to n-decane. The Yu-Lu (YL) equation (1987) was
designed for asymmetric nonpolar mixtures, but not for polar substances. The
Iwai-Margerum-Lu (IML) equation ( 1987) was developed for polar substances, but
not suitable for nonpolar substances with large molecular weight.
"""
from math import log, exp
from scipy.constants import R
from tools.qt import translate
from lib import unidades
from lib.eos import EoS
from lib.physics import R_atml, cubicCardano
from lib.bip import Kij, Mixing_Rule
from lib.utilities import refDoc
# TODO: Añadir parámetros, archivo /media/datos/Biblioteca/archivos/alfas.pdf
# self.Mathias = 0
# self.Adachi = [0, 0]
# self.Andoulakis = [0, 0, 0]
__doi__ = {
1:
{"autor": "Poling, B.E, Prausnitz, J.M, O'Connell, J.P",
"title": "The Properties of Gases and Liquids 5th Edition",
"ref": "McGraw-Hill, New York, 2001",
"doi": ""},
2:
{"autor": "Ahmed, T.",
"title": "Equations of State and PVT Analysis: Applications for"
"Improved Reservoir Modeling, 2nd Edition",
"ref": "Gulf Professional Publishing, 2016, ISBN 9780128015704,",
"doi": "10.1016/B978-0-12-801570-4.00002-7"},
3:
{"autor": "Bell, I.H., Jäger, A.",
"title": "Helmholtz Energy Transformations of Common Cubic Equations "
"of State for Use with Pure Fluids and Mixtures",
"ref": "J. Res. of NIST 121 (2016) 236-263",
"doi": "10.6028/jres.121.011"},
4:
{"autor": "",
"title": "",
"ref": "",
"doi": ""},
}
alfa = (translate("EoS", "Original"),
"Boston-Mathias",
"Twu",
"Doridon")
[docs]
@refDoc(__doi__, [3])
def CubicHelmholtz(tau, delta, **kw):
r"""Residual contribution to the free Helmholtz energy from a generic cubic
equation of state with the form:
.. math::
P = \frac{RT}{V-b}-\frac{\alpha(T)}{\left(v+\Delta_1b\right)
\left(v+\Delta_2b\right)}
From this formulation it's possible calculate the Helmholtz free energy
with the equation:
.. math::
\alpha^r = \phi^{(-)}-\frac{\tau\alpha}{RT_c}\phi^{(+)}
Parameters
----------
tau : float
Inverse reduced temperature, Tc/T [-]
delta : float
Reduced density, rho/rhoc [-]
kw : list
Aditional parameters specific of cubic equation of state
The parameters include: rhoc, Tc, b, alfa, Delta1, Delta2
Returns
-------
prop : dictionary with residual adimensional helmholtz energy and deriv
fir [-]
firt: [∂fir/∂τ]δ,x [-]
fird: [∂fir/∂δ]τ,x [-]
firtt: [∂²fir/∂τ²]δ,x [-]
firdt: [∂²fir/∂τ∂δ]x [-]
firdd: [∂²fir/∂δ²]τ,x [-]
"""
b = kw["b"]
a = kw["a"]
dat = kw["dat"]
datt = kw["datt"]
dattt = kw["dattt"]
Delta1 = kw["Delta1"]
Delta2 = kw["Delta2"]
R = kw["R"]
# This parameters are necessary only for multicomponent mixtures to
# calculate fugacity coefficient
bi = kw.get("bi", None)
daxi = kw.get("daxi", None)
rhoc = kw.get("rhoc", 1)
Tc = kw.get("Tc", 1)
phi1 = -log(1-b*delta*rhoc)
if Delta1 == Delta2:
# Special case using the l'Hôpital's rule
phi2 = rhoc*delta
else:
phi2 = log((Delta1*b*rhoc*delta+1)/(Delta2*b*rhoc*delta+1)) / \
b/(Delta1-Delta2)
phi1d = b*rhoc/(1-b*delta*rhoc)
phi1dd = b**2*rhoc**2/(1-b*delta*rhoc)**2
phi1ddd = 2*b**3*rhoc**3/(1-b*delta*rhoc)**3
PI12 = (1+Delta1*b*rhoc*delta) * (1+Delta2*b*rhoc*delta)
PI12d = b*rhoc * (2*Delta1*Delta2*b*delta*rhoc + Delta1 + Delta2)
PI12dd = 2*Delta1*Delta2*b**2*rhoc**2
phi2d = rhoc/PI12
phi2dd = -rhoc*PI12d/PI12**2
phi2ddd = rhoc*(-PI12*PI12dd+2*PI12d**2)/PI12**3
fir = phi1 - tau*a/R/Tc*phi2
fird = phi1d - tau*a/R/Tc*phi2d
firdd = phi1dd - tau*a/R/Tc*phi2dd
firddd = phi1ddd - tau*a/R/Tc*phi2ddd
# Eq 32
dtat = tau*dat + a
dtatt = tau*datt + 2*dat
dtattt = tau*dattt + 3*datt
firt = -dtat/R/Tc * phi2
firtt = -dtatt/R/Tc * phi2
firttt = -dtattt/R/Tc * phi2
firdt = -dtat/R/Tc * phi2d
firddt = -dtat/R/Tc * phi2dd
firdtt = -dtatt/R/Tc * phi2d
prop = {}
prop["fir"] = fir
prop["fird"] = fird
prop["firt"] = firt
prop["firdd"] = firdd
prop["firdt"] = firdt
prop["firtt"] = firtt
prop["firddd"] = firddd
prop["firddt"] = firddt
prop["firdtt"] = firdtt
prop["firttt"] = firttt
# Virial coefficient
phi1d0 = b*rhoc
phi2d0 = rhoc
fird0 = phi1d0 - tau*a/R/Tc*phi2d0
phi1dd0 = b**2*rhoc**2
PI12d0 = b*rhoc * (Delta1 + Delta2)
phi2dd0 = -rhoc*PI12d0
firdd0 = phi1dd0 - tau*a/R/Tc*phi2dd0
prop["B"] = fird0
prop["C"] = firdd0
prop["D"] = 0
if bi:
# Composition derivatives for fugacity coefficient calculation
c = 1/b
dbxi = bi # Eq 132
A = log((delta*rhoc*b*Delta1+1)/(delta*rhoc*b*Delta2+1)) # Eq 103
dAxi = [delta*rhoc*db*(Delta1-Delta2)/PI12 for db in dbxi] # Eq 104
dcxi = [-db/b**2 for db in dbxi] # Eq 107
phi1xi = [delta*rhoc*db/(1-delta*rhoc*b) for db in dbxi] # Eq 80
# Eq 111
phi2xi = [(A*dc + c*dA)/(Delta1-Delta2) for dc, dA in zip(dcxi, dAxi)]
dtaxi = [tau*da for da in daxi]
# Eq 77
phirxi = []
for dt, p1x, p2x in zip(dtaxi, phi1xi, phi2xi):
phirxi.append(p1x - 1/R/Tc*(dt*phi2 + tau*a*p2x))
prop["firxi"] = phirxi
return prop
[docs]
@refDoc(__doi__, [1, 2])
class Cubic(EoS):
r"""Class to implement the common functionality of cubic equation of state
This class implement a general cubic equation of state in the form:
.. math::
P = \frac{RT}{V-b}-\frac{\alpha(T)}{V^2+\delta V+\epsilon}
.. math::
P = \frac{RT}{V-b}-\frac{\alpha(T)}{\left(V+\delta_1b\right)
\left(V+\delta_2b\right)}
.. math::
\delta_1 = -\frac{\sqrt{\delta^2-4\epsilon}-\delta}{2b}
.. math::
\delta_2 = -\frac{\sqrt{\delta^2-4\epsilon}+\delta}{2b}
"""
[docs]
def __init__(self, T, P, mezcla, **kwargs):
EoS.__init__(self, T, P, mezcla, **kwargs)
if "R" in kwargs:
self.R = kwargs["R"]
else:
self.R = R
self._cubicDefinition(T)
# if self.mezcla.Tc < T:
# self.x = 1
# self.xi = self.zi
# self.yi = self.zi
# self.Zg = self._Z(self.zi, T, P)[-1]
# self.Zl = None
# else:
self.x, self.Zl, self.Zg, self.xi, self.yi, self.Ki = self._Flash()
# print("q = ", self.x)
# print("x = ", self.xi)
# print("y = ", self.yi)
# print("K = ", self.Ki)
if self.Zl:
self.Vl = unidades.MolarVolume(self.Zl*self.R*T/P, "m3mol")
rhoL = self.P/self.Zl/self.R/self.T
self.rhoL = unidades.MolarDensity(rhoL, "molm3")
else:
self.Vl = None
self.rhoL = None
if self.Zg:
self.Vg = unidades.MolarVolume(self.Zg*self.R*T/P, "m3mol")
rhoG = self.P/self.Zg/self.R/self.T
self.rhoG = unidades.MolarDensity(rhoG, "molm3")
else:
self.Vg = None
self.rhoG = None
self._volumeCorrection()
# tau = mezcla.Tc/T
# delta = self.V[-1]*mezcla.Vc
# kw = {}
# print(CubicHelmholtz(tau, delta, **kw))
# dep_v = self._departure(self.tita, self.b, self.delta, self.epsilon, self.dTitadT, self.V[-1], T)
# dep_l = self._departure(self.tita, self.b, self.delta, self.epsilon, self.dTitadT, self.V[0], T)
# rho = self.rhoG.molm3
# from pprint import pprint
# pprint(self._phir(self.T, rho, self.yi))
[docs]
def _volumeCorrection(self):
"""Apply volume correction to the rhoL property"""
pass
[docs]
def _cubicDefinition(self, T):
"""Definition of individual component parameters of generalized cubic
equation of state, its calculation don't depend composition"""
# TODO: Split fixed paremeters calculation from temperature dependences
# to speed up
pass
[docs]
def _GEOS(self, xi):
"""Definition of parameters of generalized cubic equation of state,
each child class must define in this procedure the values of mixture
a, b, delta, epsilon. The returned values are not dimensionless.
Parameters
----------
xi : list
Molar fraction of component in mixture, [-]
Returns
-------
parameters : list
Mixture parameters of equation, a, b, c, d
"""
pass
[docs]
def _Z(self, xi, T, P):
"""Calculate root of cubic polynomial in terms of GCEoS as give in
[1]_.
Parameters
----------
xi : list
Molar fraction of component in mixture, [-]
T : float
Temperature, [K]
P : float
Pressure, [Pa]
Returns
-------
Z : list
List with real root of equation
"""
self._cubicDefinition(T)
tita, b, delta, epsilon = self._GEOS(xi)
B = b*P/self.R/T
A = tita*P/(self.R*T)**2
D = delta*P/self.R/T
E = epsilon*(P/self.R/T)**2
# Eq 4-6.3 in [1]_
# η by default set to b to reduce terms, if any equations need that
# term redefine this procedure
coeff = (1, D-B-1, A+E-D*(B+1), -E*(B+1)-A*B)
Z = cubicCardano(*coeff)
# Sort Z values, if typeerror is raise return is because there is
# complex root, so return only the real root
try:
Z = sorted(map(float, Z))
except TypeError:
Z = Z[0:1]
return Z
[docs]
def _fug(self, xi, yi, T, P):
"""Fugacities of component in mixture calculation
Parameters
----------
xi : list
Molar fraction of component in liquid phase, [-]
yi : list
Molar fraction of component in vapor phase, [-]
T : float
Temperature, [K]
P : float
Pressure, [Pa]
Returns
-------
tital : list
List with liquid phase component fugacities
titav : list
List with vapour phase component fugacities
"""
self._cubicDefinition(T)
Bi = [bi*P/self.R/T for bi in self.bi]
Ai = [ai*P/(self.R*T)**2 for ai in self.ai]
al, bl, deltal, epsilonl = self._GEOS(xi)
Bl = bl*P/self.R/T
Al = al*P/(self.R*T)**2
Zl = self._Z(xi, T, P)[0]
tital = self._fugacity(Zl, xi, Al, Bl, Ai, Bi)
Zv = self._Z(yi, T, P)[-1]
av, bv, deltav, epsilonv = self._GEOS(yi)
Bv = bv*P/self.R/T
Av = av*P/(self.R*T)**2
titav = self._fugacity(Zv, yi, Av, Bv, Ai, Bi)
return tital, titav
[docs]
def _fugacity(self, Z, zi, A, B, Ai, Bi):
"""Fugacity for individual components in a mixture using the GEoS in
the Schmidt-Wenzel formulation, so the subclass must define the
parameters u and w in the EoS
Any other subclass with different formulation must overwrite this
method
"""
# Precalculation of inner sum in equation
aij = []
for ai, kiji in zip(Ai, self.kij):
suma = 0
for xj, aj, kij in zip(zi, Ai, kiji):
suma += xj*(1-kij)*(ai*aj)**0.5
aij.append(suma)
tita = []
for bi, aai in zip(Bi, aij):
rhs = bi/B*(Z-1) - log(Z-B) + A/B/(self.u-self.w)*(
bi/B-2/A*aai) * log((Z+self.u*B)/(Z+self.w*B))
tita.append(exp(rhs))
return tita
[docs]
def _mixture(self, eq, xi, par):
"""Apply mixing rules to individual parameters to get the mixture
parameters for EoS
Although it possible use any of available mixing rules, for now other
properties calculation as fugacity helmholtz free energy are defined
using the vdW mixing rules.
Parameters
----------
eq : str
codename of equation, PR, SRK...
xi : list
Molar fraction of component, [-]
par : list
list with individual parameters of equation, [-]
Returns
-------
mixpar : list
List with mixture parameters, [-]
"""
self.kij = self._Kij(eq)
mixpar = Mixing_Rule(xi, par, self.kij)
return mixpar
[docs]
def _Kij(self, eq):
return Kij(self.mezcla.ids, eq)
[docs]
def _Tr(self):
"""Definition of reducing parameters"""
if len(self.mezcla.componente) > 1:
# Mixture as one-fluid
Tr = 1
rhor = 1
else:
# Pure fluid
Tr = self.mezcla.Tc
rhor = 1/self.mezcla.Vc/1000 # m3/mol
return Tr, rhor
[docs]
def _phir(self, T, rho, xi):
Tr, rhor = self._Tr()
tau = Tr/T
delta = rho/rhor
a, b, d, e = self._GEOS(xi)
kw = self._da(tau, xi)
Tr, rhor = self._Tr()
kw["rhoc"] = rhor
kw["Tc"] = Tr
kw["Delta1"] = self.u
kw["Delta2"] = self.w
kw["bi"] = self.bi
kw["b"] = b
kw["a"] = a
kw["R"] = self.R
fir = CubicHelmholtz(tau, delta, **kw)
# print(self._excess(tau, delta, fir))
# print("fir: ", fir["fir"])
# print("fird: ", fir["fird"]*delta)
# print("firt: ", fir["firt"]*tau)
# print("firdd: ", fir["firdd"]*delta**2)
# print("firdt: ", fir["firdt"]*delta*tau)
# print("firtt: ", fir["firtt"]*tau**2)
# print("firddd: ", fir["firddd"]*delta**3)
# print("firddt: ", fir["firddt"]*delta**2*tau)
# print("firdtt: ", fir["firdtt"]*delta*tau**2)
# print("firttt: ", fir["firttt"]*tau**3)
# T = Tr/tau
# rho = rhor*delta
# print("P", (1+delta*fir["fird"])*R*T*rho)
# print(delta, fir["fird"], R, T, rho)
return fir
[docs]
def _excess(self, tau, delta, phir):
fir = phir["fir"]
fird = phir["fird"]
firt = phir["firt"]
firtt = phir["firtt"]
p = {}
p["Z"] = 1 + delta*fird
p["H"] = tau*firt + delta*fird
p["S"] = tau*firt - fir
p["cv"] = -tau**2*firtt
return p
[docs]
def _departure(self, a, b, d, e, TdadT, V, T):
"""Calculate departure function, Table 6-3 from [1]"""
Z = 1 + b/(V-b) - a*V/R_atml/T/(V**2+d*V+e)
# Numerador and denominator used in several expression
K = (d**2-4*e)**0.5
num = 2*V + d - K
den = 2*V + d + K
kw = {}
kw["Z"] = Z
if K:
kw["H"] = 1 - (a+TdadT)/R_atml/T/K*log(num/den) - Z
kw["S"] = TdadT/R_atml/K*log(num/den) - log(Z*(1-b/V))
kw["A"] = -a/R_atml/T/K*log(num/den) + log(Z*(1-b/V))
kw["f"] = a/R_atml/T/K*log(num/den) - log(Z*(1-b/V)) - (1-Z)
else:
kw["H"] = 1 - Z
kw["S"] = -log(Z*(1-b/V))
kw["A"] = log(Z*(1-b/V))
kw["f"] = -log(Z*(1-b/V)) - (1-Z)
return kw
# def _fug2(self, Z, xi):
# """Calculate partial fugacities coefficieint of components
# References
# ----------
# mollerup, Chap 2, pag 64 and so
# """
# V = Z*R_atml*self.T/self.P
# g = log(V-self.b) - log(V) # Eq 61
# f = 1/R_atml/self.b/(self.delta1-self.delta2) * \
# log((V+self.delta1*self.b)/(V+self.delta2*self.b)) # Eq 62
# gB = -1/(V-self.b) # Eq 80
# An = -g # Eq 75
# AB = -n*gB-D/self.T*fB # Eq 78
# AD = -f/self.T # Eq 79
# # Ch.3, Eq 66
# dAni = An+AB*Bi+AD*Di
# # Ch.2, Eq 13
# fi = dAni - log(Z)
# return fi