#!/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 flux equipment definition
# * Divider: Simple divider equipment
# * Mixer: Simple mixer equipment
# * Valve: Simple valve equipment
###############################################################################
import os
from scipy.optimize import fsolve
from equipment.parents import equipment
from lib import unidades
from lib.corriente import Corriente
from tools.qt import translate
[docs]
class Divider(equipment):
"""Class to define a simple divider equipment, only splliting input stream
Parameters:
entrada: Corriente instance to define de input stream
salidas: Number of output streams
criterio: split ratio
0 - fractions, ratio of input stream in each output stream
1 - molar flow. Overwrite the input stream molar flow.
fracciones: array with the split values, it depend of criterio,
fractions of input stream or molar flow in output streams
deltaP: Pressure loss in equipment, optional
>>> agua=Corriente(T=300, P=101325, caudalMasico=1, ids=[62],
fraccionMasica=[1])
>>> divisor=Divider(entrada=agua, criterio=0, salidas=3,
split=[0.7, 0.2, 0.1])
>>> print(divisor.entrada.caudalmasico.kgh)
3600.0
>>> for salida in divisor.salida: print("%0.1f" % salida.caudalmasico.kgh)
2520.0
720.0
360.0
"""
title = translate("equipment", "Divider")
kwargs = {"entrada": None,
"criterio": 0,
"salidas": 0,
"split": [],
"deltaP": 0.0}
kwargsInput = ("entrada", )
kwargsList = ("criterio", )
kwargsValue = ("deltaP", )
TEXT_CRITERIO = [
translate("equipment", "Flux ratio"),
translate(
"equipment", "Flowrate (overwrite input flow)")]
@property
def isCalculable(self):
"""Check equipment input parameter
Mandatory parameter:
entrada, split
Incompatibilities:
salidas must be equal to len of split
"""
# Auto select split for trivial divider with one output stream
if self.kwargs["salidas"] == 1:
self.kwargs["split"] = [1.]
if not self.kwargs["entrada"]:
self.msg = translate(
"equipment", "undefined input")
self.status = 0
elif not self.kwargs["split"]:
self.msg = translate(
"equipment", "undefined split fraction")
self.status = 0
elif self.kwargs["salidas"] != len(self.kwargs["split"]):
self.msg = translate(
"equipment", "incomplete split fraction")
self.status = 0
else:
self.status = 1
self.msg = ""
return True
[docs]
def calculo(self):
"""Calculate procedure, only a mass balance"""
self.entrada = self.kwargs["entrada"]
self.criterio = self.kwargs["criterio"]
self.split = [unidades.Dimensionless(i) for i in self.kwargs["split"]]
self.deltaP = unidades.DeltaP(self.kwargs["deltaP"])
# Normalize fractions
if self.criterio == 0:
if sum(self.split) != 1:
fracciones_normalizadas = []
total = sum(self.split)
for i in self.split:
fracciones_normalizadas.append(i/total)
self.split = fracciones_normalizadas
# Calculate output stream
self.salida = []
if self.criterio == 0:
for i in self.split:
self.salida.append(self.entrada.clone(
P=self.entrada.P-self.deltaP, split=i))
else:
self.entrada = self.entrada.clone(caudalMasico=sum(self.split))
for i in self.split:
self.salida.append(self.entrada.clone(
P=self.entrada.P-self.deltaP, split=i))
# Calculate other properties
self.inputMolarFlow = self.entrada.caudalmolar
self.inputMassFlow = self.entrada.caudalmasico
self.inputVolFlow = self.entrada.Q
self.inputT = self.entrada.T
self.inputP = self.entrada.P
self.output = unidades.Dimensionless(self.kwargs["salidas"])
[docs]
def propTxt(self):
"""Text format for report"""
txt = "#---------------"
txt += translate(
"equipment", "Calculate properties")
txt += "-----------------#"+os.linesep
txt += self.propertiesToText(0)
for i, salida in enumerate(self.salida):
txt += "%-25s\t%s" % (
translate(
"equipment", "Output stream")+str(i),
salida.caudalmolar.str)+os.linesep
txt += os.linesep
txt += self.propertiesToText(1)
for i, salida in enumerate(self.salida):
txt += "%-25s\t%s" % (
translate(
"equipment", "Output stream")+str(i),
salida.caudalmasico.str)+os.linesep
txt += os.linesep
txt += self.propertiesToText(2)
for i, salida in enumerate(self.salida):
txt += "%-25s\t%s" % (
translate(
"equipment", "Output stream")+str(i),
salida.Q.str)+os.linesep
txt += os.linesep
txt += self.propertiesToText(7)
return txt
[docs]
@classmethod
def propertiesEquipment(cls):
"""Properties availables to show in report"""
l = [(translate("equipment", "Feed Molar Flow"),
"inputMolarFlow", unidades.MolarFlow),
(translate("equipment", "Feed Mass Flow"),
"inputMassFlow", unidades.MassFlow),
(translate(
"equipment", "Feed Volumetric Flow"),
"inputVolFlow", unidades.VolFlow),
(translate("equipment", "Feed Temperature"),
"inputT", unidades.Temperature),
(translate("equipment", "Feed Pressure"),
"inputP", unidades.Pressure),
(translate(
"equipment", "Number of Product Streams"),
"output", unidades.Dimensionless),
(translate("equipment", "Split Fractions"),
"split", unidades.Dimensionless),
(translate("equipment", "Pressure Loss"),
"deltaP", unidades.DeltaP)]
return l
[docs]
def propertiesListTitle(self, index):
"""Titles of properties of type list"""
lista = []
for i in range(self.kwargs["salidas"]):
lista.append("Output %i" % (i+1))
return lista
[docs]
def writeStatetoJSON(self, state):
"""Write instance parameter to file"""
state["criterio"] = self.criterio
state["split"] = self.split
state["deltaP"] = self.deltaP
state["inputMolarFlow"] = self.inputMolarFlow
state["inputMassFlow"] = self.inputMassFlow
state["inputVolFlow"] = self.inputVolFlow
state["inputT"] = self.inputT
state["inputP"] = self.inputP
state["output"] = self.output
[docs]
def readStatefromJSON(self, state):
"""Load instance parameter from saved file"""
self.criterio = state["criterio"]
self.split = [unidades.Dimensionless(x) for x in state["split"]]
self.deltaP = unidades.DeltaP(state["deltaP"])
self.inputMolarFlow = unidades.MolarFlow(state["inputMolarFlow"])
self.inputMassFlow = unidades.MassFlow(state["inputMassFlow"])
self.inputVolFlow = unidades.VolFlow(state["inputVolFlow"])
self.inputT = unidades.Temperature(state["inputT"])
self.inputP = unidades.Pressure(state["inputP"])
self.output = unidades.Dimensionless(state["output"])
self.salida = [None]*self.kwargs["salidas"]
[docs]
def ajustState(self, stream):
print(self.kwargs)
[docs]
class Mixer(equipment):
"""Class to define a simple stream mixer, only apply a mass balance and
specified the output pressure
Parameters
n_entradas: Input number
entrada: Corriente instance to define a input stream to equipment,
if use a list of them, it fix all the input stream
id_entrada: id of defined input
criterio: mode of define output pressure
0 - Minimum pressure of input streams
1 - Mean pressure of input streams
2 - Specified pressure, must be input in Pout parameter
Pout: Output pressure
>>> agua=Corriente(T=300, P=101325, caudalMasico=1, ids=[62],
fraccionMasica=[1.])
>>> agua2=Corriente(T=350, P=101325, caudalMasico=5, ids=[62],
fraccionMasica=[1.])
>>> mezclador=Mixer(entrada=[agua, agua2], criterio=0)
>>> print(mezclador.salida[0].T)
341.6818851215901
"""
title = translate("equipment", "Mixer")
help = ""
kwargs = {"entrada": [],
"id_entrada": 0,
"criterio": 0,
"Pout": 0.0}
kwargs_forbidden = ["entrada", "id_entrada"]
kwargsValue = ("Pout", )
kwargsList = ("criterio", )
TEXT_METODO = [
translate("equipment", "Inputs minimum pressure"),
translate("equipment", "Inputs mean pressure"),
translate("equipment", "Custom")]
@property
def isCalculable(self):
"""Check equipment input parameter
Mandatory parameter:
entrada
Pout if criterio is setted to 2
Incompatibilities:
All corriente instance in entrada must be defined fine
salidas must be equal to len of split
Warnings:
Some input stream have a warning status definitions
"""
if self.kwargs["criterio"] == 2 and not self.kwargs["Pout"]:
self.msg = translate(
"equipment", "pressure output not defined")
self.status = 0
elif not self.kwargs["entrada"]:
self.msg = translate(
"equipment", "undefined input")
self.status = 0
elif sum([s.status for s in self.kwargs["entrada"]]) == 0:
self.msg = translate(
"equipment", "undefined input")
self.status = 0
elif len(self.kwargs["entrada"]) != sum(
[s.status for s in self.kwargs["entrada"]]):
self.msg = translate(
"equipment", "some input stream isn't defined")
self.status = 3
return True
else:
self.msg = ""
self.status = 1
return True
[docs]
def cleanOldValues(self, **kwargs):
"""Clean incompatible kwargs parameters
New defined entrada delete old entrada
Entrada as list not need the id_entrada
"""
if "entrada" in kwargs:
if isinstance(kwargs["entrada"], list):
kwargs["id_entrada"] = None
else:
corriente = kwargs["entrada"]
kwargs["entrada"] = self.kwargs["entrada"][:]
while len(kwargs["entrada"]) < kwargs["id_entrada"]+1:
kwargs["entrada"].append(Corriente())
kwargs["entrada"][kwargs["id_entrada"]] = corriente
kwargs["id_entrada"] = None
self.kwargs.update(kwargs)
[docs]
def calculo(self):
"""Calculate procedure, only a mass balance"""
self.entrada = self.kwargs["entrada"]
self.criterio = self.kwargs["criterio"]
# Define output pressure
if self.criterio == 2:
Pout = self.kwargs["Pout"]
else:
lst = []
for entrada in self.entrada:
if entrada.status:
lst.append(entrada.P)
if self.criterio == 0:
Pout = min(lst)
else:
Pout = sum(lst, 0.0) / len(lst)
self.Pout = unidades.Pressure(Pout)
# Heat balance for calculate output temperature
h_in = 0
To = 0
massUnitFlow = [0]*len(self.entrada[0].fraccion)
for entrada in self.entrada:
if entrada.status:
h_in += entrada.h
To += entrada.T*entrada.caudalmasico
for i, caudal in enumerate(entrada.caudalunitariomasico):
massUnitFlow[i] += caudal
To /= sum(massUnitFlow)
def f(T):
output = Corriente(
T=T, P=self.Pout, caudalUnitarioMasico=massUnitFlow)
return output.h-h_in
T = fsolve(f, To)[0]
# TODO: Add solid mixer capability
if self.entrada[0].solido:
pass
salida = Corriente(T=T, P=self.Pout, caudalUnitarioMasico=massUnitFlow)
self.salida = [salida]
# Calculate other properties
self.outT = salida.T
self.outX = salida.x
self.outMolarFlow = salida.caudalmolar
self.outMassFlow = salida.caudalmasico
self.outVolFlow = salida.Q
[docs]
def propTxt(self):
"""Text format for report"""
txt = "#---------------"
txt += translate(
"equipment", "Calculate properties")
txt += "-----------------#"+os.linesep
txt += self.propertiesToText(range(3))
txt += os.linesep
txt += self.propertiesToText(3)
for i, entrada in enumerate(self.kwargs["entrada"]):
txt += "%-25s\t%s" % (
translate(
"equipment", "Input stream")+str(i),
entrada.caudalmolar.str)+os.linesep
txt += os.linesep
txt += self.propertiesToText(4)
for i, entrada in enumerate(self.kwargs["entrada"]):
txt += "%-25s\t%s" % (
translate(
"equipment", "Input stream")+str(i),
entrada.caudalmasico.str)+os.linesep
txt += os.linesep
txt += self.propertiesToText(5)
for i, entrada in enumerate(self.kwargs["entrada"]):
txt += "%-25s\t%s" % (
translate(
"equipment", "Input stream")+str(i),
entrada.Q.str)+os.linesep
txt += os.linesep+"#"
txt += translate(
"equipment", "Output Molar Composition")
txt += os.linesep
for comp, x in zip(self.salida[0].componente, self.salida[0].fraccion):
txt += "%-25s\t %0.4f" % (comp.nombre, x)+os.linesep
return txt
[docs]
@classmethod
def propertiesEquipment(cls):
"""Properties availables to show in report"""
l = [(translate("equipment", "Output Temperature"),
"outT", unidades.Temperature),
(translate("equipment", "Output Pressure"),
"Pout", unidades.Pressure),
(translate("equipment", "Output vapor fraction"),
"outX", unidades.Dimensionless),
(translate("equipment", "Output Molar Flow"),
"outMolarFlow", unidades.MolarFlow),
(translate("equipment", "Output Mass Flow"),
"outMassFlow", unidades.MassFlow),
(translate("equipment", "Output Volumetric Flow"),
"outVolFlow", unidades.VolFlow)]
return l
[docs]
def writeStatetoJSON(self, state):
"""Write instance parameter to file"""
state["criterio"] = self.criterio
state["Pout"] = self.Pout
state["outT"] = self.outT
state["outX"] = self.outX
state["outMolarFlow"] = self.outMolarFlow
state["outMassFlow"] = self.outMassFlow
state["outVolFlow"] = self.outVolFlow
[docs]
def readStatefromJSON(self, state):
"""Load instance parameter from saved file"""
self.criterio = state["criterio"]
self.Pout = unidades.Pressure(state["Pout"])
self.outT = unidades.Temperature(state["outT"])
self.outX = unidades.Dimensionless(state["outX"])
self.outMolarFlow = unidades.MolarFlow(state["outMolarFlow"])
self.outMassFlow = unidades.MassFlow(state["outMassFlow"])
self.outVolFlow = unidades.VolFlow(state["outVolFlow"])
self.salida = [None]
[docs]
class Valve(equipment):
"""Class to define a simple valve, only output pressure calculation
Parameters:
entrada: Instance of Corriente to define the valve input stream
off: state of valve
0 - open
1 - open partially
2 - closed
Pout: Output pressure
DeltaP: Pressure loss in equipment
Dew: Dew point temperature
Bubble: Bubble point temperature
>>> agua=Corriente(T=300, P=202650, caudalMasico=1, ids=[62],
fraccionMasica=[1])
>>> valvula=Valve(entrada=agua, Pout=101325, off=1)
>>> print(agua.P.atm, valvula.salida[0].P.atm)
2.0 1.0
>>> valvula(DeltaP=2650)
>>> print(valvula.salida[0].P.atm)
1.9738465334320257
>>> valvula(off=0)
>>> print(valvula.salida[0].P.atm)
2.0
"""
title = translate("equipment", "Valve")
help = ""
kwargs = {"entrada": None,
"off": 0,
"Pout": 0.0,
"DeltaP": 0.0,
"Dew": 0.0,
"Bubble": 0.0}
kwargsInput = ("entrada", )
kwargsValue = ("Pout", "DeltaP", "Dew", "Bubble")
kwargsList = ("off", )
TEXT_WORKING = [
translate("equipment", "Totally open"),
translate("equipment", "Partially open"),
translate("equipment", "Close")]
@property
def isCalculable(self):
"""Check equipment input parameter
Mandatory parameter:
entrada if valve is working, open or partially open
Pout (DeltaP, Dew, Bubble) if valve is partially open
"""
if self.kwargs["off"] == 1:
if not self.kwargs["entrada"]:
self.msg = translate(
"equipment", "undefined input")
self.status = 0
elif self.kwargs["Pout"] or self.kwargs["DeltaP"] or \
self.kwargs["Dew"] or self.kwargs["Bubble"]:
self.status = 1
self.msg = ""
return True
else:
self.msg = translate(
"equipment", "undefined exit condition")
self.status = 0
elif self.kwargs["off"] == 2:
self.msg = ""
self.status = 1
return True
else:
if not self.kwargs["entrada"]:
self.msg = translate(
"equipment", "undefined input")
self.status = 0
else:
self.msg = ""
self.status = 1
return True
[docs]
def cleanOldValues(self, **kwargs):
"""Clean incompatible kwargs parameters
Each output pressure definition disabled any old definition
"""
if "Pout" in kwargs:
self.kwargs["DeltaP"] = 0
self.kwargs["Dew"] = 0
self.kwargs["Bubble"] = 0
elif "DeltaP" in kwargs:
self.kwargs["Pout"] = 0
self.kwargs["Dew"] = 0
self.kwargs["Bubble"] = 0
elif "Dew" in kwargs:
self.kwargs["Pout"] = 0
self.kwargs["DeltaP"] = 0
self.kwargs["Bubble"] = 0
elif "Bubble" in kwargs:
self.kwargs["Pout"] = 0
self.kwargs["DeltaP"] = 0
self.kwargs["Dew"] = 0
self.kwargs.update(kwargs)
[docs]
def calculo(self):
"""Calculate procedure, only apply pressure loss method chosen"""
self.entrada = self.kwargs["entrada"]
Pout = self.kwargs["Pout"]
DeltaP = self.kwargs["DeltaP"]
Dew = self.kwargs["Dew"]
Bubble = self.kwargs["Bubble"]
if self.kwargs["off"] == 1:
if Pout:
self.Pout = unidades.Pressure(Pout)
elif DeltaP:
self.Pout = unidades.Pressure(self.entrada.P-DeltaP)
elif Dew:
corriente = self.entrada.clone(T=Dew)
self.Pout = corriente.eos._Dew_P()
elif Bubble:
corriente = self.entrada.clone(T=Bubble)
self.Pout = corriente.eos._Bubble_P()
self.salida = [self.entrada.clone(P=self.Pout)]
elif self.kwargs["off"] == 2:
self.entrada = Corriente()
self.salida = [self.entrada]
else:
self.salida = [self.entrada]
self.Pout = unidades.Pressure(self.salida[0].P)
# Calculate other properties
self.outT = self.salida[0].T
self.outX = self.salida[0].x
[docs]
def propTxt(self):
"""Text format for report"""
txt = "#---------------"
txt += translate(
"equipment", "Calculate properties")
txt += "-----------------#"+os.linesep
txt += self.propertiesToText(range(4))
return txt
[docs]
@classmethod
def propertiesEquipment(cls):
"""Properties availables to show in report"""
l = [(translate("equipment", "Output Temperature"),
"outT", unidades.Temperature),
(translate("equipment", "Output Pressure"),
"Pout", unidades.Pressure),
(translate("equipment", "Output vapor fraction"),
"outX", unidades.Dimensionless),
(translate("equipment", "Working Condition"),
("TEXT_WORKING", "off"), str)]
return l
[docs]
def writeStatetoJSON(self, state):
"""Write instance parameter to file"""
state["Pout"] = self.Pout
state["outT"] = self.outT
state["outX"] = self.outX
[docs]
def readStatefromJSON(self, state):
"""Load instance parameter from saved file"""
self.Pout = unidades.Pressure(state["Pout"])
self.outT = unidades.Temperature(state["outT"])
self.outX = unidades.Dimensionless(state["outX"])
self.salida = [None]