Process Engineering Project¶
Project for testing the implementation of different Process Engineering concepts.
Attention:¶
I haven’t yet studied the technical vocabulary in english for this subject. It may occur that some translations are imprecise or incorrect.
This started as a code for implementing an equation ordering algorithm (EOA). A complex process engineering problem contains many equations that must be solved for a complete description of the system. This modeling establishes an algorithm for picking the order in which the equations are solved in order to minimize the computational burden.
This inspired me to attempt to develop the algorithm in such a way that it fetches the equations’ variables automatically. Equations are defined as functions, the values of which should equal zero. The following image shows how a set of equations is transformed.
We can then define these functions in python and pass them to the equation
ordering algorithm (aoe2()
). Just by passing the functions, the algorithm is able to identify their
arguments. This is done with the inspect
module. We can even specify the values of the known
variables (in this case, x1 = 1
).
# Some outputs have been hidden
from main import *
def f1(x1, x2):
return x1 + x2
def f2(x1, x2, x3, x4):
return x2*x1 + x3 - x4
def f3(x3, x4):
return x3 - x4
def f4(x4, x5):
return x4 + 5*x5
_ = aoe2(f1, f2, f3, f4, x1=1)
# Output shows:
# Equation sequence:
# - f1;
# - f3;
# - f2;
# - f4;
#
# Variable sequence:
# - x2;
# - x3;
# - x4;
# - x5;
#
# Opening variables:
# [comment]: variables we must give a first guess
# for solving.
# - x4;
#
# Project Variables:
# [comment]: known variables.
# - x1;
In this case, we had the same amount of equations and variables (because we
specified the value of x1
). In cases where we have more variables than
equations, the algorithm will propose project variables that minimize the size
of iterative loops.
The following image illustrates the process of picking the order in which the
equations are to be solved. We see that x1
is already dimmed since the
beginning since it’s a known variable. The matrix illustrated is called the
Incidence Matrix.
Another algorithm I intend on implementing is a procedure for choosing the order in which the system’s equipments are to be simulated. Each equipment has its own set of equations, that themselves are ordered through the EO algorithm. Since the equipments are interconnected in a complex manner, the order in which they are solved can also be optimized to minimize computational burden, but the algorithm is a little different.
So far, I’ve finished developing an algorithm for identifying loops in processes. For example, the following block diagram:
Can be modelled with the Flow
and Equipment
classes, and
integrated to a Process
class in the following way:
# Some outputs have been hidden
from main import *
from substances import *
p = Process()
# Notice we aren't defining the stream's compositions.
F1 = Flow("F1")
F2 = Flow("F2")
F3 = Flow("F3")
F4 = Flow("F4")
F5 = Flow("F5")
F6 = Flow("F6")
F7 = Flow("F7")
F8 = Flow("F8")
F9 = Flow("F9")
F10 = Flow("F10")
F11 = Flow("F11")
A = Equipment("A")
B = Equipment("B")
C = Equipment("C")
D = Equipment("D")
E = Equipment("E")
F = Equipment("F")
# Adding the objects to the process:
p.add_objects(F1,F2,F3,F4,F5,F6,F7,F8,F9,F10,F11,D,B,C,A,E,F)
# Adding links:
p.D.add_inflows(F1,F10)
p.D.add_outflows(F2)
p.A.add_inflows(F5, F8, F9)
p.A.add_outflows(F6)
p.B.add_inflows(F6)
p.B.add_outflows(F7, F9)
p.C.add_inflows(F7)
p.C.add_outflows(F8,F10)
p.E.add_inflows(F2, F4)
p.E.add_outflows(F3)
p.F.add_inflows(F3)
p.F.add_outflows(F4, F11)
# Defining the composition of the incoming streams:
F5.add_substances(Water)
F1.add_substances(Water)
# Updating the other streams' compositions based on those:
p.update_compositions()
The cycles present in this block diagram can be found through the moa()
function:
from main import *
cycle_list = moa(p)
# Ignoring output for clarity
for cycle in cycle_list:
for data in cycle:
print(*(arg.name for arg in data))
# Output is:
# F3 F4
# E F E
# F6 F9
# A B A
# F6 F7 F8
# A B C A
The algorithm will also pick the streams that must give an initial guess for in order to open the processes’ cycles and solve the problem. This is done in such a way that it minimizes the number of streams we have to estimate, minimizing again the computational burden.
Note
Implementation isn’t yet finished, the algorithm isn’t able to take known stream information into account just yet.
This is what is pushing me to develop classes for representing streams
and equipments, so that I can integrate them more easily, and in the
future integrate both approaches. A highlight is that the Flow
class
(that represents process streams) dynamically generates its
restriction
method in a way that it can be used by the equation
ordering algorithm (given a few details) and also represent an unique
equation for only that process stream.
- main.aoe2(*fns: Callable, **xs: Union[float, int])[source]¶
Equation Oriented Modeling Algorithm.
The name aoe stands for “Algorítmo de Ordenação de Equações”, which means “Equation Ordering Algorithm”. The ‘2’ in the name stands for version 2.
- Parameters
*fns – Functions that represent the equations that must equal 0.
**xs – Specified/Known variable values.
- Returns
The order in which the equations should be solved (expressed through a list called
func_seq
).The order in which the variables should be solved for (expressed through a list called
var_seq
).A list with the project variables (those that must be specified or optimized).
- Return type
A tuple with the
- class main.Substance[source]¶
Class for a chemical substance.
- Class Attributes:
_name: The molecule’s _name mm: Molar mass (kg/kmol). composition: The number of atoms of each element that the molecule has. atomic_mass: Look-up table for atomic masses.
- static add_substances(cls_inst: Union[main.Flow, main.Equipment], *substances: main.Substance)[source]¶
Method for adding substances to the current.
- Parameters
substances –
Substance
objects of the substances we want to add.info – Additional info we want to add the the flow’s attributes. It doesn’t have to be related the the substances that are being added.
- static remove_substances(cls_inst: Union[main.Flow, main.Equipment], *substances: main.Substance)[source]¶
Method for removing substances from a current or equipment.
- class main.Flow(name: str, *substances: main.Substance, **info: float)[source]¶
A process current.
- TODO: There still need to be constant updates of the w, wmol, x, xmol
quantities.
- Class Attributes:
tolerance: Tolerance for mass/molar fraction sum errors.
- composition¶
A list with the substances present in the flow.
- w¶
Flow rate (mass per unit time).
- x¶
A
dict
for storing the value of the mass fractions. Each entry corresponds to a component. Sum must equal one. Unknown values are marked asNone
.
- wmol¶
Flow rate (mol per unit time).
- xmol¶
Molar fractions.
- T¶
Temperature (in K).
- static restriction(flow: main.Flow) → float[source]¶
Mass fraction restriction equation (sum(x) = 1).
Warning
As of now, the code ignores the
None
values (considers them as equal to zero). No Exception is raised.- Parameters
flow – A Flow object
- Returns
The sum of the stream’s mass fractions minus 1.
- add_substances(*substances: main.Substance, **info: float)[source]¶
Method for adding substances to the current.
- Parameters
substances –
Substance
objects of the substances we want to add.info – Additional info we want to add the the flow’s attributes. It doesn’t have to be related the the substances that are being added.
- class main.Equipment(name: str)[source]¶
Class for equipments
- TODO: There still need to be constant updates of the w, wmol, x, xmol
quantities.
- static component_mass_balance(equipment: main.Equipment, substance: main.Substance) → float[source]¶
Component Mass Balance for substance a given substance and equipment. TODO: maybe raise an error if the return value goes over the tolerance.
- add_inflows(*inflows: main.Flow)[source]¶
Method for adding a current to the inflows. Automatically adds new substances to the class’s composition attribute. :param *inflows:
Flow
objects we want to add to the inflow.
- add_outflows(*outflows: main.Flow)[source]¶
Method for adding a current to the outflows.
- Parameters
*outflows –
Flow
objects we want to add to the inflow.
- remove_flow(flow: Union[str, main.Flow])[source]¶
Method for removing a current from the in and outflows.
- Parameters
flow – Either a
Flow
object or the name of an instance of one.
- add_reaction(**kwargs)[source]¶
Adds a chemical reaction to the equipment.
- Parameters
**kwargs –
Returns:
- toggle_reaction()[source]¶
TODO: update everything else that is related to chemical reactions. (mass balances etc.).
- update_composition(update_outflows: bool = False)[source]¶
Updates the equipment’s composition attribute, based on its streams.
This may also update its outflow streams if the equipment’s
reaction
attribute isFalse
(meaning that no reaction takes place in the equipment) and theupdate_outflows
argument is True.This is to avoid problems when generating errors when creating processes through the
Process.sequential()
method. If the outflows are updated as the process is being created, then it will overwrite and delete substances. However, when all connections are already established, it may be useful to useupdate_outflows = True
.Note
This implementation is only valid for an Equipment that does not involve a chemical reaction, because it removes substances that do not enter the equipment. For an equipment with reaction see
Reactor
.
- main.moa(process: main.Process, *known_variables)[source]¶
Module ordering algorithm. Orders different process modules (equipments) in order for solving problems.
- Parameters
process – A
Process
object with all of connected equipments and streams for ordering.*known_variables – The variables that are considered to be known.