#!/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/>."""
###############################################################################
# library for distillation equipment calculation
# - Flash
# - Tower
# - ColumnFUG
###############################################################################
from math import exp, log, pi
import os
from numpy import linspace
from numpy.lib.scimath import log10
from scipy.optimize import fsolve
from tools.qt import translate
from equipment.parents import equipment
from equipment.heatExchanger import Heat_Exchanger
from lib import unidades
from lib.corriente import Corriente
from lib.plot import PlotDialog
[docs]
class Flash(equipment):
"""Class to model a flash separation equipment
Parameters:
entrada: Corriente instance to define the input stream to equipment
flash: Calculate method
0 - Use temperature and pressure of input stream
Cost:
orientacion:
0 - Horizontal
1 - Vertical
material:
0 - Carbon steel
1 - Stainless steel 304
2 - Stainless steel 316
3 - Carpenter 20CB-3
4 - Nickel 200
5 - Monel 400
6 - Inconel 600
7 - Incoloy 825
8 - Titanium
densidad: Material density
diametro: Equipment diameter
longitud: Height of equipment
espesor: Width of cover
cabeza: type of cover
0 - Ellipsoidal
1 - Hemispherical
2 - Bumped
3 - Flat
espesor_cabeza: Width of head of cover
reborde: longitud reborde
>>> kw = {"MEoS": True, "coolProp": True, "ids": [5, 6, 7, 8, 10]}
>>> entrada = Corriente(T=300, x=0.5, caudalMasico=0.01, \
fraccionMolar=[.3, 0.25, 0.15, 0.05, 0.25], **kw)
>>> flash = Flash(entrada=entrada)
"""
title = translate("equipment", "Flash Separator")
help = ""
kwargs = {
"entrada": None,
"flash": 0,
"f_install": 1.7,
"Base_index": 0.0,
"Current_index": 0.0,
"orientacion": 0,
"material": 0,
"densidad": 0.0,
"diametro": 0.0,
"longitud": 0.0,
"espesor": 0.0,
"cabeza": 0,
"espesor_cabeza": 0.0,
"reborde": 0.0}
kwargsInput = ("entrada", )
kwargsList = ("flash", "orientacion", "material", "cabeza")
kwargsValue = ("diametro", "longitud", "espesor", "espesor_cabeza",
"reborde")
calculateCostos = ("C_adq", "C_inst", "Peso", "Volumen")
indiceCostos = 3
TEXT_FLASH = [
translate("equipment", "Use P and T from input stream")]
TEXT_ORIENTATION = [translate("equipment", "Horizontal"),
translate("equipment", "Vertical")]
TEXT_MATERIAL = [
translate("equipment", "Carbon steel"),
translate("equipment", "Stainless steel 304"),
translate("equipment", "Stainless steel 316"),
translate("equipment", "Carpenter 20CB-3"),
translate("equipment", "Nickel 200"),
translate("equipment", "Monel 400"),
translate("equipment", "Inconel 600"),
translate("equipment", "Incoloy 825"),
translate("equipment", "Titanium")]
TEXT_HEAD = [
translate("equipment", "Ellipsoidal"),
translate("equipment", "Hemispherical"),
translate("equipment", "Bumped"),
translate("equipment", "Flat")]
@property
def isCalculable(self):
if self.kwargs["f_install"] and self.kwargs["Base_index"] and \
self.kwargs["Current_index"] and self.kwargs["diametro"] and \
self.kwargs["longitud"] and self.kwargs["espesor"]:
self.statusCoste = True
else:
self.statusCoste = False
if not self.kwargs["entrada"]:
self.msg = translate("equipment", "undefined input")
self.status = 0
return
self.msg = ""
self.status = 1
return True
[docs]
def calculo(self):
self.entrada = self.kwargs["entrada"]
if self.kwargs["flash"] == 0:
Vapor = self.entrada.clone(
fraccionMolar=self.entrada.Gas.fraccion,
caudalMasico=self.entrada.Gas.caudalmasico)
Liquido = self.entrada.clone(
fraccionMolar=self.entrada.Liquido.fraccion,
caudalMasico=self.entrada.Liquido.caudalmasico)
self.salida = [Vapor, Liquido]
self.Tout = Vapor.T
self.Pout = Vapor.P
self.VaporMolarFlow = Vapor.caudalmolar
self.VaporMassFlow = Vapor.caudalmasico
self.VaporVolFlow = Vapor.Q
self.VaporMolarComposition = Vapor.fraccion
self.VaporMassComposition = Vapor.fraccion_masica
self.LiquidMolarFlow = Liquido.caudalmolar
self.LiquidMassFlow = Liquido.caudalmasico
self.LiquidVolFlow = Liquido.Q
self.LiquidMolarComposition = Liquido.fraccion
self.LiquidMassComposition = Liquido.fraccion_masica
[docs]
def volumen(self):
self.Di = unidades.Length(0)
self.L = unidades.Length(0)
self.reborde = 0
self.espesor = 0
self.espesor_cabeza = 0
V_carcasa = pi/4*self.Di**2*self.L
if self.kwargs["cabeza"] == 0:
V_cabeza = 4./3*pi/8*self.Di**3
elif self.kwargs["cabeza"] == 1:
V_cabeza = 4./3*pi/8/2*self.Di**3
elif self.kwargs["cabeza"] == 2:
V_cabeza = 0.215483/2*self.Di**3
else:
V_cabeza = 0.
self.V = unidades.Volume(V_carcasa+V_cabeza)
[docs]
def peso(self):
W_carcasa = pi/4*(self.De**2-self.Di**2)*self.L*self.densidad
if self.kwargs["cabeza"] == 3:
W_cabeza = pi/4*self.Di**2*self.espesor_cabeza*self.densidad
else:
ratio = self.De/self.espesor_cabeza
if self.kwargs["cabeza"] == 0:
if ratio > 20:
hb = 1.24
else:
hb = 1.3
elif self.kwargs["cabeza"] == 2:
if ratio > 50:
hb = 1.09
elif ratio > 30:
hb = 1.11
else:
hb = 1.15
else:
if ratio > 30:
hb = 1.6
elif ratio > 18:
hb = 1.65
else:
hb = 1.70
Do = hb*self.De+2*self.reborde
W_cabeza = pi/4*Do**2*self.espesor_cabeza*self.densidad
self.W = unidades.Mass(W_carcasa+2*W_cabeza)
[docs]
def coste(self):
CI = self.kwargs["Current_index"]
BI = self.kwargs["Base_index"]
if self.kwargs["densidad"]:
self.densidad = unidades.Density(self.kwargs["densidad"])
else:
# Use density of Stainless Steel 304
self.densidad = unidades.Density(501, "lbft3")
self.Di = unidades.Length(self.kwargs["diametro"])
self.L = unidades.Length(self.kwargs["longitud"])
self.espesor = unidades.Length(self.kwargs["espesor"])
if self.kwargs["espesor_cabeza"]:
self.espesor_cabeza = unidades.Length(
self.kwargs["espesor_cabeza"])
else:
self.espesor_cabeza = self.espesor
self.reborde = unidades.Length(self.kwargs["reborde"])
self.De = unidades.Length(self.Di+2*self.espesor)
self.volumen()
self.peso()
ind_material = self.kwargs["material"]
Fm = [1., 1.7, 2.1, 3.2, 5.4, 3.6, 3.9, 3.7, 7.7][ind_material]
if self.kwargs["tipo"] == 0:
Cb = exp(8.571-0.233*log(self.W.lb)+0.04333*log(self.W.lb)**2)
Ca = 1370*self.Di.ft**0.2029
else:
Cb = exp(9.1-0.2889*log(self.W.lb)+0.04576*log(self.W.lb)**2)
Ca = 246*self.Di.ft**0.7396*self.L.ft**0.7068
C = Fm*Cb+Ca
self.C_adq = unidades.Currency(C*CI/BI)
self.C_inst = unidades.Currency(self.C_adq*self.kwargs["f_install"])
[docs]
def propTxt(self):
txt = "#---------------"
txt += translate("equipment", "Calculate properties")
txt += "-----------------#" + os.linesep
txt += self.propertiesToText(range(3)) + os.linesep
txt += self.propertiesToText(range(3, 6))
txt += translate("equipment", "Vapor Output Molar Composition") + os.linesep
for cmp, xi in zip(self.salida[0].componente, self.salida[0].fraccion):
txt += " %-21s\t %0.4f" % (cmp.nombre, xi) + os.linesep
txt += os.linesep
txt += self.propertiesToText(range(8, 11))
txt += translate("equipment", "Liquid Output Molar Composition") + os.linesep
for cmp, xi in zip(self.salida[1].componente, self.salida[1].fraccion):
txt += " %-21s\t %0.4f" % (cmp.nombre, xi) + os.linesep
return txt
[docs]
@classmethod
def propertiesEquipment(cls):
l = [(translate("equipment", "Mode"), ("TEXT_FLASH", "flash"), str),
(translate("equipment", "Output Temperature"), "Tout",
unidades.Temperature),
(translate("equipment", "Output Pressure"), "Pout", unidades.Pressure),
(translate("equipment", "Vapor Output Molar Flow"), "VaporMolarFlow",
unidades.MolarFlow),
(translate("equipment", "Vapor Output Mass Flow"), "VaporMassFlow",
unidades.MassFlow),
(translate("equipment", "Vapor Output Volumetric Flow"), "VaporVolFlow",
unidades.VolFlow),
(translate("equipment", "Vapor Output Molar Composition"),
"VaporMolarComposition", unidades.Dimensionless),
(translate("equipment", "Vapor Output Mass Composition"),
"VaporMassComposition", unidades.Dimensionless),
(translate("equipment", "Liquid Output Molar Flow"), "LiquidMolarFlow",
unidades.MolarFlow),
(translate("equipment", "Liquid Output Mass Flow"), "LiquidMassFlow",
unidades.MassFlow),
(translate("equipment", "Liquid Output Volumetric Flow"),
"LiquidVolFlow", unidades.VolFlow),
(translate("equipment", "Liquid Output Molar Composition"),
"LiquidMolarComposition", unidades.Dimensionless),
(translate("equipment", "Liquid Output Mass Composition"),
"LiquidMassComposition", unidades.Dimensionless)]
return l
[docs]
def propertiesListTitle(self, index):
"""Define los titulos para los popup de listas"""
l = [comp.nombre for comp in self.kwargs["entrada"].componente]
return l
[docs]
def writeStatetoJSON(self, state):
"""Write instance parameter to file"""
state["Tout"] = self.Tout
state["Pout"] = self.Pout
state["VaporMolarFlow"] = self.VaporMolarFlow
state["VaporVolFlow"] = self.VaporVolFlow
state["VaporMolarComposition"] = self.VaporMolarComposition
state["VaporMassComposition"] = self.VaporMassComposition
state["LiquidMolarFlow"] = self.LiquidMolarFlow
state["LiquidVolFlow"] = self.LiquidVolFlow
state["LiquidMolarComposition"] = self.LiquidMolarComposition
state["LiquidMassComposition"] = self.LiquidMassComposition
state["statusCoste"] = self.statusCoste
if self.statusCoste:
state["C_adq"] = self.C_adq
state["C_inst"] = self.C_inst
[docs]
def readStatefromJSON(self, state):
"""Load instance parameter from saved file"""
self.Tout = unidades.Temperature(state["Tout"])
self.Pout = unidades.Pressure(state["Pout"])
self.VaporMolarFlow = unidades.MassFlow(state["VaporMolarFlow"])
self.VaporVolFlow = unidades.VolFlow(state["VaporVolFlow"])
self.VaporMolarComposition = [
unidades.MolarFlow(p) for p in state["VaporMolarComposition"]]
self.VaporMassComposition = [
unidades.MassFlow(p) for p in state["VaporMassComposition"]]
self.LiquidMolarFlow = unidades.MolarFlow(state["LiquidMolarFlow"])
self.LiquidVolFlow = unidades.VolFlow(state["LiquidVolFlow"])
self.LiquidMolarComposition = [
unidades.MolarFlow(p) for p in state["LiquidMolarComposition"]]
self.LiquidMassComposition = [
unidades.MassFlow(p) for p in state["LiquidMassComposition"]]
self.statusCoste = state["statusCoste"]
if self.statusCoste:
self.C_adq = unidades.Currency(state["C_adq"])
self.C_inst = unidades.Currency(state["C_inst"])
self.salida = [None]
class Tower(equipment):
"""Class to define the common functionality of distillation units"""
title = translate("equipment", "Distillation Tower")
Condenser = Heat_Exchanger()
Reboiler = Heat_Exchanger()
calculateCostos = ("C_pisos", "C_carcasa", "C_accesorios", "C_col_adq",
"C_adq", "C_inst")
TEXT_PROCESS = [
translate("equipment", "Destillation"),
translate("equipment", "Absortion")]
TEXT_COLUMN = [
translate("equipment", "Tray column"),
translate("equipment", "Packed column")]
TEXT_MATERIAL = [
translate("equipment", "Carbon steel"),
translate("equipment", "Stainless steel 304"),
translate("equipment", "Stainless steel 316"),
translate("equipment", "Carpenter 20CB-3"),
translate("equipment", "Nickel 200"),
translate("equipment", "Monel 400"),
translate("equipment", "Inconel 600"),
translate("equipment", "Incoloy 825"),
translate("equipment", "Titanium")]
TEXT_TRAY = [
translate("equipment", "Valve tray"),
translate("equipment", "Grid tray"),
translate("equipment", "Bubble cap tray"),
translate("equipment", "Sieve tray")]
def isCalculable(self):
if self.kwargs["tipo"]:
if not self.kwargs["C_unitario"]:
self.statusCoste = False
return
if self.kwargs["Di"] and self.kwargs["h"] and self.kwargs["W"] and \
self.kwargs["Wb"]:
self.statusCoste = True
else:
self.statusCoste = False
def peso(self):
"""Calculate the weight of cover"""
rho = self.densidad
r = self.kwargs["h"]+0.8116*self.kwargs["Di"]
m = pi*self.kwargs["Di"]*r*0.5*(self.kwargs["Wb"]+self.kwargs["W"])*rho
return unidades.Mass(m)
def volumen(self):
"""Calculate the internal volume of column"""
return unidades.Volume(pi/4*self.kwargs["Di"]**2*self.kwargs["h"])
def McCabe(self, LK=0, HK=1):
A = self.kwargs["entrada"].componente[LK].nombre
B = self.kwargs["entrada"].componente[HK].nombre
P = self.kwargs["entrada"].P
Psat = dict()
Psat[A] = self.kwargs["entrada"].componente[LK].Pv
Psat[B] = self.kwargs["entrada"].componente[HK].Pv
Tsat = dict()
for i, s in enumerate(Psat.keys()):
Tsat[s] = lambda P, s=s: fsolve(
lambda T: Psat[s](T)-P,
self.kwargs["entrada"].componente[i].Tb)[0]
T = linspace(Tsat[A](P),Tsat[B](P))
x = lambda T: (P-Psat[B](T))/(Psat[A](T)-Psat[B](T))
y = lambda T: x(T)*Psat[A](T)/P
xB = 1-self.kwargs["HKsplit"]
xF = self.kwargs["entrada"].fraccion[LK]
xD = self.kwargs["LKsplit"]
Tbub = fsolve(lambda T:x(T) - xF,T[25])
yF = y(Tbub)
if self.kwargs["R"]:
R = self.kwargs["R"]
else:
Eslope = (xD-yF)/(xD-xF)
Rmin = Eslope/(1-Eslope)
R = self.kwargs["R_Rmin"]*Rmin
zF = xD-R*(xD-xF)/(R+1)
Sslope = (zF-xB)/(xF-xB)
S = 1/(Sslope-1)
xP = xD
yP = xD
dialog = PlotDialog()
dialog.plot.ax.grid(True)
dialog.plot.ax.set_title(
translate("equipment", "x-y Diagram for")
+ "{:s}/{:s} P={:s}".format(A, B, P.str), size="x-large")
dialog.plot.ax.set_xlabel(
translate("equipment", "Liquid Mole Fraction")
+ " {:s}".format(A), size="x-large")
dialog.plot.ax.set_ylabel(
translate("equipment", "Vapor Mole Fraction")
+ " {:s}".format(B), size="x-large")
dialog.plot.ax.set_xticks(linspace(0, 1.0, 21))
dialog.plot.ax.set_yticks(linspace(0.05, 1.0, 20))
dialog.addData([0, 1], [0, 1], 'b--')
dialog.addData(list(map(x, T)), list(map(y, T)))
dialog.addData([xB, xB], [0, xB], 'r--')
dialog.addData(xB, xB, 'ro', ms=5)
dialog.addText(xB+0.005, 0.02, 'xB = {:0.3f}'.format(float(xB)))
dialog.addData([xF, xF, xF], [0, xF, yF], 'r--')
dialog.addData([xF, xF], [xF, yF], 'ro', ms=5)
dialog.addText(xF+0.005, 0.02, 'xF = {:0.3f}'.format(float(xF)))
dialog.addData([xD, xD], [0, xD], 'r--')
dialog.addData(xD, xD, 'ro', ms=5)
dialog.addText(xD-0.005, 0.02, 'xD = {:0.3f}'.format(float(xD)),
ha="right")
dialog.addData([xD, xF], [xD, yF], 'r--')
dialog.addData([xD, xF], [xD, zF], 'r-')
dialog.addData([xB, xF], [xB, xB + (S+1)*(xF-xB)/S], 'r-')
nTray = 0
while xP > xB:
nTray += 1
Tdew = fsolve(lambda T: y(T) - yP, T[25])
xQ = xP
xP = x(Tdew)
dialog.addData([xQ, xP], [yP, yP], 'r')
dialog.addData(xP, yP, 'ro', ms=5)
dialog.addText(xP-0.03, yP, nTray)
yQ = yP
yP = min([xD - (R/(R+1))*(xD-xP), xB + ((S+1)/S)*(xP-xB)])
dialog.addData([xP, xP], [yQ, yP], 'r')
nTray -= 1
dialog.addText(0.1, 0.90, 'Rmin = {:0.2f}'.format(Rmin), size="large")
dialog.addText(0.1, 0.85, 'R = {:0.2f}'.format(R), size="large")
dialog.addText(0.1, 0.80, 'S = {:0.2f}'.format(S), size="large")
dialog.addText(0.1, 0.75, 'nTrays = {:d}'.format(nTray), size="large")
dialog.exec()
def coste(self):
self.tipo_pisos = self.kwargs.get("tipo_pisos", 0)
self.material_columna = self.kwargs.get("material_columna", 0)
self.material_pisos = self.kwargs.get("material_pisos", 0)
Di = unidades.Length(self.kwargs["Di"]).ft
L = unidades.Length(self.kwargs["L"]).ft
W = self.peso().lb
f1_id = self.kwargs["material_columna"]
f1 = [1., 1.7, 2.1, 3.2, 5.4, 3.6, 3.9, 3.7, 7.7][f1_id]
if self.kwargs["proceso"]:
# Destilación
Cb = exp(7.123 + 0.1478*log(W) + 0.02488*log(W)**2
+ 0.0158*L/Di*log(self.kwargs["Wb"]/self.kwargs["W"]))
Cp = 204.9*Di**0.6332*L**0.8016
else:
# Absorción
Cb = exp(6.629 + 0.1826*log(W) + 0.02297*log(W)**2)
Cp = 246.4*Di**0.7396*L**0.7068
if self.kwargs["tipo"] == 0:
# Columna de pisos
f2 = [1., 1.189+0.0577*Di, 1.401+0.0724*Di, 1.525+0.0788*Di, 1.,
2.306+0.112*Di, 1., 1., 1.][self.kwargs["material_pisos"]]
f3 = [1., 0.8, 1.59, 0.85][self.kwargs["tipo_pisos"]]
if self.NTray <= 20:
f4 = 2.25/1.0414**self.NTray
else:
f4 = 1.
Ci = f2*f3*f4*self.NTray*375.8*exp(0.1739*Di)
else:
# Columna de relleno
Ci = self.volumen()*self.kwargs["C_unitario"]
C = f1*Cb+Cp+Ci
C_adq = C*self.Current_index/self.Base_index
C_inst = C_adq*self.f_install
# This variable is used for the tray cost in tray column but too for
# packed in packed column
self.C_pisos = Ci
self.C_carcasa = f1*Cb
self.C_accesorios = Cp # other costs, stairs, platform...
self.C_col_adq = C_adq
self.C_col_inst = C_inst
self.C_adq = C_adq+self.caldera.C_adq+self.condensador.C_adq
self.C_inst = C_inst+self.caldera.C_inst+self.condensador.C_inst
[docs]
class ColumnFUG(Tower):
"""Simple distillation equipment using the simplified method of
Fenske-Underwood-Gilliland
Parameters:
entrada: Corriente instance to define the input to equipment
feed: Method to calculate the feed tray
0 - Kirkbride
1 - Fenske
LK: Index with light key component
LKsplit: Light key component required separation at top of equipment
HK: Index with heavy key component
HKsplit: Heavy key component required separation at bootom of equipment
condenser: Condenser type
0 - Total
1 - Partial
R: Reflux ratio
R_Rmin: Reflux ratio over the minimum ratio
Pd: Design presure for column, default pressure of input stream
DeltaP: Pressure loss in equipment
Cost:
proceso: Process type
0 - Absortion
1 - Distillation
tipo: Column type
0 - Tray column
1 - Torre de relleno
tipo_pisos: Tray type
0 - De válvula
1 - De rejilla
2 - De borboteo
3 - De tamiz
material_columna
0 - carbon steel
1 - Stainless steel 304
2 - Stainless steel 316
3 - Carpenter 20CB-3
4 - Nickel 200
5 - Monel 400
6 - Inconel 600
7 - Incoloy 825
8 - Titanium
material_pisos
0 - carbon steel
1 - Stainless steel 304
2 - Stainless steel 316
3 - Carpenter 20CB-3
4 - Nickel 200
5 - Monel 400
6 - Inconel 600
7 - Incoloy 825
8 - Titanium
C_unitario: Unitary cost of filler material, $/m³
Di: Internal diameter
h: Height
W: Cover width
Wb: Cover width at botom head
"""
title = translate("equipment", "Column (Shortcut method)")
kwargs = {"entrada": None,
"feed": 0,
"LK": 0,
"LKsplit": 0.0,
"HK": 0,
"HKsplit": 0.0,
"condenser": 0,
"R": 0.0,
"R_Rmin": 0.0,
"Pd": 0.0,
"DeltaP": 0.0,
"f_install": 3,
"Base_index": 0.0,
"Current_index": 0.0,
"proceso": 0,
"tipo": 0,
"tipo_pisos": 0,
"material_columna": 0,
"material_pisos": 0,
"C_unitario": 0.0,
"Di": 0.0,
"h": 0.0,
"W": 0.0,
"Wb": 0.0}
kwargsInput = ("entrada", )
kwargsValue = ("LKsplit", "HKsplit", "R", "R_Rmin", "Pd", "DeltaP")
kwargsList = ("feed", "LK", "HK", "condenser")
calculateValue = ("DutyCondenser", "DutyReboiler", "Rmin", "RCalculada",
"Nmin", "NTray", "N_feed")
indiceCostos = 3
TEXT_FEED = ["Kirkbride", "Fenske"]
TEXT_CONDENSER = [translate("equipment", "Total"),
translate("equipment", "Partial")]
[docs]
def cleanOldValues(self, **kwargs):
if "R" in kwargs:
self.kwargs["R_Rmin"] = 0.0
elif "R_Rmin" in kwargs:
self.kwargs["R_Rmin"] = 0.0
self.kwargs.update(kwargs)
@property
def isCalculable(self):
Tower.isCalculable(self)
self.statusMcCabe = True
if not self.kwargs["R"] and not self.kwargs["R_Rmin"]:
self.statusMcCabe = False
elif not self.kwargs["LKsplit"] or not self.kwargs["HKsplit"]:
self.statusMcCabe = False
if not self.kwargs["entrada"]:
self.msg = translate("equipment", "undefined input")
self.status = 0
return
if not self.kwargs["R"] and not self.kwargs["R_Rmin"]:
self.msg = translate("equipment", "undefined reflux ratio condition")
self.status = 0
return
if not self.kwargs["LKsplit"]:
self.msg = translate("equipment",
"undefined light key component recuperation in top product")
self.status = 0
return
if not self.kwargs["HKsplit"]:
self.msg = translate("equipment",
"undefined heavy key component recuperation in bottom product")
self.status = 0
return
if self.kwargs["LK"] == -1:
self.msg = translate("equipment", "undefined light key component")
self.status = 0
return
if self.kwargs["HK"] == -1:
self.msg = translate("equipment", "undefined heavy key component")
self.status = 0
return
if self.kwargs["HK"] <= self.kwargs["LK"]:
self.msg = translate("equipment", "key component bad specified")
self.status = 0
return
self.msg = ""
self.status = 1
return True
[docs]
def calculo(self):
self.entrada = self.kwargs["entrada"]
self.LKsplit = unidades.Dimensionless(self.kwargs["LKsplit"])
self.HKsplit = unidades.Dimensionless(self.kwargs["HKsplit"])
self.RCalculada = unidades.Dimensionless(self.kwargs["R"])
self.R_Rmin = unidades.Dimensionless(self.kwargs["R_Rmin"])
if self.kwargs["Pd"]:
self.Pd = unidades.Pressure(self.kwargs["Pd"])
else:
self.Pd = self.entrada.P
self.DeltaP = unidades.Pressure(self.kwargs["DeltaP"])
# Estimate splits of components
b = []
d = []
for i, caudal_i in enumerate(self.entrada.caudalunitariomolar):
if i == self.kwargs["LK"]:
b.append(caudal_i*(1-self.LKsplit))
d.append(caudal_i*self.LKsplit)
elif i == self.kwargs["HK"]:
d.append(caudal_i*(1-self.HKsplit))
b.append(caudal_i*self.HKsplit)
elif self.entrada.eos.Ki[i] > self.entrada.eos.Ki[self.kwargs["LK"]]:
b.append(0)
d.append(caudal_i)
elif self.entrada.eos.Ki[i] < self.entrada.eos.Ki[self.kwargs["HK"]]:
d.append(0)
b.append(caudal_i)
else:
d.append(caudal_i*0.5)
b.append(caudal_i*0.5)
while True:
bo = b
do = d
xt = [Q/sum(d) for Q in d]
xb = [Q/sum(b) for Q in b]
Qt = sum([di*cmp.M for di, cmp in zip(d, self.entrada.componente)])
Qb = sum([bi*cmp.M for bi, cmp in zip(b, self.entrada.componente)])
destilado = Corriente(T=self.entrada.T, P=self.Pd, caudalMasico=Qt, fraccionMolar=xt)
residuo = Corriente(T=self.entrada.T, P=self.Pd, caudalMasico=Qb, fraccionMolar=xb)
# TODO: Add algorithm to calculate Pd and condenser type fig 12.4 pag 230
# Fenske equation for Nmin
alfam = (destilado.eos.Ki[self.kwargs["LK"]]/destilado.eos.Ki[self.kwargs["HK"]]*residuo.eos.Ki[self.kwargs["LK"]]/residuo.eos.Ki[self.kwargs["HK"]])**0.5
Nmin = log10(destilado.caudalunitariomolar[self.kwargs["LK"]]/destilado.caudalunitariomolar[self.kwargs["HK"]] * residuo.caudalunitariomolar[self.kwargs["HK"]]/residuo.caudalunitariomolar[self.kwargs["LK"]]) / log10(alfam)
#Evaluación composición salidas
b = []
d = []
for i in range(len(self.entrada.ids)):
if i in [self.kwargs["LK"], self.kwargs["HK"]]:
b.append(bo[i])
d.append(do[i])
else:
alfa = (destilado.eos.Ki[i]/destilado.eos.Ki[self.kwargs["HK"]]*residuo.eos.Ki[i]/residuo.eos.Ki[self.kwargs["HK"]])**0.5
b.append(self.entrada.caudalunitariomolar[i]/(1+do[self.kwargs["HK"]]/bo[self.kwargs["HK"]]*alfa**Nmin))
d.append(self.entrada.caudalunitariomolar[i]*do[self.kwargs["HK"]]/bo[self.kwargs["HK"]]*alfa**Nmin/(1+do[self.kwargs["HK"]]/bo[self.kwargs["HK"]]*alfa**Nmin))
res = sum([abs(inicial-final) for inicial, final in zip(bo, b)]) + sum([abs(inicial-final) for inicial, final in zip(do, d)])
if res < 1e-10:
self.Nmin = Nmin-self.kwargs["condenser"]+1
break
# Calculo de la razón de reflujo mínima, ecuación de Underwood
alfa = self.entrada.eos.Ki[self.kwargs["LK"]]/self.entrada.eos.Ki[self.kwargs["HK"]]
self.Rmin = unidades.Dimensionless(abs(float(destilado.caudalmolar/self.entrada.caudalmolar*(destilado.fraccion[self.kwargs["LK"]]/self.entrada.Liquido.fraccion[self.kwargs["LK"]]-alfa*destilado.fraccion[self.kwargs["HK"]]/self.entrada.Liquido.fraccion[self.kwargs["HK"]])/(alfa-1))))
# Cálculo del número de etapas reales, ecuación de Gilliland
if self.R_Rmin and not self.RCalculada:
self.RCalculada = unidades.Dimensionless(self.R_Rmin*self.Rmin)
X = (self.RCalculada-self.Rmin)/(self.RCalculada+1)
Y = 1-exp((1+54.4*X)/(11+117.2*X)*(X-1)/X**0.5)
self.NTray = unidades.Dimensionless((Y+self.Nmin)/(1-Y)-1-self.kwargs["condenser"])
# Cálculo del piso de la alimentación
if self.kwargs["feed"]:
# Equation of Fenske
alfa_b = residuo.eos.Ki[self.kwargs["LK"]]/residuo.eos.Ki[self.kwargs["HK"]]
alfa_d = destilado.eos.Ki[self.kwargs["LK"]]/destilado.eos.Ki[self.kwargs["HK"]]
alfa_f = self.entrada.eos.Ki[self.kwargs["LK"]]/self.entrada.eos.Ki[self.kwargs["HK"]]
ratio = log(destilado.fraccion[self.kwargs["LK"]]/self.entrada.fraccion[self.kwargs["LK"]]*self.entrada.fraccion[self.kwargs["HK"]]/destilado.fraccion[self.kwargs["HK"]])/log(self.entrada.fraccion[self.kwargs["LK"]]/residuo.fraccion[self.kwargs["LK"]]*residuo.fraccion[self.kwargs["HK"]]/self.entrada.fraccion[self.kwargs["HK"]])*log((alfa_b*alfa_f)**0.5)/log((alfa_d*alfa_f)**0.5)
else:
# Equation of Kirkbride
ratio = (self.entrada.fraccion[self.kwargs["HK"]]/self.entrada.fraccion[self.kwargs["LK"]]*residuo.fraccion[self.kwargs["LK"]]**2/destilado.fraccion[self.kwargs["HK"]]**2*residuo.caudalmolar/destilado.caudalmolar)**0.206
self.Ns = self.NTray/(ratio+1)
self.Nr = self.NTray-self.Ns
self.N_feed = unidades.Dimensionless(self.Ns+1)
if self.kwargs["condenser"]:
# Partial condenser
Tout = destilado.eos._Dew_T()
else:
Tout = destilado.eos._Bubble_T()
Tin = destilado.eos._Dew_T()
SalidaDestilado = destilado.clone(T=Tout)
# FIXME: o el ejemplo está mal planteado o este valor es ilógico
ToutReboiler = residuo.eos._Bubble_T()
ToutReboiler2 = residuo.eos._Dew_T()
print(ToutReboiler, ToutReboiler2, Tin, Tout)
SalidaResiduo = residuo.clone(T=ToutReboiler)
self.salida = [SalidaDestilado, SalidaResiduo]
inCondenser = destilado.clone(T=Tin, P=self.entrada.P, split=self.RCalculada+1)
outCondenser = destilado.clone(T=Tout, P=self.entrada.P, split=self.RCalculada+1)
self.DutyCondenser = unidades.Power(outCondenser.h-inCondenser.h)
self.DutyReboiler = unidades.Power(SalidaDestilado.h+SalidaResiduo.h-self.DutyCondenser-self.entrada.h)
self.DestiladoT = SalidaDestilado.T
self.DestiladoP = SalidaDestilado.P
self.DestiladoMassFlow = SalidaDestilado.caudalmasico
self.DestiladoMolarComposition = SalidaDestilado.fraccion
self.ResiduoT = SalidaResiduo.T
self.ResiduoP = SalidaResiduo.P
self.ResiduoMassFlow = SalidaResiduo.caudalmasico
self.ResiduoMolarComposition = SalidaResiduo.fraccion
self.LKName = self.salida[0].componente[self.kwargs["LK"]].nombre
self.HKName = self.salida[0].componente[self.kwargs["HK"]].nombre
[docs]
def McCabe(self):
return Tower.McCabe(self, self.kwargs["LK"], self.kwargs["HK"])
[docs]
def propTxt(self):
txt="#---------------"+translate("equipment", "Calculate properties")+"-----------------#"+os.linesep
txt+=os.linesep+"%-25s\t%s" %(translate("equipment", "Top Output Temperature"), self.salida[0].T.str)+os.linesep
txt+="%-25s\t%s" %(translate("equipment", "Top Output Pressure"), self.salida[0].P.str)+os.linesep
txt+="%-25s\t%s" %(translate("equipment", "Top Output Mass Flow"), self.salida[0].caudalmolar.str)+os.linesep
txt+="#"+translate("equipment", "Top Output Molar Composition")+os.linesep
for componente, fraccion in zip(self.salida[0].componente, self.salida[0].fraccion):
txt+="%-25s\t %0.4f" %(componente.nombre, fraccion)+os.linesep
txt+=os.linesep+"%-25s\t%s" %(translate("equipment", "Bottom Output Temperature"), self.salida[1].T.str)+os.linesep
txt+="%-25s\t%s" %(translate("equipment", "Bottom Output Pressure"), self.salida[1].P.str)+os.linesep
txt+="%-25s\t%s" %(translate("equipment", "Bottom Output Mass Flow"), self.salida[1].caudalmolar.str)+os.linesep
txt+="#"+translate("equipment", "Bottom Output Molar Composition")+os.linesep
for componente, fraccion in zip(self.salida[1].componente, self.salida[1].fraccion):
txt+="%-25s\t %0.4f" %(componente.nombre, fraccion)+os.linesep
txt+=os.linesep+"%-25s\t %s" %(translate("equipment", "Feed Calculate method"), self.TEXT_FEED[self.kwargs["feed"]])+os.linesep
txt+="%-25s\t %s" %(translate("equipment", "Condenser type"), self.TEXT_CONDENSER[self.kwargs["condenser"]])+os.linesep
txt+="%-25s\t %s" %(translate("equipment", "Light Key Component"), self.salida[0].componente[self.kwargs["LK"]].nombre)+os.linesep
txt+="%-25s\t %s" %(translate("equipment", "Heavy Key Component"), self.salida[0].componente[self.kwargs["HK"]].nombre)+os.linesep
txt+="%-25s\t%s" %(translate("equipment", "Minimum Reflux Ratio"), self.Rmin.str)+os.linesep
txt+="%-25s\t%s" %(translate("equipment", "Reflux Ratio"), self.RCalculada.str)+os.linesep
txt+="%-25s\t%s" %(translate("equipment", "Stage Number"), self.NTray.str)+os.linesep
txt+="%-25s\t%s" %(translate("equipment", "Feed Stage"), self.N_feed.str)+os.linesep
txt+="%-25s\t%s" %(translate("equipment", "Condenser Duty"), self.DutyCondenser.str)+os.linesep
txt+="%-25s\t%s" %(translate("equipment", "Reboiler Duty"), self.DutyReboiler.str)+os.linesep
return txt
[docs]
@classmethod
def propertiesEquipment(cls):
l = [(translate("equipment", "Top Output Temperature"), "DestiladoT",
unidades.Temperature),
(translate("equipment", "Top Output Pressure"), "DestiladoP",
unidades.Pressure),
(translate("equipment", "Top Output Mass Flow"), "DestiladoMassFlow",
unidades.MassFlow),
(translate("equipment", "Top Output Molar Composition"),
"DestiladoMolarComposition", unidades.Dimensionless),
(translate("equipment", "Bottom Output Temperature"), "ResiduoT",
unidades.Temperature),
(translate("equipment", "Bottom Output Pressure"), "ResiduoP",
unidades.Pressure),
(translate("equipment", "Bottom Output Mass Flow"), "ResiduoMassFlow",
unidades.MassFlow),
(translate("equipment", "Bottom Output Molar Composition"),
"ResiduoMolarComposition", unidades.Dimensionless),
(translate("equipment", "Feed Calculate method"),
("TEXT_FEED", "feed"), str),
(translate("equipment", "Condenser type"),
("TEXT_CONDENSER", "condenser"), str),
(translate("equipment", "Light Key Component"), "LKName", str),
(translate("equipment", "Heavy Key Component"), "HKName", str),
(translate("equipment", "Minimum Reflux Ratio"), "Rmin",
unidades.Dimensionless),
(translate("equipment", "Reflux Ratio"), "RCalculada",
unidades.Dimensionless),
(translate("equipment", "Stage Number"), "NTray", unidades.Dimensionless),
(translate("equipment", "Feed Stage"), "N_feed", unidades.Dimensionless),
(translate("equipment", "Condenser Duty"), "DutyCondenser", unidades.Power),
(translate("equipment", "Reboiler Duty"), "DutyReboiler", unidades.Power)]
return l
[docs]
def propertiesListTitle(self, index):
"""Define los titulos para los popup de listas"""
lista = [comp.nombre for comp in self.kwargs["entrada"].componente]
return lista
def batch():
# Plugging-in contant values
D = 10
a = 2.41
W_int = 100
x_w_int = 0.5
t = list(range(0, 6, 1))
# Defining function for integration
def batch(x_w, t):
W_t = -D*t + W_int
y = a*x_w/ (1.0 + x_w*(a-1.0))
dx_wdt = -(D/W_t)*(y - x_w)
return dx_wdt
x_w_sol = odeint(batch, x_w_int, t)
# Plotting the solution
figure()
plot(t, x_w_sol, '-o')
xlabel('Time, hours')
ylabel('x_w')
grid()
plt.show()
if __name__ == '__main__':
# import doctest
# doctest.testmod()
# kw = {"MEoS": True, "coolprop": True, "ids": [5, 6, 7, 8, 10]}
# entrada = Corriente(T=300, x=0.5, caudalMasico=0.01,
# fraccionMolar=[.3, 0.25, 0.05, 0.15, 0.25], **kw)
# columna=ColumnFUG(entrada=entrada, LK=2, LKsplit=0.9, HK=3, HKsplit=0.9, R_Rmin=1.05, calc_feed=0, DeltaP=0.1)
# print columna.Rmin
# print columna.DutyCondenser.MJh, columna.DutyReboiler.MJh
# blend=Corriente(T=340, P=101325, caudalMasico=1, fraccionMolar=[0.3, 0.5, 0.05, 0.15])
# columna=ColumnFUG(entrada=blend, LK=0, LKsplit=0.9866, HK=3, HKsplit=0.639, R_Rmin=1.1, feed=0)
# P = unidades.Pressure(315, "psi")
# blend=Corriente(T=T, P=P, caudalMasico=1, fraccionMolar=[0.26, 0.09, 0.25, 0.17, 0.11, 0.12])
# columna=ColumnFUG(entrada=blend, LK=0, LKsplit=0.96666, HK=3, HKsplit=0.95, R_Rmin=1.2)
# print columna.DutyCondenser
# print((blend.mezcla.ids))
# kw = {"MEoS": True, "coolProp": True}
# entrada = Corriente(T=300, x=0.5, caudalMasico=0.01, ids=[5, 6, 7, 8],
# fraccionMolar=[.3, 0.5, 0.05, 0.15], **kw)
# from scipy import *
# from scipy.integrate import odeint
# from pylab import *
# import matplotlib.pyplot as plt
# batch()
kw = {"MEoS": True, "coolProp": False, "ids": [5, 6, 7, 8, 10]}
entrada = Corriente(T=300, x=0.5, caudalMasico=0.01,
fraccionMolar=[.3, 0.25, 0.15, 0.05, 0.25], **kw)
flash = Flash(entrada=entrada)