#!/usr/bin/python3
# -*- coding: utf-8 -*-
'''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/>.'''
from numpy import exp
from numpy.lib.scimath import log
from scipy.optimize import fsolve
from lib import unidades
from lib.utilities import refDoc
__doi__ = {
1:
{"autor": "Rachford, H.H., Rice, J.D.",
"title": "Procedure for Use of Electronic Digital Computers in "
"Calculating Flash Vaporization Hydrocarbon Equilibrium",
"ref": "Petroleum Transactions, AIME 195 (1952) 327-328",
"doi": "10.2118/952327-G"},
2:
{"autor": "Peng, D.-Y.",
"title": "Accelerated Successive Substitution Schemes for "
"Bubble-Point and Dew-Point Calculations",
"ref": "Can. J. Chem. Eng. 69(4) (1991) 978-985",
"doi": "10.1002/cjce.5450690421"},
3:
{"autor": "",
"title": "",
"ref": "",
"doi": ""},
}
# TODO: Add UI configuration support for specific parameters
# Grayson-Streed: Flory option
# SRK: alpha option
# PR: alpha option
# PRMathiasCopeman: alpha option
# BWRS: extended option
# virial: B, C, method configuration
[docs]
@refDoc(__doi__, [1])
class EoS(object):
"""Base class for equation of state modeling, define common functionality
as LV flash algorithm"""
[docs]
def __init__(self, T, P, mezcla, **kwargs):
self.T = unidades.Temperature(T)
self.P = unidades.Pressure(P)
self.mezcla = mezcla
self.componente = mezcla.componente
self.zi = mezcla.fraccion
self.kwargs = kwargs
[docs]
def _fug(self, xi, yi, T, P):
"""Each child class with liquid-vapor equilibrium support must define
this procedure to do the flash iteration, it return fugacity
coefficient for both phases"""
# If this method if executed the child class don't define it
# So raise NotImplementedError to let user to know that
msg = "Derived class %s don't define Liquid-Vapor fugacities" % (
self.__class__.__name__)
raise NotImplementedError(msg)
[docs]
def _Flash(self):
"""Calculation K values for liquid-vapour phase equilibrium
Naji, H.S.
Conventional and Rapid Flash Calculations for the Soave-Redlich-Kwong
and Peng-Robinson Equations of State
Emirates J. Eng. Res. 13(3) (2008) 81-91
"""
# Initial estimation using Wilson correlation, Eq 19
Ki = []
for c in self.componente:
Ki.append(c.Pc/self.P*exp(5.37*(1.+c.f_acent)*(1.-c.Tc/self.T)))
# Rachford-Rice equation, [1]_
def RR(q):
f = 0
for zi, ki in zip(self.zi, Ki):
f += zi*(1-ki)/(1+q*(ki-1))
return f
if RR(0) > 0 and RR(1) > 0:
# q>1, superheated gas
xi = self.zi
yi = self.zi
q = 1
Zv = self._Z(self.zi, self.T, self.P)[-1]
Zl = None
elif RR(0) < 0 and RR(1) < 0:
# q<0, subcooled liquid
xi = self.zi
yi = self.zi
q = 0
Zl = self._Z(self.zi, self.T, self.P)[0]
Zv = None
else:
q = 0.5
while True:
qo = q
solucion = fsolve(RR, q, full_output=True)
if solucion[2] != 1:
break
else:
q = solucion[0][0]
xi = []
yi = []
for zi, ki in zip(self.zi, Ki):
xi.append(zi/(1+q*(ki-1)))
yi.append(zi*ki/(1+q*(ki-1)))
tital, titav = self._fug(xi, yi, self.T, self.P)
fiv = [z*t*self.P for z, t in zip(yi, titav)]
fil = [z*t*self.P for z, t in zip(xi, tital)]
# criterio de convergencia Eq 21
err = sum([abs(l/v-1) for l, v in zip(fil, fiv)])
if err < 1e-12 and (q-qo)**2 < 1e-15:
break
else:
Ki = [l/v for l, v in zip(tital, titav)]
Zl = self._Z(xi, self.T, self.P)[0]
Zv = self._Z(yi, self.T, self.P)[-1]
return q, Zl, Zv, xi, yi, Ki
[docs]
def _Bubble_T(self, P):
"""Calculation Bubble Point Temperature"""
# Initial estimation of temperature
T = 0
for xi, cmp in zip(self.zi, self.componente):
Ti = cmp.Tc/(1-3*log(P/cmp.Pc)/(log(10)*(7+7*cmp.f_acent)))
T += Ti*xi
# Initial estimation of K using inverted Wilson correlation
Ki = []
for c in self.componente:
Ki.append(c.Pc/P*exp(5.37*(1.+c.f_acent)*(1.-c.Tc/T)))
yi = [k*x for k, x in zip(Ki, self.zi)]
ym = sum(yi)
yi = [y/ym for y in yi]
def f():
val = 0
for zi, ki in zip(self.zi, Ki):
val += zi*ki
return val - 1
c = 0
while True:
c += 1
tital, titav = self._fug(self.zi, yi, T, P)
Ki = [l/v for l, v in zip(tital, titav)]
if abs(f()) <= 1e-9:
find = 1
break
else:
yi = [k*x for k, x in zip(Ki, self.zi)]
ym = sum(yi)
yi = [y/ym for y in yi]
# Estimating next temperature value from [2]_, eq 11
s = 0
for cmp, x, k in zip(self.componente, self.zi, Ki):
s += (1+cmp.f_acent)*cmp.Tc*x*k
ed = T/5.373/s
T *= (1-ed*f())
if c > 500:
print("reach limit iteration count")
find = 0
break
return T, find
[docs]
def _Dew_T(self, P):
"""Calculation Dew Point Temperature"""
# Initial estimation of temperature
T = 0
for xi, cmp in zip(self.zi, self.componente):
Ti = cmp.Tc/(1-3*log(P/cmp.Pc)/(log(10)*(7+7*cmp.f_acent)))
T += Ti*xi
# Initial estimation of K using inverted Wilson correlation
Ki = []
for c in self.componente:
Ki.append(c.Pc/P*exp(5.37*(1.+c.f_acent)*(1.-c.Tc/T)))
xi = [k/y for k, y in zip(Ki, self.zi)]
xm = sum(xi)
xi = [x/xm for x in xi]
def f():
val = 1
for zi, ki in zip(self.zi, Ki):
val -= zi/ki
return val
c = 0
while True:
c += 1
tital, titav = self._fug(xi, self.zi, T, P)
# print(xi, tital, titav)
Ki = [l/v for l, v in zip(tital, titav)]
if abs(f()) <= 1e-9:
find = 1
break
else:
xi = [k/y for k, y in zip(Ki, self.zi)]
xm = sum(xi)
xi = [x/xm for x in xi]
# Estimating next temperature value from [2]_, eq 13
s = 0
for cmp, x, k in zip(self.componente, self.zi, Ki):
s += (1+cmp.f_acent)*cmp.Tc*x/k
ed = T/5.373/s
T *= (1-ed*f())
if c > 500:
print("reach limit iteration count")
find = 0
break
return T, find
[docs]
def _Bubble_P(self, T):
"""Calculation Bubble Point Pressure"""
# Initial estimation of pressure
P = 0
for xi, cmp in zip(self.zi, self.componente):
Pi = cmp.Pc*exp(5.373*(1+cmp.f_acent)*(1.-cmp.Tc/T))
if T <= cmp.Tc:
P += Pi*xi
else:
P += (Pi*cmp.Pc)**0.5*xi
# Initial estimation of K using inverted Wilson correlation
Ki = []
for c in self.componente:
Ki.append(c.Pc/P*exp(5.37*(1.+c.f_acent)*(1.-c.Tc/T)))
yi = [k*x for k, x in zip(Ki, self.zi)]
def f():
val = 0
for zi, ki in zip(self.zi, Ki):
val += zi*ki
return val - 1
c = 0
while True:
c += 1
tital, titav = self._fug(self.zi, yi, T, P)
Ki = [l/v for l, v in zip(tital, titav)]
if abs(f()) <= 1e-9:
find = 1
break
else:
yi = [k*x for k, x in zip(Ki, self.zi)]
ym = sum(yi)
yi = [y/ym for y in yi]
# Estimating next temperature value from [2]_, eq 8
s = sum([zi*ki for zi, ki in zip(self.zi, Ki)])
P *= 2-1*s
if c > 100:
print("reach limit iteration count")
find = 0
break
return P, find
[docs]
def _Dew_P(self, T):
"""Calculation Dew Point Pressure"""
# Initial estimation of pressure
P = 0
for xi, cmp in zip(self.zi, self.componente):
Pi = cmp.Pc*exp(5.373*(1+cmp.f_acent)*(1-cmp.Tc/T))
if T <= cmp.Tc:
P += xi/Pi
else:
P += xi/(Pi*cmp.Pc)**0.5
P = 1/P
# Initial estimation of K using inverted Wilson correlation
Ki = []
for c in self.componente:
Ki.append(c.Pc/P*exp(5.373*(1+c.f_acent)*(1-c.Tc/T)))
xi = [k/y for k, y in zip(Ki, self.zi)]
xm = sum(xi)
xi = [x/xm for x in xi]
def f():
val = 1
for zi, ki in zip(self.zi, Ki):
val -= zi/ki
return val
c = 0
while True:
c += 1
tital, titav = self._fug(xi, self.zi, T, P)
# print(xi, tital, titav)
Ki = [l/v for l, v in zip(tital, titav)]
if abs(f()) <= 1e-9:
find = 1
break
else:
xi = [k/y for k, y in zip(Ki, self.zi)]
xm = sum(xi)
xi = [x/xm for x in xi]
# Estimating next temperature value from [2]_, eq 9
s = sum([zi/ki for zi, ki in zip(self.zi, Ki)])
P /= s
if c > 100:
print("reach limit iteration count")
find = 0
break
return P, find
[docs]
def envelope(self):
# TODO: Implement algorithm
from numpy import logspace
P = logspace(6, 7, 50)
Pd = []
dew = []
Pb = []
bubble = []
for p in P:
try:
print(p)
Td, success = self._Dew_T(p)
if success:
Pd.append(p)
dew.append(Td)
Tb, success = self._Bubble_T(p)
if success:
Pb.append(p)
bubble.append(Tb)
except:
pass
from pylab import plot, show
plot(dew, Pd, "o")
plot(bubble, Pb, "x")
# yscale("log")
show()