From 161ce1daf364084298a77c671fbf727aecc1c8d9 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Mon, 13 Nov 2023 10:37:59 -0500 Subject: [PATCH 01/79] Added the small batch on the gdplib --- gdplib/small_batch/gdp_small_batch.py | 460 ++++++++++++++++++++++++++ 1 file changed, 460 insertions(+) create mode 100644 gdplib/small_batch/gdp_small_batch.py diff --git a/gdplib/small_batch/gdp_small_batch.py b/gdplib/small_batch/gdp_small_batch.py new file mode 100644 index 0000000..b9cdce5 --- /dev/null +++ b/gdplib/small_batch/gdp_small_batch.py @@ -0,0 +1,460 @@ +""" +gdp_small_batch.py +The gdp_small_batch.py module contains the GDP model for the small batch problem based on the Kocis and Grossmann (1988) paper. +The problem is based on the Example 4 of the paper. + +References: + - Kocis, G. R.; Grossmann, I. E. Global Optimization of Nonconvex Mixed-Integer Nonlinear Programming (MINLP) Problems in Process Synthesis. Ind. Eng. Chem. Res. 1988, 27 (8), 1407–1421. +""" +import os + +import pyomo.environ as pe +from pyomo.core.base.misc import display +from pyomo.core.plugins.transform.logical_to_linear import ( + update_boolean_vars_from_binary, +) +from pyomo.gdp import Disjunct, Disjunction +from pyomo.opt.base.solvers import SolverFactory + + +def build_small_batch(): + """ + The function build the GDP model for the small batch problem. + + References: + - Kocis, G. R.; Grossmann, I. E. Global Optimization of Nonconvex Mixed-Integer Nonlinear Programming (MINLP) Problems in Process Synthesis. Ind. Eng. Chem. Res. 1988, 27 (8), 1407–1421. + + Args: + None + + Returns: + m (pyomo.ConcreteModel): The GDP model for the small batch problem is created. + """ + NK = 3 + + # Model + m = pe.ConcreteModel() + + # Sets + m.i = pe.Set( + initialize=['a', 'b'], doc='Set of products' + ) # Set of products, i = a, b + m.j = pe.Set( + initialize=['mixer', 'reactor', 'centrifuge'] + ) # Set of stages, j = mixer, reactor, centrifuge + m.k = pe.RangeSet(NK) # Set of potential number of parallel units, k = 1, 2, 3 + + # Parameters and Scalars + + m.h = pe.Param( + initialize=6000, doc='Horizon time [hr]' + ) # Horizon time (available time) [hr] + m.vlow = pe.Param( + initialize=250, doc='Lower bound for size of batch unit [L]' + ) # Lower bound for size of batch unit [L] + m.vupp = pe.Param( + initialize=2500, doc='Upper bound for size of batch unit [L]' + ) # Upper bound for size of batch unit [L] + + # Demand of product i + m.q = pe.Param( + m.i, + initialize={'a': 200000, 'b': 150000}, + doc='Production rate of the product [kg]', + ) + # Cost coefficient for batch units + m.alpha = pe.Param( + m.j, + initialize={'mixer': 250, 'reactor': 500, 'centrifuge': 340}, + doc='Cost coefficient for batch units [$/L^beta*No. of units]]', + ) + # Cost exponent for batch units + m.beta = pe.Param( + m.j, + initialize={'mixer': 0.6, 'reactor': 0.6, 'centrifuge': 0.6}, + doc='Cost exponent for batch units', + ) + + def coeff_init(m, k): + """ + Coefficient for number of parallel units. + + Args: + m (pyomo.ConcreteModel): small batch GDP model + k (int): number of parallel units + + Returns: + Coefficient for number of parallel units. + """ + return pe.log(k) + + # Represent number of parallel units + m.coeff = pe.Param( + m.k, initialize=coeff_init, doc='Coefficient for number of parallel units' + ) + + s_init = { + ('a', 'mixer'): 2, + ('a', 'reactor'): 3, + ('a', 'centrifuge'): 4, + ('b', 'mixer'): 4, + ('b', 'reactor'): 6, + ('b', 'centrifuge'): 3, + } + + # Size factor for product i in stage j [kg/L] + m.s = pe.Param( + m.i, m.j, initialize=s_init, doc='Size factor for product i in stage j [kg/L]' + ) + + t_init = { + ('a', 'mixer'): 8, + ('a', 'reactor'): 20, + ('a', 'centrifuge'): 4, + ('b', 'mixer'): 10, + ('b', 'reactor'): 12, + ('b', 'centrifuge'): 3, + } + + # Processing time of product i in batch j [hr] + m.t = pe.Param( + m.i, m.j, initialize=t_init, doc='Processing time of product i in batch j [hr]' + ) + + # Variables + m.Y = pe.BooleanVar(m.k, m.j, doc='Stage existence') # Stage existence + m.coeffval = pe.Var( + m.k, + m.j, + within=pe.NonNegativeReals, + bounds=(0, pe.log(NK)), + doc='Activation of Coefficient', + ) # Activation of coeff + m.v = pe.Var( + m.j, + within=pe.NonNegativeReals, + bounds=(pe.log(m.vlow), pe.log(m.vupp)), + doc='Colume of stage j [L]', + ) # Volume of stage j [L] + m.b = pe.Var( + m.i, within=pe.NonNegativeReals, doc='Batch size of product i [L]' + ) # Batch size of product i [L] + m.tl = pe.Var( + m.i, within=pe.NonNegativeReals, doc='Cycle time of product i [hr]' + ) # Cycle time of product i [hr] + # Number of units in parallel stage j + m.n = pe.Var( + m.j, within=pe.NonNegativeReals, doc='Number of units in parallel stage j' + ) + + # Constraints + + # Volume requirement in stage j + @m.Constraint(m.i, m.j) + def vol(m, i, j): + """ + Volume Requirement for Stage j. + Equation: + v_j \geq log(s_ij) + b_i for i = a, b and j = mixer, reactor, centrifuge + + Args: + m (pyomo.ConcreteModel): small batch GDP model + i (str): product + j (str): stage + + Returns: + Algebraic Constraint + """ + return m.v[j] >= pe.log(m.s[i, j]) + m.b[i] + + # Cycle time for each product i + @m.Constraint(m.i, m.j) + def cycle(m, i, j): + """ + Cycle time for each product i. + Equation: + n_j + tl_i \geq log(t_ij) for i = a, b and j = mixer, reactor, centrifuge + + Args: + m (pyomo.ConcreteModel): small batch GDP model + i (str): product + j (str): stage + + Returns: + Algebraic Constraint + """ + return m.n[j] + m.tl[i] >= pe.log(m.t[i, j]) + + # Constraint for production time + @m.Constraint() + def time(m): + """ + Production time constraint. + Equation: + \sum_{i \in I} q_i * \exp(tl_i - b_i) \leq h + + Args: + m (pyomo.ConcreteModel): small batch GDP model + + Returns: + Algebraic Constraint + """ + return sum(m.q[i] * pe.exp(m.tl[i] - m.b[i]) for i in m.i) <= m.h + + # Relating number of units to 0-1 variables + @m.Constraint(m.j) + def units(m, j): + """ + Relating number of units to 0-1 variables. + Equation: + n_j = \sum_{k \in K} coeffval_{k,j} for j = mixer, reactor, centrifuge + + Args: + m (pyomo.ConcreteModel): small batch GDP model + j (str): stage + k (int): number of parallel units + + Returns: + Algebraic Constraint + """ + return m.n[j] == sum(m.coeffval[k, j] for k in m.k) + + # Only one choice for parallel units is feasible + @m.LogicalConstraint(m.j) + def lim(m, j): + """ + Only one choice for parallel units is feasible. + Equation: + \sum_{k \in K} Y_{k,j} = 1 for j = mixer, reactor, centrifuge + + Args: + m (pyomo.ConcreteModel): small batch GDP model + j (str): stage + + Returns: + Logical Constraint + """ + return pe.exactly(1, m.Y[1, j], m.Y[2, j], m.Y[3, j]) + + # _______ Disjunction_________ + + def build_existence_equations(disjunct, k, j): + """ + Build the Logic Proposition (euqations) for the existence of the stage. + + Args: + disjunct (pyomo.gdp.Disjunct): Disjunct block + k (int): number of parallel units + j (str): stage + + Returns: + None, the proposition is built inside the function + """ + m = disjunct.model() + + # Coeffval activation + @disjunct.Constraint() + def coeffval_act(disjunct): + """ + Coeffval activation. + m.coeffval[k,j] = m.coeff[k] = log(k) + + Args: + disjunct (pyomo.gdp.Disjunct): Disjunct block + + Returns: + Logical Constraint + """ + return m.coeffval[k, j] == m.coeff[k] + + def build_not_existence_equations(disjunct, k, j): + """ + Build the Logic Proposition (euqations) for the unexistence of the stage. + + Args: + disjunct (pyomo.gdp.Disjunct): Disjunct block + k (int): number of parallel units + j (str): stage + + Returns: + None, the proposition is built inside the function. + """ + m = disjunct.model() + + # Coeffval deactivation + @disjunct.Constraint() + def coeffval_deact(disjunct): + """ + Coeffval deactivation. + m.coeffval[k,j] = 0 + + Args: + disjunct (pyomo.gdp.Disjunct): Disjunct block + + Returns: + Logical Constraint + """ + return m.coeffval[k, j] == 0 + + # Create disjunction block + m.Y_exists = Disjunct(m.k, m.j, rule=build_existence_equations) + m.Y_not_exists = Disjunct(m.k, m.j, rule=build_not_existence_equations) + + # Create disjunction + + @m.Disjunction(m.k, m.j) + def Y_exists_or_not(m, k, j): + """ + Build the Logical Disjunctions of the GDP model for the small batch problem. + + Args: + m (pyomo.ConcreteModel): small batch GDP model + k (int): number of parallel units + j (str): stage + + Returns: + Y_exists_or_not (list): List of disjuncts + """ + return [m.Y_exists[k, j], m.Y_not_exists[k, j]] + + # Associate Boolean variables with with disjunction + for k in m.k: + for j in m.j: + m.Y[k, j].associate_binary_var(m.Y_exists[k, j].indicator_var) + + # ____________________________ + + # Objective + def obj_rule(m): + """ + Objective: mininimize the investment cost [$]. + Equation: + min z = sum(alpha[j] * exp(n[j] + beta[j]*v[j])) for j = mixer, reactor, centrifuge + + Args: + m (pyomo.ConcreteModel): small batch GDP model + + Returns: + Objective function (pyomo.Objective): Objective function to minimize the investment cost [$]. + """ + return sum(m.alpha[j] * (pe.exp(m.n[j] + m.beta[j] * m.v[j])) for j in m.j) + + m.obj = pe.Objective(rule=obj_rule, sense=pe.minimize) + + return m + + +def external_ref(m, x, logic_expr=None): + """ + Add the external variables to the GDP optimization problem. + + Args: + m (pyomo.ConcreteModel): GDP optimization model + x (list): External variables + logic_expr (list, optional): Logic expressions to be used in the disjunctive constraints + + Returns: + m (pyomo.ConcreteModel): GDP optimization model with the external variables + """ + ext_var = {} + p = 0 + for j in m.j: + ext_var[j] = x[p] + p = p + 1 + + for k in m.k: + for j in m.j: + if k == ext_var[j]: + m.Y[k, j].fix(True) + # m.Y_exists[k, j].indicator_var.fix( + # True + # ) # Is this necessary?: m.Y_exists[k, j].indicator_var.fix(True). + # m.Y_not_exists[k, j].indicator_var.fix( + # False + # ) # Is this necessary?: m.Y_not_exists[k, j].indicator_var.fix(True), + else: + m.Y[k, j].fix(False) + # m.Y_exists[k, j].indicator_var.fix( + # False + # ) # Is this necessary?: m.Y_exists[k, j].indicator_var.fix(True), + # m.Y_not_exists[k, j].indicator_var.fix( + # True + # ) # Is this necessary?: m.Y_not_exists[k, j].indicator_var.fix(True), + + pe.TransformationFactory('core.logical_to_linear').apply_to(m) + pe.TransformationFactory('gdp.fix_disjuncts').apply_to(m) + pe.TransformationFactory('contrib.deactivate_trivial_constraints').apply_to( + m, tmp=False, ignore_infeasible=True + ) + + return m + + +def solve_with_minlp(m, transformation='bigm', minlp='baron', timelimit=10): + """ + Solve the GDP optimization problem with a MINLP solver. + The function applies the big-M Reformulation on the GDP and solve the MINLP problem with BARON. + + Args: + m (pyomo.ConcreteModel): GDP optimization model + transformation (str, optional): Reformulation applied to the GDP. + minlp (str, optional): MINLP solver. + timelimit (float, optional): Time limit for the MINLP solver. + + Returns: + m (pyomo.ConcreteModel): GDP optimization model with the solution. + """ + # Transformation step + pe.TransformationFactory('core.logical_to_linear').apply_to(m) + transformation_string = 'gdp.' + transformation + pe.TransformationFactory(transformation_string).apply_to(m) + + # Solution step + dir_path = os.path.dirname(os.path.abspath(__file__)) + gams_path = os.path.join(dir_path, "gamsfiles/") + if not (os.path.exists(gams_path)): + print( + 'Directory for automatically generated files ' + + gams_path + + ' does not exist. We will create it' + ) + os.makedirs(gams_path) + + solvername = 'gams' + opt = SolverFactory(solvername, solver=minlp) + m.results = opt.solve( + m, + tee=True, + # Uncomment the following lines if you want to save GAMS models + # keepfiles=True, + # tmpdir=gams_path, + # symbolic_solver_labels=True, + add_options=[ + 'option reslim = ' + str(timelimit) + ';' + 'option optcr = 0.0;' + # Uncomment the following lines to setup IIS computation of BARON through option file + # 'GAMS_MODEL.optfile = 1;' + # '\n' + # '$onecho > baron.opt \n' + # 'CompIIS 1 \n' + # '$offecho' + # 'display(execError);' + ], + ) + update_boolean_vars_from_binary(m) + return m + + +if __name__ == "__main__": + m = build_small_batch() + m_solved = solve_with_minlp(m, transformation='bigm', minlp='baron', timelimit=120) + + # EXTERNAL REF TEST (this thest can be deleted) + newmodel = external_ref(m, [1, 2, 3], logic_expr=None) + # print('External Ref Test') + # print('Y[1, mixer] = ', newmodel.Y[1, 'mixer'].value) + # print('Y_exists[1, mixer] = ', newmodel.Y_exists[1, 'mixer'].indicator_var.value) + # print('Y_not_exists[1, mixer] = ', newmodel.Y_not_exists[1, 'mixer'].indicator_var.value) + # print('Y[2, mixer] = ', newmodel.Y[2, 'mixer'].value) + # print('Y_exists[2, mixer] = ', newmodel.Y_exists[2, 'mixer'].indicator_var.value) + # print('Y_not_exists[2, mixer] = ', newmodel.Y_not_exists[2, 'mixer'].indicator_var.value) From 938387d37f047d17b07acd18850b14ea9ed137cb Mon Sep 17 00:00:00 2001 From: parkyr Date: Fri, 10 May 2024 10:22:49 -0400 Subject: [PATCH 02/79] adding documentation to hda file --- gdplib/hda/HDA_GDP_gdpopt.py | 544 ++++++++++++++++++----------------- 1 file changed, 280 insertions(+), 264 deletions(-) diff --git a/gdplib/hda/HDA_GDP_gdpopt.py b/gdplib/hda/HDA_GDP_gdpopt.py index ef9565b..0e55d14 100644 --- a/gdplib/hda/HDA_GDP_gdpopt.py +++ b/gdplib/hda/HDA_GDP_gdpopt.py @@ -1,3 +1,21 @@ +""" +HDA_GDP_gdpopt.py +This model describes the profit maximization of a Hydrodealkylation of Toluene process, first presented in Reference [1], and later implemented as a GDP in Reference [2]. The MINLP formulation of this problem is available in GAMS, Reference [3]. + +The chemical plant performed the hydro-dealkylation of toluene into benzene and methane. The flowsheet model was used to make decisions on choosing between alternative process units at various stages of the process. The resulting model is GDP model. + + + +# The model enforces constraints to ensure that the product purity meets the minimum requirement, the production flowrate is within the specified range, and the temperature and pressure conditions in the process units are maintained within the operational limits. +# The disjunctions in the model define the operational modes for feedstocks, compressors, and reactors. There are two alternative feedstocks (cheap or expensive), two alternative compressor types (single stage or double stage) in the reactor feed stream and the recycle stream, and two alternative reactors (lower or higher conversion and cost--denoted as cheap or expensive respectively). +# The objective of the model is to maximize profit by minimizing costs and maximizing revenue, including feedstock costs, product prices, reactor costs, electricity costs, and cooling and heating costs. + +References: + [1] James M Douglas (1988). Conceptual Design of Chemical Processes, McGraw-Hill. ISBN-13: 978-0070177628 + [2] G.R. Kocis, and I.E. Grossmann (1989). Computational Experience with DICOPT Solving Minlp Problems in Process Synthesis. Computers and Chemical Engineering 13, 3, 307-315. https://doi.org/10.1016/0098-1354(89)85008-2 + [3] GAMS Development Corporation (2023). Hydrodealkylation Process. Available at: https://www.gams.com/latest/gamslib_ml/libhtml/gamslib_hda.html +""" + import math import os import pandas as pd @@ -8,17 +26,122 @@ def HDA_model(): - ''' - The chemical plant performed the hydro-dealkylation of toluene into benzene and methane. The flowsheet model was used to make decisions on choosing between alternative process units at various stages of the process. The resulting model is GDP model. - ''' + """ + Builds the Hydrodealkylation of Toluene process model. + + Parameters + ---------- + alpha : float + compressor coefficient + compeff : float + compressor efficiency + gam : float + ratio of cp to cv + abseff : float + absorber tray efficiency + disteff : float + column tray efficiency + uflow : float + upper bound - flow logicals + upress : float + upper bound - pressure logicals + utemp : float + upper bound - temperature logicals + costelec : float + electricity cost + costqc : float + cooling cost + costqh : float + heating cost + costfuel : float + fuel cost furnace + furnpdrop : float + pressure drop of furnace + heatvap : float + heat of vaporization [kj per kg-mol] + cppure : float + pure component heat capacities [kj per kg-mol-K] + gcomp : float + guess composition values + cp : float + heat capacities [kj per kg-mol-K] + anta : float + antoine coefficient A + antb : float + antoine coefficient B + antc : float + antoine coefficient C + perm : float + permeability [kg-mole/m**2-min-mpa] + cbeta : float + constant values (exp(beta)) in absorber + aabs : float + absorption factors + eps1 : float + small number to avoid div. by zero + heatrxn : float + heat of reaction [kj per kg-mol] + f1comp : float + feedstock compositions (h2 feed) + f66comp : float + feedstock compositions (tol feed) + f67comp : float + feedstock compositions (tol feed) + + Sets + ---- + str : int + process streams + compon : str + chemical components + abs : int + absorber + comp : int + compressor + dist : int + distillation column + flsh : int + flash drums + furn : int + furnace + hec : int + coolers + heh : int + heaters + exch : int + heat exchangers + memb : int + membrane separators + mxr1 : int + single inlet stream mixers + mxr : int + mixers + pump : int + pumps + rct : int + reactors + spl1 : int + single outlet stream splitters + spl : int + splitter + valve : int + expansion valve + str2 : int + process streams + compon2 : str + chemical components + + Returns + ------- + m : Pyomo model + Pyomo model of the Hydrodealkylation of Toluene process + + """ dir_path = os.path.dirname(os.path.abspath(__file__)) m = ConcreteModel() - # ## scalars - - m.alpha = Param(initialize=0.3665, doc="compressor coefficient") m.compeff = Param(initialize=0.750, doc="compressor effiency") m.gam = Param(initialize=1.300, doc="ratio of cp to cv") @@ -35,8 +158,15 @@ def HDA_model(): # ## sets - def strset(i): + """ + Process streams + + Returns + ------- + s : list + integer list from 1 to 74 + """ s = [] i = 1 for i in range(1, 36): @@ -48,8 +178,7 @@ def strset(i): i += i return s m.str = Set(initialize=strset, doc="process streams") - m.compon = Set(initialize=['h2', 'ch4', 'ben', - 'tol', 'dip'], doc="chemical components") + m.compon = Set(initialize=['h2', 'ch4', 'ben', 'tol', 'dip'], doc="chemical components") m.abs = RangeSet(1) m.comp = RangeSet(4) m.dist = RangeSet(3) @@ -67,22 +196,19 @@ def strset(i): m.spl = RangeSet(3) m.valve = RangeSet(6) m.str2 = Set(initialize=strset, doc="process streams") - m.compon2 = Set(initialize=['h2', 'ch4', 'ben', - 'tol', 'dip'], doc="chemical components") + m.compon2 = Set(initialize=['h2', 'ch4', 'ben', 'tol', 'dip'], doc="chemical components") # parameters Heatvap = {} Heatvap['tol'] = 30890.00 - m.heatvap = Param(m.compon, initialize=Heatvap, default=0, - doc='heat of vaporization (kj per kg-mol)') + m.heatvap = Param(m.compon, initialize=Heatvap, default=0, doc='heat of vaporization [kj per kg-mol]') Cppure = {} Cppure['h2'] = 30 Cppure['ch4'] = 40 Cppure['ben'] = 225 Cppure['tol'] = 225 Cppure['dip'] = 450 - m.cppure = Param(m.compon, initialize=Cppure, default=0, - doc='pure component heat capacities') + m.cppure = Param(m.compon, initialize=Cppure, default=0, doc='pure component heat capacities') Gcomp = {} Gcomp[7, 'h2'] = 0.95 Gcomp[7, 'ch4'] = 0.05 @@ -183,13 +309,11 @@ def strset(i): Gcomp[71, 'tol'] = 0.10 Gcomp[72, 'h2'] = 0.50 Gcomp[72, 'ch4'] = 0.50 - m.gcomp = Param(m.str, m.compon, initialize=Gcomp, - default=0, doc='guess composition values') + m.gcomp = Param(m.str, m.compon, initialize=Gcomp, default=0, doc='guess composition values') def cppara(compon, stream): return sum(m.cppure[compon] * m.gcomp[stream, compon] for compon in m.compon) - m.cp = Param(m.str, initialize=cppara, default=0, - doc='heat capacities ( kj per kgmole-k)') + m.cp = Param(m.str, initialize=cppara, default=0, doc='heat capacities [kJ per kg-mol-k]') Anta = {} Anta['h2'] = 13.6333 @@ -197,24 +321,22 @@ def cppara(compon, stream): Anta['ben'] = 15.9008 Anta['tol'] = 16.0137 Anta['dip'] = 16.6832 - m.anta = Param(m.compon, initialize=Anta, - default=0, doc='antoine coefficient') + m.anta = Param(m.compon, initialize=Anta, default=0, doc='antoine coefficient A') + Antb = {} Antb['h2'] = 164.9 Antb['ch4'] = 897.84 Antb['ben'] = 2788.51 Antb['tol'] = 3096.52 Antb['dip'] = 4602.23 - m.antb = Param(m.compon, initialize=Antb, - default=0, doc='antoine coefficient') + m.antb = Param(m.compon, initialize=Antb, default=0, doc='antoine coefficient B') Antc = {} Antc['h2'] = 3.19 Antc['ch4'] = -7.16 Antc['ben'] = -52.36 Antc['tol'] = -53.67 Antc['dip'] = -70.42 - m.antc = Param(m.compon, initialize=Antc, - default=0, doc='antoine coefficient') + m.antc = Param(m.compon, initialize=Antc, default=0, doc='antoine coefficient C') Perm = {} for i in m.compon: Perm[i] = 0 @@ -223,258 +345,178 @@ def cppara(compon, stream): def Permset(m, compon): return Perm[compon] * (1. / 22400.) * 1.0e4 * 750.062 * 60. / 1000. - m.perm = Param(m.compon, initialize=Permset, - default=0, doc='permeability ') + m.perm = Param(m.compon, initialize=Permset, default=0, doc='permeability') Cbeta = {} Cbeta['h2'] = 1.0003 Cbeta['ch4'] = 1.0008 Cbeta['dip'] = 1.0e+04 - m.cbeta = Param(m.compon, initialize=Cbeta, default=0, - doc='constant values (exp(beta)) in absorber') + m.cbeta = Param(m.compon, initialize=Cbeta, default=0, doc='constant values (exp(beta)) in absorber') Aabs = {} Aabs['ben'] = 1.4 Aabs['tol'] = 4.0 - m.aabs = Param(m.compon, initialize=Aabs, - default=0, doc=' absorption factors') + m.aabs = Param(m.compon, initialize=Aabs, default=0, doc=' absorption factors') m.eps1 = Param(initialize=1e-4, doc='small number to avoid div. by zero') Heatrxn = {} Heatrxn[1] = 50100. Heatrxn[2] = 50100. - m.heatrxn = Param(m.rct, initialize=Heatrxn, default=0, - doc='heat of reaction (kj per kg-mol)') + m.heatrxn = Param(m.rct, initialize=Heatrxn, default=0, doc='heat of reaction [kj per kg-mol]') + F1comp = {} F1comp['h2'] = 0.95 F1comp['ch4'] = 0.05 F1comp['dip'] = 0.00 F1comp['ben'] = 0.00 F1comp['tol'] = 0.00 - m.f1comp = Param(m.compon, initialize=F1comp, default=0, - doc='feedstock compositions (h2 feed)') + m.f1comp = Param(m.compon, initialize=F1comp, default=0, doc='feedstock compositions (h2 feed)') + F66comp = {} F66comp['tol'] = 1.0 F66comp['h2'] = 0.00 F66comp['ch4'] = 0.00 F66comp['dip'] = 0.00 F66comp['ben'] = 0.00 - m.f66comp = Param(m.compon, initialize=F66comp, default=0, - doc='feedstock compositions (tol feed)') + m.f66comp = Param(m.compon, initialize=F66comp, default=0, doc='feedstock compositions (tol feed)') + F67comp = {} F67comp['tol'] = 1.0 F67comp['h2'] = 0.00 F67comp['ch4'] = 0.00 F67comp['dip'] = 0.00 F67comp['ben'] = 0.00 - m.f67comp = Param(m.compon, initialize=F67comp, default=0, - doc='feedstock compositions (tol feed)') + m.f67comp = Param(m.compon, initialize=F67comp, default=0, doc='feedstock compositions (tol feed)') # # matching streams - m.ilabs = Set(initialize=[(1, 67)], - doc="abs-stream (inlet liquid) matches") - m.olabs = Set(initialize=[(1, 68)], - doc="abs-stream (outlet liquid) matches") - m.ivabs = Set(initialize=[(1, 63)], - doc=" abs-stream (inlet vapor) matches ") - m.ovabs = Set(initialize=[(1, 64)], - doc="abs-stream (outlet vapor) matches") + m.ilabs = Set(initialize=[(1, 67)], doc="abs-stream (inlet liquid) matches") + m.olabs = Set(initialize=[(1, 68)], doc="abs-stream (outlet liquid) matches") + m.ivabs = Set(initialize=[(1, 63)], doc=" abs-stream (inlet vapor) matches ") + m.ovabs = Set(initialize=[(1, 64)], doc="abs-stream (outlet vapor) matches") m.asolv = Set(initialize=[(1, 'tol')], doc="abs-solvent component matches") - m.anorm = Set(initialize=[(1, 'ben')], - doc="abs-comp matches (normal model)") - m.asimp = Set(initialize=[(1, 'h2'), (1, 'ch4'), - (1, 'dip')], doc="abs-heavy component matches") - - m.icomp = Set(initialize=[(1, 5), (2, 59), (3, 64), - (4, 56)], doc="compressor-stream (inlet) matches") - m.ocomp = Set(initialize=[(1, 6), (2, 60), (3, 65), - (4, 57)], doc=" compressor-stream (outlet) matches") - - m.idist = Set(initialize=[(1, 25), (2, 30), (3, 33)], - doc="dist-stream (inlet) matches") - m.vdist = Set(initialize=[(1, 26), (2, 31), (3, 34)], - doc="dist-stream (vapor) matches") - m.ldist = Set(initialize=[(1, 27), (2, 32), (3, 35)], - doc="dist-stream (liquid) matches") - m.dl = Set(initialize=[(1, 'h2'), (2, 'ch4'), - (3, 'ben')], doc="dist-light components matches") - m.dlkey = Set(initialize=[(1, 'ch4'), (2, 'ben'), - (3, 'tol')], doc="dist-heavy key component matches") - m.dhkey = Set(initialize=[(1, 'ben'), (2, 'tol'), - (3, 'dip')], doc="dist-heavy components matches ") - m.dh = Set(initialize=[(1, 'tol'), (1, 'dip'), - (2, 'dip')], doc="dist-key component matches") + m.anorm = Set(initialize=[(1, 'ben')], doc="abs-comp matches (normal model)") + m.asimp = Set(initialize=[(1, 'h2'), (1, 'ch4'), (1, 'dip')], doc="abs-heavy component matches") + + m.icomp = Set(initialize=[(1, 5), (2, 59), (3, 64), (4, 56)], doc="compressor-stream (inlet) matches") + m.ocomp = Set(initialize=[(1, 6), (2, 60), (3, 65), (4, 57)], doc=" compressor-stream (outlet) matches") + + m.idist = Set(initialize=[(1, 25), (2, 30), (3, 33)], doc="dist-stream (inlet) matches") + m.vdist = Set(initialize=[(1, 26), (2, 31), (3, 34)], doc="dist-stream (vapor) matches") + m.ldist = Set(initialize=[(1, 27), (2, 32), (3, 35)], doc="dist-stream (liquid) matches") + m.dl = Set(initialize=[(1, 'h2'), (2, 'ch4'), (3, 'ben')], doc="dist-light components matches") + m.dlkey = Set(initialize=[(1, 'ch4'), (2, 'ben'), (3, 'tol')], doc="dist-heavy key component matches") + m.dhkey = Set(initialize=[(1, 'ben'), (2, 'tol'), (3, 'dip')], doc="dist-heavy components matches ") + m.dh = Set(initialize=[(1, 'tol'), (1, 'dip'), (2, 'dip')], doc="dist-key component matches") i = list(m.dlkey) q = list(m.dhkey) dkeyset = i + q m.dkey = Set(initialize=dkeyset, doc='dist-key component matches') - m.iflsh = Set(initialize=[(1, 17), (2, 46), (3, 39)], - doc="flsh-stream (inlet) matches") - m.vflsh = Set(initialize=[(1, 18), (2, 47), (3, 40)], - doc="flsh-stream (vapor) matches") - m.lflsh = Set(initialize=[(1, 19), (2, 48), (3, 41)], - doc="flsh-stream (liquid) matches") - m.fkey = Set(initialize=[(1, 'ch4'), (2, 'ch4'), - (3, 'tol')], doc="flash-key component matches") + m.iflsh = Set(initialize=[(1, 17), (2, 46), (3, 39)], doc="flsh-stream (inlet) matches") + m.vflsh = Set(initialize=[(1, 18), (2, 47), (3, 40)], doc="flsh-stream (vapor) matches") + m.lflsh = Set(initialize=[(1, 19), (2, 48), (3, 41)], doc="flsh-stream (liquid) matches") + m.fkey = Set(initialize=[(1, 'ch4'), (2, 'ch4'), (3, 'tol')], doc="flash-key component matches") m.ifurn = Set(initialize=[(1, 70)], doc="furn-stream (inlet) matches") m.ofurn = Set(initialize=[(1, 9)], doc="furn-stream (outlet) matches") - m.ihec = Set(initialize=[(1, 71), (2, 45)], - doc="hec-stream (inlet) matches") - m.ohec = Set(initialize=[(1, 17), (2, 46)], - doc="hec-stream (outlet) matches") - - m.iheh = Set(initialize=[(1, 24), (2, 23), (3, 37), - (4, 61)], doc="heh-stream (inlet) matches") - m.oheh = Set(initialize=[(1, 25), (2, 44), (3, 38), - (4, 73)], doc="heh-stream (outlet) matches") - - m.icexch = Set(initialize=[(1, 8)], - doc="exch-cold stream (inlet) matches") - m.ocexch = Set(initialize=[(1, 70)], - doc="exch-cold stream (outlet) matches") - m.ihexch = Set(initialize=[(1, 16)], - doc="exch-hot stream (inlet) matches") - m.ohexch = Set(initialize=[(1, 71)], - doc="exch-hot stream (outlet) matches") - - m.imemb = Set(initialize=[(1, 3), (2, 54)], - doc="memb-stream (inlet) matches") - m.nmemb = Set(initialize=[(1, 4), (2, 55)], - doc=" memb-stream (non-permeate) matches") - m.pmemb = Set(initialize=[(1, 5), (2, 56)], - doc="memb-stream (permeate) matches") - m.mnorm = Set(initialize=[(1, 'h2'), (1, 'ch4'), - (2, 'h2'), (2, 'ch4')], doc="normal components ") - m.msimp = Set(initialize=[(1, 'ben'), (1, 'tol'), (1, 'dip'), (2, 'ben'), - (2, 'tol'), (2, 'dip')], doc="simplified flux components ") - - m.imxr1 = Set(initialize=[(1, 2), (1, 6), (2, 11), (2, 13), (3, 27), (3, 48), ( - 4, 34), (4, 40), (5, 49), (5, 50)], doc="mixer-stream (inlet) matches") - m.omxr1 = Set(initialize=[(1, 7), (2, 14), (3, 30), (4, 42), - (5, 51)], doc=" mixer-stream (outlet) matches") - m.mxr1spl1 = Set(initialize=[(1, 2, 2), (1, 6, 3), (2, 11, 10), (2, 13, 12), (3, 27, 24), (3, 48, 23), ( - 4, 34, 33), (4, 40, 37), (5, 49, 23), (5, 50, 24)], doc="1-mxr-inlet 1-spl-outlet matches") - - m.imxr = Set(initialize=[(1, 7), (1, 43), (1, 66), (1, 72), (2, 15), (2, 20), (3, 21), ( - 3, 69), (4, 51), (4, 62), (5, 57), (5, 60), (5, 65)], doc="mixer-stream (inlet) matches") - m.omxr = Set(initialize=[(1, 8), (2, 16), (3, 22), (4, 63), - (5, 72)], doc=" mixer-stream (outlet) matches ") - - m.ipump = Set(initialize=[(1, 42), (2, 68)], - doc="pump-stream (inlet) matches") - m.opump = Set(initialize=[(1, 43), (2, 69)], - doc="pump-stream (outlet) matches") - - m.irct = Set(initialize=[(1, 10), (2, 12)], - doc="reactor-stream (inlet) matches") - m.orct = Set(initialize=[(1, 11), (2, 13)], - doc="reactor-stream (outlet) matches") - m.rkey = Set(initialize=[(1, 'tol'), (2, 'tol')], - doc="reactor-key component matches") - - m.ispl1 = Set(initialize=[(1, 1), (2, 9), (3, 22), (4, 32), - (5, 52), (6, 58)], doc="splitter-stream (inlet) matches ") - m.ospl1 = Set(initialize=[(1, 2), (1, 3), (2, 10), (2, 12), (3, 23), (3, 24), (4, 33), ( - 4, 37), (5, 53), (5, 54), (6, 59), (6, 61)], doc="splitter-stream (outlet) matches") - - m.ispl = Set(initialize=[(1, 19), (2, 18), (3, 26)], - doc="splitter-stream (inlet) matches") - m.ospl = Set(initialize=[(1, 20), (1, 21), (2, 52), (2, 58), - (3, 28), (3, 29)], doc="splitter-stream (outlet) matches") - - m.ival = Set(initialize=[(1, 44), (2, 38), (3, 14), (4, 47), - (5, 29), (6, 73)], doc="exp.valve-stream (inlet) matches") - m.oval = Set(initialize=[(1, 45), (2, 39), (3, 15), (4, 49), - (5, 50), (6, 62)], doc="exp.valve-stream (outlet) matches") + m.ihec = Set(initialize=[(1, 71), (2, 45)], doc="hec-stream (inlet) matches") + m.ohec = Set(initialize=[(1, 17), (2, 46)], doc="hec-stream (outlet) matches") + + m.iheh = Set(initialize=[(1, 24), (2, 23), (3, 37), (4, 61)], doc="heh-stream (inlet) matches") + m.oheh = Set(initialize=[(1, 25), (2, 44), (3, 38), (4, 73)], doc="heh-stream (outlet) matches") + + m.icexch = Set(initialize=[(1, 8)], doc="exch-cold stream (inlet) matches") + m.ocexch = Set(initialize=[(1, 70)], doc="exch-cold stream (outlet) matches") + m.ihexch = Set(initialize=[(1, 16)], doc="exch-hot stream (inlet) matches") + m.ohexch = Set(initialize=[(1, 71)], doc="exch-hot stream (outlet) matches") + + m.imemb = Set(initialize=[(1, 3), (2, 54)], doc="memb-stream (inlet) matches") + m.nmemb = Set(initialize=[(1, 4), (2, 55)], doc=" memb-stream (non-permeate) matches") + m.pmemb = Set(initialize=[(1, 5), (2, 56)], doc="memb-stream (permeate) matches") + m.mnorm = Set(initialize=[(1, 'h2'), (1, 'ch4'), (2, 'h2'), (2, 'ch4')], doc="normal components ") + m.msimp = Set(initialize=[(1, 'ben'), (1, 'tol'), (1, 'dip'), (2, 'ben'), (2, 'tol'), (2, 'dip')], doc="simplified flux components ") + + m.imxr1 = Set(initialize=[(1, 2), (1, 6), (2, 11), (2, 13), (3, 27), (3, 48), (4, 34), (4, 40), (5, 49), (5, 50)], doc="mixer-stream (inlet) matches") + m.omxr1 = Set(initialize=[(1, 7), (2, 14), (3, 30), (4, 42), (5, 51)], doc=" mixer-stream (outlet) matches") + m.mxr1spl1 = Set(initialize=[(1, 2, 2), (1, 6, 3), (2, 11, 10), (2, 13, 12), (3, 27, 24), (3, 48, 23), (4, 34, 33), (4, 40, 37), (5, 49, 23), (5, 50, 24)], doc="1-mxr-inlet 1-spl-outlet matches") + + m.imxr = Set(initialize=[(1, 7), (1, 43), (1, 66), (1, 72), (2, 15), (2, 20), (3, 21), (3, 69), (4, 51), (4, 62), (5, 57), (5, 60), (5, 65)], doc="mixer-stream (inlet) matches") + m.omxr = Set(initialize=[(1, 8), (2, 16), (3, 22), (4, 63), (5, 72)], doc=" mixer-stream (outlet) matches ") + + m.ipump = Set(initialize=[(1, 42), (2, 68)], doc="pump-stream (inlet) matches") + m.opump = Set(initialize=[(1, 43), (2, 69)], doc="pump-stream (outlet) matches") + + m.irct = Set(initialize=[(1, 10), (2, 12)], doc="reactor-stream (inlet) matches") + m.orct = Set(initialize=[(1, 11), (2, 13)], doc="reactor-stream (outlet) matches") + m.rkey = Set(initialize=[(1, 'tol'), (2, 'tol')], doc="reactor-key component matches") + + m.ispl1 = Set(initialize=[(1, 1), (2, 9), (3, 22), (4, 32), (5, 52), (6, 58)], doc="splitter-stream (inlet) matches ") + m.ospl1 = Set(initialize=[(1, 2), (1, 3), (2, 10), (2, 12), (3, 23), (3, 24), (4, 33), (4, 37), (5, 53), (5, 54), (6, 59), (6, 61)], doc="splitter-stream (outlet) matches") + + m.ispl = Set(initialize=[(1, 19), (2, 18), (3, 26)], doc="splitter-stream (inlet) matches") + m.ospl = Set(initialize=[(1, 20), (1, 21), (2, 52), (2, 58), (3, 28), (3, 29)], doc="splitter-stream (outlet) matches") + + m.ival = Set(initialize=[(1, 44), (2, 38), (3, 14), (4, 47), (5, 29), (6, 73)], doc="exp.valve-stream (inlet) matches") + m.oval = Set(initialize=[(1, 45), (2, 39), (3, 15), (4, 49), (5, 50), (6, 62)], doc="exp.valve-stream (outlet) matches") # variables # absorber - m.nabs = Var(m.abs, within=NonNegativeReals, bounds=(0, 40), - initialize=1, doc='number of absorber trays') + m.nabs = Var(m.abs, within=NonNegativeReals, bounds=(0, 40), initialize=1, doc='number of absorber trays') m.gamma = Var(m.abs, m.compon, within=Reals, initialize=1) m.beta = Var(m.abs, m.compon, within=Reals, initialize=1) + # compressor - m.elec = Var(m.comp, within=NonNegativeReals, bounds=(0, 100), - initialize=1, doc='electricity requirement (kw)') - m.presrat = Var(m.comp, within=NonNegativeReals, bounds=( - 1, 8/3), initialize=1, doc='ratio of outlet to inlet pressure') + m.elec = Var(m.comp, within=NonNegativeReals, bounds=(0, 100), initialize=1, doc='electricity requirement (kw)') + m.presrat = Var(m.comp, within=NonNegativeReals, bounds=(1, 8/3), initialize=1, doc='ratio of outlet to inlet pressure') + # distillation m.nmin = Var(m.dist, within=NonNegativeReals, initialize=1) - m.ndist = Var(m.dist, within=NonNegativeReals, - initialize=1, doc='number of trays in column') - m.rmin = Var(m.dist, within=NonNegativeReals, - initialize=1, doc='minimum reflux ratio') - m.reflux = Var(m.dist, within=NonNegativeReals, - initialize=1, doc='reflux ratio') - m.distp = Var(m.dist, within=NonNegativeReals, initialize=1, - bounds=(0.1, 4.0), doc='column pressure') - m.avevlt = Var(m.dist, within=NonNegativeReals, - initialize=1, doc='average volatility') + m.ndist = Var(m.dist, within=NonNegativeReals, initialize=1, doc='number of trays in column') + m.rmin = Var(m.dist, within=NonNegativeReals, initialize=1, doc='minimum reflux ratio') + m.reflux = Var(m.dist, within=NonNegativeReals, initialize=1, doc='reflux ratio') + m.distp = Var(m.dist, within=NonNegativeReals, initialize=1, bounds=(0.1, 4.0), doc='column pressure') + m.avevlt = Var(m.dist, within=NonNegativeReals, initialize=1, doc='average volatility') + # flash - m.flsht = Var(m.flsh, within=NonNegativeReals, initialize=1, - doc='flash temperature (100 k)') - m.flshp = Var(m.flsh, within=NonNegativeReals, initialize=1, - doc='flash pressure (mega-pascal)') - m.eflsh = Var(m.flsh, m.compon, within=NonNegativeReals, bounds=( - 0, 1), initialize=0.5, doc='vapor phase recovery in flash') + m.flsht = Var(m.flsh, within=NonNegativeReals, initialize=1, doc='flash temperature [100 k]') + m.flshp = Var(m.flsh, within=NonNegativeReals, initialize=1, doc='flash pressure [mega-pascal]') + m.eflsh = Var(m.flsh, m.compon, within=NonNegativeReals, bounds=(0, 1), initialize=0.5, doc='vapor phase recovery in flash') + # furnace - m.qfuel = Var(m.furn, within=NonNegativeReals, bounds=( - None, 10), initialize=1, doc='heating requied (1.e+12 kj per yr)') + m.qfuel = Var(m.furn, within=NonNegativeReals, bounds=(None, 10), initialize=1, doc='heating requied [1.e+12 kj per yr]') # cooler - m.qc = Var(m.hec, within=NonNegativeReals, bounds=(None, 10), - initialize=1, doc='utility requirement (1.e+12 kj per yr)') + m.qc = Var(m.hec, within=NonNegativeReals, bounds=(None, 10), initialize=1, doc='utility requirement [1.e+12 kj per yr]') # heater - m.qh = Var(m.heh, within=NonNegativeReals, bounds=(None, 10), - initialize=1, doc='utility requirement (1.e+12 kj per yr)') + m.qh = Var(m.heh, within=NonNegativeReals, bounds=(None, 10), initialize=1, doc='utility requirement [1.e+12 kj per yr]') # exchanger - m.qexch = Var(m.exch, within=NonNegativeReals, bounds=( - None, 10), initialize=1, doc='heat exchanged (1.e+12 kj per yr)') + m.qexch = Var(m.exch, within=NonNegativeReals, bounds=(None, 10), initialize=1, doc='heat exchanged [1.e+12 kj per yr]') # membrane - m.a = Var(m.memb, within=NonNegativeReals, bounds=(100, 10000), - initialize=1, doc='surface area for mass transfer ( m**2 )') + m.a = Var(m.memb, within=NonNegativeReals, bounds=(100, 10000), initialize=1, doc='surface area for mass transfer [m**2]') # mixer(1 input) - m.mxr1p = Var(m.mxr1, within=NonNegativeReals, bounds=( - 0.1, 4), initialize=0, doc='mixer temperature (100 k)') - m.mxr1t = Var(m.mxr1, within=NonNegativeReals, bounds=(3, 10), - initialize=0, doc='mixer pressure (m-pa)') + m.mxr1p = Var(m.mxr1, within=NonNegativeReals, bounds=(0.1, 4), initialize=0, doc='mixer temperature [100 k]') + m.mxr1t = Var(m.mxr1, within=NonNegativeReals, bounds=(3, 10), initialize=0, doc='mixer pressure [m-pa]') # mixer - m.mxrt = Var(m.mxr, within=NonNegativeReals, bounds=(3.0, 10), - initialize=3, doc='mixer temperature (100 k)') # ? - m.mxrp = Var(m.mxr, within=NonNegativeReals, bounds=(0.1, 4.0), - initialize=3, doc='mixer pressure (m-pa)') + m.mxrt = Var(m.mxr, within=NonNegativeReals, bounds=(3.0, 10), initialize=3, doc='mixer temperature [100 k]') # ? + m.mxrp = Var(m.mxr, within=NonNegativeReals, bounds=(0.1, 4.0), initialize=3, doc='mixer pressure [m-pa]') # reactor - m.rctt = Var(m.rct, within=NonNegativeReals, bounds=( - 8.9427, 9.7760), doc='reactor temperature (100 k)') - m.rctp = Var(m.rct, within=NonNegativeReals, bounds=( - 3.4474, 3.4474), doc=' reactor pressure (m-pa)') - m.rctvol = Var(m.rct, within=NonNegativeReals, bounds=( - None, 200), doc='reactor volume (cubic meter)') - m.krct = Var(m.rct, within=NonNegativeReals, initialize=1, - bounds=(0.0123471, 0.149543), doc='rate constant') - m.conv = Var(m.rct, m.compon, within=NonNegativeReals, bounds=( - None, 0.973), doc='conversion of key component') - m.sel = Var(m.rct, within=NonNegativeReals, bounds=( - None, 0.9964), doc='selectivity to benzene') - m.consum = Var(m.rct, m.compon, within=NonNegativeReals, bounds=( - 0, 10000000000), initialize=0, doc='consumption rate of key') - m.q = Var(m.rct, within=NonNegativeReals, bounds=( - 0, 10000000000), doc='heat removed (1.e+9 kj per yr)') + m.rctt = Var(m.rct, within=NonNegativeReals, bounds=(8.9427, 9.7760), doc='reactor temperature [100 k]') + m.rctp = Var(m.rct, within=NonNegativeReals, bounds=(3.4474, 3.4474), doc=' reactor pressure [m-pa]') + m.rctvol = Var(m.rct, within=NonNegativeReals, bounds=(None, 200), doc='reactor volume [cubic meter]') + m.krct = Var(m.rct, within=NonNegativeReals, initialize=1, bounds=(0.0123471, 0.149543), doc='rate constant') + m.conv = Var(m.rct, m.compon, within=NonNegativeReals, bounds=(None, 0.973), doc='conversion of key component') + m.sel = Var(m.rct, within=NonNegativeReals, bounds=(None, 0.9964), doc='selectivity to benzene') + m.consum = Var(m.rct, m.compon, within=NonNegativeReals, bounds=(0, 10000000000), initialize=0, doc='consumption rate of key') + m.q = Var(m.rct, within=NonNegativeReals, bounds=(0, 10000000000), doc='heat removed [1.e+9 kj per yr]') # splitter (1 output) - m.spl1t = Var(m.spl1, within=PositiveReals, bounds=( - 3.00, 10.00), doc='splitter temperature (100 k)') - m.spl1p = Var(m.spl1, within=PositiveReals, bounds=( - 0.1, 4.0), doc='splitter pressure (m-pa)') + m.spl1t = Var(m.spl1, within=PositiveReals, bounds=(3.00, 10.00), doc='splitter temperature [100 k]') + m.spl1p = Var(m.spl1, within=PositiveReals, bounds=(0.1, 4.0), doc='splitter pressure [m-pa]') # splitter - m.splp = Var(m.spl, within=Reals, bounds=(0.1, 4.0), - doc='splitter pressure (m-pa)') - m.splt = Var(m.spl, within=Reals, bounds=(3.0, 10.0), - doc='splitter temperature (100 k)') - # stream + m.splp = Var(m.spl, within=Reals, bounds=(0.1, 4.0), doc='splitter pressure [m-pa]') + m.splt = Var(m.spl, within=Reals, bounds=(3.0, 10.0), doc='splitter temperature [100 k]') + # stream def bound_f(m, stream): if stream in range(8, 19): return (0, 50) @@ -482,22 +524,17 @@ def bound_f(m, stream): return(0, 50) else: return (0, 10) - m.f = Var(m.str, within=NonNegativeReals, bounds=bound_f, - initialize=1, doc='stream flowrates (kg-mole per min)') + m.f = Var(m.str, within=NonNegativeReals, bounds=bound_f, initialize=1, doc='stream flowrates [kg-mole per min]') def bound_fc(m, stream, compon): if stream in range(8, 19) or stream in [52, 54, 56, 57, 58, 59, 60, 70, 71, 72]: return (0, 30) else: return (0, 10) - m.fc = Var(m.str, m.compon, within=Reals, bounds=bound_fc, - initialize=1, doc='component flowrates (kg-mole per min)') - m.p = Var(m.str, within=NonNegativeReals, bounds=(0.1, 4.0), - initialize=3.0, doc='stream pressure (mega_pascal)') - m.t = Var(m.str, within=NonNegativeReals, bounds=(3.0, 10.0), - initialize=3.0, doc='stream temperature (100 k)') - m.vp = Var(m.str, m.compon, within=NonNegativeReals, initialize=1, - bounds=(0, 10), doc='vapor pressure (mega-pascal)') + m.fc = Var(m.str, m.compon, within=Reals, bounds=bound_fc, initialize=1, doc='component flowrates [kg-mole per min]') + m.p = Var(m.str, within=NonNegativeReals, bounds=(0.1, 4.0), initialize=3.0, doc='stream pressure [mega_pascal]') + m.t = Var(m.str, within=NonNegativeReals, bounds=(3.0, 10.0), initialize=3.0, doc='stream temperature [100 k]') + m.vp = Var(m.str, m.compon, within=NonNegativeReals, initialize=1, bounds=(0, 10), doc='vapor pressure [mega-pascal]') def boundsofe(m): if i == 20: @@ -506,8 +543,7 @@ def boundsofe(m): return (0.5, 1.0) else: return (None, 1.0) - m.e = Var(m.str, within=NonNegativeReals, - bounds=boundsofe, doc='split fraction') + m.e = Var(m.str, within=NonNegativeReals, bounds=boundsofe, doc='split fraction') # obj function m.const = Param(initialize=22.5, doc='constant term in obj fcn') @@ -549,36 +585,24 @@ def boundsofe(m): m.t[26].setub(3.2) for i in range(49, 52): m.t[i].setlb(2.0) - m.t[27].setlb((m.antb['ben'] / (m.anta['ben'] - - log(m.distp[1].lb * 7500.6168)) - m.antc['ben']) / 100.) - m.t[27].setub((m.antb['ben'] / (m.anta['ben'] - - log(m.distp[1].ub * 7500.6168)) - m.antc['ben']) / 100.) - m.t[31].setlb((m.antb['ben'] / (m.anta['ben'] - - log(m.distp[2].lb * 7500.6168)) - m.antc['ben']) / 100.) - m.t[31].setub((m.antb['ben'] / (m.anta['ben'] - - log(m.distp[2].ub * 7500.6168)) - m.antc['ben']) / 100.) - m.t[32].setlb((m.antb['tol'] / (m.anta['tol'] - - log(m.distp[2].lb * 7500.6168)) - m.antc['tol']) / 100.) - m.t[32].setub((m.antb['tol'] / (m.anta['tol'] - - log(m.distp[2].ub * 7500.6168)) - m.antc['tol']) / 100.) - m.t[34].setlb((m.antb['tol'] / (m.anta['tol'] - - log(m.distp[3].lb * 7500.6168)) - m.antc['tol']) / 100.) - m.t[34].setub((m.antb['tol'] / (m.anta['tol'] - - log(m.distp[3].ub * 7500.6168)) - m.antc['tol']) / 100.) - m.t[35].setlb((m.antb['dip'] / (m.anta['dip'] - - log(m.distp[3].lb * 7500.6168)) - m.antc['dip']) / 100.) - m.t[35].setub((m.antb['dip'] / (m.anta['dip'] - - log(m.distp[3].ub * 7500.6168)) - m.antc['dip']) / 100.) + m.t[27].setlb((m.antb['ben'] / (m.anta['ben'] - log(m.distp[1].lb * 7500.6168)) - m.antc['ben']) / 100.) + m.t[27].setub((m.antb['ben'] / (m.anta['ben'] - log(m.distp[1].ub * 7500.6168)) - m.antc['ben']) / 100.) + m.t[31].setlb((m.antb['ben'] / (m.anta['ben'] - log(m.distp[2].lb * 7500.6168)) - m.antc['ben']) / 100.) + m.t[31].setub((m.antb['ben'] / (m.anta['ben'] - log(m.distp[2].ub * 7500.6168)) - m.antc['ben']) / 100.) + m.t[32].setlb((m.antb['tol'] / (m.anta['tol'] - log(m.distp[2].lb * 7500.6168)) - m.antc['tol']) / 100.) + m.t[32].setub((m.antb['tol'] / (m.anta['tol'] - log(m.distp[2].ub * 7500.6168)) - m.antc['tol']) / 100.) + m.t[34].setlb((m.antb['tol'] / (m.anta['tol'] - log(m.distp[3].lb * 7500.6168)) - m.antc['tol']) / 100.) + m.t[34].setub((m.antb['tol'] / (m.anta['tol'] - log(m.distp[3].ub * 7500.6168)) - m.antc['tol']) / 100.) + m.t[35].setlb((m.antb['dip'] / (m.anta['dip'] - log(m.distp[3].lb * 7500.6168)) - m.antc['dip']) / 100.) + m.t[35].setub((m.antb['dip'] / (m.anta['dip'] - log(m.distp[3].ub * 7500.6168)) - m.antc['dip']) / 100.) # absorber m.beta[1, 'ben'].setlb(0.00011776) m.beta[1, 'ben'].setub(5.72649) m.beta[1, 'tol'].setlb(0.00018483515) m.beta[1, 'tol'].setub(15) - m.gamma[1, 'tol'].setlb(log( - (1 - m.aabs['tol'] ** (m.nabs[1].lb * m.abseff + m.eps1)) / (1 - m.aabs['tol']))) - m.gamma[1, 'tol'].setub(min(15, log( - (1 - m.aabs['tol'] ** (m.nabs[1].ub * m.abseff + m.eps1)) / (1 - m.aabs['tol'])))) + m.gamma[1, 'tol'].setlb(log((1 - m.aabs['tol'] ** (m.nabs[1].lb * m.abseff + m.eps1)) / (1 - m.aabs['tol']))) + m.gamma[1, 'tol'].setub(min(15, log((1 - m.aabs['tol'] ** (m.nabs[1].ub * m.abseff + m.eps1)) / (1 - m.aabs['tol'])))) for abso in m.abs: for compon in m.compon: m.beta[abso, compon].setlb(log( @@ -588,10 +612,8 @@ def boundsofe(m): m.t[67].setlb(3.0) m.t[67].setub(3.0) for compon in m.compon: - m.vp[67, compon].setlb((1. / 7500.6168) * exp(m.anta[compon] - - m.antb[compon] / (value(m.t[67]) * 100. + m.antc[compon]))) - m.vp[67, compon].setub((1. / 7500.6168) * exp(m.anta[compon] - - m.antb[compon] / (value(m.t[67]) * 100. + m.antc[compon]))) + m.vp[67, compon].setlb((1. / 7500.6168) * exp(m.anta[compon] - m.antb[compon] / (value(m.t[67]) * 100. + m.antc[compon]))) + m.vp[67, compon].setub((1. / 7500.6168) * exp(m.anta[compon] - m.antb[compon] / (value(m.t[67]) * 100. + m.antc[compon]))) flashdata_file = os.path.join(dir_path,'flashdata.csv') @@ -600,10 +622,8 @@ def boundsofe(m): two_digit_number = flash.iloc[:, [0]].dropna().values two_digit_compon = flash.iloc[:, [1]].dropna().values for i in range(len(two_digit_number)): - m.eflsh[two_digit_number[i, 0], two_digit_compon[i, 0]].setlb( - flash.iloc[:, [2]].dropna().values[i, 0]) - m.eflsh[two_digit_number[i, 0], two_digit_compon[i, 0]].setub( - flash.iloc[:, [3]].dropna().values[i, 0]) + m.eflsh[two_digit_number[i, 0], two_digit_compon[i, 0]].setlb(flash.iloc[:, [2]].dropna().values[i, 0]) + m.eflsh[two_digit_number[i, 0], two_digit_compon[i, 0]].setub(flash.iloc[:, [3]].dropna().values[i, 0]) for i in range(len(number)): m.flshp[number[i, 0]].setlb(flash.iloc[:, [5]].dropna().values[i, 0]) m.flshp[number[i, 0]].setub(flash.iloc[:, [6]].dropna().values[i, 0]) @@ -622,10 +642,8 @@ def boundsofe(m): for stream in m.str: for compon in m.compon: - m.vp[stream, compon].setlb((1. / 7500.6168) * exp( - m.anta[compon] - m.antb[compon] / (m.t[stream].lb * 100. + m.antc[compon]))) - m.vp[stream, compon].setub((1. / 7500.6168) * exp( - m.anta[compon] - m.antb[compon] / (m.t[stream].ub * 100. + m.antc[compon]))) + m.vp[stream, compon].setlb((1. / 7500.6168) * exp(m.anta[compon] - m.antb[compon] / (m.t[stream].lb * 100. + m.antc[compon]))) + m.vp[stream, compon].setub((1. / 7500.6168) * exp(m.anta[compon] - m.antb[compon] / (m.t[stream].ub * 100. + m.antc[compon]))) m.p[1].setub(3.93) m.p[1].setlb(3.93) @@ -799,15 +817,13 @@ def Absfact(_m, i, compon): if (i, compon) in m.anorm: return sum(m.f[stream] * m.p[stream] for (absb, stream) in m.ilabs if absb == i) == sum(m.f[stream] for (absc, stream) in m.ivabs if absc == i) * m.aabs[compon] * sum(m.vp[stream, compon] for (absd, stream) in m.ilabs if absd == i) return Constraint.Skip - b.absfact = Constraint( - [absorber], m.compon, rule=Absfact, doc='absorbption factor equation') + b.absfact = Constraint([absorber], m.compon, rule=Absfact, doc='absorbption factor equation') def Gameqn(_m, i, compon): if (i, compon) in m.asolv: return m.gamma[i, compon] == log((1 - m.aabs[compon] ** (m.nabs[i] * m.abseff + m.eps1)) / (1 - m.aabs[compon])) return Constraint.Skip - b.gameqn = Constraint([absorber], m.compon, - rule=Gameqn, doc='definition of gamma') + b.gameqn = Constraint([absorber], m.compon, rule=Gameqn, doc='definition of gamma') def Betaeqn(_m, i, compon): if (i, compon) not in m.asimp: From 5d0f34bde874f01303710be7151d9757f80625fb Mon Sep 17 00:00:00 2001 From: parkyr Date: Thu, 16 May 2024 18:28:26 -0400 Subject: [PATCH 03/79] fixing typos and adding documentation --- gdplib/hda/HDA_GDP_gdpopt.py | 527 ++++++++++++++++++++++++----------- 1 file changed, 369 insertions(+), 158 deletions(-) diff --git a/gdplib/hda/HDA_GDP_gdpopt.py b/gdplib/hda/HDA_GDP_gdpopt.py index 0e55d14..d4c743f 100644 --- a/gdplib/hda/HDA_GDP_gdpopt.py +++ b/gdplib/hda/HDA_GDP_gdpopt.py @@ -2,17 +2,21 @@ HDA_GDP_gdpopt.py This model describes the profit maximization of a Hydrodealkylation of Toluene process, first presented in Reference [1], and later implemented as a GDP in Reference [2]. The MINLP formulation of this problem is available in GAMS, Reference [3]. -The chemical plant performed the hydro-dealkylation of toluene into benzene and methane. The flowsheet model was used to make decisions on choosing between alternative process units at various stages of the process. The resulting model is GDP model. +The chemical plant performed the hydro-dealkylation of toluene into benzene and methane. The flowsheet model was used to make decisions on choosing between alternative process units at various stages of the process. The resulting model is GDP model. The disjunctions in the model include: + 1. Inlet purify selection at feed + 2. Reactor operation mode selection (adiabatic / isothermal) + 3. Vapor recovery methane purge / recycle with membrane + 4. Vapor recovery hydrogen recycle + 5. Liquid separation system methane stabilizing via column or flash drum + 6. Liquid separation system toluene recovery via column or flash drum +The model enforces constraints to ensure that the mass and energy balances are satisfied, the purity of the products is within the required limits, the recovery specification are met, and the temperature and pressure conditions in the process units are maintained within the operational limits. - -# The model enforces constraints to ensure that the product purity meets the minimum requirement, the production flowrate is within the specified range, and the temperature and pressure conditions in the process units are maintained within the operational limits. -# The disjunctions in the model define the operational modes for feedstocks, compressors, and reactors. There are two alternative feedstocks (cheap or expensive), two alternative compressor types (single stage or double stage) in the reactor feed stream and the recycle stream, and two alternative reactors (lower or higher conversion and cost--denoted as cheap or expensive respectively). -# The objective of the model is to maximize profit by minimizing costs and maximizing revenue, including feedstock costs, product prices, reactor costs, electricity costs, and cooling and heating costs. +The objective of the model is to maximize the profit by determining the optimal process configuration and operating conditions. The decision variables include the number of trays in the absorber and distillation column, the reflux ratio, the pressure in the distillation column, the temperature and pressure in the flash drums, the heating requirement in the furnace, the electricity requirement in the compressor, the heat exchange in the coolers and heaters, the surface area in the membrane separators, the temperature and pressure in the mixers, the temperature and pressure in the reactors, and the volume and rate constant in the reactors. References: [1] James M Douglas (1988). Conceptual Design of Chemical Processes, McGraw-Hill. ISBN-13: 978-0070177628 - [2] G.R. Kocis, and I.E. Grossmann (1989). Computational Experience with DICOPT Solving Minlp Problems in Process Synthesis. Computers and Chemical Engineering 13, 3, 307-315. https://doi.org/10.1016/0098-1354(89)85008-2 + [2] G.R. Kocis, and I.E. Grossmann (1989). Computational Experience with DICOPT Solving MINLP Problems in Process Synthesis. Computers and Chemical Engineering 13, 3, 307-315. https://doi.org/10.1016/0098-1354(89)85008-2 [3] GAMS Development Corporation (2023). Hydrodealkylation Process. Available at: https://www.gams.com/latest/gamslib_ml/libhtml/gamslib_hda.html """ @@ -60,11 +64,11 @@ def HDA_model(): heatvap : float heat of vaporization [kj per kg-mol] cppure : float - pure component heat capacities [kj per kg-mol-K] + pure component heat capacities [kj per kg-mol-k] gcomp : float - guess composition values + guess composition values cp : float - heat capacities [kj per kg-mol-K] + heat capacities [kj per kg-mol-k] anta : float antoine coefficient A antb : float @@ -134,7 +138,7 @@ def HDA_model(): Returns ------- - m : Pyomo model + m : Pyomo ConcreteModel Pyomo model of the Hydrodealkylation of Toluene process """ @@ -143,10 +147,10 @@ def HDA_model(): m = ConcreteModel() m.alpha = Param(initialize=0.3665, doc="compressor coefficient") - m.compeff = Param(initialize=0.750, doc="compressor effiency") + m.compeff = Param(initialize=0.750, doc="compressor efficiency") m.gam = Param(initialize=1.300, doc="ratio of cp to cv") m.abseff = Param(initialize=0.333, doc="absorber tray efficiency") - m.disteff = Param(initialize=0.5000, doc="column tray effiency") + m.disteff = Param(initialize=0.5000, doc="column tray efficiency") m.uflow = Param(initialize=50, doc="upper bound - flow logicals") m.upress = Param(initialize=4.0, doc="upper bound - pressure logicals") m.utemp = Param(initialize=7.0, doc="upper bound - temperature logicals") @@ -203,12 +207,13 @@ def strset(i): Heatvap['tol'] = 30890.00 m.heatvap = Param(m.compon, initialize=Heatvap, default=0, doc='heat of vaporization [kj per kg-mol]') Cppure = {} + # h2 'hydrogen', ch4 'methane', ben 'benzene', tol 'toluene', dip 'diphenyl' Cppure['h2'] = 30 Cppure['ch4'] = 40 Cppure['ben'] = 225 Cppure['tol'] = 225 Cppure['dip'] = 450 - m.cppure = Param(m.compon, initialize=Cppure, default=0, doc='pure component heat capacities') + m.cppure = Param(m.compon, initialize=Cppure, default=0, doc='pure component heat capacities [kj per kg-mol-k]') Gcomp = {} Gcomp[7, 'h2'] = 0.95 Gcomp[7, 'ch4'] = 0.05 @@ -312,8 +317,12 @@ def strset(i): m.gcomp = Param(m.str, m.compon, initialize=Gcomp, default=0, doc='guess composition values') def cppara(compon, stream): + """ + heat capacities [kj per kg-mol-k] + sum of heat capacities of all components in a stream, weighted by their composition + """ return sum(m.cppure[compon] * m.gcomp[stream, compon] for compon in m.compon) - m.cp = Param(m.str, initialize=cppara, default=0, doc='heat capacities [kJ per kg-mol-k]') + m.cp = Param(m.str, initialize=cppara, default=0, doc='heat capacities [kj per kg-mol-k]') Anta = {} Anta['h2'] = 13.6333 @@ -330,6 +339,7 @@ def cppara(compon, stream): Antb['tol'] = 3096.52 Antb['dip'] = 4602.23 m.antb = Param(m.compon, initialize=Antb, default=0, doc='antoine coefficient B') + Antc = {} Antc['h2'] = 3.19 Antc['ch4'] = -7.16 @@ -337,26 +347,32 @@ def cppara(compon, stream): Antc['tol'] = -53.67 Antc['dip'] = -70.42 m.antc = Param(m.compon, initialize=Antc, default=0, doc='antoine coefficient C') + Perm = {} for i in m.compon: Perm[i] = 0 Perm['h2'] = 55.0e-06 Perm['ch4'] = 2.3e-06 - def Permset(m, compon): + """ + permeability [kg-mole/m**2-min-mpa] + converting unit for permeability from cc/cm**2-sec-cmHg to kg-mole/m**2-min-mpa + """ return Perm[compon] * (1. / 22400.) * 1.0e4 * 750.062 * 60. / 1000. - m.perm = Param(m.compon, initialize=Permset, default=0, doc='permeability') + m.perm = Param(m.compon, initialize=Permset, default=0, doc='permeability [kg-mole/m**2-min-mpa]') Cbeta = {} Cbeta['h2'] = 1.0003 Cbeta['ch4'] = 1.0008 Cbeta['dip'] = 1.0e+04 m.cbeta = Param(m.compon, initialize=Cbeta, default=0, doc='constant values (exp(beta)) in absorber') + Aabs = {} Aabs['ben'] = 1.4 Aabs['tol'] = 4.0 - m.aabs = Param(m.compon, initialize=Aabs, default=0, doc=' absorption factors') + m.aabs = Param(m.compon, initialize=Aabs, default=0, doc='absorption factors') m.eps1 = Param(initialize=1e-4, doc='small number to avoid div. by zero') + Heatrxn = {} Heatrxn[1] = 50100. Heatrxn[2] = 50100. @@ -387,24 +403,23 @@ def Permset(m, compon): m.f67comp = Param(m.compon, initialize=F67comp, default=0, doc='feedstock compositions (tol feed)') # # matching streams - m.ilabs = Set(initialize=[(1, 67)], doc="abs-stream (inlet liquid) matches") m.olabs = Set(initialize=[(1, 68)], doc="abs-stream (outlet liquid) matches") - m.ivabs = Set(initialize=[(1, 63)], doc=" abs-stream (inlet vapor) matches ") + m.ivabs = Set(initialize=[(1, 63)], doc="abs-stream (inlet vapor) matches") m.ovabs = Set(initialize=[(1, 64)], doc="abs-stream (outlet vapor) matches") m.asolv = Set(initialize=[(1, 'tol')], doc="abs-solvent component matches") m.anorm = Set(initialize=[(1, 'ben')], doc="abs-comp matches (normal model)") m.asimp = Set(initialize=[(1, 'h2'), (1, 'ch4'), (1, 'dip')], doc="abs-heavy component matches") m.icomp = Set(initialize=[(1, 5), (2, 59), (3, 64), (4, 56)], doc="compressor-stream (inlet) matches") - m.ocomp = Set(initialize=[(1, 6), (2, 60), (3, 65), (4, 57)], doc=" compressor-stream (outlet) matches") + m.ocomp = Set(initialize=[(1, 6), (2, 60), (3, 65), (4, 57)], doc="compressor-stream (outlet) matches") m.idist = Set(initialize=[(1, 25), (2, 30), (3, 33)], doc="dist-stream (inlet) matches") m.vdist = Set(initialize=[(1, 26), (2, 31), (3, 34)], doc="dist-stream (vapor) matches") m.ldist = Set(initialize=[(1, 27), (2, 32), (3, 35)], doc="dist-stream (liquid) matches") m.dl = Set(initialize=[(1, 'h2'), (2, 'ch4'), (3, 'ben')], doc="dist-light components matches") m.dlkey = Set(initialize=[(1, 'ch4'), (2, 'ben'), (3, 'tol')], doc="dist-heavy key component matches") - m.dhkey = Set(initialize=[(1, 'ben'), (2, 'tol'), (3, 'dip')], doc="dist-heavy components matches ") + m.dhkey = Set(initialize=[(1, 'ben'), (2, 'tol'), (3, 'dip')], doc="dist-heavy components matches") m.dh = Set(initialize=[(1, 'tol'), (1, 'dip'), (2, 'dip')], doc="dist-key component matches") i = list(m.dlkey) @@ -428,21 +443,21 @@ def Permset(m, compon): m.icexch = Set(initialize=[(1, 8)], doc="exch-cold stream (inlet) matches") m.ocexch = Set(initialize=[(1, 70)], doc="exch-cold stream (outlet) matches") - m.ihexch = Set(initialize=[(1, 16)], doc="exch-hot stream (inlet) matches") - m.ohexch = Set(initialize=[(1, 71)], doc="exch-hot stream (outlet) matches") + m.ihexch = Set(initialize=[(1, 16)], doc="exch-hot stream (inlet) matches") + m.ohexch = Set(initialize=[(1, 71)], doc="exch-hot stream (outlet) matches") m.imemb = Set(initialize=[(1, 3), (2, 54)], doc="memb-stream (inlet) matches") - m.nmemb = Set(initialize=[(1, 4), (2, 55)], doc=" memb-stream (non-permeate) matches") + m.nmemb = Set(initialize=[(1, 4), (2, 55)], doc="memb-stream (non-permeate) matches") m.pmemb = Set(initialize=[(1, 5), (2, 56)], doc="memb-stream (permeate) matches") - m.mnorm = Set(initialize=[(1, 'h2'), (1, 'ch4'), (2, 'h2'), (2, 'ch4')], doc="normal components ") - m.msimp = Set(initialize=[(1, 'ben'), (1, 'tol'), (1, 'dip'), (2, 'ben'), (2, 'tol'), (2, 'dip')], doc="simplified flux components ") + m.mnorm = Set(initialize=[(1, 'h2'), (1, 'ch4'), (2, 'h2'), (2, 'ch4')], doc="normal components") + m.msimp = Set(initialize=[(1, 'ben'), (1, 'tol'), (1, 'dip'), (2, 'ben'), (2, 'tol'), (2, 'dip')], doc="simplified flux components") m.imxr1 = Set(initialize=[(1, 2), (1, 6), (2, 11), (2, 13), (3, 27), (3, 48), (4, 34), (4, 40), (5, 49), (5, 50)], doc="mixer-stream (inlet) matches") - m.omxr1 = Set(initialize=[(1, 7), (2, 14), (3, 30), (4, 42), (5, 51)], doc=" mixer-stream (outlet) matches") + m.omxr1 = Set(initialize=[(1, 7), (2, 14), (3, 30), (4, 42), (5, 51)], doc="mixer-stream (outlet) matches") m.mxr1spl1 = Set(initialize=[(1, 2, 2), (1, 6, 3), (2, 11, 10), (2, 13, 12), (3, 27, 24), (3, 48, 23), (4, 34, 33), (4, 40, 37), (5, 49, 23), (5, 50, 24)], doc="1-mxr-inlet 1-spl-outlet matches") m.imxr = Set(initialize=[(1, 7), (1, 43), (1, 66), (1, 72), (2, 15), (2, 20), (3, 21), (3, 69), (4, 51), (4, 62), (5, 57), (5, 60), (5, 65)], doc="mixer-stream (inlet) matches") - m.omxr = Set(initialize=[(1, 8), (2, 16), (3, 22), (4, 63), (5, 72)], doc=" mixer-stream (outlet) matches ") + m.omxr = Set(initialize=[(1, 8), (2, 16), (3, 22), (4, 63), (5, 72)], doc="mixer-stream (outlet) matches ") m.ipump = Set(initialize=[(1, 42), (2, 68)], doc="pump-stream (inlet) matches") m.opump = Set(initialize=[(1, 43), (2, 69)], doc="pump-stream (outlet) matches") @@ -451,7 +466,7 @@ def Permset(m, compon): m.orct = Set(initialize=[(1, 11), (2, 13)], doc="reactor-stream (outlet) matches") m.rkey = Set(initialize=[(1, 'tol'), (2, 'tol')], doc="reactor-key component matches") - m.ispl1 = Set(initialize=[(1, 1), (2, 9), (3, 22), (4, 32), (5, 52), (6, 58)], doc="splitter-stream (inlet) matches ") + m.ispl1 = Set(initialize=[(1, 1), (2, 9), (3, 22), (4, 32), (5, 52), (6, 58)], doc="splitter-stream (inlet) matches") m.ospl1 = Set(initialize=[(1, 2), (1, 3), (2, 10), (2, 12), (3, 23), (3, 24), (4, 33), (4, 37), (5, 53), (5, 54), (6, 59), (6, 61)], doc="splitter-stream (outlet) matches") m.ispl = Set(initialize=[(1, 19), (2, 18), (3, 26)], doc="splitter-stream (inlet) matches") @@ -468,24 +483,24 @@ def Permset(m, compon): m.beta = Var(m.abs, m.compon, within=Reals, initialize=1) # compressor - m.elec = Var(m.comp, within=NonNegativeReals, bounds=(0, 100), initialize=1, doc='electricity requirement (kw)') + m.elec = Var(m.comp, within=NonNegativeReals, bounds=(0, 100), initialize=1, doc='electricity requirement [kw]') m.presrat = Var(m.comp, within=NonNegativeReals, bounds=(1, 8/3), initialize=1, doc='ratio of outlet to inlet pressure') # distillation - m.nmin = Var(m.dist, within=NonNegativeReals, initialize=1) + m.nmin = Var(m.dist, within=NonNegativeReals, initialize=1, doc='minimum number of trays in column') m.ndist = Var(m.dist, within=NonNegativeReals, initialize=1, doc='number of trays in column') m.rmin = Var(m.dist, within=NonNegativeReals, initialize=1, doc='minimum reflux ratio') m.reflux = Var(m.dist, within=NonNegativeReals, initialize=1, doc='reflux ratio') - m.distp = Var(m.dist, within=NonNegativeReals, initialize=1, bounds=(0.1, 4.0), doc='column pressure') + m.distp = Var(m.dist, within=NonNegativeReals, initialize=1, bounds=(0.1, 4.0), doc='column pressure [mega-pascal]') m.avevlt = Var(m.dist, within=NonNegativeReals, initialize=1, doc='average volatility') # flash - m.flsht = Var(m.flsh, within=NonNegativeReals, initialize=1, doc='flash temperature [100 k]') + m.flsht = Var(m.flsh, within=NonNegativeReals, initialize=1, doc='flash temperature [100 k]') m.flshp = Var(m.flsh, within=NonNegativeReals, initialize=1, doc='flash pressure [mega-pascal]') m.eflsh = Var(m.flsh, m.compon, within=NonNegativeReals, bounds=(0, 1), initialize=0.5, doc='vapor phase recovery in flash') # furnace - m.qfuel = Var(m.furn, within=NonNegativeReals, bounds=(None, 10), initialize=1, doc='heating requied [1.e+12 kj per yr]') + m.qfuel = Var(m.furn, within=NonNegativeReals, bounds=(None, 10), initialize=1, doc='heating required [1.e+12 kj per yr]') # cooler m.qc = Var(m.hec, within=NonNegativeReals, bounds=(None, 10), initialize=1, doc='utility requirement [1.e+12 kj per yr]') # heater @@ -496,13 +511,13 @@ def Permset(m, compon): m.a = Var(m.memb, within=NonNegativeReals, bounds=(100, 10000), initialize=1, doc='surface area for mass transfer [m**2]') # mixer(1 input) m.mxr1p = Var(m.mxr1, within=NonNegativeReals, bounds=(0.1, 4), initialize=0, doc='mixer temperature [100 k]') - m.mxr1t = Var(m.mxr1, within=NonNegativeReals, bounds=(3, 10), initialize=0, doc='mixer pressure [m-pa]') + m.mxr1t = Var(m.mxr1, within=NonNegativeReals, bounds=(3, 10), initialize=0, doc='mixer pressure [mega-pascal]') # mixer m.mxrt = Var(m.mxr, within=NonNegativeReals, bounds=(3.0, 10), initialize=3, doc='mixer temperature [100 k]') # ? - m.mxrp = Var(m.mxr, within=NonNegativeReals, bounds=(0.1, 4.0), initialize=3, doc='mixer pressure [m-pa]') + m.mxrp = Var(m.mxr, within=NonNegativeReals, bounds=(0.1, 4.0), initialize=3, doc='mixer pressure [mega-pascal]') # reactor m.rctt = Var(m.rct, within=NonNegativeReals, bounds=(8.9427, 9.7760), doc='reactor temperature [100 k]') - m.rctp = Var(m.rct, within=NonNegativeReals, bounds=(3.4474, 3.4474), doc=' reactor pressure [m-pa]') + m.rctp = Var(m.rct, within=NonNegativeReals, bounds=(3.4474, 3.4474), doc='reactor pressure [mega-pascal]') m.rctvol = Var(m.rct, within=NonNegativeReals, bounds=(None, 200), doc='reactor volume [cubic meter]') m.krct = Var(m.rct, within=NonNegativeReals, initialize=1, bounds=(0.0123471, 0.149543), doc='rate constant') m.conv = Var(m.rct, m.compon, within=NonNegativeReals, bounds=(None, 0.973), doc='conversion of key component') @@ -511,13 +526,17 @@ def Permset(m, compon): m.q = Var(m.rct, within=NonNegativeReals, bounds=(0, 10000000000), doc='heat removed [1.e+9 kj per yr]') # splitter (1 output) m.spl1t = Var(m.spl1, within=PositiveReals, bounds=(3.00, 10.00), doc='splitter temperature [100 k]') - m.spl1p = Var(m.spl1, within=PositiveReals, bounds=(0.1, 4.0), doc='splitter pressure [m-pa]') + m.spl1p = Var(m.spl1, within=PositiveReals, bounds=(0.1, 4.0), doc='splitter pressure [mega-pascal]') # splitter - m.splp = Var(m.spl, within=Reals, bounds=(0.1, 4.0), doc='splitter pressure [m-pa]') + m.splp = Var(m.spl, within=Reals, bounds=(0.1, 4.0), doc='splitter pressure [mega-pascal]') m.splt = Var(m.spl, within=Reals, bounds=(3.0, 10.0), doc='splitter temperature [100 k]') # stream def bound_f(m, stream): + """ + stream flowrates [kg-mole per min] + setting appropriate bounds for stream flowrates + """ if stream in range(8, 19): return (0, 50) elif stream in [52, 54, 56, 57, 58, 59, 60, 70, 71, 72]: @@ -527,16 +546,22 @@ def bound_f(m, stream): m.f = Var(m.str, within=NonNegativeReals, bounds=bound_f, initialize=1, doc='stream flowrates [kg-mole per min]') def bound_fc(m, stream, compon): + """ + setting appropriate bounds for component flowrates + """ if stream in range(8, 19) or stream in [52, 54, 56, 57, 58, 59, 60, 70, 71, 72]: return (0, 30) else: return (0, 10) m.fc = Var(m.str, m.compon, within=Reals, bounds=bound_fc, initialize=1, doc='component flowrates [kg-mole per min]') - m.p = Var(m.str, within=NonNegativeReals, bounds=(0.1, 4.0), initialize=3.0, doc='stream pressure [mega_pascal]') + m.p = Var(m.str, within=NonNegativeReals, bounds=(0.1, 4.0), initialize=3.0, doc='stream pressure [mega-pascal]') m.t = Var(m.str, within=NonNegativeReals, bounds=(3.0, 10.0), initialize=3.0, doc='stream temperature [100 k]') m.vp = Var(m.str, m.compon, within=NonNegativeReals, initialize=1, bounds=(0, 10), doc='vapor pressure [mega-pascal]') def boundsofe(m): + """ + setting appropriate bounds for split fraction + """ if i == 20: return (None, 0.5) elif i == 21: @@ -544,7 +569,8 @@ def boundsofe(m): else: return (None, 1.0) m.e = Var(m.str, within=NonNegativeReals, bounds=boundsofe, doc='split fraction') - # obj function + + # obj function constant term m.const = Param(initialize=22.5, doc='constant term in obj fcn') # ## setting variable bounds @@ -605,10 +631,8 @@ def boundsofe(m): m.gamma[1, 'tol'].setub(min(15, log((1 - m.aabs['tol'] ** (m.nabs[1].ub * m.abseff + m.eps1)) / (1 - m.aabs['tol'])))) for abso in m.abs: for compon in m.compon: - m.beta[abso, compon].setlb(log( - (1 - m.aabs[compon] ** (m.nabs[1].lb * m.abseff + m.eps1 + 1)) / (1 - m.aabs[compon]))) - m.beta[abso, compon].setub(min(15, log( - (1 - m.aabs[compon] ** (m.nabs[1].ub * m.abseff + m.eps1 + 1)) / (1 - m.aabs[compon])))) + m.beta[abso, compon].setlb(log((1 - m.aabs[compon] ** (m.nabs[1].lb * m.abseff + m.eps1 + 1)) / (1 - m.aabs[compon]))) + m.beta[abso, compon].setub(min(15, log((1 - m.aabs[compon] ** (m.nabs[1].ub * m.abseff + m.eps1 + 1)) / (1 - m.aabs[compon])))) m.t[67].setlb(3.0) m.t[67].setub(3.0) for compon in m.compon: @@ -789,61 +813,85 @@ def boundsofe(m): m.spl1t[i+1] = spl1t.to_numpy()[i, 0] # ## constraints - - - m.specrec = Constraint(expr=m.fc[72, 'h2'] >= 0.5 * m.f[72]) - - m.specprod = Constraint(expr=m.fc[31, 'ben'] >= 0.9997 * m.f[31]) + m.specrec = Constraint(expr=m.fc[72, 'h2'] >= 0.5 * m.f[72], doc='specification on h2 reccycle') + m.specprod = Constraint(expr=m.fc[31, 'ben'] >= 0.9997 * m.f[31], doc='specification on benzene production') def Fbal(_m, stream): return m.f[stream] == sum(m.fc[stream, compon] for compon in m.compon) - m.fbal = Constraint(m.str, rule=Fbal) + m.fbal = Constraint(m.str, rule=Fbal, doc='flow balance') def H2feed(m, compon): return m.fc[1, compon] == m.f[1] * m.f1comp[compon] - m.h2feed = Constraint(m.compon, rule=H2feed) + m.h2feed = Constraint(m.compon, rule=H2feed, doc='h2 feed composition') def Tolfeed(_m, compon): return m.fc[66, compon] == m.f[66] * m.f66comp[compon] - m.tolfeed = Constraint(m.compon, rule=Tolfeed) + m.tolfeed = Constraint(m.compon, rule=Tolfeed, doc='toluene feed composition') def Tolabs(_m, compon): return m.fc[67, compon] == m.f[67] * m.f67comp[compon] - m.tolabs = Constraint(m.compon, rule=Tolabs) - + m.tolabs = Constraint(m.compon, rule=Tolabs, doc='toluene absorber composition') + def build_absorber(b, absorber): - " Function for absorber" + """ + Functions relevant to the absorber block + + Parameters + ---------- + b : Pyomo Block + absorber block + absorber : int + Index of the absorber + """ def Absfact(_m, i, compon): + """ + Absorption factor equation + sum of flowrates of feed components = sum of flowrates of vapor components * absorption factor * sum of vapor pressures + + """ if (i, compon) in m.anorm: return sum(m.f[stream] * m.p[stream] for (absb, stream) in m.ilabs if absb == i) == sum(m.f[stream] for (absc, stream) in m.ivabs if absc == i) * m.aabs[compon] * sum(m.vp[stream, compon] for (absd, stream) in m.ilabs if absd == i) return Constraint.Skip - b.absfact = Constraint([absorber], m.compon, rule=Absfact, doc='absorbption factor equation') + b.absfact = Constraint([absorber], m.compon, rule=Absfact, doc='absorption factor equation') def Gameqn(_m, i, compon): + # definition of gamma if (i, compon) in m.asolv: return m.gamma[i, compon] == log((1 - m.aabs[compon] ** (m.nabs[i] * m.abseff + m.eps1)) / (1 - m.aabs[compon])) return Constraint.Skip b.gameqn = Constraint([absorber], m.compon, rule=Gameqn, doc='definition of gamma') def Betaeqn(_m, i, compon): + # definition of beta if (i, compon) not in m.asimp: return m.beta[i, compon] == log((1 - m.aabs[compon] ** (m.nabs[i] * m.abseff + 1)) / (1 - m.aabs[compon])) return Constraint.Skip + b.betaeqn = Constraint([absorber], m.compon, + rule=Betaeqn, doc='definition of beta') def Abssvrec(_m, i, compon): + # recovery of solvent if (i, compon) in m.asolv: return sum(m.fc[stream, compon] for (i, stream) in m.ovabs) * exp(m.beta[i, compon]) == sum(m.fc[stream, compon] for (i_, stream) in m.ivabs) + exp(m.gamma[i, compon]) * sum(m.fc[stream, compon] for (i_, stream) in m.ilabs) return Constraint.Skip + b.abssvrec = Constraint([absorber], m.compon, + rule=Abssvrec, doc='recovery of solvent') def Absrec(_m, i, compon): + # recovery of non-solvent if (i, compon) in m.anorm: return sum(m.fc[i, compon] for (abs, i) in m.ovabs) * exp(m.beta[i, compon]) == sum(m.fc[i, compon] for(abs, i) in m.ivabs) return Constraint.Skip + b.absrec = Constraint([absorber], m.compon, + rule=Absrec, doc='recovery of non-solvent') def abssimp(_m, absorb, compon): + # recovery of simplified components if (absorb, compon) in m.asimp: return sum(m.fc[i, compon] for (absorb, i) in m.ovabs) == sum(m.fc[i, compon] for (absorb, i) in m.ivabs) / m.cbeta[compon] return Constraint.Skip + b.abssimp = Constraint( + [absorber], m.compon, rule=abssimp, doc='recovery of simplified components') def Abscmb(_m, i, compon): return sum(m.fc[stream, compon] for (i, stream) in m.ilabs) + sum(m.fc[stream, compon] for (i, stream) in m.ivabs) == sum(m.fc[stream, compon] for (i, stream) in m.olabs) + sum(m.fc[stream, compon] for (i, stream) in m.ovabs) @@ -858,31 +906,33 @@ def Abspl(_m, i): def Abstl(_m, i): return sum(m.t[stream] for (_, stream) in m.ilabs) == sum(m.t[stream] for (_, stream) in m.olabs) b.abstl = Constraint([absorber], rule=Abstl, - doc=' temperature relation for liquid') + doc='temperature relation for liquid') def Abspv(_m, i): return sum(m.p[stream] for (_, stream) in m.ivabs) == sum(m.p[stream] for (_, stream) in m.ovabs) b.abspv = Constraint([absorber], rule=Abspv, - doc=' pressure relation for vapor') + doc='pressure relation for vapor') def Abspin(_m, i): return sum(m.p[stream] for (_, stream) in m.ilabs) == sum(m.p[stream] for (_, stream) in m.ivabs) - b.absp = Constraint([absorber], rule=Abspin) + b.absp = Constraint([absorber], rule=Abspin, doc='pressure relation at inlet') def Absttop(_m, i): return sum(m.t[stream] for (_, stream) in m.ilabs) == sum(m.t[stream] for (_, stream) in m.ovabs) b.abst = Constraint([absorber], rule=Absttop, doc='temperature relation at top') - b.abssimp = Constraint( - [absorber], m.compon, rule=abssimp, doc=' recovery of simplified components') - b.absrec = Constraint([absorber], m.compon, - rule=Absrec, doc='recovery of non-solvent') - b.abssvrec = Constraint([absorber], m.compon, - rule=Abssvrec, doc='recovery of solvent') - b.betaeqn = Constraint([absorber], m.compon, - rule=Betaeqn, doc='definition of beta') def build_compressor(b, comp): + """ + Functions relevant to the compressor block + + Parameters + ---------- + b : Pyomo Block + compressor block + comp : int + Index of the compressor + """ def Compcmb(_m, comp1, compon): if comp1 == comp: return sum(m.fc[stream, compon] for (comp_, stream) in m.ocomp if comp_ == comp1) == sum(m.fc[stream, compon] for (comp_, stream) in m.icomp if comp_ == comp1) @@ -913,19 +963,29 @@ def Ratio(_m, comp_): m.vapor_pressure_unit_match = Param( - initialize=7500.6168, doc="unit match coeffieicnt for vapor pressure calculation") + initialize=7500.6168, doc="unit match coefficient for vapor pressure calculation") m.actual_reflux_ratio = Param( - initialize=1.2, doc="actual reflux ratio coeffieicnt") - m.recovery_specification_coeffieicnt = Param( - initialize=0.05, doc="recovery specification coeffieicnt") + initialize=1.2, doc="actual reflux ratio coefficient") + m.recovery_specification_coefficient = Param( + initialize=0.05, doc="recovery specification coefficient") def build_distillation(b, dist): + """ + Functions relevant to the distillation block + + Parameters + ---------- + b : Pyomo Block + distillation block + dist : int + Index of the distillation column + """ def Antdistb(_m, dist_, stream, compon): if (dist_, stream) in m.ldist and (dist_, compon) in m.dkey and dist_ == dist: return log(m.vp[stream, compon] * m.vapor_pressure_unit_match) == m.anta[compon] - m.antb[compon] / (m.t[stream] * 100. + m.antc[compon]) return Constraint.Skip b.antdistb = Constraint( - [dist], m.str, m.compon, rule=Antdistb, doc=' vapor pressure correlation (bot)') + [dist], m.str, m.compon, rule=Antdistb, doc='vapor pressure correlation (bot)') def Antdistt(_m, dist_, stream, compon): if (dist_, stream) in m.vdist and (dist_, compon) in m.dkey and dist == dist_: @@ -943,9 +1003,10 @@ def Relvol(_m, dist_): return m.avevlt[dist] == sqrt(divided1 * divided2) return Constraint.Skip b.relvol = Constraint([dist], rule=Relvol, - doc='average relative volatilty') + doc='average relative volatility') def Undwood(_m, dist_): + # minimum reflux ratio from Underwood equation if dist_ == dist: return sum(m.fc[stream, compon] for (dist1, compon) in m.dlkey if dist1 == dist_ for (dist1, stream) in m.idist if dist1 == dist_) * m.rmin[dist_] * (m.avevlt[dist_] - 1) == sum(m.f[stream] for (dist1, stream) in m.idist if dist1 == dist_) return Constraint.Skip @@ -953,6 +1014,7 @@ def Undwood(_m, dist_): doc='minimum reflux ratio equation') def Actreflux(_m, dist_): + # actual reflux ratio (heuristic) if dist_ == dist: return m.reflux[dist_] == m.actual_reflux_ratio * m.rmin[dist_] return Constraint.Skip @@ -960,6 +1022,7 @@ def Actreflux(_m, dist_): [dist], rule=Actreflux, doc='actual reflux ratio') def Fenske(_m, dist_): + # minimum number of trays from Fenske equation if dist == dist_: sum1 = sum((m.f[stream] + m.eps1)/(m.fc[stream, compon] + m.eps1) for (dist1, compon) in m.dhkey if dist1 == dist_ for (dist1, stream) in m.vdist if dist1 == dist_) @@ -971,6 +1034,7 @@ def Fenske(_m, dist_): doc='minimum number of trays') def Acttray(_m, dist_): + # actual number of trays (gillilands approximation) if dist == dist_: return m.ndist[dist_] == m.nmin[dist_] * 2. / m.disteff return Constraint.Skip @@ -979,7 +1043,7 @@ def Acttray(_m, dist_): def Distspec(_m, dist_, stream, compon): if (dist_, stream) in m.vdist and (dist_, compon) in m.dhkey and dist_ == dist: - return m.fc[stream, compon] <= m.recovery_specification_coeffieicnt * sum(m.fc[str2, compon] for (dist_, str2) in m.idist if dist == dist_) + return m.fc[stream, compon] <= m.recovery_specification_coefficient * sum(m.fc[str2, compon] for (dist_, str2) in m.idist if dist == dist_) return Constraint.Skip b.distspec = Constraint([dist], m.str, m.compon, rule=Distspec, doc='recovery specification') @@ -1024,14 +1088,14 @@ def Distpl(_m, dist_, stream): return m.distp[dist_] == m.p[stream] return Constraint.Skip b.distpl = Constraint([dist], m.str, rule=Distpl, - doc='outlet pressure relation(liquid)') + doc='outlet pressure relation (liquid)') def Distpv(_m, dist_, stream): if (dist_, stream) in m.vdist and dist == dist_: return m.distp[dist_] == m.p[stream] return Constraint.Skip b.distpv = Constraint([dist], m.str, rule=Distpv, - doc='outlet pressure relation(vapor)') + doc='outlet pressure relation (vapor)') def Distcmb(_m, dist_, compon): if dist_ == dist: @@ -1043,6 +1107,16 @@ def Distcmb(_m, dist_, compon): def build_flash(b, flsh): + """ + Functions relevant to the flash block + + Parameters + ---------- + b : Pyomo Block + flash block + flsh : int + Index of the flash + """ def Flshcmb(_m, flsh_, compon): if flsh_ in m.flsh and compon in m.compon and flsh_ == flsh: return sum(m.fc[stream, compon] for (flsh1, stream) in m.iflsh if flsh1 == flsh_) == sum(m.fc[stream, compon] for (flsh1, stream) in m.vflsh if flsh1 == flsh_) + sum(m.fc[stream, compon] for (flsh1, stream) in m.lflsh if flsh1 == flsh_) @@ -1090,14 +1164,14 @@ def Flshpl(_m, flsh_, stream): return m.flshp[flsh_] == m.p[stream] return Constraint.Skip b.flshpl = Constraint([flsh], m.str, rule=Flshpl, - doc='outlet pressure relation(liquid)') + doc='outlet pressure relation (liquid)') def Flshpv(_m, flsh_, stream): if (flsh_, stream) in m.vflsh and flsh_ == flsh: return m.flshp[flsh_] == m.p[stream] return Constraint.Skip b.flshpv = Constraint([flsh], m.str, rule=Flshpv, - doc='outlet pressure relation(vapor)') + doc='outlet pressure relation (vapor)') def Flshti(_m, flsh_, stream): if (flsh_, stream) in m.iflsh and flsh_ == flsh: @@ -1111,25 +1185,35 @@ def Flshtl(_m, flsh_, stream): return m.flsht[flsh_] == m.t[stream] return Constraint.Skip b.flshtl = Constraint([flsh], m.str, rule=Flshtl, - doc='outlet temp. relation(liquid)') + doc='outlet temp. relation (liquid)') def Flshtv(_m, flsh_, stream): if (flsh_, stream) in m.vflsh and flsh_ == flsh: return m.flsht[flsh_] == m.t[stream] return Constraint.Skip b.flshtv = Constraint([flsh], m.str, rule=Flshtv, - doc='outlet temp. relation(vapor)') + doc='outlet temp. relation (vapor)') m.heat_unit_match = Param( initialize=3600. * 8500. * 1.0e-12 / 60., doc="unit change on temp") def build_furnace(b, furnace): + """ + Functions relevant to the furnace block + + Parameters + ---------- + b : Pyomo Block + furnace block + furnace : int + Index of the furnace + """ def Furnhb(_m, furn): if furn == furnace: return m.qfuel[furn] == (sum(m.cp[stream] * m.f[stream] * 100. * m.t[stream] for (furn, stream) in m.ofurn) - sum(m.cp[stream] * m.f[stream] * 100. * m.t[stream] for (furn, stream) in m.ifurn)) * m.heat_unit_match return Constraint.Skip - b.furnhb = Constraint([furnace], rule=Furnhb, doc='heat balance') + b.furnhb = Constraint([furnace], rule=Furnhb, doc='heat balance for furnace') def Furncmb(_m, furn, compon): if furn == furnace: @@ -1142,15 +1226,25 @@ def Furnp(_m, furn): if furn == furnace: return sum(m.p[stream] for (furn, stream) in m.ofurn) == sum(m.p[stream] for (furn, stream) in m.ifurn) - m.furnpdrop return Constraint.Skip - b.furnp = Constraint([furnace], rule=Furnp, doc=' pressure relation ') + b.furnp = Constraint([furnace], rule=Furnp, doc='pressure relation') def build_cooler(b, cooler): + """ + Functions relevant to the cooler block + + Parameters + ---------- + b : Pyomo Block + cooler block + cooler : int + Index of the cooler + """ def Heccmb(_m, hec, compon): return sum(m.fc[stream, compon] for (hec_, stream) in m.ohec if hec_ == hec) == sum(m.fc[stream, compon] for (hec_, stream) in m.ihec if hec_ == hec) b.heccmb = Constraint([cooler], m.compon, - rule=Heccmb, doc='heat balance') + rule=Heccmb, doc='heat balance for cooler') def Hechb(_m, hec): return m.qc[hec] == (sum(m.cp[stream] * m.f[stream] * 100. * m.t[stream] for (hec_, stream) in m.ihec if hec_ == hec) - sum(m.cp[stream] * m.f[stream] * 100. * m.t[stream] for (hec_, stream) in m.ohec if hec_ == hec)) * m.heat_unit_match @@ -1164,6 +1258,16 @@ def Hecp(_m, hec): def build_heater(b, heater): + """ + Functions relevant to the heater block + + Parameters + ---------- + b : Pyomo Block + heater block + heater : int + Index of the heater + """ def Hehcmb(_m, heh, compon): if heh == heater and compon in m.compon: return sum(m.fc[stream, compon] for (heh_, stream) in m.oheh if heh_ == heh) == sum(m.fc[stream, compon] for (heh_, stream) in m.iheh if heh == heh_) @@ -1190,6 +1294,16 @@ def hehp(_m, heh): m.exchanger_temp_drop = Param(initialize=0.25) def build_exchanger(b, exchanger): + """ + Functions relevant to the exchanger block + + Parameters + ---------- + b : Pyomo Block + exchanger block + exchanger : int + Index of the exchanger + """ def Exchcmbc(_m, exch, compon): if exch in m.exch and compon in m.compon: return sum(m.fc[stream, compon] for (exch_, stream) in m.ocexch if exch == exch_) == sum(m.fc[stream, compon] for (exch_, stream) in m.icexch if exch == exch_) @@ -1214,7 +1328,7 @@ def Exchhbc(_m, exch): def Exchhbh(_m, exch): return (sum(m.cp[stream] * m.f[stream] * 100. * m.t[stream] for (exch, stream) in m.ihexch) - sum(m.cp[stream] * m.f[stream] * 100. * m.t[stream] for (exch, stream) in m.ohexch)) * m.heat_unit_match == m.qexch[exch] b.exchhbh = Constraint([exchanger], rule=Exchhbh, - doc='heat balance for hot stream') + doc='heat balance for hot stream') def Exchdtm1(_m, exch): return sum(m.t[stream] for (exch, stream) in m.ohexch) >= sum(m.t[stream] for (exch, stream) in m.icexch) + m.exchanger_temp_drop @@ -1229,18 +1343,28 @@ def Exchdtm2(_m, exch): def Exchpc(_m, exch): return sum(m.p[stream] for (exch, stream) in m.ocexch) == sum(m.p[stream] for (exch, stream) in m.icexch) b.exchpc = Constraint([exchanger], rule=Exchpc, - doc='pressure relation (cold)') + doc='pressure relation (cold)') def Exchph(_m, exch): return sum(m.p[stream] for (exch, stream) in m.ohexch) == sum(m.p[stream] for (exch, stream) in m.ihexch) b.exchph = Constraint([exchanger], rule=Exchph, - doc='pressure relation (hot)') + doc='pressure relation (hot)') m.membrane_recovery_sepc = Param(initialize=0.50) m.membrane_purity_sepc = Param(initialize=0.50) def build_membrane(b, membrane): + """ + Functions relevant to the membrane block + + Parameters + ---------- + b : Pyomo Block + membrane block + membrane : int + Index of the membrane + """ def Memcmb(_m, memb, stream, compon): if (memb, stream) in m.imemb and memb == membrane: return m.fc[stream, compon] == sum(m.fc[stream, compon] for (memb_, stream) in m.pmemb if memb == memb_) + sum(m.fc[stream, compon] for (memb_, stream) in m.nmemb if memb == memb_) @@ -1305,6 +1429,16 @@ def Pure(_m, memb, stream): def build_multiple_mixer(b, multiple_mxr): + """ + Functions relevant to the mixer block + + Parameters + ---------- + b : Pyomo Block + mixer block + multiple_mxr : int + Index of the mixer + """ def Mxrcmb(_b, mxr, compon): if mxr == multiple_mxr: return sum(m.fc[stream, compon] for (mxr_, stream) in m.omxr if mxr == mxr_) == sum(m.fc[stream, compon] for (mxr_, stream) in m.imxr if mxr == mxr_) @@ -1324,7 +1458,7 @@ def Mxrhbq(_b, mxr): return m.f[16] * m.t[16] == m.f[15] * m.t[15] - (m.fc[20, 'ben'] + m.fc[20, 'tol']) * m.heatvap['tol'] / (100. * m.cp[15]) return Constraint.Skip b.mxrhbq = Constraint([multiple_mxr], rule=Mxrhbq, - doc=' heat balance in quench') + doc='heat balance in quench') def Mxrpi(_b, mxr, stream): if (mxr, stream) in m.imxr and mxr == multiple_mxr: @@ -1343,95 +1477,135 @@ def Mxrpo(_b, mxr, stream): def build_pump(b, pump_): + """ + Functions relevant to the pump block + + Parameters + ---------- + b : Pyomo Block + pump block + pump_ : int + Index of the pump + """ def Pumpcmb(_m, pump, compon): if pump == pump_ and compon in m.compon: return sum(m.fc[stream, compon] for (pump_, stream) in m.opump if pump == pump_) == sum(m.fc[stream, compon] for (pump_, stream) in m.ipump if pump_ == pump) return Constraint.Skip b.pumpcmb = Constraint( - [pump_], m.compon, rule=Pumpcmb, doc='component balance') + [pump_], m.compon, rule=Pumpcmb, doc='component balance for pump') def Pumphb(_m, pump): if pump == pump_: return sum(m.t[stream] for (pump_, stream) in m.opump if pump == pump_) == sum(m.t[stream] for (pump_, stream) in m.ipump if pump == pump_) return Constraint.Skip - b.pumphb = Constraint([pump_], rule=Pumphb, doc='heat balance') + b.pumphb = Constraint([pump_], rule=Pumphb, doc='heat balance for pump') def Pumppr(_m, pump): if pump == pump_: return sum(m.p[stream] for (pump_, stream) in m.opump if pump == pump_) >= sum(m.p[stream] for (pump_, stream) in m.ipump if pump == pump_) return Constraint.Skip - b.pumppr = Constraint([pump_], rule=Pumppr, doc='pressure relation') + b.pumppr = Constraint([pump_], rule=Pumppr, doc='pressure relation for pump') def build_multiple_splitter(b, multi_splitter): + """ + Functions relevant to the splitter block + + Parameters + ---------- + b : Pyomo Block + splitter block + multi_splitter : int + Index of the splitter + """ def Splcmb(_m, spl, stream, compon): if (spl, stream) in m.ospl and spl == multi_splitter: return m.fc[stream, compon] == sum(m.e[stream]*m.fc[str2, compon] for (spl_, str2) in m.ispl if spl == spl_) return Constraint.Skip b.splcmb = Constraint([multi_splitter], m.str, m.compon, - rule=Splcmb, doc='component balance in splitter') + rule=Splcmb, doc='component balance for splitter') def Esum(_m, spl): if spl in m.spl and spl == multi_splitter: return sum(m.e[stream] for (spl_, stream) in m.ospl if spl_ == spl) == 1.0 return Constraint.Skip b.esum = Constraint([multi_splitter], rule=Esum, - doc='split fraction relation') + doc='split fraction relation for splitter') def Splpi(_m, spl, stream): if (spl, stream) in m.ispl and spl == multi_splitter: return m.splp[spl] == m.p[stream] return Constraint.Skip b.splpi = Constraint([multi_splitter], m.str, - rule=Splpi, doc='inlet pressure relation') + rule=Splpi, doc='inlet pressure relation (splitter)') def Splpo(_m, spl, stream): if (spl, stream) in m.ospl and spl == multi_splitter: return m.splp[spl] == m.p[stream] return Constraint.Skip b.splpo = Constraint([multi_splitter], m.str, - rule=Splpo, doc='outlet pressure relation') + rule=Splpo, doc='outlet pressure relation (splitter)') def Splti(_m, spl, stream): if (spl, stream) in m.ispl and spl == multi_splitter: return m.splt[spl] == m.t[stream] return Constraint.Skip b.splti = Constraint([multi_splitter], m.str, - rule=Splti, doc='inlet temperature relation') + rule=Splti, doc='inlet temperature relation (splitter)') def Splto(_m, spl, stream): if (spl, stream) in m.ospl and spl == multi_splitter: return m.splt[spl] == m.t[stream] return Constraint.Skip b.splto = Constraint([multi_splitter], m.str, - rule=Splto, doc='outlet temperature relation') + rule=Splto, doc='outlet temperature relation (splitter)') def build_valve(b, valve_): + """ + Functions relevant to the valve block + + Parameters + ---------- + b : Pyomo Block + valve block + valve_ : int + Index of the valve + """ def Valcmb(_m, valve, compon): return sum(m.fc[stream, compon] for (valve_, stream) in m.oval if valve == valve_) == sum(m.fc[stream, compon] for (valve_, stream) in m.ival if valve == valve_) - b.valcmb = Constraint([valve_], m.compon, rule=Valcmb, doc='valcmb') + b.valcmb = Constraint([valve_], m.compon, rule=Valcmb, doc='component balance for valve') def Valt(_m, valve): return sum(m.t[stream] / (m.p[stream] ** ((m.gam - 1.) / m.gam)) for (valv, stream) in m.oval if valv == valve) == sum(m.t[stream] / (m.p[stream] ** ((m.gam - 1.) / m.gam)) for (valv, stream) in m.ival if valv == valve) - b.valt = Constraint([valve_], rule=Valt, doc='temperature relation') + b.valt = Constraint([valve_], rule=Valt, doc='temperature relation for valve') def Valp(_m, valve): return sum(m.p[stream] for (valv, stream) in m.oval if valv == valve) <= sum(m.p[stream] for (valv, stream) in m.ival if valv == valve) - b.valp = Constraint([valve_], rule=Valp, doc='pressure relation') + b.valp = Constraint([valve_], rule=Valp, doc='pressure relation for valve') m.Prereference_factor = Param( initialize=6.3e+10, doc="Pre-reference factor for reaction rate constant") - m.Ea_R = Param(initialize=-26167.) - m.pressure_drop = Param(initialize=0.20684) - m.selectivity_1 = Param(initialize=0.0036) - m.selectivity_2 = Param(initialize=-1.544) - m.conversion_coefficient = Param(initialize=0.372) + m.Ea_R = Param(initialize=-26167.,doc="Activation energy for reaction rate constant") + m.pressure_drop = Param(initialize=0.20684, doc="Pressure drop") + m.selectivity_1 = Param(initialize=0.0036, doc="Selectivity to benzene") + m.selectivity_2 = Param(initialize=-1.544, doc="Selectivity to benzene") + m.conversion_coefficient = Param(initialize=0.372, doc="Conversion coefficient") def build_reactor(b, rct): + """ + Functions relevant to the reactor block + + Parameters + ---------- + b : Pyomo Block + reactor block + rct : int + Index of the reactor + """ def rctspec(_m, rct, stream): if (rct, stream) in m.irct: return m.fc[stream, 'h2'] >= 5 * (m.fc[stream, 'ben'] + m.fc[stream, 'tol'] + m.fc[stream, 'dip']) @@ -1454,7 +1628,7 @@ def rctconv(_m, rct, stream, compon): def rctsel(_m, rct): return (1. - m.sel[rct]) == m.selectivity_1 * (1. - m.conv[rct, 'tol']) ** m.selectivity_2 b.Rctsel = Constraint([rct], rule=rctsel, - doc=' selectivity to benzene') + doc='selectivity to benzene') def rctcns(_m, rct, stream, compon): if (rct, compon) in m.rkey and (rct, stream) in m.irct: @@ -1470,15 +1644,15 @@ def rctmbtol(_m, rct): def rctmbben(_m, rct): return sum(m.fc[stream, 'ben'] for (rct_, stream) in m.orct if rct_ == rct) == sum(m.fc[stream, 'ben'] for (rct_, stream) in m.irct if rct_ == rct) + m.consum[rct, 'tol'] * m.sel[rct] - b.Rctmbben = Constraint([rct], rule=rctmbben) + b.Rctmbben = Constraint([rct], rule=rctmbben, doc='mass balance in reactor (ben)') def rctmbdip(_m, rct): return sum(m.fc[stream, 'dip'] for (rct1, stream) in m.orct if rct1 == rct) == sum(m.fc[stream, 'dip'] for (rct1, stream) in m.irct if rct1 == rct) + m.consum[rct, 'tol'] * 0.5 + (sum(m.fc[stream, 'ben'] for (rct1, stream) in m.irct if rct1 == rct) - sum(m.fc[stream, 'ben'] for (rct1, stream) in m.orct if rct1 == rct)) * 0.5 - b.Rctmbdip = Constraint([rct], rule=rctmbdip) + b.Rctmbdip = Constraint([rct], rule=rctmbdip, doc='mass balance in reactor (dip)') def rctmbh2(_m, rct): return sum(m.fc[stream, 'h2'] for (rct1, stream) in m.orct if rct1 == rct) == sum(m.fc[stream, 'h2'] for (rct1, stream) in m.irct if rct1 == rct) - m.consum[rct, 'tol'] - sum(m.fc[stream, 'dip'] for (rct1, stream) in m.irct if rct1 == rct) + sum(m.fc[stream, 'dip'] for (rct1, stream) in m.orct if rct1 == rct) - b.Rctmbh2 = Constraint([rct], rule=rctmbh2) + b.Rctmbh2 = Constraint([rct], rule=rctmbh2, doc='mass balance in reactor (h2)') def rctpi(_m, rct, stream): if (rct, stream) in m.irct: @@ -1497,7 +1671,7 @@ def rctpo(_m, rct, stream): def rcttave(_m, rct): return m.rctt[rct] == (sum(m.t[stream] for (rct1, stream) in m.irct if rct1 == rct) + sum(m.t[stream] for (rct1, stream) in m.orct if rct1 == rct))/2 b.Rcttave = Constraint([rct], rule=rcttave, - doc='average temperature relation ') + doc='average temperature relation') def Rctmbch4(_m, rct): return sum(m.fc[stream, 'ch4'] for (rct_, stream) in m.orct if rct_ == rct) == sum(m.fc[stream, 'ch4'] for (rct_, stream) in m.irct if rct == rct_) + m.consum[rct, 'tol'] @@ -1528,6 +1702,16 @@ def Rctisot(_m, rct): def build_single_mixer(b, mixer): + """ + Functions relevant to the single mixer block + + Parameters + ---------- + b : Pyomo Block + single mixer block + mixer : int + Index of the mixer + """ def Mxr1cmb(m_, mxr1, str1, compon): if (mxr1, str1) in m.omxr1 and mxr1 == mixer: return m.fc[str1, compon] == sum(m.fc[str2, compon] for (mxr1_, str2) in m.imxr1 if mxr1_ == mxr1) @@ -1540,6 +1724,16 @@ def Mxr1cmb(m_, mxr1, str1, compon): # single output splitter def build_single_splitter(b, splitter): + """ + Functions relevant to the single splitter block + + Parameters + ---------- + b : Pyomo Block + single splitter block + splitter : int + Index of the splitter + """ def Spl1cmb(m_, spl1, compon): return sum(m.fc[str1, compon] for (spl1_, str1) in m.ospl1 if spl1_ == spl1) == sum(m.fc[str1, compon] for (spl1_, str1) in m.ispl1 if spl1_ == spl1) b.spl1cmb = Constraint( @@ -1550,34 +1744,32 @@ def Spl1pi(m_, spl1, str1): return m.spl1p[spl1] == m.p[str1] return Constraint.Skip b.spl1pi = Constraint([splitter], m.str, rule=Spl1pi, - doc='inlet pressure relation') + doc='inlet pressure relation (splitter)') def Spl1po(m_, spl1, str1): if (spl1, str1) in m.ospl1: return m.spl1p[spl1] == m.p[str1] return Constraint.Skip b.spl1po = Constraint([splitter], m.str, rule=Spl1po, - doc='outlet pressure relation') + doc='outlet pressure relation (splitter)') def Spl1ti(m_, spl1, str1): if (spl1, str1) in m.ispl1: return m.spl1t[spl1] == m.t[str1] return Constraint.Skip b.spl1ti = Constraint([splitter], m.str, rule=Spl1ti, - doc='inlet temperature relation') + doc='inlet temperature relation (splitter)') def Spl1to(m_, spl1, str1): if (spl1, str1) in m.ospl1: return m.spl1t[spl1] == m.t[str1] return Constraint.Skip b.spl1to = Constraint([splitter], m.str, rule=Spl1to, - doc='outlet temperature relation') + doc='outlet temperature relation (splitter)') m.single_splitter = Block(m.spl1, rule=build_single_splitter) # ## GDP formulation - - m.one = Set(initialize=[1]) m.two = Set(initialize=[2]) m.three = Set(initialize=[3]) @@ -1611,8 +1803,8 @@ def inlet_treatment(m): m.multi_mixer_1 = Block(m.one, rule=build_multiple_mixer) m.furnace_1 = Block(m.one, rule=build_furnace) - # Second disjunction: Adiabatic or isothermal reactor + # Second disjunction: Adiabatic or isothermal reactor @m.Disjunct() def adiabatic_reactor(disj): disj.Adiabatic_reactor = Block(m.one, rule=build_reactor) @@ -1633,8 +1825,6 @@ def isothermal_reactor(disj): def reactor_selection(m): return[m.adiabatic_reactor, m.isothermal_reactor] - - m.valve_3 = Block(m.three, rule=build_valve) m.multi_mixer_2 = Block(m.two, rule=build_multiple_mixer) m.exchanger_1 = Block(m.one, rule=build_exchanger) @@ -1642,6 +1832,7 @@ def reactor_selection(m): m.flash_1 = Block(m.one, rule=build_flash) m.multi_splitter_2 = Block(m.two, rule=build_multiple_splitter) + # thrid disjunction: recycle methane with membrane or purge it @m.Disjunct() def recycle_methane_purge(disj): @@ -1692,12 +1883,7 @@ def absorber_hydrogen(disj): def recycle_selection(m): return [m.recycle_hydrogen, m.absorber_hydrogen] - - m.multi_mixer_5 = Block(m.five, rule=build_multiple_mixer) - - - m.multi_mixer_3 = Block(m.three, rule=build_multiple_mixer) m.multi_splitter_1 = Block(m.one, rule=build_multiple_splitter) @@ -1744,8 +1930,6 @@ def methane_flash_separation(disj): def H2_selection(m): return [m.methane_distillation_column, m.methane_flash_separation] - - m.benzene_column = Block(m.two, rule=build_distillation) @@ -1776,40 +1960,39 @@ def toluene_flash_separation(disj): def toluene_selection(m): return [m.toluene_distillation_column, m.toluene_flash_separation] - - m.pump_1 = Block(m.one, rule=build_pump) m.abound = Constraint(expr=m.a[1] >= 0.0) + # ## objective function m.hydrogen_purge_value = Param(initialize = 1.08,doc = "heating value of hydrogen purge") - m.electricity_cost = Param(initialize = 0.04 * 24 * 365 / 1000 , doc ="electricity cost, value is 0.04 with the unit of kw/h, now is kw/yr/k$") + m.electricity_cost = Param(initialize = 0.04 * 24 * 365 / 1000 , doc ="electricity cost, value is 0.04 with the unit of [kw/h], now is [kw/yr/k$]") m.meathane_purge_value = Param(initialize = 3.37, doc = "heating value of meathane purge") - m.heating_cost = Param(initialize = 8000., doc = "Heating cost(steam) with unit 1e6 KJ") - m.cooling_cost = Param(initialize = 700.0, doc = "heating cost (water) with unit 1e6 KJ") - m.fuel_cost = Param(initialize = 4000.0 ,doc = "fuel cost with unit 1e6 KJ") - m.abs_fixed_cost = Param(initialize = 13, doc = "fixed cost of absober ($1e3 per year)") - m.abs_linear_coeffcient = Param(initialize = 1.2, doc = "linear coeffcient of absorber (times tray number) ($1e3 per year)") - m.compressor_fixed_cost = Param(initialize = 7.155, doc = "compressor fixed cost ($1e3 per year)") - m.compressor_fixed_cost_4 = Param(initialize = 4.866, doc = "compressor fixed cost for compressor 4 ($1e3 per year)") - m.compressor_linear_coeffcient = Param(initialize = 0.815 ,doc = "compressor linear coeffcient (vaporflow rate) ($1e3 per year)") - m.compressor_linear_coeffcient_4 = Param(initialize = 0.887 ,doc = "compressor linear coeffcient (vaporflow rate) ($1e3 per year)") - m.stabilizing_column_fixed_cost = Param(initialize = 1.126 ,doc = "stabilizing column fixed cost ($1e3 per year)") - m.stabilizing_column_linear_coeffcient = Param(initialize = 0.375 ,doc = "stabilizing column linear coeffcient (times number of trays) ($1e3 per year)") - m.benzene_column_fixed_cost = Param(initialize = 16.3 ,doc = "benzene column fixed cost ($1e3 per year)") - m.benzene_column_linear_coeffcient = Param(initialize = 1.55 ,doc = "benzene column linear coeffcient (times number of trays) ($1e3 per year)") - m.toluene_column_fixed_cost = Param(initialize = 3.9 ,doc = "toluene column fixed cost ($1e3 per year)") - m.toluene_column_linear_coeffcient = Param(initialize = 1.12 ,doc = "toluene column linear coeffcient (times number of trays) ($1e3 per year)") - m.furnace_fixed_cost = Param(initialize = 6.20 ,doc = "toluene column fixed cost ($1e3 per year)") - m.furnace_linear_coeffcient = Param(initialize = 1171.7 ,doc = "furnace column linear coeffcient (1e9KJ/yr) ($1e3 per year)") - m.membrane_seperator_fixed_cost = Param(initialize = 43.24 ,doc = "membrane seperator fixed cost ($1e3 per year)") - m.membrane_seperator_linear_coeffcient = Param(initialize = 49.0 ,doc = "furnace column linear coeffcient (times inlet flowrate) ($1e3 per year)") - m.adiabtic_reactor_fixed_cost = Param(initialize = 74.3 ,doc = "adiabtic reactor fixed cost ($1e3 per year)") - m.adiabtic_reactor_linear_coeffcient = Param(initialize = 1.257 ,doc = "adiabtic reactor linear coeffcient (times reactor volumn) ($1e3 per year)") - m.isothermal_reactor_fixed_cost = Param(initialize = 92.875 ,doc = "isothermal reactor fixed cost ($1e3 per year)") - m.isothermal_reactor_linear_coeffcient = Param(initialize = 1.57125 ,doc = "isothermal reactor linear coeffcient (times reactor volumn) ($1e3 per year)") - m.h2_feed_cost = Param(initialize = 2.5, doc = "h2 feed cost (95% h2,5% Ch4)") + m.heating_cost = Param(initialize = 8000., doc = "Heating cost (steam) with unit [1e6 kj]") + m.cooling_cost = Param(initialize = 700.0, doc = "heating cost (water) with unit [1e6 kj]") + m.fuel_cost = Param(initialize = 4000.0 ,doc = "fuel cost with unit [1e6 kj]") + m.abs_fixed_cost = Param(initialize = 13, doc = "fixed cost of absober [$1e3 per year]") + m.abs_linear_coeffcient = Param(initialize = 1.2, doc = "linear coeffcient of absorber (times tray number) [$1e3 per year]") + m.compressor_fixed_cost = Param(initialize = 7.155, doc = "compressor fixed cost [$1e3 per year]") + m.compressor_fixed_cost_4 = Param(initialize = 4.866, doc = "compressor fixed cost for compressor 4 [$1e3 per year]") + m.compressor_linear_coeffcient = Param(initialize = 0.815 ,doc = "compressor linear coeffcient (vaporflow rate) [$1e3 per year]") + m.compressor_linear_coeffcient_4 = Param(initialize = 0.887 ,doc = "compressor linear coeffcient (vaporflow rate) [$1e3 per year]") + m.stabilizing_column_fixed_cost = Param(initialize = 1.126 ,doc = "stabilizing column fixed cost [$1e3 per year]") + m.stabilizing_column_linear_coeffcient = Param(initialize = 0.375 ,doc = "stabilizing column linear coeffcient (times number of trays) [$1e3 per year]") + m.benzene_column_fixed_cost = Param(initialize = 16.3 ,doc = "benzene column fixed cost [$1e3 per year]") + m.benzene_column_linear_coeffcient = Param(initialize = 1.55 ,doc = "benzene column linear coeffcient (times number of trays) [$1e3 per year]") + m.toluene_column_fixed_cost = Param(initialize = 3.9 ,doc = "toluene column fixed cost [$1e3 per year]") + m.toluene_column_linear_coeffcient = Param(initialize = 1.12 ,doc = "toluene column linear coeffcient (times number of trays) [$1e3 per year]") + m.furnace_fixed_cost = Param(initialize = 6.20 ,doc = "toluene column fixed cost [$1e3 per year]") + m.furnace_linear_coeffcient = Param(initialize = 1171.7 ,doc = "furnace column linear coeffcient [1e9kj/yr] [$1e3 per year]") + m.membrane_seperator_fixed_cost = Param(initialize = 43.24 ,doc = "membrane seperator fixed cost [$1e3 per year]") + m.membrane_seperator_linear_coeffcient = Param(initialize = 49.0 ,doc = "furnace column linear coeffcient (times inlet flowrate) [$1e3 per year]") + m.adiabtic_reactor_fixed_cost = Param(initialize = 74.3 ,doc = "adiabatic reactor fixed cost [$1e3 per year]") + m.adiabtic_reactor_linear_coeffcient = Param(initialize = 1.257 ,doc = "adiabatic reactor linear coeffcient (times reactor volumn) [$1e3 per year]") + m.isothermal_reactor_fixed_cost = Param(initialize = 92.875 ,doc = "isothermal reactor fixed cost [$1e3 per year]") + m.isothermal_reactor_linear_coeffcient = Param(initialize = 1.57125 ,doc = "isothermal reactor linear coeffcient (times reactor volumn) [$1e3 per year]") + m.h2_feed_cost = Param(initialize = 2.5, doc = "h2 feed cost (95% h2,5% Ch4)") m.toluene_feed_cost = Param(initialize = 14., doc = "toluene feed cost (100% toluene)") m.benzene_product = Param(initialize = 19.9,doc = "benzene product profit(benzene >= 99.97%)") m.diphenyl_product = Param(initialize = 11.84,doc= "diphenyl product profit(diphenyl = 100%)") @@ -1817,6 +2000,7 @@ def toluene_selection(m): def profits_from_paper(m): return 510. * (- m.h2_feed_cost * m.f[1] - m.toluene_feed_cost * (m.f[66] + m.f[67]) + m.benzene_product * m.f[31] + m.diphenyl_product * m.f[35] + m.hydrogen_purge_value * (m.fc[4, 'h2'] + m.fc[28, 'h2'] + m.fc[53, 'h2'] + m.fc[55, 'h2']) + m.meathane_purge_value * (m.fc[4, 'ch4'] + m.fc[28, 'ch4'] + m.fc[53, 'ch4'] + m.fc[55, 'ch4'])) - m.compressor_linear_coeffcient * (m.elec[1] + m.elec[2] + m.elec[3]) - m.compressor_linear_coeffcient * m.elec[4] - m.compressor_fixed_cost * (m.purify_H2.binary_indicator_var + m.recycle_hydrogen.binary_indicator_var + m.absorber_hydrogen.binary_indicator_var) - m.compressor_fixed_cost * m.recycle_methane_membrane.binary_indicator_var - sum((m.electricity_cost * m.elec[comp]) for comp in m.comp) - (m.adiabtic_reactor_fixed_cost * m.adiabatic_reactor.binary_indicator_var + m.adiabtic_reactor_linear_coeffcient * m.rctvol[1]) - (m.isothermal_reactor_fixed_cost * m.isothermal_reactor.binary_indicator_var + m.isothermal_reactor_linear_coeffcient * m.rctvol[2]) - m.cooling_cost/1000 * m.q[2] - (m.stabilizing_column_fixed_cost * m.methane_distillation_column.binary_indicator_var +m.stabilizing_column_linear_coeffcient * m.ndist[1]) - (m.benzene_column_fixed_cost+ m.benzene_column_linear_coeffcient * m.ndist[2]) - (m.toluene_column_fixed_cost * m.toluene_distillation_column.binary_indicator_var + m.toluene_column_linear_coeffcient * m.ndist[3]) - (m.membrane_seperator_fixed_cost * m.purify_H2.binary_indicator_var + m.membrane_seperator_linear_coeffcient * m.f[3]) - (m.membrane_seperator_fixed_cost * m.recycle_methane_membrane.binary_indicator_var + m.membrane_seperator_linear_coeffcient * m.f[54]) - (m.abs_fixed_cost * m.absorber_hydrogen.binary_indicator_var + m.abs_linear_coeffcient * m.nabs[1]) - ( m.fuel_cost * m.qfuel[1] + m.furnace_linear_coeffcient* m.qfuel[1] ) - sum(m.cooling_cost * m.qc[hec] for hec in m.hec) - sum(m.heating_cost * m.qh[heh] for heh in m.heh) - m.furnace_fixed_cost m.obj = Objective(rule = profits_from_paper, sense=maximize) + # def profits_GAMS_file(m): # "there are several differences between the data from GAMS file and the paper: 1. all the compressor share the same fixed and linear cost in paper but in GAMS they have different fixed and linear cost in GAMS file. 2. the fixed cost for absorber in GAMS file is 3.0 but in the paper is 13.0, but they are getting the same results 3. the electricity cost is not the same" @@ -1830,10 +2014,19 @@ def profits_from_paper(m): # %% def solve_with_gdpopt(m): - ''' + """ This function solves model m using GDPOpt - ''' + + Parameters + ---------- + m : Pyomo Model + The model to be solved + Returns + ------- + res : solver results + The result of the optimization + """ opt = SolverFactory('gdpopt') res = opt.solve(m, tee=True, strategy='LOA', @@ -1853,9 +2046,19 @@ def solve_with_gdpopt(m): return res def solve_with_minlp(m): - ''' - This function solves model m using minlp transformation by either Big-M or convex hull - ''' + """ + This function solves model m using minlp transformation by either Big-M or convex hull + + Parameters + ---------- + m : Pyomo Model + The model to be solved + + Returns + ------- + result : solver results + The result of the optimization + """ TransformationFactory('gdp.bigm').apply_to(m, bigM=60) # TransformationFactory('gdp.hull').apply_to(m) @@ -1886,6 +2089,14 @@ def infeasible_constraints(m): # enumeration each possible route selection by fixing binary variable values in every disjunctions def enumerate_solutions(m): + """ + Enumerate all possible route selections by fixing binary variables in each disjunctions + + Parameters + ---------- + m : Pyomo Model + Pyomo model to be solved + """ H2_treatments = ['purify','none_purify'] Reactor_selections = ['adiabatic_reactor','isothermal_reactor'] From 6d196e9992009c7c592faec3e5a76a76db06570a Mon Sep 17 00:00:00 2001 From: parkyr Date: Thu, 16 May 2024 18:40:54 -0400 Subject: [PATCH 04/79] additional documentation --- gdplib/hda/HDA_GDP_gdpopt.py | 82 ++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/gdplib/hda/HDA_GDP_gdpopt.py b/gdplib/hda/HDA_GDP_gdpopt.py index d4c743f..2bb0970 100644 --- a/gdplib/hda/HDA_GDP_gdpopt.py +++ b/gdplib/hda/HDA_GDP_gdpopt.py @@ -376,7 +376,7 @@ def Permset(m, compon): Heatrxn = {} Heatrxn[1] = 50100. Heatrxn[2] = 50100. - m.heatrxn = Param(m.rct, initialize=Heatrxn, default=0, doc='heat of reaction [kj per kg-mol]') + m.heatrxn = Param(m.rct, initialize=Heatrxn, default=0, doc='heat of reaction [kj per kg-mol]') F1comp = {} F1comp['h2'] = 0.95 @@ -479,8 +479,8 @@ def Permset(m, compon): # absorber m.nabs = Var(m.abs, within=NonNegativeReals, bounds=(0, 40), initialize=1, doc='number of absorber trays') - m.gamma = Var(m.abs, m.compon, within=Reals, initialize=1) - m.beta = Var(m.abs, m.compon, within=Reals, initialize=1) + m.gamma = Var(m.abs, m.compon, within=Reals, initialize=1, doc='gamma') + m.beta = Var(m.abs, m.compon, within=Reals, initialize=1, doc='beta') # compressor m.elec = Var(m.comp, within=NonNegativeReals, bounds=(0, 100), initialize=1, doc='electricity requirement [kw]') @@ -513,7 +513,7 @@ def Permset(m, compon): m.mxr1p = Var(m.mxr1, within=NonNegativeReals, bounds=(0.1, 4), initialize=0, doc='mixer temperature [100 k]') m.mxr1t = Var(m.mxr1, within=NonNegativeReals, bounds=(3, 10), initialize=0, doc='mixer pressure [mega-pascal]') # mixer - m.mxrt = Var(m.mxr, within=NonNegativeReals, bounds=(3.0, 10), initialize=3, doc='mixer temperature [100 k]') # ? + m.mxrt = Var(m.mxr, within=NonNegativeReals, bounds=(3.0, 10), initialize=3, doc='mixer temperature [100 k]') m.mxrp = Var(m.mxr, within=NonNegativeReals, bounds=(0.1, 4.0), initialize=3, doc='mixer pressure [mega-pascal]') # reactor m.rctt = Var(m.rct, within=NonNegativeReals, bounds=(8.9427, 9.7760), doc='reactor temperature [100 k]') @@ -813,7 +813,7 @@ def boundsofe(m): m.spl1t[i+1] = spl1t.to_numpy()[i, 0] # ## constraints - m.specrec = Constraint(expr=m.fc[72, 'h2'] >= 0.5 * m.f[72], doc='specification on h2 reccycle') + m.specrec = Constraint(expr=m.fc[72, 'h2'] >= 0.5 * m.f[72], doc='specification on h2 recycle') m.specprod = Constraint(expr=m.fc[31, 'ben'] >= 0.9997 * m.f[31], doc='specification on benzene production') def Fbal(_m, stream): @@ -896,31 +896,31 @@ def abssimp(_m, absorb, compon): def Abscmb(_m, i, compon): return sum(m.fc[stream, compon] for (i, stream) in m.ilabs) + sum(m.fc[stream, compon] for (i, stream) in m.ivabs) == sum(m.fc[stream, compon] for (i, stream) in m.olabs) + sum(m.fc[stream, compon] for (i, stream) in m.ovabs) b.abscmb = Constraint([absorber], m.compon, rule=Abscmb, - doc='overall component mass balance') + doc='overall component mass balance in absorber') def Abspl(_m, i): return sum(m.p[stream] for (_, stream) in m.ilabs) == sum(m.p[stream] for (_, stream) in m.olabs) b.abspl = Constraint([absorber], rule=Abspl, - doc='pressure relation for liquid') + doc='pressure relation for liquid in absorber') def Abstl(_m, i): return sum(m.t[stream] for (_, stream) in m.ilabs) == sum(m.t[stream] for (_, stream) in m.olabs) b.abstl = Constraint([absorber], rule=Abstl, - doc='temperature relation for liquid') + doc='temperature relation for liquid in absorber') def Abspv(_m, i): return sum(m.p[stream] for (_, stream) in m.ivabs) == sum(m.p[stream] for (_, stream) in m.ovabs) b.abspv = Constraint([absorber], rule=Abspv, - doc='pressure relation for vapor') + doc='pressure relation for vapor in absorber') def Abspin(_m, i): return sum(m.p[stream] for (_, stream) in m.ilabs) == sum(m.p[stream] for (_, stream) in m.ivabs) - b.absp = Constraint([absorber], rule=Abspin, doc='pressure relation at inlet') + b.absp = Constraint([absorber], rule=Abspin, doc='pressure relation at inlet of absorber') def Absttop(_m, i): return sum(m.t[stream] for (_, stream) in m.ilabs) == sum(m.t[stream] for (_, stream) in m.ovabs) b.abst = Constraint([absorber], rule=Absttop, - doc='temperature relation at top') + doc='temperature relation at top of absorber') def build_compressor(b, comp): """ @@ -938,28 +938,28 @@ def Compcmb(_m, comp1, compon): return sum(m.fc[stream, compon] for (comp_, stream) in m.ocomp if comp_ == comp1) == sum(m.fc[stream, compon] for (comp_, stream) in m.icomp if comp_ == comp1) return Constraint.Skip b.compcmb = Constraint( - [comp], m.compon, rule=Compcmb, doc='component balance for compressor') + [comp], m.compon, rule=Compcmb, doc='component balance in compressor') def Comphb(_m, comp1): if comp1 == comp: return sum(m.t[stream] for (_, stream) in m.ocomp if _ == comp) == m.presrat[comp] * sum(m.t[stream] for (_, stream) in m.icomp if _ == comp) return Constraint.Skip b.comphb = Constraint([comp], rule=Comphb, - doc='heat balance for compressor') + doc='heat balance in compressor') def Compelec(_m, comp_): if comp_ == comp: return m.elec[comp_] == m.alpha * (m.presrat[comp_] - 1) * sum(100. * m.t[stream] * m.f[stream] / 60. * (1./m.compeff) * (m.gam / (m.gam - 1.)) for (comp1, stream) in m.icomp if comp_ == comp1) return Constraint.Skip b.compelec = Constraint([comp], rule=Compelec, - doc="energy balance for compressor") + doc="energy balance in compressor") def Ratio(_m, comp_): if comp == comp_: return m.presrat[comp_] ** (m.gam/(m.gam-1.)) == sum(m.p[stream] for (comp1, stream) in m.ocomp if comp_ == comp1) / sum(m.p[stream] for (comp1, stream) in m.icomp if comp1 == comp_) return Constraint.Skip b.ratio = Constraint([comp], rule=Ratio, - doc='pressure ratio (out to in)') + doc='pressure ratio (out to in) in compressor') m.vapor_pressure_unit_match = Param( @@ -1102,7 +1102,7 @@ def Distcmb(_m, dist_, compon): return sum(m.fc[stream, compon] for (dist1, stream) in m.idist if dist1 == dist_) == sum(m.fc[stream, compon] for (dist1, stream) in m.vdist if dist1 == dist_) + sum(m.fc[stream, compon] for (dist1, stream) in m.ldist if dist1 == dist_) return Constraint.Skip b.distcmb = Constraint( - [dist], m.compon, rule=Distcmb, doc='component mass balance') + [dist], m.compon, rule=Distcmb, doc='component mass balance in distillation column') @@ -1122,7 +1122,7 @@ def Flshcmb(_m, flsh_, compon): return sum(m.fc[stream, compon] for (flsh1, stream) in m.iflsh if flsh1 == flsh_) == sum(m.fc[stream, compon] for (flsh1, stream) in m.vflsh if flsh1 == flsh_) + sum(m.fc[stream, compon] for (flsh1, stream) in m.lflsh if flsh1 == flsh_) return Constraint.Skip b.flshcmb = Constraint( - [flsh], m.compon, rule=Flshcmb, doc='component mass balance') + [flsh], m.compon, rule=Flshcmb, doc='component mass balance in flash') def Antflsh(_m, flsh_, stream, compon): if (flsh_, stream) in m.lflsh and flsh_ == flsh: @@ -1213,20 +1213,20 @@ def Furnhb(_m, furn): if furn == furnace: return m.qfuel[furn] == (sum(m.cp[stream] * m.f[stream] * 100. * m.t[stream] for (furn, stream) in m.ofurn) - sum(m.cp[stream] * m.f[stream] * 100. * m.t[stream] for (furn, stream) in m.ifurn)) * m.heat_unit_match return Constraint.Skip - b.furnhb = Constraint([furnace], rule=Furnhb, doc='heat balance for furnace') + b.furnhb = Constraint([furnace], rule=Furnhb, doc='heat balance in furnace') def Furncmb(_m, furn, compon): if furn == furnace: return sum(m.fc[stream, compon] for (furn, stream) in m.ofurn) == sum(m.fc[stream, compon] for (furn, stream) in m.ifurn) return Constraint.Skip b.furncmb = Constraint([furnace], m.compon, - rule=Furncmb, doc='component mass balance') + rule=Furncmb, doc='component mass balance in furnace') def Furnp(_m, furn): if furn == furnace: return sum(m.p[stream] for (furn, stream) in m.ofurn) == sum(m.p[stream] for (furn, stream) in m.ifurn) - m.furnpdrop return Constraint.Skip - b.furnp = Constraint([furnace], rule=Furnp, doc='pressure relation') + b.furnp = Constraint([furnace], rule=Furnp, doc='pressure relation in furnace') @@ -1244,16 +1244,16 @@ def build_cooler(b, cooler): def Heccmb(_m, hec, compon): return sum(m.fc[stream, compon] for (hec_, stream) in m.ohec if hec_ == hec) == sum(m.fc[stream, compon] for (hec_, stream) in m.ihec if hec_ == hec) b.heccmb = Constraint([cooler], m.compon, - rule=Heccmb, doc='heat balance for cooler') + rule=Heccmb, doc='heat balance in cooler') def Hechb(_m, hec): return m.qc[hec] == (sum(m.cp[stream] * m.f[stream] * 100. * m.t[stream] for (hec_, stream) in m.ihec if hec_ == hec) - sum(m.cp[stream] * m.f[stream] * 100. * m.t[stream] for (hec_, stream) in m.ohec if hec_ == hec)) * m.heat_unit_match b.hechb = Constraint([cooler], rule=Hechb, - doc='component mass balance') + doc='component mass balance in cooler') def Hecp(_m, hec): return sum(m.p[stream] for(hec_, stream) in m.ihec if hec_ == hec) == sum(m.p[stream] for(hec_, stream) in m.ohec if hec_ == hec) - b.hecp = Constraint([cooler], rule=Hecp, doc='pressure relation') + b.hecp = Constraint([cooler], rule=Hecp, doc='pressure relation in cooler') @@ -1281,7 +1281,7 @@ def Hehhb(_m, heh): sum(m.cp[stream] * m.f[stream] * 100. * m.t[stream] for (heh_, stream) in m.iheh if heh_ == heh)) * m.heat_unit_match return Constraint.Skip b.hehhb = Constraint( - Set(initialize=[heater]), rule=Hehhb, doc='heat balance for heater') + Set(initialize=[heater]), rule=Hehhb, doc='heat balance in heater') def hehp(_m, heh): if heh == heater: @@ -1309,26 +1309,26 @@ def Exchcmbc(_m, exch, compon): return sum(m.fc[stream, compon] for (exch_, stream) in m.ocexch if exch == exch_) == sum(m.fc[stream, compon] for (exch_, stream) in m.icexch if exch == exch_) return Constraint.Skip b.exchcmbc = Constraint([exchanger], m.compon, - rule=Exchcmbc, doc='component balance (cold)') + rule=Exchcmbc, doc='component balance (cold) in exchanger') def Exchcmbh(_m, exch, compon): if exch in m.exch and compon in m.compon: return sum(m.fc[stream, compon] for (exch_, stream) in m.ohexch if exch == exch_) == sum(m.fc[stream, compon] for (exch_, stream) in m.ihexch if exch == exch_) return Constraint.Skip b.exchcmbh = Constraint([exchanger], m.compon, - rule=Exchcmbh, doc='component balance (hot)') + rule=Exchcmbh, doc='component balance (hot) in exchanger') def Exchhbc(_m, exch): if exch in m.exch: return (sum(m.cp[stream] * m.f[stream] * 100. * m.t[stream] for (exch_, stream) in m.ocexch if exch == exch_) - sum(m.cp[stream] * m.f[stream] * 100. * m.t[stream] for (exch_, stream) in m.icexch if exch == exch_)) * m.heat_unit_match == m.qexch[exch] return Constraint.Skip b.exchhbc = Constraint([exchanger], rule=Exchhbc, - doc='heat balance for cold stream') + doc='heat balance for cold stream in exchanger') def Exchhbh(_m, exch): return (sum(m.cp[stream] * m.f[stream] * 100. * m.t[stream] for (exch, stream) in m.ihexch) - sum(m.cp[stream] * m.f[stream] * 100. * m.t[stream] for (exch, stream) in m.ohexch)) * m.heat_unit_match == m.qexch[exch] b.exchhbh = Constraint([exchanger], rule=Exchhbh, - doc='heat balance for hot stream') + doc='heat balance for hot stream in exchanger') def Exchdtm1(_m, exch): return sum(m.t[stream] for (exch, stream) in m.ohexch) >= sum(m.t[stream] for (exch, stream) in m.icexch) + m.exchanger_temp_drop @@ -1343,12 +1343,12 @@ def Exchdtm2(_m, exch): def Exchpc(_m, exch): return sum(m.p[stream] for (exch, stream) in m.ocexch) == sum(m.p[stream] for (exch, stream) in m.icexch) b.exchpc = Constraint([exchanger], rule=Exchpc, - doc='pressure relation (cold)') + doc='pressure relation (cold) in exchanger') def Exchph(_m, exch): return sum(m.p[stream] for (exch, stream) in m.ohexch) == sum(m.p[stream] for (exch, stream) in m.ihexch) b.exchph = Constraint([exchanger], rule=Exchph, - doc='pressure relation (hot)') + doc='pressure relation (hot) in exchanger') m.membrane_recovery_sepc = Param(initialize=0.50) @@ -1370,21 +1370,21 @@ def Memcmb(_m, memb, stream, compon): return m.fc[stream, compon] == sum(m.fc[stream, compon] for (memb_, stream) in m.pmemb if memb == memb_) + sum(m.fc[stream, compon] for (memb_, stream) in m.nmemb if memb == memb_) return Constraint.Skip b.memcmb = Constraint([membrane], m.str, m.compon, - rule=Memcmb, doc='component mass balance') + rule=Memcmb, doc='component mass balance in membrane separator') def Flux(_m, memb, stream, compon): if (memb, stream) in m.pmemb and (memb, compon) in m.mnorm and memb == membrane: return m.fc[stream, compon] == m.a[memb] * m.perm[compon] / 2.0 * (sum(m.p[stream2] for (memb_, stream2) in m.imemb if memb_ == memb) * (sum((m.fc[stream2, compon] + m.eps1)/(m.f[stream2] + m.eps1) for (memb_, stream2) in m.imemb if memb_ == memb) + sum((m.fc[stream2, compon] + m.eps1)/(m.f[stream2] + m.eps1) for (memb_, stream2) in m.nmemb if memb_ == memb)) - 2.0 * m.p[stream] * (m.fc[stream, compon] + m.eps1) / (m.f[stream] + m.eps1)) return Constraint.Skip b.flux = Constraint([membrane], m.str, m.compon, - rule=Flux, doc='mass flux relation') + rule=Flux, doc='mass flux relation in membrane separator') def Simp(_m, memb, stream, compon): if (memb, stream) in m.pmemb and (memb, compon) in m.msimp and memb == membrane: return m.fc[stream, compon] == 0.0 return Constraint.Skip b.simp = Constraint([membrane], m.str, m.compon, - rule=Simp, doc='mass flux relation (simplified)') + rule=Simp, doc='mass flux relation (simplified) in membrane separator') def Memtp(_m, memb, stream): if (memb, stream) in m.pmemb and memb == membrane: @@ -1492,19 +1492,19 @@ def Pumpcmb(_m, pump, compon): return sum(m.fc[stream, compon] for (pump_, stream) in m.opump if pump == pump_) == sum(m.fc[stream, compon] for (pump_, stream) in m.ipump if pump_ == pump) return Constraint.Skip b.pumpcmb = Constraint( - [pump_], m.compon, rule=Pumpcmb, doc='component balance for pump') + [pump_], m.compon, rule=Pumpcmb, doc='component balance in pump') def Pumphb(_m, pump): if pump == pump_: return sum(m.t[stream] for (pump_, stream) in m.opump if pump == pump_) == sum(m.t[stream] for (pump_, stream) in m.ipump if pump == pump_) return Constraint.Skip - b.pumphb = Constraint([pump_], rule=Pumphb, doc='heat balance for pump') + b.pumphb = Constraint([pump_], rule=Pumphb, doc='heat balance in pump') def Pumppr(_m, pump): if pump == pump_: return sum(m.p[stream] for (pump_, stream) in m.opump if pump == pump_) >= sum(m.p[stream] for (pump_, stream) in m.ipump if pump == pump_) return Constraint.Skip - b.pumppr = Constraint([pump_], rule=Pumppr, doc='pressure relation for pump') + b.pumppr = Constraint([pump_], rule=Pumppr, doc='pressure relation in pump') @@ -1524,14 +1524,14 @@ def Splcmb(_m, spl, stream, compon): return m.fc[stream, compon] == sum(m.e[stream]*m.fc[str2, compon] for (spl_, str2) in m.ispl if spl == spl_) return Constraint.Skip b.splcmb = Constraint([multi_splitter], m.str, m.compon, - rule=Splcmb, doc='component balance for splitter') + rule=Splcmb, doc='component balance in splitter') def Esum(_m, spl): if spl in m.spl and spl == multi_splitter: return sum(m.e[stream] for (spl_, stream) in m.ospl if spl_ == spl) == 1.0 return Constraint.Skip b.esum = Constraint([multi_splitter], rule=Esum, - doc='split fraction relation for splitter') + doc='split fraction relation in splitter') def Splpi(_m, spl, stream): if (spl, stream) in m.ispl and spl == multi_splitter: @@ -1576,15 +1576,15 @@ def build_valve(b, valve_): """ def Valcmb(_m, valve, compon): return sum(m.fc[stream, compon] for (valve_, stream) in m.oval if valve == valve_) == sum(m.fc[stream, compon] for (valve_, stream) in m.ival if valve == valve_) - b.valcmb = Constraint([valve_], m.compon, rule=Valcmb, doc='component balance for valve') + b.valcmb = Constraint([valve_], m.compon, rule=Valcmb, doc='component balance in valve') def Valt(_m, valve): return sum(m.t[stream] / (m.p[stream] ** ((m.gam - 1.) / m.gam)) for (valv, stream) in m.oval if valv == valve) == sum(m.t[stream] / (m.p[stream] ** ((m.gam - 1.) / m.gam)) for (valv, stream) in m.ival if valv == valve) - b.valt = Constraint([valve_], rule=Valt, doc='temperature relation for valve') + b.valt = Constraint([valve_], rule=Valt, doc='temperature relation in valve') def Valp(_m, valve): return sum(m.p[stream] for (valv, stream) in m.oval if valv == valve) <= sum(m.p[stream] for (valv, stream) in m.ival if valv == valve) - b.valp = Constraint([valve_], rule=Valp, doc='pressure relation for valve') + b.valp = Constraint([valve_], rule=Valp, doc='pressure relation in valve') m.Prereference_factor = Param( From 6b331062d332021b26207d934029ca79a9ac59c1 Mon Sep 17 00:00:00 2001 From: parkyr Date: Thu, 16 May 2024 18:44:22 -0400 Subject: [PATCH 05/79] black formatting the file --- gdplib/hda/HDA_GDP_gdpopt.py | 2835 +++++++++++++++++++++++++--------- 1 file changed, 2080 insertions(+), 755 deletions(-) diff --git a/gdplib/hda/HDA_GDP_gdpopt.py b/gdplib/hda/HDA_GDP_gdpopt.py index 2bb0970..9923427 100644 --- a/gdplib/hda/HDA_GDP_gdpopt.py +++ b/gdplib/hda/HDA_GDP_gdpopt.py @@ -47,7 +47,7 @@ def HDA_model(): column tray efficiency uflow : float upper bound - flow logicals - upress : float + upress : float upper bound - pressure logicals utemp : float upper bound - temperature logicals @@ -66,7 +66,7 @@ def HDA_model(): cppure : float pure component heat capacities [kj per kg-mol-k] gcomp : float - guess composition values + guess composition values cp : float heat capacities [kj per kg-mol-k] anta : float @@ -78,7 +78,7 @@ def HDA_model(): perm : float permeability [kg-mole/m**2-min-mpa] cbeta : float - constant values (exp(beta)) in absorber + constant values (exp(beta)) in absorber aabs : float absorption factors eps1 : float @@ -134,13 +134,13 @@ def HDA_model(): process streams compon2 : str chemical components - + Returns ------- m : Pyomo ConcreteModel Pyomo model of the Hydrodealkylation of Toluene process - + """ dir_path = os.path.dirname(os.path.abspath(__file__)) @@ -164,7 +164,7 @@ def HDA_model(): def strset(i): """ - Process streams + Process streams Returns ------- @@ -181,8 +181,11 @@ def strset(i): s.append(i) i += i return s + m.str = Set(initialize=strset, doc="process streams") - m.compon = Set(initialize=['h2', 'ch4', 'ben', 'tol', 'dip'], doc="chemical components") + m.compon = Set( + initialize=["h2", "ch4", "ben", "tol", "dip"], doc="chemical components" + ) m.abs = RangeSet(1) m.comp = RangeSet(4) m.dist = RangeSet(3) @@ -200,121 +203,135 @@ def strset(i): m.spl = RangeSet(3) m.valve = RangeSet(6) m.str2 = Set(initialize=strset, doc="process streams") - m.compon2 = Set(initialize=['h2', 'ch4', 'ben', 'tol', 'dip'], doc="chemical components") + m.compon2 = Set( + initialize=["h2", "ch4", "ben", "tol", "dip"], doc="chemical components" + ) # parameters Heatvap = {} - Heatvap['tol'] = 30890.00 - m.heatvap = Param(m.compon, initialize=Heatvap, default=0, doc='heat of vaporization [kj per kg-mol]') + Heatvap["tol"] = 30890.00 + m.heatvap = Param( + m.compon, + initialize=Heatvap, + default=0, + doc="heat of vaporization [kj per kg-mol]", + ) Cppure = {} # h2 'hydrogen', ch4 'methane', ben 'benzene', tol 'toluene', dip 'diphenyl' - Cppure['h2'] = 30 - Cppure['ch4'] = 40 - Cppure['ben'] = 225 - Cppure['tol'] = 225 - Cppure['dip'] = 450 - m.cppure = Param(m.compon, initialize=Cppure, default=0, doc='pure component heat capacities [kj per kg-mol-k]') + Cppure["h2"] = 30 + Cppure["ch4"] = 40 + Cppure["ben"] = 225 + Cppure["tol"] = 225 + Cppure["dip"] = 450 + m.cppure = Param( + m.compon, + initialize=Cppure, + default=0, + doc="pure component heat capacities [kj per kg-mol-k]", + ) Gcomp = {} - Gcomp[7, 'h2'] = 0.95 - Gcomp[7, 'ch4'] = 0.05 - Gcomp[8, 'h2'] = 0.5 - Gcomp[8, 'ch4'] = 0.40 - Gcomp[8, 'tol'] = 0.1 - Gcomp[9, 'h2'] = 0.5 - Gcomp[9, 'ch4'] = 0.40 - Gcomp[9, 'tol'] = 0.1 - Gcomp[10, 'h2'] = 0.5 - Gcomp[10, 'ch4'] = 0.40 - Gcomp[10, 'tol'] = 0.1 - Gcomp[11, 'h2'] = 0.45 - Gcomp[11, 'ben'] = 0.05 - Gcomp[11, 'ch4'] = 0.45 - Gcomp[11, 'tol'] = 0.05 - Gcomp[12, 'h2'] = 0.50 - Gcomp[12, 'ch4'] = 0.40 - Gcomp[12, 'tol'] = 0.10 - Gcomp[13, 'h2'] = 0.45 - Gcomp[13, 'ch4'] = 0.45 - Gcomp[13, 'ben'] = 0.05 - Gcomp[13, 'tol'] = 0.05 - Gcomp[14, 'h2'] = 0.45 - Gcomp[14, 'ch4'] = 0.45 - Gcomp[14, 'ben'] = 0.05 - Gcomp[14, 'tol'] = 0.05 - Gcomp[15, 'h2'] = 0.45 - Gcomp[15, 'ch4'] = 0.45 - Gcomp[15, 'ben'] = 0.05 - Gcomp[15, 'tol'] = 0.05 - Gcomp[16, 'h2'] = 0.4 - Gcomp[16, 'ch4'] = 0.4 - Gcomp[16, 'ben'] = 0.1 - Gcomp[16, 'tol'] = 0.1 - Gcomp[17, 'h2'] = 0.40 - Gcomp[17, 'ch4'] = 0.40 - Gcomp[17, 'ben'] = 0.1 - Gcomp[17, 'tol'] = 0.1 - Gcomp[20, 'h2'] = 0.03 - Gcomp[20, 'ch4'] = 0.07 - Gcomp[20, 'ben'] = 0.55 - Gcomp[20, 'tol'] = 0.35 - Gcomp[21, 'h2'] = 0.03 - Gcomp[21, 'ch4'] = 0.07 - Gcomp[21, 'ben'] = 0.55 - Gcomp[21, 'tol'] = 0.35 - Gcomp[22, 'h2'] = 0.03 - Gcomp[22, 'ch4'] = 0.07 - Gcomp[22, 'ben'] = 0.55 - Gcomp[22, 'tol'] = 0.35 - Gcomp[24, 'h2'] = 0.03 - Gcomp[24, 'ch4'] = 0.07 - Gcomp[24, 'ben'] = 0.55 - Gcomp[24, 'tol'] = 0.35 - Gcomp[25, 'h2'] = 0.03 - Gcomp[25, 'ch4'] = 0.07 - Gcomp[25, 'ben'] = 0.55 - Gcomp[25, 'tol'] = 0.35 - Gcomp[37, 'tol'] = 1.00 - Gcomp[38, 'tol'] = 1.00 - Gcomp[43, 'ben'] = 0.05 - Gcomp[43, 'tol'] = 0.95 - Gcomp[44, 'h2'] = 0.03 - Gcomp[44, 'ch4'] = 0.07 - Gcomp[44, 'ben'] = 0.55 - Gcomp[44, 'tol'] = 0.35 - Gcomp[45, 'h2'] = 0.03 - Gcomp[45, 'ch4'] = 0.07 - Gcomp[45, 'ben'] = 0.55 - Gcomp[45, 'tol'] = 0.35 - Gcomp[46, 'h2'] = 0.03 - Gcomp[46, 'ch4'] = 0.07 - Gcomp[46, 'ben'] = 0.55 - Gcomp[46, 'tol'] = 0.35 - Gcomp[51, 'h2'] = 0.30 - Gcomp[51, 'ch4'] = 0.70 - Gcomp[57, 'h2'] = 0.80 - Gcomp[57, 'ch4'] = 0.20 - Gcomp[60, 'h2'] = 0.50 - Gcomp[60, 'ch4'] = 0.50 - Gcomp[62, 'h2'] = 0.50 - Gcomp[62, 'ch4'] = 0.50 - Gcomp[63, 'h2'] = 0.47 - Gcomp[63, 'ch4'] = 0.40 - Gcomp[63, 'ben'] = 0.01 - Gcomp[63, 'tol'] = 0.12 - Gcomp[65, 'h2'] = 0.50 - Gcomp[65, 'ch4'] = 0.50 - Gcomp[66, 'tol'] = 1.0 - Gcomp[69, 'tol'] = 1.0 - Gcomp[70, 'h2'] = 0.5 - Gcomp[70, 'ch4'] = 0.4 - Gcomp[70, 'tol'] = 0.10 - Gcomp[71, 'h2'] = 0.40 - Gcomp[71, 'ch4'] = 0.40 - Gcomp[71, 'ben'] = 0.10 - Gcomp[71, 'tol'] = 0.10 - Gcomp[72, 'h2'] = 0.50 - Gcomp[72, 'ch4'] = 0.50 - m.gcomp = Param(m.str, m.compon, initialize=Gcomp, default=0, doc='guess composition values') + Gcomp[7, "h2"] = 0.95 + Gcomp[7, "ch4"] = 0.05 + Gcomp[8, "h2"] = 0.5 + Gcomp[8, "ch4"] = 0.40 + Gcomp[8, "tol"] = 0.1 + Gcomp[9, "h2"] = 0.5 + Gcomp[9, "ch4"] = 0.40 + Gcomp[9, "tol"] = 0.1 + Gcomp[10, "h2"] = 0.5 + Gcomp[10, "ch4"] = 0.40 + Gcomp[10, "tol"] = 0.1 + Gcomp[11, "h2"] = 0.45 + Gcomp[11, "ben"] = 0.05 + Gcomp[11, "ch4"] = 0.45 + Gcomp[11, "tol"] = 0.05 + Gcomp[12, "h2"] = 0.50 + Gcomp[12, "ch4"] = 0.40 + Gcomp[12, "tol"] = 0.10 + Gcomp[13, "h2"] = 0.45 + Gcomp[13, "ch4"] = 0.45 + Gcomp[13, "ben"] = 0.05 + Gcomp[13, "tol"] = 0.05 + Gcomp[14, "h2"] = 0.45 + Gcomp[14, "ch4"] = 0.45 + Gcomp[14, "ben"] = 0.05 + Gcomp[14, "tol"] = 0.05 + Gcomp[15, "h2"] = 0.45 + Gcomp[15, "ch4"] = 0.45 + Gcomp[15, "ben"] = 0.05 + Gcomp[15, "tol"] = 0.05 + Gcomp[16, "h2"] = 0.4 + Gcomp[16, "ch4"] = 0.4 + Gcomp[16, "ben"] = 0.1 + Gcomp[16, "tol"] = 0.1 + Gcomp[17, "h2"] = 0.40 + Gcomp[17, "ch4"] = 0.40 + Gcomp[17, "ben"] = 0.1 + Gcomp[17, "tol"] = 0.1 + Gcomp[20, "h2"] = 0.03 + Gcomp[20, "ch4"] = 0.07 + Gcomp[20, "ben"] = 0.55 + Gcomp[20, "tol"] = 0.35 + Gcomp[21, "h2"] = 0.03 + Gcomp[21, "ch4"] = 0.07 + Gcomp[21, "ben"] = 0.55 + Gcomp[21, "tol"] = 0.35 + Gcomp[22, "h2"] = 0.03 + Gcomp[22, "ch4"] = 0.07 + Gcomp[22, "ben"] = 0.55 + Gcomp[22, "tol"] = 0.35 + Gcomp[24, "h2"] = 0.03 + Gcomp[24, "ch4"] = 0.07 + Gcomp[24, "ben"] = 0.55 + Gcomp[24, "tol"] = 0.35 + Gcomp[25, "h2"] = 0.03 + Gcomp[25, "ch4"] = 0.07 + Gcomp[25, "ben"] = 0.55 + Gcomp[25, "tol"] = 0.35 + Gcomp[37, "tol"] = 1.00 + Gcomp[38, "tol"] = 1.00 + Gcomp[43, "ben"] = 0.05 + Gcomp[43, "tol"] = 0.95 + Gcomp[44, "h2"] = 0.03 + Gcomp[44, "ch4"] = 0.07 + Gcomp[44, "ben"] = 0.55 + Gcomp[44, "tol"] = 0.35 + Gcomp[45, "h2"] = 0.03 + Gcomp[45, "ch4"] = 0.07 + Gcomp[45, "ben"] = 0.55 + Gcomp[45, "tol"] = 0.35 + Gcomp[46, "h2"] = 0.03 + Gcomp[46, "ch4"] = 0.07 + Gcomp[46, "ben"] = 0.55 + Gcomp[46, "tol"] = 0.35 + Gcomp[51, "h2"] = 0.30 + Gcomp[51, "ch4"] = 0.70 + Gcomp[57, "h2"] = 0.80 + Gcomp[57, "ch4"] = 0.20 + Gcomp[60, "h2"] = 0.50 + Gcomp[60, "ch4"] = 0.50 + Gcomp[62, "h2"] = 0.50 + Gcomp[62, "ch4"] = 0.50 + Gcomp[63, "h2"] = 0.47 + Gcomp[63, "ch4"] = 0.40 + Gcomp[63, "ben"] = 0.01 + Gcomp[63, "tol"] = 0.12 + Gcomp[65, "h2"] = 0.50 + Gcomp[65, "ch4"] = 0.50 + Gcomp[66, "tol"] = 1.0 + Gcomp[69, "tol"] = 1.0 + Gcomp[70, "h2"] = 0.5 + Gcomp[70, "ch4"] = 0.4 + Gcomp[70, "tol"] = 0.10 + Gcomp[71, "h2"] = 0.40 + Gcomp[71, "ch4"] = 0.40 + Gcomp[71, "ben"] = 0.10 + Gcomp[71, "tol"] = 0.10 + Gcomp[72, "h2"] = 0.50 + Gcomp[72, "ch4"] = 0.50 + m.gcomp = Param( + m.str, m.compon, initialize=Gcomp, default=0, doc="guess composition values" + ) def cppara(compon, stream): """ @@ -322,115 +339,174 @@ def cppara(compon, stream): sum of heat capacities of all components in a stream, weighted by their composition """ return sum(m.cppure[compon] * m.gcomp[stream, compon] for compon in m.compon) - m.cp = Param(m.str, initialize=cppara, default=0, doc='heat capacities [kj per kg-mol-k]') + + m.cp = Param( + m.str, initialize=cppara, default=0, doc="heat capacities [kj per kg-mol-k]" + ) Anta = {} - Anta['h2'] = 13.6333 - Anta['ch4'] = 15.2243 - Anta['ben'] = 15.9008 - Anta['tol'] = 16.0137 - Anta['dip'] = 16.6832 - m.anta = Param(m.compon, initialize=Anta, default=0, doc='antoine coefficient A') + Anta["h2"] = 13.6333 + Anta["ch4"] = 15.2243 + Anta["ben"] = 15.9008 + Anta["tol"] = 16.0137 + Anta["dip"] = 16.6832 + m.anta = Param(m.compon, initialize=Anta, default=0, doc="antoine coefficient A") Antb = {} - Antb['h2'] = 164.9 - Antb['ch4'] = 897.84 - Antb['ben'] = 2788.51 - Antb['tol'] = 3096.52 - Antb['dip'] = 4602.23 - m.antb = Param(m.compon, initialize=Antb, default=0, doc='antoine coefficient B') + Antb["h2"] = 164.9 + Antb["ch4"] = 897.84 + Antb["ben"] = 2788.51 + Antb["tol"] = 3096.52 + Antb["dip"] = 4602.23 + m.antb = Param(m.compon, initialize=Antb, default=0, doc="antoine coefficient B") Antc = {} - Antc['h2'] = 3.19 - Antc['ch4'] = -7.16 - Antc['ben'] = -52.36 - Antc['tol'] = -53.67 - Antc['dip'] = -70.42 - m.antc = Param(m.compon, initialize=Antc, default=0, doc='antoine coefficient C') + Antc["h2"] = 3.19 + Antc["ch4"] = -7.16 + Antc["ben"] = -52.36 + Antc["tol"] = -53.67 + Antc["dip"] = -70.42 + m.antc = Param(m.compon, initialize=Antc, default=0, doc="antoine coefficient C") Perm = {} for i in m.compon: Perm[i] = 0 - Perm['h2'] = 55.0e-06 - Perm['ch4'] = 2.3e-06 + Perm["h2"] = 55.0e-06 + Perm["ch4"] = 2.3e-06 + def Permset(m, compon): """ permeability [kg-mole/m**2-min-mpa] converting unit for permeability from cc/cm**2-sec-cmHg to kg-mole/m**2-min-mpa """ - return Perm[compon] * (1. / 22400.) * 1.0e4 * 750.062 * 60. / 1000. - m.perm = Param(m.compon, initialize=Permset, default=0, doc='permeability [kg-mole/m**2-min-mpa]') + return Perm[compon] * (1.0 / 22400.0) * 1.0e4 * 750.062 * 60.0 / 1000.0 + + m.perm = Param( + m.compon, + initialize=Permset, + default=0, + doc="permeability [kg-mole/m**2-min-mpa]", + ) Cbeta = {} - Cbeta['h2'] = 1.0003 - Cbeta['ch4'] = 1.0008 - Cbeta['dip'] = 1.0e+04 - m.cbeta = Param(m.compon, initialize=Cbeta, default=0, doc='constant values (exp(beta)) in absorber') + Cbeta["h2"] = 1.0003 + Cbeta["ch4"] = 1.0008 + Cbeta["dip"] = 1.0e04 + m.cbeta = Param( + m.compon, + initialize=Cbeta, + default=0, + doc="constant values (exp(beta)) in absorber", + ) Aabs = {} - Aabs['ben'] = 1.4 - Aabs['tol'] = 4.0 - m.aabs = Param(m.compon, initialize=Aabs, default=0, doc='absorption factors') - m.eps1 = Param(initialize=1e-4, doc='small number to avoid div. by zero') + Aabs["ben"] = 1.4 + Aabs["tol"] = 4.0 + m.aabs = Param(m.compon, initialize=Aabs, default=0, doc="absorption factors") + m.eps1 = Param(initialize=1e-4, doc="small number to avoid div. by zero") Heatrxn = {} - Heatrxn[1] = 50100. - Heatrxn[2] = 50100. - m.heatrxn = Param(m.rct, initialize=Heatrxn, default=0, doc='heat of reaction [kj per kg-mol]') + Heatrxn[1] = 50100.0 + Heatrxn[2] = 50100.0 + m.heatrxn = Param( + m.rct, initialize=Heatrxn, default=0, doc="heat of reaction [kj per kg-mol]" + ) F1comp = {} - F1comp['h2'] = 0.95 - F1comp['ch4'] = 0.05 - F1comp['dip'] = 0.00 - F1comp['ben'] = 0.00 - F1comp['tol'] = 0.00 - m.f1comp = Param(m.compon, initialize=F1comp, default=0, doc='feedstock compositions (h2 feed)') + F1comp["h2"] = 0.95 + F1comp["ch4"] = 0.05 + F1comp["dip"] = 0.00 + F1comp["ben"] = 0.00 + F1comp["tol"] = 0.00 + m.f1comp = Param( + m.compon, initialize=F1comp, default=0, doc="feedstock compositions (h2 feed)" + ) F66comp = {} - F66comp['tol'] = 1.0 - F66comp['h2'] = 0.00 - F66comp['ch4'] = 0.00 - F66comp['dip'] = 0.00 - F66comp['ben'] = 0.00 - m.f66comp = Param(m.compon, initialize=F66comp, default=0, doc='feedstock compositions (tol feed)') + F66comp["tol"] = 1.0 + F66comp["h2"] = 0.00 + F66comp["ch4"] = 0.00 + F66comp["dip"] = 0.00 + F66comp["ben"] = 0.00 + m.f66comp = Param( + m.compon, initialize=F66comp, default=0, doc="feedstock compositions (tol feed)" + ) F67comp = {} - F67comp['tol'] = 1.0 - F67comp['h2'] = 0.00 - F67comp['ch4'] = 0.00 - F67comp['dip'] = 0.00 - F67comp['ben'] = 0.00 - m.f67comp = Param(m.compon, initialize=F67comp, default=0, doc='feedstock compositions (tol feed)') + F67comp["tol"] = 1.0 + F67comp["h2"] = 0.00 + F67comp["ch4"] = 0.00 + F67comp["dip"] = 0.00 + F67comp["ben"] = 0.00 + m.f67comp = Param( + m.compon, initialize=F67comp, default=0, doc="feedstock compositions (tol feed)" + ) # # matching streams m.ilabs = Set(initialize=[(1, 67)], doc="abs-stream (inlet liquid) matches") m.olabs = Set(initialize=[(1, 68)], doc="abs-stream (outlet liquid) matches") m.ivabs = Set(initialize=[(1, 63)], doc="abs-stream (inlet vapor) matches") m.ovabs = Set(initialize=[(1, 64)], doc="abs-stream (outlet vapor) matches") - m.asolv = Set(initialize=[(1, 'tol')], doc="abs-solvent component matches") - m.anorm = Set(initialize=[(1, 'ben')], doc="abs-comp matches (normal model)") - m.asimp = Set(initialize=[(1, 'h2'), (1, 'ch4'), (1, 'dip')], doc="abs-heavy component matches") - - m.icomp = Set(initialize=[(1, 5), (2, 59), (3, 64), (4, 56)], doc="compressor-stream (inlet) matches") - m.ocomp = Set(initialize=[(1, 6), (2, 60), (3, 65), (4, 57)], doc="compressor-stream (outlet) matches") - - m.idist = Set(initialize=[(1, 25), (2, 30), (3, 33)], doc="dist-stream (inlet) matches") - m.vdist = Set(initialize=[(1, 26), (2, 31), (3, 34)], doc="dist-stream (vapor) matches") - m.ldist = Set(initialize=[(1, 27), (2, 32), (3, 35)], doc="dist-stream (liquid) matches") - m.dl = Set(initialize=[(1, 'h2'), (2, 'ch4'), (3, 'ben')], doc="dist-light components matches") - m.dlkey = Set(initialize=[(1, 'ch4'), (2, 'ben'), (3, 'tol')], doc="dist-heavy key component matches") - m.dhkey = Set(initialize=[(1, 'ben'), (2, 'tol'), (3, 'dip')], doc="dist-heavy components matches") - m.dh = Set(initialize=[(1, 'tol'), (1, 'dip'), (2, 'dip')], doc="dist-key component matches") + m.asolv = Set(initialize=[(1, "tol")], doc="abs-solvent component matches") + m.anorm = Set(initialize=[(1, "ben")], doc="abs-comp matches (normal model)") + m.asimp = Set( + initialize=[(1, "h2"), (1, "ch4"), (1, "dip")], + doc="abs-heavy component matches", + ) + + m.icomp = Set( + initialize=[(1, 5), (2, 59), (3, 64), (4, 56)], + doc="compressor-stream (inlet) matches", + ) + m.ocomp = Set( + initialize=[(1, 6), (2, 60), (3, 65), (4, 57)], + doc="compressor-stream (outlet) matches", + ) + + m.idist = Set( + initialize=[(1, 25), (2, 30), (3, 33)], doc="dist-stream (inlet) matches" + ) + m.vdist = Set( + initialize=[(1, 26), (2, 31), (3, 34)], doc="dist-stream (vapor) matches" + ) + m.ldist = Set( + initialize=[(1, 27), (2, 32), (3, 35)], doc="dist-stream (liquid) matches" + ) + m.dl = Set( + initialize=[(1, "h2"), (2, "ch4"), (3, "ben")], + doc="dist-light components matches", + ) + m.dlkey = Set( + initialize=[(1, "ch4"), (2, "ben"), (3, "tol")], + doc="dist-heavy key component matches", + ) + m.dhkey = Set( + initialize=[(1, "ben"), (2, "tol"), (3, "dip")], + doc="dist-heavy components matches", + ) + m.dh = Set( + initialize=[(1, "tol"), (1, "dip"), (2, "dip")], + doc="dist-key component matches", + ) i = list(m.dlkey) q = list(m.dhkey) dkeyset = i + q - m.dkey = Set(initialize=dkeyset, doc='dist-key component matches') - - m.iflsh = Set(initialize=[(1, 17), (2, 46), (3, 39)], doc="flsh-stream (inlet) matches") - m.vflsh = Set(initialize=[(1, 18), (2, 47), (3, 40)], doc="flsh-stream (vapor) matches") - m.lflsh = Set(initialize=[(1, 19), (2, 48), (3, 41)], doc="flsh-stream (liquid) matches") - m.fkey = Set(initialize=[(1, 'ch4'), (2, 'ch4'), (3, 'tol')], doc="flash-key component matches") + m.dkey = Set(initialize=dkeyset, doc="dist-key component matches") + + m.iflsh = Set( + initialize=[(1, 17), (2, 46), (3, 39)], doc="flsh-stream (inlet) matches" + ) + m.vflsh = Set( + initialize=[(1, 18), (2, 47), (3, 40)], doc="flsh-stream (vapor) matches" + ) + m.lflsh = Set( + initialize=[(1, 19), (2, 48), (3, 41)], doc="flsh-stream (liquid) matches" + ) + m.fkey = Set( + initialize=[(1, "ch4"), (2, "ch4"), (3, "tol")], + doc="flash-key component matches", + ) m.ifurn = Set(initialize=[(1, 70)], doc="furn-stream (inlet) matches") m.ofurn = Set(initialize=[(1, 9)], doc="furn-stream (outlet) matches") @@ -438,8 +514,14 @@ def Permset(m, compon): m.ihec = Set(initialize=[(1, 71), (2, 45)], doc="hec-stream (inlet) matches") m.ohec = Set(initialize=[(1, 17), (2, 46)], doc="hec-stream (outlet) matches") - m.iheh = Set(initialize=[(1, 24), (2, 23), (3, 37), (4, 61)], doc="heh-stream (inlet) matches") - m.oheh = Set(initialize=[(1, 25), (2, 44), (3, 38), (4, 73)], doc="heh-stream (outlet) matches") + m.iheh = Set( + initialize=[(1, 24), (2, 23), (3, 37), (4, 61)], + doc="heh-stream (inlet) matches", + ) + m.oheh = Set( + initialize=[(1, 25), (2, 44), (3, 38), (4, 73)], + doc="heh-stream (outlet) matches", + ) m.icexch = Set(initialize=[(1, 8)], doc="exch-cold stream (inlet) matches") m.ocexch = Set(initialize=[(1, 70)], doc="exch-cold stream (outlet) matches") @@ -447,89 +529,348 @@ def Permset(m, compon): m.ohexch = Set(initialize=[(1, 71)], doc="exch-hot stream (outlet) matches") m.imemb = Set(initialize=[(1, 3), (2, 54)], doc="memb-stream (inlet) matches") - m.nmemb = Set(initialize=[(1, 4), (2, 55)], doc="memb-stream (non-permeate) matches") + m.nmemb = Set( + initialize=[(1, 4), (2, 55)], doc="memb-stream (non-permeate) matches" + ) m.pmemb = Set(initialize=[(1, 5), (2, 56)], doc="memb-stream (permeate) matches") - m.mnorm = Set(initialize=[(1, 'h2'), (1, 'ch4'), (2, 'h2'), (2, 'ch4')], doc="normal components") - m.msimp = Set(initialize=[(1, 'ben'), (1, 'tol'), (1, 'dip'), (2, 'ben'), (2, 'tol'), (2, 'dip')], doc="simplified flux components") - - m.imxr1 = Set(initialize=[(1, 2), (1, 6), (2, 11), (2, 13), (3, 27), (3, 48), (4, 34), (4, 40), (5, 49), (5, 50)], doc="mixer-stream (inlet) matches") - m.omxr1 = Set(initialize=[(1, 7), (2, 14), (3, 30), (4, 42), (5, 51)], doc="mixer-stream (outlet) matches") - m.mxr1spl1 = Set(initialize=[(1, 2, 2), (1, 6, 3), (2, 11, 10), (2, 13, 12), (3, 27, 24), (3, 48, 23), (4, 34, 33), (4, 40, 37), (5, 49, 23), (5, 50, 24)], doc="1-mxr-inlet 1-spl-outlet matches") - - m.imxr = Set(initialize=[(1, 7), (1, 43), (1, 66), (1, 72), (2, 15), (2, 20), (3, 21), (3, 69), (4, 51), (4, 62), (5, 57), (5, 60), (5, 65)], doc="mixer-stream (inlet) matches") - m.omxr = Set(initialize=[(1, 8), (2, 16), (3, 22), (4, 63), (5, 72)], doc="mixer-stream (outlet) matches ") + m.mnorm = Set( + initialize=[(1, "h2"), (1, "ch4"), (2, "h2"), (2, "ch4")], + doc="normal components", + ) + m.msimp = Set( + initialize=[ + (1, "ben"), + (1, "tol"), + (1, "dip"), + (2, "ben"), + (2, "tol"), + (2, "dip"), + ], + doc="simplified flux components", + ) + + m.imxr1 = Set( + initialize=[ + (1, 2), + (1, 6), + (2, 11), + (2, 13), + (3, 27), + (3, 48), + (4, 34), + (4, 40), + (5, 49), + (5, 50), + ], + doc="mixer-stream (inlet) matches", + ) + m.omxr1 = Set( + initialize=[(1, 7), (2, 14), (3, 30), (4, 42), (5, 51)], + doc="mixer-stream (outlet) matches", + ) + m.mxr1spl1 = Set( + initialize=[ + (1, 2, 2), + (1, 6, 3), + (2, 11, 10), + (2, 13, 12), + (3, 27, 24), + (3, 48, 23), + (4, 34, 33), + (4, 40, 37), + (5, 49, 23), + (5, 50, 24), + ], + doc="1-mxr-inlet 1-spl-outlet matches", + ) + + m.imxr = Set( + initialize=[ + (1, 7), + (1, 43), + (1, 66), + (1, 72), + (2, 15), + (2, 20), + (3, 21), + (3, 69), + (4, 51), + (4, 62), + (5, 57), + (5, 60), + (5, 65), + ], + doc="mixer-stream (inlet) matches", + ) + m.omxr = Set( + initialize=[(1, 8), (2, 16), (3, 22), (4, 63), (5, 72)], + doc="mixer-stream (outlet) matches ", + ) m.ipump = Set(initialize=[(1, 42), (2, 68)], doc="pump-stream (inlet) matches") m.opump = Set(initialize=[(1, 43), (2, 69)], doc="pump-stream (outlet) matches") m.irct = Set(initialize=[(1, 10), (2, 12)], doc="reactor-stream (inlet) matches") m.orct = Set(initialize=[(1, 11), (2, 13)], doc="reactor-stream (outlet) matches") - m.rkey = Set(initialize=[(1, 'tol'), (2, 'tol')], doc="reactor-key component matches") - - m.ispl1 = Set(initialize=[(1, 1), (2, 9), (3, 22), (4, 32), (5, 52), (6, 58)], doc="splitter-stream (inlet) matches") - m.ospl1 = Set(initialize=[(1, 2), (1, 3), (2, 10), (2, 12), (3, 23), (3, 24), (4, 33), (4, 37), (5, 53), (5, 54), (6, 59), (6, 61)], doc="splitter-stream (outlet) matches") - - m.ispl = Set(initialize=[(1, 19), (2, 18), (3, 26)], doc="splitter-stream (inlet) matches") - m.ospl = Set(initialize=[(1, 20), (1, 21), (2, 52), (2, 58), (3, 28), (3, 29)], doc="splitter-stream (outlet) matches") - - m.ival = Set(initialize=[(1, 44), (2, 38), (3, 14), (4, 47), (5, 29), (6, 73)], doc="exp.valve-stream (inlet) matches") - m.oval = Set(initialize=[(1, 45), (2, 39), (3, 15), (4, 49), (5, 50), (6, 62)], doc="exp.valve-stream (outlet) matches") + m.rkey = Set( + initialize=[(1, "tol"), (2, "tol")], doc="reactor-key component matches" + ) + + m.ispl1 = Set( + initialize=[(1, 1), (2, 9), (3, 22), (4, 32), (5, 52), (6, 58)], + doc="splitter-stream (inlet) matches", + ) + m.ospl1 = Set( + initialize=[ + (1, 2), + (1, 3), + (2, 10), + (2, 12), + (3, 23), + (3, 24), + (4, 33), + (4, 37), + (5, 53), + (5, 54), + (6, 59), + (6, 61), + ], + doc="splitter-stream (outlet) matches", + ) + + m.ispl = Set( + initialize=[(1, 19), (2, 18), (3, 26)], doc="splitter-stream (inlet) matches" + ) + m.ospl = Set( + initialize=[(1, 20), (1, 21), (2, 52), (2, 58), (3, 28), (3, 29)], + doc="splitter-stream (outlet) matches", + ) + + m.ival = Set( + initialize=[(1, 44), (2, 38), (3, 14), (4, 47), (5, 29), (6, 73)], + doc="exp.valve-stream (inlet) matches", + ) + m.oval = Set( + initialize=[(1, 45), (2, 39), (3, 15), (4, 49), (5, 50), (6, 62)], + doc="exp.valve-stream (outlet) matches", + ) # variables # absorber - m.nabs = Var(m.abs, within=NonNegativeReals, bounds=(0, 40), initialize=1, doc='number of absorber trays') - m.gamma = Var(m.abs, m.compon, within=Reals, initialize=1, doc='gamma') - m.beta = Var(m.abs, m.compon, within=Reals, initialize=1, doc='beta') + m.nabs = Var( + m.abs, + within=NonNegativeReals, + bounds=(0, 40), + initialize=1, + doc="number of absorber trays", + ) + m.gamma = Var(m.abs, m.compon, within=Reals, initialize=1, doc="gamma") + m.beta = Var(m.abs, m.compon, within=Reals, initialize=1, doc="beta") # compressor - m.elec = Var(m.comp, within=NonNegativeReals, bounds=(0, 100), initialize=1, doc='electricity requirement [kw]') - m.presrat = Var(m.comp, within=NonNegativeReals, bounds=(1, 8/3), initialize=1, doc='ratio of outlet to inlet pressure') + m.elec = Var( + m.comp, + within=NonNegativeReals, + bounds=(0, 100), + initialize=1, + doc="electricity requirement [kw]", + ) + m.presrat = Var( + m.comp, + within=NonNegativeReals, + bounds=(1, 8 / 3), + initialize=1, + doc="ratio of outlet to inlet pressure", + ) # distillation - m.nmin = Var(m.dist, within=NonNegativeReals, initialize=1, doc='minimum number of trays in column') - m.ndist = Var(m.dist, within=NonNegativeReals, initialize=1, doc='number of trays in column') - m.rmin = Var(m.dist, within=NonNegativeReals, initialize=1, doc='minimum reflux ratio') - m.reflux = Var(m.dist, within=NonNegativeReals, initialize=1, doc='reflux ratio') - m.distp = Var(m.dist, within=NonNegativeReals, initialize=1, bounds=(0.1, 4.0), doc='column pressure [mega-pascal]') - m.avevlt = Var(m.dist, within=NonNegativeReals, initialize=1, doc='average volatility') + m.nmin = Var( + m.dist, + within=NonNegativeReals, + initialize=1, + doc="minimum number of trays in column", + ) + m.ndist = Var( + m.dist, within=NonNegativeReals, initialize=1, doc="number of trays in column" + ) + m.rmin = Var( + m.dist, within=NonNegativeReals, initialize=1, doc="minimum reflux ratio" + ) + m.reflux = Var(m.dist, within=NonNegativeReals, initialize=1, doc="reflux ratio") + m.distp = Var( + m.dist, + within=NonNegativeReals, + initialize=1, + bounds=(0.1, 4.0), + doc="column pressure [mega-pascal]", + ) + m.avevlt = Var( + m.dist, within=NonNegativeReals, initialize=1, doc="average volatility" + ) # flash - m.flsht = Var(m.flsh, within=NonNegativeReals, initialize=1, doc='flash temperature [100 k]') - m.flshp = Var(m.flsh, within=NonNegativeReals, initialize=1, doc='flash pressure [mega-pascal]') - m.eflsh = Var(m.flsh, m.compon, within=NonNegativeReals, bounds=(0, 1), initialize=0.5, doc='vapor phase recovery in flash') + m.flsht = Var( + m.flsh, within=NonNegativeReals, initialize=1, doc="flash temperature [100 k]" + ) + m.flshp = Var( + m.flsh, + within=NonNegativeReals, + initialize=1, + doc="flash pressure [mega-pascal]", + ) + m.eflsh = Var( + m.flsh, + m.compon, + within=NonNegativeReals, + bounds=(0, 1), + initialize=0.5, + doc="vapor phase recovery in flash", + ) # furnace - m.qfuel = Var(m.furn, within=NonNegativeReals, bounds=(None, 10), initialize=1, doc='heating required [1.e+12 kj per yr]') + m.qfuel = Var( + m.furn, + within=NonNegativeReals, + bounds=(None, 10), + initialize=1, + doc="heating required [1.e+12 kj per yr]", + ) # cooler - m.qc = Var(m.hec, within=NonNegativeReals, bounds=(None, 10), initialize=1, doc='utility requirement [1.e+12 kj per yr]') + m.qc = Var( + m.hec, + within=NonNegativeReals, + bounds=(None, 10), + initialize=1, + doc="utility requirement [1.e+12 kj per yr]", + ) # heater - m.qh = Var(m.heh, within=NonNegativeReals, bounds=(None, 10), initialize=1, doc='utility requirement [1.e+12 kj per yr]') + m.qh = Var( + m.heh, + within=NonNegativeReals, + bounds=(None, 10), + initialize=1, + doc="utility requirement [1.e+12 kj per yr]", + ) # exchanger - m.qexch = Var(m.exch, within=NonNegativeReals, bounds=(None, 10), initialize=1, doc='heat exchanged [1.e+12 kj per yr]') + m.qexch = Var( + m.exch, + within=NonNegativeReals, + bounds=(None, 10), + initialize=1, + doc="heat exchanged [1.e+12 kj per yr]", + ) # membrane - m.a = Var(m.memb, within=NonNegativeReals, bounds=(100, 10000), initialize=1, doc='surface area for mass transfer [m**2]') + m.a = Var( + m.memb, + within=NonNegativeReals, + bounds=(100, 10000), + initialize=1, + doc="surface area for mass transfer [m**2]", + ) # mixer(1 input) - m.mxr1p = Var(m.mxr1, within=NonNegativeReals, bounds=(0.1, 4), initialize=0, doc='mixer temperature [100 k]') - m.mxr1t = Var(m.mxr1, within=NonNegativeReals, bounds=(3, 10), initialize=0, doc='mixer pressure [mega-pascal]') + m.mxr1p = Var( + m.mxr1, + within=NonNegativeReals, + bounds=(0.1, 4), + initialize=0, + doc="mixer temperature [100 k]", + ) + m.mxr1t = Var( + m.mxr1, + within=NonNegativeReals, + bounds=(3, 10), + initialize=0, + doc="mixer pressure [mega-pascal]", + ) # mixer - m.mxrt = Var(m.mxr, within=NonNegativeReals, bounds=(3.0, 10), initialize=3, doc='mixer temperature [100 k]') - m.mxrp = Var(m.mxr, within=NonNegativeReals, bounds=(0.1, 4.0), initialize=3, doc='mixer pressure [mega-pascal]') + m.mxrt = Var( + m.mxr, + within=NonNegativeReals, + bounds=(3.0, 10), + initialize=3, + doc="mixer temperature [100 k]", + ) + m.mxrp = Var( + m.mxr, + within=NonNegativeReals, + bounds=(0.1, 4.0), + initialize=3, + doc="mixer pressure [mega-pascal]", + ) # reactor - m.rctt = Var(m.rct, within=NonNegativeReals, bounds=(8.9427, 9.7760), doc='reactor temperature [100 k]') - m.rctp = Var(m.rct, within=NonNegativeReals, bounds=(3.4474, 3.4474), doc='reactor pressure [mega-pascal]') - m.rctvol = Var(m.rct, within=NonNegativeReals, bounds=(None, 200), doc='reactor volume [cubic meter]') - m.krct = Var(m.rct, within=NonNegativeReals, initialize=1, bounds=(0.0123471, 0.149543), doc='rate constant') - m.conv = Var(m.rct, m.compon, within=NonNegativeReals, bounds=(None, 0.973), doc='conversion of key component') - m.sel = Var(m.rct, within=NonNegativeReals, bounds=(None, 0.9964), doc='selectivity to benzene') - m.consum = Var(m.rct, m.compon, within=NonNegativeReals, bounds=(0, 10000000000), initialize=0, doc='consumption rate of key') - m.q = Var(m.rct, within=NonNegativeReals, bounds=(0, 10000000000), doc='heat removed [1.e+9 kj per yr]') + m.rctt = Var( + m.rct, + within=NonNegativeReals, + bounds=(8.9427, 9.7760), + doc="reactor temperature [100 k]", + ) + m.rctp = Var( + m.rct, + within=NonNegativeReals, + bounds=(3.4474, 3.4474), + doc="reactor pressure [mega-pascal]", + ) + m.rctvol = Var( + m.rct, + within=NonNegativeReals, + bounds=(None, 200), + doc="reactor volume [cubic meter]", + ) + m.krct = Var( + m.rct, + within=NonNegativeReals, + initialize=1, + bounds=(0.0123471, 0.149543), + doc="rate constant", + ) + m.conv = Var( + m.rct, + m.compon, + within=NonNegativeReals, + bounds=(None, 0.973), + doc="conversion of key component", + ) + m.sel = Var( + m.rct, + within=NonNegativeReals, + bounds=(None, 0.9964), + doc="selectivity to benzene", + ) + m.consum = Var( + m.rct, + m.compon, + within=NonNegativeReals, + bounds=(0, 10000000000), + initialize=0, + doc="consumption rate of key", + ) + m.q = Var( + m.rct, + within=NonNegativeReals, + bounds=(0, 10000000000), + doc="heat removed [1.e+9 kj per yr]", + ) # splitter (1 output) - m.spl1t = Var(m.spl1, within=PositiveReals, bounds=(3.00, 10.00), doc='splitter temperature [100 k]') - m.spl1p = Var(m.spl1, within=PositiveReals, bounds=(0.1, 4.0), doc='splitter pressure [mega-pascal]') + m.spl1t = Var( + m.spl1, + within=PositiveReals, + bounds=(3.00, 10.00), + doc="splitter temperature [100 k]", + ) + m.spl1p = Var( + m.spl1, + within=PositiveReals, + bounds=(0.1, 4.0), + doc="splitter pressure [mega-pascal]", + ) # splitter - m.splp = Var(m.spl, within=Reals, bounds=(0.1, 4.0), doc='splitter pressure [mega-pascal]') - m.splt = Var(m.spl, within=Reals, bounds=(3.0, 10.0), doc='splitter temperature [100 k]') + m.splp = Var( + m.spl, within=Reals, bounds=(0.1, 4.0), doc="splitter pressure [mega-pascal]" + ) + m.splt = Var( + m.spl, within=Reals, bounds=(3.0, 10.0), doc="splitter temperature [100 k]" + ) # stream def bound_f(m, stream): @@ -540,10 +881,17 @@ def bound_f(m, stream): if stream in range(8, 19): return (0, 50) elif stream in [52, 54, 56, 57, 58, 59, 60, 70, 71, 72]: - return(0, 50) + return (0, 50) else: return (0, 10) - m.f = Var(m.str, within=NonNegativeReals, bounds=bound_f, initialize=1, doc='stream flowrates [kg-mole per min]') + + m.f = Var( + m.str, + within=NonNegativeReals, + bounds=bound_f, + initialize=1, + doc="stream flowrates [kg-mole per min]", + ) def bound_fc(m, stream, compon): """ @@ -553,10 +901,37 @@ def bound_fc(m, stream, compon): return (0, 30) else: return (0, 10) - m.fc = Var(m.str, m.compon, within=Reals, bounds=bound_fc, initialize=1, doc='component flowrates [kg-mole per min]') - m.p = Var(m.str, within=NonNegativeReals, bounds=(0.1, 4.0), initialize=3.0, doc='stream pressure [mega-pascal]') - m.t = Var(m.str, within=NonNegativeReals, bounds=(3.0, 10.0), initialize=3.0, doc='stream temperature [100 k]') - m.vp = Var(m.str, m.compon, within=NonNegativeReals, initialize=1, bounds=(0, 10), doc='vapor pressure [mega-pascal]') + + m.fc = Var( + m.str, + m.compon, + within=Reals, + bounds=bound_fc, + initialize=1, + doc="component flowrates [kg-mole per min]", + ) + m.p = Var( + m.str, + within=NonNegativeReals, + bounds=(0.1, 4.0), + initialize=3.0, + doc="stream pressure [mega-pascal]", + ) + m.t = Var( + m.str, + within=NonNegativeReals, + bounds=(3.0, 10.0), + initialize=3.0, + doc="stream temperature [100 k]", + ) + m.vp = Var( + m.str, + m.compon, + within=NonNegativeReals, + initialize=1, + bounds=(0, 10), + doc="vapor pressure [mega-pascal]", + ) def boundsofe(m): """ @@ -568,23 +943,24 @@ def boundsofe(m): return (0.5, 1.0) else: return (None, 1.0) - m.e = Var(m.str, within=NonNegativeReals, bounds=boundsofe, doc='split fraction') - # obj function constant term - m.const = Param(initialize=22.5, doc='constant term in obj fcn') + m.e = Var(m.str, within=NonNegativeReals, bounds=boundsofe, doc="split fraction") + + # obj function constant term + m.const = Param(initialize=22.5, doc="constant term in obj fcn") # ## setting variable bounds m.q[2].setub(100) for rct in m.rct: - m.conv[rct, 'tol'].setub(0.973) + m.conv[rct, "tol"].setub(0.973) m.sel.setub(1.0 - 0.0036) - m.reflux[1].setlb(0.02*1.2) - m.reflux[1].setub(0.10*1.2) - m.reflux[2].setlb(0.50*1.2) - m.reflux[2].setub(2.00*1.2) - m.reflux[3].setlb(0.02*1.2) - m.reflux[3].setub(0.1*1.2) + m.reflux[1].setlb(0.02 * 1.2) + m.reflux[1].setub(0.10 * 1.2) + m.reflux[2].setlb(0.50 * 1.2) + m.reflux[2].setub(2.00 * 1.2) + m.reflux[3].setlb(0.02 * 1.2) + m.reflux[3].setub(0.1 * 1.2) m.nmin[1].setlb(0) m.nmin[1].setub(4) m.nmin[2].setlb(8) @@ -592,11 +968,11 @@ def boundsofe(m): m.nmin[3].setlb(0) m.nmin[3].setub(4) m.ndist[1].setlb(0) - m.ndist[1].setub(4*2/m.disteff) + m.ndist[1].setub(4 * 2 / m.disteff) m.ndist[3].setlb(0) - m.ndist[3].setub(4*2/m.disteff) - m.ndist[2].setlb(8*2/m.disteff) - m.ndist[2].setub(14*2/m.disteff) + m.ndist[3].setub(4 * 2 / m.disteff) + m.ndist[2].setlb(8 * 2 / m.disteff) + m.ndist[2].setub(14 * 2 / m.disteff) m.rmin[1].setlb(0.02) m.rmin[1].setub(0.10) m.rmin[2].setlb(0.50) @@ -611,43 +987,144 @@ def boundsofe(m): m.t[26].setub(3.2) for i in range(49, 52): m.t[i].setlb(2.0) - m.t[27].setlb((m.antb['ben'] / (m.anta['ben'] - log(m.distp[1].lb * 7500.6168)) - m.antc['ben']) / 100.) - m.t[27].setub((m.antb['ben'] / (m.anta['ben'] - log(m.distp[1].ub * 7500.6168)) - m.antc['ben']) / 100.) - m.t[31].setlb((m.antb['ben'] / (m.anta['ben'] - log(m.distp[2].lb * 7500.6168)) - m.antc['ben']) / 100.) - m.t[31].setub((m.antb['ben'] / (m.anta['ben'] - log(m.distp[2].ub * 7500.6168)) - m.antc['ben']) / 100.) - m.t[32].setlb((m.antb['tol'] / (m.anta['tol'] - log(m.distp[2].lb * 7500.6168)) - m.antc['tol']) / 100.) - m.t[32].setub((m.antb['tol'] / (m.anta['tol'] - log(m.distp[2].ub * 7500.6168)) - m.antc['tol']) / 100.) - m.t[34].setlb((m.antb['tol'] / (m.anta['tol'] - log(m.distp[3].lb * 7500.6168)) - m.antc['tol']) / 100.) - m.t[34].setub((m.antb['tol'] / (m.anta['tol'] - log(m.distp[3].ub * 7500.6168)) - m.antc['tol']) / 100.) - m.t[35].setlb((m.antb['dip'] / (m.anta['dip'] - log(m.distp[3].lb * 7500.6168)) - m.antc['dip']) / 100.) - m.t[35].setub((m.antb['dip'] / (m.anta['dip'] - log(m.distp[3].ub * 7500.6168)) - m.antc['dip']) / 100.) + m.t[27].setlb( + ( + m.antb["ben"] / (m.anta["ben"] - log(m.distp[1].lb * 7500.6168)) + - m.antc["ben"] + ) + / 100.0 + ) + m.t[27].setub( + ( + m.antb["ben"] / (m.anta["ben"] - log(m.distp[1].ub * 7500.6168)) + - m.antc["ben"] + ) + / 100.0 + ) + m.t[31].setlb( + ( + m.antb["ben"] / (m.anta["ben"] - log(m.distp[2].lb * 7500.6168)) + - m.antc["ben"] + ) + / 100.0 + ) + m.t[31].setub( + ( + m.antb["ben"] / (m.anta["ben"] - log(m.distp[2].ub * 7500.6168)) + - m.antc["ben"] + ) + / 100.0 + ) + m.t[32].setlb( + ( + m.antb["tol"] / (m.anta["tol"] - log(m.distp[2].lb * 7500.6168)) + - m.antc["tol"] + ) + / 100.0 + ) + m.t[32].setub( + ( + m.antb["tol"] / (m.anta["tol"] - log(m.distp[2].ub * 7500.6168)) + - m.antc["tol"] + ) + / 100.0 + ) + m.t[34].setlb( + ( + m.antb["tol"] / (m.anta["tol"] - log(m.distp[3].lb * 7500.6168)) + - m.antc["tol"] + ) + / 100.0 + ) + m.t[34].setub( + ( + m.antb["tol"] / (m.anta["tol"] - log(m.distp[3].ub * 7500.6168)) + - m.antc["tol"] + ) + / 100.0 + ) + m.t[35].setlb( + ( + m.antb["dip"] / (m.anta["dip"] - log(m.distp[3].lb * 7500.6168)) + - m.antc["dip"] + ) + / 100.0 + ) + m.t[35].setub( + ( + m.antb["dip"] / (m.anta["dip"] - log(m.distp[3].ub * 7500.6168)) + - m.antc["dip"] + ) + / 100.0 + ) # absorber - m.beta[1, 'ben'].setlb(0.00011776) - m.beta[1, 'ben'].setub(5.72649) - m.beta[1, 'tol'].setlb(0.00018483515) - m.beta[1, 'tol'].setub(15) - m.gamma[1, 'tol'].setlb(log((1 - m.aabs['tol'] ** (m.nabs[1].lb * m.abseff + m.eps1)) / (1 - m.aabs['tol']))) - m.gamma[1, 'tol'].setub(min(15, log((1 - m.aabs['tol'] ** (m.nabs[1].ub * m.abseff + m.eps1)) / (1 - m.aabs['tol'])))) + m.beta[1, "ben"].setlb(0.00011776) + m.beta[1, "ben"].setub(5.72649) + m.beta[1, "tol"].setlb(0.00018483515) + m.beta[1, "tol"].setub(15) + m.gamma[1, "tol"].setlb( + log( + (1 - m.aabs["tol"] ** (m.nabs[1].lb * m.abseff + m.eps1)) + / (1 - m.aabs["tol"]) + ) + ) + m.gamma[1, "tol"].setub( + min( + 15, + log( + (1 - m.aabs["tol"] ** (m.nabs[1].ub * m.abseff + m.eps1)) + / (1 - m.aabs["tol"]) + ), + ) + ) for abso in m.abs: for compon in m.compon: - m.beta[abso, compon].setlb(log((1 - m.aabs[compon] ** (m.nabs[1].lb * m.abseff + m.eps1 + 1)) / (1 - m.aabs[compon]))) - m.beta[abso, compon].setub(min(15, log((1 - m.aabs[compon] ** (m.nabs[1].ub * m.abseff + m.eps1 + 1)) / (1 - m.aabs[compon])))) + m.beta[abso, compon].setlb( + log( + (1 - m.aabs[compon] ** (m.nabs[1].lb * m.abseff + m.eps1 + 1)) + / (1 - m.aabs[compon]) + ) + ) + m.beta[abso, compon].setub( + min( + 15, + log( + (1 - m.aabs[compon] ** (m.nabs[1].ub * m.abseff + m.eps1 + 1)) + / (1 - m.aabs[compon]) + ), + ) + ) m.t[67].setlb(3.0) m.t[67].setub(3.0) for compon in m.compon: - m.vp[67, compon].setlb((1. / 7500.6168) * exp(m.anta[compon] - m.antb[compon] / (value(m.t[67]) * 100. + m.antc[compon]))) - m.vp[67, compon].setub((1. / 7500.6168) * exp(m.anta[compon] - m.antb[compon] / (value(m.t[67]) * 100. + m.antc[compon]))) - - - flashdata_file = os.path.join(dir_path,'flashdata.csv') + m.vp[67, compon].setlb( + (1.0 / 7500.6168) + * exp( + m.anta[compon] + - m.antb[compon] / (value(m.t[67]) * 100.0 + m.antc[compon]) + ) + ) + m.vp[67, compon].setub( + (1.0 / 7500.6168) + * exp( + m.anta[compon] + - m.antb[compon] / (value(m.t[67]) * 100.0 + m.antc[compon]) + ) + ) + + flashdata_file = os.path.join(dir_path, "flashdata.csv") flash = pd.read_csv(flashdata_file, header=0) number = flash.iloc[:, [4]].dropna().values two_digit_number = flash.iloc[:, [0]].dropna().values two_digit_compon = flash.iloc[:, [1]].dropna().values for i in range(len(two_digit_number)): - m.eflsh[two_digit_number[i, 0], two_digit_compon[i, 0]].setlb(flash.iloc[:, [2]].dropna().values[i, 0]) - m.eflsh[two_digit_number[i, 0], two_digit_compon[i, 0]].setub(flash.iloc[:, [3]].dropna().values[i, 0]) + m.eflsh[two_digit_number[i, 0], two_digit_compon[i, 0]].setlb( + flash.iloc[:, [2]].dropna().values[i, 0] + ) + m.eflsh[two_digit_number[i, 0], two_digit_compon[i, 0]].setub( + flash.iloc[:, [3]].dropna().values[i, 0] + ) for i in range(len(number)): m.flshp[number[i, 0]].setlb(flash.iloc[:, [5]].dropna().values[i, 0]) m.flshp[number[i, 0]].setub(flash.iloc[:, [6]].dropna().values[i, 0]) @@ -666,8 +1143,20 @@ def boundsofe(m): for stream in m.str: for compon in m.compon: - m.vp[stream, compon].setlb((1. / 7500.6168) * exp(m.anta[compon] - m.antb[compon] / (m.t[stream].lb * 100. + m.antc[compon]))) - m.vp[stream, compon].setub((1. / 7500.6168) * exp(m.anta[compon] - m.antb[compon] / (m.t[stream].ub * 100. + m.antc[compon]))) + m.vp[stream, compon].setlb( + (1.0 / 7500.6168) + * exp( + m.anta[compon] + - m.antb[compon] / (m.t[stream].lb * 100.0 + m.antc[compon]) + ) + ) + m.vp[stream, compon].setub( + (1.0 / 7500.6168) + * exp( + m.anta[compon] + - m.antb[compon] / (m.t[stream].ub * 100.0 + m.antc[compon]) + ) + ) m.p[1].setub(3.93) m.p[1].setlb(3.93) @@ -683,25 +1172,23 @@ def boundsofe(m): if (dist, stream) in m.ldist and (dist, compon) in m.dlkey: m.avevlt[dist].setlb(m.vp[stream, compon].ub) if (dist, stream) in m.ldist and (dist, compon) in m.dhkey: - m.avevlt[dist].setlb( - m.avevlt[dist].lb/m.vp[stream, compon].ub) + m.avevlt[dist].setlb(m.avevlt[dist].lb / m.vp[stream, compon].ub) for dist in m.dist: for stream in m.str: for compon in m.compon: if (dist, stream) in m.vdist and (dist, compon) in m.dlkey: m.avevlt[dist].setub(m.vp[stream, compon].lb) if (dist, stream) in m.vdist and (dist, compon) in m.dhkey: - m.avevlt[dist].setub( - m.avevlt[dist].ub/m.vp[stream, compon].lb) + m.avevlt[dist].setub(m.avevlt[dist].ub / m.vp[stream, compon].lb) # ## initialization procedure # flash1 - m.eflsh[1, 'h2'] = 0.995 - m.eflsh[1, 'ch4'] = 0.99 - m.eflsh[1, 'ben'] = 0.04 - m.eflsh[1, 'tol'] = 0.01 - m.eflsh[1, 'dip'] = 0.0001 + m.eflsh[1, "h2"] = 0.995 + m.eflsh[1, "ch4"] = 0.99 + m.eflsh[1, "ben"] = 0.04 + m.eflsh[1, "tol"] = 0.01 + m.eflsh[1, "dip"] = 0.0001 # compressor m.distp[1] = 1.02 @@ -719,7 +1206,7 @@ def boundsofe(m): m.qfuel[1] = 0.0475341 m.q[2] = 54.3002 - file_1 = os.path.join(dir_path,'GAMS_init_stream_data.csv') + file_1 = os.path.join(dir_path, "GAMS_init_stream_data.csv") stream = pd.read_csv(file_1, usecols=[0]) data = pd.read_csv(file_1, usecols=[1]) temp = pd.read_csv(file_1, usecols=[3]) @@ -733,7 +1220,7 @@ def boundsofe(m): m.f[stream.to_numpy()[i, 0]] = flow.to_numpy()[i, 0] m.e[stream.to_numpy()[i, 0]] = e.to_numpy()[i, 0] - file_2 = os.path.join(dir_path,'GAMS_init_stream_compon_data.csv') + file_2 = os.path.join(dir_path, "GAMS_init_stream_compon_data.csv") streamfc = pd.read_csv(file_2, usecols=[0]) comp = pd.read_csv(file_2, usecols=[1]) fc = pd.read_csv(file_2, usecols=[2]) @@ -742,12 +1229,10 @@ def boundsofe(m): vp = pd.read_csv(file_2, usecols=[5]) for i in range(len(streamfc)): - m.fc[streamfc.to_numpy()[i, 0], comp.to_numpy()[ - i, 0]] = fc.to_numpy()[i, 0] - m.vp[streamvp.to_numpy()[i, 0], compvp.to_numpy()[ - i, 0]] = vp.to_numpy()[i, 0] + m.fc[streamfc.to_numpy()[i, 0], comp.to_numpy()[i, 0]] = fc.to_numpy()[i, 0] + m.vp[streamvp.to_numpy()[i, 0], compvp.to_numpy()[i, 0]] = vp.to_numpy()[i, 0] - file_3 = os.path.join(dir_path,'GAMS_init_data.csv') + file_3 = os.path.join(dir_path, "GAMS_init_data.csv") stream3 = pd.read_csv(file_3, usecols=[0]) a = pd.read_csv(file_3, usecols=[1]) avevlt = pd.read_csv(file_3, usecols=[3]) @@ -778,60 +1263,69 @@ def boundsofe(m): splp = pd.read_csv(file_3, usecols=[51]) splt = pd.read_csv(file_3, usecols=[53]) - for i in range(2): - m.rctp[i+1] = rctp.to_numpy()[i, 0] - m.rctt[i+1] = rctt.to_numpy()[i, 0] - m.rctvol[i+1] = rctvol.to_numpy()[i, 0] - m.sel[i+1] = sel.to_numpy()[i, 0] - m.krct[i+1] = krct.to_numpy()[i, 0] - m.consum[i+1, 'tol'] = consum.to_numpy()[i, 0] - m.conv[i+1, 'tol'] = conv.to_numpy()[i, 0] + m.rctp[i + 1] = rctp.to_numpy()[i, 0] + m.rctt[i + 1] = rctt.to_numpy()[i, 0] + m.rctvol[i + 1] = rctvol.to_numpy()[i, 0] + m.sel[i + 1] = sel.to_numpy()[i, 0] + m.krct[i + 1] = krct.to_numpy()[i, 0] + m.consum[i + 1, "tol"] = consum.to_numpy()[i, 0] + m.conv[i + 1, "tol"] = conv.to_numpy()[i, 0] m.a[stream3.to_numpy()[i, 0]] = a.to_numpy()[i, 0] - m.qc[i+1] = qc.to_numpy()[i, 0] + m.qc[i + 1] = qc.to_numpy()[i, 0] for i in range(3): - m.avevlt[i+1] = avevlt.to_numpy()[i, 0] - m.distp[i+1] = disp.to_numpy()[i, 0] - m.flshp[i+1] = flshp.to_numpy()[i, 0] - m.flsht[i+1] = flsht.to_numpy()[i, 0] - m.ndist[i+1] = ndist.to_numpy()[i, 0] - m.nmin[i+1] = nmin.to_numpy()[i, 0] - m.reflux[i+1] = reflux.to_numpy()[i, 0] - m.rmin[i+1] = rmin.to_numpy()[i, 0] - m.splp[i+1] = splp.to_numpy()[i, 0] - m.splt[i+1] = splt.to_numpy()[i, 0] + m.avevlt[i + 1] = avevlt.to_numpy()[i, 0] + m.distp[i + 1] = disp.to_numpy()[i, 0] + m.flshp[i + 1] = flshp.to_numpy()[i, 0] + m.flsht[i + 1] = flsht.to_numpy()[i, 0] + m.ndist[i + 1] = ndist.to_numpy()[i, 0] + m.nmin[i + 1] = nmin.to_numpy()[i, 0] + m.reflux[i + 1] = reflux.to_numpy()[i, 0] + m.rmin[i + 1] = rmin.to_numpy()[i, 0] + m.splp[i + 1] = splp.to_numpy()[i, 0] + m.splt[i + 1] = splt.to_numpy()[i, 0] for i in range(5): m.beta[1, comp1.to_numpy()[i, 0]] = beta.to_numpy()[i, 0] - m.mxrp[i+1] = mxrp.to_numpy()[i, 0] + m.mxrp[i + 1] = mxrp.to_numpy()[i, 0] for i in range(4): - m.qh[i+1] = qh.to_numpy()[i, 0] + m.qh[i + 1] = qh.to_numpy()[i, 0] for i in range(len(stream4)): - m.eflsh[stream4.to_numpy()[i, 0], comp2.to_numpy()[ - i, 0]] = eflsh.to_numpy()[i, 0] + m.eflsh[stream4.to_numpy()[i, 0], comp2.to_numpy()[i, 0]] = eflsh.to_numpy()[ + i, 0 + ] for i in range(6): - m.spl1p[i+1] = spl1p.to_numpy()[i, 0] - m.spl1t[i+1] = spl1t.to_numpy()[i, 0] + m.spl1p[i + 1] = spl1p.to_numpy()[i, 0] + m.spl1t[i + 1] = spl1t.to_numpy()[i, 0] # ## constraints - m.specrec = Constraint(expr=m.fc[72, 'h2'] >= 0.5 * m.f[72], doc='specification on h2 recycle') - m.specprod = Constraint(expr=m.fc[31, 'ben'] >= 0.9997 * m.f[31], doc='specification on benzene production') + m.specrec = Constraint( + expr=m.fc[72, "h2"] >= 0.5 * m.f[72], doc="specification on h2 recycle" + ) + m.specprod = Constraint( + expr=m.fc[31, "ben"] >= 0.9997 * m.f[31], + doc="specification on benzene production", + ) def Fbal(_m, stream): return m.f[stream] == sum(m.fc[stream, compon] for compon in m.compon) - m.fbal = Constraint(m.str, rule=Fbal, doc='flow balance') + + m.fbal = Constraint(m.str, rule=Fbal, doc="flow balance") def H2feed(m, compon): return m.fc[1, compon] == m.f[1] * m.f1comp[compon] - m.h2feed = Constraint(m.compon, rule=H2feed, doc='h2 feed composition') + + m.h2feed = Constraint(m.compon, rule=H2feed, doc="h2 feed composition") def Tolfeed(_m, compon): return m.fc[66, compon] == m.f[66] * m.f66comp[compon] - m.tolfeed = Constraint(m.compon, rule=Tolfeed, doc='toluene feed composition') + + m.tolfeed = Constraint(m.compon, rule=Tolfeed, doc="toluene feed composition") def Tolabs(_m, compon): return m.fc[67, compon] == m.f[67] * m.f67comp[compon] - m.tolabs = Constraint(m.compon, rule=Tolabs, doc='toluene absorber composition') - + + m.tolabs = Constraint(m.compon, rule=Tolabs, doc="toluene absorber composition") + def build_absorber(b, absorber): """ Functions relevant to the absorber block @@ -839,10 +1333,11 @@ def build_absorber(b, absorber): Parameters ---------- b : Pyomo Block - absorber block + absorber block absorber : int Index of the absorber """ + def Absfact(_m, i, compon): """ Absorption factor equation @@ -850,77 +1345,146 @@ def Absfact(_m, i, compon): """ if (i, compon) in m.anorm: - return sum(m.f[stream] * m.p[stream] for (absb, stream) in m.ilabs if absb == i) == sum(m.f[stream] for (absc, stream) in m.ivabs if absc == i) * m.aabs[compon] * sum(m.vp[stream, compon] for (absd, stream) in m.ilabs if absd == i) + return sum( + m.f[stream] * m.p[stream] for (absb, stream) in m.ilabs if absb == i + ) == sum( + m.f[stream] for (absc, stream) in m.ivabs if absc == i + ) * m.aabs[ + compon + ] * sum( + m.vp[stream, compon] for (absd, stream) in m.ilabs if absd == i + ) return Constraint.Skip - b.absfact = Constraint([absorber], m.compon, rule=Absfact, doc='absorption factor equation') + + b.absfact = Constraint( + [absorber], m.compon, rule=Absfact, doc="absorption factor equation" + ) def Gameqn(_m, i, compon): # definition of gamma if (i, compon) in m.asolv: - return m.gamma[i, compon] == log((1 - m.aabs[compon] ** (m.nabs[i] * m.abseff + m.eps1)) / (1 - m.aabs[compon])) + return m.gamma[i, compon] == log( + (1 - m.aabs[compon] ** (m.nabs[i] * m.abseff + m.eps1)) + / (1 - m.aabs[compon]) + ) return Constraint.Skip - b.gameqn = Constraint([absorber], m.compon, rule=Gameqn, doc='definition of gamma') + + b.gameqn = Constraint( + [absorber], m.compon, rule=Gameqn, doc="definition of gamma" + ) def Betaeqn(_m, i, compon): # definition of beta if (i, compon) not in m.asimp: - return m.beta[i, compon] == log((1 - m.aabs[compon] ** (m.nabs[i] * m.abseff + 1)) / (1 - m.aabs[compon])) + return m.beta[i, compon] == log( + (1 - m.aabs[compon] ** (m.nabs[i] * m.abseff + 1)) + / (1 - m.aabs[compon]) + ) return Constraint.Skip - b.betaeqn = Constraint([absorber], m.compon, - rule=Betaeqn, doc='definition of beta') + + b.betaeqn = Constraint( + [absorber], m.compon, rule=Betaeqn, doc="definition of beta" + ) def Abssvrec(_m, i, compon): # recovery of solvent if (i, compon) in m.asolv: - return sum(m.fc[stream, compon] for (i, stream) in m.ovabs) * exp(m.beta[i, compon]) == sum(m.fc[stream, compon] for (i_, stream) in m.ivabs) + exp(m.gamma[i, compon]) * sum(m.fc[stream, compon] for (i_, stream) in m.ilabs) + return sum(m.fc[stream, compon] for (i, stream) in m.ovabs) * exp( + m.beta[i, compon] + ) == sum(m.fc[stream, compon] for (i_, stream) in m.ivabs) + exp( + m.gamma[i, compon] + ) * sum( + m.fc[stream, compon] for (i_, stream) in m.ilabs + ) return Constraint.Skip - b.abssvrec = Constraint([absorber], m.compon, - rule=Abssvrec, doc='recovery of solvent') + + b.abssvrec = Constraint( + [absorber], m.compon, rule=Abssvrec, doc="recovery of solvent" + ) def Absrec(_m, i, compon): # recovery of non-solvent if (i, compon) in m.anorm: - return sum(m.fc[i, compon] for (abs, i) in m.ovabs) * exp(m.beta[i, compon]) == sum(m.fc[i, compon] for(abs, i) in m.ivabs) + return sum(m.fc[i, compon] for (abs, i) in m.ovabs) * exp( + m.beta[i, compon] + ) == sum(m.fc[i, compon] for (abs, i) in m.ivabs) return Constraint.Skip - b.absrec = Constraint([absorber], m.compon, - rule=Absrec, doc='recovery of non-solvent') + + b.absrec = Constraint( + [absorber], m.compon, rule=Absrec, doc="recovery of non-solvent" + ) def abssimp(_m, absorb, compon): # recovery of simplified components if (absorb, compon) in m.asimp: - return sum(m.fc[i, compon] for (absorb, i) in m.ovabs) == sum(m.fc[i, compon] for (absorb, i) in m.ivabs) / m.cbeta[compon] + return ( + sum(m.fc[i, compon] for (absorb, i) in m.ovabs) + == sum(m.fc[i, compon] for (absorb, i) in m.ivabs) / m.cbeta[compon] + ) return Constraint.Skip + b.abssimp = Constraint( - [absorber], m.compon, rule=abssimp, doc='recovery of simplified components') + [absorber], m.compon, rule=abssimp, doc="recovery of simplified components" + ) def Abscmb(_m, i, compon): - return sum(m.fc[stream, compon] for (i, stream) in m.ilabs) + sum(m.fc[stream, compon] for (i, stream) in m.ivabs) == sum(m.fc[stream, compon] for (i, stream) in m.olabs) + sum(m.fc[stream, compon] for (i, stream) in m.ovabs) - b.abscmb = Constraint([absorber], m.compon, rule=Abscmb, - doc='overall component mass balance in absorber') + return sum(m.fc[stream, compon] for (i, stream) in m.ilabs) + sum( + m.fc[stream, compon] for (i, stream) in m.ivabs + ) == sum(m.fc[stream, compon] for (i, stream) in m.olabs) + sum( + m.fc[stream, compon] for (i, stream) in m.ovabs + ) + + b.abscmb = Constraint( + [absorber], + m.compon, + rule=Abscmb, + doc="overall component mass balance in absorber", + ) def Abspl(_m, i): - return sum(m.p[stream] for (_, stream) in m.ilabs) == sum(m.p[stream] for (_, stream) in m.olabs) - b.abspl = Constraint([absorber], rule=Abspl, - doc='pressure relation for liquid in absorber') + return sum(m.p[stream] for (_, stream) in m.ilabs) == sum( + m.p[stream] for (_, stream) in m.olabs + ) + + b.abspl = Constraint( + [absorber], rule=Abspl, doc="pressure relation for liquid in absorber" + ) def Abstl(_m, i): - return sum(m.t[stream] for (_, stream) in m.ilabs) == sum(m.t[stream] for (_, stream) in m.olabs) - b.abstl = Constraint([absorber], rule=Abstl, - doc='temperature relation for liquid in absorber') + return sum(m.t[stream] for (_, stream) in m.ilabs) == sum( + m.t[stream] for (_, stream) in m.olabs + ) + + b.abstl = Constraint( + [absorber], rule=Abstl, doc="temperature relation for liquid in absorber" + ) def Abspv(_m, i): - return sum(m.p[stream] for (_, stream) in m.ivabs) == sum(m.p[stream] for (_, stream) in m.ovabs) - b.abspv = Constraint([absorber], rule=Abspv, - doc='pressure relation for vapor in absorber') + return sum(m.p[stream] for (_, stream) in m.ivabs) == sum( + m.p[stream] for (_, stream) in m.ovabs + ) + + b.abspv = Constraint( + [absorber], rule=Abspv, doc="pressure relation for vapor in absorber" + ) def Abspin(_m, i): - return sum(m.p[stream] for (_, stream) in m.ilabs) == sum(m.p[stream] for (_, stream) in m.ivabs) - b.absp = Constraint([absorber], rule=Abspin, doc='pressure relation at inlet of absorber') + return sum(m.p[stream] for (_, stream) in m.ilabs) == sum( + m.p[stream] for (_, stream) in m.ivabs + ) + + b.absp = Constraint( + [absorber], rule=Abspin, doc="pressure relation at inlet of absorber" + ) def Absttop(_m, i): - return sum(m.t[stream] for (_, stream) in m.ilabs) == sum(m.t[stream] for (_, stream) in m.ovabs) - b.abst = Constraint([absorber], rule=Absttop, - doc='temperature relation at top of absorber') + return sum(m.t[stream] for (_, stream) in m.ilabs) == sum( + m.t[stream] for (_, stream) in m.ovabs + ) + + b.abst = Constraint( + [absorber], rule=Absttop, doc="temperature relation at top of absorber" + ) def build_compressor(b, comp): """ @@ -933,41 +1497,72 @@ def build_compressor(b, comp): comp : int Index of the compressor """ + def Compcmb(_m, comp1, compon): if comp1 == comp: - return sum(m.fc[stream, compon] for (comp_, stream) in m.ocomp if comp_ == comp1) == sum(m.fc[stream, compon] for (comp_, stream) in m.icomp if comp_ == comp1) + return sum( + m.fc[stream, compon] + for (comp_, stream) in m.ocomp + if comp_ == comp1 + ) == sum( + m.fc[stream, compon] + for (comp_, stream) in m.icomp + if comp_ == comp1 + ) return Constraint.Skip + b.compcmb = Constraint( - [comp], m.compon, rule=Compcmb, doc='component balance in compressor') + [comp], m.compon, rule=Compcmb, doc="component balance in compressor" + ) def Comphb(_m, comp1): if comp1 == comp: - return sum(m.t[stream] for (_, stream) in m.ocomp if _ == comp) == m.presrat[comp] * sum(m.t[stream] for (_, stream) in m.icomp if _ == comp) + return sum( + m.t[stream] for (_, stream) in m.ocomp if _ == comp + ) == m.presrat[comp] * sum( + m.t[stream] for (_, stream) in m.icomp if _ == comp + ) return Constraint.Skip - b.comphb = Constraint([comp], rule=Comphb, - doc='heat balance in compressor') + + b.comphb = Constraint([comp], rule=Comphb, doc="heat balance in compressor") def Compelec(_m, comp_): if comp_ == comp: - return m.elec[comp_] == m.alpha * (m.presrat[comp_] - 1) * sum(100. * m.t[stream] * m.f[stream] / 60. * (1./m.compeff) * (m.gam / (m.gam - 1.)) for (comp1, stream) in m.icomp if comp_ == comp1) - return Constraint.Skip - b.compelec = Constraint([comp], rule=Compelec, - doc="energy balance in compressor") + return m.elec[comp_] == m.alpha * (m.presrat[comp_] - 1) * sum( + 100.0 + * m.t[stream] + * m.f[stream] + / 60.0 + * (1.0 / m.compeff) + * (m.gam / (m.gam - 1.0)) + for (comp1, stream) in m.icomp + if comp_ == comp1 + ) + return Constraint.Skip + + b.compelec = Constraint( + [comp], rule=Compelec, doc="energy balance in compressor" + ) def Ratio(_m, comp_): if comp == comp_: - return m.presrat[comp_] ** (m.gam/(m.gam-1.)) == sum(m.p[stream] for (comp1, stream) in m.ocomp if comp_ == comp1) / sum(m.p[stream] for (comp1, stream) in m.icomp if comp1 == comp_) + return m.presrat[comp_] ** (m.gam / (m.gam - 1.0)) == sum( + m.p[stream] for (comp1, stream) in m.ocomp if comp_ == comp1 + ) / sum(m.p[stream] for (comp1, stream) in m.icomp if comp1 == comp_) return Constraint.Skip - b.ratio = Constraint([comp], rule=Ratio, - doc='pressure ratio (out to in) in compressor') + b.ratio = Constraint( + [comp], rule=Ratio, doc="pressure ratio (out to in) in compressor" + ) m.vapor_pressure_unit_match = Param( - initialize=7500.6168, doc="unit match coefficient for vapor pressure calculation") - m.actual_reflux_ratio = Param( - initialize=1.2, doc="actual reflux ratio coefficient") + initialize=7500.6168, + doc="unit match coefficient for vapor pressure calculation", + ) + m.actual_reflux_ratio = Param(initialize=1.2, doc="actual reflux ratio coefficient") m.recovery_specification_coefficient = Param( - initialize=0.05, doc="recovery specification coefficient") + initialize=0.05, doc="recovery specification coefficient" + ) def build_distillation(b, dist): """ @@ -980,131 +1575,249 @@ def build_distillation(b, dist): dist : int Index of the distillation column """ + def Antdistb(_m, dist_, stream, compon): - if (dist_, stream) in m.ldist and (dist_, compon) in m.dkey and dist_ == dist: - return log(m.vp[stream, compon] * m.vapor_pressure_unit_match) == m.anta[compon] - m.antb[compon] / (m.t[stream] * 100. + m.antc[compon]) + if ( + (dist_, stream) in m.ldist + and (dist_, compon) in m.dkey + and dist_ == dist + ): + return log( + m.vp[stream, compon] * m.vapor_pressure_unit_match + ) == m.anta[compon] - m.antb[compon] / ( + m.t[stream] * 100.0 + m.antc[compon] + ) return Constraint.Skip + b.antdistb = Constraint( - [dist], m.str, m.compon, rule=Antdistb, doc='vapor pressure correlation (bot)') + [dist], + m.str, + m.compon, + rule=Antdistb, + doc="vapor pressure correlation (bot)", + ) def Antdistt(_m, dist_, stream, compon): - if (dist_, stream) in m.vdist and (dist_, compon) in m.dkey and dist == dist_: - return log(m.vp[stream, compon] * m.vapor_pressure_unit_match) == m.anta[compon] - m.antb[compon] / (m.t[stream] * 100. + m.antc[compon]) + if ( + (dist_, stream) in m.vdist + and (dist_, compon) in m.dkey + and dist == dist_ + ): + return log( + m.vp[stream, compon] * m.vapor_pressure_unit_match + ) == m.anta[compon] - m.antb[compon] / ( + m.t[stream] * 100.0 + m.antc[compon] + ) return Constraint.Skip + b.antdistt = Constraint( - [dist], m.str, m.compon, rule=Antdistt, doc='vapor pressure correlation (top)') + [dist], + m.str, + m.compon, + rule=Antdistt, + doc="vapor pressure correlation (top)", + ) def Relvol(_m, dist_): if dist == dist_: - divided1 = sum(sum(m.vp[stream, compon] for (dist_, compon) in m.dlkey if dist_ == dist)/sum(m.vp[stream, compon] - for (dist_, compon) in m.dhkey if dist_ == dist) for (dist_, stream) in m.vdist if dist_ == dist) - divided2 = sum(sum(m.vp[stream, compon] for (dist_, compon) in m.dlkey if dist_ == dist)/sum(m.vp[stream, compon] - for (dist_, compon) in m.dhkey if dist_ == dist) for (dist_, stream) in m.ldist if dist_ == dist) + divided1 = sum( + sum( + m.vp[stream, compon] + for (dist_, compon) in m.dlkey + if dist_ == dist + ) + / sum( + m.vp[stream, compon] + for (dist_, compon) in m.dhkey + if dist_ == dist + ) + for (dist_, stream) in m.vdist + if dist_ == dist + ) + divided2 = sum( + sum( + m.vp[stream, compon] + for (dist_, compon) in m.dlkey + if dist_ == dist + ) + / sum( + m.vp[stream, compon] + for (dist_, compon) in m.dhkey + if dist_ == dist + ) + for (dist_, stream) in m.ldist + if dist_ == dist + ) return m.avevlt[dist] == sqrt(divided1 * divided2) return Constraint.Skip - b.relvol = Constraint([dist], rule=Relvol, - doc='average relative volatility') + + b.relvol = Constraint([dist], rule=Relvol, doc="average relative volatility") def Undwood(_m, dist_): # minimum reflux ratio from Underwood equation if dist_ == dist: - return sum(m.fc[stream, compon] for (dist1, compon) in m.dlkey if dist1 == dist_ for (dist1, stream) in m.idist if dist1 == dist_) * m.rmin[dist_] * (m.avevlt[dist_] - 1) == sum(m.f[stream] for (dist1, stream) in m.idist if dist1 == dist_) + return sum( + m.fc[stream, compon] + for (dist1, compon) in m.dlkey + if dist1 == dist_ + for (dist1, stream) in m.idist + if dist1 == dist_ + ) * m.rmin[dist_] * (m.avevlt[dist_] - 1) == sum( + m.f[stream] for (dist1, stream) in m.idist if dist1 == dist_ + ) return Constraint.Skip - b.undwood = Constraint([dist], rule=Undwood, - doc='minimum reflux ratio equation') + + b.undwood = Constraint( + [dist], rule=Undwood, doc="minimum reflux ratio equation" + ) def Actreflux(_m, dist_): # actual reflux ratio (heuristic) if dist_ == dist: return m.reflux[dist_] == m.actual_reflux_ratio * m.rmin[dist_] return Constraint.Skip - b.actreflux = Constraint( - [dist], rule=Actreflux, doc='actual reflux ratio') + + b.actreflux = Constraint([dist], rule=Actreflux, doc="actual reflux ratio") def Fenske(_m, dist_): # minimum number of trays from Fenske equation if dist == dist_: - sum1 = sum((m.f[stream] + m.eps1)/(m.fc[stream, compon] + m.eps1) for (dist1, compon) - in m.dhkey if dist1 == dist_ for (dist1, stream) in m.vdist if dist1 == dist_) - sum2 = sum((m.f[stream] + m.eps1)/(m.fc[stream, compon] + m.eps1) for (dist1, compon) - in m.dlkey if dist1 == dist_ for (dist1, stream) in m.ldist if dist1 == dist_) + sum1 = sum( + (m.f[stream] + m.eps1) / (m.fc[stream, compon] + m.eps1) + for (dist1, compon) in m.dhkey + if dist1 == dist_ + for (dist1, stream) in m.vdist + if dist1 == dist_ + ) + sum2 = sum( + (m.f[stream] + m.eps1) / (m.fc[stream, compon] + m.eps1) + for (dist1, compon) in m.dlkey + if dist1 == dist_ + for (dist1, stream) in m.ldist + if dist1 == dist_ + ) return m.nmin[dist_] * log(m.avevlt[dist_]) == log(sum1 * sum2) return Constraint.Skip - b.fenske = Constraint([dist], rule=Fenske, - doc='minimum number of trays') + + b.fenske = Constraint([dist], rule=Fenske, doc="minimum number of trays") def Acttray(_m, dist_): # actual number of trays (gillilands approximation) if dist == dist_: - return m.ndist[dist_] == m.nmin[dist_] * 2. / m.disteff + return m.ndist[dist_] == m.nmin[dist_] * 2.0 / m.disteff return Constraint.Skip - b.acttray = Constraint([dist], rule=Acttray, - doc='actual number of trays') + + b.acttray = Constraint([dist], rule=Acttray, doc="actual number of trays") def Distspec(_m, dist_, stream, compon): - if (dist_, stream) in m.vdist and (dist_, compon) in m.dhkey and dist_ == dist: - return m.fc[stream, compon] <= m.recovery_specification_coefficient * sum(m.fc[str2, compon] for (dist_, str2) in m.idist if dist == dist_) - return Constraint.Skip - b.distspec = Constraint([dist], m.str, m.compon, - rule=Distspec, doc='recovery specification') + if ( + (dist_, stream) in m.vdist + and (dist_, compon) in m.dhkey + and dist_ == dist + ): + return m.fc[ + stream, compon + ] <= m.recovery_specification_coefficient * sum( + m.fc[str2, compon] for (dist_, str2) in m.idist if dist == dist_ + ) + return Constraint.Skip + + b.distspec = Constraint( + [dist], m.str, m.compon, rule=Distspec, doc="recovery specification" + ) def Distheav(_m, dist_, compon): if (dist_, compon) in m.dh and dist == dist_: - return sum(m.fc[str2, compon] for (dist_, str2) in m.idist if dist_ == dist) == sum(m.fc[str2, compon] for (dist_, str2) in m.ldist if dist_ == dist) + return sum( + m.fc[str2, compon] for (dist_, str2) in m.idist if dist_ == dist + ) == sum( + m.fc[str2, compon] for (dist_, str2) in m.ldist if dist_ == dist + ) return Constraint.Skip - b.distheav = Constraint( - [dist], m.compon, rule=Distheav, doc='heavy components') + + b.distheav = Constraint([dist], m.compon, rule=Distheav, doc="heavy components") def Distlite(_m, dist_, compon): if (dist_, compon) in m.dl and dist_ == dist: - return sum(m.fc[str2, compon] for (dist_, str2) in m.idist if dist == dist_) == sum(m.fc[str2, compon] for (dist_, str2) in m.vdist if dist == dist_) + return sum( + m.fc[str2, compon] for (dist_, str2) in m.idist if dist == dist_ + ) == sum( + m.fc[str2, compon] for (dist_, str2) in m.vdist if dist == dist_ + ) return Constraint.Skip - b.distlite = Constraint( - [dist], m.compon, rule=Distlite, doc='light components') + + b.distlite = Constraint([dist], m.compon, rule=Distlite, doc="light components") def Distpi(_m, dist_, stream): if (dist_, stream) in m.idist and dist_ == dist: return m.distp[dist_] <= m.p[stream] return Constraint.Skip - b.distpi = Constraint([dist], m.str, rule=Distpi, - doc='inlet pressure relation') + + b.distpi = Constraint([dist], m.str, rule=Distpi, doc="inlet pressure relation") def Distvpl(_m, dist_, stream): if (dist_, stream) in m.ldist and dist == dist_: - return m.distp[dist_] == sum(m.vp[stream, compon] for (dist_, compon) in m.dhkey if dist_ == dist) + return m.distp[dist_] == sum( + m.vp[stream, compon] for (dist_, compon) in m.dhkey if dist_ == dist + ) return Constraint.Skip - b.distvpl = Constraint([dist], m.str, rule=Distvpl, - doc='bottom vapor pressure relation') + + b.distvpl = Constraint( + [dist], m.str, rule=Distvpl, doc="bottom vapor pressure relation" + ) def Distvpv(_m, dist_, stream): if dist > 1 and (dist, stream) in m.vdist and dist_ == dist: - return m.distp[dist_] == sum(m.vp[stream, compon] for (dist_, compon) in m.dlkey if dist_ == dist) + return m.distp[dist_] == sum( + m.vp[stream, compon] for (dist_, compon) in m.dlkey if dist_ == dist + ) return Constraint.Skip - b.distvpv = Constraint([dist], m.str, rule=Distvpv, - doc='top vapor pressure relation') + + b.distvpv = Constraint( + [dist], m.str, rule=Distvpv, doc="top vapor pressure relation" + ) def Distpl(_m, dist_, stream): if (dist_, stream) in m.ldist and dist_ == dist: return m.distp[dist_] == m.p[stream] return Constraint.Skip - b.distpl = Constraint([dist], m.str, rule=Distpl, - doc='outlet pressure relation (liquid)') + + b.distpl = Constraint( + [dist], m.str, rule=Distpl, doc="outlet pressure relation (liquid)" + ) def Distpv(_m, dist_, stream): if (dist_, stream) in m.vdist and dist == dist_: return m.distp[dist_] == m.p[stream] return Constraint.Skip - b.distpv = Constraint([dist], m.str, rule=Distpv, - doc='outlet pressure relation (vapor)') + + b.distpv = Constraint( + [dist], m.str, rule=Distpv, doc="outlet pressure relation (vapor)" + ) def Distcmb(_m, dist_, compon): if dist_ == dist: - return sum(m.fc[stream, compon] for (dist1, stream) in m.idist if dist1 == dist_) == sum(m.fc[stream, compon] for (dist1, stream) in m.vdist if dist1 == dist_) + sum(m.fc[stream, compon] for (dist1, stream) in m.ldist if dist1 == dist_) + return sum( + m.fc[stream, compon] + for (dist1, stream) in m.idist + if dist1 == dist_ + ) == sum( + m.fc[stream, compon] + for (dist1, stream) in m.vdist + if dist1 == dist_ + ) + sum( + m.fc[stream, compon] + for (dist1, stream) in m.ldist + if dist1 == dist_ + ) return Constraint.Skip - b.distcmb = Constraint( - [dist], m.compon, rule=Distcmb, doc='component mass balance in distillation column') - + b.distcmb = Constraint( + [dist], + m.compon, + rule=Distcmb, + doc="component mass balance in distillation column", + ) def build_flash(b, flsh): """ @@ -1117,86 +1830,154 @@ def build_flash(b, flsh): flsh : int Index of the flash """ + def Flshcmb(_m, flsh_, compon): if flsh_ in m.flsh and compon in m.compon and flsh_ == flsh: - return sum(m.fc[stream, compon] for (flsh1, stream) in m.iflsh if flsh1 == flsh_) == sum(m.fc[stream, compon] for (flsh1, stream) in m.vflsh if flsh1 == flsh_) + sum(m.fc[stream, compon] for (flsh1, stream) in m.lflsh if flsh1 == flsh_) + return sum( + m.fc[stream, compon] + for (flsh1, stream) in m.iflsh + if flsh1 == flsh_ + ) == sum( + m.fc[stream, compon] + for (flsh1, stream) in m.vflsh + if flsh1 == flsh_ + ) + sum( + m.fc[stream, compon] + for (flsh1, stream) in m.lflsh + if flsh1 == flsh_ + ) return Constraint.Skip + b.flshcmb = Constraint( - [flsh], m.compon, rule=Flshcmb, doc='component mass balance in flash') + [flsh], m.compon, rule=Flshcmb, doc="component mass balance in flash" + ) def Antflsh(_m, flsh_, stream, compon): if (flsh_, stream) in m.lflsh and flsh_ == flsh: - return log(m.vp[stream, compon] * m.vapor_pressure_unit_match) == m.anta[compon] - m.antb[compon] / (m.t[stream] * 100. + m.antc[compon]) + return log( + m.vp[stream, compon] * m.vapor_pressure_unit_match + ) == m.anta[compon] - m.antb[compon] / ( + m.t[stream] * 100.0 + m.antc[compon] + ) return Constraint.Skip - b.antflsh = Constraint([flsh], m.str, m.compon, - rule=Antflsh, doc='flash pressure relation') + + b.antflsh = Constraint( + [flsh], m.str, m.compon, rule=Antflsh, doc="flash pressure relation" + ) def Flshrec(_m, flsh_, stream, compon): if (flsh_, stream) in m.lflsh and flsh_ == flsh: - return sum(m.eflsh[flsh1, compon2] for (flsh1, compon2) in m.fkey if flsh1 == flsh_) * (m.eflsh[flsh_, compon] * sum(m.vp[stream, compon2] for (flsh1, compon2) in m.fkey if flsh_ == flsh1) + (1. - m.eflsh[flsh_, compon]) * m.vp[stream, compon]) == sum(m.vp[stream, compon2] for (flsh1, compon2) in m.fkey if flsh_ == flsh1) * m.eflsh[flsh_, compon] + return ( + sum( + m.eflsh[flsh1, compon2] + for (flsh1, compon2) in m.fkey + if flsh1 == flsh_ + ) + * ( + m.eflsh[flsh_, compon] + * sum( + m.vp[stream, compon2] + for (flsh1, compon2) in m.fkey + if flsh_ == flsh1 + ) + + (1.0 - m.eflsh[flsh_, compon]) * m.vp[stream, compon] + ) + == sum( + m.vp[stream, compon2] + for (flsh1, compon2) in m.fkey + if flsh_ == flsh1 + ) + * m.eflsh[flsh_, compon] + ) return Constraint.Skip - b.flshrec = Constraint([flsh], m.str, m.compon, - rule=Flshrec, doc='vapor recovery relation') + + b.flshrec = Constraint( + [flsh], m.str, m.compon, rule=Flshrec, doc="vapor recovery relation" + ) def Flsheql(_m, flsh_, compon): if flsh in m.flsh and compon in m.compon and flsh_ == flsh: - return sum(m.fc[stream, compon] for (flsh1, stream) in m.vflsh if flsh1 == flsh_) == sum(m.fc[stream, compon] for (flsh1, stream) in m.iflsh if flsh1 == flsh_) * m.eflsh[flsh, compon] + return ( + sum( + m.fc[stream, compon] + for (flsh1, stream) in m.vflsh + if flsh1 == flsh_ + ) + == sum( + m.fc[stream, compon] + for (flsh1, stream) in m.iflsh + if flsh1 == flsh_ + ) + * m.eflsh[flsh, compon] + ) return Constraint.Skip + b.flsheql = Constraint( - [flsh], m.compon, rule=Flsheql, doc='equilibrium relation') + [flsh], m.compon, rule=Flsheql, doc="equilibrium relation" + ) def Flshpr(_m, flsh_, stream): if (flsh_, stream) in m.lflsh and flsh_ == flsh: - return m.flshp[flsh_] * m.f[stream] == sum(m.vp[stream, compon] * m.fc[stream, compon] for compon in m.compon) + return m.flshp[flsh_] * m.f[stream] == sum( + m.vp[stream, compon] * m.fc[stream, compon] for compon in m.compon + ) return Constraint.Skip - b.flshpr = Constraint([flsh], m.str, rule=Flshpr, - doc='flash pressure relation') + + b.flshpr = Constraint([flsh], m.str, rule=Flshpr, doc="flash pressure relation") def Flshpi(_m, flsh_, stream): if (flsh_, stream) in m.iflsh and flsh_ == flsh: return m.flshp[flsh_] == m.p[stream] return Constraint.Skip - b.flshpi = Constraint([flsh], m.str, rule=Flshpi, - doc='inlet pressure relation') + + b.flshpi = Constraint([flsh], m.str, rule=Flshpi, doc="inlet pressure relation") def Flshpl(_m, flsh_, stream): if (flsh_, stream) in m.lflsh and flsh_ == flsh: return m.flshp[flsh_] == m.p[stream] return Constraint.Skip - b.flshpl = Constraint([flsh], m.str, rule=Flshpl, - doc='outlet pressure relation (liquid)') + + b.flshpl = Constraint( + [flsh], m.str, rule=Flshpl, doc="outlet pressure relation (liquid)" + ) def Flshpv(_m, flsh_, stream): if (flsh_, stream) in m.vflsh and flsh_ == flsh: return m.flshp[flsh_] == m.p[stream] return Constraint.Skip - b.flshpv = Constraint([flsh], m.str, rule=Flshpv, - doc='outlet pressure relation (vapor)') + + b.flshpv = Constraint( + [flsh], m.str, rule=Flshpv, doc="outlet pressure relation (vapor)" + ) def Flshti(_m, flsh_, stream): if (flsh_, stream) in m.iflsh and flsh_ == flsh: return m.flsht[flsh_] == m.t[stream] return Constraint.Skip - b.flshti = Constraint([flsh], m.str, rule=Flshti, - doc='inlet temp. relation') + + b.flshti = Constraint([flsh], m.str, rule=Flshti, doc="inlet temp. relation") def Flshtl(_m, flsh_, stream): if (flsh_, stream) in m.lflsh and flsh_ == flsh: return m.flsht[flsh_] == m.t[stream] return Constraint.Skip - b.flshtl = Constraint([flsh], m.str, rule=Flshtl, - doc='outlet temp. relation (liquid)') + + b.flshtl = Constraint( + [flsh], m.str, rule=Flshtl, doc="outlet temp. relation (liquid)" + ) def Flshtv(_m, flsh_, stream): if (flsh_, stream) in m.vflsh and flsh_ == flsh: return m.flsht[flsh_] == m.t[stream] return Constraint.Skip - b.flshtv = Constraint([flsh], m.str, rule=Flshtv, - doc='outlet temp. relation (vapor)') - + b.flshtv = Constraint( + [flsh], m.str, rule=Flshtv, doc="outlet temp. relation (vapor)" + ) + m.heat_unit_match = Param( - initialize=3600. * 8500. * 1.0e-12 / 60., doc="unit change on temp") + initialize=3600.0 * 8500.0 * 1.0e-12 / 60.0, doc="unit change on temp" + ) def build_furnace(b, furnace): """ @@ -1209,26 +1990,47 @@ def build_furnace(b, furnace): furnace : int Index of the furnace """ + def Furnhb(_m, furn): if furn == furnace: - return m.qfuel[furn] == (sum(m.cp[stream] * m.f[stream] * 100. * m.t[stream] for (furn, stream) in m.ofurn) - sum(m.cp[stream] * m.f[stream] * 100. * m.t[stream] for (furn, stream) in m.ifurn)) * m.heat_unit_match + return ( + m.qfuel[furn] + == ( + sum( + m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] + for (furn, stream) in m.ofurn + ) + - sum( + m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] + for (furn, stream) in m.ifurn + ) + ) + * m.heat_unit_match + ) return Constraint.Skip - b.furnhb = Constraint([furnace], rule=Furnhb, doc='heat balance in furnace') + + b.furnhb = Constraint([furnace], rule=Furnhb, doc="heat balance in furnace") def Furncmb(_m, furn, compon): if furn == furnace: - return sum(m.fc[stream, compon] for (furn, stream) in m.ofurn) == sum(m.fc[stream, compon] for (furn, stream) in m.ifurn) + return sum(m.fc[stream, compon] for (furn, stream) in m.ofurn) == sum( + m.fc[stream, compon] for (furn, stream) in m.ifurn + ) return Constraint.Skip - b.furncmb = Constraint([furnace], m.compon, - rule=Furncmb, doc='component mass balance in furnace') + + b.furncmb = Constraint( + [furnace], m.compon, rule=Furncmb, doc="component mass balance in furnace" + ) def Furnp(_m, furn): if furn == furnace: - return sum(m.p[stream] for (furn, stream) in m.ofurn) == sum(m.p[stream] for (furn, stream) in m.ifurn) - m.furnpdrop + return ( + sum(m.p[stream] for (furn, stream) in m.ofurn) + == sum(m.p[stream] for (furn, stream) in m.ifurn) - m.furnpdrop + ) return Constraint.Skip - b.furnp = Constraint([furnace], rule=Furnp, doc='pressure relation in furnace') - + b.furnp = Constraint([furnace], rule=Furnp, doc="pressure relation in furnace") def build_cooler(b, cooler): """ @@ -1241,21 +2043,44 @@ def build_cooler(b, cooler): cooler : int Index of the cooler """ + def Heccmb(_m, hec, compon): - return sum(m.fc[stream, compon] for (hec_, stream) in m.ohec if hec_ == hec) == sum(m.fc[stream, compon] for (hec_, stream) in m.ihec if hec_ == hec) - b.heccmb = Constraint([cooler], m.compon, - rule=Heccmb, doc='heat balance in cooler') + return sum( + m.fc[stream, compon] for (hec_, stream) in m.ohec if hec_ == hec + ) == sum(m.fc[stream, compon] for (hec_, stream) in m.ihec if hec_ == hec) + + b.heccmb = Constraint( + [cooler], m.compon, rule=Heccmb, doc="heat balance in cooler" + ) def Hechb(_m, hec): - return m.qc[hec] == (sum(m.cp[stream] * m.f[stream] * 100. * m.t[stream] for (hec_, stream) in m.ihec if hec_ == hec) - sum(m.cp[stream] * m.f[stream] * 100. * m.t[stream] for (hec_, stream) in m.ohec if hec_ == hec)) * m.heat_unit_match - b.hechb = Constraint([cooler], rule=Hechb, - doc='component mass balance in cooler') + return ( + m.qc[hec] + == ( + sum( + m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] + for (hec_, stream) in m.ihec + if hec_ == hec + ) + - sum( + m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] + for (hec_, stream) in m.ohec + if hec_ == hec + ) + ) + * m.heat_unit_match + ) + + b.hechb = Constraint( + [cooler], rule=Hechb, doc="component mass balance in cooler" + ) def Hecp(_m, hec): - return sum(m.p[stream] for(hec_, stream) in m.ihec if hec_ == hec) == sum(m.p[stream] for(hec_, stream) in m.ohec if hec_ == hec) - b.hecp = Constraint([cooler], rule=Hecp, doc='pressure relation in cooler') + return sum(m.p[stream] for (hec_, stream) in m.ihec if hec_ == hec) == sum( + m.p[stream] for (hec_, stream) in m.ohec if hec_ == hec + ) - + b.hecp = Constraint([cooler], rule=Hecp, doc="pressure relation in cooler") def build_heater(b, heater): """ @@ -1268,29 +2093,58 @@ def build_heater(b, heater): heater : int Index of the heater """ + def Hehcmb(_m, heh, compon): if heh == heater and compon in m.compon: - return sum(m.fc[stream, compon] for (heh_, stream) in m.oheh if heh_ == heh) == sum(m.fc[stream, compon] for (heh_, stream) in m.iheh if heh == heh_) + return sum( + m.fc[stream, compon] for (heh_, stream) in m.oheh if heh_ == heh + ) == sum( + m.fc[stream, compon] for (heh_, stream) in m.iheh if heh == heh_ + ) return Constraint.Skip - b.hehcmb = Constraint(Set( - initialize=[heater]), m.compon, rule=Hehcmb, doc='component balance in heater') + + b.hehcmb = Constraint( + Set(initialize=[heater]), + m.compon, + rule=Hehcmb, + doc="component balance in heater", + ) def Hehhb(_m, heh): if heh == heater: - return m.qh[heh] == (sum(m.cp[stream] * m.f[stream] * 100. * m.t[stream] for (heh_, stream) in m.oheh if heh_ == heh) - - sum(m.cp[stream] * m.f[stream] * 100. * m.t[stream] for (heh_, stream) in m.iheh if heh_ == heh)) * m.heat_unit_match + return ( + m.qh[heh] + == ( + sum( + m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] + for (heh_, stream) in m.oheh + if heh_ == heh + ) + - sum( + m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] + for (heh_, stream) in m.iheh + if heh_ == heh + ) + ) + * m.heat_unit_match + ) return Constraint.Skip + b.hehhb = Constraint( - Set(initialize=[heater]), rule=Hehhb, doc='heat balance in heater') + Set(initialize=[heater]), rule=Hehhb, doc="heat balance in heater" + ) def hehp(_m, heh): if heh == heater: - return sum(m.p[stream] for(heh_, stream) in m.iheh if heh_ == heh) == sum(m.p[stream] for(heh_, stream) in m.oheh if heh == heh_) + return sum( + m.p[stream] for (heh_, stream) in m.iheh if heh_ == heh + ) == sum(m.p[stream] for (heh_, stream) in m.oheh if heh == heh_) return Constraint.Skip + b.Hehp = Constraint( - Set(initialize=[heater]), rule=hehp, doc='no pressure drop thru heater') + Set(initialize=[heater]), rule=hehp, doc="no pressure drop thru heater" + ) - m.exchanger_temp_drop = Param(initialize=0.25) def build_exchanger(b, exchanger): @@ -1304,53 +2158,119 @@ def build_exchanger(b, exchanger): exchanger : int Index of the exchanger """ + def Exchcmbc(_m, exch, compon): if exch in m.exch and compon in m.compon: - return sum(m.fc[stream, compon] for (exch_, stream) in m.ocexch if exch == exch_) == sum(m.fc[stream, compon] for (exch_, stream) in m.icexch if exch == exch_) - return Constraint.Skip - b.exchcmbc = Constraint([exchanger], m.compon, - rule=Exchcmbc, doc='component balance (cold) in exchanger') + return sum( + m.fc[stream, compon] + for (exch_, stream) in m.ocexch + if exch == exch_ + ) == sum( + m.fc[stream, compon] + for (exch_, stream) in m.icexch + if exch == exch_ + ) + return Constraint.Skip + + b.exchcmbc = Constraint( + [exchanger], + m.compon, + rule=Exchcmbc, + doc="component balance (cold) in exchanger", + ) def Exchcmbh(_m, exch, compon): if exch in m.exch and compon in m.compon: - return sum(m.fc[stream, compon] for (exch_, stream) in m.ohexch if exch == exch_) == sum(m.fc[stream, compon] for (exch_, stream) in m.ihexch if exch == exch_) - return Constraint.Skip - b.exchcmbh = Constraint([exchanger], m.compon, - rule=Exchcmbh, doc='component balance (hot) in exchanger') + return sum( + m.fc[stream, compon] + for (exch_, stream) in m.ohexch + if exch == exch_ + ) == sum( + m.fc[stream, compon] + for (exch_, stream) in m.ihexch + if exch == exch_ + ) + return Constraint.Skip + + b.exchcmbh = Constraint( + [exchanger], + m.compon, + rule=Exchcmbh, + doc="component balance (hot) in exchanger", + ) def Exchhbc(_m, exch): if exch in m.exch: - return (sum(m.cp[stream] * m.f[stream] * 100. * m.t[stream] for (exch_, stream) in m.ocexch if exch == exch_) - sum(m.cp[stream] * m.f[stream] * 100. * m.t[stream] for (exch_, stream) in m.icexch if exch == exch_)) * m.heat_unit_match == m.qexch[exch] + return ( + sum( + m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] + for (exch_, stream) in m.ocexch + if exch == exch_ + ) + - sum( + m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] + for (exch_, stream) in m.icexch + if exch == exch_ + ) + ) * m.heat_unit_match == m.qexch[exch] return Constraint.Skip - b.exchhbc = Constraint([exchanger], rule=Exchhbc, - doc='heat balance for cold stream in exchanger') + + b.exchhbc = Constraint( + [exchanger], rule=Exchhbc, doc="heat balance for cold stream in exchanger" + ) def Exchhbh(_m, exch): - return (sum(m.cp[stream] * m.f[stream] * 100. * m.t[stream] for (exch, stream) in m.ihexch) - sum(m.cp[stream] * m.f[stream] * 100. * m.t[stream] for (exch, stream) in m.ohexch)) * m.heat_unit_match == m.qexch[exch] - b.exchhbh = Constraint([exchanger], rule=Exchhbh, - doc='heat balance for hot stream in exchanger') + return ( + sum( + m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] + for (exch, stream) in m.ihexch + ) + - sum( + m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] + for (exch, stream) in m.ohexch + ) + ) * m.heat_unit_match == m.qexch[exch] + + b.exchhbh = Constraint( + [exchanger], rule=Exchhbh, doc="heat balance for hot stream in exchanger" + ) def Exchdtm1(_m, exch): - return sum(m.t[stream] for (exch, stream) in m.ohexch) >= sum(m.t[stream] for (exch, stream) in m.icexch) + m.exchanger_temp_drop - b.exchdtm1 = Constraint( - [exchanger], rule=Exchdtm1, doc='delta t min condition') + return ( + sum(m.t[stream] for (exch, stream) in m.ohexch) + >= sum(m.t[stream] for (exch, stream) in m.icexch) + + m.exchanger_temp_drop + ) + + b.exchdtm1 = Constraint([exchanger], rule=Exchdtm1, doc="delta t min condition") def Exchdtm2(_m, exch): - return sum(m.t[stream] for (exch, stream) in m.ocexch) <= sum(m.t[stream] for (exch, stream) in m.ihexch) - m.exchanger_temp_drop - b.exchdtm2 = Constraint( - [exchanger], rule=Exchdtm2, doc='delta t min condition') + return ( + sum(m.t[stream] for (exch, stream) in m.ocexch) + <= sum(m.t[stream] for (exch, stream) in m.ihexch) + - m.exchanger_temp_drop + ) + + b.exchdtm2 = Constraint([exchanger], rule=Exchdtm2, doc="delta t min condition") def Exchpc(_m, exch): - return sum(m.p[stream] for (exch, stream) in m.ocexch) == sum(m.p[stream] for (exch, stream) in m.icexch) - b.exchpc = Constraint([exchanger], rule=Exchpc, - doc='pressure relation (cold) in exchanger') + return sum(m.p[stream] for (exch, stream) in m.ocexch) == sum( + m.p[stream] for (exch, stream) in m.icexch + ) + + b.exchpc = Constraint( + [exchanger], rule=Exchpc, doc="pressure relation (cold) in exchanger" + ) def Exchph(_m, exch): - return sum(m.p[stream] for (exch, stream) in m.ohexch) == sum(m.p[stream] for (exch, stream) in m.ihexch) - b.exchph = Constraint([exchanger], rule=Exchph, - doc='pressure relation (hot) in exchanger') + return sum(m.p[stream] for (exch, stream) in m.ohexch) == sum( + m.p[stream] for (exch, stream) in m.ihexch + ) + + b.exchph = Constraint( + [exchanger], rule=Exchph, doc="pressure relation (hot) in exchanger" + ) - m.membrane_recovery_sepc = Param(initialize=0.50) m.membrane_purity_sepc = Param(initialize=0.50) @@ -1365,68 +2285,135 @@ def build_membrane(b, membrane): membrane : int Index of the membrane """ + def Memcmb(_m, memb, stream, compon): if (memb, stream) in m.imemb and memb == membrane: - return m.fc[stream, compon] == sum(m.fc[stream, compon] for (memb_, stream) in m.pmemb if memb == memb_) + sum(m.fc[stream, compon] for (memb_, stream) in m.nmemb if memb == memb_) + return m.fc[stream, compon] == sum( + m.fc[stream, compon] for (memb_, stream) in m.pmemb if memb == memb_ + ) + sum( + m.fc[stream, compon] for (memb_, stream) in m.nmemb if memb == memb_ + ) return Constraint.Skip - b.memcmb = Constraint([membrane], m.str, m.compon, - rule=Memcmb, doc='component mass balance in membrane separator') + + b.memcmb = Constraint( + [membrane], + m.str, + m.compon, + rule=Memcmb, + doc="component mass balance in membrane separator", + ) def Flux(_m, memb, stream, compon): - if (memb, stream) in m.pmemb and (memb, compon) in m.mnorm and memb == membrane: - return m.fc[stream, compon] == m.a[memb] * m.perm[compon] / 2.0 * (sum(m.p[stream2] for (memb_, stream2) in m.imemb if memb_ == memb) * (sum((m.fc[stream2, compon] + m.eps1)/(m.f[stream2] + m.eps1) for (memb_, stream2) in m.imemb if memb_ == memb) + sum((m.fc[stream2, compon] + m.eps1)/(m.f[stream2] + m.eps1) for (memb_, stream2) in m.nmemb if memb_ == memb)) - 2.0 * m.p[stream] * (m.fc[stream, compon] + m.eps1) / (m.f[stream] + m.eps1)) + if ( + (memb, stream) in m.pmemb + and (memb, compon) in m.mnorm + and memb == membrane + ): + return m.fc[stream, compon] == m.a[memb] * m.perm[compon] / 2.0 * ( + sum(m.p[stream2] for (memb_, stream2) in m.imemb if memb_ == memb) + * ( + sum( + (m.fc[stream2, compon] + m.eps1) / (m.f[stream2] + m.eps1) + for (memb_, stream2) in m.imemb + if memb_ == memb + ) + + sum( + (m.fc[stream2, compon] + m.eps1) / (m.f[stream2] + m.eps1) + for (memb_, stream2) in m.nmemb + if memb_ == memb + ) + ) + - 2.0 + * m.p[stream] + * (m.fc[stream, compon] + m.eps1) + / (m.f[stream] + m.eps1) + ) return Constraint.Skip - b.flux = Constraint([membrane], m.str, m.compon, - rule=Flux, doc='mass flux relation in membrane separator') + + b.flux = Constraint( + [membrane], + m.str, + m.compon, + rule=Flux, + doc="mass flux relation in membrane separator", + ) def Simp(_m, memb, stream, compon): - if (memb, stream) in m.pmemb and (memb, compon) in m.msimp and memb == membrane: + if ( + (memb, stream) in m.pmemb + and (memb, compon) in m.msimp + and memb == membrane + ): return m.fc[stream, compon] == 0.0 return Constraint.Skip - b.simp = Constraint([membrane], m.str, m.compon, - rule=Simp, doc='mass flux relation (simplified) in membrane separator') + + b.simp = Constraint( + [membrane], + m.str, + m.compon, + rule=Simp, + doc="mass flux relation (simplified) in membrane separator", + ) def Memtp(_m, memb, stream): if (memb, stream) in m.pmemb and memb == membrane: - return m.t[stream] == sum(m.t[stream2] for (memb, stream2) in m.imemb if memb == membrane) + return m.t[stream] == sum( + m.t[stream2] for (memb, stream2) in m.imemb if memb == membrane + ) return Constraint.Skip - b.memtp = Constraint([membrane], m.str, rule=Memtp, - doc='temp relation for permeate') + + b.memtp = Constraint( + [membrane], m.str, rule=Memtp, doc="temp relation for permeate" + ) def Mempp(_m, memb, stream): if (memb, stream) in m.pmemb and memb == membrane: - return m.p[stream] <= sum(m.p[stream2] for (memb, stream2) in m.imemb if memb == membrane) + return m.p[stream] <= sum( + m.p[stream2] for (memb, stream2) in m.imemb if memb == membrane + ) return Constraint.Skip - b.mempp = Constraint([membrane], m.str, rule=Mempp, - doc='pressure relation for permeate') + + b.mempp = Constraint( + [membrane], m.str, rule=Mempp, doc="pressure relation for permeate" + ) def Memtn(_m, memb, stream): if (memb, stream) in m.nmemb and memb == membrane: - return m.t[stream] == sum(m.t[stream2] for (memb, stream2) in m.imemb if memb == membrane) + return m.t[stream] == sum( + m.t[stream2] for (memb, stream2) in m.imemb if memb == membrane + ) return Constraint.Skip - b.Memtn = Constraint([membrane], m.str, rule=Memtn, - doc='temp relation for non-permeate') + + b.Memtn = Constraint( + [membrane], m.str, rule=Memtn, doc="temp relation for non-permeate" + ) def Mempn(_m, memb, stream): if (memb, stream) in m.nmemb and memb == membrane: - return m.p[stream] == sum(m.p[stream] for (memb_, stream) in m.imemb if memb_ == memb) + return m.p[stream] == sum( + m.p[stream] for (memb_, stream) in m.imemb if memb_ == memb + ) return Constraint.Skip - b.Mempn = Constraint([membrane], m.str, rule=Mempn, - doc='pressure relation for non-permeate') + + b.Mempn = Constraint( + [membrane], m.str, rule=Mempn, doc="pressure relation for non-permeate" + ) def Rec(_m, memb_, stream): if (memb_, stream) in m.pmemb and memb_ == membrane: - return m.fc[stream, 'h2'] >= m.membrane_recovery_sepc * sum(m.fc[stream, 'h2'] for (memb, stream) in m.imemb if memb == memb_) + return m.fc[stream, "h2"] >= m.membrane_recovery_sepc * sum( + m.fc[stream, "h2"] for (memb, stream) in m.imemb if memb == memb_ + ) return Constraint.Skip - b.rec = Constraint([membrane], m.str, rule=Rec, doc='recovery spec') + + b.rec = Constraint([membrane], m.str, rule=Rec, doc="recovery spec") def Pure(_m, memb, stream): if (memb, stream) in m.pmemb and memb == membrane: - return m.fc[stream, 'h2'] >= m.membrane_purity_sepc * m.f[stream] + return m.fc[stream, "h2"] >= m.membrane_purity_sepc * m.f[stream] return Constraint.Skip - b.pure = Constraint([membrane], m.str, rule=Pure, doc='purity spec') - + b.pure = Constraint([membrane], m.str, rule=Pure, doc="purity spec") def build_multiple_mixer(b, multiple_mxr): """ @@ -1439,42 +2426,61 @@ def build_multiple_mixer(b, multiple_mxr): multiple_mxr : int Index of the mixer """ + def Mxrcmb(_b, mxr, compon): if mxr == multiple_mxr: - return sum(m.fc[stream, compon] for (mxr_, stream) in m.omxr if mxr == mxr_) == sum(m.fc[stream, compon] for (mxr_, stream) in m.imxr if mxr == mxr_) + return sum( + m.fc[stream, compon] for (mxr_, stream) in m.omxr if mxr == mxr_ + ) == sum( + m.fc[stream, compon] for (mxr_, stream) in m.imxr if mxr == mxr_ + ) return Constraint.Skip - b.mxrcmb = Constraint([multiple_mxr], m.compon, - rule=Mxrcmb, doc='component balance in mixer') + + b.mxrcmb = Constraint( + [multiple_mxr], m.compon, rule=Mxrcmb, doc="component balance in mixer" + ) def Mxrhb(_b, mxr): if mxr == multiple_mxr and mxr != 2: - return sum(m.f[stream] * m.t[stream] * m.cp[stream] for (mxr_, stream) in m.imxr if mxr == mxr_) == sum(m.f[stream] * m.t[stream] * m.cp[stream] for (mxr_, stream) in m.omxr if mxr == mxr_) + return sum( + m.f[stream] * m.t[stream] * m.cp[stream] + for (mxr_, stream) in m.imxr + if mxr == mxr_ + ) == sum( + m.f[stream] * m.t[stream] * m.cp[stream] + for (mxr_, stream) in m.omxr + if mxr == mxr_ + ) return Constraint.Skip - b.mxrhb = Constraint([multiple_mxr], rule=Mxrhb, - doc="heat balance in mixer") + + b.mxrhb = Constraint([multiple_mxr], rule=Mxrhb, doc="heat balance in mixer") def Mxrhbq(_b, mxr): if mxr == 2 and mxr == multiple_mxr: - return m.f[16] * m.t[16] == m.f[15] * m.t[15] - (m.fc[20, 'ben'] + m.fc[20, 'tol']) * m.heatvap['tol'] / (100. * m.cp[15]) + return m.f[16] * m.t[16] == m.f[15] * m.t[15] - ( + m.fc[20, "ben"] + m.fc[20, "tol"] + ) * m.heatvap["tol"] / (100.0 * m.cp[15]) return Constraint.Skip - b.mxrhbq = Constraint([multiple_mxr], rule=Mxrhbq, - doc='heat balance in quench') + + b.mxrhbq = Constraint([multiple_mxr], rule=Mxrhbq, doc="heat balance in quench") def Mxrpi(_b, mxr, stream): if (mxr, stream) in m.imxr and mxr == multiple_mxr: return m.mxrp[mxr] == m.p[stream] return Constraint.Skip - b.mxrpi = Constraint([multiple_mxr], m.str, - rule=Mxrpi, doc='inlet pressure relation') + + b.mxrpi = Constraint( + [multiple_mxr], m.str, rule=Mxrpi, doc="inlet pressure relation" + ) def Mxrpo(_b, mxr, stream): if (mxr, stream) in m.omxr and mxr == multiple_mxr: return m.mxrp[mxr] == m.p[stream] return Constraint.Skip - b.mxrpo = Constraint([multiple_mxr], m.str, - rule=Mxrpo, doc='outlet pressure relation') - + b.mxrpo = Constraint( + [multiple_mxr], m.str, rule=Mxrpo, doc="outlet pressure relation" + ) def build_pump(b, pump_): """ @@ -1487,26 +2493,37 @@ def build_pump(b, pump_): pump_ : int Index of the pump """ + def Pumpcmb(_m, pump, compon): if pump == pump_ and compon in m.compon: - return sum(m.fc[stream, compon] for (pump_, stream) in m.opump if pump == pump_) == sum(m.fc[stream, compon] for (pump_, stream) in m.ipump if pump_ == pump) + return sum( + m.fc[stream, compon] for (pump_, stream) in m.opump if pump == pump_ + ) == sum( + m.fc[stream, compon] for (pump_, stream) in m.ipump if pump_ == pump + ) return Constraint.Skip + b.pumpcmb = Constraint( - [pump_], m.compon, rule=Pumpcmb, doc='component balance in pump') + [pump_], m.compon, rule=Pumpcmb, doc="component balance in pump" + ) def Pumphb(_m, pump): if pump == pump_: - return sum(m.t[stream] for (pump_, stream) in m.opump if pump == pump_) == sum(m.t[stream] for (pump_, stream) in m.ipump if pump == pump_) + return sum( + m.t[stream] for (pump_, stream) in m.opump if pump == pump_ + ) == sum(m.t[stream] for (pump_, stream) in m.ipump if pump == pump_) return Constraint.Skip - b.pumphb = Constraint([pump_], rule=Pumphb, doc='heat balance in pump') + + b.pumphb = Constraint([pump_], rule=Pumphb, doc="heat balance in pump") def Pumppr(_m, pump): if pump == pump_: - return sum(m.p[stream] for (pump_, stream) in m.opump if pump == pump_) >= sum(m.p[stream] for (pump_, stream) in m.ipump if pump == pump_) + return sum( + m.p[stream] for (pump_, stream) in m.opump if pump == pump_ + ) >= sum(m.p[stream] for (pump_, stream) in m.ipump if pump == pump_) return Constraint.Skip - b.pumppr = Constraint([pump_], rule=Pumppr, doc='pressure relation in pump') - + b.pumppr = Constraint([pump_], rule=Pumppr, doc="pressure relation in pump") def build_multiple_splitter(b, multi_splitter): """ @@ -1519,49 +2536,82 @@ def build_multiple_splitter(b, multi_splitter): multi_splitter : int Index of the splitter """ + def Splcmb(_m, spl, stream, compon): if (spl, stream) in m.ospl and spl == multi_splitter: - return m.fc[stream, compon] == sum(m.e[stream]*m.fc[str2, compon] for (spl_, str2) in m.ispl if spl == spl_) + return m.fc[stream, compon] == sum( + m.e[stream] * m.fc[str2, compon] + for (spl_, str2) in m.ispl + if spl == spl_ + ) return Constraint.Skip - b.splcmb = Constraint([multi_splitter], m.str, m.compon, - rule=Splcmb, doc='component balance in splitter') + + b.splcmb = Constraint( + [multi_splitter], + m.str, + m.compon, + rule=Splcmb, + doc="component balance in splitter", + ) def Esum(_m, spl): if spl in m.spl and spl == multi_splitter: - return sum(m.e[stream] for (spl_, stream) in m.ospl if spl_ == spl) == 1.0 + return ( + sum(m.e[stream] for (spl_, stream) in m.ospl if spl_ == spl) == 1.0 + ) return Constraint.Skip - b.esum = Constraint([multi_splitter], rule=Esum, - doc='split fraction relation in splitter') + + b.esum = Constraint( + [multi_splitter], rule=Esum, doc="split fraction relation in splitter" + ) def Splpi(_m, spl, stream): if (spl, stream) in m.ispl and spl == multi_splitter: return m.splp[spl] == m.p[stream] return Constraint.Skip - b.splpi = Constraint([multi_splitter], m.str, - rule=Splpi, doc='inlet pressure relation (splitter)') + + b.splpi = Constraint( + [multi_splitter], + m.str, + rule=Splpi, + doc="inlet pressure relation (splitter)", + ) def Splpo(_m, spl, stream): if (spl, stream) in m.ospl and spl == multi_splitter: return m.splp[spl] == m.p[stream] return Constraint.Skip - b.splpo = Constraint([multi_splitter], m.str, - rule=Splpo, doc='outlet pressure relation (splitter)') + + b.splpo = Constraint( + [multi_splitter], + m.str, + rule=Splpo, + doc="outlet pressure relation (splitter)", + ) def Splti(_m, spl, stream): if (spl, stream) in m.ispl and spl == multi_splitter: return m.splt[spl] == m.t[stream] return Constraint.Skip - b.splti = Constraint([multi_splitter], m.str, - rule=Splti, doc='inlet temperature relation (splitter)') + + b.splti = Constraint( + [multi_splitter], + m.str, + rule=Splti, + doc="inlet temperature relation (splitter)", + ) def Splto(_m, spl, stream): if (spl, stream) in m.ospl and spl == multi_splitter: return m.splt[spl] == m.t[stream] return Constraint.Skip - b.splto = Constraint([multi_splitter], m.str, - rule=Splto, doc='outlet temperature relation (splitter)') - + b.splto = Constraint( + [multi_splitter], + m.str, + rule=Splto, + doc="outlet temperature relation (splitter)", + ) def build_valve(b, valve_): """ @@ -1574,22 +2624,44 @@ def build_valve(b, valve_): valve_ : int Index of the valve """ + def Valcmb(_m, valve, compon): - return sum(m.fc[stream, compon] for (valve_, stream) in m.oval if valve == valve_) == sum(m.fc[stream, compon] for (valve_, stream) in m.ival if valve == valve_) - b.valcmb = Constraint([valve_], m.compon, rule=Valcmb, doc='component balance in valve') + return sum( + m.fc[stream, compon] for (valve_, stream) in m.oval if valve == valve_ + ) == sum( + m.fc[stream, compon] for (valve_, stream) in m.ival if valve == valve_ + ) + + b.valcmb = Constraint( + [valve_], m.compon, rule=Valcmb, doc="component balance in valve" + ) def Valt(_m, valve): - return sum(m.t[stream] / (m.p[stream] ** ((m.gam - 1.) / m.gam)) for (valv, stream) in m.oval if valv == valve) == sum(m.t[stream] / (m.p[stream] ** ((m.gam - 1.) / m.gam)) for (valv, stream) in m.ival if valv == valve) - b.valt = Constraint([valve_], rule=Valt, doc='temperature relation in valve') + return sum( + m.t[stream] / (m.p[stream] ** ((m.gam - 1.0) / m.gam)) + for (valv, stream) in m.oval + if valv == valve + ) == sum( + m.t[stream] / (m.p[stream] ** ((m.gam - 1.0) / m.gam)) + for (valv, stream) in m.ival + if valv == valve + ) + + b.valt = Constraint([valve_], rule=Valt, doc="temperature relation in valve") def Valp(_m, valve): - return sum(m.p[stream] for (valv, stream) in m.oval if valv == valve) <= sum(m.p[stream] for (valv, stream) in m.ival if valv == valve) - b.valp = Constraint([valve_], rule=Valp, doc='pressure relation in valve') + return sum( + m.p[stream] for (valv, stream) in m.oval if valv == valve + ) <= sum(m.p[stream] for (valv, stream) in m.ival if valv == valve) + b.valp = Constraint([valve_], rule=Valp, doc="pressure relation in valve") m.Prereference_factor = Param( - initialize=6.3e+10, doc="Pre-reference factor for reaction rate constant") - m.Ea_R = Param(initialize=-26167.,doc="Activation energy for reaction rate constant") + initialize=6.3e10, doc="Pre-reference factor for reaction rate constant" + ) + m.Ea_R = Param( + initialize=-26167.0, doc="Activation energy for reaction rate constant" + ) m.pressure_drop = Param(initialize=0.20684, doc="Pressure drop") m.selectivity_1 = Param(initialize=0.0036, doc="Selectivity to benzene") m.selectivity_2 = Param(initialize=-1.544, doc="Selectivity to benzene") @@ -1602,104 +2674,195 @@ def build_reactor(b, rct): Parameters ---------- b : Pyomo Block - reactor block + reactor block rct : int Index of the reactor """ + def rctspec(_m, rct, stream): if (rct, stream) in m.irct: - return m.fc[stream, 'h2'] >= 5 * (m.fc[stream, 'ben'] + m.fc[stream, 'tol'] + m.fc[stream, 'dip']) + return m.fc[stream, "h2"] >= 5 * ( + m.fc[stream, "ben"] + m.fc[stream, "tol"] + m.fc[stream, "dip"] + ) return Constraint.Skip - b.Rctspec = Constraint([rct], m.str, rule=rctspec, - doc='spec. on reactor feed stream') + + b.Rctspec = Constraint( + [rct], m.str, rule=rctspec, doc="spec. on reactor feed stream" + ) def rxnrate(_m, rct): - return m.krct[rct] == m.Prereference_factor * exp(m.Ea_R / (m.rctt[rct] * 100.)) - b.Rxnrate = Constraint([rct], rule=rxnrate, - doc='reaction rate constant') + return m.krct[rct] == m.Prereference_factor * exp( + m.Ea_R / (m.rctt[rct] * 100.0) + ) + + b.Rxnrate = Constraint([rct], rule=rxnrate, doc="reaction rate constant") def rctconv(_m, rct, stream, compon): if (rct, compon) in m.rkey and (rct, stream) in m.irct: - return 1. - m.conv[rct, compon] == (1. / (1. + m.conversion_coefficient * m.krct[rct] * m.rctvol[rct] * sqrt(m.fc[stream, compon] / 60 + m.eps1) * (m.f[stream] / 60. + m.eps1) ** (-3./2.))) ** 2. + return ( + 1.0 - m.conv[rct, compon] + == ( + 1.0 + / ( + 1.0 + + m.conversion_coefficient + * m.krct[rct] + * m.rctvol[rct] + * sqrt(m.fc[stream, compon] / 60 + m.eps1) + * (m.f[stream] / 60.0 + m.eps1) ** (-3.0 / 2.0) + ) + ) + ** 2.0 + ) return Constraint.Skip - b.Rctconv = Constraint([rct], m.str, m.compon, - rule=rctconv, doc="conversion of key component") + + b.Rctconv = Constraint( + [rct], m.str, m.compon, rule=rctconv, doc="conversion of key component" + ) def rctsel(_m, rct): - return (1. - m.sel[rct]) == m.selectivity_1 * (1. - m.conv[rct, 'tol']) ** m.selectivity_2 - b.Rctsel = Constraint([rct], rule=rctsel, - doc='selectivity to benzene') + return (1.0 - m.sel[rct]) == m.selectivity_1 * ( + 1.0 - m.conv[rct, "tol"] + ) ** m.selectivity_2 + + b.Rctsel = Constraint([rct], rule=rctsel, doc="selectivity to benzene") def rctcns(_m, rct, stream, compon): if (rct, compon) in m.rkey and (rct, stream) in m.irct: - return m.consum[rct, compon] == m.conv[rct, compon] * m.fc[stream, compon] + return ( + m.consum[rct, compon] == m.conv[rct, compon] * m.fc[stream, compon] + ) return Constraint.Skip - b.Rctcns = Constraint([rct], m.str, m.compon, - rule=rctcns, doc='consumption rate of key comp.') + + b.Rctcns = Constraint( + [rct], m.str, m.compon, rule=rctcns, doc="consumption rate of key comp." + ) def rctmbtol(_m, rct): - return sum(m.fc[stream, 'tol'] for (rct_, stream) in m.orct if rct_ == rct) == sum(m.fc[stream, 'tol'] for (rct_, stream) in m.irct if rct_ == rct) - m.consum[rct, 'tol'] - b.Rctmbtol = Constraint([rct], rule=rctmbtol, - doc='mass balance in reactor (tol)') + return ( + sum(m.fc[stream, "tol"] for (rct_, stream) in m.orct if rct_ == rct) + == sum(m.fc[stream, "tol"] for (rct_, stream) in m.irct if rct_ == rct) + - m.consum[rct, "tol"] + ) + + b.Rctmbtol = Constraint( + [rct], rule=rctmbtol, doc="mass balance in reactor (tol)" + ) def rctmbben(_m, rct): - return sum(m.fc[stream, 'ben'] for (rct_, stream) in m.orct if rct_ == rct) == sum(m.fc[stream, 'ben'] for (rct_, stream) in m.irct if rct_ == rct) + m.consum[rct, 'tol'] * m.sel[rct] - b.Rctmbben = Constraint([rct], rule=rctmbben, doc='mass balance in reactor (ben)') + return ( + sum(m.fc[stream, "ben"] for (rct_, stream) in m.orct if rct_ == rct) + == sum(m.fc[stream, "ben"] for (rct_, stream) in m.irct if rct_ == rct) + + m.consum[rct, "tol"] * m.sel[rct] + ) + + b.Rctmbben = Constraint( + [rct], rule=rctmbben, doc="mass balance in reactor (ben)" + ) def rctmbdip(_m, rct): - return sum(m.fc[stream, 'dip'] for (rct1, stream) in m.orct if rct1 == rct) == sum(m.fc[stream, 'dip'] for (rct1, stream) in m.irct if rct1 == rct) + m.consum[rct, 'tol'] * 0.5 + (sum(m.fc[stream, 'ben'] for (rct1, stream) in m.irct if rct1 == rct) - sum(m.fc[stream, 'ben'] for (rct1, stream) in m.orct if rct1 == rct)) * 0.5 - b.Rctmbdip = Constraint([rct], rule=rctmbdip, doc='mass balance in reactor (dip)') + return ( + sum(m.fc[stream, "dip"] for (rct1, stream) in m.orct if rct1 == rct) + == sum(m.fc[stream, "dip"] for (rct1, stream) in m.irct if rct1 == rct) + + m.consum[rct, "tol"] * 0.5 + + ( + sum(m.fc[stream, "ben"] for (rct1, stream) in m.irct if rct1 == rct) + - sum( + m.fc[stream, "ben"] for (rct1, stream) in m.orct if rct1 == rct + ) + ) + * 0.5 + ) + + b.Rctmbdip = Constraint( + [rct], rule=rctmbdip, doc="mass balance in reactor (dip)" + ) def rctmbh2(_m, rct): - return sum(m.fc[stream, 'h2'] for (rct1, stream) in m.orct if rct1 == rct) == sum(m.fc[stream, 'h2'] for (rct1, stream) in m.irct if rct1 == rct) - m.consum[rct, 'tol'] - sum(m.fc[stream, 'dip'] for (rct1, stream) in m.irct if rct1 == rct) + sum(m.fc[stream, 'dip'] for (rct1, stream) in m.orct if rct1 == rct) - b.Rctmbh2 = Constraint([rct], rule=rctmbh2, doc='mass balance in reactor (h2)') + return sum( + m.fc[stream, "h2"] for (rct1, stream) in m.orct if rct1 == rct + ) == sum( + m.fc[stream, "h2"] for (rct1, stream) in m.irct if rct1 == rct + ) - m.consum[ + rct, "tol" + ] - sum( + m.fc[stream, "dip"] for (rct1, stream) in m.irct if rct1 == rct + ) + sum( + m.fc[stream, "dip"] for (rct1, stream) in m.orct if rct1 == rct + ) + + b.Rctmbh2 = Constraint([rct], rule=rctmbh2, doc="mass balance in reactor (h2)") def rctpi(_m, rct, stream): if (rct, stream) in m.irct: return m.rctp[rct] == m.p[stream] return Constraint.Skip - b.Rctpi = Constraint([rct], m.str, rule=rctpi, - doc='inlet pressure relation') + + b.Rctpi = Constraint([rct], m.str, rule=rctpi, doc="inlet pressure relation") def rctpo(_m, rct, stream): if (rct, stream) in m.orct: return m.rctp[rct] - m.pressure_drop == m.p[stream] return Constraint.Skip - b.Rctpo = Constraint([rct], m.str, rule=rctpo, - doc='outlet pressure relation') + + b.Rctpo = Constraint([rct], m.str, rule=rctpo, doc="outlet pressure relation") def rcttave(_m, rct): - return m.rctt[rct] == (sum(m.t[stream] for (rct1, stream) in m.irct if rct1 == rct) + sum(m.t[stream] for (rct1, stream) in m.orct if rct1 == rct))/2 - b.Rcttave = Constraint([rct], rule=rcttave, - doc='average temperature relation') + return ( + m.rctt[rct] + == ( + sum(m.t[stream] for (rct1, stream) in m.irct if rct1 == rct) + + sum(m.t[stream] for (rct1, stream) in m.orct if rct1 == rct) + ) + / 2 + ) + + b.Rcttave = Constraint([rct], rule=rcttave, doc="average temperature relation") def Rctmbch4(_m, rct): - return sum(m.fc[stream, 'ch4'] for (rct_, stream) in m.orct if rct_ == rct) == sum(m.fc[stream, 'ch4'] for (rct_, stream) in m.irct if rct == rct_) + m.consum[rct, 'tol'] - b.rctmbch4 = Constraint([rct], rule=Rctmbch4, - doc='mass balance in reactor (ch4)') + return ( + sum(m.fc[stream, "ch4"] for (rct_, stream) in m.orct if rct_ == rct) + == sum(m.fc[stream, "ch4"] for (rct_, stream) in m.irct if rct == rct_) + + m.consum[rct, "tol"] + ) + + b.rctmbch4 = Constraint( + [rct], rule=Rctmbch4, doc="mass balance in reactor (ch4)" + ) def Rcthbadb(_m, rct): if rct == 1: - return m.heatrxn[rct] * m.consum[rct, 'tol'] / 100. == sum(m.cp[stream] * m.f[stream] * m.t[stream] for (rct_, stream) in m.orct if rct_ == rct) - sum(m.cp[stream] * m.f[stream] * m.t[stream] for (rct_, stream) in m.irct if rct_ == rct) + return m.heatrxn[rct] * m.consum[rct, "tol"] / 100.0 == sum( + m.cp[stream] * m.f[stream] * m.t[stream] + for (rct_, stream) in m.orct + if rct_ == rct + ) - sum( + m.cp[stream] * m.f[stream] * m.t[stream] + for (rct_, stream) in m.irct + if rct_ == rct + ) return Constraint.Skip - b.rcthbadb = Constraint([rct], rule=Rcthbadb, - doc='heat balance (adiabatic)') + + b.rcthbadb = Constraint([rct], rule=Rcthbadb, doc="heat balance (adiabatic)") def Rcthbiso(_m, rct): if rct == 2: - return m.heatrxn[rct] * m.consum[rct, 'tol'] * 60. * 8500 * 1.0e-09 == m.q[rct] + return ( + m.heatrxn[rct] * m.consum[rct, "tol"] * 60.0 * 8500 * 1.0e-09 + == m.q[rct] + ) return Constraint.Skip - b.rcthbiso = Constraint([rct], rule=Rcthbiso, - doc='temp relation (isothermal)') + + b.rcthbiso = Constraint([rct], rule=Rcthbiso, doc="temp relation (isothermal)") def Rctisot(_m, rct): if rct == 2: - return sum(m.t[stream] for (rct_, stream) in m.irct if rct_ == rct) == sum(m.t[stream] for (rct_, stream) in m.orct if rct_ == rct) + return sum( + m.t[stream] for (rct_, stream) in m.irct if rct_ == rct + ) == sum(m.t[stream] for (rct_, stream) in m.orct if rct_ == rct) return Constraint.Skip - b.rctisot = Constraint([rct], rule=Rctisot, - doc='temp relation (isothermal)') - + b.rctisot = Constraint([rct], rule=Rctisot, doc="temp relation (isothermal)") def build_single_mixer(b, mixer): """ @@ -1712,15 +2875,19 @@ def build_single_mixer(b, mixer): mixer : int Index of the mixer """ + def Mxr1cmb(m_, mxr1, str1, compon): if (mxr1, str1) in m.omxr1 and mxr1 == mixer: - return m.fc[str1, compon] == sum(m.fc[str2, compon] for (mxr1_, str2) in m.imxr1 if mxr1_ == mxr1) + return m.fc[str1, compon] == sum( + m.fc[str2, compon] for (mxr1_, str2) in m.imxr1 if mxr1_ == mxr1 + ) return Constraint.Skip - b.mxr1cmb = Constraint([mixer], m.str, m.compon, - rule=Mxr1cmb, doc='component balance in mixer') - m.single_mixer = Block(m.mxr1, rule=build_single_mixer) - + b.mxr1cmb = Constraint( + [mixer], m.str, m.compon, rule=Mxr1cmb, doc="component balance in mixer" + ) + + m.single_mixer = Block(m.mxr1, rule=build_single_mixer) # single output splitter def build_single_splitter(b, splitter): @@ -1734,38 +2901,52 @@ def build_single_splitter(b, splitter): splitter : int Index of the splitter """ + def Spl1cmb(m_, spl1, compon): - return sum(m.fc[str1, compon] for (spl1_, str1) in m.ospl1 if spl1_ == spl1) == sum(m.fc[str1, compon] for (spl1_, str1) in m.ispl1 if spl1_ == spl1) + return sum( + m.fc[str1, compon] for (spl1_, str1) in m.ospl1 if spl1_ == spl1 + ) == sum(m.fc[str1, compon] for (spl1_, str1) in m.ispl1 if spl1_ == spl1) + b.spl1cmb = Constraint( - [splitter], m.compon, rule=Spl1cmb, doc='component balance in splitter') + [splitter], m.compon, rule=Spl1cmb, doc="component balance in splitter" + ) def Spl1pi(m_, spl1, str1): if (spl1, str1) in m.ispl1: return m.spl1p[spl1] == m.p[str1] return Constraint.Skip - b.spl1pi = Constraint([splitter], m.str, rule=Spl1pi, - doc='inlet pressure relation (splitter)') + + b.spl1pi = Constraint( + [splitter], m.str, rule=Spl1pi, doc="inlet pressure relation (splitter)" + ) def Spl1po(m_, spl1, str1): if (spl1, str1) in m.ospl1: return m.spl1p[spl1] == m.p[str1] return Constraint.Skip - b.spl1po = Constraint([splitter], m.str, rule=Spl1po, - doc='outlet pressure relation (splitter)') + + b.spl1po = Constraint( + [splitter], m.str, rule=Spl1po, doc="outlet pressure relation (splitter)" + ) def Spl1ti(m_, spl1, str1): if (spl1, str1) in m.ispl1: return m.spl1t[spl1] == m.t[str1] return Constraint.Skip - b.spl1ti = Constraint([splitter], m.str, rule=Spl1ti, - doc='inlet temperature relation (splitter)') + + b.spl1ti = Constraint( + [splitter], m.str, rule=Spl1ti, doc="inlet temperature relation (splitter)" + ) def Spl1to(m_, spl1, str1): if (spl1, str1) in m.ospl1: return m.spl1t[spl1] == m.t[str1] return Constraint.Skip - b.spl1to = Constraint([splitter], m.str, rule=Spl1to, - doc='outlet temperature relation (splitter)') + + b.spl1to = Constraint( + [splitter], m.str, rule=Spl1to, doc="outlet temperature relation (splitter)" + ) + m.single_splitter = Block(m.spl1, rule=build_single_splitter) # ## GDP formulation @@ -1777,7 +2958,6 @@ def Spl1to(m_, spl1, str1): m.five = Set(initialize=[5]) m.six = Set(initialize=[6]) - # first disjunction: Purify H2 inlet or not @m.Disjunct() def purify_H2(disj): @@ -1798,12 +2978,11 @@ def no_purify_H2(disj): @m.Disjunction() def inlet_treatment(m): - return[m.purify_H2, m.no_purify_H2] + return [m.purify_H2, m.no_purify_H2] m.multi_mixer_1 = Block(m.one, rule=build_multiple_mixer) m.furnace_1 = Block(m.one, rule=build_furnace) - # Second disjunction: Adiabatic or isothermal reactor @m.Disjunct() def adiabatic_reactor(disj): @@ -1823,7 +3002,7 @@ def isothermal_reactor(disj): @m.Disjunction() def reactor_selection(m): - return[m.adiabatic_reactor, m.isothermal_reactor] + return [m.adiabatic_reactor, m.isothermal_reactor] m.valve_3 = Block(m.three, rule=build_valve) m.multi_mixer_2 = Block(m.two, rule=build_multiple_mixer) @@ -1832,8 +3011,7 @@ def reactor_selection(m): m.flash_1 = Block(m.one, rule=build_flash) m.multi_splitter_2 = Block(m.two, rule=build_multiple_splitter) - - # thrid disjunction: recycle methane with membrane or purge it + # thrid disjunction: recycle methane with membrane or purge it @m.Disjunct() def recycle_methane_purge(disj): disj.no_flow_54 = Constraint(expr=m.f[54] == 0) @@ -1849,10 +3027,9 @@ def recycle_methane_membrane(disj): @m.Disjunction() def methane_treatmet(m): - return[m.recycle_methane_purge, m.recycle_methane_membrane] + return [m.recycle_methane_purge, m.recycle_methane_membrane] - - # fourth disjunction: recycle hydrogen with absorber or not + # fourth disjunction: recycle hydrogen with absorber or not @m.Disjunct() def recycle_hydrogen(disj): disj.no_flow_61 = Constraint(expr=m.f[61] == 0) @@ -1887,7 +3064,6 @@ def recycle_selection(m): m.multi_mixer_3 = Block(m.three, rule=build_multiple_mixer) m.multi_splitter_1 = Block(m.one, rule=build_multiple_splitter) - # fifth disjunction: methane stabilizing selection @m.Disjunct() def methane_distillation_column(disj): @@ -1932,7 +3108,6 @@ def H2_selection(m): m.benzene_column = Block(m.two, rule=build_distillation) - # sixth disjunction: toluene stabilizing selection @m.Disjunct() def toluene_distillation_column(disj): @@ -1963,43 +3138,169 @@ def toluene_selection(m): m.pump_1 = Block(m.one, rule=build_pump) m.abound = Constraint(expr=m.a[1] >= 0.0) - # ## objective function - m.hydrogen_purge_value = Param(initialize = 1.08,doc = "heating value of hydrogen purge") - m.electricity_cost = Param(initialize = 0.04 * 24 * 365 / 1000 , doc ="electricity cost, value is 0.04 with the unit of [kw/h], now is [kw/yr/k$]") - m.meathane_purge_value = Param(initialize = 3.37, doc = "heating value of meathane purge") - m.heating_cost = Param(initialize = 8000., doc = "Heating cost (steam) with unit [1e6 kj]") - m.cooling_cost = Param(initialize = 700.0, doc = "heating cost (water) with unit [1e6 kj]") - m.fuel_cost = Param(initialize = 4000.0 ,doc = "fuel cost with unit [1e6 kj]") - m.abs_fixed_cost = Param(initialize = 13, doc = "fixed cost of absober [$1e3 per year]") - m.abs_linear_coeffcient = Param(initialize = 1.2, doc = "linear coeffcient of absorber (times tray number) [$1e3 per year]") - m.compressor_fixed_cost = Param(initialize = 7.155, doc = "compressor fixed cost [$1e3 per year]") - m.compressor_fixed_cost_4 = Param(initialize = 4.866, doc = "compressor fixed cost for compressor 4 [$1e3 per year]") - m.compressor_linear_coeffcient = Param(initialize = 0.815 ,doc = "compressor linear coeffcient (vaporflow rate) [$1e3 per year]") - m.compressor_linear_coeffcient_4 = Param(initialize = 0.887 ,doc = "compressor linear coeffcient (vaporflow rate) [$1e3 per year]") - m.stabilizing_column_fixed_cost = Param(initialize = 1.126 ,doc = "stabilizing column fixed cost [$1e3 per year]") - m.stabilizing_column_linear_coeffcient = Param(initialize = 0.375 ,doc = "stabilizing column linear coeffcient (times number of trays) [$1e3 per year]") - m.benzene_column_fixed_cost = Param(initialize = 16.3 ,doc = "benzene column fixed cost [$1e3 per year]") - m.benzene_column_linear_coeffcient = Param(initialize = 1.55 ,doc = "benzene column linear coeffcient (times number of trays) [$1e3 per year]") - m.toluene_column_fixed_cost = Param(initialize = 3.9 ,doc = "toluene column fixed cost [$1e3 per year]") - m.toluene_column_linear_coeffcient = Param(initialize = 1.12 ,doc = "toluene column linear coeffcient (times number of trays) [$1e3 per year]") - m.furnace_fixed_cost = Param(initialize = 6.20 ,doc = "toluene column fixed cost [$1e3 per year]") - m.furnace_linear_coeffcient = Param(initialize = 1171.7 ,doc = "furnace column linear coeffcient [1e9kj/yr] [$1e3 per year]") - m.membrane_seperator_fixed_cost = Param(initialize = 43.24 ,doc = "membrane seperator fixed cost [$1e3 per year]") - m.membrane_seperator_linear_coeffcient = Param(initialize = 49.0 ,doc = "furnace column linear coeffcient (times inlet flowrate) [$1e3 per year]") - m.adiabtic_reactor_fixed_cost = Param(initialize = 74.3 ,doc = "adiabatic reactor fixed cost [$1e3 per year]") - m.adiabtic_reactor_linear_coeffcient = Param(initialize = 1.257 ,doc = "adiabatic reactor linear coeffcient (times reactor volumn) [$1e3 per year]") - m.isothermal_reactor_fixed_cost = Param(initialize = 92.875 ,doc = "isothermal reactor fixed cost [$1e3 per year]") - m.isothermal_reactor_linear_coeffcient = Param(initialize = 1.57125 ,doc = "isothermal reactor linear coeffcient (times reactor volumn) [$1e3 per year]") - m.h2_feed_cost = Param(initialize = 2.5, doc = "h2 feed cost (95% h2,5% Ch4)") - m.toluene_feed_cost = Param(initialize = 14., doc = "toluene feed cost (100% toluene)") - m.benzene_product = Param(initialize = 19.9,doc = "benzene product profit(benzene >= 99.97%)") - m.diphenyl_product = Param(initialize = 11.84,doc= "diphenyl product profit(diphenyl = 100%)") + m.hydrogen_purge_value = Param( + initialize=1.08, doc="heating value of hydrogen purge" + ) + m.electricity_cost = Param( + initialize=0.04 * 24 * 365 / 1000, + doc="electricity cost, value is 0.04 with the unit of [kw/h], now is [kw/yr/k$]", + ) + m.meathane_purge_value = Param( + initialize=3.37, doc="heating value of meathane purge" + ) + m.heating_cost = Param( + initialize=8000.0, doc="Heating cost (steam) with unit [1e6 kj]" + ) + m.cooling_cost = Param( + initialize=700.0, doc="heating cost (water) with unit [1e6 kj]" + ) + m.fuel_cost = Param(initialize=4000.0, doc="fuel cost with unit [1e6 kj]") + m.abs_fixed_cost = Param(initialize=13, doc="fixed cost of absober [$1e3 per year]") + m.abs_linear_coeffcient = Param( + initialize=1.2, + doc="linear coeffcient of absorber (times tray number) [$1e3 per year]", + ) + m.compressor_fixed_cost = Param( + initialize=7.155, doc="compressor fixed cost [$1e3 per year]" + ) + m.compressor_fixed_cost_4 = Param( + initialize=4.866, doc="compressor fixed cost for compressor 4 [$1e3 per year]" + ) + m.compressor_linear_coeffcient = Param( + initialize=0.815, + doc="compressor linear coeffcient (vaporflow rate) [$1e3 per year]", + ) + m.compressor_linear_coeffcient_4 = Param( + initialize=0.887, + doc="compressor linear coeffcient (vaporflow rate) [$1e3 per year]", + ) + m.stabilizing_column_fixed_cost = Param( + initialize=1.126, doc="stabilizing column fixed cost [$1e3 per year]" + ) + m.stabilizing_column_linear_coeffcient = Param( + initialize=0.375, + doc="stabilizing column linear coeffcient (times number of trays) [$1e3 per year]", + ) + m.benzene_column_fixed_cost = Param( + initialize=16.3, doc="benzene column fixed cost [$1e3 per year]" + ) + m.benzene_column_linear_coeffcient = Param( + initialize=1.55, + doc="benzene column linear coeffcient (times number of trays) [$1e3 per year]", + ) + m.toluene_column_fixed_cost = Param( + initialize=3.9, doc="toluene column fixed cost [$1e3 per year]" + ) + m.toluene_column_linear_coeffcient = Param( + initialize=1.12, + doc="toluene column linear coeffcient (times number of trays) [$1e3 per year]", + ) + m.furnace_fixed_cost = Param( + initialize=6.20, doc="toluene column fixed cost [$1e3 per year]" + ) + m.furnace_linear_coeffcient = Param( + initialize=1171.7, + doc="furnace column linear coeffcient [1e9kj/yr] [$1e3 per year]", + ) + m.membrane_seperator_fixed_cost = Param( + initialize=43.24, doc="membrane seperator fixed cost [$1e3 per year]" + ) + m.membrane_seperator_linear_coeffcient = Param( + initialize=49.0, + doc="furnace column linear coeffcient (times inlet flowrate) [$1e3 per year]", + ) + m.adiabtic_reactor_fixed_cost = Param( + initialize=74.3, doc="adiabatic reactor fixed cost [$1e3 per year]" + ) + m.adiabtic_reactor_linear_coeffcient = Param( + initialize=1.257, + doc="adiabatic reactor linear coeffcient (times reactor volumn) [$1e3 per year]", + ) + m.isothermal_reactor_fixed_cost = Param( + initialize=92.875, doc="isothermal reactor fixed cost [$1e3 per year]" + ) + m.isothermal_reactor_linear_coeffcient = Param( + initialize=1.57125, + doc="isothermal reactor linear coeffcient (times reactor volumn) [$1e3 per year]", + ) + m.h2_feed_cost = Param(initialize=2.5, doc="h2 feed cost (95% h2,5% Ch4)") + m.toluene_feed_cost = Param(initialize=14.0, doc="toluene feed cost (100% toluene)") + m.benzene_product = Param( + initialize=19.9, doc="benzene product profit(benzene >= 99.97%)" + ) + m.diphenyl_product = Param( + initialize=11.84, doc="diphenyl product profit(diphenyl = 100%)" + ) def profits_from_paper(m): - return 510. * (- m.h2_feed_cost * m.f[1] - m.toluene_feed_cost * (m.f[66] + m.f[67]) + m.benzene_product * m.f[31] + m.diphenyl_product * m.f[35] + m.hydrogen_purge_value * (m.fc[4, 'h2'] + m.fc[28, 'h2'] + m.fc[53, 'h2'] + m.fc[55, 'h2']) + m.meathane_purge_value * (m.fc[4, 'ch4'] + m.fc[28, 'ch4'] + m.fc[53, 'ch4'] + m.fc[55, 'ch4'])) - m.compressor_linear_coeffcient * (m.elec[1] + m.elec[2] + m.elec[3]) - m.compressor_linear_coeffcient * m.elec[4] - m.compressor_fixed_cost * (m.purify_H2.binary_indicator_var + m.recycle_hydrogen.binary_indicator_var + m.absorber_hydrogen.binary_indicator_var) - m.compressor_fixed_cost * m.recycle_methane_membrane.binary_indicator_var - sum((m.electricity_cost * m.elec[comp]) for comp in m.comp) - (m.adiabtic_reactor_fixed_cost * m.adiabatic_reactor.binary_indicator_var + m.adiabtic_reactor_linear_coeffcient * m.rctvol[1]) - (m.isothermal_reactor_fixed_cost * m.isothermal_reactor.binary_indicator_var + m.isothermal_reactor_linear_coeffcient * m.rctvol[2]) - m.cooling_cost/1000 * m.q[2] - (m.stabilizing_column_fixed_cost * m.methane_distillation_column.binary_indicator_var +m.stabilizing_column_linear_coeffcient * m.ndist[1]) - (m.benzene_column_fixed_cost+ m.benzene_column_linear_coeffcient * m.ndist[2]) - (m.toluene_column_fixed_cost * m.toluene_distillation_column.binary_indicator_var + m.toluene_column_linear_coeffcient * m.ndist[3]) - (m.membrane_seperator_fixed_cost * m.purify_H2.binary_indicator_var + m.membrane_seperator_linear_coeffcient * m.f[3]) - (m.membrane_seperator_fixed_cost * m.recycle_methane_membrane.binary_indicator_var + m.membrane_seperator_linear_coeffcient * m.f[54]) - (m.abs_fixed_cost * m.absorber_hydrogen.binary_indicator_var + m.abs_linear_coeffcient * m.nabs[1]) - ( m.fuel_cost * m.qfuel[1] + m.furnace_linear_coeffcient* m.qfuel[1] ) - sum(m.cooling_cost * m.qc[hec] for hec in m.hec) - sum(m.heating_cost * m.qh[heh] for heh in m.heh) - m.furnace_fixed_cost - m.obj = Objective(rule = profits_from_paper, sense=maximize) + return ( + 510.0 + * ( + -m.h2_feed_cost * m.f[1] + - m.toluene_feed_cost * (m.f[66] + m.f[67]) + + m.benzene_product * m.f[31] + + m.diphenyl_product * m.f[35] + + m.hydrogen_purge_value + * (m.fc[4, "h2"] + m.fc[28, "h2"] + m.fc[53, "h2"] + m.fc[55, "h2"]) + + m.meathane_purge_value + * (m.fc[4, "ch4"] + m.fc[28, "ch4"] + m.fc[53, "ch4"] + m.fc[55, "ch4"]) + ) + - m.compressor_linear_coeffcient * (m.elec[1] + m.elec[2] + m.elec[3]) + - m.compressor_linear_coeffcient * m.elec[4] + - m.compressor_fixed_cost + * ( + m.purify_H2.binary_indicator_var + + m.recycle_hydrogen.binary_indicator_var + + m.absorber_hydrogen.binary_indicator_var + ) + - m.compressor_fixed_cost * m.recycle_methane_membrane.binary_indicator_var + - sum((m.electricity_cost * m.elec[comp]) for comp in m.comp) + - ( + m.adiabtic_reactor_fixed_cost * m.adiabatic_reactor.binary_indicator_var + + m.adiabtic_reactor_linear_coeffcient * m.rctvol[1] + ) + - ( + m.isothermal_reactor_fixed_cost + * m.isothermal_reactor.binary_indicator_var + + m.isothermal_reactor_linear_coeffcient * m.rctvol[2] + ) + - m.cooling_cost / 1000 * m.q[2] + - ( + m.stabilizing_column_fixed_cost + * m.methane_distillation_column.binary_indicator_var + + m.stabilizing_column_linear_coeffcient * m.ndist[1] + ) + - ( + m.benzene_column_fixed_cost + + m.benzene_column_linear_coeffcient * m.ndist[2] + ) + - ( + m.toluene_column_fixed_cost + * m.toluene_distillation_column.binary_indicator_var + + m.toluene_column_linear_coeffcient * m.ndist[3] + ) + - ( + m.membrane_seperator_fixed_cost * m.purify_H2.binary_indicator_var + + m.membrane_seperator_linear_coeffcient * m.f[3] + ) + - ( + m.membrane_seperator_fixed_cost + * m.recycle_methane_membrane.binary_indicator_var + + m.membrane_seperator_linear_coeffcient * m.f[54] + ) + - ( + m.abs_fixed_cost * m.absorber_hydrogen.binary_indicator_var + + m.abs_linear_coeffcient * m.nabs[1] + ) + - (m.fuel_cost * m.qfuel[1] + m.furnace_linear_coeffcient * m.qfuel[1]) + - sum(m.cooling_cost * m.qc[hec] for hec in m.hec) + - sum(m.heating_cost * m.qh[heh] for heh in m.heh) + - m.furnace_fixed_cost + ) + + m.obj = Objective(rule=profits_from_paper, sense=maximize) # def profits_GAMS_file(m): @@ -2013,10 +3314,11 @@ def profits_from_paper(m): # %% + def solve_with_gdpopt(m): """ This function solves model m using GDPOpt - + Parameters ---------- m : Pyomo Model @@ -2027,24 +3329,30 @@ def solve_with_gdpopt(m): res : solver results The result of the optimization """ - opt = SolverFactory('gdpopt') - res = opt.solve(m, tee=True, - strategy='LOA', - # strategy='GLOA', - time_limit=3600, - mip_solver='gams', - mip_solver_args=dict(solver='cplex', warmstart=True), - nlp_solver='gams', - nlp_solver_args=dict(solver='ipopth', warmstart=True,), - minlp_solver='gams', - minlp_solver_args=dict(solver='dicopt', warmstart=True), - subproblem_presolve=False, - # init_strategy='no_init', - set_cover_iterlim=20, - # calc_disjunctive_bounds=True - ) + opt = SolverFactory("gdpopt") + res = opt.solve( + m, + tee=True, + strategy="LOA", + # strategy='GLOA', + time_limit=3600, + mip_solver="gams", + mip_solver_args=dict(solver="cplex", warmstart=True), + nlp_solver="gams", + nlp_solver_args=dict( + solver="ipopth", + warmstart=True, + ), + minlp_solver="gams", + minlp_solver_args=dict(solver="dicopt", warmstart=True), + subproblem_presolve=False, + # init_strategy='no_init', + set_cover_iterlim=20, + # calc_disjunctive_bounds=True + ) return res + def solve_with_minlp(m): """ This function solves model m using minlp transformation by either Big-M or convex hull @@ -2060,34 +3368,31 @@ def solve_with_minlp(m): The result of the optimization """ - TransformationFactory('gdp.bigm').apply_to(m, bigM=60) + TransformationFactory("gdp.bigm").apply_to(m, bigM=60) # TransformationFactory('gdp.hull').apply_to(m) # result = SolverFactory('baron').solve(m, tee=True) - result = SolverFactory('gams').solve( - m, solver='baron', tee=True, - add_options=[ - 'option reslim=120;' - ] - ); + result = SolverFactory("gams").solve( + m, solver="baron", tee=True, add_options=["option reslim=120;"] + ) return result - # %% + def infeasible_constraints(m): - ''' + """ This function checks infeasible constraint in the model - ''' + """ log_infeasible_constraints(m) - # %% # enumeration each possible route selection by fixing binary variable values in every disjunctions + def enumerate_solutions(m): """ Enumerate all possible route selections by fixing binary variables in each disjunctions @@ -2098,13 +3403,12 @@ def enumerate_solutions(m): Pyomo model to be solved """ - H2_treatments = ['purify','none_purify'] - Reactor_selections = ['adiabatic_reactor','isothermal_reactor'] - Methane_recycle_selections = ['recycle_membrane','recycle_purge'] - Absorber_recycle_selections = ['no_absorber','yes_absorber'] - Methane_product_selections = ['methane_flash','methane_column'] - Toluene_product_selections = ['toluene_flash','toluene_column'] - + H2_treatments = ["purify", "none_purify"] + Reactor_selections = ["adiabatic_reactor", "isothermal_reactor"] + Methane_recycle_selections = ["recycle_membrane", "recycle_purge"] + Absorber_recycle_selections = ["no_absorber", "yes_absorber"] + Methane_product_selections = ["methane_flash", "methane_column"] + Toluene_product_selections = ["toluene_flash", "toluene_column"] for H2_treatment in H2_treatments: for Reactor_selection in Reactor_selections: @@ -2112,62 +3416,81 @@ def enumerate_solutions(m): for Absorber_recycle_selection in Absorber_recycle_selections: for Methane_product_selection in Methane_product_selections: for Toluene_product_selection in Toluene_product_selections: - if H2_treatment == 'purify': + if H2_treatment == "purify": m.purify_H2.indicator_var.fix(True) m.no_purify_H2.indicator_var.fix(False) else: m.purify_H2.indicator_var.fix(False) m.no_purify_H2.indicator_var.fix(True) - if Reactor_selection == 'adiabatic_reactor': + if Reactor_selection == "adiabatic_reactor": m.adiabatic_reactor.indicator_var.fix(True) m.isothermal_reactor.indicator_var.fix(False) else: m.adiabatic_reactor.indicator_var.fix(False) m.isothermal_reactor.indicator_var.fix(True) - if Methane_recycle_selection == 'recycle_membrane': + if Methane_recycle_selection == "recycle_membrane": m.recycle_methane_purge.indicator_var.fix(False) m.recycle_methane_membrane.indicator_var.fix(True) else: m.recycle_methane_purge.indicator_var.fix(True) m.recycle_methane_membrane.indicator_var.fix(False) - if Absorber_recycle_selection == 'yes_absorber': + if Absorber_recycle_selection == "yes_absorber": m.absorber_hydrogen.indicator_var.fix(True) m.recycle_hydrogen.indicator_var.fix(False) else: m.absorber_hydrogen.indicator_var.fix(False) m.recycle_hydrogen.indicator_var.fix(True) - if Methane_product_selection == 'methane_column': + if Methane_product_selection == "methane_column": m.methane_flash_separation.indicator_var.fix(False) m.methane_distillation_column.indicator_var.fix(True) else: m.methane_flash_separation.indicator_var.fix(True) m.methane_distillation_column.indicator_var.fix(False) - if Toluene_product_selection == 'toluene_column': + if Toluene_product_selection == "toluene_column": m.toluene_flash_separation.indicator_var.fix(False) m.toluene_distillation_column.indicator_var.fix(True) else: m.toluene_flash_separation.indicator_var.fix(True) m.toluene_distillation_column.indicator_var.fix(False) - opt = SolverFactory('gdpopt') - res = opt.solve(m,tee =False, - strategy = 'LOA', - time_limit = 3600, - mip_solver = 'gams', - mip_solver_args= dict(solver = 'gurobi', warmstart=True), - nlp_solver = 'gams', - nlp_solver_args= dict(solver = 'ipopth', add_options = ['option optcr = 0'],warmstart=True), - minlp_solver = 'gams', - minlp_solver_args= dict(solver = 'dicopt', warmstart=True), - subproblem_presolve=False, - init_strategy = 'no_init', - set_cover_iterlim = 20 - ) - print('{0:<30}{1:<30}{2:<30}{3:<30}{4:<30}{5:<30}{6:<30}{7:<30}'.format(H2_treatment, Reactor_selection, Methane_recycle_selection,Absorber_recycle_selection,Methane_product_selection,Toluene_product_selection,str(res.solver.termination_condition),value(m.obj))) + opt = SolverFactory("gdpopt") + res = opt.solve( + m, + tee=False, + strategy="LOA", + time_limit=3600, + mip_solver="gams", + mip_solver_args=dict(solver="gurobi", warmstart=True), + nlp_solver="gams", + nlp_solver_args=dict( + solver="ipopth", + add_options=["option optcr = 0"], + warmstart=True, + ), + minlp_solver="gams", + minlp_solver_args=dict(solver="dicopt", warmstart=True), + subproblem_presolve=False, + init_strategy="no_init", + set_cover_iterlim=20, + ) + print( + "{0:<30}{1:<30}{2:<30}{3:<30}{4:<30}{5:<30}{6:<30}{7:<30}".format( + H2_treatment, + Reactor_selection, + Methane_recycle_selection, + Absorber_recycle_selection, + Methane_product_selection, + Toluene_product_selection, + str(res.solver.termination_condition), + value(m.obj), + ) + ) + + # %% def show_decision(m): - ''' + """ print indicator variable value - ''' + """ if value(m.purify_H2.binary_indicator_var) == 1: print("purify inlet H2") else: @@ -2192,6 +3515,8 @@ def show_decision(m): print("toluene_column") else: print("toluene_flash") + + # %% From bdd5ab4afd21cfd0fd15b10379efb1c57e52eac5 Mon Sep 17 00:00:00 2001 From: Carolina Tristan Date: Fri, 17 May 2024 07:12:11 -0400 Subject: [PATCH 06/79] Update documentation and units --- gdplib/kaibel/kaibel_prop.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/gdplib/kaibel/kaibel_prop.py b/gdplib/kaibel/kaibel_prop.py index bd43032..51b50cd 100644 --- a/gdplib/kaibel/kaibel_prop.py +++ b/gdplib/kaibel/kaibel_prop.py @@ -1,24 +1,30 @@ """ Properties of the system """ -from __future__ import division - from pyomo.environ import ConcreteModel def get_model_with_properties(): - - """Attach properties to the model.""" + """ + Attach properties to the model and return the updated model. + The properties are the physical properties of the components in the system and constants for the calculation of liquid heat capacity. + It also includes the known initial values and scaling factors for the system, such as the number of trays, components, and flowrates. + Specifications for the product and the column are also included. + Case Study: methanol (1), ethanol (2), propanol (3), and butanol (4) + + Returns: + Pyomo ConcreteModel: The Pyomo model object with attached properties. + """ - m = ConcreteModel() + m = ConcreteModel("Properties of the system") # ------------------------------------------------------------------ # Data # ------------------------------------------------------------------ - m.np = 25 # Number of possible tays - m.c = 4 # Number of components - m.lc = 1 # Light component - m.hc = 4 # Heavy component + m.np = 25 # Number of possible trays. Upper bound for each section. + m.c = 4 # Number of components. Methanol (1), ethanol (2), propanol (3), and butanol (4) + m.lc = 1 # Light component, methanol + m.hc = 4 # Heavy component, butanol #### Constant parameters m.Rgas = 8.314 # Ideal gas constant in J/mol K @@ -44,7 +50,7 @@ def get_model_with_properties(): m.Pbot = 1.12 # Bottom-most tray pressure in bar m.Ptop = 1.08 # Top-most tray pressure in bar m.Pcon = 1.05 # Condenser pressure in bar - m.Pf = 1.02 + m.Pf = 1.02 # Column pressure in bar m.rr0 = 0.893 # Internal reflux ratio initial value m.bu0 = 0.871 # Internal reflux ratio initial value From 723fc8076ecb9ed775877c9e4c8fae02609756b7 Mon Sep 17 00:00:00 2001 From: Carolina Tristan Date: Fri, 17 May 2024 07:40:56 -0400 Subject: [PATCH 07/79] Black formatted --- gdplib/kaibel/kaibel_prop.py | 148 +++++++++++++++++------------------ 1 file changed, 72 insertions(+), 76 deletions(-) diff --git a/gdplib/kaibel/kaibel_prop.py b/gdplib/kaibel/kaibel_prop.py index 51b50cd..b758901 100644 --- a/gdplib/kaibel/kaibel_prop.py +++ b/gdplib/kaibel/kaibel_prop.py @@ -14,64 +14,61 @@ def get_model_with_properties(): Returns: Pyomo ConcreteModel: The Pyomo model object with attached properties. """ - + m = ConcreteModel("Properties of the system") # ------------------------------------------------------------------ # Data # ------------------------------------------------------------------ - m.np = 25 # Number of possible trays. Upper bound for each section. - m.c = 4 # Number of components. Methanol (1), ethanol (2), propanol (3), and butanol (4) - m.lc = 1 # Light component, methanol - m.hc = 4 # Heavy component, butanol + m.np = 25 # Number of possible trays. Upper bound for each section. + m.c = 4 # Number of components. Methanol (1), ethanol (2), propanol (3), and butanol (4) + m.lc = 1 # Light component, methanol + m.hc = 4 # Heavy component, butanol #### Constant parameters - m.Rgas = 8.314 # Ideal gas constant in J/mol K - m.Tref = 298.15 # Reference temperature in K + m.Rgas = 8.314 # Ideal gas constant in J/mol K + m.Tref = 298.15 # Reference temperature in K #### Product specifications - m.xspec_lc = 0.99 # Final liquid composition for methanol (1) - m.xspec_hc = 0.99 # Fnal liquid composition for butanol (4) - m.xspec_inter2 = 0.99 # Final liquid composition for ethanol (2) - m.xspec_inter3 = 0.99 # Final liquid composition for propanol (3) - m.Ddes = 50 # Final flowrate in distillate in mol/s - m.Bdes = 50 # Final flowrate in bottoms in mol/s - m.Sdes = 50 # Final flowrate in side product streams in mol/s + m.xspec_lc = 0.99 # Final liquid composition for methanol (1) + m.xspec_hc = 0.99 # Fnal liquid composition for butanol (4) + m.xspec_inter2 = 0.99 # Final liquid composition for ethanol (2) + m.xspec_inter3 = 0.99 # Final liquid composition for propanol (3) + m.Ddes = 50 # Final flowrate in distillate in mol/s + m.Bdes = 50 # Final flowrate in bottoms in mol/s + m.Sdes = 50 # Final flowrate in side product streams in mol/s # #### Known initial values - m.Fi = m.Ddes + m.Bdes + 2 * m.Sdes # Side feed flowrate in mol/s - m.Vi = 400 # Initial value for vapor flowrate in mol/s - m.Li = 400 # Initial value for liquid flowrate in mol/s - - m.Tf = 358 # Side feed temperature in K + m.Fi = m.Ddes + m.Bdes + 2 * m.Sdes # Side feed flowrate in mol/s + m.Vi = 400 # Initial value for vapor flowrate in mol/s + m.Li = 400 # Initial value for liquid flowrate in mol/s - m.Preb = 1.2 # Reboiler pressure in bar - m.Pbot = 1.12 # Bottom-most tray pressure in bar - m.Ptop = 1.08 # Top-most tray pressure in bar - m.Pcon = 1.05 # Condenser pressure in bar - m.Pf = 1.02 # Column pressure in bar + m.Tf = 358 # Side feed temperature in K - m.rr0 = 0.893 # Internal reflux ratio initial value - m.bu0 = 0.871 # Internal reflux ratio initial value + m.Preb = 1.2 # Reboiler pressure in bar + m.Pbot = 1.12 # Bottom-most tray pressure in bar + m.Ptop = 1.08 # Top-most tray pressure in bar + m.Pcon = 1.05 # Condenser pressure in bar + m.Pf = 1.02 # Column pressure in bar + m.rr0 = 0.893 # Internal reflux ratio initial value + m.bu0 = 0.871 # Internal reflux ratio initial value #### Scaling factors - m.Hscale = 1e3 - m.Qscale = 1e-3 + m.Hscale = 1e3 + m.Qscale = 1e-3 - #### Constants for the calculation of liquid heat capacity - m.cpc = {} # Constant 1 for liquid heat capacity - m.cpc2 = {} # Constant 2 for liquid heat capacity - m.cpc[1] = m.Rgas + m.cpc = {} # Constant 1 for liquid heat capacity + m.cpc2 = {} # Constant 2 for liquid heat capacity + m.cpc[1] = m.Rgas m.cpc[2] = 1 m.cpc2['A', 1] = 1 / 100 m.cpc2['B', 1] = 1 / 1e4 m.cpc2['A', 2] = 1 m.cpc2['B', 2] = 1 - # ------------------------------------------------------------------ # Physical Properties # @@ -113,45 +110,48 @@ def get_model_with_properties(): cpL['a', 'C(H3)(O)'] = 3.70344 cpL['b', 'C(H3)(O)'] = -1.12884 cpL['c', 'C(H3)(O)'] = 0.51239 - sumA[1] = (cpL['a', 'C(H3)(O)'] - + cpL['a', 'O(H)(C)']) - sumB[1] = (cpL['b', 'C(H3)(O)'] - + cpL['b', 'O(H)(C)']) - sumC[1] = (cpL['c', 'C(H3)(O)'] - + cpL['c', 'O(H)(C)']) - sumA[2] = (cpL['a', 'C(H3)(C)'] - + cpL['a', 'C(H2)(C)(O)'] - + cpL['a', 'O(H)(C)']) - sumB[2] = (cpL['b', 'C(H3)(C)'] - + cpL['b', 'C(H2)(C)(O)'] - + cpL['b', 'O(H)(C)']) - sumC[2] = (cpL['c', 'C(H3)(C)'] - + cpL['c', 'C(H2)(C)(O)'] - + cpL['c', 'O(H)(C)']) - sumA[3] = (cpL['a', 'C(H3)(C)'] - + cpL['a', 'C(H2)(C2)'] - + cpL['a', 'C(H2)(C)(O)'] - + cpL['a', 'O(H)(C)']) - sumB[3] = (cpL['b', 'C(H3)(C)'] - + cpL['b', 'C(H2)(C2)'] - + cpL['b', 'C(H2)(C)(O)'] - + cpL['b', 'O(H)(C)']) - sumC[3] = (cpL['c', 'C(H3)(C)'] - + cpL['c', 'C(H2)(C2)'] - + cpL['c', 'C(H2)(C)(O)'] - + cpL['c', 'O(H)(C)']) - sumA[4] = (cpL['a', 'C(H3)(C)'] - + 2 * cpL['a', 'C(H2)(C2)'] - + cpL['a', 'C(H2)(C)(O)'] - + cpL['a', 'O(H)(C)']) - sumB[4] = (cpL['b', 'C(H3)(C)'] - + 2 * cpL['b', 'C(H2)(C2)'] - + cpL['b', 'C(H2)(C)(O)'] - + cpL['b', 'O(H)(C)']) - sumC[4] = (cpL['c', 'C(H3)(C)'] - + 2 * cpL['c', 'C(H2)(C2)'] - + cpL['c', 'C(H2)(C)(O)'] - + cpL['c', 'O(H)(C)']) + sumA[1] = cpL['a', 'C(H3)(O)'] + cpL['a', 'O(H)(C)'] + sumB[1] = cpL['b', 'C(H3)(O)'] + cpL['b', 'O(H)(C)'] + sumC[1] = cpL['c', 'C(H3)(O)'] + cpL['c', 'O(H)(C)'] + sumA[2] = cpL['a', 'C(H3)(C)'] + cpL['a', 'C(H2)(C)(O)'] + cpL['a', 'O(H)(C)'] + sumB[2] = cpL['b', 'C(H3)(C)'] + cpL['b', 'C(H2)(C)(O)'] + cpL['b', 'O(H)(C)'] + sumC[2] = cpL['c', 'C(H3)(C)'] + cpL['c', 'C(H2)(C)(O)'] + cpL['c', 'O(H)(C)'] + sumA[3] = ( + cpL['a', 'C(H3)(C)'] + + cpL['a', 'C(H2)(C2)'] + + cpL['a', 'C(H2)(C)(O)'] + + cpL['a', 'O(H)(C)'] + ) + sumB[3] = ( + cpL['b', 'C(H3)(C)'] + + cpL['b', 'C(H2)(C2)'] + + cpL['b', 'C(H2)(C)(O)'] + + cpL['b', 'O(H)(C)'] + ) + sumC[3] = ( + cpL['c', 'C(H3)(C)'] + + cpL['c', 'C(H2)(C2)'] + + cpL['c', 'C(H2)(C)(O)'] + + cpL['c', 'O(H)(C)'] + ) + sumA[4] = ( + cpL['a', 'C(H3)(C)'] + + 2 * cpL['a', 'C(H2)(C2)'] + + cpL['a', 'C(H2)(C)(O)'] + + cpL['a', 'O(H)(C)'] + ) + sumB[4] = ( + cpL['b', 'C(H3)(C)'] + + 2 * cpL['b', 'C(H2)(C2)'] + + cpL['b', 'C(H2)(C)(O)'] + + cpL['b', 'O(H)(C)'] + ) + sumC[4] = ( + cpL['c', 'C(H3)(C)'] + + 2 * cpL['c', 'C(H2)(C2)'] + + cpL['c', 'C(H2)(C)(O)'] + + cpL['c', 'O(H)(C)'] + ) ## Methanol: component 1 m.prop[1, 'MW'] = 32.042 @@ -174,7 +174,6 @@ def get_model_with_properties(): m.prop[1, 'cpC', 2] = 2.587e-5 m.prop[1, 'cpD', 2] = -2.852e-8 - ## Ethanol: component 2 m.prop[2, 'MW'] = 46.069 m.prop[2, 'TB'] = 351.4 @@ -196,7 +195,6 @@ def get_model_with_properties(): m.prop[2, 'cpC', 2] = -8.390e-5 m.prop[2, 'cpD', 2] = 1.373e-9 - ## Propanol: component 3 m.prop[3, 'MW'] = 60.096 m.prop[3, 'TB'] = 370.3 @@ -218,7 +216,6 @@ def get_model_with_properties(): m.prop[3, 'cpC', 2] = -1.855e-4 m.prop[3, 'cpD', 2] = 4.296e-8 - ## Butanol: component 4 m.prop[4, 'MW'] = 74.123 m.prop[4, 'TB'] = 390.9 @@ -240,5 +237,4 @@ def get_model_with_properties(): m.prop[4, 'cpC', 2] = -2.242e-4 m.prop[4, 'cpD', 2] = 4.685e-8 - return m From 487fa38c6be67d3148db1b5567ccf30cc4cf34b7 Mon Sep 17 00:00:00 2001 From: parkyr Date: Fri, 17 May 2024 11:29:51 -0400 Subject: [PATCH 08/79] fixing the formatting --- gdplib/hda/HDA_GDP_gdpopt.py | 7078 +++++++++++++++++----------------- 1 file changed, 3538 insertions(+), 3540 deletions(-) diff --git a/gdplib/hda/HDA_GDP_gdpopt.py b/gdplib/hda/HDA_GDP_gdpopt.py index d16f651..c454efd 100644 --- a/gdplib/hda/HDA_GDP_gdpopt.py +++ b/gdplib/hda/HDA_GDP_gdpopt.py @@ -1,3540 +1,3538 @@ -""" -HDA_GDP_gdpopt.py -This model describes the profit maximization of a Hydrodealkylation of Toluene process, first presented in Reference [1], and later implemented as a GDP in Reference [2]. The MINLP formulation of this problem is available in GAMS, Reference [3]. - -The chemical plant performed the hydro-dealkylation of toluene into benzene and methane. The flowsheet model was used to make decisions on choosing between alternative process units at various stages of the process. The resulting model is GDP model. The disjunctions in the model include: - 1. Inlet purify selection at feed - 2. Reactor operation mode selection (adiabatic / isothermal) - 3. Vapor recovery methane purge / recycle with membrane - 4. Vapor recovery hydrogen recycle - 5. Liquid separation system methane stabilizing via column or flash drum - 6. Liquid separation system toluene recovery via column or flash drum - -The model enforces constraints to ensure that the mass and energy balances are satisfied, the purity of the products is within the required limits, the recovery specification are met, and the temperature and pressure conditions in the process units are maintained within the operational limits. - -The objective of the model is to maximize the profit by determining the optimal process configuration and operating conditions. The decision variables include the number of trays in the absorber and distillation column, the reflux ratio, the pressure in the distillation column, the temperature and pressure in the flash drums, the heating requirement in the furnace, the electricity requirement in the compressor, the heat exchange in the coolers and heaters, the surface area in the membrane separators, the temperature and pressure in the mixers, the temperature and pressure in the reactors, and the volume and rate constant in the reactors. - -References: - [1] James M Douglas (1988). Conceptual Design of Chemical Processes, McGraw-Hill. ISBN-13: 978-0070177628 - [2] G.R. Kocis, and I.E. Grossmann (1989). Computational Experience with DICOPT Solving MINLP Problems in Process Synthesis. Computers and Chemical Engineering 13, 3, 307-315. https://doi.org/10.1016/0098-1354(89)85008-2 - [3] GAMS Development Corporation (2023). Hydrodealkylation Process. Available at: https://www.gams.com/latest/gamslib_ml/libhtml/gamslib_hda.html -""" - -import math -import os -import pandas as pd - -from pyomo.environ import * -from pyomo.gdp import * -from pyomo.util.infeasible import log_infeasible_constraints - - -def HDA_model(): - """ - Builds the Hydrodealkylation of Toluene process model. - - Parameters - ---------- - alpha : float - compressor coefficient - compeff : float - compressor efficiency - gam : float - ratio of cp to cv - abseff : float - absorber tray efficiency - disteff : float - column tray efficiency - uflow : float - upper bound - flow logicals - upress : float - upper bound - pressure logicals - utemp : float - upper bound - temperature logicals - costelec : float - electricity cost - costqc : float - cooling cost - costqh : float - heating cost - costfuel : float - fuel cost furnace - furnpdrop : float - pressure drop of furnace - heatvap : float - heat of vaporization [kj per kg-mol] - cppure : float - pure component heat capacities [kj per kg-mol-k] - gcomp : float - guess composition values - cp : float - heat capacities [kj per kg-mol-k] - anta : float - antoine coefficient A - antb : float - antoine coefficient B - antc : float - antoine coefficient C - perm : float - permeability [kg-mole/m**2-min-mpa] - cbeta : float - constant values (exp(beta)) in absorber - aabs : float - absorption factors - eps1 : float - small number to avoid div. by zero - heatrxn : float - heat of reaction [kj per kg-mol] - f1comp : float - feedstock compositions (h2 feed) - f66comp : float - feedstock compositions (tol feed) - f67comp : float - feedstock compositions (tol feed) - - Sets - ---- - str : int - process streams - compon : str - chemical components - abs : int - absorber - comp : int - compressor - dist : int - distillation column - flsh : int - flash drums - furn : int - furnace - hec : int - coolers - heh : int - heaters - exch : int - heat exchangers - memb : int - membrane separators - mxr1 : int - single inlet stream mixers - mxr : int - mixers - pump : int - pumps - rct : int - reactors - spl1 : int - single outlet stream splitters - spl : int - splitter - valve : int - expansion valve - str2 : int - process streams - compon2 : str - chemical components - - - Returns - ------- - m : Pyomo ConcreteModel - Pyomo model of the Hydrodealkylation of Toluene process - - """ - dir_path = os.path.dirname(os.path.abspath(__file__)) - - m = ConcreteModel() - - # ## scalars - - m.alpha = Param(initialize=0.3665, doc="compressor coefficient") - m.compeff = Param(initialize=0.750, doc="compressor efficiency") - m.gam = Param(initialize=1.300, doc="ratio of cp to cv") - m.abseff = Param(initialize=0.333, doc="absorber tray efficiency") - m.disteff = Param(initialize=0.5000, doc="column tray efficiency") - m.uflow = Param(initialize=50, doc="upper bound - flow logicals") - m.upress = Param(initialize=4.0, doc="upper bound - pressure logicals") - m.utemp = Param(initialize=7.0, doc="upper bound - temperature logicals") - m.costelec = Param(initialize=0.340, doc="electricity cost") - m.costqc = Param(initialize=0.7000, doc="cooling cost") - m.costqh = Param(initialize=8.0000, doc="heating cost") - m.costfuel = Param(initialize=4.0, doc="fuel cost furnace") - m.furnpdrop = Param(initialize=0.4826, doc="pressure drop of furnace") - - # ## sets - - def strset(i): - """ - Process streams - - Returns - ------- - s : list - integer list from 1 to 74 - """ - s = [] - i = 1 - for i in range(1, 36): - s.append(i) - i += i - i = 37 - for i in range(37, 74): - s.append(i) - i += i - return s - - m.str = Set(initialize=strset, doc="process streams") - m.compon = Set( - initialize=["h2", "ch4", "ben", "tol", "dip"], doc="chemical components" - ) - m.abs = RangeSet(1) - m.comp = RangeSet(4) - m.dist = RangeSet(3) - m.flsh = RangeSet(3) - m.furn = RangeSet(1) - m.hec = RangeSet(2) - m.heh = RangeSet(4) - m.exch = RangeSet(1) - m.memb = RangeSet(2) - m.mxr1 = RangeSet(5) - m.mxr = RangeSet(5) - m.pump = RangeSet(2) - m.rct = RangeSet(2) - m.spl1 = RangeSet(6) - m.spl = RangeSet(3) - m.valve = RangeSet(6) - m.str2 = Set(initialize=strset, doc="process streams") - m.compon2 = Set( - initialize=["h2", "ch4", "ben", "tol", "dip"], doc="chemical components" - ) - - # parameters - Heatvap = {} - Heatvap["tol"] = 30890.00 - m.heatvap = Param( - m.compon, - initialize=Heatvap, - default=0, - doc="heat of vaporization [kj per kg-mol]", - ) - Cppure = {} - # h2 'hydrogen', ch4 'methane', ben 'benzene', tol 'toluene', dip 'diphenyl' - Cppure["h2"] = 30 - Cppure["ch4"] = 40 - Cppure["ben"] = 225 - Cppure["tol"] = 225 - Cppure["dip"] = 450 - m.cppure = Param( - m.compon, - initialize=Cppure, - default=0, - doc="pure component heat capacities [kj per kg-mol-k]", - ) - Gcomp = {} - Gcomp[7, "h2"] = 0.95 - Gcomp[7, "ch4"] = 0.05 - Gcomp[8, "h2"] = 0.5 - Gcomp[8, "ch4"] = 0.40 - Gcomp[8, "tol"] = 0.1 - Gcomp[9, "h2"] = 0.5 - Gcomp[9, "ch4"] = 0.40 - Gcomp[9, "tol"] = 0.1 - Gcomp[10, "h2"] = 0.5 - Gcomp[10, "ch4"] = 0.40 - Gcomp[10, "tol"] = 0.1 - Gcomp[11, "h2"] = 0.45 - Gcomp[11, "ben"] = 0.05 - Gcomp[11, "ch4"] = 0.45 - Gcomp[11, "tol"] = 0.05 - Gcomp[12, "h2"] = 0.50 - Gcomp[12, "ch4"] = 0.40 - Gcomp[12, "tol"] = 0.10 - Gcomp[13, "h2"] = 0.45 - Gcomp[13, "ch4"] = 0.45 - Gcomp[13, "ben"] = 0.05 - Gcomp[13, "tol"] = 0.05 - Gcomp[14, "h2"] = 0.45 - Gcomp[14, "ch4"] = 0.45 - Gcomp[14, "ben"] = 0.05 - Gcomp[14, "tol"] = 0.05 - Gcomp[15, "h2"] = 0.45 - Gcomp[15, "ch4"] = 0.45 - Gcomp[15, "ben"] = 0.05 - Gcomp[15, "tol"] = 0.05 - Gcomp[16, "h2"] = 0.4 - Gcomp[16, "ch4"] = 0.4 - Gcomp[16, "ben"] = 0.1 - Gcomp[16, "tol"] = 0.1 - Gcomp[17, "h2"] = 0.40 - Gcomp[17, "ch4"] = 0.40 - Gcomp[17, "ben"] = 0.1 - Gcomp[17, "tol"] = 0.1 - Gcomp[20, "h2"] = 0.03 - Gcomp[20, "ch4"] = 0.07 - Gcomp[20, "ben"] = 0.55 - Gcomp[20, "tol"] = 0.35 - Gcomp[21, "h2"] = 0.03 - Gcomp[21, "ch4"] = 0.07 - Gcomp[21, "ben"] = 0.55 - Gcomp[21, "tol"] = 0.35 - Gcomp[22, "h2"] = 0.03 - Gcomp[22, "ch4"] = 0.07 - Gcomp[22, "ben"] = 0.55 - Gcomp[22, "tol"] = 0.35 - Gcomp[24, "h2"] = 0.03 - Gcomp[24, "ch4"] = 0.07 - Gcomp[24, "ben"] = 0.55 - Gcomp[24, "tol"] = 0.35 - Gcomp[25, "h2"] = 0.03 - Gcomp[25, "ch4"] = 0.07 - Gcomp[25, "ben"] = 0.55 - Gcomp[25, "tol"] = 0.35 - Gcomp[37, "tol"] = 1.00 - Gcomp[38, "tol"] = 1.00 - Gcomp[43, "ben"] = 0.05 - Gcomp[43, "tol"] = 0.95 - Gcomp[44, "h2"] = 0.03 - Gcomp[44, "ch4"] = 0.07 - Gcomp[44, "ben"] = 0.55 - Gcomp[44, "tol"] = 0.35 - Gcomp[45, "h2"] = 0.03 - Gcomp[45, "ch4"] = 0.07 - Gcomp[45, "ben"] = 0.55 - Gcomp[45, "tol"] = 0.35 - Gcomp[46, "h2"] = 0.03 - Gcomp[46, "ch4"] = 0.07 - Gcomp[46, "ben"] = 0.55 - Gcomp[46, "tol"] = 0.35 - Gcomp[51, "h2"] = 0.30 - Gcomp[51, "ch4"] = 0.70 - Gcomp[57, "h2"] = 0.80 - Gcomp[57, "ch4"] = 0.20 - Gcomp[60, "h2"] = 0.50 - Gcomp[60, "ch4"] = 0.50 - Gcomp[62, "h2"] = 0.50 - Gcomp[62, "ch4"] = 0.50 - Gcomp[63, "h2"] = 0.47 - Gcomp[63, "ch4"] = 0.40 - Gcomp[63, "ben"] = 0.01 - Gcomp[63, "tol"] = 0.12 - Gcomp[65, "h2"] = 0.50 - Gcomp[65, "ch4"] = 0.50 - Gcomp[66, "tol"] = 1.0 - Gcomp[69, "tol"] = 1.0 - Gcomp[70, "h2"] = 0.5 - Gcomp[70, "ch4"] = 0.4 - Gcomp[70, "tol"] = 0.10 - Gcomp[71, "h2"] = 0.40 - Gcomp[71, "ch4"] = 0.40 - Gcomp[71, "ben"] = 0.10 - Gcomp[71, "tol"] = 0.10 - Gcomp[72, "h2"] = 0.50 - Gcomp[72, "ch4"] = 0.50 - m.gcomp = Param( - m.str, m.compon, initialize=Gcomp, default=0, doc="guess composition values" - ) - - def cppara(compon, stream): - """ - heat capacities [kj per kg-mol-k] - sum of heat capacities of all components in a stream, weighted by their composition - """ - return sum(m.cppure[compon] * m.gcomp[stream, compon] for compon in m.compon) - - m.cp = Param( - m.str, initialize=cppara, default=0, doc="heat capacities [kj per kg-mol-k]" - ) - - Anta = {} - Anta["h2"] = 13.6333 - Anta["ch4"] = 15.2243 - Anta["ben"] = 15.9008 - Anta["tol"] = 16.0137 - Anta["dip"] = 16.6832 - m.anta = Param(m.compon, initialize=Anta, default=0, doc="antoine coefficient A") - - Antb = {} - Antb["h2"] = 164.9 - Antb["ch4"] = 897.84 - Antb["ben"] = 2788.51 - Antb["tol"] = 3096.52 - Antb["dip"] = 4602.23 - m.antb = Param(m.compon, initialize=Antb, default=0, doc="antoine coefficient B") - - Antc = {} - Antc["h2"] = 3.19 - Antc["ch4"] = -7.16 - Antc["ben"] = -52.36 - Antc["tol"] = -53.67 - Antc["dip"] = -70.42 - m.antc = Param(m.compon, initialize=Antc, default=0, doc="antoine coefficient C") - - Perm = {} - for i in m.compon: - Perm[i] = 0 - Perm["h2"] = 55.0e-06 - Perm["ch4"] = 2.3e-06 - - def Permset(m, compon): - """ - permeability [kg-mole/m**2-min-mpa] - converting unit for permeability from cc/cm**2-sec-cmHg to kg-mole/m**2-min-mpa - """ - return Perm[compon] * (1.0 / 22400.0) * 1.0e4 * 750.062 * 60.0 / 1000.0 - - m.perm = Param( - m.compon, - initialize=Permset, - default=0, - doc="permeability [kg-mole/m**2-min-mpa]", - ) - - Cbeta = {} - Cbeta["h2"] = 1.0003 - Cbeta["ch4"] = 1.0008 - Cbeta["dip"] = 1.0e04 - m.cbeta = Param( - m.compon, - initialize=Cbeta, - default=0, - doc="constant values (exp(beta)) in absorber", - ) - - Aabs = {} - Aabs["ben"] = 1.4 - Aabs["tol"] = 4.0 - m.aabs = Param(m.compon, initialize=Aabs, default=0, doc="absorption factors") - m.eps1 = Param(initialize=1e-4, doc="small number to avoid div. by zero") - - Heatrxn = {} - Heatrxn[1] = 50100.0 - Heatrxn[2] = 50100.0 - m.heatrxn = Param( - m.rct, initialize=Heatrxn, default=0, doc="heat of reaction [kj per kg-mol]" - ) - - F1comp = {} - F1comp["h2"] = 0.95 - F1comp["ch4"] = 0.05 - F1comp["dip"] = 0.00 - F1comp["ben"] = 0.00 - F1comp["tol"] = 0.00 - m.f1comp = Param( - m.compon, initialize=F1comp, default=0, doc="feedstock compositions (h2 feed)" - ) - - F66comp = {} - F66comp["tol"] = 1.0 - F66comp["h2"] = 0.00 - F66comp["ch4"] = 0.00 - F66comp["dip"] = 0.00 - F66comp["ben"] = 0.00 - m.f66comp = Param( - m.compon, initialize=F66comp, default=0, doc="feedstock compositions (tol feed)" - ) - - F67comp = {} - F67comp["tol"] = 1.0 - F67comp["h2"] = 0.00 - F67comp["ch4"] = 0.00 - F67comp["dip"] = 0.00 - F67comp["ben"] = 0.00 - m.f67comp = Param( - m.compon, initialize=F67comp, default=0, doc="feedstock compositions (tol feed)" - ) - - # # matching streams - m.ilabs = Set(initialize=[(1, 67)], doc="abs-stream (inlet liquid) matches") - m.olabs = Set(initialize=[(1, 68)], doc="abs-stream (outlet liquid) matches") - m.ivabs = Set(initialize=[(1, 63)], doc="abs-stream (inlet vapor) matches") - m.ovabs = Set(initialize=[(1, 64)], doc="abs-stream (outlet vapor) matches") - m.asolv = Set(initialize=[(1, "tol")], doc="abs-solvent component matches") - m.anorm = Set(initialize=[(1, "ben")], doc="abs-comp matches (normal model)") - m.asimp = Set( - initialize=[(1, "h2"), (1, "ch4"), (1, "dip")], - doc="abs-heavy component matches", - ) - - m.icomp = Set( - initialize=[(1, 5), (2, 59), (3, 64), (4, 56)], - doc="compressor-stream (inlet) matches", - ) - m.ocomp = Set( - initialize=[(1, 6), (2, 60), (3, 65), (4, 57)], - doc="compressor-stream (outlet) matches", - ) - - m.idist = Set( - initialize=[(1, 25), (2, 30), (3, 33)], doc="dist-stream (inlet) matches" - ) - m.vdist = Set( - initialize=[(1, 26), (2, 31), (3, 34)], doc="dist-stream (vapor) matches" - ) - m.ldist = Set( - initialize=[(1, 27), (2, 32), (3, 35)], doc="dist-stream (liquid) matches" - ) - m.dl = Set( - initialize=[(1, "h2"), (2, "ch4"), (3, "ben")], - doc="dist-light components matches", - ) - m.dlkey = Set( - initialize=[(1, "ch4"), (2, "ben"), (3, "tol")], - doc="dist-heavy key component matches", - ) - m.dhkey = Set( - initialize=[(1, "ben"), (2, "tol"), (3, "dip")], - doc="dist-heavy components matches", - ) - m.dh = Set( - initialize=[(1, "tol"), (1, "dip"), (2, "dip")], - doc="dist-key component matches", - ) - - i = list(m.dlkey) - q = list(m.dhkey) - dkeyset = i + q - m.dkey = Set(initialize=dkeyset, doc="dist-key component matches") - - m.iflsh = Set( - initialize=[(1, 17), (2, 46), (3, 39)], doc="flsh-stream (inlet) matches" - ) - m.vflsh = Set( - initialize=[(1, 18), (2, 47), (3, 40)], doc="flsh-stream (vapor) matches" - ) - m.lflsh = Set( - initialize=[(1, 19), (2, 48), (3, 41)], doc="flsh-stream (liquid) matches" - ) - m.fkey = Set( - initialize=[(1, "ch4"), (2, "ch4"), (3, "tol")], - doc="flash-key component matches", - ) - - m.ifurn = Set(initialize=[(1, 70)], doc="furn-stream (inlet) matches") - m.ofurn = Set(initialize=[(1, 9)], doc="furn-stream (outlet) matches") - - m.ihec = Set(initialize=[(1, 71), (2, 45)], doc="hec-stream (inlet) matches") - m.ohec = Set(initialize=[(1, 17), (2, 46)], doc="hec-stream (outlet) matches") - - m.iheh = Set( - initialize=[(1, 24), (2, 23), (3, 37), (4, 61)], - doc="heh-stream (inlet) matches", - ) - m.oheh = Set( - initialize=[(1, 25), (2, 44), (3, 38), (4, 73)], - doc="heh-stream (outlet) matches", - ) - - m.icexch = Set(initialize=[(1, 8)], doc="exch-cold stream (inlet) matches") - m.ocexch = Set(initialize=[(1, 70)], doc="exch-cold stream (outlet) matches") - m.ihexch = Set(initialize=[(1, 16)], doc="exch-hot stream (inlet) matches") - m.ohexch = Set(initialize=[(1, 71)], doc="exch-hot stream (outlet) matches") - - m.imemb = Set(initialize=[(1, 3), (2, 54)], doc="memb-stream (inlet) matches") - m.nmemb = Set( - initialize=[(1, 4), (2, 55)], doc="memb-stream (non-permeate) matches" - ) - m.pmemb = Set(initialize=[(1, 5), (2, 56)], doc="memb-stream (permeate) matches") - m.mnorm = Set( - initialize=[(1, "h2"), (1, "ch4"), (2, "h2"), (2, "ch4")], - doc="normal components", - ) - m.msimp = Set( - initialize=[ - (1, "ben"), - (1, "tol"), - (1, "dip"), - (2, "ben"), - (2, "tol"), - (2, "dip"), - ], - doc="simplified flux components", - ) - - m.imxr1 = Set( - initialize=[ - (1, 2), - (1, 6), - (2, 11), - (2, 13), - (3, 27), - (3, 48), - (4, 34), - (4, 40), - (5, 49), - (5, 50), - ], - doc="mixer-stream (inlet) matches", - ) - m.omxr1 = Set( - initialize=[(1, 7), (2, 14), (3, 30), (4, 42), (5, 51)], - doc="mixer-stream (outlet) matches", - ) - m.mxr1spl1 = Set( - initialize=[ - (1, 2, 2), - (1, 6, 3), - (2, 11, 10), - (2, 13, 12), - (3, 27, 24), - (3, 48, 23), - (4, 34, 33), - (4, 40, 37), - (5, 49, 23), - (5, 50, 24), - ], - doc="1-mxr-inlet 1-spl-outlet matches", - ) - - m.imxr = Set( - initialize=[ - (1, 7), - (1, 43), - (1, 66), - (1, 72), - (2, 15), - (2, 20), - (3, 21), - (3, 69), - (4, 51), - (4, 62), - (5, 57), - (5, 60), - (5, 65), - ], - doc="mixer-stream (inlet) matches", - ) - m.omxr = Set( - initialize=[(1, 8), (2, 16), (3, 22), (4, 63), (5, 72)], - doc="mixer-stream (outlet) matches ", - ) - - m.ipump = Set(initialize=[(1, 42), (2, 68)], doc="pump-stream (inlet) matches") - m.opump = Set(initialize=[(1, 43), (2, 69)], doc="pump-stream (outlet) matches") - - m.irct = Set(initialize=[(1, 10), (2, 12)], doc="reactor-stream (inlet) matches") - m.orct = Set(initialize=[(1, 11), (2, 13)], doc="reactor-stream (outlet) matches") - m.rkey = Set( - initialize=[(1, "tol"), (2, "tol")], doc="reactor-key component matches" - ) - - m.ispl1 = Set( - initialize=[(1, 1), (2, 9), (3, 22), (4, 32), (5, 52), (6, 58)], - doc="splitter-stream (inlet) matches", - ) - m.ospl1 = Set( - initialize=[ - (1, 2), - (1, 3), - (2, 10), - (2, 12), - (3, 23), - (3, 24), - (4, 33), - (4, 37), - (5, 53), - (5, 54), - (6, 59), - (6, 61), - ], - doc="splitter-stream (outlet) matches", - ) - - m.ispl = Set( - initialize=[(1, 19), (2, 18), (3, 26)], doc="splitter-stream (inlet) matches" - ) - m.ospl = Set( - initialize=[(1, 20), (1, 21), (2, 52), (2, 58), (3, 28), (3, 29)], - doc="splitter-stream (outlet) matches", - ) - - m.ival = Set( - initialize=[(1, 44), (2, 38), (3, 14), (4, 47), (5, 29), (6, 73)], - doc="exp.valve-stream (inlet) matches", - ) - m.oval = Set( - initialize=[(1, 45), (2, 39), (3, 15), (4, 49), (5, 50), (6, 62)], - doc="exp.valve-stream (outlet) matches", - ) - - # variables - - # absorber - m.nabs = Var( - m.abs, - within=NonNegativeReals, - bounds=(0, 40), - initialize=1, - doc="number of absorber trays", - ) - m.gamma = Var(m.abs, m.compon, within=Reals, initialize=1, doc="gamma") - m.beta = Var(m.abs, m.compon, within=Reals, initialize=1, doc="beta") - - # compressor - m.elec = Var( - m.comp, - within=NonNegativeReals, - bounds=(0, 100), - initialize=1, - doc="electricity requirement [kw]", - ) - m.presrat = Var( - m.comp, - within=NonNegativeReals, - bounds=(1, 8 / 3), - initialize=1, - doc="ratio of outlet to inlet pressure", - ) - - # distillation - m.nmin = Var( - m.dist, - within=NonNegativeReals, - initialize=1, - doc="minimum number of trays in column", - ) - m.ndist = Var( - m.dist, within=NonNegativeReals, initialize=1, doc="number of trays in column" - ) - m.rmin = Var( - m.dist, within=NonNegativeReals, initialize=1, doc="minimum reflux ratio" - ) - m.reflux = Var(m.dist, within=NonNegativeReals, initialize=1, doc="reflux ratio") - m.distp = Var( - m.dist, - within=NonNegativeReals, - initialize=1, - bounds=(0.1, 4.0), - doc="column pressure [mega-pascal]", - ) - m.avevlt = Var( - m.dist, within=NonNegativeReals, initialize=1, doc="average volatility" - ) - - # flash - m.flsht = Var( - m.flsh, within=NonNegativeReals, initialize=1, doc="flash temperature [100 k]" - ) - m.flshp = Var( - m.flsh, - within=NonNegativeReals, - initialize=1, - doc="flash pressure [mega-pascal]", - ) - m.eflsh = Var( - m.flsh, - m.compon, - within=NonNegativeReals, - bounds=(0, 1), - initialize=0.5, - doc="vapor phase recovery in flash", - ) - - # furnace - m.qfuel = Var( - m.furn, - within=NonNegativeReals, - bounds=(None, 10), - initialize=1, - doc="heating required [1.e+12 kj per yr]", - ) - # cooler - m.qc = Var( - m.hec, - within=NonNegativeReals, - bounds=(None, 10), - initialize=1, - doc="utility requirement [1.e+12 kj per yr]", - ) - # heater - m.qh = Var( - m.heh, - within=NonNegativeReals, - bounds=(None, 10), - initialize=1, - doc="utility requirement [1.e+12 kj per yr]", - ) - # exchanger - m.qexch = Var( - m.exch, - within=NonNegativeReals, - bounds=(None, 10), - initialize=1, - doc="heat exchanged [1.e+12 kj per yr]", - ) - # membrane - m.a = Var( - m.memb, - within=NonNegativeReals, - bounds=(100, 10000), - initialize=1, - doc="surface area for mass transfer [m**2]", - ) - # mixer(1 input) - m.mxr1p = Var( - m.mxr1, - within=NonNegativeReals, - bounds=(0.1, 4), - initialize=0, - doc="mixer temperature [100 k]", - ) - m.mxr1t = Var( - m.mxr1, - within=NonNegativeReals, - bounds=(3, 10), - initialize=0, - doc="mixer pressure [mega-pascal]", - ) - # mixer - m.mxrt = Var( - m.mxr, - within=NonNegativeReals, - bounds=(3.0, 10), - initialize=3, - doc="mixer temperature [100 k]", - ) - m.mxrp = Var( - m.mxr, - within=NonNegativeReals, - bounds=(0.1, 4.0), - initialize=3, - doc="mixer pressure [mega-pascal]", - ) - # reactor - m.rctt = Var( - m.rct, - within=NonNegativeReals, - bounds=(8.9427, 9.7760), - doc="reactor temperature [100 k]", - ) - m.rctp = Var( - m.rct, - within=NonNegativeReals, - bounds=(3.4474, 3.4474), - doc="reactor pressure [mega-pascal]", - ) - m.rctvol = Var( - m.rct, - within=NonNegativeReals, - bounds=(None, 200), - doc="reactor volume [cubic meter]", - ) - m.krct = Var( - m.rct, - within=NonNegativeReals, - initialize=1, - bounds=(0.0123471, 0.149543), - doc="rate constant", - ) - m.conv = Var( - m.rct, - m.compon, - within=NonNegativeReals, - bounds=(None, 0.973), - doc="conversion of key component", - ) - m.sel = Var( - m.rct, - within=NonNegativeReals, - bounds=(None, 0.9964), - doc="selectivity to benzene", - ) - m.consum = Var( - m.rct, - m.compon, - within=NonNegativeReals, - bounds=(0, 10000000000), - initialize=0, - doc="consumption rate of key", - ) - m.q = Var( - m.rct, - within=NonNegativeReals, - bounds=(0, 10000000000), - doc="heat removed [1.e+9 kj per yr]", - ) - # splitter (1 output) - m.spl1t = Var( - m.spl1, - within=PositiveReals, - bounds=(3.00, 10.00), - doc="splitter temperature [100 k]", - ) - m.spl1p = Var( - m.spl1, - within=PositiveReals, - bounds=(0.1, 4.0), - doc="splitter pressure [mega-pascal]", - ) - # splitter - m.splp = Var( - m.spl, within=Reals, bounds=(0.1, 4.0), doc="splitter pressure [mega-pascal]" - ) - m.splt = Var( - m.spl, within=Reals, bounds=(3.0, 10.0), doc="splitter temperature [100 k]" - ) - - # stream - def bound_f(m, stream): - """ - stream flowrates [kg-mole per min] - setting appropriate bounds for stream flowrates - """ - if stream in range(8, 19): - return (0, 50) - elif stream in [52, 54, 56, 57, 58, 59, 60, 70, 71, 72]: - return (0, 50) - else: - return (0, 10) - - m.f = Var( - m.str, - within=NonNegativeReals, - bounds=bound_f, - initialize=1, - doc="stream flowrates [kg-mole per min]", - ) - - def bound_fc(m, stream, compon): - """ - setting appropriate bounds for component flowrates - """ - if stream in range(8, 19) or stream in [52, 54, 56, 57, 58, 59, 60, 70, 71, 72]: - return (0, 30) - else: - return (0, 10) - - m.fc = Var( - m.str, - m.compon, - within=Reals, - bounds=bound_fc, - initialize=1, - doc="component flowrates [kg-mole per min]", - ) - m.p = Var( - m.str, - within=NonNegativeReals, - bounds=(0.1, 4.0), - initialize=3.0, - doc="stream pressure [mega-pascal]", - ) - m.t = Var( - m.str, - within=NonNegativeReals, - bounds=(3.0, 10.0), - initialize=3.0, - doc="stream temperature [100 k]", - ) - m.vp = Var( - m.str, - m.compon, - within=NonNegativeReals, - initialize=1, - bounds=(0, 10), - doc="vapor pressure [mega-pascal]", - ) - - def boundsofe(m): - """ - setting appropriate bounds for split fraction - """ - if i == 20: - return (None, 0.5) - elif i == 21: - return (0.5, 1.0) - else: - return (None, 1.0) - - m.e = Var(m.str, within=NonNegativeReals, bounds=boundsofe, doc="split fraction") - - # obj function constant term - m.const = Param(initialize=22.5, doc="constant term in obj fcn") - - # ## setting variable bounds - - m.q[2].setub(100) - for rct in m.rct: - m.conv[rct, "tol"].setub(0.973) - m.sel.setub(1.0 - 0.0036) - m.reflux[1].setlb(0.02 * 1.2) - m.reflux[1].setub(0.10 * 1.2) - m.reflux[2].setlb(0.50 * 1.2) - m.reflux[2].setub(2.00 * 1.2) - m.reflux[3].setlb(0.02 * 1.2) - m.reflux[3].setub(0.1 * 1.2) - m.nmin[1].setlb(0) - m.nmin[1].setub(4) - m.nmin[2].setlb(8) - m.nmin[2].setub(14) - m.nmin[3].setlb(0) - m.nmin[3].setub(4) - m.ndist[1].setlb(0) - m.ndist[1].setub(4 * 2 / m.disteff) - m.ndist[3].setlb(0) - m.ndist[3].setub(4 * 2 / m.disteff) - m.ndist[2].setlb(8 * 2 / m.disteff) - m.ndist[2].setub(14 * 2 / m.disteff) - m.rmin[1].setlb(0.02) - m.rmin[1].setub(0.10) - m.rmin[2].setlb(0.50) - m.rmin[2].setub(2.00) - m.rmin[3].setlb(0.02) - m.rmin[3].setub(0.1) - m.distp[1].setub(1.0200000000000002) - m.distp[1].setlb(1.0200000000000002) - m.distp[2].setub(0.4) - m.distp[3].setub(0.250) - m.t[26].setlb(3.2) - m.t[26].setub(3.2) - for i in range(49, 52): - m.t[i].setlb(2.0) - m.t[27].setlb( - ( - m.antb["ben"] / (m.anta["ben"] - log(m.distp[1].lb * 7500.6168)) - - m.antc["ben"] - ) - / 100.0 - ) - m.t[27].setub( - ( - m.antb["ben"] / (m.anta["ben"] - log(m.distp[1].ub * 7500.6168)) - - m.antc["ben"] - ) - / 100.0 - ) - m.t[31].setlb( - ( - m.antb["ben"] / (m.anta["ben"] - log(m.distp[2].lb * 7500.6168)) - - m.antc["ben"] - ) - / 100.0 - ) - m.t[31].setub( - ( - m.antb["ben"] / (m.anta["ben"] - log(m.distp[2].ub * 7500.6168)) - - m.antc["ben"] - ) - / 100.0 - ) - m.t[32].setlb( - ( - m.antb["tol"] / (m.anta["tol"] - log(m.distp[2].lb * 7500.6168)) - - m.antc["tol"] - ) - / 100.0 - ) - m.t[32].setub( - ( - m.antb["tol"] / (m.anta["tol"] - log(m.distp[2].ub * 7500.6168)) - - m.antc["tol"] - ) - / 100.0 - ) - m.t[34].setlb( - ( - m.antb["tol"] / (m.anta["tol"] - log(m.distp[3].lb * 7500.6168)) - - m.antc["tol"] - ) - / 100.0 - ) - m.t[34].setub( - ( - m.antb["tol"] / (m.anta["tol"] - log(m.distp[3].ub * 7500.6168)) - - m.antc["tol"] - ) - / 100.0 - ) - m.t[35].setlb( - ( - m.antb["dip"] / (m.anta["dip"] - log(m.distp[3].lb * 7500.6168)) - - m.antc["dip"] - ) - / 100.0 - ) - m.t[35].setub( - ( - m.antb["dip"] / (m.anta["dip"] - log(m.distp[3].ub * 7500.6168)) - - m.antc["dip"] - ) - / 100.0 - ) - - # absorber - m.beta[1, "ben"].setlb(0.00011776) - m.beta[1, "ben"].setub(5.72649) - m.beta[1, "tol"].setlb(0.00018483515) - m.beta[1, "tol"].setub(15) - m.gamma[1, "tol"].setlb( - log( - (1 - m.aabs["tol"] ** (m.nabs[1].lb * m.abseff + m.eps1)) - / (1 - m.aabs["tol"]) - ) - ) - m.gamma[1, "tol"].setub( - min( - 15, - log( - (1 - m.aabs["tol"] ** (m.nabs[1].ub * m.abseff + m.eps1)) - / (1 - m.aabs["tol"]) - ), - ) - ) - for abso in m.abs: - for compon in m.compon: - m.beta[abso, compon].setlb( - log( - (1 - m.aabs[compon] ** (m.nabs[1].lb * m.abseff + m.eps1 + 1)) - / (1 - m.aabs[compon]) - ) - ) - m.beta[abso, compon].setub( - min( - 15, - log( - (1 - m.aabs[compon] ** (m.nabs[1].ub * m.abseff + m.eps1 + 1)) - / (1 - m.aabs[compon]) - ), - ) - ) - m.t[67].setlb(3.0) - m.t[67].setub(3.0) - for compon in m.compon: - m.vp[67, compon].setlb( - (1.0 / 7500.6168) - * exp( - m.anta[compon] - - m.antb[compon] / (value(m.t[67]) * 100.0 + m.antc[compon]) - ) - ) - m.vp[67, compon].setub( - (1.0 / 7500.6168) - * exp( - m.anta[compon] - - m.antb[compon] / (value(m.t[67]) * 100.0 + m.antc[compon]) - ) - ) - - flashdata_file = os.path.join(dir_path, "flashdata.csv") - flash = pd.read_csv(flashdata_file, header=0) - number = flash.iloc[:, [4]].dropna().values - two_digit_number = flash.iloc[:, [0]].dropna().values - two_digit_compon = flash.iloc[:, [1]].dropna().values - for i in range(len(two_digit_number)): - m.eflsh[two_digit_number[i, 0], two_digit_compon[i, 0]].setlb( - flash.iloc[:, [2]].dropna().values[i, 0] - ) - m.eflsh[two_digit_number[i, 0], two_digit_compon[i, 0]].setub( - flash.iloc[:, [3]].dropna().values[i, 0] - ) - for i in range(len(number)): - m.flshp[number[i, 0]].setlb(flash.iloc[:, [5]].dropna().values[i, 0]) - m.flshp[number[i, 0]].setub(flash.iloc[:, [6]].dropna().values[i, 0]) - m.flsht[number[i, 0]].setlb(flash.iloc[:, [7]].dropna().values[i, 0]) - m.flsht[number[i, 0]].setub(flash.iloc[:, [8]].dropna().values[i, 0]) - m.t[19].setlb(m.flsht[1].lb) - m.t[19].setub(m.flsht[1].ub) - m.t[48].setlb(m.flsht[2].lb) - m.t[48].setub(m.flsht[2].ub) - m.t[41].setlb(m.t[32].lb) - m.t[41].setub(m.flsht[3].ub) - m.t[1].setlb(3.0) - m.t[1].setub(3.0) - m.t[16].setub(8.943) - m.t[66].setlb(3.0) - - for stream in m.str: - for compon in m.compon: - m.vp[stream, compon].setlb( - (1.0 / 7500.6168) - * exp( - m.anta[compon] - - m.antb[compon] / (m.t[stream].lb * 100.0 + m.antc[compon]) - ) - ) - m.vp[stream, compon].setub( - (1.0 / 7500.6168) - * exp( - m.anta[compon] - - m.antb[compon] / (m.t[stream].ub * 100.0 + m.antc[compon]) - ) - ) - - m.p[1].setub(3.93) - m.p[1].setlb(3.93) - m.f[31].setlb(2.08) - m.f[31].setub(2.08) - m.p[66].setub(3.93) - m.p[66].setub(3.93) - - # distillation bounds - for dist in m.dist: - for stream in m.str: - for compon in m.compon: - if (dist, stream) in m.ldist and (dist, compon) in m.dlkey: - m.avevlt[dist].setlb(m.vp[stream, compon].ub) - if (dist, stream) in m.ldist and (dist, compon) in m.dhkey: - m.avevlt[dist].setlb(m.avevlt[dist].lb / m.vp[stream, compon].ub) - for dist in m.dist: - for stream in m.str: - for compon in m.compon: - if (dist, stream) in m.vdist and (dist, compon) in m.dlkey: - m.avevlt[dist].setub(m.vp[stream, compon].lb) - if (dist, stream) in m.vdist and (dist, compon) in m.dhkey: - m.avevlt[dist].setub(m.avevlt[dist].ub / m.vp[stream, compon].lb) - - # ## initialization procedure - - # flash1 - m.eflsh[1, "h2"] = 0.995 - m.eflsh[1, "ch4"] = 0.99 - m.eflsh[1, "ben"] = 0.04 - m.eflsh[1, "tol"] = 0.01 - m.eflsh[1, "dip"] = 0.0001 - - # compressor - m.distp[1] = 1.02 - m.distp[2] = 0.1 - m.distp[3] = 0.1 - m.qexch[1] = 0.497842 - m.elec[1] = 0 - m.elec[2] = 12.384 - m.elec[3] = 0 - m.elec[4] = 28.7602 - m.presrat[1] = 1 - m.presrat[2] = 1.04552 - m.presrat[3] = 1.36516 - m.presrat[4] = 1.95418 - m.qfuel[1] = 0.0475341 - m.q[2] = 54.3002 - - file_1 = os.path.join(dir_path, "GAMS_init_stream_data.csv") - stream = pd.read_csv(file_1, usecols=[0]) - data = pd.read_csv(file_1, usecols=[1]) - temp = pd.read_csv(file_1, usecols=[3]) - flow = pd.read_csv(file_1, usecols=[4]) - e = pd.read_csv(file_1, usecols=[5]) - - for i in range(len(stream)): - m.p[stream.to_numpy()[i, 0]] = data.to_numpy()[i, 0] - for i in range(72): - m.t[stream.to_numpy()[i, 0]] = temp.to_numpy()[i, 0] - m.f[stream.to_numpy()[i, 0]] = flow.to_numpy()[i, 0] - m.e[stream.to_numpy()[i, 0]] = e.to_numpy()[i, 0] - - file_2 = os.path.join(dir_path, "GAMS_init_stream_compon_data.csv") - streamfc = pd.read_csv(file_2, usecols=[0]) - comp = pd.read_csv(file_2, usecols=[1]) - fc = pd.read_csv(file_2, usecols=[2]) - streamvp = pd.read_csv(file_2, usecols=[3]) - compvp = pd.read_csv(file_2, usecols=[4]) - vp = pd.read_csv(file_2, usecols=[5]) - - for i in range(len(streamfc)): - m.fc[streamfc.to_numpy()[i, 0], comp.to_numpy()[i, 0]] = fc.to_numpy()[i, 0] - m.vp[streamvp.to_numpy()[i, 0], compvp.to_numpy()[i, 0]] = vp.to_numpy()[i, 0] - - file_3 = os.path.join(dir_path, "GAMS_init_data.csv") - stream3 = pd.read_csv(file_3, usecols=[0]) - a = pd.read_csv(file_3, usecols=[1]) - avevlt = pd.read_csv(file_3, usecols=[3]) - comp1 = pd.read_csv(file_3, usecols=[5]) - beta = pd.read_csv(file_3, usecols=[6]) - consum = pd.read_csv(file_3, usecols=[9]) - conv = pd.read_csv(file_3, usecols=[12]) - disp = pd.read_csv(file_3, usecols=[14]) - stream4 = pd.read_csv(file_3, usecols=[15]) - comp2 = pd.read_csv(file_3, usecols=[16]) - eflsh = pd.read_csv(file_3, usecols=[17]) - flshp = pd.read_csv(file_3, usecols=[19]) - flsht = pd.read_csv(file_3, usecols=[21]) - krct = pd.read_csv(file_3, usecols=[23]) - mxrp = pd.read_csv(file_3, usecols=[25]) - ndist = pd.read_csv(file_3, usecols=[27]) - nmin = pd.read_csv(file_3, usecols=[29]) - qc = pd.read_csv(file_3, usecols=[31]) - qh = pd.read_csv(file_3, usecols=[33]) - rctp = pd.read_csv(file_3, usecols=[35]) - rctt = pd.read_csv(file_3, usecols=[37]) - rctvol = pd.read_csv(file_3, usecols=[39]) - reflux = pd.read_csv(file_3, usecols=[41]) - rmin = pd.read_csv(file_3, usecols=[43]) - sel = pd.read_csv(file_3, usecols=[45]) - spl1p = pd.read_csv(file_3, usecols=[47]) - spl1t = pd.read_csv(file_3, usecols=[49]) - splp = pd.read_csv(file_3, usecols=[51]) - splt = pd.read_csv(file_3, usecols=[53]) - - for i in range(2): - m.rctp[i + 1] = rctp.to_numpy()[i, 0] - m.rctt[i + 1] = rctt.to_numpy()[i, 0] - m.rctvol[i + 1] = rctvol.to_numpy()[i, 0] - m.sel[i + 1] = sel.to_numpy()[i, 0] - m.krct[i + 1] = krct.to_numpy()[i, 0] - m.consum[i + 1, "tol"] = consum.to_numpy()[i, 0] - m.conv[i + 1, "tol"] = conv.to_numpy()[i, 0] - m.a[stream3.to_numpy()[i, 0]] = a.to_numpy()[i, 0] - m.qc[i + 1] = qc.to_numpy()[i, 0] - for i in range(3): - m.avevlt[i + 1] = avevlt.to_numpy()[i, 0] - m.distp[i + 1] = disp.to_numpy()[i, 0] - m.flshp[i + 1] = flshp.to_numpy()[i, 0] - m.flsht[i + 1] = flsht.to_numpy()[i, 0] - m.ndist[i + 1] = ndist.to_numpy()[i, 0] - m.nmin[i + 1] = nmin.to_numpy()[i, 0] - m.reflux[i + 1] = reflux.to_numpy()[i, 0] - m.rmin[i + 1] = rmin.to_numpy()[i, 0] - m.splp[i + 1] = splp.to_numpy()[i, 0] - m.splt[i + 1] = splt.to_numpy()[i, 0] - for i in range(5): - m.beta[1, comp1.to_numpy()[i, 0]] = beta.to_numpy()[i, 0] - m.mxrp[i + 1] = mxrp.to_numpy()[i, 0] - for i in range(4): - m.qh[i + 1] = qh.to_numpy()[i, 0] - for i in range(len(stream4)): - m.eflsh[stream4.to_numpy()[i, 0], comp2.to_numpy()[i, 0]] = eflsh.to_numpy()[ - i, 0 - ] - for i in range(6): - m.spl1p[i + 1] = spl1p.to_numpy()[i, 0] - m.spl1t[i + 1] = spl1t.to_numpy()[i, 0] - - # ## constraints - m.specrec = Constraint( - expr=m.fc[72, "h2"] >= 0.5 * m.f[72], doc="specification on h2 recycle" - ) - m.specprod = Constraint( - expr=m.fc[31, "ben"] >= 0.9997 * m.f[31], - doc="specification on benzene production", - ) - - def Fbal(_m, stream): - return m.f[stream] == sum(m.fc[stream, compon] for compon in m.compon) - - m.fbal = Constraint(m.str, rule=Fbal, doc="flow balance") - - def H2feed(m, compon): - return m.fc[1, compon] == m.f[1] * m.f1comp[compon] - - m.h2feed = Constraint(m.compon, rule=H2feed, doc="h2 feed composition") - - def Tolfeed(_m, compon): - return m.fc[66, compon] == m.f[66] * m.f66comp[compon] - - m.tolfeed = Constraint(m.compon, rule=Tolfeed, doc="toluene feed composition") - - def Tolabs(_m, compon): - return m.fc[67, compon] == m.f[67] * m.f67comp[compon] - - m.tolabs = Constraint(m.compon, rule=Tolabs, doc="toluene absorber composition") - - def build_absorber(b, absorber): - """ - Functions relevant to the absorber block - - Parameters - ---------- - b : Pyomo Block - absorber block - absorber : int - Index of the absorber - """ - - def Absfact(_m, i, compon): - """ - Absorption factor equation - sum of flowrates of feed components = sum of flowrates of vapor components * absorption factor * sum of vapor pressures - - """ - if (i, compon) in m.anorm: - return sum( - m.f[stream] * m.p[stream] for (absb, stream) in m.ilabs if absb == i - ) == sum( - m.f[stream] for (absc, stream) in m.ivabs if absc == i - ) * m.aabs[ - compon - ] * sum( - m.vp[stream, compon] for (absd, stream) in m.ilabs if absd == i - ) - return Constraint.Skip - - b.absfact = Constraint( - [absorber], m.compon, rule=Absfact, doc="absorption factor equation" - ) - - def Gameqn(_m, i, compon): - # definition of gamma - if (i, compon) in m.asolv: - return m.gamma[i, compon] == log( - (1 - m.aabs[compon] ** (m.nabs[i] * m.abseff + m.eps1)) - / (1 - m.aabs[compon]) - ) - return Constraint.Skip - - b.gameqn = Constraint( - [absorber], m.compon, rule=Gameqn, doc="definition of gamma" - ) - - def Betaeqn(_m, i, compon): - # definition of beta - if (i, compon) not in m.asimp: - return m.beta[i, compon] == log( - (1 - m.aabs[compon] ** (m.nabs[i] * m.abseff + 1)) - / (1 - m.aabs[compon]) - ) - return Constraint.Skip - - b.betaeqn = Constraint( - [absorber], m.compon, rule=Betaeqn, doc="definition of beta" - ) - - def Abssvrec(_m, i, compon): - # recovery of solvent - if (i, compon) in m.asolv: - return sum(m.fc[stream, compon] for (i, stream) in m.ovabs) * exp( - m.beta[i, compon] - ) == sum(m.fc[stream, compon] for (i_, stream) in m.ivabs) + exp( - m.gamma[i, compon] - ) * sum( - m.fc[stream, compon] for (i_, stream) in m.ilabs - ) - return Constraint.Skip - - b.abssvrec = Constraint( - [absorber], m.compon, rule=Abssvrec, doc="recovery of solvent" - ) - - def Absrec(_m, i, compon): - # recovery of non-solvent - if (i, compon) in m.anorm: - return sum(m.fc[i, compon] for (abs, i) in m.ovabs) * exp( - m.beta[i, compon] - ) == sum(m.fc[i, compon] for (abs, i) in m.ivabs) - return Constraint.Skip - - b.absrec = Constraint( - [absorber], m.compon, rule=Absrec, doc="recovery of non-solvent" - ) - - def abssimp(_m, absorb, compon): - # recovery of simplified components - if (absorb, compon) in m.asimp: - return ( - sum(m.fc[i, compon] for (absorb, i) in m.ovabs) - == sum(m.fc[i, compon] for (absorb, i) in m.ivabs) / m.cbeta[compon] - ) - return Constraint.Skip - - b.abssimp = Constraint( - [absorber], m.compon, rule=abssimp, doc="recovery of simplified components" - ) - - def Abscmb(_m, i, compon): - return sum(m.fc[stream, compon] for (i, stream) in m.ilabs) + sum( - m.fc[stream, compon] for (i, stream) in m.ivabs - ) == sum(m.fc[stream, compon] for (i, stream) in m.olabs) + sum( - m.fc[stream, compon] for (i, stream) in m.ovabs - ) - - b.abscmb = Constraint( - [absorber], - m.compon, - rule=Abscmb, - doc="overall component mass balance in absorber", - ) - - def Abspl(_m, i): - return sum(m.p[stream] for (_, stream) in m.ilabs) == sum( - m.p[stream] for (_, stream) in m.olabs - ) - - b.abspl = Constraint( - [absorber], rule=Abspl, doc="pressure relation for liquid in absorber" - ) - - def Abstl(_m, i): - return sum(m.t[stream] for (_, stream) in m.ilabs) == sum( - m.t[stream] for (_, stream) in m.olabs - ) - - b.abstl = Constraint( - [absorber], rule=Abstl, doc="temperature relation for liquid in absorber" - ) - - def Abspv(_m, i): - return sum(m.p[stream] for (_, stream) in m.ivabs) == sum( - m.p[stream] for (_, stream) in m.ovabs - ) - - b.abspv = Constraint( - [absorber], rule=Abspv, doc="pressure relation for vapor in absorber" - ) - - def Abspin(_m, i): - return sum(m.p[stream] for (_, stream) in m.ilabs) == sum( - m.p[stream] for (_, stream) in m.ivabs - ) - - b.absp = Constraint( - [absorber], rule=Abspin, doc="pressure relation at inlet of absorber" - ) - - def Absttop(_m, i): - return sum(m.t[stream] for (_, stream) in m.ilabs) == sum( - m.t[stream] for (_, stream) in m.ovabs - ) - - b.abst = Constraint( - [absorber], rule=Absttop, doc="temperature relation at top of absorber" - ) - - def build_compressor(b, comp): - """ - Functions relevant to the compressor block - - Parameters - ---------- - b : Pyomo Block - compressor block - comp : int - Index of the compressor - """ - - def Compcmb(_m, comp1, compon): - if comp1 == comp: - return sum( - m.fc[stream, compon] - for (comp_, stream) in m.ocomp - if comp_ == comp1 - ) == sum( - m.fc[stream, compon] - for (comp_, stream) in m.icomp - if comp_ == comp1 - ) - return Constraint.Skip - - b.compcmb = Constraint( - [comp], m.compon, rule=Compcmb, doc="component balance in compressor" - ) - - def Comphb(_m, comp1): - if comp1 == comp: - return sum( - m.t[stream] for (_, stream) in m.ocomp if _ == comp - ) == m.presrat[comp] * sum( - m.t[stream] for (_, stream) in m.icomp if _ == comp - ) - return Constraint.Skip - - b.comphb = Constraint([comp], rule=Comphb, doc="heat balance in compressor") - - def Compelec(_m, comp_): - if comp_ == comp: - return m.elec[comp_] == m.alpha * (m.presrat[comp_] - 1) * sum( - 100.0 - * m.t[stream] - * m.f[stream] - / 60.0 - * (1.0 / m.compeff) - * (m.gam / (m.gam - 1.0)) - for (comp1, stream) in m.icomp - if comp_ == comp1 - ) - return Constraint.Skip - - b.compelec = Constraint( - [comp], rule=Compelec, doc="energy balance in compressor" - ) - - def Ratio(_m, comp_): - if comp == comp_: - return m.presrat[comp_] ** (m.gam / (m.gam - 1.0)) == sum( - m.p[stream] for (comp1, stream) in m.ocomp if comp_ == comp1 - ) / sum(m.p[stream] for (comp1, stream) in m.icomp if comp1 == comp_) - return Constraint.Skip - - b.ratio = Constraint( - [comp], rule=Ratio, doc="pressure ratio (out to in) in compressor" - ) - - m.vapor_pressure_unit_match = Param( - initialize=7500.6168, - doc="unit match coefficient for vapor pressure calculation", - ) - m.actual_reflux_ratio = Param(initialize=1.2, doc="actual reflux ratio coefficient") - m.recovery_specification_coefficient = Param( - initialize=0.05, doc="recovery specification coefficient" - ) - - def build_distillation(b, dist): - """ - Functions relevant to the distillation block - - Parameters - ---------- - b : Pyomo Block - distillation block - dist : int - Index of the distillation column - """ - - def Antdistb(_m, dist_, stream, compon): - if ( - (dist_, stream) in m.ldist - and (dist_, compon) in m.dkey - and dist_ == dist - ): - return log( - m.vp[stream, compon] * m.vapor_pressure_unit_match - ) == m.anta[compon] - m.antb[compon] / ( - m.t[stream] * 100.0 + m.antc[compon] - ) - return Constraint.Skip - - b.antdistb = Constraint( - [dist], - m.str, - m.compon, - rule=Antdistb, - doc="vapor pressure correlation (bot)", - ) - - def Antdistt(_m, dist_, stream, compon): - if ( - (dist_, stream) in m.vdist - and (dist_, compon) in m.dkey - and dist == dist_ - ): - return log( - m.vp[stream, compon] * m.vapor_pressure_unit_match - ) == m.anta[compon] - m.antb[compon] / ( - m.t[stream] * 100.0 + m.antc[compon] - ) - return Constraint.Skip - - b.antdistt = Constraint( - [dist], - m.str, - m.compon, - rule=Antdistt, - doc="vapor pressure correlation (top)", - ) - - def Relvol(_m, dist_): - if dist == dist_: - divided1 = sum( - sum( - m.vp[stream, compon] - for (dist_, compon) in m.dlkey - if dist_ == dist - ) - / sum( - m.vp[stream, compon] - for (dist_, compon) in m.dhkey - if dist_ == dist - ) - for (dist_, stream) in m.vdist - if dist_ == dist - ) - divided2 = sum( - sum( - m.vp[stream, compon] - for (dist_, compon) in m.dlkey - if dist_ == dist - ) - / sum( - m.vp[stream, compon] - for (dist_, compon) in m.dhkey - if dist_ == dist - ) - for (dist_, stream) in m.ldist - if dist_ == dist - ) - return m.avevlt[dist] == sqrt(divided1 * divided2) - return Constraint.Skip - - b.relvol = Constraint([dist], rule=Relvol, doc="average relative volatility") - - def Undwood(_m, dist_): - # minimum reflux ratio from Underwood equation - if dist_ == dist: - return sum( - m.fc[stream, compon] - for (dist1, compon) in m.dlkey - if dist1 == dist_ - for (dist1, stream) in m.idist - if dist1 == dist_ - ) * m.rmin[dist_] * (m.avevlt[dist_] - 1) == sum( - m.f[stream] for (dist1, stream) in m.idist if dist1 == dist_ - ) - return Constraint.Skip - - b.undwood = Constraint( - [dist], rule=Undwood, doc="minimum reflux ratio equation" - ) - - def Actreflux(_m, dist_): - # actual reflux ratio (heuristic) - if dist_ == dist: - return m.reflux[dist_] == m.actual_reflux_ratio * m.rmin[dist_] - return Constraint.Skip - - b.actreflux = Constraint([dist], rule=Actreflux, doc="actual reflux ratio") - - def Fenske(_m, dist_): - # minimum number of trays from Fenske equation - if dist == dist_: - sum1 = sum( - (m.f[stream] + m.eps1) / (m.fc[stream, compon] + m.eps1) - for (dist1, compon) in m.dhkey - if dist1 == dist_ - for (dist1, stream) in m.vdist - if dist1 == dist_ - ) - sum2 = sum( - (m.f[stream] + m.eps1) / (m.fc[stream, compon] + m.eps1) - for (dist1, compon) in m.dlkey - if dist1 == dist_ - for (dist1, stream) in m.ldist - if dist1 == dist_ - ) - return m.nmin[dist_] * log(m.avevlt[dist_]) == log(sum1 * sum2) - return Constraint.Skip - - b.fenske = Constraint([dist], rule=Fenske, doc="minimum number of trays") - - def Acttray(_m, dist_): - # actual number of trays (gillilands approximation) - if dist == dist_: - return m.ndist[dist_] == m.nmin[dist_] * 2.0 / m.disteff - return Constraint.Skip - - b.acttray = Constraint([dist], rule=Acttray, doc="actual number of trays") - - def Distspec(_m, dist_, stream, compon): - if ( - (dist_, stream) in m.vdist - and (dist_, compon) in m.dhkey - and dist_ == dist - ): - return m.fc[ - stream, compon - ] <= m.recovery_specification_coefficient * sum( - m.fc[str2, compon] for (dist_, str2) in m.idist if dist == dist_ - ) - return Constraint.Skip - - b.distspec = Constraint( - [dist], m.str, m.compon, rule=Distspec, doc="recovery specification" - ) - - def Distheav(_m, dist_, compon): - if (dist_, compon) in m.dh and dist == dist_: - return sum( - m.fc[str2, compon] for (dist_, str2) in m.idist if dist_ == dist - ) == sum( - m.fc[str2, compon] for (dist_, str2) in m.ldist if dist_ == dist - ) - return Constraint.Skip - - b.distheav = Constraint([dist], m.compon, rule=Distheav, doc="heavy components") - - def Distlite(_m, dist_, compon): - if (dist_, compon) in m.dl and dist_ == dist: - return sum( - m.fc[str2, compon] for (dist_, str2) in m.idist if dist == dist_ - ) == sum( - m.fc[str2, compon] for (dist_, str2) in m.vdist if dist == dist_ - ) - return Constraint.Skip - - b.distlite = Constraint([dist], m.compon, rule=Distlite, doc="light components") - - def Distpi(_m, dist_, stream): - if (dist_, stream) in m.idist and dist_ == dist: - return m.distp[dist_] <= m.p[stream] - return Constraint.Skip - - b.distpi = Constraint([dist], m.str, rule=Distpi, doc="inlet pressure relation") - - def Distvpl(_m, dist_, stream): - if (dist_, stream) in m.ldist and dist == dist_: - return m.distp[dist_] == sum( - m.vp[stream, compon] for (dist_, compon) in m.dhkey if dist_ == dist - ) - return Constraint.Skip - - b.distvpl = Constraint( - [dist], m.str, rule=Distvpl, doc="bottom vapor pressure relation" - ) - - def Distvpv(_m, dist_, stream): - if dist > 1 and (dist, stream) in m.vdist and dist_ == dist: - return m.distp[dist_] == sum( - m.vp[stream, compon] for (dist_, compon) in m.dlkey if dist_ == dist - ) - return Constraint.Skip - - b.distvpv = Constraint( - [dist], m.str, rule=Distvpv, doc="top vapor pressure relation" - ) - - def Distpl(_m, dist_, stream): - if (dist_, stream) in m.ldist and dist_ == dist: - return m.distp[dist_] == m.p[stream] - return Constraint.Skip - - b.distpl = Constraint( - [dist], m.str, rule=Distpl, doc="outlet pressure relation (liquid)" - ) - - def Distpv(_m, dist_, stream): - if (dist_, stream) in m.vdist and dist == dist_: - return m.distp[dist_] == m.p[stream] - return Constraint.Skip - - b.distpv = Constraint( - [dist], m.str, rule=Distpv, doc="outlet pressure relation (vapor)" - ) - - def Distcmb(_m, dist_, compon): - if dist_ == dist: - return sum( - m.fc[stream, compon] - for (dist1, stream) in m.idist - if dist1 == dist_ - ) == sum( - m.fc[stream, compon] - for (dist1, stream) in m.vdist - if dist1 == dist_ - ) + sum( - m.fc[stream, compon] - for (dist1, stream) in m.ldist - if dist1 == dist_ - ) - return Constraint.Skip - - b.distcmb = Constraint( - [dist], - m.compon, - rule=Distcmb, - doc="component mass balance in distillation column", - ) - - def build_flash(b, flsh): - """ - Functions relevant to the flash block - - Parameters - ---------- - b : Pyomo Block - flash block - flsh : int - Index of the flash - """ - - def Flshcmb(_m, flsh_, compon): - if flsh_ in m.flsh and compon in m.compon and flsh_ == flsh: - return sum( - m.fc[stream, compon] - for (flsh1, stream) in m.iflsh - if flsh1 == flsh_ - ) == sum( - m.fc[stream, compon] - for (flsh1, stream) in m.vflsh - if flsh1 == flsh_ - ) + sum( - m.fc[stream, compon] - for (flsh1, stream) in m.lflsh - if flsh1 == flsh_ - ) - return Constraint.Skip - - b.flshcmb = Constraint( - [flsh], m.compon, rule=Flshcmb, doc="component mass balance in flash" - ) - - def Antflsh(_m, flsh_, stream, compon): - if (flsh_, stream) in m.lflsh and flsh_ == flsh: - return log( - m.vp[stream, compon] * m.vapor_pressure_unit_match - ) == m.anta[compon] - m.antb[compon] / ( - m.t[stream] * 100.0 + m.antc[compon] - ) - return Constraint.Skip - - b.antflsh = Constraint( - [flsh], m.str, m.compon, rule=Antflsh, doc="flash pressure relation" - ) - - def Flshrec(_m, flsh_, stream, compon): - if (flsh_, stream) in m.lflsh and flsh_ == flsh: - return ( - sum( - m.eflsh[flsh1, compon2] - for (flsh1, compon2) in m.fkey - if flsh1 == flsh_ - ) - * ( - m.eflsh[flsh_, compon] - * sum( - m.vp[stream, compon2] - for (flsh1, compon2) in m.fkey - if flsh_ == flsh1 - ) - + (1.0 - m.eflsh[flsh_, compon]) * m.vp[stream, compon] - ) - == sum( - m.vp[stream, compon2] - for (flsh1, compon2) in m.fkey - if flsh_ == flsh1 - ) - * m.eflsh[flsh_, compon] - ) - return Constraint.Skip - - b.flshrec = Constraint( - [flsh], m.str, m.compon, rule=Flshrec, doc="vapor recovery relation" - ) - - def Flsheql(_m, flsh_, compon): - if flsh in m.flsh and compon in m.compon and flsh_ == flsh: - return ( - sum( - m.fc[stream, compon] - for (flsh1, stream) in m.vflsh - if flsh1 == flsh_ - ) - == sum( - m.fc[stream, compon] - for (flsh1, stream) in m.iflsh - if flsh1 == flsh_ - ) - * m.eflsh[flsh, compon] - ) - return Constraint.Skip - - b.flsheql = Constraint( - [flsh], m.compon, rule=Flsheql, doc="equilibrium relation" - ) - - def Flshpr(_m, flsh_, stream): - if (flsh_, stream) in m.lflsh and flsh_ == flsh: - return m.flshp[flsh_] * m.f[stream] == sum( - m.vp[stream, compon] * m.fc[stream, compon] for compon in m.compon - ) - return Constraint.Skip - - b.flshpr = Constraint([flsh], m.str, rule=Flshpr, doc="flash pressure relation") - - def Flshpi(_m, flsh_, stream): - if (flsh_, stream) in m.iflsh and flsh_ == flsh: - return m.flshp[flsh_] == m.p[stream] - return Constraint.Skip - - b.flshpi = Constraint([flsh], m.str, rule=Flshpi, doc="inlet pressure relation") - - def Flshpl(_m, flsh_, stream): - if (flsh_, stream) in m.lflsh and flsh_ == flsh: - return m.flshp[flsh_] == m.p[stream] - return Constraint.Skip - - b.flshpl = Constraint( - [flsh], m.str, rule=Flshpl, doc="outlet pressure relation (liquid)" - ) - - def Flshpv(_m, flsh_, stream): - if (flsh_, stream) in m.vflsh and flsh_ == flsh: - return m.flshp[flsh_] == m.p[stream] - return Constraint.Skip - - b.flshpv = Constraint( - [flsh], m.str, rule=Flshpv, doc="outlet pressure relation (vapor)" - ) - - def Flshti(_m, flsh_, stream): - if (flsh_, stream) in m.iflsh and flsh_ == flsh: - return m.flsht[flsh_] == m.t[stream] - return Constraint.Skip - - b.flshti = Constraint([flsh], m.str, rule=Flshti, doc="inlet temp. relation") - - def Flshtl(_m, flsh_, stream): - if (flsh_, stream) in m.lflsh and flsh_ == flsh: - return m.flsht[flsh_] == m.t[stream] - return Constraint.Skip - - b.flshtl = Constraint( - [flsh], m.str, rule=Flshtl, doc="outlet temp. relation (liquid)" - ) - - def Flshtv(_m, flsh_, stream): - if (flsh_, stream) in m.vflsh and flsh_ == flsh: - return m.flsht[flsh_] == m.t[stream] - return Constraint.Skip - - b.flshtv = Constraint( - [flsh], m.str, rule=Flshtv, doc="outlet temp. relation (vapor)" - ) - - m.heat_unit_match = Param( - initialize=3600.0 * 8500.0 * 1.0e-12 / 60.0, doc="unit change on temp" - ) - - def build_furnace(b, furnace): - """ - Functions relevant to the furnace block - - Parameters - ---------- - b : Pyomo Block - furnace block - furnace : int - Index of the furnace - """ - - def Furnhb(_m, furn): - if furn == furnace: - return ( - m.qfuel[furn] - == ( - sum( - m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] - for (furn, stream) in m.ofurn - ) - - sum( - m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] - for (furn, stream) in m.ifurn - ) - ) - * m.heat_unit_match - ) - return Constraint.Skip - - b.furnhb = Constraint([furnace], rule=Furnhb, doc="heat balance in furnace") - - def Furncmb(_m, furn, compon): - if furn == furnace: - return sum(m.fc[stream, compon] for (furn, stream) in m.ofurn) == sum( - m.fc[stream, compon] for (furn, stream) in m.ifurn - ) - return Constraint.Skip - - b.furncmb = Constraint( - [furnace], m.compon, rule=Furncmb, doc="component mass balance in furnace" - ) - - def Furnp(_m, furn): - if furn == furnace: - return ( - sum(m.p[stream] for (furn, stream) in m.ofurn) - == sum(m.p[stream] for (furn, stream) in m.ifurn) - m.furnpdrop - ) - return Constraint.Skip - - b.furnp = Constraint([furnace], rule=Furnp, doc="pressure relation in furnace") - - def build_cooler(b, cooler): - """ - Functions relevant to the cooler block - - Parameters - ---------- - b : Pyomo Block - cooler block - cooler : int - Index of the cooler - """ - - def Heccmb(_m, hec, compon): - return sum( - m.fc[stream, compon] for (hec_, stream) in m.ohec if hec_ == hec - ) == sum(m.fc[stream, compon] for (hec_, stream) in m.ihec if hec_ == hec) - - b.heccmb = Constraint( - [cooler], m.compon, rule=Heccmb, doc="heat balance in cooler" - ) - - def Hechb(_m, hec): - return ( - m.qc[hec] - == ( - sum( - m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] - for (hec_, stream) in m.ihec - if hec_ == hec - ) - - sum( - m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] - for (hec_, stream) in m.ohec - if hec_ == hec - ) - ) - * m.heat_unit_match - ) - - b.hechb = Constraint( - [cooler], rule=Hechb, doc="component mass balance in cooler" - ) - - def Hecp(_m, hec): - return sum(m.p[stream] for (hec_, stream) in m.ihec if hec_ == hec) == sum( - m.p[stream] for (hec_, stream) in m.ohec if hec_ == hec - ) - - b.hecp = Constraint([cooler], rule=Hecp, doc="pressure relation in cooler") - - def build_heater(b, heater): - """ - Functions relevant to the heater block - - Parameters - ---------- - b : Pyomo Block - heater block - heater : int - Index of the heater - """ - - def Hehcmb(_m, heh, compon): - if heh == heater and compon in m.compon: - return sum( - m.fc[stream, compon] for (heh_, stream) in m.oheh if heh_ == heh - ) == sum( - m.fc[stream, compon] for (heh_, stream) in m.iheh if heh == heh_ - ) - return Constraint.Skip - - b.hehcmb = Constraint( - Set(initialize=[heater]), - m.compon, - rule=Hehcmb, - doc="component balance in heater", - ) - - def Hehhb(_m, heh): - if heh == heater: - return ( - m.qh[heh] - == ( - sum( - m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] - for (heh_, stream) in m.oheh - if heh_ == heh - ) - - sum( - m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] - for (heh_, stream) in m.iheh - if heh_ == heh - ) - ) - * m.heat_unit_match - ) - return Constraint.Skip - - b.hehhb = Constraint( - Set(initialize=[heater]), rule=Hehhb, doc="heat balance in heater" - ) - - def hehp(_m, heh): - if heh == heater: - return sum( - m.p[stream] for (heh_, stream) in m.iheh if heh_ == heh - ) == sum(m.p[stream] for (heh_, stream) in m.oheh if heh == heh_) - return Constraint.Skip - - b.Hehp = Constraint( - Set(initialize=[heater]), rule=hehp, doc="no pressure drop thru heater" - ) - - m.exchanger_temp_drop = Param(initialize=0.25) - - def build_exchanger(b, exchanger): - """ - Functions relevant to the exchanger block - - Parameters - ---------- - b : Pyomo Block - exchanger block - exchanger : int - Index of the exchanger - """ - - def Exchcmbc(_m, exch, compon): - if exch in m.exch and compon in m.compon: - return sum( - m.fc[stream, compon] - for (exch_, stream) in m.ocexch - if exch == exch_ - ) == sum( - m.fc[stream, compon] - for (exch_, stream) in m.icexch - if exch == exch_ - ) - return Constraint.Skip - - b.exchcmbc = Constraint( - [exchanger], - m.compon, - rule=Exchcmbc, - doc="component balance (cold) in exchanger", - ) - - def Exchcmbh(_m, exch, compon): - if exch in m.exch and compon in m.compon: - return sum( - m.fc[stream, compon] - for (exch_, stream) in m.ohexch - if exch == exch_ - ) == sum( - m.fc[stream, compon] - for (exch_, stream) in m.ihexch - if exch == exch_ - ) - return Constraint.Skip - - b.exchcmbh = Constraint( - [exchanger], - m.compon, - rule=Exchcmbh, - doc="component balance (hot) in exchanger", - ) - - def Exchhbc(_m, exch): - if exch in m.exch: - return ( - sum( - m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] - for (exch_, stream) in m.ocexch - if exch == exch_ - ) - - sum( - m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] - for (exch_, stream) in m.icexch - if exch == exch_ - ) - ) * m.heat_unit_match == m.qexch[exch] - return Constraint.Skip - - b.exchhbc = Constraint( - [exchanger], rule=Exchhbc, doc="heat balance for cold stream in exchanger" - ) - - def Exchhbh(_m, exch): - return ( - sum( - m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] - for (exch, stream) in m.ihexch - ) - - sum( - m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] - for (exch, stream) in m.ohexch - ) - ) * m.heat_unit_match == m.qexch[exch] - - b.exchhbh = Constraint( - [exchanger], rule=Exchhbh, doc="heat balance for hot stream in exchanger" - ) - - def Exchdtm1(_m, exch): - return ( - sum(m.t[stream] for (exch, stream) in m.ohexch) - >= sum(m.t[stream] for (exch, stream) in m.icexch) - + m.exchanger_temp_drop - ) - - b.exchdtm1 = Constraint([exchanger], rule=Exchdtm1, doc="delta t min condition") - - def Exchdtm2(_m, exch): - return ( - sum(m.t[stream] for (exch, stream) in m.ocexch) - <= sum(m.t[stream] for (exch, stream) in m.ihexch) - - m.exchanger_temp_drop - ) - - b.exchdtm2 = Constraint([exchanger], rule=Exchdtm2, doc="delta t min condition") - - def Exchpc(_m, exch): - return sum(m.p[stream] for (exch, stream) in m.ocexch) == sum( - m.p[stream] for (exch, stream) in m.icexch - ) - - b.exchpc = Constraint( - [exchanger], rule=Exchpc, doc="pressure relation (cold) in exchanger" - ) - - def Exchph(_m, exch): - return sum(m.p[stream] for (exch, stream) in m.ohexch) == sum( - m.p[stream] for (exch, stream) in m.ihexch - ) - - b.exchph = Constraint( - [exchanger], rule=Exchph, doc="pressure relation (hot) in exchanger" - ) - - m.membrane_recovery_sepc = Param(initialize=0.50) - m.membrane_purity_sepc = Param(initialize=0.50) - - def build_membrane(b, membrane): - """ - Functions relevant to the membrane block - - Parameters - ---------- - b : Pyomo Block - membrane block - membrane : int - Index of the membrane - """ - - def Memcmb(_m, memb, stream, compon): - if (memb, stream) in m.imemb and memb == membrane: - return m.fc[stream, compon] == sum( - m.fc[stream, compon] for (memb_, stream) in m.pmemb if memb == memb_ - ) + sum( - m.fc[stream, compon] for (memb_, stream) in m.nmemb if memb == memb_ - ) - return Constraint.Skip - - b.memcmb = Constraint( - [membrane], - m.str, - m.compon, - rule=Memcmb, - doc="component mass balance in membrane separator", - ) - - def Flux(_m, memb, stream, compon): - if ( - (memb, stream) in m.pmemb - and (memb, compon) in m.mnorm - and memb == membrane - ): - return m.fc[stream, compon] == m.a[memb] * m.perm[compon] / 2.0 * ( - sum(m.p[stream2] for (memb_, stream2) in m.imemb if memb_ == memb) - * ( - sum( - (m.fc[stream2, compon] + m.eps1) / (m.f[stream2] + m.eps1) - for (memb_, stream2) in m.imemb - if memb_ == memb - ) - + sum( - (m.fc[stream2, compon] + m.eps1) / (m.f[stream2] + m.eps1) - for (memb_, stream2) in m.nmemb - if memb_ == memb - ) - ) - - 2.0 - * m.p[stream] - * (m.fc[stream, compon] + m.eps1) - / (m.f[stream] + m.eps1) - ) - return Constraint.Skip - - b.flux = Constraint( - [membrane], - m.str, - m.compon, - rule=Flux, - doc="mass flux relation in membrane separator", - ) - - def Simp(_m, memb, stream, compon): - if ( - (memb, stream) in m.pmemb - and (memb, compon) in m.msimp - and memb == membrane - ): - return m.fc[stream, compon] == 0.0 - return Constraint.Skip - - b.simp = Constraint( - [membrane], - m.str, - m.compon, - rule=Simp, - doc="mass flux relation (simplified) in membrane separator", - ) - - def Memtp(_m, memb, stream): - if (memb, stream) in m.pmemb and memb == membrane: - return m.t[stream] == sum( - m.t[stream2] for (memb, stream2) in m.imemb if memb == membrane - ) - return Constraint.Skip - - b.memtp = Constraint( - [membrane], m.str, rule=Memtp, doc="temp relation for permeate" - ) - - def Mempp(_m, memb, stream): - if (memb, stream) in m.pmemb and memb == membrane: - return m.p[stream] <= sum( - m.p[stream2] for (memb, stream2) in m.imemb if memb == membrane - ) - return Constraint.Skip - - b.mempp = Constraint( - [membrane], m.str, rule=Mempp, doc="pressure relation for permeate" - ) - - def Memtn(_m, memb, stream): - if (memb, stream) in m.nmemb and memb == membrane: - return m.t[stream] == sum( - m.t[stream2] for (memb, stream2) in m.imemb if memb == membrane - ) - return Constraint.Skip - - b.Memtn = Constraint( - [membrane], m.str, rule=Memtn, doc="temp relation for non-permeate" - ) - - def Mempn(_m, memb, stream): - if (memb, stream) in m.nmemb and memb == membrane: - return m.p[stream] == sum( - m.p[stream] for (memb_, stream) in m.imemb if memb_ == memb - ) - return Constraint.Skip - - b.Mempn = Constraint( - [membrane], m.str, rule=Mempn, doc="pressure relation for non-permeate" - ) - - def Rec(_m, memb_, stream): - if (memb_, stream) in m.pmemb and memb_ == membrane: - return m.fc[stream, "h2"] >= m.membrane_recovery_sepc * sum( - m.fc[stream, "h2"] for (memb, stream) in m.imemb if memb == memb_ - ) - return Constraint.Skip - - b.rec = Constraint([membrane], m.str, rule=Rec, doc="recovery spec") - - def Pure(_m, memb, stream): - if (memb, stream) in m.pmemb and memb == membrane: - return m.fc[stream, "h2"] >= m.membrane_purity_sepc * m.f[stream] - return Constraint.Skip - - b.pure = Constraint([membrane], m.str, rule=Pure, doc="purity spec") - - def build_multiple_mixer(b, multiple_mxr): - """ - Functions relevant to the mixer block - - Parameters - ---------- - b : Pyomo Block - mixer block - multiple_mxr : int - Index of the mixer - """ - - def Mxrcmb(_b, mxr, compon): - if mxr == multiple_mxr: - return sum( - m.fc[stream, compon] for (mxr_, stream) in m.omxr if mxr == mxr_ - ) == sum( - m.fc[stream, compon] for (mxr_, stream) in m.imxr if mxr == mxr_ - ) - return Constraint.Skip - - b.mxrcmb = Constraint( - [multiple_mxr], m.compon, rule=Mxrcmb, doc="component balance in mixer" - ) - - def Mxrhb(_b, mxr): - if mxr == multiple_mxr and mxr != 2: - return sum( - m.f[stream] * m.t[stream] * m.cp[stream] - for (mxr_, stream) in m.imxr - if mxr == mxr_ - ) == sum( - m.f[stream] * m.t[stream] * m.cp[stream] - for (mxr_, stream) in m.omxr - if mxr == mxr_ - ) - return Constraint.Skip - - b.mxrhb = Constraint([multiple_mxr], rule=Mxrhb, doc="heat balance in mixer") - - def Mxrhbq(_b, mxr): - if mxr == 2 and mxr == multiple_mxr: - return m.f[16] * m.t[16] == m.f[15] * m.t[15] - ( - m.fc[20, "ben"] + m.fc[20, "tol"] - ) * m.heatvap["tol"] / (100.0 * m.cp[15]) - return Constraint.Skip - - b.mxrhbq = Constraint([multiple_mxr], rule=Mxrhbq, doc="heat balance in quench") - - def Mxrpi(_b, mxr, stream): - if (mxr, stream) in m.imxr and mxr == multiple_mxr: - return m.mxrp[mxr] == m.p[stream] - return Constraint.Skip - - b.mxrpi = Constraint( - [multiple_mxr], m.str, rule=Mxrpi, doc="inlet pressure relation" - ) - - def Mxrpo(_b, mxr, stream): - if (mxr, stream) in m.omxr and mxr == multiple_mxr: - return m.mxrp[mxr] == m.p[stream] - return Constraint.Skip - - b.mxrpo = Constraint( - [multiple_mxr], m.str, rule=Mxrpo, doc="outlet pressure relation" - ) - - def build_pump(b, pump_): - """ - Functions relevant to the pump block - - Parameters - ---------- - b : Pyomo Block - pump block - pump_ : int - Index of the pump - """ - - def Pumpcmb(_m, pump, compon): - if pump == pump_ and compon in m.compon: - return sum( - m.fc[stream, compon] for (pump_, stream) in m.opump if pump == pump_ - ) == sum( - m.fc[stream, compon] for (pump_, stream) in m.ipump if pump_ == pump - ) - return Constraint.Skip - - b.pumpcmb = Constraint( - [pump_], m.compon, rule=Pumpcmb, doc="component balance in pump" - ) - - def Pumphb(_m, pump): - if pump == pump_: - return sum( - m.t[stream] for (pump_, stream) in m.opump if pump == pump_ - ) == sum(m.t[stream] for (pump_, stream) in m.ipump if pump == pump_) - return Constraint.Skip - - b.pumphb = Constraint([pump_], rule=Pumphb, doc="heat balance in pump") - - def Pumppr(_m, pump): - if pump == pump_: - return sum( - m.p[stream] for (pump_, stream) in m.opump if pump == pump_ - ) >= sum(m.p[stream] for (pump_, stream) in m.ipump if pump == pump_) - return Constraint.Skip - - b.pumppr = Constraint([pump_], rule=Pumppr, doc="pressure relation in pump") - - def build_multiple_splitter(b, multi_splitter): - """ - Functions relevant to the splitter block - - Parameters - ---------- - b : Pyomo Block - splitter block - multi_splitter : int - Index of the splitter - """ - - def Splcmb(_m, spl, stream, compon): - if (spl, stream) in m.ospl and spl == multi_splitter: - return m.fc[stream, compon] == sum( - m.e[stream] * m.fc[str2, compon] - for (spl_, str2) in m.ispl - if spl == spl_ - ) - return Constraint.Skip - - b.splcmb = Constraint( - [multi_splitter], - m.str, - m.compon, - rule=Splcmb, - doc="component balance in splitter", - ) - - def Esum(_m, spl): - if spl in m.spl and spl == multi_splitter: - return ( - sum(m.e[stream] for (spl_, stream) in m.ospl if spl_ == spl) == 1.0 - ) - return Constraint.Skip - - b.esum = Constraint( - [multi_splitter], rule=Esum, doc="split fraction relation in splitter" - ) - - def Splpi(_m, spl, stream): - if (spl, stream) in m.ispl and spl == multi_splitter: - return m.splp[spl] == m.p[stream] - return Constraint.Skip - - b.splpi = Constraint( - [multi_splitter], - m.str, - rule=Splpi, - doc="inlet pressure relation (splitter)", - ) - - def Splpo(_m, spl, stream): - if (spl, stream) in m.ospl and spl == multi_splitter: - return m.splp[spl] == m.p[stream] - return Constraint.Skip - - b.splpo = Constraint( - [multi_splitter], - m.str, - rule=Splpo, - doc="outlet pressure relation (splitter)", - ) - - def Splti(_m, spl, stream): - if (spl, stream) in m.ispl and spl == multi_splitter: - return m.splt[spl] == m.t[stream] - return Constraint.Skip - - b.splti = Constraint( - [multi_splitter], - m.str, - rule=Splti, - doc="inlet temperature relation (splitter)", - ) - - def Splto(_m, spl, stream): - if (spl, stream) in m.ospl and spl == multi_splitter: - return m.splt[spl] == m.t[stream] - return Constraint.Skip - - b.splto = Constraint( - [multi_splitter], - m.str, - rule=Splto, - doc="outlet temperature relation (splitter)", - ) - - def build_valve(b, valve_): - """ - Functions relevant to the valve block - - Parameters - ---------- - b : Pyomo Block - valve block - valve_ : int - Index of the valve - """ - - def Valcmb(_m, valve, compon): - return sum( - m.fc[stream, compon] for (valve_, stream) in m.oval if valve == valve_ - ) == sum( - m.fc[stream, compon] for (valve_, stream) in m.ival if valve == valve_ - ) - - b.valcmb = Constraint( - [valve_], m.compon, rule=Valcmb, doc="component balance in valve" - ) - - def Valt(_m, valve): - return sum( - m.t[stream] / (m.p[stream] ** ((m.gam - 1.0) / m.gam)) - for (valv, stream) in m.oval - if valv == valve - ) == sum( - m.t[stream] / (m.p[stream] ** ((m.gam - 1.0) / m.gam)) - for (valv, stream) in m.ival - if valv == valve - ) - - b.valt = Constraint([valve_], rule=Valt, doc="temperature relation in valve") - - def Valp(_m, valve): - return sum( - m.p[stream] for (valv, stream) in m.oval if valv == valve - ) <= sum(m.p[stream] for (valv, stream) in m.ival if valv == valve) - - b.valp = Constraint([valve_], rule=Valp, doc="pressure relation in valve") - - m.Prereference_factor = Param( - initialize=6.3e10, doc="Pre-reference factor for reaction rate constant" - ) - m.Ea_R = Param( - initialize=-26167.0, doc="Activation energy for reaction rate constant" - ) - m.pressure_drop = Param(initialize=0.20684, doc="Pressure drop") - m.selectivity_1 = Param(initialize=0.0036, doc="Selectivity to benzene") - m.selectivity_2 = Param(initialize=-1.544, doc="Selectivity to benzene") - m.conversion_coefficient = Param(initialize=0.372, doc="Conversion coefficient") - - def build_reactor(b, rct): - """ - Functions relevant to the reactor block - - Parameters - ---------- - b : Pyomo Block - reactor block - rct : int - Index of the reactor - """ - - def rctspec(_m, rct, stream): - if (rct, stream) in m.irct: - return m.fc[stream, "h2"] >= 5 * ( - m.fc[stream, "ben"] + m.fc[stream, "tol"] + m.fc[stream, "dip"] - ) - return Constraint.Skip - - b.Rctspec = Constraint( - [rct], m.str, rule=rctspec, doc="spec. on reactor feed stream" - ) - - def rxnrate(_m, rct): - return m.krct[rct] == m.Prereference_factor * exp( - m.Ea_R / (m.rctt[rct] * 100.0) - ) - - b.Rxnrate = Constraint([rct], rule=rxnrate, doc="reaction rate constant") - - def rctconv(_m, rct, stream, compon): - if (rct, compon) in m.rkey and (rct, stream) in m.irct: - return ( - 1.0 - m.conv[rct, compon] - == ( - 1.0 - / ( - 1.0 - + m.conversion_coefficient - * m.krct[rct] - * m.rctvol[rct] - * sqrt(m.fc[stream, compon] / 60 + m.eps1) - * (m.f[stream] / 60.0 + m.eps1) ** (-3.0 / 2.0) - ) - ) - ** 2.0 - ) - return Constraint.Skip - - b.Rctconv = Constraint( - [rct], m.str, m.compon, rule=rctconv, doc="conversion of key component" - ) - - def rctsel(_m, rct): - return (1.0 - m.sel[rct]) == m.selectivity_1 * ( - 1.0 - m.conv[rct, "tol"] - ) ** m.selectivity_2 - - b.Rctsel = Constraint([rct], rule=rctsel, doc="selectivity to benzene") - - def rctcns(_m, rct, stream, compon): - if (rct, compon) in m.rkey and (rct, stream) in m.irct: - return ( - m.consum[rct, compon] == m.conv[rct, compon] * m.fc[stream, compon] - ) - return Constraint.Skip - - b.Rctcns = Constraint( - [rct], m.str, m.compon, rule=rctcns, doc="consumption rate of key comp." - ) - - def rctmbtol(_m, rct): - return ( - sum(m.fc[stream, "tol"] for (rct_, stream) in m.orct if rct_ == rct) - == sum(m.fc[stream, "tol"] for (rct_, stream) in m.irct if rct_ == rct) - - m.consum[rct, "tol"] - ) - - b.Rctmbtol = Constraint( - [rct], rule=rctmbtol, doc="mass balance in reactor (tol)" - ) - - def rctmbben(_m, rct): - return ( - sum(m.fc[stream, "ben"] for (rct_, stream) in m.orct if rct_ == rct) - == sum(m.fc[stream, "ben"] for (rct_, stream) in m.irct if rct_ == rct) - + m.consum[rct, "tol"] * m.sel[rct] - ) - - b.Rctmbben = Constraint( - [rct], rule=rctmbben, doc="mass balance in reactor (ben)" - ) - - def rctmbdip(_m, rct): - return ( - sum(m.fc[stream, "dip"] for (rct1, stream) in m.orct if rct1 == rct) - == sum(m.fc[stream, "dip"] for (rct1, stream) in m.irct if rct1 == rct) - + m.consum[rct, "tol"] * 0.5 - + ( - sum(m.fc[stream, "ben"] for (rct1, stream) in m.irct if rct1 == rct) - - sum( - m.fc[stream, "ben"] for (rct1, stream) in m.orct if rct1 == rct - ) - ) - * 0.5 - ) - - b.Rctmbdip = Constraint( - [rct], rule=rctmbdip, doc="mass balance in reactor (dip)" - ) - - def rctmbh2(_m, rct): - return sum( - m.fc[stream, "h2"] for (rct1, stream) in m.orct if rct1 == rct - ) == sum( - m.fc[stream, "h2"] for (rct1, stream) in m.irct if rct1 == rct - ) - m.consum[ - rct, "tol" - ] - sum( - m.fc[stream, "dip"] for (rct1, stream) in m.irct if rct1 == rct - ) + sum( - m.fc[stream, "dip"] for (rct1, stream) in m.orct if rct1 == rct - ) - - b.Rctmbh2 = Constraint([rct], rule=rctmbh2, doc="mass balance in reactor (h2)") - - def rctpi(_m, rct, stream): - if (rct, stream) in m.irct: - return m.rctp[rct] == m.p[stream] - return Constraint.Skip - - b.Rctpi = Constraint([rct], m.str, rule=rctpi, doc="inlet pressure relation") - - def rctpo(_m, rct, stream): - if (rct, stream) in m.orct: - return m.rctp[rct] - m.pressure_drop == m.p[stream] - return Constraint.Skip - - b.Rctpo = Constraint([rct], m.str, rule=rctpo, doc="outlet pressure relation") - - def rcttave(_m, rct): - return ( - m.rctt[rct] - == ( - sum(m.t[stream] for (rct1, stream) in m.irct if rct1 == rct) - + sum(m.t[stream] for (rct1, stream) in m.orct if rct1 == rct) - ) - / 2 - ) - - b.Rcttave = Constraint([rct], rule=rcttave, doc="average temperature relation") - - def Rctmbch4(_m, rct): - return ( - sum(m.fc[stream, "ch4"] for (rct_, stream) in m.orct if rct_ == rct) - == sum(m.fc[stream, "ch4"] for (rct_, stream) in m.irct if rct == rct_) - + m.consum[rct, "tol"] - ) - - b.rctmbch4 = Constraint( - [rct], rule=Rctmbch4, doc="mass balance in reactor (ch4)" - ) - - def Rcthbadb(_m, rct): - if rct == 1: - return m.heatrxn[rct] * m.consum[rct, "tol"] / 100.0 == sum( - m.cp[stream] * m.f[stream] * m.t[stream] - for (rct_, stream) in m.orct - if rct_ == rct - ) - sum( - m.cp[stream] * m.f[stream] * m.t[stream] - for (rct_, stream) in m.irct - if rct_ == rct - ) - return Constraint.Skip - - b.rcthbadb = Constraint([rct], rule=Rcthbadb, doc="heat balance (adiabatic)") - - def Rcthbiso(_m, rct): - if rct == 2: - return ( - m.heatrxn[rct] * m.consum[rct, "tol"] * 60.0 * 8500 * 1.0e-09 - == m.q[rct] - ) - return Constraint.Skip - - b.rcthbiso = Constraint([rct], rule=Rcthbiso, doc="temp relation (isothermal)") - - def Rctisot(_m, rct): - if rct == 2: - return sum( - m.t[stream] for (rct_, stream) in m.irct if rct_ == rct - ) == sum(m.t[stream] for (rct_, stream) in m.orct if rct_ == rct) - return Constraint.Skip - - b.rctisot = Constraint([rct], rule=Rctisot, doc="temp relation (isothermal)") - - def build_single_mixer(b, mixer): - """ - Functions relevant to the single mixer block - - Parameters - ---------- - b : Pyomo Block - single mixer block - mixer : int - Index of the mixer - """ - - def Mxr1cmb(m_, mxr1, str1, compon): - if (mxr1, str1) in m.omxr1 and mxr1 == mixer: - return m.fc[str1, compon] == sum( - m.fc[str2, compon] for (mxr1_, str2) in m.imxr1 if mxr1_ == mxr1 - ) - return Constraint.Skip - - b.mxr1cmb = Constraint( - [mixer], m.str, m.compon, rule=Mxr1cmb, doc="component balance in mixer" - ) - - m.single_mixer = Block(m.mxr1, rule=build_single_mixer) - - # single output splitter - def build_single_splitter(b, splitter): - """ - Functions relevant to the single splitter block - - Parameters - ---------- - b : Pyomo Block - single splitter block - splitter : int - Index of the splitter - """ - - def Spl1cmb(m_, spl1, compon): - return sum( - m.fc[str1, compon] for (spl1_, str1) in m.ospl1 if spl1_ == spl1 - ) == sum(m.fc[str1, compon] for (spl1_, str1) in m.ispl1 if spl1_ == spl1) - - b.spl1cmb = Constraint( - [splitter], m.compon, rule=Spl1cmb, doc="component balance in splitter" - ) - - def Spl1pi(m_, spl1, str1): - if (spl1, str1) in m.ispl1: - return m.spl1p[spl1] == m.p[str1] - return Constraint.Skip - - b.spl1pi = Constraint( - [splitter], m.str, rule=Spl1pi, doc="inlet pressure relation (splitter)" - ) - - def Spl1po(m_, spl1, str1): - if (spl1, str1) in m.ospl1: - return m.spl1p[spl1] == m.p[str1] - return Constraint.Skip - - b.spl1po = Constraint( - [splitter], m.str, rule=Spl1po, doc="outlet pressure relation (splitter)" - ) - - def Spl1ti(m_, spl1, str1): - if (spl1, str1) in m.ispl1: - return m.spl1t[spl1] == m.t[str1] - return Constraint.Skip - - b.spl1ti = Constraint( - [splitter], m.str, rule=Spl1ti, doc="inlet temperature relation (splitter)" - ) - - def Spl1to(m_, spl1, str1): - if (spl1, str1) in m.ospl1: - return m.spl1t[spl1] == m.t[str1] - return Constraint.Skip - - b.spl1to = Constraint( - [splitter], m.str, rule=Spl1to, doc="outlet temperature relation (splitter)" - ) - - m.single_splitter = Block(m.spl1, rule=build_single_splitter) - - # ## GDP formulation - - m.one = Set(initialize=[1]) - m.two = Set(initialize=[2]) - m.three = Set(initialize=[3]) - m.four = Set(initialize=[4]) - m.five = Set(initialize=[5]) - m.six = Set(initialize=[6]) - - # first disjunction: Purify H2 inlet or not - @m.Disjunct() - def purify_H2(disj): - disj.membrane_1 = Block(m.one, rule=build_membrane) - disj.compressor_1 = Block(m.one, rule=build_compressor) - disj.no_flow_2 = Constraint(expr=m.f[2] == 0) - disj.pressure_match_out = Constraint(expr=m.p[6] == m.p[7]) - disj.tempressure_match_out = Constraint(expr=m.t[6] == m.t[7]) - - @m.Disjunct() - def no_purify_H2(disj): - disj.no_flow_3 = Constraint(expr=m.f[3] == 0) - disj.no_flow_4 = Constraint(expr=m.f[4] == 0) - disj.no_flow_5 = Constraint(expr=m.f[5] == 0) - disj.no_flow_6 = Constraint(expr=m.f[6] == 0) - disj.pressure_match = Constraint(expr=m.p[2] == m.p[7]) - disj.tempressure_match = Constraint(expr=m.t[2] == m.t[7]) - - @m.Disjunction() - def inlet_treatment(m): - return [m.purify_H2, m.no_purify_H2] - - m.multi_mixer_1 = Block(m.one, rule=build_multiple_mixer) - m.furnace_1 = Block(m.one, rule=build_furnace) - - # Second disjunction: Adiabatic or isothermal reactor - @m.Disjunct() - def adiabatic_reactor(disj): - disj.Adiabatic_reactor = Block(m.one, rule=build_reactor) - disj.no_flow_12 = Constraint(expr=m.f[12] == 0) - disj.no_flow_13 = Constraint(expr=m.f[13] == 0) - disj.pressure_match = Constraint(expr=m.p[11] == m.p[14]) - disj.tempressure_match = Constraint(expr=m.t[11] == m.t[14]) - - @m.Disjunct() - def isothermal_reactor(disj): - disj.Isothermal_reactor = Block(m.two, rule=build_reactor) - disj.no_flow_10 = Constraint(expr=m.f[10] == 0) - disj.no_flow_11 = Constraint(expr=m.f[11] == 0) - disj.pressure_match = Constraint(expr=m.p[13] == m.p[14]) - disj.tempressure_match = Constraint(expr=m.t[13] == m.t[14]) - - @m.Disjunction() - def reactor_selection(m): - return [m.adiabatic_reactor, m.isothermal_reactor] - - m.valve_3 = Block(m.three, rule=build_valve) - m.multi_mixer_2 = Block(m.two, rule=build_multiple_mixer) - m.exchanger_1 = Block(m.one, rule=build_exchanger) - m.cooler_1 = Block(m.one, rule=build_cooler) - m.flash_1 = Block(m.one, rule=build_flash) - m.multi_splitter_2 = Block(m.two, rule=build_multiple_splitter) - - # thrid disjunction: recycle methane with membrane or purge it - @m.Disjunct() - def recycle_methane_purge(disj): - disj.no_flow_54 = Constraint(expr=m.f[54] == 0) - disj.no_flow_55 = Constraint(expr=m.f[55] == 0) - disj.no_flow_56 = Constraint(expr=m.f[56] == 0) - disj.no_flow_57 = Constraint(expr=m.f[57] == 0) - - @m.Disjunct() - def recycle_methane_membrane(disj): - disj.no_flow_53 = Constraint(expr=m.f[53] == 0) - disj.membrane_2 = Block(m.two, rule=build_membrane) - disj.compressor_4 = Block(m.four, rule=build_compressor) - - @m.Disjunction() - def methane_treatmet(m): - return [m.recycle_methane_purge, m.recycle_methane_membrane] - - # fourth disjunction: recycle hydrogen with absorber or not - @m.Disjunct() - def recycle_hydrogen(disj): - disj.no_flow_61 = Constraint(expr=m.f[61] == 0) - disj.no_flow_73 = Constraint(expr=m.f[73] == 0) - disj.no_flow_62 = Constraint(expr=m.f[62] == 0) - disj.no_flow_64 = Constraint(expr=m.f[64] == 0) - disj.no_flow_65 = Constraint(expr=m.f[65] == 0) - disj.no_flow_68 = Constraint(expr=m.f[68] == 0) - disj.no_flow_51 = Constraint(expr=m.f[51] == 0) - disj.compressor_2 = Block(m.two, rule=build_compressor) - disj.stream_1 = Constraint(expr=m.f[63] == 0) - disj.stream_2 = Constraint(expr=m.f[67] == 0) - disj.no_flow_69 = Constraint(expr=m.f[69] == 0) - - @m.Disjunct() - def absorber_hydrogen(disj): - disj.heater_4 = Block(m.four, rule=build_heater) - disj.no_flow_59 = Constraint(expr=m.f[59] == 0) - disj.no_flow_60 = Constraint(expr=m.f[60] == 0) - disj.valve_6 = Block(m.six, rule=build_valve) - disj.multi_mixer_4 = Block(m.four, rule=build_multiple_mixer) - disj.absorber_1 = Block(m.one, rule=build_absorber) - disj.compressor_3 = Block(m.three, rule=build_compressor) - disj.absorber_stream = Constraint(expr=m.f[63] + m.f[67] <= 25) - disj.pump_2 = Block(m.two, rule=build_pump) - - @m.Disjunction() - def recycle_selection(m): - return [m.recycle_hydrogen, m.absorber_hydrogen] - - m.multi_mixer_5 = Block(m.five, rule=build_multiple_mixer) - m.multi_mixer_3 = Block(m.three, rule=build_multiple_mixer) - m.multi_splitter_1 = Block(m.one, rule=build_multiple_splitter) - - # fifth disjunction: methane stabilizing selection - @m.Disjunct() - def methane_distillation_column(disj): - disj.no_flow_23 = Constraint(expr=m.f[23] == 0) - disj.no_flow_44 = Constraint(expr=m.f[44] == 0) - disj.no_flow_45 = Constraint(expr=m.f[45] == 0) - disj.no_flow_46 = Constraint(expr=m.f[46] == 0) - disj.no_flow_47 = Constraint(expr=m.f[47] == 0) - disj.no_flow_49 = Constraint(expr=m.f[49] == 0) - disj.no_flow_48 = Constraint(expr=m.f[48] == 0) - disj.heater_1 = Block(m.one, rule=build_heater) - disj.stabilizing_Column_1 = Block(m.one, rule=build_distillation) - disj.multi_splitter_3 = Block(m.three, rule=build_multiple_splitter) - disj.valve_5 = Block(m.five, rule=build_valve) - disj.pressure_match_1 = Constraint(expr=m.p[27] == m.p[30]) - disj.tempressure_match_1 = Constraint(expr=m.t[27] == m.t[30]) - disj.pressure_match_2 = Constraint(expr=m.p[50] == m.p[51]) - disj.tempressure_match_2 = Constraint(expr=m.t[50] == m.t[51]) - - @m.Disjunct() - def methane_flash_separation(disj): - disj.heater_2 = Block(m.two, rule=build_heater) - disj.no_flow_24 = Constraint(expr=m.f[24] == 0) - disj.no_flow_25 = Constraint(expr=m.f[25] == 0) - disj.no_flow_26 = Constraint(expr=m.f[26] == 0) - disj.no_flow_27 = Constraint(expr=m.f[27] == 0) - disj.no_flow_28 = Constraint(expr=m.f[28] == 0) - disj.no_flow_29 = Constraint(expr=m.f[29] == 0) - disj.no_flow_50 = Constraint(expr=m.f[50] == 0) - disj.valve_1 = Block(m.one, rule=build_valve) - disj.cooler_2 = Block(m.two, rule=build_cooler) - disj.flash_2 = Block(m.two, rule=build_flash) - disj.valve_4 = Block(m.four, rule=build_valve) - disj.pressure_match_1 = Constraint(expr=m.p[48] == m.p[30]) - disj.tempressure_match_1 = Constraint(expr=m.t[48] == m.t[30]) - disj.pressure_match_2 = Constraint(expr=m.p[49] == m.p[51]) - disj.tempressure_match_2 = Constraint(expr=m.t[49] == m.t[51]) - - @m.Disjunction() - def H2_selection(m): - return [m.methane_distillation_column, m.methane_flash_separation] - - m.benzene_column = Block(m.two, rule=build_distillation) - - # sixth disjunction: toluene stabilizing selection - @m.Disjunct() - def toluene_distillation_column(disj): - disj.no_flow_37 = Constraint(expr=m.f[37] == 0) - disj.no_flow_38 = Constraint(expr=m.f[38] == 0) - disj.no_flow_39 = Constraint(expr=m.f[39] == 0) - disj.no_flow_40 = Constraint(expr=m.f[40] == 0) - disj.no_flow_41 = Constraint(expr=m.f[41] == 0) - disj.stabilizing_Column_3 = Block(m.three, rule=build_distillation) - disj.pressure_match = Constraint(expr=m.p[34] == m.p[42]) - disj.tempressure_match = Constraint(expr=m.t[34] == m.t[42]) - - @m.Disjunct() - def toluene_flash_separation(disj): - disj.heater_3 = Block(m.three, rule=build_heater) - disj.no_flow_33 = Constraint(expr=m.f[33] == 0) - disj.no_flow_34 = Constraint(expr=m.f[34] == 0) - disj.no_flow_35 = Constraint(expr=m.f[35] == 0) - disj.valve_2 = Block(m.two, rule=build_valve) - disj.flash_3 = Block(m.three, rule=build_flash) - disj.pressure_match = Constraint(expr=m.p[40] == m.p[42]) - disj.tempressure_match = Constraint(expr=m.t[40] == m.t[42]) - - @m.Disjunction() - def toluene_selection(m): - return [m.toluene_distillation_column, m.toluene_flash_separation] - - m.pump_1 = Block(m.one, rule=build_pump) - m.abound = Constraint(expr=m.a[1] >= 0.0) - - # ## objective function - - m.hydrogen_purge_value = Param( - initialize=1.08, doc="heating value of hydrogen purge" - ) - m.electricity_cost = Param( - initialize=0.04 * 24 * 365 / 1000, - doc="electricity cost, value is 0.04 with the unit of [kw/h], now is [kw/yr/k$]", - ) - m.meathane_purge_value = Param( - initialize=3.37, doc="heating value of meathane purge" - ) - m.heating_cost = Param( - initialize=8000.0, doc="Heating cost (steam) with unit [1e6 kj]" - ) - m.cooling_cost = Param( - initialize=700.0, doc="heating cost (water) with unit [1e6 kj]" - ) - m.fuel_cost = Param(initialize=4000.0, doc="fuel cost with unit [1e6 kj]") - m.abs_fixed_cost = Param(initialize=13, doc="fixed cost of absober [$1e3 per year]") - m.abs_linear_coeffcient = Param( - initialize=1.2, - doc="linear coeffcient of absorber (times tray number) [$1e3 per year]", - ) - m.compressor_fixed_cost = Param( - initialize=7.155, doc="compressor fixed cost [$1e3 per year]" - ) - m.compressor_fixed_cost_4 = Param( - initialize=4.866, doc="compressor fixed cost for compressor 4 [$1e3 per year]" - ) - m.compressor_linear_coeffcient = Param( - initialize=0.815, - doc="compressor linear coeffcient (vaporflow rate) [$1e3 per year]", - ) - m.compressor_linear_coeffcient_4 = Param( - initialize=0.887, - doc="compressor linear coeffcient (vaporflow rate) [$1e3 per year]", - ) - m.stabilizing_column_fixed_cost = Param( - initialize=1.126, doc="stabilizing column fixed cost [$1e3 per year]" - ) - m.stabilizing_column_linear_coeffcient = Param( - initialize=0.375, - doc="stabilizing column linear coeffcient (times number of trays) [$1e3 per year]", - ) - m.benzene_column_fixed_cost = Param( - initialize=16.3, doc="benzene column fixed cost [$1e3 per year]" - ) - m.benzene_column_linear_coeffcient = Param( - initialize=1.55, - doc="benzene column linear coeffcient (times number of trays) [$1e3 per year]", - ) - m.toluene_column_fixed_cost = Param( - initialize=3.9, doc="toluene column fixed cost [$1e3 per year]" - ) - m.toluene_column_linear_coeffcient = Param( - initialize=1.12, - doc="toluene column linear coeffcient (times number of trays) [$1e3 per year]", - ) - m.furnace_fixed_cost = Param( - initialize=6.20, doc="toluene column fixed cost [$1e3 per year]" - ) - m.furnace_linear_coeffcient = Param( - initialize=1171.7, - doc="furnace column linear coeffcient [1e9kj/yr] [$1e3 per year]", - ) - m.membrane_seperator_fixed_cost = Param( - initialize=43.24, doc="membrane seperator fixed cost [$1e3 per year]" - ) - m.membrane_seperator_linear_coeffcient = Param( - initialize=49.0, - doc="furnace column linear coeffcient (times inlet flowrate) [$1e3 per year]", - ) - m.adiabtic_reactor_fixed_cost = Param( - initialize=74.3, doc="adiabatic reactor fixed cost [$1e3 per year]" - ) - m.adiabtic_reactor_linear_coeffcient = Param( - initialize=1.257, - doc="adiabatic reactor linear coeffcient (times reactor volumn) [$1e3 per year]", - ) - m.isothermal_reactor_fixed_cost = Param( - initialize=92.875, doc="isothermal reactor fixed cost [$1e3 per year]" - ) - m.isothermal_reactor_linear_coeffcient = Param( - initialize=1.57125, - doc="isothermal reactor linear coeffcient (times reactor volumn) [$1e3 per year]", - ) - m.h2_feed_cost = Param(initialize=2.5, doc="h2 feed cost (95% h2,5% Ch4)") - m.toluene_feed_cost = Param(initialize=14.0, doc="toluene feed cost (100% toluene)") - m.benzene_product = Param( - initialize=19.9, doc="benzene product profit(benzene >= 99.97%)" - ) - m.diphenyl_product = Param( - initialize=11.84, doc="diphenyl product profit(diphenyl = 100%)" - ) - - def profits_from_paper(m): - return ( - 510.0 - * ( - -m.h2_feed_cost * m.f[1] - - m.toluene_feed_cost * (m.f[66] + m.f[67]) - + m.benzene_product * m.f[31] - + m.diphenyl_product * m.f[35] - + m.hydrogen_purge_value - * (m.fc[4, "h2"] + m.fc[28, "h2"] + m.fc[53, "h2"] + m.fc[55, "h2"]) - + m.meathane_purge_value - * (m.fc[4, "ch4"] + m.fc[28, "ch4"] + m.fc[53, "ch4"] + m.fc[55, "ch4"]) - ) - - m.compressor_linear_coeffcient * (m.elec[1] + m.elec[2] + m.elec[3]) - - m.compressor_linear_coeffcient * m.elec[4] - - m.compressor_fixed_cost - * ( - m.purify_H2.binary_indicator_var - + m.recycle_hydrogen.binary_indicator_var - + m.absorber_hydrogen.binary_indicator_var - ) - - m.compressor_fixed_cost * m.recycle_methane_membrane.binary_indicator_var - - sum((m.electricity_cost * m.elec[comp]) for comp in m.comp) - - ( - m.adiabtic_reactor_fixed_cost * m.adiabatic_reactor.binary_indicator_var - + m.adiabtic_reactor_linear_coeffcient * m.rctvol[1] - ) - - ( - m.isothermal_reactor_fixed_cost - * m.isothermal_reactor.binary_indicator_var - + m.isothermal_reactor_linear_coeffcient * m.rctvol[2] - ) - - m.cooling_cost / 1000 * m.q[2] - - ( - m.stabilizing_column_fixed_cost - * m.methane_distillation_column.binary_indicator_var - + m.stabilizing_column_linear_coeffcient * m.ndist[1] - ) - - ( - m.benzene_column_fixed_cost - + m.benzene_column_linear_coeffcient * m.ndist[2] - ) - - ( - m.toluene_column_fixed_cost - * m.toluene_distillation_column.binary_indicator_var - + m.toluene_column_linear_coeffcient * m.ndist[3] - ) - - ( - m.membrane_seperator_fixed_cost * m.purify_H2.binary_indicator_var - + m.membrane_seperator_linear_coeffcient * m.f[3] - ) - - ( - m.membrane_seperator_fixed_cost - * m.recycle_methane_membrane.binary_indicator_var - + m.membrane_seperator_linear_coeffcient * m.f[54] - ) - - ( - m.abs_fixed_cost * m.absorber_hydrogen.binary_indicator_var - + m.abs_linear_coeffcient * m.nabs[1] - ) - - (m.fuel_cost * m.qfuel[1] + m.furnace_linear_coeffcient * m.qfuel[1]) - - sum(m.cooling_cost * m.qc[hec] for hec in m.hec) - - sum(m.heating_cost * m.qh[heh] for heh in m.heh) - - m.furnace_fixed_cost - ) - - m.obj = Objective(rule=profits_from_paper, sense=maximize) - # def profits_GAMS_file(m): - - # "there are several differences between the data from GAMS file and the paper: 1. all the compressor share the same fixed and linear cost in paper but in GAMS they have different fixed and linear cost in GAMS file. 2. the fixed cost for absorber in GAMS file is 3.0 but in the paper is 13.0, but they are getting the same results 3. the electricity cost is not the same" - - # return 510. * (- m.h2_feed_cost * m.f[1] - m.toluene_feed_cost * (m.f[66] + m.f[67]) + m.benzene_product * m.f[31] + m.diphenyl_product * m.f[35] + m.hydrogen_purge_value * (m.fc[4, 'h2'] + m.fc[28, 'h2'] + m.fc[53, 'h2'] + m.fc[55, 'h2']) + m.meathane_purge_value * (m.fc[4, 'ch4'] + m.fc[28, 'ch4'] + m.fc[53, 'ch4'] + m.fc[55, 'ch4'])) - m.compressor_linear_coefficient * (m.elec[1] + m.elec[2] + m.elec[3]) - m.compressor_linear_coefficient_4 * m.elec[4] - m.compressor_fixed_cost * (m.purify_H2.binary_indicator_var + m.recycle_hydrogen.binary_indicator_var + m.absorber_hydrogen.binary_indicator_var) - m.compressor_fixed_cost_4 * m.recycle_methane_membrane.binary_indicator_var - sum((m.costelec * m.elec[comp]) for comp in m.comp) - (m.adiabtic_reactor_fixed_cost * m.adiabatic_reactor.binary_indicator_var + m.adiabtic_reactor_linear_coefficient * m.rctvol[1]) - (m.isothermal_reactor_fixed_cost * m.isothermal_reactor.binary_indicator_var + m.isothermal_reactor_linear_coefficient * m.rctvol[2]) - m.cooling_cost/1000 * m.q[2] - (m.stabilizing_column_fixed_cost * m.methane_distillation_column.binary_indicator_var +m.stabilizing_column_linear_coefficient * m.ndist[1]) - (m.benzene_column_fixed_cost + m.benzene_column_linear_coefficient * m.ndist[2]) - (m.toluene_column_fixed_cost * m.toluene_distillation_column.binary_indicator_var + m.toluene_column_linear_coefficient * m.ndist[3]) - (m.membrane_separator_fixed_cost * m.purify_H2.binary_indicator_var + m.membrane_separator_linear_coefficient * m.f[3]) - (m.membrane_separator_fixed_cost * m.recycle_methane_membrane.binary_indicator_var + m.membrane_separator_linear_coefficient * m.f[54]) - (3.0 * m.absorber_hydrogen.binary_indicator_var + m.abs_linear_coefficient * m.nabs[1]) - (m.fuel_cost * m.qfuel[1] + m.furnace_linear_coefficient* m.qfuel[1]) - sum(m.cooling_cost * m.qc[hec] for hec in m.hec) - sum(m.heating_cost * m.qh[heh] for heh in m.heh) - m.furnace_fixed_cost - # m.obj = Objective(rule=profits_GAMS_file, sense=maximize) - - return m - - -# %% - - -def solve_with_gdpopt(m): - """ - This function solves model m using GDPOpt - - Parameters - ---------- - m : Pyomo Model - The model to be solved - - Returns - ------- - res : solver results - The result of the optimization - """ - opt = SolverFactory("gdpopt") - res = opt.solve( - m, - tee=True, - strategy="LOA", - # strategy='GLOA', - time_limit=3600, - mip_solver="gams", - mip_solver_args=dict(solver="cplex", warmstart=True), - nlp_solver="gams", - nlp_solver_args=dict( - solver="ipopth", - warmstart=True, - ), - minlp_solver="gams", - minlp_solver_args=dict(solver="dicopt", warmstart=True), - subproblem_presolve=False, - # init_strategy='no_init', - set_cover_iterlim=20, - # calc_disjunctive_bounds=True - ) - return res - - -def solve_with_minlp(m): - """ - This function solves model m using minlp transformation by either Big-M or convex hull - - Parameters - ---------- - m : Pyomo Model - The model to be solved - - Returns - ------- - result : solver results - The result of the optimization - """ - - TransformationFactory("gdp.bigm").apply_to(m, bigM=60) - # TransformationFactory('gdp.hull').apply_to(m) - # result = SolverFactory('baron').solve(m, tee=True) - result = SolverFactory("gams").solve( - m, solver="baron", tee=True, add_options=["option reslim=120;"] - ) - - return result - - -# %% - - -def infeasible_constraints(m): - """ - This function checks infeasible constraint in the model - """ - log_infeasible_constraints(m) - -# %% - -# enumeration each possible route selection by fixing binary variable values in every disjunctions - - -def enumerate_solutions(m): - """ - Enumerate all possible route selections by fixing binary variables in each disjunctions - - Parameters - ---------- - m : Pyomo Model - Pyomo model to be solved - """ - - H2_treatments = ["purify", "none_purify"] - Reactor_selections = ["adiabatic_reactor", "isothermal_reactor"] - Methane_recycle_selections = ["recycle_membrane", "recycle_purge"] - Absorber_recycle_selections = ["no_absorber", "yes_absorber"] - Methane_product_selections = ["methane_flash", "methane_column"] - Toluene_product_selections = ["toluene_flash", "toluene_column"] - - for H2_treatment in H2_treatments: - for Reactor_selection in Reactor_selections: - for Methane_recycle_selection in Methane_recycle_selections: - for Absorber_recycle_selection in Absorber_recycle_selections: - for Methane_product_selection in Methane_product_selections: - for Toluene_product_selection in Toluene_product_selections: - if H2_treatment == "purify": - m.purify_H2.indicator_var.fix(True) - m.no_purify_H2.indicator_var.fix(False) - else: - m.purify_H2.indicator_var.fix(False) - m.no_purify_H2.indicator_var.fix(True) - if Reactor_selection == "adiabatic_reactor": - m.adiabatic_reactor.indicator_var.fix(True) - m.isothermal_reactor.indicator_var.fix(False) - else: - m.adiabatic_reactor.indicator_var.fix(False) - m.isothermal_reactor.indicator_var.fix(True) - if Methane_recycle_selection == "recycle_membrane": - m.recycle_methane_purge.indicator_var.fix(False) - m.recycle_methane_membrane.indicator_var.fix(True) - else: - m.recycle_methane_purge.indicator_var.fix(True) - m.recycle_methane_membrane.indicator_var.fix(False) - if Absorber_recycle_selection == "yes_absorber": - m.absorber_hydrogen.indicator_var.fix(True) - m.recycle_hydrogen.indicator_var.fix(False) - else: - m.absorber_hydrogen.indicator_var.fix(False) - m.recycle_hydrogen.indicator_var.fix(True) - if Methane_product_selection == "methane_column": - m.methane_flash_separation.indicator_var.fix(False) - m.methane_distillation_column.indicator_var.fix(True) - else: - m.methane_flash_separation.indicator_var.fix(True) - m.methane_distillation_column.indicator_var.fix(False) - if Toluene_product_selection == "toluene_column": - m.toluene_flash_separation.indicator_var.fix(False) - m.toluene_distillation_column.indicator_var.fix(True) - else: - m.toluene_flash_separation.indicator_var.fix(True) - m.toluene_distillation_column.indicator_var.fix(False) - opt = SolverFactory("gdpopt") - res = opt.solve( - m, - tee=False, - strategy="LOA", - time_limit=3600, - mip_solver="gams", - mip_solver_args=dict(solver="gurobi", warmstart=True), - nlp_solver="gams", - nlp_solver_args=dict( - solver="ipopth", - add_options=["option optcr = 0"], - warmstart=True, - ), - minlp_solver="gams", - minlp_solver_args=dict(solver="dicopt", warmstart=True), - subproblem_presolve=False, - init_strategy="no_init", - set_cover_iterlim=20, - ) - print( - "{0:<30}{1:<30}{2:<30}{3:<30}{4:<30}{5:<30}{6:<30}{7:<30}".format( - H2_treatment, - Reactor_selection, - Methane_recycle_selection, - Absorber_recycle_selection, - Methane_product_selection, - Toluene_product_selection, - str(res.solver.termination_condition), - value(m.obj), - ) - ) - - -# %% -def show_decision(m): - """ - print indicator variable value - """ - if value(m.purify_H2.binary_indicator_var) == 1: - print("purify inlet H2") - else: - print("no purify inlet H2") - if value(m.adiabatic_reactor.binary_indicator_var) == 1: - print("adiabatic reactor") - else: - print("isothermal reactor") - if value(m.recycle_methane_membrane.binary_indicator_var) == 1: - print("recycle_membrane") - else: - print("methane purge") - if value(m.absorber_hydrogen.binary_indicator_var) == 1: - print("yes_absorber") - else: - print("no_absorber") - if value(m.methane_distillation_column.binary_indicator_var) == 1: - print("methane_column") - else: - print("methane_flash") - if value(m.toluene_distillation_column.binary_indicator_var) == 1: - print("toluene_column") - else: - print("toluene_flash") - - -# %% - - -if __name__ == "__main__": - # Create GDP model - m = HDA_model() - - # Solve model - res = solve_with_gdpopt(m) - # res = solve_with_minlp(m) - - # Enumerate all solutions - # res = enumerate_solutions(m) - - # Check if constraints are violated - infeasible_constraints(m) - - # show optimal flowsheet selection - # show_decision(m) - - print(res) +""" +HDA_GDP_gdpopt.py +This model describes the profit maximization of a Hydrodealkylation of Toluene process, first presented in Reference [1], and later implemented as a GDP in Reference [2]. The MINLP formulation of this problem is available in GAMS, Reference [3]. + +The chemical plant performed the hydro-dealkylation of toluene into benzene and methane. The flowsheet model was used to make decisions on choosing between alternative process units at various stages of the process. The resulting model is GDP model. The disjunctions in the model include: + 1. Inlet purify selection at feed + 2. Reactor operation mode selection (adiabatic / isothermal) + 3. Vapor recovery methane purge / recycle with membrane + 4. Vapor recovery hydrogen recycle + 5. Liquid separation system methane stabilizing via column or flash drum + 6. Liquid separation system toluene recovery via column or flash drum + +The model enforces constraints to ensure that the mass and energy balances are satisfied, the purity of the products is within the required limits, the recovery specification are met, and the temperature and pressure conditions in the process units are maintained within the operational limits. + +The objective of the model is to maximize the profit by determining the optimal process configuration and operating conditions. The decision variables include the number of trays in the absorber and distillation column, the reflux ratio, the pressure in the distillation column, the temperature and pressure in the flash drums, the heating requirement in the furnace, the electricity requirement in the compressor, the heat exchange in the coolers and heaters, the surface area in the membrane separators, the temperature and pressure in the mixers, the temperature and pressure in the reactors, and the volume and rate constant in the reactors. + +References: + [1] James M Douglas (1988). Conceptual Design of Chemical Processes, McGraw-Hill. ISBN-13: 978-0070177628 + [2] G.R. Kocis, and I.E. Grossmann (1989). Computational Experience with DICOPT Solving MINLP Problems in Process Synthesis. Computers and Chemical Engineering 13, 3, 307-315. https://doi.org/10.1016/0098-1354(89)85008-2 + [3] GAMS Development Corporation (2023). Hydrodealkylation Process. Available at: https://www.gams.com/latest/gamslib_ml/libhtml/gamslib_hda.html +""" + +import math +import os +import pandas as pd + +from pyomo.environ import * +from pyomo.gdp import * +from pyomo.util.infeasible import log_infeasible_constraints + + +def HDA_model(): + """ + Builds the Hydrodealkylation of Toluene process model. + + Parameters + ---------- + alpha : float + compressor coefficient + compeff : float + compressor efficiency + gam : float + ratio of cp to cv + abseff : float + absorber tray efficiency + disteff : float + column tray efficiency + uflow : float + upper bound - flow logicals + upress : float + upper bound - pressure logicals + utemp : float + upper bound - temperature logicals + costelec : float + electricity cost + costqc : float + cooling cost + costqh : float + heating cost + costfuel : float + fuel cost furnace + furnpdrop : float + pressure drop of furnace + heatvap : float + heat of vaporization [kj per kg-mol] + cppure : float + pure component heat capacities [kj per kg-mol-k] + gcomp : float + guess composition values + cp : float + heat capacities [kj per kg-mol-k] + anta : float + antoine coefficient A + antb : float + antoine coefficient B + antc : float + antoine coefficient C + perm : float + permeability [kg-mole/m**2-min-mpa] + cbeta : float + constant values (exp(beta)) in absorber + aabs : float + absorption factors + eps1 : float + small number to avoid div. by zero + heatrxn : float + heat of reaction [kj per kg-mol] + f1comp : float + feedstock compositions (h2 feed) + f66comp : float + feedstock compositions (tol feed) + f67comp : float + feedstock compositions (tol feed) + + Sets + ---- + str : int + process streams + compon : str + chemical components + abs : int + absorber + comp : int + compressor + dist : int + distillation column + flsh : int + flash drums + furn : int + furnace + hec : int + coolers + heh : int + heaters + exch : int + heat exchangers + memb : int + membrane separators + mxr1 : int + single inlet stream mixers + mxr : int + mixers + pump : int + pumps + rct : int + reactors + spl1 : int + single outlet stream splitters + spl : int + splitter + valve : int + expansion valve + str2 : int + process streams + compon2 : str + chemical components + + + Returns + ------- + m : Pyomo ConcreteModel + Pyomo model of the Hydrodealkylation of Toluene process + + """ + dir_path = os.path.dirname(os.path.abspath(__file__)) + + m = ConcreteModel() + + # ## scalars + + m.alpha = Param(initialize=0.3665, doc="compressor coefficient") + m.compeff = Param(initialize=0.750, doc="compressor efficiency") + m.gam = Param(initialize=1.300, doc="ratio of cp to cv") + m.abseff = Param(initialize=0.333, doc="absorber tray efficiency") + m.disteff = Param(initialize=0.5000, doc="column tray efficiency") + m.uflow = Param(initialize=50, doc="upper bound - flow logicals") + m.upress = Param(initialize=4.0, doc="upper bound - pressure logicals") + m.utemp = Param(initialize=7.0, doc="upper bound - temperature logicals") + m.costelec = Param(initialize=0.340, doc="electricity cost") + m.costqc = Param(initialize=0.7000, doc="cooling cost") + m.costqh = Param(initialize=8.0000, doc="heating cost") + m.costfuel = Param(initialize=4.0, doc="fuel cost furnace") + m.furnpdrop = Param(initialize=0.4826, doc="pressure drop of furnace") + + # ## sets + + def strset(i): + """ + Process streams + + Returns + ------- + s : list + integer list from 1 to 74 + """ + s = [] + i = 1 + for i in range(1, 36): + s.append(i) + i += i + i = 37 + for i in range(37, 74): + s.append(i) + i += i + return s + + m.str = Set(initialize=strset, doc="process streams") + m.compon = Set( + initialize=["h2", "ch4", "ben", "tol", "dip"], doc="chemical components" + ) + m.abs = RangeSet(1) + m.comp = RangeSet(4) + m.dist = RangeSet(3) + m.flsh = RangeSet(3) + m.furn = RangeSet(1) + m.hec = RangeSet(2) + m.heh = RangeSet(4) + m.exch = RangeSet(1) + m.memb = RangeSet(2) + m.mxr1 = RangeSet(5) + m.mxr = RangeSet(5) + m.pump = RangeSet(2) + m.rct = RangeSet(2) + m.spl1 = RangeSet(6) + m.spl = RangeSet(3) + m.valve = RangeSet(6) + m.str2 = Set(initialize=strset, doc="process streams") + m.compon2 = Set( + initialize=["h2", "ch4", "ben", "tol", "dip"], doc="chemical components" + ) + + # parameters + Heatvap = {} + Heatvap["tol"] = 30890.00 + m.heatvap = Param( + m.compon, + initialize=Heatvap, + default=0, + doc="heat of vaporization [kj per kg-mol]", + ) + Cppure = {} + # h2 'hydrogen', ch4 'methane', ben 'benzene', tol 'toluene', dip 'diphenyl' + Cppure["h2"] = 30 + Cppure["ch4"] = 40 + Cppure["ben"] = 225 + Cppure["tol"] = 225 + Cppure["dip"] = 450 + m.cppure = Param( + m.compon, + initialize=Cppure, + default=0, + doc="pure component heat capacities [kj per kg-mol-k]", + ) + Gcomp = {} + Gcomp[7, "h2"] = 0.95 + Gcomp[7, "ch4"] = 0.05 + Gcomp[8, "h2"] = 0.5 + Gcomp[8, "ch4"] = 0.40 + Gcomp[8, "tol"] = 0.1 + Gcomp[9, "h2"] = 0.5 + Gcomp[9, "ch4"] = 0.40 + Gcomp[9, "tol"] = 0.1 + Gcomp[10, "h2"] = 0.5 + Gcomp[10, "ch4"] = 0.40 + Gcomp[10, "tol"] = 0.1 + Gcomp[11, "h2"] = 0.45 + Gcomp[11, "ben"] = 0.05 + Gcomp[11, "ch4"] = 0.45 + Gcomp[11, "tol"] = 0.05 + Gcomp[12, "h2"] = 0.50 + Gcomp[12, "ch4"] = 0.40 + Gcomp[12, "tol"] = 0.10 + Gcomp[13, "h2"] = 0.45 + Gcomp[13, "ch4"] = 0.45 + Gcomp[13, "ben"] = 0.05 + Gcomp[13, "tol"] = 0.05 + Gcomp[14, "h2"] = 0.45 + Gcomp[14, "ch4"] = 0.45 + Gcomp[14, "ben"] = 0.05 + Gcomp[14, "tol"] = 0.05 + Gcomp[15, "h2"] = 0.45 + Gcomp[15, "ch4"] = 0.45 + Gcomp[15, "ben"] = 0.05 + Gcomp[15, "tol"] = 0.05 + Gcomp[16, "h2"] = 0.4 + Gcomp[16, "ch4"] = 0.4 + Gcomp[16, "ben"] = 0.1 + Gcomp[16, "tol"] = 0.1 + Gcomp[17, "h2"] = 0.40 + Gcomp[17, "ch4"] = 0.40 + Gcomp[17, "ben"] = 0.1 + Gcomp[17, "tol"] = 0.1 + Gcomp[20, "h2"] = 0.03 + Gcomp[20, "ch4"] = 0.07 + Gcomp[20, "ben"] = 0.55 + Gcomp[20, "tol"] = 0.35 + Gcomp[21, "h2"] = 0.03 + Gcomp[21, "ch4"] = 0.07 + Gcomp[21, "ben"] = 0.55 + Gcomp[21, "tol"] = 0.35 + Gcomp[22, "h2"] = 0.03 + Gcomp[22, "ch4"] = 0.07 + Gcomp[22, "ben"] = 0.55 + Gcomp[22, "tol"] = 0.35 + Gcomp[24, "h2"] = 0.03 + Gcomp[24, "ch4"] = 0.07 + Gcomp[24, "ben"] = 0.55 + Gcomp[24, "tol"] = 0.35 + Gcomp[25, "h2"] = 0.03 + Gcomp[25, "ch4"] = 0.07 + Gcomp[25, "ben"] = 0.55 + Gcomp[25, "tol"] = 0.35 + Gcomp[37, "tol"] = 1.00 + Gcomp[38, "tol"] = 1.00 + Gcomp[43, "ben"] = 0.05 + Gcomp[43, "tol"] = 0.95 + Gcomp[44, "h2"] = 0.03 + Gcomp[44, "ch4"] = 0.07 + Gcomp[44, "ben"] = 0.55 + Gcomp[44, "tol"] = 0.35 + Gcomp[45, "h2"] = 0.03 + Gcomp[45, "ch4"] = 0.07 + Gcomp[45, "ben"] = 0.55 + Gcomp[45, "tol"] = 0.35 + Gcomp[46, "h2"] = 0.03 + Gcomp[46, "ch4"] = 0.07 + Gcomp[46, "ben"] = 0.55 + Gcomp[46, "tol"] = 0.35 + Gcomp[51, "h2"] = 0.30 + Gcomp[51, "ch4"] = 0.70 + Gcomp[57, "h2"] = 0.80 + Gcomp[57, "ch4"] = 0.20 + Gcomp[60, "h2"] = 0.50 + Gcomp[60, "ch4"] = 0.50 + Gcomp[62, "h2"] = 0.50 + Gcomp[62, "ch4"] = 0.50 + Gcomp[63, "h2"] = 0.47 + Gcomp[63, "ch4"] = 0.40 + Gcomp[63, "ben"] = 0.01 + Gcomp[63, "tol"] = 0.12 + Gcomp[65, "h2"] = 0.50 + Gcomp[65, "ch4"] = 0.50 + Gcomp[66, "tol"] = 1.0 + Gcomp[69, "tol"] = 1.0 + Gcomp[70, "h2"] = 0.5 + Gcomp[70, "ch4"] = 0.4 + Gcomp[70, "tol"] = 0.10 + Gcomp[71, "h2"] = 0.40 + Gcomp[71, "ch4"] = 0.40 + Gcomp[71, "ben"] = 0.10 + Gcomp[71, "tol"] = 0.10 + Gcomp[72, "h2"] = 0.50 + Gcomp[72, "ch4"] = 0.50 + m.gcomp = Param( + m.str, m.compon, initialize=Gcomp, default=0, doc="guess composition values" + ) + + def cppara(compon, stream): + """ + heat capacities [kj per kg-mol-k] + sum of heat capacities of all components in a stream, weighted by their composition + """ + return sum(m.cppure[compon] * m.gcomp[stream, compon] for compon in m.compon) + + m.cp = Param( + m.str, initialize=cppara, default=0, doc="heat capacities [kj per kg-mol-k]" + ) + + Anta = {} + Anta["h2"] = 13.6333 + Anta["ch4"] = 15.2243 + Anta["ben"] = 15.9008 + Anta["tol"] = 16.0137 + Anta["dip"] = 16.6832 + m.anta = Param(m.compon, initialize=Anta, default=0, doc="antoine coefficient A") + + Antb = {} + Antb["h2"] = 164.9 + Antb["ch4"] = 897.84 + Antb["ben"] = 2788.51 + Antb["tol"] = 3096.52 + Antb["dip"] = 4602.23 + m.antb = Param(m.compon, initialize=Antb, default=0, doc="antoine coefficient B") + + Antc = {} + Antc["h2"] = 3.19 + Antc["ch4"] = -7.16 + Antc["ben"] = -52.36 + Antc["tol"] = -53.67 + Antc["dip"] = -70.42 + m.antc = Param(m.compon, initialize=Antc, default=0, doc="antoine coefficient C") + + Perm = {} + for i in m.compon: + Perm[i] = 0 + Perm["h2"] = 55.0e-06 + Perm["ch4"] = 2.3e-06 + + def Permset(m, compon): + """ + permeability [kg-mole/m**2-min-mpa] + converting unit for permeability from cc/cm**2-sec-cmHg to kg-mole/m**2-min-mpa + """ + return Perm[compon] * (1.0 / 22400.0) * 1.0e4 * 750.062 * 60.0 / 1000.0 + + m.perm = Param( + m.compon, + initialize=Permset, + default=0, + doc="permeability [kg-mole/m**2-min-mpa]", + ) + + Cbeta = {} + Cbeta["h2"] = 1.0003 + Cbeta["ch4"] = 1.0008 + Cbeta["dip"] = 1.0e04 + m.cbeta = Param( + m.compon, + initialize=Cbeta, + default=0, + doc="constant values (exp(beta)) in absorber", + ) + + Aabs = {} + Aabs["ben"] = 1.4 + Aabs["tol"] = 4.0 + m.aabs = Param(m.compon, initialize=Aabs, default=0, doc="absorption factors") + m.eps1 = Param(initialize=1e-4, doc="small number to avoid div. by zero") + + Heatrxn = {} + Heatrxn[1] = 50100.0 + Heatrxn[2] = 50100.0 + m.heatrxn = Param( + m.rct, initialize=Heatrxn, default=0, doc="heat of reaction [kj per kg-mol]" + ) + + F1comp = {} + F1comp["h2"] = 0.95 + F1comp["ch4"] = 0.05 + F1comp["dip"] = 0.00 + F1comp["ben"] = 0.00 + F1comp["tol"] = 0.00 + m.f1comp = Param( + m.compon, initialize=F1comp, default=0, doc="feedstock compositions (h2 feed)" + ) + + F66comp = {} + F66comp["tol"] = 1.0 + F66comp["h2"] = 0.00 + F66comp["ch4"] = 0.00 + F66comp["dip"] = 0.00 + F66comp["ben"] = 0.00 + m.f66comp = Param( + m.compon, initialize=F66comp, default=0, doc="feedstock compositions (tol feed)" + ) + + F67comp = {} + F67comp["tol"] = 1.0 + F67comp["h2"] = 0.00 + F67comp["ch4"] = 0.00 + F67comp["dip"] = 0.00 + F67comp["ben"] = 0.00 + m.f67comp = Param( + m.compon, initialize=F67comp, default=0, doc="feedstock compositions (tol feed)" + ) + + # # matching streams + m.ilabs = Set(initialize=[(1, 67)], doc="abs-stream (inlet liquid) matches") + m.olabs = Set(initialize=[(1, 68)], doc="abs-stream (outlet liquid) matches") + m.ivabs = Set(initialize=[(1, 63)], doc="abs-stream (inlet vapor) matches") + m.ovabs = Set(initialize=[(1, 64)], doc="abs-stream (outlet vapor) matches") + m.asolv = Set(initialize=[(1, "tol")], doc="abs-solvent component matches") + m.anorm = Set(initialize=[(1, "ben")], doc="abs-comp matches (normal model)") + m.asimp = Set( + initialize=[(1, "h2"), (1, "ch4"), (1, "dip")], + doc="abs-heavy component matches", + ) + + m.icomp = Set( + initialize=[(1, 5), (2, 59), (3, 64), (4, 56)], + doc="compressor-stream (inlet) matches", + ) + m.ocomp = Set( + initialize=[(1, 6), (2, 60), (3, 65), (4, 57)], + doc="compressor-stream (outlet) matches", + ) + + m.idist = Set( + initialize=[(1, 25), (2, 30), (3, 33)], doc="dist-stream (inlet) matches" + ) + m.vdist = Set( + initialize=[(1, 26), (2, 31), (3, 34)], doc="dist-stream (vapor) matches" + ) + m.ldist = Set( + initialize=[(1, 27), (2, 32), (3, 35)], doc="dist-stream (liquid) matches" + ) + m.dl = Set( + initialize=[(1, "h2"), (2, "ch4"), (3, "ben")], + doc="dist-light components matches", + ) + m.dlkey = Set( + initialize=[(1, "ch4"), (2, "ben"), (3, "tol")], + doc="dist-heavy key component matches", + ) + m.dhkey = Set( + initialize=[(1, "ben"), (2, "tol"), (3, "dip")], + doc="dist-heavy components matches", + ) + m.dh = Set( + initialize=[(1, "tol"), (1, "dip"), (2, "dip")], + doc="dist-key component matches", + ) + + i = list(m.dlkey) + q = list(m.dhkey) + dkeyset = i + q + m.dkey = Set(initialize=dkeyset, doc="dist-key component matches") + + m.iflsh = Set( + initialize=[(1, 17), (2, 46), (3, 39)], doc="flsh-stream (inlet) matches" + ) + m.vflsh = Set( + initialize=[(1, 18), (2, 47), (3, 40)], doc="flsh-stream (vapor) matches" + ) + m.lflsh = Set( + initialize=[(1, 19), (2, 48), (3, 41)], doc="flsh-stream (liquid) matches" + ) + m.fkey = Set( + initialize=[(1, "ch4"), (2, "ch4"), (3, "tol")], + doc="flash-key component matches", + ) + + m.ifurn = Set(initialize=[(1, 70)], doc="furn-stream (inlet) matches") + m.ofurn = Set(initialize=[(1, 9)], doc="furn-stream (outlet) matches") + + m.ihec = Set(initialize=[(1, 71), (2, 45)], doc="hec-stream (inlet) matches") + m.ohec = Set(initialize=[(1, 17), (2, 46)], doc="hec-stream (outlet) matches") + + m.iheh = Set( + initialize=[(1, 24), (2, 23), (3, 37), (4, 61)], + doc="heh-stream (inlet) matches", + ) + m.oheh = Set( + initialize=[(1, 25), (2, 44), (3, 38), (4, 73)], + doc="heh-stream (outlet) matches", + ) + + m.icexch = Set(initialize=[(1, 8)], doc="exch-cold stream (inlet) matches") + m.ocexch = Set(initialize=[(1, 70)], doc="exch-cold stream (outlet) matches") + m.ihexch = Set(initialize=[(1, 16)], doc="exch-hot stream (inlet) matches") + m.ohexch = Set(initialize=[(1, 71)], doc="exch-hot stream (outlet) matches") + + m.imemb = Set(initialize=[(1, 3), (2, 54)], doc="memb-stream (inlet) matches") + m.nmemb = Set( + initialize=[(1, 4), (2, 55)], doc="memb-stream (non-permeate) matches" + ) + m.pmemb = Set(initialize=[(1, 5), (2, 56)], doc="memb-stream (permeate) matches") + m.mnorm = Set( + initialize=[(1, "h2"), (1, "ch4"), (2, "h2"), (2, "ch4")], + doc="normal components", + ) + m.msimp = Set( + initialize=[ + (1, "ben"), + (1, "tol"), + (1, "dip"), + (2, "ben"), + (2, "tol"), + (2, "dip"), + ], + doc="simplified flux components", + ) + + m.imxr1 = Set( + initialize=[ + (1, 2), + (1, 6), + (2, 11), + (2, 13), + (3, 27), + (3, 48), + (4, 34), + (4, 40), + (5, 49), + (5, 50), + ], + doc="mixer-stream (inlet) matches", + ) + m.omxr1 = Set( + initialize=[(1, 7), (2, 14), (3, 30), (4, 42), (5, 51)], + doc="mixer-stream (outlet) matches", + ) + m.mxr1spl1 = Set( + initialize=[ + (1, 2, 2), + (1, 6, 3), + (2, 11, 10), + (2, 13, 12), + (3, 27, 24), + (3, 48, 23), + (4, 34, 33), + (4, 40, 37), + (5, 49, 23), + (5, 50, 24), + ], + doc="1-mxr-inlet 1-spl-outlet matches", + ) + + m.imxr = Set( + initialize=[ + (1, 7), + (1, 43), + (1, 66), + (1, 72), + (2, 15), + (2, 20), + (3, 21), + (3, 69), + (4, 51), + (4, 62), + (5, 57), + (5, 60), + (5, 65), + ], + doc="mixer-stream (inlet) matches", + ) + m.omxr = Set( + initialize=[(1, 8), (2, 16), (3, 22), (4, 63), (5, 72)], + doc="mixer-stream (outlet) matches ", + ) + + m.ipump = Set(initialize=[(1, 42), (2, 68)], doc="pump-stream (inlet) matches") + m.opump = Set(initialize=[(1, 43), (2, 69)], doc="pump-stream (outlet) matches") + + m.irct = Set(initialize=[(1, 10), (2, 12)], doc="reactor-stream (inlet) matches") + m.orct = Set(initialize=[(1, 11), (2, 13)], doc="reactor-stream (outlet) matches") + m.rkey = Set( + initialize=[(1, "tol"), (2, "tol")], doc="reactor-key component matches" + ) + + m.ispl1 = Set( + initialize=[(1, 1), (2, 9), (3, 22), (4, 32), (5, 52), (6, 58)], + doc="splitter-stream (inlet) matches", + ) + m.ospl1 = Set( + initialize=[ + (1, 2), + (1, 3), + (2, 10), + (2, 12), + (3, 23), + (3, 24), + (4, 33), + (4, 37), + (5, 53), + (5, 54), + (6, 59), + (6, 61), + ], + doc="splitter-stream (outlet) matches", + ) + + m.ispl = Set( + initialize=[(1, 19), (2, 18), (3, 26)], doc="splitter-stream (inlet) matches" + ) + m.ospl = Set( + initialize=[(1, 20), (1, 21), (2, 52), (2, 58), (3, 28), (3, 29)], + doc="splitter-stream (outlet) matches", + ) + + m.ival = Set( + initialize=[(1, 44), (2, 38), (3, 14), (4, 47), (5, 29), (6, 73)], + doc="exp.valve-stream (inlet) matches", + ) + m.oval = Set( + initialize=[(1, 45), (2, 39), (3, 15), (4, 49), (5, 50), (6, 62)], + doc="exp.valve-stream (outlet) matches", + ) + + # variables + + # absorber + m.nabs = Var( + m.abs, + within=NonNegativeReals, + bounds=(0, 40), + initialize=1, + doc="number of absorber trays", + ) + m.gamma = Var(m.abs, m.compon, within=Reals, initialize=1, doc="gamma") + m.beta = Var(m.abs, m.compon, within=Reals, initialize=1, doc="beta") + + # compressor + m.elec = Var( + m.comp, + within=NonNegativeReals, + bounds=(0, 100), + initialize=1, + doc="electricity requirement [kw]", + ) + m.presrat = Var( + m.comp, + within=NonNegativeReals, + bounds=(1, 8 / 3), + initialize=1, + doc="ratio of outlet to inlet pressure", + ) + + # distillation + m.nmin = Var( + m.dist, + within=NonNegativeReals, + initialize=1, + doc="minimum number of trays in column", + ) + m.ndist = Var( + m.dist, within=NonNegativeReals, initialize=1, doc="number of trays in column" + ) + m.rmin = Var( + m.dist, within=NonNegativeReals, initialize=1, doc="minimum reflux ratio" + ) + m.reflux = Var(m.dist, within=NonNegativeReals, initialize=1, doc="reflux ratio") + m.distp = Var( + m.dist, + within=NonNegativeReals, + initialize=1, + bounds=(0.1, 4.0), + doc="column pressure [mega-pascal]", + ) + m.avevlt = Var( + m.dist, within=NonNegativeReals, initialize=1, doc="average volatility" + ) + + # flash + m.flsht = Var( + m.flsh, within=NonNegativeReals, initialize=1, doc="flash temperature [100 k]" + ) + m.flshp = Var( + m.flsh, + within=NonNegativeReals, + initialize=1, + doc="flash pressure [mega-pascal]", + ) + m.eflsh = Var( + m.flsh, + m.compon, + within=NonNegativeReals, + bounds=(0, 1), + initialize=0.5, + doc="vapor phase recovery in flash", + ) + + # furnace + m.qfuel = Var( + m.furn, + within=NonNegativeReals, + bounds=(None, 10), + initialize=1, + doc="heating required [1.e+12 kj per yr]", + ) + # cooler + m.qc = Var( + m.hec, + within=NonNegativeReals, + bounds=(None, 10), + initialize=1, + doc="utility requirement [1.e+12 kj per yr]", + ) + # heater + m.qh = Var( + m.heh, + within=NonNegativeReals, + bounds=(None, 10), + initialize=1, + doc="utility requirement [1.e+12 kj per yr]", + ) + # exchanger + m.qexch = Var( + m.exch, + within=NonNegativeReals, + bounds=(None, 10), + initialize=1, + doc="heat exchanged [1.e+12 kj per yr]", + ) + # membrane + m.a = Var( + m.memb, + within=NonNegativeReals, + bounds=(100, 10000), + initialize=1, + doc="surface area for mass transfer [m**2]", + ) + # mixer(1 input) + m.mxr1p = Var( + m.mxr1, + within=NonNegativeReals, + bounds=(0.1, 4), + initialize=0, + doc="mixer temperature [100 k]", + ) + m.mxr1t = Var( + m.mxr1, + within=NonNegativeReals, + bounds=(3, 10), + initialize=0, + doc="mixer pressure [mega-pascal]", + ) + # mixer + m.mxrt = Var( + m.mxr, + within=NonNegativeReals, + bounds=(3.0, 10), + initialize=3, + doc="mixer temperature [100 k]", + ) + m.mxrp = Var( + m.mxr, + within=NonNegativeReals, + bounds=(0.1, 4.0), + initialize=3, + doc="mixer pressure [mega-pascal]", + ) + # reactor + m.rctt = Var( + m.rct, + within=NonNegativeReals, + bounds=(8.9427, 9.7760), + doc="reactor temperature [100 k]", + ) + m.rctp = Var( + m.rct, + within=NonNegativeReals, + bounds=(3.4474, 3.4474), + doc="reactor pressure [mega-pascal]", + ) + m.rctvol = Var( + m.rct, + within=NonNegativeReals, + bounds=(None, 200), + doc="reactor volume [cubic meter]", + ) + m.krct = Var( + m.rct, + within=NonNegativeReals, + initialize=1, + bounds=(0.0123471, 0.149543), + doc="rate constant", + ) + m.conv = Var( + m.rct, + m.compon, + within=NonNegativeReals, + bounds=(None, 0.973), + doc="conversion of key component", + ) + m.sel = Var( + m.rct, + within=NonNegativeReals, + bounds=(None, 0.9964), + doc="selectivity to benzene", + ) + m.consum = Var( + m.rct, + m.compon, + within=NonNegativeReals, + bounds=(0, 10000000000), + initialize=0, + doc="consumption rate of key", + ) + m.q = Var( + m.rct, + within=NonNegativeReals, + bounds=(0, 10000000000), + doc="heat removed [1.e+9 kj per yr]", + ) + # splitter (1 output) + m.spl1t = Var( + m.spl1, + within=PositiveReals, + bounds=(3.00, 10.00), + doc="splitter temperature [100 k]", + ) + m.spl1p = Var( + m.spl1, + within=PositiveReals, + bounds=(0.1, 4.0), + doc="splitter pressure [mega-pascal]", + ) + # splitter + m.splp = Var( + m.spl, within=Reals, bounds=(0.1, 4.0), doc="splitter pressure [mega-pascal]" + ) + m.splt = Var( + m.spl, within=Reals, bounds=(3.0, 10.0), doc="splitter temperature [100 k]" + ) + + # stream + def bound_f(m, stream): + """ + stream flowrates [kg-mole per min] + setting appropriate bounds for stream flowrates + """ + if stream in range(8, 19): + return (0, 50) + elif stream in [52, 54, 56, 57, 58, 59, 60, 70, 71, 72]: + return (0, 50) + else: + return (0, 10) + + m.f = Var( + m.str, + within=NonNegativeReals, + bounds=bound_f, + initialize=1, + doc="stream flowrates [kg-mole per min]", + ) + + def bound_fc(m, stream, compon): + """ + setting appropriate bounds for component flowrates + """ + if stream in range(8, 19) or stream in [52, 54, 56, 57, 58, 59, 60, 70, 71, 72]: + return (0, 30) + else: + return (0, 10) + + m.fc = Var( + m.str, + m.compon, + within=Reals, + bounds=bound_fc, + initialize=1, + doc="component flowrates [kg-mole per min]", + ) + m.p = Var( + m.str, + within=NonNegativeReals, + bounds=(0.1, 4.0), + initialize=3.0, + doc="stream pressure [mega-pascal]", + ) + m.t = Var( + m.str, + within=NonNegativeReals, + bounds=(3.0, 10.0), + initialize=3.0, + doc="stream temperature [100 k]", + ) + m.vp = Var( + m.str, + m.compon, + within=NonNegativeReals, + initialize=1, + bounds=(0, 10), + doc="vapor pressure [mega-pascal]", + ) + + def boundsofe(m): + """ + setting appropriate bounds for split fraction + """ + if i == 20: + return (None, 0.5) + elif i == 21: + return (0.5, 1.0) + else: + return (None, 1.0) + + m.e = Var(m.str, within=NonNegativeReals, bounds=boundsofe, doc="split fraction") + + # obj function constant term + m.const = Param(initialize=22.5, doc="constant term in obj fcn") + + # ## setting variable bounds + + m.q[2].setub(100) + for rct in m.rct: + m.conv[rct, "tol"].setub(0.973) + m.sel.setub(1.0 - 0.0036) + m.reflux[1].setlb(0.02 * 1.2) + m.reflux[1].setub(0.10 * 1.2) + m.reflux[2].setlb(0.50 * 1.2) + m.reflux[2].setub(2.00 * 1.2) + m.reflux[3].setlb(0.02 * 1.2) + m.reflux[3].setub(0.1 * 1.2) + m.nmin[1].setlb(0) + m.nmin[1].setub(4) + m.nmin[2].setlb(8) + m.nmin[2].setub(14) + m.nmin[3].setlb(0) + m.nmin[3].setub(4) + m.ndist[1].setlb(0) + m.ndist[1].setub(4 * 2 / m.disteff) + m.ndist[3].setlb(0) + m.ndist[3].setub(4 * 2 / m.disteff) + m.ndist[2].setlb(8 * 2 / m.disteff) + m.ndist[2].setub(14 * 2 / m.disteff) + m.rmin[1].setlb(0.02) + m.rmin[1].setub(0.10) + m.rmin[2].setlb(0.50) + m.rmin[2].setub(2.00) + m.rmin[3].setlb(0.02) + m.rmin[3].setub(0.1) + m.distp[1].setub(1.0200000000000002) + m.distp[1].setlb(1.0200000000000002) + m.distp[2].setub(0.4) + m.distp[3].setub(0.250) + m.t[26].setlb(3.2) + m.t[26].setub(3.2) + for i in range(49, 52): + m.t[i].setlb(2.0) + m.t[27].setlb( + ( + m.antb["ben"] / (m.anta["ben"] - log(m.distp[1].lb * 7500.6168)) + - m.antc["ben"] + ) + / 100.0 + ) + m.t[27].setub( + ( + m.antb["ben"] / (m.anta["ben"] - log(m.distp[1].ub * 7500.6168)) + - m.antc["ben"] + ) + / 100.0 + ) + m.t[31].setlb( + ( + m.antb["ben"] / (m.anta["ben"] - log(m.distp[2].lb * 7500.6168)) + - m.antc["ben"] + ) + / 100.0 + ) + m.t[31].setub( + ( + m.antb["ben"] / (m.anta["ben"] - log(m.distp[2].ub * 7500.6168)) + - m.antc["ben"] + ) + / 100.0 + ) + m.t[32].setlb( + ( + m.antb["tol"] / (m.anta["tol"] - log(m.distp[2].lb * 7500.6168)) + - m.antc["tol"] + ) + / 100.0 + ) + m.t[32].setub( + ( + m.antb["tol"] / (m.anta["tol"] - log(m.distp[2].ub * 7500.6168)) + - m.antc["tol"] + ) + / 100.0 + ) + m.t[34].setlb( + ( + m.antb["tol"] / (m.anta["tol"] - log(m.distp[3].lb * 7500.6168)) + - m.antc["tol"] + ) + / 100.0 + ) + m.t[34].setub( + ( + m.antb["tol"] / (m.anta["tol"] - log(m.distp[3].ub * 7500.6168)) + - m.antc["tol"] + ) + / 100.0 + ) + m.t[35].setlb( + ( + m.antb["dip"] / (m.anta["dip"] - log(m.distp[3].lb * 7500.6168)) + - m.antc["dip"] + ) + / 100.0 + ) + m.t[35].setub( + ( + m.antb["dip"] / (m.anta["dip"] - log(m.distp[3].ub * 7500.6168)) + - m.antc["dip"] + ) + / 100.0 + ) + + # absorber + m.beta[1, "ben"].setlb(0.00011776) + m.beta[1, "ben"].setub(5.72649) + m.beta[1, "tol"].setlb(0.00018483515) + m.beta[1, "tol"].setub(15) + m.gamma[1, "tol"].setlb( + log( + (1 - m.aabs["tol"] ** (m.nabs[1].lb * m.abseff + m.eps1)) + / (1 - m.aabs["tol"]) + ) + ) + m.gamma[1, "tol"].setub( + min( + 15, + log( + (1 - m.aabs["tol"] ** (m.nabs[1].ub * m.abseff + m.eps1)) + / (1 - m.aabs["tol"]) + ), + ) + ) + for abso in m.abs: + for compon in m.compon: + m.beta[abso, compon].setlb( + log( + (1 - m.aabs[compon] ** (m.nabs[1].lb * m.abseff + m.eps1 + 1)) + / (1 - m.aabs[compon]) + ) + ) + m.beta[abso, compon].setub( + min( + 15, + log( + (1 - m.aabs[compon] ** (m.nabs[1].ub * m.abseff + m.eps1 + 1)) + / (1 - m.aabs[compon]) + ), + ) + ) + m.t[67].setlb(3.0) + m.t[67].setub(3.0) + for compon in m.compon: + m.vp[67, compon].setlb( + (1.0 / 7500.6168) + * exp( + m.anta[compon] + - m.antb[compon] / (value(m.t[67]) * 100.0 + m.antc[compon]) + ) + ) + m.vp[67, compon].setub( + (1.0 / 7500.6168) + * exp( + m.anta[compon] + - m.antb[compon] / (value(m.t[67]) * 100.0 + m.antc[compon]) + ) + ) + + flashdata_file = os.path.join(dir_path, "flashdata.csv") + flash = pd.read_csv(flashdata_file, header=0) + number = flash.iloc[:, [4]].dropna().values + two_digit_number = flash.iloc[:, [0]].dropna().values + two_digit_compon = flash.iloc[:, [1]].dropna().values + for i in range(len(two_digit_number)): + m.eflsh[two_digit_number[i, 0], two_digit_compon[i, 0]].setlb( + flash.iloc[:, [2]].dropna().values[i, 0] + ) + m.eflsh[two_digit_number[i, 0], two_digit_compon[i, 0]].setub( + flash.iloc[:, [3]].dropna().values[i, 0] + ) + for i in range(len(number)): + m.flshp[number[i, 0]].setlb(flash.iloc[:, [5]].dropna().values[i, 0]) + m.flshp[number[i, 0]].setub(flash.iloc[:, [6]].dropna().values[i, 0]) + m.flsht[number[i, 0]].setlb(flash.iloc[:, [7]].dropna().values[i, 0]) + m.flsht[number[i, 0]].setub(flash.iloc[:, [8]].dropna().values[i, 0]) + m.t[19].setlb(m.flsht[1].lb) + m.t[19].setub(m.flsht[1].ub) + m.t[48].setlb(m.flsht[2].lb) + m.t[48].setub(m.flsht[2].ub) + m.t[41].setlb(m.t[32].lb) + m.t[41].setub(m.flsht[3].ub) + m.t[1].setlb(3.0) + m.t[1].setub(3.0) + m.t[16].setub(8.943) + m.t[66].setlb(3.0) + + for stream in m.str: + for compon in m.compon: + m.vp[stream, compon].setlb( + (1.0 / 7500.6168) + * exp( + m.anta[compon] + - m.antb[compon] / (m.t[stream].lb * 100.0 + m.antc[compon]) + ) + ) + m.vp[stream, compon].setub( + (1.0 / 7500.6168) + * exp( + m.anta[compon] + - m.antb[compon] / (m.t[stream].ub * 100.0 + m.antc[compon]) + ) + ) + + m.p[1].setub(3.93) + m.p[1].setlb(3.93) + m.f[31].setlb(2.08) + m.f[31].setub(2.08) + m.p[66].setub(3.93) + m.p[66].setub(3.93) + + # distillation bounds + for dist in m.dist: + for stream in m.str: + for compon in m.compon: + if (dist, stream) in m.ldist and (dist, compon) in m.dlkey: + m.avevlt[dist].setlb(m.vp[stream, compon].ub) + if (dist, stream) in m.ldist and (dist, compon) in m.dhkey: + m.avevlt[dist].setlb(m.avevlt[dist].lb / m.vp[stream, compon].ub) + for dist in m.dist: + for stream in m.str: + for compon in m.compon: + if (dist, stream) in m.vdist and (dist, compon) in m.dlkey: + m.avevlt[dist].setub(m.vp[stream, compon].lb) + if (dist, stream) in m.vdist and (dist, compon) in m.dhkey: + m.avevlt[dist].setub(m.avevlt[dist].ub / m.vp[stream, compon].lb) + + # ## initialization procedure + + # flash1 + m.eflsh[1, "h2"] = 0.995 + m.eflsh[1, "ch4"] = 0.99 + m.eflsh[1, "ben"] = 0.04 + m.eflsh[1, "tol"] = 0.01 + m.eflsh[1, "dip"] = 0.0001 + + # compressor + m.distp[1] = 1.02 + m.distp[2] = 0.1 + m.distp[3] = 0.1 + m.qexch[1] = 0.497842 + m.elec[1] = 0 + m.elec[2] = 12.384 + m.elec[3] = 0 + m.elec[4] = 28.7602 + m.presrat[1] = 1 + m.presrat[2] = 1.04552 + m.presrat[3] = 1.36516 + m.presrat[4] = 1.95418 + m.qfuel[1] = 0.0475341 + m.q[2] = 54.3002 + + file_1 = os.path.join(dir_path, "GAMS_init_stream_data.csv") + stream = pd.read_csv(file_1, usecols=[0]) + data = pd.read_csv(file_1, usecols=[1]) + temp = pd.read_csv(file_1, usecols=[3]) + flow = pd.read_csv(file_1, usecols=[4]) + e = pd.read_csv(file_1, usecols=[5]) + + for i in range(len(stream)): + m.p[stream.to_numpy()[i, 0]] = data.to_numpy()[i, 0] + for i in range(72): + m.t[stream.to_numpy()[i, 0]] = temp.to_numpy()[i, 0] + m.f[stream.to_numpy()[i, 0]] = flow.to_numpy()[i, 0] + m.e[stream.to_numpy()[i, 0]] = e.to_numpy()[i, 0] + + file_2 = os.path.join(dir_path, "GAMS_init_stream_compon_data.csv") + streamfc = pd.read_csv(file_2, usecols=[0]) + comp = pd.read_csv(file_2, usecols=[1]) + fc = pd.read_csv(file_2, usecols=[2]) + streamvp = pd.read_csv(file_2, usecols=[3]) + compvp = pd.read_csv(file_2, usecols=[4]) + vp = pd.read_csv(file_2, usecols=[5]) + + for i in range(len(streamfc)): + m.fc[streamfc.to_numpy()[i, 0], comp.to_numpy()[i, 0]] = fc.to_numpy()[i, 0] + m.vp[streamvp.to_numpy()[i, 0], compvp.to_numpy()[i, 0]] = vp.to_numpy()[i, 0] + + file_3 = os.path.join(dir_path, "GAMS_init_data.csv") + stream3 = pd.read_csv(file_3, usecols=[0]) + a = pd.read_csv(file_3, usecols=[1]) + avevlt = pd.read_csv(file_3, usecols=[3]) + comp1 = pd.read_csv(file_3, usecols=[5]) + beta = pd.read_csv(file_3, usecols=[6]) + consum = pd.read_csv(file_3, usecols=[9]) + conv = pd.read_csv(file_3, usecols=[12]) + disp = pd.read_csv(file_3, usecols=[14]) + stream4 = pd.read_csv(file_3, usecols=[15]) + comp2 = pd.read_csv(file_3, usecols=[16]) + eflsh = pd.read_csv(file_3, usecols=[17]) + flshp = pd.read_csv(file_3, usecols=[19]) + flsht = pd.read_csv(file_3, usecols=[21]) + krct = pd.read_csv(file_3, usecols=[23]) + mxrp = pd.read_csv(file_3, usecols=[25]) + ndist = pd.read_csv(file_3, usecols=[27]) + nmin = pd.read_csv(file_3, usecols=[29]) + qc = pd.read_csv(file_3, usecols=[31]) + qh = pd.read_csv(file_3, usecols=[33]) + rctp = pd.read_csv(file_3, usecols=[35]) + rctt = pd.read_csv(file_3, usecols=[37]) + rctvol = pd.read_csv(file_3, usecols=[39]) + reflux = pd.read_csv(file_3, usecols=[41]) + rmin = pd.read_csv(file_3, usecols=[43]) + sel = pd.read_csv(file_3, usecols=[45]) + spl1p = pd.read_csv(file_3, usecols=[47]) + spl1t = pd.read_csv(file_3, usecols=[49]) + splp = pd.read_csv(file_3, usecols=[51]) + splt = pd.read_csv(file_3, usecols=[53]) + + for i in range(2): + m.rctp[i + 1] = rctp.to_numpy()[i, 0] + m.rctt[i + 1] = rctt.to_numpy()[i, 0] + m.rctvol[i + 1] = rctvol.to_numpy()[i, 0] + m.sel[i + 1] = sel.to_numpy()[i, 0] + m.krct[i + 1] = krct.to_numpy()[i, 0] + m.consum[i + 1, "tol"] = consum.to_numpy()[i, 0] + m.conv[i + 1, "tol"] = conv.to_numpy()[i, 0] + m.a[stream3.to_numpy()[i, 0]] = a.to_numpy()[i, 0] + m.qc[i + 1] = qc.to_numpy()[i, 0] + for i in range(3): + m.avevlt[i + 1] = avevlt.to_numpy()[i, 0] + m.distp[i + 1] = disp.to_numpy()[i, 0] + m.flshp[i + 1] = flshp.to_numpy()[i, 0] + m.flsht[i + 1] = flsht.to_numpy()[i, 0] + m.ndist[i + 1] = ndist.to_numpy()[i, 0] + m.nmin[i + 1] = nmin.to_numpy()[i, 0] + m.reflux[i + 1] = reflux.to_numpy()[i, 0] + m.rmin[i + 1] = rmin.to_numpy()[i, 0] + m.splp[i + 1] = splp.to_numpy()[i, 0] + m.splt[i + 1] = splt.to_numpy()[i, 0] + for i in range(5): + m.beta[1, comp1.to_numpy()[i, 0]] = beta.to_numpy()[i, 0] + m.mxrp[i + 1] = mxrp.to_numpy()[i, 0] + for i in range(4): + m.qh[i + 1] = qh.to_numpy()[i, 0] + for i in range(len(stream4)): + m.eflsh[stream4.to_numpy()[i, 0], comp2.to_numpy()[i, 0]] = eflsh.to_numpy()[ + i, 0 + ] + for i in range(6): + m.spl1p[i + 1] = spl1p.to_numpy()[i, 0] + m.spl1t[i + 1] = spl1t.to_numpy()[i, 0] + + # ## constraints + m.specrec = Constraint( + expr=m.fc[72, "h2"] >= 0.5 * m.f[72], doc="specification on h2 recycle" + ) + m.specprod = Constraint( + expr=m.fc[31, "ben"] >= 0.9997 * m.f[31], + doc="specification on benzene production", + ) + + def Fbal(_m, stream): + return m.f[stream] == sum(m.fc[stream, compon] for compon in m.compon) + + m.fbal = Constraint(m.str, rule=Fbal, doc="flow balance") + + def H2feed(m, compon): + return m.fc[1, compon] == m.f[1] * m.f1comp[compon] + + m.h2feed = Constraint(m.compon, rule=H2feed, doc="h2 feed composition") + + def Tolfeed(_m, compon): + return m.fc[66, compon] == m.f[66] * m.f66comp[compon] + + m.tolfeed = Constraint(m.compon, rule=Tolfeed, doc="toluene feed composition") + + def Tolabs(_m, compon): + return m.fc[67, compon] == m.f[67] * m.f67comp[compon] + + m.tolabs = Constraint(m.compon, rule=Tolabs, doc="toluene absorber composition") + + def build_absorber(b, absorber): + """ + Functions relevant to the absorber block + + Parameters + ---------- + b : Pyomo Block + absorber block + absorber : int + Index of the absorber + """ + + def Absfact(_m, i, compon): + """ + Absorption factor equation + sum of flowrates of feed components = sum of flowrates of vapor components * absorption factor * sum of vapor pressures + + """ + if (i, compon) in m.anorm: + return sum( + m.f[stream] * m.p[stream] for (absb, stream) in m.ilabs if absb == i + ) == sum( + m.f[stream] for (absc, stream) in m.ivabs if absc == i + ) * m.aabs[ + compon + ] * sum( + m.vp[stream, compon] for (absd, stream) in m.ilabs if absd == i + ) + return Constraint.Skip + + b.absfact = Constraint( + [absorber], m.compon, rule=Absfact, doc="absorption factor equation" + ) + + def Gameqn(_m, i, compon): + # definition of gamma + if (i, compon) in m.asolv: + return m.gamma[i, compon] == log( + (1 - m.aabs[compon] ** (m.nabs[i] * m.abseff + m.eps1)) + / (1 - m.aabs[compon]) + ) + return Constraint.Skip + + b.gameqn = Constraint( + [absorber], m.compon, rule=Gameqn, doc="definition of gamma" + ) + + def Betaeqn(_m, i, compon): + # definition of beta + if (i, compon) not in m.asimp: + return m.beta[i, compon] == log( + (1 - m.aabs[compon] ** (m.nabs[i] * m.abseff + 1)) + / (1 - m.aabs[compon]) + ) + return Constraint.Skip + + b.betaeqn = Constraint( + [absorber], m.compon, rule=Betaeqn, doc="definition of beta" + ) + + def Abssvrec(_m, i, compon): + # recovery of solvent + if (i, compon) in m.asolv: + return sum(m.fc[stream, compon] for (i, stream) in m.ovabs) * exp( + m.beta[i, compon] + ) == sum(m.fc[stream, compon] for (i_, stream) in m.ivabs) + exp( + m.gamma[i, compon] + ) * sum( + m.fc[stream, compon] for (i_, stream) in m.ilabs + ) + return Constraint.Skip + + b.abssvrec = Constraint( + [absorber], m.compon, rule=Abssvrec, doc="recovery of solvent" + ) + + def Absrec(_m, i, compon): + # recovery of non-solvent + if (i, compon) in m.anorm: + return sum(m.fc[i, compon] for (abs, i) in m.ovabs) * exp( + m.beta[i, compon] + ) == sum(m.fc[i, compon] for (abs, i) in m.ivabs) + return Constraint.Skip + + b.absrec = Constraint( + [absorber], m.compon, rule=Absrec, doc="recovery of non-solvent" + ) + + def abssimp(_m, absorb, compon): + # recovery of simplified components + if (absorb, compon) in m.asimp: + return ( + sum(m.fc[i, compon] for (absorb, i) in m.ovabs) + == sum(m.fc[i, compon] for (absorb, i) in m.ivabs) / m.cbeta[compon] + ) + return Constraint.Skip + + b.abssimp = Constraint( + [absorber], m.compon, rule=abssimp, doc="recovery of simplified components" + ) + + def Abscmb(_m, i, compon): + return sum(m.fc[stream, compon] for (i, stream) in m.ilabs) + sum( + m.fc[stream, compon] for (i, stream) in m.ivabs + ) == sum(m.fc[stream, compon] for (i, stream) in m.olabs) + sum( + m.fc[stream, compon] for (i, stream) in m.ovabs + ) + + b.abscmb = Constraint( + [absorber], + m.compon, + rule=Abscmb, + doc="overall component mass balance in absorber", + ) + + def Abspl(_m, i): + return sum(m.p[stream] for (_, stream) in m.ilabs) == sum( + m.p[stream] for (_, stream) in m.olabs + ) + + b.abspl = Constraint( + [absorber], rule=Abspl, doc="pressure relation for liquid in absorber" + ) + + def Abstl(_m, i): + return sum(m.t[stream] for (_, stream) in m.ilabs) == sum( + m.t[stream] for (_, stream) in m.olabs + ) + + b.abstl = Constraint( + [absorber], rule=Abstl, doc="temperature relation for liquid in absorber" + ) + + def Abspv(_m, i): + return sum(m.p[stream] for (_, stream) in m.ivabs) == sum( + m.p[stream] for (_, stream) in m.ovabs + ) + + b.abspv = Constraint( + [absorber], rule=Abspv, doc="pressure relation for vapor in absorber" + ) + + def Abspin(_m, i): + return sum(m.p[stream] for (_, stream) in m.ilabs) == sum( + m.p[stream] for (_, stream) in m.ivabs + ) + + b.absp = Constraint( + [absorber], rule=Abspin, doc="pressure relation at inlet of absorber" + ) + + def Absttop(_m, i): + return sum(m.t[stream] for (_, stream) in m.ilabs) == sum( + m.t[stream] for (_, stream) in m.ovabs + ) + + b.abst = Constraint( + [absorber], rule=Absttop, doc="temperature relation at top of absorber" + ) + + def build_compressor(b, comp): + """ + Functions relevant to the compressor block + + Parameters + ---------- + b : Pyomo Block + compressor block + comp : int + Index of the compressor + """ + + def Compcmb(_m, comp1, compon): + if comp1 == comp: + return sum( + m.fc[stream, compon] + for (comp_, stream) in m.ocomp + if comp_ == comp1 + ) == sum( + m.fc[stream, compon] + for (comp_, stream) in m.icomp + if comp_ == comp1 + ) + return Constraint.Skip + + b.compcmb = Constraint( + [comp], m.compon, rule=Compcmb, doc="component balance in compressor" + ) + + def Comphb(_m, comp1): + if comp1 == comp: + return sum( + m.t[stream] for (_, stream) in m.ocomp if _ == comp + ) == m.presrat[comp] * sum( + m.t[stream] for (_, stream) in m.icomp if _ == comp + ) + return Constraint.Skip + + b.comphb = Constraint([comp], rule=Comphb, doc="heat balance in compressor") + + def Compelec(_m, comp_): + if comp_ == comp: + return m.elec[comp_] == m.alpha * (m.presrat[comp_] - 1) * sum( + 100.0 + * m.t[stream] + * m.f[stream] + / 60.0 + * (1.0 / m.compeff) + * (m.gam / (m.gam - 1.0)) + for (comp1, stream) in m.icomp + if comp_ == comp1 + ) + return Constraint.Skip + + b.compelec = Constraint( + [comp], rule=Compelec, doc="energy balance in compressor" + ) + + def Ratio(_m, comp_): + if comp == comp_: + return m.presrat[comp_] ** (m.gam / (m.gam - 1.0)) == sum( + m.p[stream] for (comp1, stream) in m.ocomp if comp_ == comp1 + ) / sum(m.p[stream] for (comp1, stream) in m.icomp if comp1 == comp_) + return Constraint.Skip + + b.ratio = Constraint( + [comp], rule=Ratio, doc="pressure ratio (out to in) in compressor" + ) + + m.vapor_pressure_unit_match = Param( + initialize=7500.6168, + doc="unit match coefficient for vapor pressure calculation", + ) + m.actual_reflux_ratio = Param(initialize=1.2, doc="actual reflux ratio coefficient") + m.recovery_specification_coefficient = Param( + initialize=0.05, doc="recovery specification coefficient" + ) + + def build_distillation(b, dist): + """ + Functions relevant to the distillation block + + Parameters + ---------- + b : Pyomo Block + distillation block + dist : int + Index of the distillation column + """ + + def Antdistb(_m, dist_, stream, compon): + if ( + (dist_, stream) in m.ldist + and (dist_, compon) in m.dkey + and dist_ == dist + ): + return log( + m.vp[stream, compon] * m.vapor_pressure_unit_match + ) == m.anta[compon] - m.antb[compon] / ( + m.t[stream] * 100.0 + m.antc[compon] + ) + return Constraint.Skip + + b.antdistb = Constraint( + [dist], + m.str, + m.compon, + rule=Antdistb, + doc="vapor pressure correlation (bot)", + ) + + def Antdistt(_m, dist_, stream, compon): + if ( + (dist_, stream) in m.vdist + and (dist_, compon) in m.dkey + and dist == dist_ + ): + return log( + m.vp[stream, compon] * m.vapor_pressure_unit_match + ) == m.anta[compon] - m.antb[compon] / ( + m.t[stream] * 100.0 + m.antc[compon] + ) + return Constraint.Skip + + b.antdistt = Constraint( + [dist], + m.str, + m.compon, + rule=Antdistt, + doc="vapor pressure correlation (top)", + ) + + def Relvol(_m, dist_): + if dist == dist_: + divided1 = sum( + sum( + m.vp[stream, compon] + for (dist_, compon) in m.dlkey + if dist_ == dist + ) + / sum( + m.vp[stream, compon] + for (dist_, compon) in m.dhkey + if dist_ == dist + ) + for (dist_, stream) in m.vdist + if dist_ == dist + ) + divided2 = sum( + sum( + m.vp[stream, compon] + for (dist_, compon) in m.dlkey + if dist_ == dist + ) + / sum( + m.vp[stream, compon] + for (dist_, compon) in m.dhkey + if dist_ == dist + ) + for (dist_, stream) in m.ldist + if dist_ == dist + ) + return m.avevlt[dist] == sqrt(divided1 * divided2) + return Constraint.Skip + + b.relvol = Constraint([dist], rule=Relvol, doc="average relative volatility") + + def Undwood(_m, dist_): + # minimum reflux ratio from Underwood equation + if dist_ == dist: + return sum( + m.fc[stream, compon] + for (dist1, compon) in m.dlkey + if dist1 == dist_ + for (dist1, stream) in m.idist + if dist1 == dist_ + ) * m.rmin[dist_] * (m.avevlt[dist_] - 1) == sum( + m.f[stream] for (dist1, stream) in m.idist if dist1 == dist_ + ) + return Constraint.Skip + + b.undwood = Constraint( + [dist], rule=Undwood, doc="minimum reflux ratio equation" + ) + + def Actreflux(_m, dist_): + # actual reflux ratio (heuristic) + if dist_ == dist: + return m.reflux[dist_] == m.actual_reflux_ratio * m.rmin[dist_] + return Constraint.Skip + + b.actreflux = Constraint([dist], rule=Actreflux, doc="actual reflux ratio") + + def Fenske(_m, dist_): + # minimum number of trays from Fenske equation + if dist == dist_: + sum1 = sum( + (m.f[stream] + m.eps1) / (m.fc[stream, compon] + m.eps1) + for (dist1, compon) in m.dhkey + if dist1 == dist_ + for (dist1, stream) in m.vdist + if dist1 == dist_ + ) + sum2 = sum( + (m.f[stream] + m.eps1) / (m.fc[stream, compon] + m.eps1) + for (dist1, compon) in m.dlkey + if dist1 == dist_ + for (dist1, stream) in m.ldist + if dist1 == dist_ + ) + return m.nmin[dist_] * log(m.avevlt[dist_]) == log(sum1 * sum2) + return Constraint.Skip + + b.fenske = Constraint([dist], rule=Fenske, doc="minimum number of trays") + + def Acttray(_m, dist_): + # actual number of trays (gillilands approximation) + if dist == dist_: + return m.ndist[dist_] == m.nmin[dist_] * 2.0 / m.disteff + return Constraint.Skip + + b.acttray = Constraint([dist], rule=Acttray, doc="actual number of trays") + + def Distspec(_m, dist_, stream, compon): + if ( + (dist_, stream) in m.vdist + and (dist_, compon) in m.dhkey + and dist_ == dist + ): + return m.fc[ + stream, compon + ] <= m.recovery_specification_coefficient * sum( + m.fc[str2, compon] for (dist_, str2) in m.idist if dist == dist_ + ) + return Constraint.Skip + + b.distspec = Constraint( + [dist], m.str, m.compon, rule=Distspec, doc="recovery specification" + ) + + def Distheav(_m, dist_, compon): + if (dist_, compon) in m.dh and dist == dist_: + return sum( + m.fc[str2, compon] for (dist_, str2) in m.idist if dist_ == dist + ) == sum( + m.fc[str2, compon] for (dist_, str2) in m.ldist if dist_ == dist + ) + return Constraint.Skip + + b.distheav = Constraint([dist], m.compon, rule=Distheav, doc="heavy components") + + def Distlite(_m, dist_, compon): + if (dist_, compon) in m.dl and dist_ == dist: + return sum( + m.fc[str2, compon] for (dist_, str2) in m.idist if dist == dist_ + ) == sum( + m.fc[str2, compon] for (dist_, str2) in m.vdist if dist == dist_ + ) + return Constraint.Skip + + b.distlite = Constraint([dist], m.compon, rule=Distlite, doc="light components") + + def Distpi(_m, dist_, stream): + if (dist_, stream) in m.idist and dist_ == dist: + return m.distp[dist_] <= m.p[stream] + return Constraint.Skip + + b.distpi = Constraint([dist], m.str, rule=Distpi, doc="inlet pressure relation") + + def Distvpl(_m, dist_, stream): + if (dist_, stream) in m.ldist and dist == dist_: + return m.distp[dist_] == sum( + m.vp[stream, compon] for (dist_, compon) in m.dhkey if dist_ == dist + ) + return Constraint.Skip + + b.distvpl = Constraint( + [dist], m.str, rule=Distvpl, doc="bottom vapor pressure relation" + ) + + def Distvpv(_m, dist_, stream): + if dist > 1 and (dist, stream) in m.vdist and dist_ == dist: + return m.distp[dist_] == sum( + m.vp[stream, compon] for (dist_, compon) in m.dlkey if dist_ == dist + ) + return Constraint.Skip + + b.distvpv = Constraint( + [dist], m.str, rule=Distvpv, doc="top vapor pressure relation" + ) + + def Distpl(_m, dist_, stream): + if (dist_, stream) in m.ldist and dist_ == dist: + return m.distp[dist_] == m.p[stream] + return Constraint.Skip + + b.distpl = Constraint( + [dist], m.str, rule=Distpl, doc="outlet pressure relation (liquid)" + ) + + def Distpv(_m, dist_, stream): + if (dist_, stream) in m.vdist and dist == dist_: + return m.distp[dist_] == m.p[stream] + return Constraint.Skip + + b.distpv = Constraint( + [dist], m.str, rule=Distpv, doc="outlet pressure relation (vapor)" + ) + + def Distcmb(_m, dist_, compon): + if dist_ == dist: + return sum( + m.fc[stream, compon] + for (dist1, stream) in m.idist + if dist1 == dist_ + ) == sum( + m.fc[stream, compon] + for (dist1, stream) in m.vdist + if dist1 == dist_ + ) + sum( + m.fc[stream, compon] + for (dist1, stream) in m.ldist + if dist1 == dist_ + ) + return Constraint.Skip + + b.distcmb = Constraint( + [dist], + m.compon, + rule=Distcmb, + doc="component mass balance in distillation column", + ) + + def build_flash(b, flsh): + """ + Functions relevant to the flash block + + Parameters + ---------- + b : Pyomo Block + flash block + flsh : int + Index of the flash + """ + + def Flshcmb(_m, flsh_, compon): + if flsh_ in m.flsh and compon in m.compon and flsh_ == flsh: + return sum( + m.fc[stream, compon] + for (flsh1, stream) in m.iflsh + if flsh1 == flsh_ + ) == sum( + m.fc[stream, compon] + for (flsh1, stream) in m.vflsh + if flsh1 == flsh_ + ) + sum( + m.fc[stream, compon] + for (flsh1, stream) in m.lflsh + if flsh1 == flsh_ + ) + return Constraint.Skip + + b.flshcmb = Constraint( + [flsh], m.compon, rule=Flshcmb, doc="component mass balance in flash" + ) + + def Antflsh(_m, flsh_, stream, compon): + if (flsh_, stream) in m.lflsh and flsh_ == flsh: + return log( + m.vp[stream, compon] * m.vapor_pressure_unit_match + ) == m.anta[compon] - m.antb[compon] / ( + m.t[stream] * 100.0 + m.antc[compon] + ) + return Constraint.Skip + + b.antflsh = Constraint( + [flsh], m.str, m.compon, rule=Antflsh, doc="flash pressure relation" + ) + + def Flshrec(_m, flsh_, stream, compon): + if (flsh_, stream) in m.lflsh and flsh_ == flsh: + return ( + sum( + m.eflsh[flsh1, compon2] + for (flsh1, compon2) in m.fkey + if flsh1 == flsh_ + ) + * ( + m.eflsh[flsh_, compon] + * sum( + m.vp[stream, compon2] + for (flsh1, compon2) in m.fkey + if flsh_ == flsh1 + ) + + (1.0 - m.eflsh[flsh_, compon]) * m.vp[stream, compon] + ) + == sum( + m.vp[stream, compon2] + for (flsh1, compon2) in m.fkey + if flsh_ == flsh1 + ) + * m.eflsh[flsh_, compon] + ) + return Constraint.Skip + + b.flshrec = Constraint( + [flsh], m.str, m.compon, rule=Flshrec, doc="vapor recovery relation" + ) + + def Flsheql(_m, flsh_, compon): + if flsh in m.flsh and compon in m.compon and flsh_ == flsh: + return ( + sum( + m.fc[stream, compon] + for (flsh1, stream) in m.vflsh + if flsh1 == flsh_ + ) + == sum( + m.fc[stream, compon] + for (flsh1, stream) in m.iflsh + if flsh1 == flsh_ + ) + * m.eflsh[flsh, compon] + ) + return Constraint.Skip + + b.flsheql = Constraint( + [flsh], m.compon, rule=Flsheql, doc="equilibrium relation" + ) + + def Flshpr(_m, flsh_, stream): + if (flsh_, stream) in m.lflsh and flsh_ == flsh: + return m.flshp[flsh_] * m.f[stream] == sum( + m.vp[stream, compon] * m.fc[stream, compon] for compon in m.compon + ) + return Constraint.Skip + + b.flshpr = Constraint([flsh], m.str, rule=Flshpr, doc="flash pressure relation") + + def Flshpi(_m, flsh_, stream): + if (flsh_, stream) in m.iflsh and flsh_ == flsh: + return m.flshp[flsh_] == m.p[stream] + return Constraint.Skip + + b.flshpi = Constraint([flsh], m.str, rule=Flshpi, doc="inlet pressure relation") + + def Flshpl(_m, flsh_, stream): + if (flsh_, stream) in m.lflsh and flsh_ == flsh: + return m.flshp[flsh_] == m.p[stream] + return Constraint.Skip + + b.flshpl = Constraint( + [flsh], m.str, rule=Flshpl, doc="outlet pressure relation (liquid)" + ) + + def Flshpv(_m, flsh_, stream): + if (flsh_, stream) in m.vflsh and flsh_ == flsh: + return m.flshp[flsh_] == m.p[stream] + return Constraint.Skip + + b.flshpv = Constraint( + [flsh], m.str, rule=Flshpv, doc="outlet pressure relation (vapor)" + ) + + def Flshti(_m, flsh_, stream): + if (flsh_, stream) in m.iflsh and flsh_ == flsh: + return m.flsht[flsh_] == m.t[stream] + return Constraint.Skip + + b.flshti = Constraint([flsh], m.str, rule=Flshti, doc="inlet temp. relation") + + def Flshtl(_m, flsh_, stream): + if (flsh_, stream) in m.lflsh and flsh_ == flsh: + return m.flsht[flsh_] == m.t[stream] + return Constraint.Skip + + b.flshtl = Constraint( + [flsh], m.str, rule=Flshtl, doc="outlet temp. relation (liquid)" + ) + + def Flshtv(_m, flsh_, stream): + if (flsh_, stream) in m.vflsh and flsh_ == flsh: + return m.flsht[flsh_] == m.t[stream] + return Constraint.Skip + + b.flshtv = Constraint( + [flsh], m.str, rule=Flshtv, doc="outlet temp. relation (vapor)" + ) + + m.heat_unit_match = Param( + initialize=3600.0 * 8500.0 * 1.0e-12 / 60.0, doc="unit change on temp" + ) + + def build_furnace(b, furnace): + """ + Functions relevant to the furnace block + + Parameters + ---------- + b : Pyomo Block + furnace block + furnace : int + Index of the furnace + """ + + def Furnhb(_m, furn): + if furn == furnace: + return ( + m.qfuel[furn] + == ( + sum( + m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] + for (furn, stream) in m.ofurn + ) + - sum( + m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] + for (furn, stream) in m.ifurn + ) + ) + * m.heat_unit_match + ) + return Constraint.Skip + + b.furnhb = Constraint([furnace], rule=Furnhb, doc="heat balance in furnace") + + def Furncmb(_m, furn, compon): + if furn == furnace: + return sum(m.fc[stream, compon] for (furn, stream) in m.ofurn) == sum( + m.fc[stream, compon] for (furn, stream) in m.ifurn + ) + return Constraint.Skip + + b.furncmb = Constraint( + [furnace], m.compon, rule=Furncmb, doc="component mass balance in furnace" + ) + + def Furnp(_m, furn): + if furn == furnace: + return ( + sum(m.p[stream] for (furn, stream) in m.ofurn) + == sum(m.p[stream] for (furn, stream) in m.ifurn) - m.furnpdrop + ) + return Constraint.Skip + + b.furnp = Constraint([furnace], rule=Furnp, doc="pressure relation in furnace") + + def build_cooler(b, cooler): + """ + Functions relevant to the cooler block + + Parameters + ---------- + b : Pyomo Block + cooler block + cooler : int + Index of the cooler + """ + + def Heccmb(_m, hec, compon): + return sum( + m.fc[stream, compon] for (hec_, stream) in m.ohec if hec_ == hec + ) == sum(m.fc[stream, compon] for (hec_, stream) in m.ihec if hec_ == hec) + + b.heccmb = Constraint( + [cooler], m.compon, rule=Heccmb, doc="heat balance in cooler" + ) + + def Hechb(_m, hec): + return ( + m.qc[hec] + == ( + sum( + m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] + for (hec_, stream) in m.ihec + if hec_ == hec + ) + - sum( + m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] + for (hec_, stream) in m.ohec + if hec_ == hec + ) + ) + * m.heat_unit_match + ) + + b.hechb = Constraint( + [cooler], rule=Hechb, doc="component mass balance in cooler" + ) + + def Hecp(_m, hec): + return sum(m.p[stream] for (hec_, stream) in m.ihec if hec_ == hec) == sum( + m.p[stream] for (hec_, stream) in m.ohec if hec_ == hec + ) + + b.hecp = Constraint([cooler], rule=Hecp, doc="pressure relation in cooler") + + def build_heater(b, heater): + """ + Functions relevant to the heater block + + Parameters + ---------- + b : Pyomo Block + heater block + heater : int + Index of the heater + """ + + def Hehcmb(_m, heh, compon): + if heh == heater and compon in m.compon: + return sum( + m.fc[stream, compon] for (heh_, stream) in m.oheh if heh_ == heh + ) == sum( + m.fc[stream, compon] for (heh_, stream) in m.iheh if heh == heh_ + ) + return Constraint.Skip + + b.hehcmb = Constraint( + Set(initialize=[heater]), + m.compon, + rule=Hehcmb, + doc="component balance in heater", + ) + + def Hehhb(_m, heh): + if heh == heater: + return ( + m.qh[heh] + == ( + sum( + m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] + for (heh_, stream) in m.oheh + if heh_ == heh + ) + - sum( + m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] + for (heh_, stream) in m.iheh + if heh_ == heh + ) + ) + * m.heat_unit_match + ) + return Constraint.Skip + + b.hehhb = Constraint( + Set(initialize=[heater]), rule=Hehhb, doc="heat balance in heater" + ) + + def hehp(_m, heh): + if heh == heater: + return sum( + m.p[stream] for (heh_, stream) in m.iheh if heh_ == heh + ) == sum(m.p[stream] for (heh_, stream) in m.oheh if heh == heh_) + return Constraint.Skip + + b.Hehp = Constraint( + Set(initialize=[heater]), rule=hehp, doc="no pressure drop thru heater" + ) + + m.exchanger_temp_drop = Param(initialize=0.25) + + def build_exchanger(b, exchanger): + """ + Functions relevant to the exchanger block + + Parameters + ---------- + b : Pyomo Block + exchanger block + exchanger : int + Index of the exchanger + """ + + def Exchcmbc(_m, exch, compon): + if exch in m.exch and compon in m.compon: + return sum( + m.fc[stream, compon] + for (exch_, stream) in m.ocexch + if exch == exch_ + ) == sum( + m.fc[stream, compon] + for (exch_, stream) in m.icexch + if exch == exch_ + ) + return Constraint.Skip + + b.exchcmbc = Constraint( + [exchanger], + m.compon, + rule=Exchcmbc, + doc="component balance (cold) in exchanger", + ) + + def Exchcmbh(_m, exch, compon): + if exch in m.exch and compon in m.compon: + return sum( + m.fc[stream, compon] + for (exch_, stream) in m.ohexch + if exch == exch_ + ) == sum( + m.fc[stream, compon] + for (exch_, stream) in m.ihexch + if exch == exch_ + ) + return Constraint.Skip + + b.exchcmbh = Constraint( + [exchanger], + m.compon, + rule=Exchcmbh, + doc="component balance (hot) in exchanger", + ) + + def Exchhbc(_m, exch): + if exch in m.exch: + return ( + sum( + m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] + for (exch_, stream) in m.ocexch + if exch == exch_ + ) + - sum( + m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] + for (exch_, stream) in m.icexch + if exch == exch_ + ) + ) * m.heat_unit_match == m.qexch[exch] + return Constraint.Skip + + b.exchhbc = Constraint( + [exchanger], rule=Exchhbc, doc="heat balance for cold stream in exchanger" + ) + + def Exchhbh(_m, exch): + return ( + sum( + m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] + for (exch, stream) in m.ihexch + ) + - sum( + m.cp[stream] * m.f[stream] * 100.0 * m.t[stream] + for (exch, stream) in m.ohexch + ) + ) * m.heat_unit_match == m.qexch[exch] + + b.exchhbh = Constraint( + [exchanger], rule=Exchhbh, doc="heat balance for hot stream in exchanger" + ) + + def Exchdtm1(_m, exch): + return ( + sum(m.t[stream] for (exch, stream) in m.ohexch) + >= sum(m.t[stream] for (exch, stream) in m.icexch) + + m.exchanger_temp_drop + ) + + b.exchdtm1 = Constraint([exchanger], rule=Exchdtm1, doc="delta t min condition") + + def Exchdtm2(_m, exch): + return ( + sum(m.t[stream] for (exch, stream) in m.ocexch) + <= sum(m.t[stream] for (exch, stream) in m.ihexch) + - m.exchanger_temp_drop + ) + + b.exchdtm2 = Constraint([exchanger], rule=Exchdtm2, doc="delta t min condition") + + def Exchpc(_m, exch): + return sum(m.p[stream] for (exch, stream) in m.ocexch) == sum( + m.p[stream] for (exch, stream) in m.icexch + ) + + b.exchpc = Constraint( + [exchanger], rule=Exchpc, doc="pressure relation (cold) in exchanger" + ) + + def Exchph(_m, exch): + return sum(m.p[stream] for (exch, stream) in m.ohexch) == sum( + m.p[stream] for (exch, stream) in m.ihexch + ) + + b.exchph = Constraint( + [exchanger], rule=Exchph, doc="pressure relation (hot) in exchanger" + ) + + m.membrane_recovery_sepc = Param(initialize=0.50) + m.membrane_purity_sepc = Param(initialize=0.50) + + def build_membrane(b, membrane): + """ + Functions relevant to the membrane block + + Parameters + ---------- + b : Pyomo Block + membrane block + membrane : int + Index of the membrane + """ + + def Memcmb(_m, memb, stream, compon): + if (memb, stream) in m.imemb and memb == membrane: + return m.fc[stream, compon] == sum( + m.fc[stream, compon] for (memb_, stream) in m.pmemb if memb == memb_ + ) + sum( + m.fc[stream, compon] for (memb_, stream) in m.nmemb if memb == memb_ + ) + return Constraint.Skip + + b.memcmb = Constraint( + [membrane], + m.str, + m.compon, + rule=Memcmb, + doc="component mass balance in membrane separator", + ) + + def Flux(_m, memb, stream, compon): + if ( + (memb, stream) in m.pmemb + and (memb, compon) in m.mnorm + and memb == membrane + ): + return m.fc[stream, compon] == m.a[memb] * m.perm[compon] / 2.0 * ( + sum(m.p[stream2] for (memb_, stream2) in m.imemb if memb_ == memb) + * ( + sum( + (m.fc[stream2, compon] + m.eps1) / (m.f[stream2] + m.eps1) + for (memb_, stream2) in m.imemb + if memb_ == memb + ) + + sum( + (m.fc[stream2, compon] + m.eps1) / (m.f[stream2] + m.eps1) + for (memb_, stream2) in m.nmemb + if memb_ == memb + ) + ) + - 2.0 + * m.p[stream] + * (m.fc[stream, compon] + m.eps1) + / (m.f[stream] + m.eps1) + ) + return Constraint.Skip + + b.flux = Constraint( + [membrane], + m.str, + m.compon, + rule=Flux, + doc="mass flux relation in membrane separator", + ) + + def Simp(_m, memb, stream, compon): + if ( + (memb, stream) in m.pmemb + and (memb, compon) in m.msimp + and memb == membrane + ): + return m.fc[stream, compon] == 0.0 + return Constraint.Skip + + b.simp = Constraint( + [membrane], + m.str, + m.compon, + rule=Simp, + doc="mass flux relation (simplified) in membrane separator", + ) + + def Memtp(_m, memb, stream): + if (memb, stream) in m.pmemb and memb == membrane: + return m.t[stream] == sum( + m.t[stream2] for (memb, stream2) in m.imemb if memb == membrane + ) + return Constraint.Skip + + b.memtp = Constraint( + [membrane], m.str, rule=Memtp, doc="temp relation for permeate" + ) + + def Mempp(_m, memb, stream): + if (memb, stream) in m.pmemb and memb == membrane: + return m.p[stream] <= sum( + m.p[stream2] for (memb, stream2) in m.imemb if memb == membrane + ) + return Constraint.Skip + + b.mempp = Constraint( + [membrane], m.str, rule=Mempp, doc="pressure relation for permeate" + ) + + def Memtn(_m, memb, stream): + if (memb, stream) in m.nmemb and memb == membrane: + return m.t[stream] == sum( + m.t[stream2] for (memb, stream2) in m.imemb if memb == membrane + ) + return Constraint.Skip + + b.Memtn = Constraint( + [membrane], m.str, rule=Memtn, doc="temp relation for non-permeate" + ) + + def Mempn(_m, memb, stream): + if (memb, stream) in m.nmemb and memb == membrane: + return m.p[stream] == sum( + m.p[stream] for (memb_, stream) in m.imemb if memb_ == memb + ) + return Constraint.Skip + + b.Mempn = Constraint( + [membrane], m.str, rule=Mempn, doc="pressure relation for non-permeate" + ) + + def Rec(_m, memb_, stream): + if (memb_, stream) in m.pmemb and memb_ == membrane: + return m.fc[stream, "h2"] >= m.membrane_recovery_sepc * sum( + m.fc[stream, "h2"] for (memb, stream) in m.imemb if memb == memb_ + ) + return Constraint.Skip + + b.rec = Constraint([membrane], m.str, rule=Rec, doc="recovery spec") + + def Pure(_m, memb, stream): + if (memb, stream) in m.pmemb and memb == membrane: + return m.fc[stream, "h2"] >= m.membrane_purity_sepc * m.f[stream] + return Constraint.Skip + + b.pure = Constraint([membrane], m.str, rule=Pure, doc="purity spec") + + def build_multiple_mixer(b, multiple_mxr): + """ + Functions relevant to the mixer block + + Parameters + ---------- + b : Pyomo Block + mixer block + multiple_mxr : int + Index of the mixer + """ + + def Mxrcmb(_b, mxr, compon): + if mxr == multiple_mxr: + return sum( + m.fc[stream, compon] for (mxr_, stream) in m.omxr if mxr == mxr_ + ) == sum( + m.fc[stream, compon] for (mxr_, stream) in m.imxr if mxr == mxr_ + ) + return Constraint.Skip + + b.mxrcmb = Constraint( + [multiple_mxr], m.compon, rule=Mxrcmb, doc="component balance in mixer" + ) + + def Mxrhb(_b, mxr): + if mxr == multiple_mxr and mxr != 2: + return sum( + m.f[stream] * m.t[stream] * m.cp[stream] + for (mxr_, stream) in m.imxr + if mxr == mxr_ + ) == sum( + m.f[stream] * m.t[stream] * m.cp[stream] + for (mxr_, stream) in m.omxr + if mxr == mxr_ + ) + return Constraint.Skip + + b.mxrhb = Constraint([multiple_mxr], rule=Mxrhb, doc="heat balance in mixer") + + def Mxrhbq(_b, mxr): + if mxr == 2 and mxr == multiple_mxr: + return m.f[16] * m.t[16] == m.f[15] * m.t[15] - ( + m.fc[20, "ben"] + m.fc[20, "tol"] + ) * m.heatvap["tol"] / (100.0 * m.cp[15]) + return Constraint.Skip + + b.mxrhbq = Constraint([multiple_mxr], rule=Mxrhbq, doc="heat balance in quench") + + def Mxrpi(_b, mxr, stream): + if (mxr, stream) in m.imxr and mxr == multiple_mxr: + return m.mxrp[mxr] == m.p[stream] + return Constraint.Skip + + b.mxrpi = Constraint( + [multiple_mxr], m.str, rule=Mxrpi, doc="inlet pressure relation" + ) + + def Mxrpo(_b, mxr, stream): + if (mxr, stream) in m.omxr and mxr == multiple_mxr: + return m.mxrp[mxr] == m.p[stream] + return Constraint.Skip + + b.mxrpo = Constraint( + [multiple_mxr], m.str, rule=Mxrpo, doc="outlet pressure relation" + ) + + def build_pump(b, pump_): + """ + Functions relevant to the pump block + + Parameters + ---------- + b : Pyomo Block + pump block + pump_ : int + Index of the pump + """ + + def Pumpcmb(_m, pump, compon): + if pump == pump_ and compon in m.compon: + return sum( + m.fc[stream, compon] for (pump_, stream) in m.opump if pump == pump_ + ) == sum( + m.fc[stream, compon] for (pump_, stream) in m.ipump if pump_ == pump + ) + return Constraint.Skip + + b.pumpcmb = Constraint( + [pump_], m.compon, rule=Pumpcmb, doc="component balance in pump" + ) + + def Pumphb(_m, pump): + if pump == pump_: + return sum( + m.t[stream] for (pump_, stream) in m.opump if pump == pump_ + ) == sum(m.t[stream] for (pump_, stream) in m.ipump if pump == pump_) + return Constraint.Skip + + b.pumphb = Constraint([pump_], rule=Pumphb, doc="heat balance in pump") + + def Pumppr(_m, pump): + if pump == pump_: + return sum( + m.p[stream] for (pump_, stream) in m.opump if pump == pump_ + ) >= sum(m.p[stream] for (pump_, stream) in m.ipump if pump == pump_) + return Constraint.Skip + + b.pumppr = Constraint([pump_], rule=Pumppr, doc="pressure relation in pump") + + def build_multiple_splitter(b, multi_splitter): + """ + Functions relevant to the splitter block + + Parameters + ---------- + b : Pyomo Block + splitter block + multi_splitter : int + Index of the splitter + """ + + def Splcmb(_m, spl, stream, compon): + if (spl, stream) in m.ospl and spl == multi_splitter: + return m.fc[stream, compon] == sum( + m.e[stream] * m.fc[str2, compon] + for (spl_, str2) in m.ispl + if spl == spl_ + ) + return Constraint.Skip + + b.splcmb = Constraint( + [multi_splitter], + m.str, + m.compon, + rule=Splcmb, + doc="component balance in splitter", + ) + + def Esum(_m, spl): + if spl in m.spl and spl == multi_splitter: + return ( + sum(m.e[stream] for (spl_, stream) in m.ospl if spl_ == spl) == 1.0 + ) + return Constraint.Skip + + b.esum = Constraint( + [multi_splitter], rule=Esum, doc="split fraction relation in splitter" + ) + + def Splpi(_m, spl, stream): + if (spl, stream) in m.ispl and spl == multi_splitter: + return m.splp[spl] == m.p[stream] + return Constraint.Skip + + b.splpi = Constraint( + [multi_splitter], + m.str, + rule=Splpi, + doc="inlet pressure relation (splitter)", + ) + + def Splpo(_m, spl, stream): + if (spl, stream) in m.ospl and spl == multi_splitter: + return m.splp[spl] == m.p[stream] + return Constraint.Skip + + b.splpo = Constraint( + [multi_splitter], + m.str, + rule=Splpo, + doc="outlet pressure relation (splitter)", + ) + + def Splti(_m, spl, stream): + if (spl, stream) in m.ispl and spl == multi_splitter: + return m.splt[spl] == m.t[stream] + return Constraint.Skip + + b.splti = Constraint( + [multi_splitter], + m.str, + rule=Splti, + doc="inlet temperature relation (splitter)", + ) + + def Splto(_m, spl, stream): + if (spl, stream) in m.ospl and spl == multi_splitter: + return m.splt[spl] == m.t[stream] + return Constraint.Skip + + b.splto = Constraint( + [multi_splitter], + m.str, + rule=Splto, + doc="outlet temperature relation (splitter)", + ) + + def build_valve(b, valve_): + """ + Functions relevant to the valve block + + Parameters + ---------- + b : Pyomo Block + valve block + valve_ : int + Index of the valve + """ + + def Valcmb(_m, valve, compon): + return sum( + m.fc[stream, compon] for (valve_, stream) in m.oval if valve == valve_ + ) == sum( + m.fc[stream, compon] for (valve_, stream) in m.ival if valve == valve_ + ) + + b.valcmb = Constraint( + [valve_], m.compon, rule=Valcmb, doc="component balance in valve" + ) + + def Valt(_m, valve): + return sum( + m.t[stream] / (m.p[stream] ** ((m.gam - 1.0) / m.gam)) + for (valv, stream) in m.oval + if valv == valve + ) == sum( + m.t[stream] / (m.p[stream] ** ((m.gam - 1.0) / m.gam)) + for (valv, stream) in m.ival + if valv == valve + ) + + b.valt = Constraint([valve_], rule=Valt, doc="temperature relation in valve") + + def Valp(_m, valve): + return sum( + m.p[stream] for (valv, stream) in m.oval if valv == valve + ) <= sum(m.p[stream] for (valv, stream) in m.ival if valv == valve) + + b.valp = Constraint([valve_], rule=Valp, doc="pressure relation in valve") + + m.Prereference_factor = Param( + initialize=6.3e10, doc="Pre-reference factor for reaction rate constant" + ) + m.Ea_R = Param( + initialize=-26167.0, doc="Activation energy for reaction rate constant" + ) + m.pressure_drop = Param(initialize=0.20684, doc="Pressure drop") + m.selectivity_1 = Param(initialize=0.0036, doc="Selectivity to benzene") + m.selectivity_2 = Param(initialize=-1.544, doc="Selectivity to benzene") + m.conversion_coefficient = Param(initialize=0.372, doc="Conversion coefficient") + + def build_reactor(b, rct): + """ + Functions relevant to the reactor block + + Parameters + ---------- + b : Pyomo Block + reactor block + rct : int + Index of the reactor + """ + + def rctspec(_m, rct, stream): + if (rct, stream) in m.irct: + return m.fc[stream, "h2"] >= 5 * ( + m.fc[stream, "ben"] + m.fc[stream, "tol"] + m.fc[stream, "dip"] + ) + return Constraint.Skip + + b.Rctspec = Constraint( + [rct], m.str, rule=rctspec, doc="spec. on reactor feed stream" + ) + + def rxnrate(_m, rct): + return m.krct[rct] == m.Prereference_factor * exp( + m.Ea_R / (m.rctt[rct] * 100.0) + ) + + b.Rxnrate = Constraint([rct], rule=rxnrate, doc="reaction rate constant") + + def rctconv(_m, rct, stream, compon): + if (rct, compon) in m.rkey and (rct, stream) in m.irct: + return ( + 1.0 - m.conv[rct, compon] + == ( + 1.0 + / ( + 1.0 + + m.conversion_coefficient + * m.krct[rct] + * m.rctvol[rct] + * sqrt(m.fc[stream, compon] / 60 + m.eps1) + * (m.f[stream] / 60.0 + m.eps1) ** (-3.0 / 2.0) + ) + ) + ** 2.0 + ) + return Constraint.Skip + + b.Rctconv = Constraint( + [rct], m.str, m.compon, rule=rctconv, doc="conversion of key component" + ) + + def rctsel(_m, rct): + return (1.0 - m.sel[rct]) == m.selectivity_1 * ( + 1.0 - m.conv[rct, "tol"] + ) ** m.selectivity_2 + + b.Rctsel = Constraint([rct], rule=rctsel, doc="selectivity to benzene") + + def rctcns(_m, rct, stream, compon): + if (rct, compon) in m.rkey and (rct, stream) in m.irct: + return ( + m.consum[rct, compon] == m.conv[rct, compon] * m.fc[stream, compon] + ) + return Constraint.Skip + + b.Rctcns = Constraint( + [rct], m.str, m.compon, rule=rctcns, doc="consumption rate of key comp." + ) + + def rctmbtol(_m, rct): + return ( + sum(m.fc[stream, "tol"] for (rct_, stream) in m.orct if rct_ == rct) + == sum(m.fc[stream, "tol"] for (rct_, stream) in m.irct if rct_ == rct) + - m.consum[rct, "tol"] + ) + + b.Rctmbtol = Constraint( + [rct], rule=rctmbtol, doc="mass balance in reactor (tol)" + ) + + def rctmbben(_m, rct): + return ( + sum(m.fc[stream, "ben"] for (rct_, stream) in m.orct if rct_ == rct) + == sum(m.fc[stream, "ben"] for (rct_, stream) in m.irct if rct_ == rct) + + m.consum[rct, "tol"] * m.sel[rct] + ) + + b.Rctmbben = Constraint( + [rct], rule=rctmbben, doc="mass balance in reactor (ben)" + ) + + def rctmbdip(_m, rct): + return ( + sum(m.fc[stream, "dip"] for (rct1, stream) in m.orct if rct1 == rct) + == sum(m.fc[stream, "dip"] for (rct1, stream) in m.irct if rct1 == rct) + + m.consum[rct, "tol"] * 0.5 + + ( + sum(m.fc[stream, "ben"] for (rct1, stream) in m.irct if rct1 == rct) + - sum( + m.fc[stream, "ben"] for (rct1, stream) in m.orct if rct1 == rct + ) + ) + * 0.5 + ) + + b.Rctmbdip = Constraint( + [rct], rule=rctmbdip, doc="mass balance in reactor (dip)" + ) + + def rctmbh2(_m, rct): + return sum( + m.fc[stream, "h2"] for (rct1, stream) in m.orct if rct1 == rct + ) == sum( + m.fc[stream, "h2"] for (rct1, stream) in m.irct if rct1 == rct + ) - m.consum[ + rct, "tol" + ] - sum( + m.fc[stream, "dip"] for (rct1, stream) in m.irct if rct1 == rct + ) + sum( + m.fc[stream, "dip"] for (rct1, stream) in m.orct if rct1 == rct + ) + + b.Rctmbh2 = Constraint([rct], rule=rctmbh2, doc="mass balance in reactor (h2)") + + def rctpi(_m, rct, stream): + if (rct, stream) in m.irct: + return m.rctp[rct] == m.p[stream] + return Constraint.Skip + + b.Rctpi = Constraint([rct], m.str, rule=rctpi, doc="inlet pressure relation") + + def rctpo(_m, rct, stream): + if (rct, stream) in m.orct: + return m.rctp[rct] - m.pressure_drop == m.p[stream] + return Constraint.Skip + + b.Rctpo = Constraint([rct], m.str, rule=rctpo, doc="outlet pressure relation") + + def rcttave(_m, rct): + return ( + m.rctt[rct] + == ( + sum(m.t[stream] for (rct1, stream) in m.irct if rct1 == rct) + + sum(m.t[stream] for (rct1, stream) in m.orct if rct1 == rct) + ) + / 2 + ) + + b.Rcttave = Constraint([rct], rule=rcttave, doc="average temperature relation") + + def Rctmbch4(_m, rct): + return ( + sum(m.fc[stream, "ch4"] for (rct_, stream) in m.orct if rct_ == rct) + == sum(m.fc[stream, "ch4"] for (rct_, stream) in m.irct if rct == rct_) + + m.consum[rct, "tol"] + ) + + b.rctmbch4 = Constraint( + [rct], rule=Rctmbch4, doc="mass balance in reactor (ch4)" + ) + + def Rcthbadb(_m, rct): + if rct == 1: + return m.heatrxn[rct] * m.consum[rct, "tol"] / 100.0 == sum( + m.cp[stream] * m.f[stream] * m.t[stream] + for (rct_, stream) in m.orct + if rct_ == rct + ) - sum( + m.cp[stream] * m.f[stream] * m.t[stream] + for (rct_, stream) in m.irct + if rct_ == rct + ) + return Constraint.Skip + + b.rcthbadb = Constraint([rct], rule=Rcthbadb, doc="heat balance (adiabatic)") + + def Rcthbiso(_m, rct): + if rct == 2: + return ( + m.heatrxn[rct] * m.consum[rct, "tol"] * 60.0 * 8500 * 1.0e-09 + == m.q[rct] + ) + return Constraint.Skip + + b.rcthbiso = Constraint([rct], rule=Rcthbiso, doc="temp relation (isothermal)") + + def Rctisot(_m, rct): + if rct == 2: + return sum( + m.t[stream] for (rct_, stream) in m.irct if rct_ == rct + ) == sum(m.t[stream] for (rct_, stream) in m.orct if rct_ == rct) + return Constraint.Skip + + b.rctisot = Constraint([rct], rule=Rctisot, doc="temp relation (isothermal)") + + def build_single_mixer(b, mixer): + """ + Functions relevant to the single mixer block + + Parameters + ---------- + b : Pyomo Block + single mixer block + mixer : int + Index of the mixer + """ + + def Mxr1cmb(m_, mxr1, str1, compon): + if (mxr1, str1) in m.omxr1 and mxr1 == mixer: + return m.fc[str1, compon] == sum( + m.fc[str2, compon] for (mxr1_, str2) in m.imxr1 if mxr1_ == mxr1 + ) + return Constraint.Skip + + b.mxr1cmb = Constraint( + [mixer], m.str, m.compon, rule=Mxr1cmb, doc="component balance in mixer" + ) + + m.single_mixer = Block(m.mxr1, rule=build_single_mixer) + + # single output splitter + def build_single_splitter(b, splitter): + """ + Functions relevant to the single splitter block + + Parameters + ---------- + b : Pyomo Block + single splitter block + splitter : int + Index of the splitter + """ + + def Spl1cmb(m_, spl1, compon): + return sum( + m.fc[str1, compon] for (spl1_, str1) in m.ospl1 if spl1_ == spl1 + ) == sum(m.fc[str1, compon] for (spl1_, str1) in m.ispl1 if spl1_ == spl1) + + b.spl1cmb = Constraint( + [splitter], m.compon, rule=Spl1cmb, doc="component balance in splitter" + ) + + def Spl1pi(m_, spl1, str1): + if (spl1, str1) in m.ispl1: + return m.spl1p[spl1] == m.p[str1] + return Constraint.Skip + + b.spl1pi = Constraint( + [splitter], m.str, rule=Spl1pi, doc="inlet pressure relation (splitter)" + ) + + def Spl1po(m_, spl1, str1): + if (spl1, str1) in m.ospl1: + return m.spl1p[spl1] == m.p[str1] + return Constraint.Skip + + b.spl1po = Constraint( + [splitter], m.str, rule=Spl1po, doc="outlet pressure relation (splitter)" + ) + + def Spl1ti(m_, spl1, str1): + if (spl1, str1) in m.ispl1: + return m.spl1t[spl1] == m.t[str1] + return Constraint.Skip + + b.spl1ti = Constraint( + [splitter], m.str, rule=Spl1ti, doc="inlet temperature relation (splitter)" + ) + + def Spl1to(m_, spl1, str1): + if (spl1, str1) in m.ospl1: + return m.spl1t[spl1] == m.t[str1] + return Constraint.Skip + + b.spl1to = Constraint( + [splitter], m.str, rule=Spl1to, doc="outlet temperature relation (splitter)" + ) + + m.single_splitter = Block(m.spl1, rule=build_single_splitter) + + # ## GDP formulation + + m.one = Set(initialize=[1]) + m.two = Set(initialize=[2]) + m.three = Set(initialize=[3]) + m.four = Set(initialize=[4]) + m.five = Set(initialize=[5]) + m.six = Set(initialize=[6]) + + # first disjunction: Purify H2 inlet or not + @m.Disjunct() + def purify_H2(disj): + disj.membrane_1 = Block(m.one, rule=build_membrane) + disj.compressor_1 = Block(m.one, rule=build_compressor) + disj.no_flow_2 = Constraint(expr=m.f[2] == 0) + disj.pressure_match_out = Constraint(expr=m.p[6] == m.p[7]) + disj.tempressure_match_out = Constraint(expr=m.t[6] == m.t[7]) + + @m.Disjunct() + def no_purify_H2(disj): + disj.no_flow_3 = Constraint(expr=m.f[3] == 0) + disj.no_flow_4 = Constraint(expr=m.f[4] == 0) + disj.no_flow_5 = Constraint(expr=m.f[5] == 0) + disj.no_flow_6 = Constraint(expr=m.f[6] == 0) + disj.pressure_match = Constraint(expr=m.p[2] == m.p[7]) + disj.tempressure_match = Constraint(expr=m.t[2] == m.t[7]) + + @m.Disjunction() + def inlet_treatment(m): + return [m.purify_H2, m.no_purify_H2] + + m.multi_mixer_1 = Block(m.one, rule=build_multiple_mixer) + m.furnace_1 = Block(m.one, rule=build_furnace) + + # Second disjunction: Adiabatic or isothermal reactor + @m.Disjunct() + def adiabatic_reactor(disj): + disj.Adiabatic_reactor = Block(m.one, rule=build_reactor) + disj.no_flow_12 = Constraint(expr=m.f[12] == 0) + disj.no_flow_13 = Constraint(expr=m.f[13] == 0) + disj.pressure_match = Constraint(expr=m.p[11] == m.p[14]) + disj.tempressure_match = Constraint(expr=m.t[11] == m.t[14]) + + @m.Disjunct() + def isothermal_reactor(disj): + disj.Isothermal_reactor = Block(m.two, rule=build_reactor) + disj.no_flow_10 = Constraint(expr=m.f[10] == 0) + disj.no_flow_11 = Constraint(expr=m.f[11] == 0) + disj.pressure_match = Constraint(expr=m.p[13] == m.p[14]) + disj.tempressure_match = Constraint(expr=m.t[13] == m.t[14]) + + @m.Disjunction() + def reactor_selection(m): + return [m.adiabatic_reactor, m.isothermal_reactor] + + m.valve_3 = Block(m.three, rule=build_valve) + m.multi_mixer_2 = Block(m.two, rule=build_multiple_mixer) + m.exchanger_1 = Block(m.one, rule=build_exchanger) + m.cooler_1 = Block(m.one, rule=build_cooler) + m.flash_1 = Block(m.one, rule=build_flash) + m.multi_splitter_2 = Block(m.two, rule=build_multiple_splitter) + + # thrid disjunction: recycle methane with membrane or purge it + @m.Disjunct() + def recycle_methane_purge(disj): + disj.no_flow_54 = Constraint(expr=m.f[54] == 0) + disj.no_flow_55 = Constraint(expr=m.f[55] == 0) + disj.no_flow_56 = Constraint(expr=m.f[56] == 0) + disj.no_flow_57 = Constraint(expr=m.f[57] == 0) + + @m.Disjunct() + def recycle_methane_membrane(disj): + disj.no_flow_53 = Constraint(expr=m.f[53] == 0) + disj.membrane_2 = Block(m.two, rule=build_membrane) + disj.compressor_4 = Block(m.four, rule=build_compressor) + + @m.Disjunction() + def methane_treatmet(m): + return [m.recycle_methane_purge, m.recycle_methane_membrane] + + # fourth disjunction: recycle hydrogen with absorber or not + @m.Disjunct() + def recycle_hydrogen(disj): + disj.no_flow_61 = Constraint(expr=m.f[61] == 0) + disj.no_flow_73 = Constraint(expr=m.f[73] == 0) + disj.no_flow_62 = Constraint(expr=m.f[62] == 0) + disj.no_flow_64 = Constraint(expr=m.f[64] == 0) + disj.no_flow_65 = Constraint(expr=m.f[65] == 0) + disj.no_flow_68 = Constraint(expr=m.f[68] == 0) + disj.no_flow_51 = Constraint(expr=m.f[51] == 0) + disj.compressor_2 = Block(m.two, rule=build_compressor) + disj.stream_1 = Constraint(expr=m.f[63] == 0) + disj.stream_2 = Constraint(expr=m.f[67] == 0) + disj.no_flow_69 = Constraint(expr=m.f[69] == 0) + + @m.Disjunct() + def absorber_hydrogen(disj): + disj.heater_4 = Block(m.four, rule=build_heater) + disj.no_flow_59 = Constraint(expr=m.f[59] == 0) + disj.no_flow_60 = Constraint(expr=m.f[60] == 0) + disj.valve_6 = Block(m.six, rule=build_valve) + disj.multi_mixer_4 = Block(m.four, rule=build_multiple_mixer) + disj.absorber_1 = Block(m.one, rule=build_absorber) + disj.compressor_3 = Block(m.three, rule=build_compressor) + disj.absorber_stream = Constraint(expr=m.f[63] + m.f[67] <= 25) + disj.pump_2 = Block(m.two, rule=build_pump) + + @m.Disjunction() + def recycle_selection(m): + return [m.recycle_hydrogen, m.absorber_hydrogen] + + m.multi_mixer_5 = Block(m.five, rule=build_multiple_mixer) + m.multi_mixer_3 = Block(m.three, rule=build_multiple_mixer) + m.multi_splitter_1 = Block(m.one, rule=build_multiple_splitter) + + # fifth disjunction: methane stabilizing selection + @m.Disjunct() + def methane_distillation_column(disj): + disj.no_flow_23 = Constraint(expr=m.f[23] == 0) + disj.no_flow_44 = Constraint(expr=m.f[44] == 0) + disj.no_flow_45 = Constraint(expr=m.f[45] == 0) + disj.no_flow_46 = Constraint(expr=m.f[46] == 0) + disj.no_flow_47 = Constraint(expr=m.f[47] == 0) + disj.no_flow_49 = Constraint(expr=m.f[49] == 0) + disj.no_flow_48 = Constraint(expr=m.f[48] == 0) + disj.heater_1 = Block(m.one, rule=build_heater) + disj.stabilizing_Column_1 = Block(m.one, rule=build_distillation) + disj.multi_splitter_3 = Block(m.three, rule=build_multiple_splitter) + disj.valve_5 = Block(m.five, rule=build_valve) + disj.pressure_match_1 = Constraint(expr=m.p[27] == m.p[30]) + disj.tempressure_match_1 = Constraint(expr=m.t[27] == m.t[30]) + disj.pressure_match_2 = Constraint(expr=m.p[50] == m.p[51]) + disj.tempressure_match_2 = Constraint(expr=m.t[50] == m.t[51]) + + @m.Disjunct() + def methane_flash_separation(disj): + disj.heater_2 = Block(m.two, rule=build_heater) + disj.no_flow_24 = Constraint(expr=m.f[24] == 0) + disj.no_flow_25 = Constraint(expr=m.f[25] == 0) + disj.no_flow_26 = Constraint(expr=m.f[26] == 0) + disj.no_flow_27 = Constraint(expr=m.f[27] == 0) + disj.no_flow_28 = Constraint(expr=m.f[28] == 0) + disj.no_flow_29 = Constraint(expr=m.f[29] == 0) + disj.no_flow_50 = Constraint(expr=m.f[50] == 0) + disj.valve_1 = Block(m.one, rule=build_valve) + disj.cooler_2 = Block(m.two, rule=build_cooler) + disj.flash_2 = Block(m.two, rule=build_flash) + disj.valve_4 = Block(m.four, rule=build_valve) + disj.pressure_match_1 = Constraint(expr=m.p[48] == m.p[30]) + disj.tempressure_match_1 = Constraint(expr=m.t[48] == m.t[30]) + disj.pressure_match_2 = Constraint(expr=m.p[49] == m.p[51]) + disj.tempressure_match_2 = Constraint(expr=m.t[49] == m.t[51]) + + @m.Disjunction() + def H2_selection(m): + return [m.methane_distillation_column, m.methane_flash_separation] + + m.benzene_column = Block(m.two, rule=build_distillation) + + # sixth disjunction: toluene stabilizing selection + @m.Disjunct() + def toluene_distillation_column(disj): + disj.no_flow_37 = Constraint(expr=m.f[37] == 0) + disj.no_flow_38 = Constraint(expr=m.f[38] == 0) + disj.no_flow_39 = Constraint(expr=m.f[39] == 0) + disj.no_flow_40 = Constraint(expr=m.f[40] == 0) + disj.no_flow_41 = Constraint(expr=m.f[41] == 0) + disj.stabilizing_Column_3 = Block(m.three, rule=build_distillation) + disj.pressure_match = Constraint(expr=m.p[34] == m.p[42]) + disj.tempressure_match = Constraint(expr=m.t[34] == m.t[42]) + + @m.Disjunct() + def toluene_flash_separation(disj): + disj.heater_3 = Block(m.three, rule=build_heater) + disj.no_flow_33 = Constraint(expr=m.f[33] == 0) + disj.no_flow_34 = Constraint(expr=m.f[34] == 0) + disj.no_flow_35 = Constraint(expr=m.f[35] == 0) + disj.valve_2 = Block(m.two, rule=build_valve) + disj.flash_3 = Block(m.three, rule=build_flash) + disj.pressure_match = Constraint(expr=m.p[40] == m.p[42]) + disj.tempressure_match = Constraint(expr=m.t[40] == m.t[42]) + + @m.Disjunction() + def toluene_selection(m): + return [m.toluene_distillation_column, m.toluene_flash_separation] + + m.pump_1 = Block(m.one, rule=build_pump) + m.abound = Constraint(expr=m.a[1] >= 0.0) + + # ## objective function + + m.hydrogen_purge_value = Param( + initialize=1.08, doc="heating value of hydrogen purge" + ) + m.electricity_cost = Param( + initialize=0.04 * 24 * 365 / 1000, + doc="electricity cost, value is 0.04 with the unit of [kw/h], now is [kw/yr/k$]", + ) + m.meathane_purge_value = Param( + initialize=3.37, doc="heating value of meathane purge" + ) + m.heating_cost = Param( + initialize=8000.0, doc="Heating cost (steam) with unit [1e6 kj]" + ) + m.cooling_cost = Param( + initialize=700.0, doc="heating cost (water) with unit [1e6 kj]" + ) + m.fuel_cost = Param(initialize=4000.0, doc="fuel cost with unit [1e6 kj]") + m.abs_fixed_cost = Param(initialize=13, doc="fixed cost of absober [$1e3 per year]") + m.abs_linear_coeffcient = Param( + initialize=1.2, + doc="linear coeffcient of absorber (times tray number) [$1e3 per year]", + ) + m.compressor_fixed_cost = Param( + initialize=7.155, doc="compressor fixed cost [$1e3 per year]" + ) + m.compressor_fixed_cost_4 = Param( + initialize=4.866, doc="compressor fixed cost for compressor 4 [$1e3 per year]" + ) + m.compressor_linear_coeffcient = Param( + initialize=0.815, + doc="compressor linear coeffcient (vaporflow rate) [$1e3 per year]", + ) + m.compressor_linear_coeffcient_4 = Param( + initialize=0.887, + doc="compressor linear coeffcient (vaporflow rate) [$1e3 per year]", + ) + m.stabilizing_column_fixed_cost = Param( + initialize=1.126, doc="stabilizing column fixed cost [$1e3 per year]" + ) + m.stabilizing_column_linear_coeffcient = Param( + initialize=0.375, + doc="stabilizing column linear coeffcient (times number of trays) [$1e3 per year]", + ) + m.benzene_column_fixed_cost = Param( + initialize=16.3, doc="benzene column fixed cost [$1e3 per year]" + ) + m.benzene_column_linear_coeffcient = Param( + initialize=1.55, + doc="benzene column linear coeffcient (times number of trays) [$1e3 per year]", + ) + m.toluene_column_fixed_cost = Param( + initialize=3.9, doc="toluene column fixed cost [$1e3 per year]" + ) + m.toluene_column_linear_coeffcient = Param( + initialize=1.12, + doc="toluene column linear coeffcient (times number of trays) [$1e3 per year]", + ) + m.furnace_fixed_cost = Param( + initialize=6.20, doc="toluene column fixed cost [$1e3 per year]" + ) + m.furnace_linear_coeffcient = Param( + initialize=1171.7, + doc="furnace column linear coeffcient [1e9kj/yr] [$1e3 per year]", + ) + m.membrane_seperator_fixed_cost = Param( + initialize=43.24, doc="membrane seperator fixed cost [$1e3 per year]" + ) + m.membrane_seperator_linear_coeffcient = Param( + initialize=49.0, + doc="furnace column linear coeffcient (times inlet flowrate) [$1e3 per year]", + ) + m.adiabtic_reactor_fixed_cost = Param( + initialize=74.3, doc="adiabatic reactor fixed cost [$1e3 per year]" + ) + m.adiabtic_reactor_linear_coeffcient = Param( + initialize=1.257, + doc="adiabatic reactor linear coeffcient (times reactor volumn) [$1e3 per year]", + ) + m.isothermal_reactor_fixed_cost = Param( + initialize=92.875, doc="isothermal reactor fixed cost [$1e3 per year]" + ) + m.isothermal_reactor_linear_coeffcient = Param( + initialize=1.57125, + doc="isothermal reactor linear coeffcient (times reactor volumn) [$1e3 per year]", + ) + m.h2_feed_cost = Param(initialize=2.5, doc="h2 feed cost (95% h2,5% Ch4)") + m.toluene_feed_cost = Param(initialize=14.0, doc="toluene feed cost (100% toluene)") + m.benzene_product = Param( + initialize=19.9, doc="benzene product profit(benzene >= 99.97%)" + ) + m.diphenyl_product = Param( + initialize=11.84, doc="diphenyl product profit(diphenyl = 100%)" + ) + + def profits_from_paper(m): + return ( + 510.0 + * ( + -m.h2_feed_cost * m.f[1] + - m.toluene_feed_cost * (m.f[66] + m.f[67]) + + m.benzene_product * m.f[31] + + m.diphenyl_product * m.f[35] + + m.hydrogen_purge_value + * (m.fc[4, "h2"] + m.fc[28, "h2"] + m.fc[53, "h2"] + m.fc[55, "h2"]) + + m.meathane_purge_value + * (m.fc[4, "ch4"] + m.fc[28, "ch4"] + m.fc[53, "ch4"] + m.fc[55, "ch4"]) + ) + - m.compressor_linear_coeffcient * (m.elec[1] + m.elec[2] + m.elec[3]) + - m.compressor_linear_coeffcient * m.elec[4] + - m.compressor_fixed_cost + * ( + m.purify_H2.binary_indicator_var + + m.recycle_hydrogen.binary_indicator_var + + m.absorber_hydrogen.binary_indicator_var + ) + - m.compressor_fixed_cost * m.recycle_methane_membrane.binary_indicator_var + - sum((m.electricity_cost * m.elec[comp]) for comp in m.comp) + - ( + m.adiabtic_reactor_fixed_cost * m.adiabatic_reactor.binary_indicator_var + + m.adiabtic_reactor_linear_coeffcient * m.rctvol[1] + ) + - ( + m.isothermal_reactor_fixed_cost + * m.isothermal_reactor.binary_indicator_var + + m.isothermal_reactor_linear_coeffcient * m.rctvol[2] + ) + - m.cooling_cost / 1000 * m.q[2] + - ( + m.stabilizing_column_fixed_cost + * m.methane_distillation_column.binary_indicator_var + + m.stabilizing_column_linear_coeffcient * m.ndist[1] + ) + - ( + m.benzene_column_fixed_cost + + m.benzene_column_linear_coeffcient * m.ndist[2] + ) + - ( + m.toluene_column_fixed_cost + * m.toluene_distillation_column.binary_indicator_var + + m.toluene_column_linear_coeffcient * m.ndist[3] + ) + - ( + m.membrane_seperator_fixed_cost * m.purify_H2.binary_indicator_var + + m.membrane_seperator_linear_coeffcient * m.f[3] + ) + - ( + m.membrane_seperator_fixed_cost + * m.recycle_methane_membrane.binary_indicator_var + + m.membrane_seperator_linear_coeffcient * m.f[54] + ) + - ( + m.abs_fixed_cost * m.absorber_hydrogen.binary_indicator_var + + m.abs_linear_coeffcient * m.nabs[1] + ) + - (m.fuel_cost * m.qfuel[1] + m.furnace_linear_coeffcient * m.qfuel[1]) + - sum(m.cooling_cost * m.qc[hec] for hec in m.hec) + - sum(m.heating_cost * m.qh[heh] for heh in m.heh) + - m.furnace_fixed_cost + ) + + m.obj = Objective(rule=profits_from_paper, sense=maximize) + # def profits_GAMS_file(m): + + # "there are several differences between the data from GAMS file and the paper: 1. all the compressor share the same fixed and linear cost in paper but in GAMS they have different fixed and linear cost in GAMS file. 2. the fixed cost for absorber in GAMS file is 3.0 but in the paper is 13.0, but they are getting the same results 3. the electricity cost is not the same" + + # return 510. * (- m.h2_feed_cost * m.f[1] - m.toluene_feed_cost * (m.f[66] + m.f[67]) + m.benzene_product * m.f[31] + m.diphenyl_product * m.f[35] + m.hydrogen_purge_value * (m.fc[4, 'h2'] + m.fc[28, 'h2'] + m.fc[53, 'h2'] + m.fc[55, 'h2']) + m.meathane_purge_value * (m.fc[4, 'ch4'] + m.fc[28, 'ch4'] + m.fc[53, 'ch4'] + m.fc[55, 'ch4'])) - m.compressor_linear_coefficient * (m.elec[1] + m.elec[2] + m.elec[3]) - m.compressor_linear_coefficient_4 * m.elec[4] - m.compressor_fixed_cost * (m.purify_H2.binary_indicator_var + m.recycle_hydrogen.binary_indicator_var + m.absorber_hydrogen.binary_indicator_var) - m.compressor_fixed_cost_4 * m.recycle_methane_membrane.binary_indicator_var - sum((m.costelec * m.elec[comp]) for comp in m.comp) - (m.adiabtic_reactor_fixed_cost * m.adiabatic_reactor.binary_indicator_var + m.adiabtic_reactor_linear_coefficient * m.rctvol[1]) - (m.isothermal_reactor_fixed_cost * m.isothermal_reactor.binary_indicator_var + m.isothermal_reactor_linear_coefficient * m.rctvol[2]) - m.cooling_cost/1000 * m.q[2] - (m.stabilizing_column_fixed_cost * m.methane_distillation_column.binary_indicator_var +m.stabilizing_column_linear_coefficient * m.ndist[1]) - (m.benzene_column_fixed_cost + m.benzene_column_linear_coefficient * m.ndist[2]) - (m.toluene_column_fixed_cost * m.toluene_distillation_column.binary_indicator_var + m.toluene_column_linear_coefficient * m.ndist[3]) - (m.membrane_separator_fixed_cost * m.purify_H2.binary_indicator_var + m.membrane_separator_linear_coefficient * m.f[3]) - (m.membrane_separator_fixed_cost * m.recycle_methane_membrane.binary_indicator_var + m.membrane_separator_linear_coefficient * m.f[54]) - (3.0 * m.absorber_hydrogen.binary_indicator_var + m.abs_linear_coefficient * m.nabs[1]) - (m.fuel_cost * m.qfuel[1] + m.furnace_linear_coefficient* m.qfuel[1]) - sum(m.cooling_cost * m.qc[hec] for hec in m.hec) - sum(m.heating_cost * m.qh[heh] for heh in m.heh) - m.furnace_fixed_cost + # m.obj = Objective(rule=profits_GAMS_file, sense=maximize) + + return m + + +# %% + + +def solve_with_gdpopt(m): + """ + This function solves model m using GDPOpt + + Parameters + ---------- + m : Pyomo Model + The model to be solved + + Returns + ------- + res : solver results + The result of the optimization + """ + opt = SolverFactory("gdpopt") + res = opt.solve( + m, + tee=True, + strategy="LOA", + # strategy='GLOA', + time_limit=3600, + mip_solver="gams", + mip_solver_args=dict(solver="cplex", warmstart=True), + nlp_solver="gams", + nlp_solver_args=dict(solver="ipopth", warmstart=True), + minlp_solver="gams", + minlp_solver_args=dict(solver="dicopt", warmstart=True), + subproblem_presolve=False, + # init_strategy='no_init', + set_cover_iterlim=20, + # calc_disjunctive_bounds=True + ) + return res + + +def solve_with_minlp(m): + """ + This function solves model m using minlp transformation by either Big-M or convex hull + + Parameters + ---------- + m : Pyomo Model + The model to be solved + + Returns + ------- + result : solver results + The result of the optimization + """ + + TransformationFactory("gdp.bigm").apply_to(m, bigM=60) + # TransformationFactory('gdp.hull').apply_to(m) + # result = SolverFactory('baron').solve(m, tee=True) + result = SolverFactory("gams").solve( + m, solver="baron", tee=True, add_options=["option reslim=120;"] + ) + + return result + + +# %% + + +def infeasible_constraints(m): + """ + This function checks infeasible constraint in the model + """ + log_infeasible_constraints(m) + + +# %% + +# enumeration each possible route selection by fixing binary variable values in every disjunctions + + +def enumerate_solutions(m): + """ + Enumerate all possible route selections by fixing binary variables in each disjunctions + + Parameters + ---------- + m : Pyomo Model + Pyomo model to be solved + """ + + H2_treatments = ["purify", "none_purify"] + Reactor_selections = ["adiabatic_reactor", "isothermal_reactor"] + Methane_recycle_selections = ["recycle_membrane", "recycle_purge"] + Absorber_recycle_selections = ["no_absorber", "yes_absorber"] + Methane_product_selections = ["methane_flash", "methane_column"] + Toluene_product_selections = ["toluene_flash", "toluene_column"] + + for H2_treatment in H2_treatments: + for Reactor_selection in Reactor_selections: + for Methane_recycle_selection in Methane_recycle_selections: + for Absorber_recycle_selection in Absorber_recycle_selections: + for Methane_product_selection in Methane_product_selections: + for Toluene_product_selection in Toluene_product_selections: + if H2_treatment == "purify": + m.purify_H2.indicator_var.fix(True) + m.no_purify_H2.indicator_var.fix(False) + else: + m.purify_H2.indicator_var.fix(False) + m.no_purify_H2.indicator_var.fix(True) + if Reactor_selection == "adiabatic_reactor": + m.adiabatic_reactor.indicator_var.fix(True) + m.isothermal_reactor.indicator_var.fix(False) + else: + m.adiabatic_reactor.indicator_var.fix(False) + m.isothermal_reactor.indicator_var.fix(True) + if Methane_recycle_selection == "recycle_membrane": + m.recycle_methane_purge.indicator_var.fix(False) + m.recycle_methane_membrane.indicator_var.fix(True) + else: + m.recycle_methane_purge.indicator_var.fix(True) + m.recycle_methane_membrane.indicator_var.fix(False) + if Absorber_recycle_selection == "yes_absorber": + m.absorber_hydrogen.indicator_var.fix(True) + m.recycle_hydrogen.indicator_var.fix(False) + else: + m.absorber_hydrogen.indicator_var.fix(False) + m.recycle_hydrogen.indicator_var.fix(True) + if Methane_product_selection == "methane_column": + m.methane_flash_separation.indicator_var.fix(False) + m.methane_distillation_column.indicator_var.fix(True) + else: + m.methane_flash_separation.indicator_var.fix(True) + m.methane_distillation_column.indicator_var.fix(False) + if Toluene_product_selection == "toluene_column": + m.toluene_flash_separation.indicator_var.fix(False) + m.toluene_distillation_column.indicator_var.fix(True) + else: + m.toluene_flash_separation.indicator_var.fix(True) + m.toluene_distillation_column.indicator_var.fix(False) + opt = SolverFactory("gdpopt") + res = opt.solve( + m, + tee=False, + strategy="LOA", + time_limit=3600, + mip_solver="gams", + mip_solver_args=dict(solver="gurobi", warmstart=True), + nlp_solver="gams", + nlp_solver_args=dict( + solver="ipopth", + add_options=["option optcr = 0"], + warmstart=True, + ), + minlp_solver="gams", + minlp_solver_args=dict(solver="dicopt", warmstart=True), + subproblem_presolve=False, + init_strategy="no_init", + set_cover_iterlim=20, + ) + print( + "{0:<30}{1:<30}{2:<30}{3:<30}{4:<30}{5:<30}{6:<30}{7:<30}".format( + H2_treatment, + Reactor_selection, + Methane_recycle_selection, + Absorber_recycle_selection, + Methane_product_selection, + Toluene_product_selection, + str(res.solver.termination_condition), + value(m.obj), + ) + ) + + +# %% +def show_decision(m): + """ + print indicator variable value + """ + if value(m.purify_H2.binary_indicator_var) == 1: + print("purify inlet H2") + else: + print("no purify inlet H2") + if value(m.adiabatic_reactor.binary_indicator_var) == 1: + print("adiabatic reactor") + else: + print("isothermal reactor") + if value(m.recycle_methane_membrane.binary_indicator_var) == 1: + print("recycle_membrane") + else: + print("methane purge") + if value(m.absorber_hydrogen.binary_indicator_var) == 1: + print("yes_absorber") + else: + print("no_absorber") + if value(m.methane_distillation_column.binary_indicator_var) == 1: + print("methane_column") + else: + print("methane_flash") + if value(m.toluene_distillation_column.binary_indicator_var) == 1: + print("toluene_column") + else: + print("toluene_flash") + + +# %% + + +if __name__ == "__main__": + # Create GDP model + m = HDA_model() + + # Solve model + res = solve_with_gdpopt(m) + # res = solve_with_minlp(m) + + # Enumerate all solutions + # res = enumerate_solutions(m) + + # Check if constraints are violated + infeasible_constraints(m) + + # show optimal flowsheet selection + # show_decision(m) + + print(res) From ed10c62c26e435b990caa1f79f1dcf8a284c750d Mon Sep 17 00:00:00 2001 From: parkyr Date: Fri, 17 May 2024 11:33:37 -0400 Subject: [PATCH 09/79] fixing coefficient typo --- gdplib/hda/HDA_GDP_gdpopt.py | 62 ++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/gdplib/hda/HDA_GDP_gdpopt.py b/gdplib/hda/HDA_GDP_gdpopt.py index c454efd..d28bd0b 100644 --- a/gdplib/hda/HDA_GDP_gdpopt.py +++ b/gdplib/hda/HDA_GDP_gdpopt.py @@ -3160,9 +3160,9 @@ def toluene_selection(m): ) m.fuel_cost = Param(initialize=4000.0, doc="fuel cost with unit [1e6 kj]") m.abs_fixed_cost = Param(initialize=13, doc="fixed cost of absober [$1e3 per year]") - m.abs_linear_coeffcient = Param( + m.abs_linear_coefficient = Param( initialize=1.2, - doc="linear coeffcient of absorber (times tray number) [$1e3 per year]", + doc="linear coefficient of absorber (times tray number) [$1e3 per year]", ) m.compressor_fixed_cost = Param( initialize=7.155, doc="compressor fixed cost [$1e3 per year]" @@ -3170,62 +3170,62 @@ def toluene_selection(m): m.compressor_fixed_cost_4 = Param( initialize=4.866, doc="compressor fixed cost for compressor 4 [$1e3 per year]" ) - m.compressor_linear_coeffcient = Param( + m.compressor_linear_coefficient = Param( initialize=0.815, - doc="compressor linear coeffcient (vaporflow rate) [$1e3 per year]", + doc="compressor linear coefficient (vaporflow rate) [$1e3 per year]", ) - m.compressor_linear_coeffcient_4 = Param( + m.compressor_linear_coefficient_4 = Param( initialize=0.887, - doc="compressor linear coeffcient (vaporflow rate) [$1e3 per year]", + doc="compressor linear coefficient (vaporflow rate) [$1e3 per year]", ) m.stabilizing_column_fixed_cost = Param( initialize=1.126, doc="stabilizing column fixed cost [$1e3 per year]" ) - m.stabilizing_column_linear_coeffcient = Param( + m.stabilizing_column_linear_coefficient = Param( initialize=0.375, - doc="stabilizing column linear coeffcient (times number of trays) [$1e3 per year]", + doc="stabilizing column linear coefficient (times number of trays) [$1e3 per year]", ) m.benzene_column_fixed_cost = Param( initialize=16.3, doc="benzene column fixed cost [$1e3 per year]" ) - m.benzene_column_linear_coeffcient = Param( + m.benzene_column_linear_coefficient = Param( initialize=1.55, - doc="benzene column linear coeffcient (times number of trays) [$1e3 per year]", + doc="benzene column linear coefficient (times number of trays) [$1e3 per year]", ) m.toluene_column_fixed_cost = Param( initialize=3.9, doc="toluene column fixed cost [$1e3 per year]" ) - m.toluene_column_linear_coeffcient = Param( + m.toluene_column_linear_coefficient = Param( initialize=1.12, - doc="toluene column linear coeffcient (times number of trays) [$1e3 per year]", + doc="toluene column linear coefficient (times number of trays) [$1e3 per year]", ) m.furnace_fixed_cost = Param( initialize=6.20, doc="toluene column fixed cost [$1e3 per year]" ) - m.furnace_linear_coeffcient = Param( + m.furnace_linear_coefficient = Param( initialize=1171.7, - doc="furnace column linear coeffcient [1e9kj/yr] [$1e3 per year]", + doc="furnace column linear coefficient [1e9kj/yr] [$1e3 per year]", ) m.membrane_seperator_fixed_cost = Param( initialize=43.24, doc="membrane seperator fixed cost [$1e3 per year]" ) - m.membrane_seperator_linear_coeffcient = Param( + m.membrane_seperator_linear_coefficient = Param( initialize=49.0, - doc="furnace column linear coeffcient (times inlet flowrate) [$1e3 per year]", + doc="furnace column linear coefficient (times inlet flowrate) [$1e3 per year]", ) m.adiabtic_reactor_fixed_cost = Param( initialize=74.3, doc="adiabatic reactor fixed cost [$1e3 per year]" ) - m.adiabtic_reactor_linear_coeffcient = Param( + m.adiabtic_reactor_linear_coefficient = Param( initialize=1.257, - doc="adiabatic reactor linear coeffcient (times reactor volumn) [$1e3 per year]", + doc="adiabatic reactor linear coefficient (times reactor volumn) [$1e3 per year]", ) m.isothermal_reactor_fixed_cost = Param( initialize=92.875, doc="isothermal reactor fixed cost [$1e3 per year]" ) - m.isothermal_reactor_linear_coeffcient = Param( + m.isothermal_reactor_linear_coefficient = Param( initialize=1.57125, - doc="isothermal reactor linear coeffcient (times reactor volumn) [$1e3 per year]", + doc="isothermal reactor linear coefficient (times reactor volumn) [$1e3 per year]", ) m.h2_feed_cost = Param(initialize=2.5, doc="h2 feed cost (95% h2,5% Ch4)") m.toluene_feed_cost = Param(initialize=14.0, doc="toluene feed cost (100% toluene)") @@ -3249,8 +3249,8 @@ def profits_from_paper(m): + m.meathane_purge_value * (m.fc[4, "ch4"] + m.fc[28, "ch4"] + m.fc[53, "ch4"] + m.fc[55, "ch4"]) ) - - m.compressor_linear_coeffcient * (m.elec[1] + m.elec[2] + m.elec[3]) - - m.compressor_linear_coeffcient * m.elec[4] + - m.compressor_linear_coefficient * (m.elec[1] + m.elec[2] + m.elec[3]) + - m.compressor_linear_coefficient * m.elec[4] - m.compressor_fixed_cost * ( m.purify_H2.binary_indicator_var @@ -3261,42 +3261,42 @@ def profits_from_paper(m): - sum((m.electricity_cost * m.elec[comp]) for comp in m.comp) - ( m.adiabtic_reactor_fixed_cost * m.adiabatic_reactor.binary_indicator_var - + m.adiabtic_reactor_linear_coeffcient * m.rctvol[1] + + m.adiabtic_reactor_linear_coefficient * m.rctvol[1] ) - ( m.isothermal_reactor_fixed_cost * m.isothermal_reactor.binary_indicator_var - + m.isothermal_reactor_linear_coeffcient * m.rctvol[2] + + m.isothermal_reactor_linear_coefficient * m.rctvol[2] ) - m.cooling_cost / 1000 * m.q[2] - ( m.stabilizing_column_fixed_cost * m.methane_distillation_column.binary_indicator_var - + m.stabilizing_column_linear_coeffcient * m.ndist[1] + + m.stabilizing_column_linear_coefficient * m.ndist[1] ) - ( m.benzene_column_fixed_cost - + m.benzene_column_linear_coeffcient * m.ndist[2] + + m.benzene_column_linear_coefficient * m.ndist[2] ) - ( m.toluene_column_fixed_cost * m.toluene_distillation_column.binary_indicator_var - + m.toluene_column_linear_coeffcient * m.ndist[3] + + m.toluene_column_linear_coefficient * m.ndist[3] ) - ( m.membrane_seperator_fixed_cost * m.purify_H2.binary_indicator_var - + m.membrane_seperator_linear_coeffcient * m.f[3] + + m.membrane_seperator_linear_coefficient * m.f[3] ) - ( m.membrane_seperator_fixed_cost * m.recycle_methane_membrane.binary_indicator_var - + m.membrane_seperator_linear_coeffcient * m.f[54] + + m.membrane_seperator_linear_coefficient * m.f[54] ) - ( m.abs_fixed_cost * m.absorber_hydrogen.binary_indicator_var - + m.abs_linear_coeffcient * m.nabs[1] + + m.abs_linear_coefficient * m.nabs[1] ) - - (m.fuel_cost * m.qfuel[1] + m.furnace_linear_coeffcient * m.qfuel[1]) + - (m.fuel_cost * m.qfuel[1] + m.furnace_linear_coefficient * m.qfuel[1]) - sum(m.cooling_cost * m.qc[hec] for hec in m.hec) - sum(m.heating_cost * m.qh[heh] for heh in m.heh) - m.furnace_fixed_cost From 2d6f2509ed333d629fce61e37ac38330d2d3b167 Mon Sep 17 00:00:00 2001 From: parkyr Date: Fri, 17 May 2024 11:37:10 -0400 Subject: [PATCH 10/79] fixing spelling of separator --- gdplib/hda/HDA_GDP_gdpopt.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/gdplib/hda/HDA_GDP_gdpopt.py b/gdplib/hda/HDA_GDP_gdpopt.py index d28bd0b..456d440 100644 --- a/gdplib/hda/HDA_GDP_gdpopt.py +++ b/gdplib/hda/HDA_GDP_gdpopt.py @@ -3206,10 +3206,10 @@ def toluene_selection(m): initialize=1171.7, doc="furnace column linear coefficient [1e9kj/yr] [$1e3 per year]", ) - m.membrane_seperator_fixed_cost = Param( - initialize=43.24, doc="membrane seperator fixed cost [$1e3 per year]" + m.membrane_separator_fixed_cost = Param( + initialize=43.24, doc="membrane separator fixed cost [$1e3 per year]" ) - m.membrane_seperator_linear_coefficient = Param( + m.membrane_separator_linear_coefficient = Param( initialize=49.0, doc="furnace column linear coefficient (times inlet flowrate) [$1e3 per year]", ) @@ -3284,13 +3284,13 @@ def profits_from_paper(m): + m.toluene_column_linear_coefficient * m.ndist[3] ) - ( - m.membrane_seperator_fixed_cost * m.purify_H2.binary_indicator_var - + m.membrane_seperator_linear_coefficient * m.f[3] + m.membrane_separator_fixed_cost * m.purify_H2.binary_indicator_var + + m.membrane_separator_linear_coefficient * m.f[3] ) - ( - m.membrane_seperator_fixed_cost + m.membrane_separator_fixed_cost * m.recycle_methane_membrane.binary_indicator_var - + m.membrane_seperator_linear_coefficient * m.f[54] + + m.membrane_separator_linear_coefficient * m.f[54] ) - ( m.abs_fixed_cost * m.absorber_hydrogen.binary_indicator_var From d8a0e2ba4326a004263e042aa481dccfb3adc186 Mon Sep 17 00:00:00 2001 From: parkyr Date: Fri, 17 May 2024 11:39:45 -0400 Subject: [PATCH 11/79] more spell fixes --- gdplib/hda/HDA_GDP_gdpopt.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gdplib/hda/HDA_GDP_gdpopt.py b/gdplib/hda/HDA_GDP_gdpopt.py index 456d440..5bb7505 100644 --- a/gdplib/hda/HDA_GDP_gdpopt.py +++ b/gdplib/hda/HDA_GDP_gdpopt.py @@ -3013,7 +3013,7 @@ def reactor_selection(m): m.flash_1 = Block(m.one, rule=build_flash) m.multi_splitter_2 = Block(m.two, rule=build_multiple_splitter) - # thrid disjunction: recycle methane with membrane or purge it + # third disjunction: recycle methane with membrane or purge it @m.Disjunct() def recycle_methane_purge(disj): disj.no_flow_54 = Constraint(expr=m.f[54] == 0) @@ -3028,7 +3028,7 @@ def recycle_methane_membrane(disj): disj.compressor_4 = Block(m.four, rule=build_compressor) @m.Disjunction() - def methane_treatmet(m): + def methane_treatment(m): return [m.recycle_methane_purge, m.recycle_methane_membrane] # fourth disjunction: recycle hydrogen with absorber or not @@ -3218,14 +3218,14 @@ def toluene_selection(m): ) m.adiabtic_reactor_linear_coefficient = Param( initialize=1.257, - doc="adiabatic reactor linear coefficient (times reactor volumn) [$1e3 per year]", + doc="adiabatic reactor linear coefficient (times reactor volume) [$1e3 per year]", ) m.isothermal_reactor_fixed_cost = Param( initialize=92.875, doc="isothermal reactor fixed cost [$1e3 per year]" ) m.isothermal_reactor_linear_coefficient = Param( initialize=1.57125, - doc="isothermal reactor linear coefficient (times reactor volumn) [$1e3 per year]", + doc="isothermal reactor linear coefficient (times reactor volume) [$1e3 per year]", ) m.h2_feed_cost = Param(initialize=2.5, doc="h2 feed cost (95% h2,5% Ch4)") m.toluene_feed_cost = Param(initialize=14.0, doc="toluene feed cost (100% toluene)") From 652020132d054b276f28992805d3a0d9cef33552 Mon Sep 17 00:00:00 2001 From: Carolina Tristan Date: Fri, 17 May 2024 13:52:57 -0400 Subject: [PATCH 12/79] Updates documentation and units --- gdplib/kaibel/kaibel_init.py | 150 +++++++++++++++++++++++++++-------- 1 file changed, 119 insertions(+), 31 deletions(-) diff --git a/gdplib/kaibel/kaibel_init.py b/gdplib/kaibel/kaibel_init.py index 82400e7..7bdac1d 100644 --- a/gdplib/kaibel/kaibel_init.py +++ b/gdplib/kaibel/kaibel_init.py @@ -25,21 +25,29 @@ Figure 2. Sequence of columns for the separation of a quaternary mixture """ -from __future__ import division - from pyomo.environ import (exp, log10, minimize, NonNegativeReals, Objective, RangeSet, SolverFactory, value, Var) from gdplib.kaibel.kaibel_prop import get_model_with_properties def initialize_kaibel(): - m = get_model_with_properties() + """Initialize the Kaibel optimization model. + + This function initializes the Kaibel optimization model by setting up the operating conditions, + initial liquid compositions, and creating the necessary variables and constraints. + Returns + ------- + None + """ + ## Get the model with properties from kaibel_prop.py + m = get_model_with_properties() + ## Operating conditions m.Preb = 1.2 # Reboiler pressure in bar m.Pcon = 1.05 # Condenser pressure in bar - m.Pf = 1.02 + m.Pf = 1.02 # Column pressure in bar Pnmin = {} # Pressure in bars Pnmin[1] = m.Preb # Reboiler pressure in bars @@ -104,7 +112,7 @@ def initialize_kaibel(): #### - mn = m.clone() + mn = m.clone() # Clone the model to add the initialization code mn.name = "Initialization Code" @@ -123,39 +131,115 @@ def initialize_kaibel(): doc='Temperature in K', bounds=(0, 500), domain=NonNegativeReals) mn.Tr1nmin = Var(mn.cols, mn.sec, mn.nc1, - doc='Temperature term for vapor pressure', + doc='Temperature term for vapor pressure column 1', domain=NonNegativeReals, bounds=(0, None)) mn.Tr2nmin = Var(mn.cols, mn.sec, mn.nc2, - doc='Temperature term for vapor pressure', + doc='Temperature term for vapor pressure column 2', domain=NonNegativeReals, bounds=(0, None)) mn.Tr3nmin = Var(mn.cols, mn.sec, mn.nc3, - doc='Temperature term for vapor pressure', + doc='Temperature term for vapor pressure column 3', domain=NonNegativeReals, bounds=(0, None)) @mn.Constraint(mn.cols, mn.sec, mn.nc1, - doc="Temperature term for vapor pressure") + doc="Temperature term for vapor pressure column 1") def _column1_reduced_temperature(mn, col, sec, nc): + """Calculate the reduced temperature for column 1. + + This function calculates the reduced temperature for column 1 based on the given parameters using the Peng-Robinson equation of state. + + Parameters + ---------- + mn : Pyomo ConcreteModel + The optimization model + col : int + The column index + sec : int + The section index + nc : int + The component index in column 1 + + Returns + ------- + Constraint + The constraint statement to calculate the reduced temperature. + """ return mn.Tr1nmin[col, sec, nc] * mn.Tnmin[col, sec] == mn.prop[nc, 'TC'] @mn.Constraint(mn.cols, mn.sec, mn.nc2, - doc="Temperature term for vapor pressure") + doc="Temperature term for vapor pressure column 2") def _column2_reduced_temperature(mn, col, sec, nc): + """Calculate the reduced temperature for column 2. + + This function calculates the reduced temperature for column 2 based on the given parameters using the Peng-Robinson equation of state. + + Parameters + ---------- + mn : Pyomo ConcreteModel + The optimization model + col : int + The column index + sec : int + The section index + nc : int + The component index in column 2 + + Returns + ------- + Constraint + The constraint equation to calculate the reduced temperature + """ return mn.Tr2nmin[col, sec, nc] * mn.Tnmin[col, sec] == mn.prop[nc, 'TC'] @mn.Constraint(mn.cols, mn.sec, mn.nc3, - doc="Temperature term for vapor pressure") + doc="Temperature term for vapor pressure column 3") def _column3_reduced_temperature(mn, col, sec, nc): + """Calculate the reduced temperature for column 3. + + This function calculates the reduced temperature for column 3 based on the given parameters. + + Parameters + ---------- + mn : Pyomo ConcreteModel + The optimization model + col : int + The column index + sec : int + The section index + nc : int + The component index in column 3 + + Returns + ------- + Constraint + The constraint equation to calculate the reduced temperature in column 3 + """ return mn.Tr3nmin[col, sec, nc] * mn.Tnmin[col, sec] == mn.prop[nc, 'TC'] @mn.Constraint(mn.cols, mn.sec, doc="Boiling point temperature") def _equilibrium_equation(mn, col, sec): + """Equilibrium equations for a given column and section. + + Parameters + ---------- + mn : Pyomo ConcreteModel + The optimization model object with properties + col : int + The column index + sec : int + The section index + + Returns + ------- + Constraint + The constraint equation to calculate the boiling point temperature using the Peng-Robinson equation of state + """ if col == 1: a = mn.Tr1nmin b = mn.nc1 @@ -200,7 +284,7 @@ def _equilibrium_equation(mn, col, sec): # 1 = number of trays in rectifying section and # 2 = number of trays in stripping section side_feed = {} # Side feed location - av_alpha = {} # Average relative volatilities + av_alpha = {} # Average relative volatilities xi_lhc = {} # Liquid composition in columns rel = mn.Bdes / mn.Ddes # Ratio between products flowrates ln = {} # Light component for the different columns @@ -220,6 +304,7 @@ def _equilibrium_equation(mn, col, sec): b = mn.nc2 else: b = mn.nc3 + # For each component in the column and section calculate the vapor composition with the Peng-Robinson equation of state for sec in mn.sec: for nc in b: yc[col, sec, nc] = xi_nmin[col, sec, nc] * mn.prop[nc, 'PC'] * exp( @@ -233,36 +318,39 @@ def _equilibrium_equation(mn, col, sec): + mn.prop[nc, 'vpD'] * \ (1 - value(mn.Tnmin[col, sec]) / mn.prop[nc, 'TC'])**6 ) - ) / Pnmin[sec] + ) / Pnmin[sec] # Vapor composition in the different sections for the different components in the columns for col in mn.cols: + # Calculate the relative volatility of the light and heavy components in the different sections for the different columns xi_lhc[col, 4] = xi_nmin[col, 1, ln[col]] / \ - xi_nmin[col, 3, hn[col]] + xi_nmin[col, 3, hn[col]] # Liquid composition in the different sections with the initial liquid composition of the components in the different sections and columns and ln and hn which are the light and heavy components in the different columns for sec in mn.sec: kl[col, sec] = yc[col, sec, ln[col]] / \ - xi_nmin[col, sec, ln[col]] + xi_nmin[col, sec, ln[col]] # Light component in the different sections kh[col, sec] = yc[col, sec, hn[col]] / \ - xi_nmin[col, sec, hn[col]] + xi_nmin[col, sec, hn[col]] # Heavy component in the different sections xi_lhc[col, sec] = xi_nmin[col, sec, hn[col]] / \ - xi_nmin[col, sec, ln[col]] - alpha[col, sec] = kl[col, sec] / kh[col, sec] + xi_nmin[col, sec, ln[col]] # Liquid composition in the different sections + alpha[col, sec] = kl[col, sec] / kh[col, sec] # Relative volatility in the different sections for col in mn.cols: + # Calculate the average relative volatilities and the minimum number of trays with Fenske's and Kirkbride's method av_alpha[col] = (alpha[col, 1] * alpha[col, 2] - * alpha[col, 3])**(1 / 3) + * alpha[col, 3])**(1 / 3) # Average relative volatilities calculated with the relative volatilities of the components in the three sections Nmin[col] = log10((1 / xi_lhc[col, 3]) * - xi_lhc[col, 1]) / log10(av_alpha[col]) - ter[col] = (rel * xi_lhc[col, 2] * (xi_lhc[col, 4]**2))**0.206 - Nfeed[1, col] = ter[col] * Nmin[col] / (1 + ter[col]) - Nfeed[2, col] = Nfeed[1, col] / ter[col] - side_feed[col] = Nfeed[2, col] - - m.Nmintot = sum(Nmin[col] for col in mn.cols) - m.Knmin = int(m.Nmintot) + 1 - - m.TB0 = value(mn.Tnmin[1, 1]) - m.Tf0 = value(mn.Tnmin[1, 2]) - m.TD0 = value(mn.Tnmin[2, 3]) + xi_lhc[col, 1]) / log10(av_alpha[col]) # Minimum number of trays calculated with Fenske's method + ter[col] = (rel * xi_lhc[col, 2] * (xi_lhc[col, 4]**2))**0.206 # Term to calculate the minimum number of trays with Kirkbride's method + # Side feed optimal location using Kirkbride's method + Nfeed[1, col] = ter[col] * Nmin[col] / (1 + ter[col]) # Number of trays in rectifying section + Nfeed[2, col] = Nfeed[1, col] / ter[col] # Number of trays in stripping section + side_feed[col] = Nfeed[2, col] # Side feed location + + m.Nmintot = sum(Nmin[col] for col in mn.cols) # Total minimum number of trays + m.Knmin = int(m.Nmintot) + 1 # Total optimal minimum number of trays + + m.TB0 = value(mn.Tnmin[1, 1]) # Reboiler temperature in K in column 1 + m.Tf0 = value(mn.Tnmin[1, 2]) # Side feed temperature in K in column 1 + m.TD0 = value(mn.Tnmin[2, 3]) # Distillate temperature in K in column 2 return m From 035eee28acc7ed1d732e38c763d9637949c69bd1 Mon Sep 17 00:00:00 2001 From: Carolina Tristan Date: Mon, 20 May 2024 07:35:56 -0400 Subject: [PATCH 13/79] Refactor side_feed _flash.py calculation for clarity and documentation --- gdplib/kaibel/kaibel_side_flash.py | 105 +++++++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 7 deletions(-) diff --git a/gdplib/kaibel/kaibel_side_flash.py b/gdplib/kaibel/kaibel_side_flash.py index 014d447..b2bdee9 100644 --- a/gdplib/kaibel/kaibel_side_flash.py +++ b/gdplib/kaibel/kaibel_side_flash.py @@ -1,19 +1,33 @@ """ Side feed flash """ - -from __future__ import division - from pyomo.environ import ( ConcreteModel, Constraint, exp, minimize, NonNegativeReals, Objective, Param, RangeSet, SolverFactory, value, Var, ) def calc_side_feed_flash(m): - msf = ConcreteModel('SIDE FEED FLASH') + """Calculate the side feed flash. + + This function solves a flash calculation for a side feed in a distillation process. + It calculates the vapor-liquid equilibrium, vapor pressure, and liquid and vapor compositions + for the given side feed. + + Parameters + ---------- + m : Pyomo ConcreteModel + The Pyomo model object containing the necessary parameters and variables. + + Returns + ------- + Pyomo ConcreteModel + The updated Pyomo model with the calculated values, which include the vapor-liquid equilibrium, vapor pressure, and liquid and vapor compositions for the side feed. + + """ + msf = ConcreteModel('SIDE FEED FLASH') # Main side feed flash model msf.nc = RangeSet(1, m.c, doc='Number of components') - m.xfi = {} + m.xfi = {} # Liquid composition in the side feed of the main model for each component. for nc in msf.nc: m.xfi[nc] = 1 / m.c @@ -47,6 +61,18 @@ def calc_side_feed_flash(m): @msf.Constraint(doc="Vapor fraction") def _algq(msf): + """This function calculates the vapor fraction (q) in the side feed using the Peng-Robinson equation of state. + + Parameters + ---------- + msf : Pyomo ConcreteModel + The main side feed flash model. + + Returns + ------- + q : float + The vapor fraction in the side feed. + """ return sum(m.xfi[nc] * (1 - msf.Keqf[nc]) / \ (1 + msf.q * (msf.Keqf[nc] - 1)) for nc in msf.nc) == 0 @@ -55,24 +81,87 @@ def _algq(msf): @msf.Constraint(msf.nc, doc="Side feed liquid composition") def _algx(msf, nc): + """Side feed liquid composition + + This function calculates the liquid composition (xf) for a given component (nc) in the side feed. + + Parameters + ---------- + msf : Pyomo ConcreteModel + The main side feed flash model. + nc : int + The component index. + + Returns + ------- + xf : float + The liquid composition for the given component with Keqf, q, and xfi, which are the equilibrium constant, vapor fraction, and liquid composition in the side feed, respectively. + """ return msf.xf[nc] * (1 + msf.q * (msf.Keqf[nc] - 1)) == m.xfi[nc] @msf.Constraint(msf.nc, doc="Side feed vapor composition") def _algy(msf, nc): + """Side feed vapor composition + + This function calculates the vapor composition (ysf) for a given component (nc) in the side. + + Parameters + ---------- + msf : Pyomo ConcreteModel + The main side feed flash model. + nc : int + The component index. + + Returns + ------- + yf : float + The vapor composition for the given component given the liquid composition (xf) and the equilibrium constant (Keqf). + """ return msf.yf[nc] == msf.xf[nc] * msf.Keqf[nc] + # TODO: Is it computed using the Peng-Robinson equation of state? @msf.Constraint(msf.nc, doc="Vapor-liquid equilibrium constant") def _algKeq(msf, nc): + """Calculate the vapor-liquid equilibrium constant for a given component using the Peng-Robinson equation of state. + + Parameters + ---------- + msf : Pyomo ConcreteModel + The MultiStageFlash model. + nc : int + The component index. + + Returns + ------- + Keqf : float + The equilibrium constant for the component taking into account the vapor pressure (Pvf) and the liquid pressure (Pf). + """ return msf.Keqf[nc] * m.Pf == msf.Pvf[nc] @msf.Constraint(msf.nc, doc="Side feed vapor pressure") def _algPvf(msf, nc): + """Calculate the vapor fraction for a given component. + + This function calculates the vapor fraction (Pvf) for a given component (nc) using the Peng-Robinson equation of state. + + Parameters + ---------- + msf : Pyomo ConcreteModel + The main side flash object. + nc : int + The component index. + + Returns + ------- + Pvf : float + The vapor fraction for the given component considering the temperature (Tf) and the properties of the component set in the main model. + """ return msf.Pvf[nc] == m.prop[nc, 'PC'] * exp( m.prop[nc, 'TC'] / msf.Tf * ( m.prop[nc, 'vpA'] * \ @@ -85,6 +174,7 @@ def _algPvf(msf, nc): (1 - msf.Tf / m.prop[nc, 'TC'])**6 ) ) + # TODO: Is it computed using the Peng-Robinson equation of state? msf.OBJ = Objective( expr=1, @@ -94,10 +184,11 @@ def _algPvf(msf, nc): SolverFactory('ipopt').solve(msf, tee=False) - m.yfi = {} + # Update the main model with the calculated values + m.yfi = {} # Vapor composition for nc in msf.nc: m.yfi[nc] = value(msf.yf[nc]) - m.q_init = value(msf.q) + m.q_init = value(msf.q) # Vapor fraction return m From 407a09352f784d00f37c9e1b6e998245f59e2530 Mon Sep 17 00:00:00 2001 From: Carolina Tristan Date: Mon, 20 May 2024 07:37:39 -0400 Subject: [PATCH 14/79] Refactor kaibel_solve_gdp.py for clarity and documentation --- gdplib/kaibel/kaibel_solve_gdp.py | 1373 +++++++++++++++++++++++++++-- 1 file changed, 1315 insertions(+), 58 deletions(-) diff --git a/gdplib/kaibel/kaibel_solve_gdp.py b/gdplib/kaibel/kaibel_solve_gdp.py index 9c1592f..f324375 100644 --- a/gdplib/kaibel/kaibel_solve_gdp.py +++ b/gdplib/kaibel/kaibel_solve_gdp.py @@ -1,7 +1,12 @@ -""" Kaibel Column model: GDP formulation """ +""" Kaibel Column model: GDP formulation. +The solution requires the specification of certain parameters, such as the number trays, feed location, etc., and an initialization procedure, which consists of the next three steps: +(i) a preliminary design of the separation considering a sequence of indirect continuous distillation columns (CDCs) to obtain the minimum number of stages with Fenske Equation in the function initialize_kaibel in kaibel_init.py +(ii) flash calculation for the feed with the function calc_side_feed_flash in kaibel_side_flash.py +(iii) calculation of variable bounds by solving the NLP problem. -from __future__ import division +After the initialization, the GDP model is built. +""" from math import copysign @@ -14,11 +19,21 @@ def build_model(): + """ + Build the GDP Kaibel Column model. + It combines the initialization of the model and the flash calculation for the side feed before the GDP formulation. + Returns + ------- + ConcreteModel + The constructed GDP Kaibel Column model. + """ + + # Calculation of the theoretical minimum number of trays (Knmin) and initial temperature values (TB0, Tf0, TD0). m = initialize_kaibel() - # Side feed init + # Side feed init. Returns side feed vapor composition yfi and vapor fraction q_init m = calc_side_feed_flash(m) @@ -43,19 +58,19 @@ def build_model(): m.Tlo = m.Tcon - 20 # Temperature lower bound m.Tup = m.Treb + 20 # Temperature upper bound - m.flow_max = 1e3 # Flowrates upper bound - m.Qmax = 60 # Heat loads upper bound + m.flow_max = 1e3 # Flowrates upper bound in mol/s + m.Qmax = 60 # Heat loads upper bound in J/s #### Column tray details - m.num_trays = m.np # Trays per section + m.num_trays = m.np # Trays per section. np = 25 m.min_num_trays = 10 # Minimum number of trays per section m.num_total = m.np * 3 # Total number of trays m.feed_tray = 12 # Side feed tray m.sideout1_tray = 8 # Side outlet 1 tray m.sideout2_tray = 17 # Side outlet 2 tray - m.reb_tray = 1 # Reboiler tray - m.con_tray = m.num_trays # Condenser tray + m.reb_tray = 1 # Reboiler tray. Dividing wall starting tray + m.con_tray = m.num_trays # Condenser tray. Dividing wall ending tray @@ -69,7 +84,8 @@ def build_model(): ## Sets m.section = RangeSet(4, doc="Column sections:1=top, 2=feed side, 3=prod side, 4=bot") - m.section_main = Set(initialize=[1, 4]) + m.section_main = Set(initialize=[1, 4], + doc="Main sections of the column") m.tray = RangeSet(m.np, doc="Potential trays in each section") @@ -80,7 +96,7 @@ def build_model(): m.tray_below_so1 = RangeSet(m.sideout1_tray, doc="Trays below side outlet 1") m.tray_below_so2 = RangeSet(m.sideout2_tray, - doc="Trays below side outlet 1") + doc="Trays below side outlet 2") m.comp = RangeSet(4, doc="Components") @@ -107,24 +123,25 @@ def build_model(): ## Calculation of initial values - m.dHvap = {} # Heat of vaporization + m.dHvap = {} # Heat of vaporization [J/mol] - m.P0 = {} # Initial pressure - m.T0 = {} # Initial temperature + m.P0 = {} # Initial pressure [bar] + m.T0 = {} # Initial temperature [K] m.L0 = {} # Initial individual liquid flowrate in mol/s - m.V0 = {} # Initial individual vapor flowrate + m.V0 = {} # Initial individual vapor flowrate in mol/s m.Vtotal0 = {} # Initial total vapor flowrate in mol/s m.Ltotal0 = {} # Initial liquid flowrate in mol/s m.x0 = {} # Initial liquid composition m.y0 = {} # Initial vapor composition m.actv0 = {} # Initial activity coefficients - m.cpdT0 = {} # Initial heat capacity for liquid and vapor phases + m.cpdT0 = {} # Initial heat capacity for liquid and vapor phases in J/mol K m.hl0 = {} # Initial liquid enthalpy in J/mol m.hv0 = {} # Initial vapor enthalpy in J/mol - m.Pi = m.Preb # Initial given pressure value - m.Ti = {} # Initial known temperature values + m.Pi = m.Preb # Initial given pressure value in bar + m.Ti = {} # Initial known temperature values in K + ## Initial values for pressure, temperature, flowrates, composition, and enthalpy for sec in m.section: for n_tray in m.tray: m.P0[sec, n_tray] = m.Pi @@ -166,6 +183,7 @@ def build_model(): elif n_tray >= m.num_trays * 2: m.T0[4, n_tray - m.num_trays*2] = m.Ti[n_tray] + ## Initial vapor and liquid composition of the feed and activity coefficients for sec in m.section: for n_tray in m.tray: for comp in m.comp: @@ -174,19 +192,19 @@ def build_model(): m.y0[sec, n_tray, comp] = m.xfi[comp] - ## Enthalpy boundary values - hlb = {} # Liquid enthalpy - hvb = {} # Vapor enthalpy - cpb = {} # Heact capacity - dHvapb = {} # Heat of vaporization - Tbounds = {} # Temperature bounds + ## Assigns the enthalpy boundary values, heat capacity, heat of vaporization calculation, temperature bounds, and light and heavy key components. + hlb = {} # Liquid enthalpy in J/mol + hvb = {} # Vapor enthalpy in J/mol + cpb = {} # Heact capacity in J/mol K + dHvapb = {} # Heat of vaporization in J/mol + Tbounds = {} # Temperature bounds in K kc = {} # Light and heavy key components - Tbounds[1] = m.Tcon - Tbounds[2] = m.Treb + Tbounds[1] = m.Tcon # Condenser temperature in K + Tbounds[2] = m.Treb # Reboiler temperature in K kc[1] = m.lc kc[2] = m.hc - + ## Heat of vaporization calculation for each component in the feed. for comp in m.comp: dHvapb[comp] = -( m.Rgas * m.prop[comp, 'TC'] * ( @@ -209,7 +227,7 @@ def build_model(): ) ) - + ## Boundary values for heat capacity and enthalpy of liquid and vapor phases for light and heavy key components in the feed. for b in m.bounds: for cp in m.cplv: cpb[b, cp] = m.cpc[cp] * ( @@ -232,16 +250,17 @@ def build_model(): + dHvapb[b] ) - m.hllo = (1 - copysign(0.2, hlb[1])) * hlb[1] / m.Hscale - m.hlup = (1 + copysign(0.2, hlb[2])) * hlb[2] / m.Hscale - m.hvlo = (1 - copysign(0.2, hvb[1])) * hvb[1] / m.Hscale - m.hvup = (1 + copysign(0.2, hvb[2])) * hvb[2] / m.Hscale - + m.hllo = (1 - copysign(0.2, hlb[1])) * hlb[1] / m.Hscale # Liquid enthalpy lower bound + m.hlup = (1 + copysign(0.2, hlb[2])) * hlb[2] / m.Hscale # Liquid enthalpy upper bound + m.hvlo = (1 - copysign(0.2, hvb[1])) * hvb[1] / m.Hscale # Vapor enthalpy lower bound + m.hvup = (1 + copysign(0.2, hvb[2])) * hvb[2] / m.Hscale # Vapor enthalpy upper bound + # copysign is a function that returns the first argument with the sign of the second argument + ## Heat of vaporization for each component in the feed scaled by Hscale for comp in m.comp: m.dHvap[comp] = dHvapb[comp] / m.Hscale - + ## Heat capacity calculation for liquid and vapor phases using Ruczika-D method for each component in the feed, section, and tray for sec in m.section: for n_tray in m.tray: for comp in m.comp: @@ -261,7 +280,7 @@ def build_model(): ) / m.Hscale ) - + ## Liquid and vapor enthalpy calculation using Ruczika-D method for each component in the feed, section, and tray for sec in m.section: for n_tray in m.tray: for comp in m.comp: @@ -274,11 +293,12 @@ def build_model(): ) #### Side feed - m.cpdTf = {} # Heat capacity for side feed J/mol K - m.hlf = {} # Liquid enthalpy for side feed in J/mol - m.hvf = {} # Vapor enthalpy for side feed in J/mol - m.F0 = {} # Side feed flowrate per component in mol/s + m.cpdTf = {} # Heat capacity for side feed [J/mol K] + m.hlf = {} # Liquid enthalpy for side feed [J/mol] + m.hvf = {} # Vapor enthalpy for side feed [J/mol] + m.F0 = {} # Side feed flowrate per component [mol/s] + ## Heat capacity in liquid and vapor phases for side feed for each component using Ruczika-D method for comp in m.comp: for cp in m.cplv: m.cpdTf[comp, cp] = ( @@ -295,16 +315,17 @@ def build_model(): m.prop[comp, 'cpD', cp] / 4 ) / m.Hscale ) - + + ## Side feed flowrate and liquid and vapor enthalpy calculation using Ruczika-D method for each component in the feed for comp in m.comp: - m.F0[comp] = m.xfi[comp] * m.Fi + m.F0[comp] = m.xfi[comp] * m.Fi # Side feed flowrate per component computed from the feed composition and flowrate Fi m.hlf[comp] = ( m.cpdTf[comp, 1] - ) + ) # Liquid enthalpy for side feed computed from the heat capacity for side feed and liquid phase m.hvf[comp] = ( m.cpdTf[comp, 2] + m.dHvap[comp] - ) + ) # Vapor enthalpy for side feed computed from the heat capacity for side feed and vapor phase and heat of vaporization m.P = Var(m.section, m.tray, doc="Pressure at each potential tray in bars", @@ -329,11 +350,11 @@ def build_model(): initialize=m.y0) m.dl = Var(m.dw, - doc="Liquid distributor", + doc="Liquid distributor in the dividing wall sections", bounds=(0.2, 0.8), initialize=m.dl0) m.dv = Var(m.dw, - doc="Vapor distributor", + doc="Vapor distributor in the dividing wall sections", bounds=(0, 1), domain=NonNegativeReals, initialize=m.dv0) @@ -405,11 +426,11 @@ def build_model(): bounds=(0, m.Qmax), initialize=1) - m.rr = Var(doc="Internal reflux ratio", + m.rr = Var(doc="Internal reflux ratio in the column", domain=NonNegativeReals, bounds=(0.7, 1), initialize=m.rr0) - m.bu = Var(doc="Boilup rate", + m.bu = Var(doc="Boilup rate in the reboiler", domain=NonNegativeReals, bounds=(0.7, 1), initialize=m.bu0) @@ -431,9 +452,11 @@ def build_model(): initialize=m.actv0) m.errx = Var(m.section, m.tray, + doc="Error in liquid composition [mol/mol]", bounds=(-1e-3, 1e-3), initialize=0) m.erry = Var(m.section, m.tray, + doc="Error in vapor composition [mol/mol]", bounds=(-1e-3, 1e-3), initialize=0) m.slack = Var(m.section, m.tray, m.comp, doc="Slack variable", @@ -441,30 +464,91 @@ def build_model(): initialize=0) m.tray_exists = Disjunct(m.section, m.tray, + doc="Disjunct that enforce the existence of each tray", rule=_build_tray_equations) m.tray_absent = Disjunct(m.section, m.tray, + doc="Disjunct that enforce the absence of each tray", rule=_build_pass_through_eqns) @m.Disjunction(m.section, m.tray, doc="Disjunction between whether each tray exists or not") def tray_exists_or_not(m, sec, n_tray): + """ + Disjunction between whether each tray exists or not. + + Parameters + ---------- + m : Pyomo ConcreteModel + The Pyomo model object. + sec : int + The section index. + n_tray : int + The tray index. + + Returns + ------- + Disjunction + The disjunction between whether each tray exists or not. + """ return [m.tray_exists[sec, n_tray], m.tray_absent[sec, n_tray]] @m.Constraint(m.section_main) def minimum_trays_main(m, sec): - return sum(m.tray_exists[sec, n_tray].binary_indicator_var - for n_tray in m.candidate_trays_main) + 1 >= m.min_num_trays + """ + Constraint that ensures the minimum number of trays in the main section. + + Parameters + ---------- + m : Pyomo ConcreteModel + The model object for the GDP Kaibel Column. + sec : Set + The section index. + + Returns + ------- + Constraint + A constraint expression that enforces the minimum number of trays in the main section to be greater than or equal to the minimum number of trays. + """ + return sum(m.tray_exists[sec, n_tray].binary_indicator_var + for n_tray in m.candidate_trays_main) + 1 >= m.min_num_trays @m.Constraint() def minimum_trays_feed(m): - return sum(m.tray_exists[2, n_tray].binary_indicator_var - for n_tray in m.candidate_trays_feed) + 1 >= m.min_num_trays + """ + Constraint function that ensures the minimum number of trays in the feed section is met. + + Parameters + ---------- + m : Pyomo ConcreteModel + The Pyomo model object. + + Returns + ------- + Constraint + The constraint expression that enforces the minimum number of trays is greater than or equal to the minimum number of trays. + """ + return sum(m.tray_exists[2, n_tray].binary_indicator_var + for n_tray in m.candidate_trays_feed) + 1 >= m.min_num_trays + #TOCHECK: pyomo.GDP Syntax @m.Constraint() def minimum_trays_product(m): - return sum(m.tray_exists[3, n_tray].binary_indicator_var - for n_tray in m.candidate_trays_product) + 1 >= m.min_num_trays + """ + Constraint function to calculate the minimum number of trays in the product section. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + + Returns + ------- + Constraint + The constraint expression that enforces the minimum number of trays is greater than or equal to the minimum number of trays. + """ + return sum(m.tray_exists[3, n_tray].binary_indicator_var + for n_tray in m.candidate_trays_product) + 1 >= m.min_num_trays ## Fixed trays @@ -484,112 +568,408 @@ def minimum_trays_product(m): #### Global constraints - @m.Constraint(m.dw, m.tray, doc="Monotonic temperature") + @m.Constraint(m.dw, m.tray, doc="Monotonic temperature in the dividing wall sections") def monotonic_temperature(m, sec, n_tray): + """This function returns a constraint object representing the monotonic temperature constraint. + + The monotonic temperature constraint ensures that the temperature on each tray in the distillation column + is less than or equal to the temperature on the top tray. + + Parameters + ---------- + m : Pyomo ConcreteModel + The Pyomo model object. + sec : Set + The set of sections in the dividing wall sections. + n_tray : Set + The set of trays in the distillation column. + + Returns + ------- + Constraint + The monotonic temperature constraint specifying that the temperature on each tray is less than or equal to the temperature on the top tray of section 1 which is the condenser. + """ return m.T[sec, n_tray] <= m.T[1, m.num_trays] @m.Constraint(doc="Liquid distributor") def liquid_distributor(m): + """Defines the liquid distributor constraint. + + This constraint ensures that the sum of the liquid distributors in all sections is equal to 1. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + + Returns + ------- + Constraint + The liquid distributor constraint that enforces the sum of the liquid flow rates in all sections is equal to 1. + + """ return sum(m.dl[sec] for sec in m.dw) - 1 == 0 @m.Constraint(doc="Vapor distributor") def vapor_distributor(m): + """ + Add a constraint to ensure that the sum of the vapor distributors is equal to 1. + + Parameters + ---------- + m : Pyomo ConcreteModel + The Pyomo model object. + + Returns + ------- + Constraint + The vapor distributor constraint. + """ return sum(m.dv[sec] for sec in m.dw) - 1 == 0 @m.Constraint(doc="Reboiler composition specification") def heavy_product(m): + """ + Reboiler composition specification for the heavy component in the feed. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + + Returns + ------- + Constraint + The constraint that enforces the reboiler composition is greater than or equal to the specified composition xspechc final liquid composition for butanol, the heavy component in the feed. + """ return m.x[1, m.reb_tray, m.hc] >= m.xspec_hc - @m.Constraint(doc="Condenser composition specification") def light_product(m): + """ + Condenser composition specification for the light component in the feed. + + Parameters + ---------- + m : Model + The optimization model. + + Returns + ------- + Constraint + The constraint that enforces the condenser composition is greater than or equal to the specified final liquid composition for ethanol, xspeclc , the light component in the feed. + """ return m.x[4, m.con_tray, m.lc] >= m.xspec_lc @m.Constraint(doc="Side outlet 1 final liquid composition") def intermediate1_product(m): + ''' + This constraint ensures that the intermediate 1 final liquid composition is greater than or equal to the specified composition xspec_inter1, which is the final liquid composition for ethanol. + + Parameters + ---------- + m : Pyomo ConcreteModel. + The optimization model. + + Returns + ------- + Constraint + The constraint that enforces the intermediate 1 final liquid composition is greater than or equal to the specified composition xspec_inter1, which is the final liquid composition for ethanol. + ''' return m.x[3, m.sideout1_tray, 3] >= m.xspec_inter3 @m.Constraint(doc="Side outlet 2 final liquid composition") def intermediate2_product(m): + """ + This constraint ensures that the intermediate 2 final liquid composition is greater than or equal to the specified composition xspec_inter2, which is the final liquid composition for butanol. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + + Returns + ------- + Constraint + The constraint that enforces the intermediate 2 final liquid composition is greater than or equal to the specified composition xspec_inter2, which is the final liquid composition for butanol. + """ return m.x[3, m.sideout2_tray, 2] >= m.xspec_inter2 @m.Constraint(doc="Reboiler flowrate") def _heavy_product_flow(m): + """ + Reboiler flowrate constraint that ensures the reboiler flowrate is greater than or equal to the specified flowrate Bdes, which is the flowrate of butanol. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + + Returns + ------- + Constraint + The constraint that enforces the reboiler flowrate is greater than or equal to the specified flowrate Bdes, which is the flowrate of butanol. + """ return m.Btotal >= m.Bdes @m.Constraint(doc="Condenser flowrate") def _light_product_flow(m): + """ + Condenser flowrate constraint that ensures the condenser flowrate is greater than or equal to the specified flowrate Ddes, which is the flowrate of ethanol. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + + Returns + ------- + Constraint + The constraint that enforces the condenser flowrate is greater than or equal to the specified flowrate Ddes, which is the flowrate of ethanol. + """ return m.Dtotal >= m.Ddes @m.Constraint(m.so, doc="Intermediate flowrate") def _intermediate_product_flow(m, so): + """ + Intermediate flowrate constraint that ensures the intermediate flowrate is greater than or equal to the specified flowrate Sdes, which is the flowrate of the intermediate side product 2 and 3. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + so : int + The side product outlet index. + + Returns + ------- + Constraint + The constraint that enforces the intermediate flowrate is greater than or equal to the specified flowrate Sdes, which is the flowrate of the intermediate side product 2 and 3. + """ return m.Stotal[so] >= m.Sdes @m.Constraint(doc="Internal boilup ratio, V/L") def _internal_boilup_ratio(m): + """ + Internal boilup ratio constraint that ensures the internal boilup ratio is equal to the boilup rate times the liquid flowrate on the reboiler tray is equal to the vapor flowrate on the tray above the reboiler tray. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + + Returns + ------- + Constraint + The constraint that enforces the boilup rate times the liquid flowrate on the reboiler tray is equal to the vapor flowrate on the tray above the reboiler tray. + """ return m.bu * m.Ltotal[1, m.reb_tray + 1] == m.Vtotal[1, m.reb_tray] @m.Constraint(doc="Internal reflux ratio, L/V") def internal_reflux_ratio(m): + """ + Internal reflux ratio constraint that ensures the internal reflux ratio is equal to the reflux rate times the vapor flowrate on the tray above the condenser tray is equal to the liquid flowrate on the condenser tray. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + + Returns + ------- + Constraint + The constraint that enforces the reflux rate times the vapor flowrate on the tray above the condenser tray is equal to the liquid flowrate on the condenser tray. + """ return m.rr * m.Vtotal[4, m.con_tray - 1] == m.Ltotal[4, m.con_tray] @m.Constraint(doc="External boilup ratio relation with bottoms") def _external_boilup_ratio(m): + """ + External boilup ratio constraint that ensures the external boilup ratio times the liquid flowrate on the reboiler tray is equal to the bottoms flowrate. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + + Returns + ------- + Constraint + The constraint that enforces the external boilup ratio times the liquid flowrate on the reboiler tray is equal to the bottoms flowrate. + """ return m.Btotal == (1 - m.bu) * m.Ltotal[1, m.reb_tray + 1] @m.Constraint(doc="External reflux ratio relation with distillate") def _external_reflux_ratio(m): + """ + External reflux ratio constraint that ensures the external reflux ratio times the vapor flowrate on the tray above the condenser tray is equal to the distillate flowrate. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + + Returns + ------- + Constraint + The constraint that enforces the external reflux ratio times the vapor flowrate on the tray above the condenser tray is equal to the distillate flowrate. + """ return m.Dtotal == (1 - m.rr) * m.Vtotal[4, m.con_tray - 1] @m.Constraint(m.section, m.tray, doc="Total vapor flowrate") def _total_vapor_flowrate(m, sec, n_tray): + """ + Constraint that ensures the total vapor flowrate is equal to the sum of the vapor flowrates of each component on each tray. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + sec : int + The section index. + n_tray : int + The tray index. + + Returns + ------- + Constraint + The constraint that enforces the total vapor flowrate is equal to the sum of the vapor flowrates of each component on each tray on each section. + """ return sum(m.V[sec, n_tray, comp] for comp in m.comp) == m.Vtotal[sec, n_tray] @m.Constraint(m.section, m.tray, doc="Total liquid flowrate") def _total_liquid_flowrate(m, sec, n_tray): + """ + Constraint that ensures the total liquid flowrate is equal to the sum of the liquid flowrates of each component on each tray on each section. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + sec : int + The section index. + n_tray : int + The tray index. + + Returns + ------- + Constraint + The constraint that enforces the total liquid flowrate is equal to the sum of the liquid flowrates of each component on each tray on each section. + """ return sum(m.L[sec, n_tray, comp] for comp in m.comp) == m.Ltotal[sec, n_tray] @m.Constraint(m.comp, doc="Bottoms and liquid relation") def bottoms_equality(m, comp): + """ + Constraint that ensures the bottoms flowrate is equal to the liquid flowrate of each component on the reboiler tray. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint that enforces the bottoms flowrate is equal to the liquid flowrate of each component on the reboiler tray. + """ return m.B[comp] == m.L[1, m.reb_tray, comp] @m.Constraint(m.comp) def condenser_total(m, comp): + """ + Constraint that ensures the distillate flowrate in the condenser is null. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + comp : int + The component index. + Returns + ------- + Constraint + The constraint that enforces the distillate flowrate is equal to zero in the condenser. + """ return m.V[4, m.con_tray, comp] == 0 @m.Constraint() def total_bottoms_product(m): + """ + Constraint that ensures the total bottoms flowrate is equal to the sum of the bottoms flowrates of each component. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + + Returns + ------- + Constraint + The constraint that enforces the total bottoms flowrate is equal to the sum of the bottoms flowrates of each component. + """ return sum(m.B[comp] for comp in m.comp) == m.Btotal @m.Constraint() def total_distillate_product(m): + """ + Constraint that ensures the total distillate flowrate is equal to the sum of the distillate flowrates of each component. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + + Returns + ------- + Constraint + The constraint that enforces the total distillate flowrate is equal to the sum of the distillate flowrates of each component. + """ return sum(m.D[comp] for comp in m.comp) == m.Dtotal @m.Constraint(m.so) def total_side_product(m, so): + """ + Constraint that ensures the total side product flowrate is equal to the sum of the side product flowrates of each component. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + so : int + The side product index, 2 or 3 for the intermediate side products. + + Returns + ------- + Constraint + The constraint that enforces the total side product flowrate is equal to the sum of the side product flowrates of each component. + """ return sum(m.S[so, comp] for comp in m.comp) == m.Stotal[so] - + # Considers the number of existent trays and operating costs (condenser and reboiler heat duties) in the column. To ensure equal weights to the capital and operating costs, the number of existent trays is multiplied by a weight coefficient of 1000. + m.obj = Objective( expr= (m.Qcon + m.Qreb) * m.Hscale + 1e3 * ( sum( @@ -601,45 +981,125 @@ def total_side_product(m, so): + sum(m.tray_exists[3, n_tray].binary_indicator_var for n_tray in m.candidate_trays_product) + 1), - sense=minimize) + sense=minimize, doc="Objective function to minimize the operating costs and number of existent trays in the column") - @m.Constraint(m.section_main, m.candidate_trays_main) + @m.Constraint(m.section_main, m.candidate_trays_main, doc="Logic proposition for main section") def _logic_proposition_main(m, sec, n_tray): + """ + Apply a logic proposition constraint to the main section and candidate trays to specify the order of trays in the column is from bottom to top provided the condition is met. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + sec : int + The section index. + n_tray : int + The tray index. + + Returns + ------- + Constraint or NoConstraint + The constraint expression or NoConstraint if the condition is not met. + """ + if n_tray > m.reb_tray and (n_tray + 1) < m.num_trays: return m.tray_exists[sec, n_tray].binary_indicator_var <= m.tray_exists[sec, n_tray + 1].binary_indicator_var else: return Constraint.NoConstraint + # TOCHECK: Update the logic proposition constraint for the main section with the new pyomo.gdp syntax @m.Constraint(m.candidate_trays_feed) def _logic_proposition_feed(m, n_tray): + """ + Apply a logic proposition constraint to the feed section and candidate trays to specify the order of trays in the column is from bottom to top provided the condition is met. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + n_tray : int + The tray index. + + Returns + ------- + Constraint or NoConstraint + The constraint expression or NoConstraint if the condition is not met. + """ if n_tray > m.reb_tray and (n_tray + 1) < m.feed_tray: return m.tray_exists[2, n_tray].binary_indicator_var <= m.tray_exists[2, n_tray + 1].binary_indicator_var elif n_tray > m.feed_tray and (n_tray + 1) < m.con_tray: return m.tray_exists[2, n_tray + 1].binary_indicator_var <= m.tray_exists[2, n_tray].binary_indicator_var else: return Constraint.NoConstraint + # TODO: Update the logic proposition constraint for the feed section with the new pyomo.gdp syntax @m.Constraint(m.candidate_trays_product) def _logic_proposition_section3(m, n_tray): + """ + Apply a logic proposition constraint to the product section and candidate trays to specify the order of trays in the column is from bottom to top provided the condition is met. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + n_tray : int + The tray index. + + Returns + ------- + Constraint or NoConstraint + The constraint expression or NoConstraint if the condition is not met. + """ if n_tray > 1 and (n_tray + 1) < m.num_trays: return m.tray_exists[3, n_tray].binary_indicator_var <= m.tray_exists[3, n_tray + 1].binary_indicator_var else: return Constraint.NoConstraint + # TODO: Update the logic proposition constraint for the product section with the new pyomo.gdp syntax @m.Constraint(m.tray) def equality_feed_product_side(m, n_tray): - return m.tray_exists[2, n_tray].binary_indicator_var == m.tray_exists[3, n_tray].binary_indicator_var + """ + Constraint that enforces the equality of the binary indicator variables for the feed and product side trays. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + n_tray : int + The tray index. + + Returns + ------- + Constraint + The constraint expression that enforces the equality of the binary indicator variables for the feed and product side trays. + """ + return m.tray_exists[2, n_tray].binary_indicator_var == m.tray_exists[3, n_tray].binary_indicator_var + # TODO: Update the equality constraint for the feed and product side trays with the new pyomo.gdp syntax @m.Constraint() def _existent_minimum_numbertrays(m): + """ + Constraint that enforces the minimum number of trays in the column. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + + Returns + ------- + Constraint + The constraint expression that enforces the minimum number of trays in each section and each tray to be greater than or equal to the minimum number of trays. + """ return sum( sum(m.tray_exists[sec, n_tray].binary_indicator_var for n_tray in m.tray) for sec in m.section) - sum(m.tray_exists[3, n_tray].binary_indicator_var for n_tray in m.tray) >= int(m.min_tray) @@ -650,11 +1110,40 @@ def _existent_minimum_numbertrays(m): def enforce_tray_exists(m, sec, n_tray): + """ + Enforce the existence of a tray in the column. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + sec : int + The section index. + n_tray : int + The tray index. + """ m.tray_exists[sec, n_tray].indicator_var.fix(True) m.tray_absent[sec, n_tray].deactivate() def _build_tray_equations(m, sec, n_tray): + """ + Build the equations for the tray in the column as a function of the section when the tray exists. + Points to the appropriate function to build the equations for the section in the column. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + sec : int + The section index. + n_tray : int + The tray index. + + Returns + ------- + None + """ build_function = { 1: _build_bottom_equations, 2: _build_feed_side_equations, @@ -666,11 +1155,40 @@ def _build_tray_equations(m, sec, n_tray): def _build_bottom_equations(disj, n_tray): + """ + Build the equations for the bottom section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the bottom section in the column. + n_tray : int + The tray index. + + Returns + ------- + None + """ m = disj.model() @disj.Constraint(m.comp, doc="Bottom section 1 mass per component balances") def _bottom_mass_percomponent_balances(disj, comp): + """ + Mass per component balances for the bottom section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the bottom section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the mass balance per component in the bottom section of the column. + """ return ( (m.L[1, n_tray + 1, comp] if n_tray < m.num_trays else 0) @@ -695,6 +1213,19 @@ def _bottom_mass_percomponent_balances(disj, comp): @disj.Constraint(doc="Bottom section 1 energy balances") def _bottom_energy_balances(disj): + """ + Energy balances for the bottom section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the bottom section in the column. + + Returns + ------- + Constraint + The constraint expression that enforces the energy balance for the bottom section in the column. + """ return ( sum( (m.L[1, n_tray + 1, comp] * m.hl[1, n_tray + 1, comp] @@ -724,28 +1255,102 @@ def _bottom_energy_balances(disj): @disj.Constraint(m.comp, doc="Bottom section 1 liquid flowrate per component") def _bottom_liquid_percomponent(disj, comp): + """ + Liquid flowrate per component in the bottom section of the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the bottom section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid flowrate per component in the bottom section of the column. + """ return m.L[1, n_tray, comp] == m.Ltotal[1, n_tray] * m.x[1, n_tray, comp] @disj.Constraint(m.comp, doc="Bottom section 1 vapor flowrate per component") def _bottom_vapor_percomponent(disj, comp): + """ + Vapor flowrate per component in the bottom section of the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the bottom section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor flowrate per component in the bottom section of the column. + """ return m.V[1, n_tray, comp] == m.Vtotal[1, n_tray] * m.y[1, n_tray, comp] @disj.Constraint(doc="Bottom section 1 liquid composition equilibrium summation") def bottom_liquid_composition_summation(disj): + """ + Liquid composition equilibrium summation for the bottom section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the bottom section in the column. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid composition equilibrium summation for the bottom section in the column. + It ensures the sum of the liquid compositions is equal to 1 plus the error in the liquid composition. + """ return sum(m.x[1, n_tray, comp] for comp in m.comp) - 1 == m.errx[1, n_tray] @disj.Constraint(doc="Bottom section 1 vapor composition equilibrium summation") def bottom_vapor_composition_summation(disj): + """ + Vapor composition equilibrium summation for the bottom section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the bottom section in the column. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor composition equilibrium summation for the bottom section in the column. + It ensures the sum of the vapor compositions is equal to 1 plus the error in the vapor composition. + """ return sum(m.y[1, n_tray, comp] for comp in m.comp) - 1 == m.erry[1, n_tray] @disj.Constraint(m.comp, doc="Bottom section 1 vapor composition") def bottom_vapor_composition(disj, comp): + """ + Vapor composition for the bottom section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the bottom section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor composition for the bottom section in the column. + The equation is derived from the vapor-liquid equilibrium relationship. + """ return m.y[1, n_tray, comp] == m.x[1, n_tray, comp] * ( m.actv[1, n_tray, comp] * ( m.prop[comp, 'PC'] * exp( @@ -768,6 +1373,21 @@ def bottom_vapor_composition(disj, comp): @disj.Constraint(m.comp, doc="Bottom section 1 liquid enthalpy") def bottom_liquid_enthalpy(disj, comp): + """ + Liquid enthalpy for the bottom section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the bottom section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid enthalpy for the bottom section in the column. + """ return m.hl[1, n_tray, comp] == ( m.cpc[1] * ( (m.T[1, n_tray] - m.Tref) * \ @@ -785,6 +1405,21 @@ def bottom_liquid_enthalpy(disj, comp): @disj.Constraint(m.comp, doc="Bottom section 1 vapor enthalpy") def bottom_vapor_enthalpy(disj, comp): + """ + Vapor enthalpy for the bottom section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the bottom section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor enthalpy for the bottom section in the column. + """ return m.hv[1, n_tray, comp] == ( m.cpc[2] * ( (m.T[1, n_tray] - m.Tref) * \ @@ -802,6 +1437,21 @@ def bottom_vapor_enthalpy(disj, comp): @disj.Constraint(m.comp, doc="Bottom section 1 liquid activity coefficient") def bottom_activity_coefficient(disj, comp): + """ + Liquid activity coefficient for the bottom section in the column equal to 1. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the bottom section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid activity coefficient for the bottom section for each component and tray in the column to be equal to 1. + """ return m.actv[1, n_tray, comp] == 1 @@ -809,11 +1459,40 @@ def bottom_activity_coefficient(disj, comp): def _build_feed_side_equations(disj, n_tray): + """ + Build the equations for the feed side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the feed side section in the column. + n_tray : int + The tray index. + + Returns + ------- + None + """ m = disj.model() @disj.Constraint(m.comp, doc="Feed section 2 mass per component balances") def _feedside_masspercomponent_balances(disj, comp): + """ + Mass per component balances for the feed side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the feed side section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the mass balance per component in the feed side section of the column. + """ return ( (m.L[2, n_tray + 1, comp] if n_tray < m.num_trays else 0) @@ -833,6 +1512,19 @@ def _feedside_masspercomponent_balances(disj, comp): @disj.Constraint(doc="Feed section 2 energy balances") def _feedside_energy_balances(disj): + """ + Energy balances for the feed side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the feed side section in the column. + + Returns + ------- + Constraint + The constraint expression that enforces the energy balance for the feed side section in the column. + """ return ( sum( (m.L[2, n_tray + 1, comp] * m.hl[2, n_tray + 1, comp] @@ -857,28 +1549,102 @@ def _feedside_energy_balances(disj): @disj.Constraint(m.comp, doc="Feed section 2 liquid flowrate per component") def _feedside_liquid_percomponent(disj, comp): + """ + Liquid flowrate per component in the feed side section of the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the feed side section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid flowrate per component in the feed side section of the column is equal to the total liquid flowrate times the liquid composition. + """ return m.L[2, n_tray, comp] == m.Ltotal[2, n_tray] * m.x[2, n_tray, comp] @disj.Constraint(m.comp, doc="Feed section 2 vapor flowrate per component") def _feedside_vapor_percomponent(disj, comp): + """ + Vapor flowrate per component in the feed side section of the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the feed side section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor flowrate per component in the feed side section of the column is equal to the total vapor flowrate times the vapor composition. + """ return m.V[2, n_tray, comp] == m.Vtotal[2, n_tray] * m.y[2, n_tray, comp] @disj.Constraint(doc="Feed section 2 liquid composition equilibrium summation") def feedside_liquid_composition_summation(disj): + """ + Liquid composition equilibrium summation for the feed side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the feed side section in the column. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid composition equilibrium summation for the feed side section in the column. + It ensures the sum of the liquid compositions is equal to 1 plus the error in the liquid composition. + """ return sum(m.x[2, n_tray, comp] for comp in m.comp) - 1 == m.errx[2, n_tray] @disj.Constraint(doc="Feed section 2 vapor composition equilibrium summation") def feedside_vapor_composition_summation(disj): + """ + Vapor composition equilibrium summation for the feed side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the feed side section in the column. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor composition equilibrium summation for the feed side section in the column. + It ensures the sum of the vapor compositions is equal to 1 plus the error in the vapor composition. + """ return sum(m.y[2, n_tray, comp] for comp in m.comp) - 1 == m.erry[2, n_tray] @disj.Constraint(m.comp, doc="Feed section 2 vapor composition") def feedside_vapor_composition(disj, comp): + """ + Vapor composition for the feed side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the feed side section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor composition for the feed side section in the column. + The equation is derived from the vapor-liquid equilibrium relationship. + """ return m.y[2, n_tray, comp] == m.x[2, n_tray, comp] * ( m.actv[2, n_tray, comp] * ( m.prop[comp, 'PC'] * exp( @@ -901,6 +1667,21 @@ def feedside_vapor_composition(disj, comp): @disj.Constraint(m.comp, doc="Feed section 2 liquid enthalpy") def feedside_liquid_enthalpy(disj, comp): + """ + Liquid enthalpy for the feed side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the feed side section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid enthalpy for the feed side section in the column. + """ return m.hl[2, n_tray, comp] == ( m.cpc[1] * ( (m.T[2, n_tray] - m.Tref) * \ @@ -918,6 +1699,21 @@ def feedside_liquid_enthalpy(disj, comp): @disj.Constraint(m.comp, doc="Feed section 2 vapor enthalpy") def feedside_vapor_enthalpy(disj, comp): + """ + Vapor enthalpy for the feed side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the feed side section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor enthalpy for the feed side section in the column. + """ return m.hv[2, n_tray, comp] == ( m.cpc[2] * ( (m.T[2, n_tray] - m.Tref) * \ @@ -936,6 +1732,22 @@ def feedside_vapor_enthalpy(disj, comp): @disj.Constraint(m.comp, doc="Feed section 2 liquid activity coefficient") def feedside_activity_coefficient(disj, comp): + """ + Liquid activity coefficient for the feed side section in the column equal to 1. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the feed side section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid activity coefficient for the feed side section for each component and tray in the column to be equal to 1. + This is an assumption for the feed side section, since the feed is assumed to be ideal. + """ return m.actv[2, n_tray, comp] == 1 @@ -943,11 +1755,40 @@ def feedside_activity_coefficient(disj, comp): def _build_product_side_equations(disj, n_tray): + """ + Build the equations for the product side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the product side section in the column. + n_tray : int + The tray index. + + Returns + ------- + None + """ m = disj.model() @disj.Constraint(m.comp, doc="Product section 3 mass per component balances") def _productside_masspercomponent_balances(disj, comp): + """ + Mass per component balances for the product side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the product side section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the mass balance per component in the product side section of the column. + """ return ( (m.L[3, n_tray + 1, comp] if n_tray < m.num_trays else 0) @@ -969,6 +1810,19 @@ def _productside_masspercomponent_balances(disj, comp): @disj.Constraint(doc="Product section 3 energy balances") def _productside_energy_balances(disj): + """ + Energy balances for the product side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the product side section in the column. + + Returns + ------- + Constraint + The constraint expression that enforces the energy balance for the product side section in the column. + """ return ( sum( (m.L[3, n_tray + 1, comp] * m.hl[3, n_tray + 1, comp] @@ -993,28 +1847,102 @@ def _productside_energy_balances(disj): @disj.Constraint(m.comp, doc="Product section 3 liquid flowrate per component") def _productside_liquid_percomponent(disj, comp): + """ + Liquid flowrate per component in the product side section of the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the product side section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid flowrate per component in the product side section of the column is equal to the total liquid flowrate times the liquid composition. + """ return m.L[3, n_tray, comp] == m.Ltotal[3, n_tray] * m.x[3, n_tray, comp] @disj.Constraint(m.comp, doc="Product section 3 vapor flowrate per component") def _productside_vapor_percomponent(disj, comp): + """ + Vapor flowrate per component in the product side section of the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the product side section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor flowrate per component in the product side section of the column is equal to the total vapor flowrate times the vapor composition. + """ return m.V[3, n_tray, comp] == m.Vtotal[3, n_tray] * m.y[3, n_tray, comp] @disj.Constraint(doc="Product section 3 liquid composition equilibrium summation") def productside_liquid_composition_summation(disj): + """ + Liquid composition equilibrium summation for the product side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the product side section in the column. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid composition equilibrium summation for the product side section in the column. + It ensures the sum of the liquid compositions is equal to 1 plus the error in the liquid composition. + """ return sum(m.x[3, n_tray, comp] for comp in m.comp) - 1 == m.errx[3, n_tray] @disj.Constraint(doc="Product section 3 vapor composition equilibrium summation") def productside_vapor_composition_summation(disj): + """ + Vapor composition equilibrium summation for the product side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the product side section in the column. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor composition equilibrium summation for the product side section in the column. + It ensures the sum of the vapor compositions is equal to 1 plus the error in the vapor composition. + """ return sum(m.y[3, n_tray, comp] for comp in m.comp) - 1 == m.erry[3, n_tray] @disj.Constraint(m.comp, doc="Product section 3 vapor composition") def productside_vapor_composition(disj, comp): + """ + Vapor composition for the product side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the product side section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor composition for the product side section in the column. + The equation is derived from the vapor-liquid equilibrium relationship. + """ return m.y[3, n_tray, comp] == m.x[3, n_tray, comp] * ( m.actv[3, n_tray, comp] * ( m.prop[comp, 'PC'] * exp( @@ -1037,6 +1965,21 @@ def productside_vapor_composition(disj, comp): @disj.Constraint(m.comp, doc="Product section 3 liquid enthalpy") def productside_liquid_enthalpy(disj, comp): + """ + Liquid enthalpy for the product side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the product side section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid enthalpy for the product side section in the column. + """ return m.hl[3, n_tray, comp] == ( m.cpc[1] * ( (m.T[3, n_tray] - m.Tref) * \ @@ -1054,6 +1997,21 @@ def productside_liquid_enthalpy(disj, comp): @disj.Constraint(m.comp, doc="Product section 3 vapor enthalpy") def productside_vapor_enthalpy(disj, comp): + """ + Vapor enthalpy for the product side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the product side section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor enthalpy for the product side section in the column. + """ return m.hv[3, n_tray, comp] == ( m.cpc[2] * ( (m.T[3, n_tray] - m.Tref) * \ @@ -1072,6 +2030,22 @@ def productside_vapor_enthalpy(disj, comp): @disj.Constraint(m.comp, doc="Product section 3 liquid activity coefficient") def productside_activity_coefficient(disj, comp): + """ + Liquid activity coefficient for the product side section in the column equal to 1. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the product side section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid activity coefficient for the product side section for each component and tray in the column to be equal to 1. + This is an assumption for the product side section, since the product is assumed to be ideal. + """ return m.actv[3, n_tray, comp] == 1 @@ -1079,11 +2053,40 @@ def productside_activity_coefficient(disj, comp): def _build_top_equations(disj, n_tray): + """ + Build the equations for the top section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the top section in the column. + n_tray : int + The tray index. + + Returns + ------- + None + """ m = disj.model() @disj.Constraint(m.comp, doc="Top section 4 mass per component balances") def _top_mass_percomponent_balances(disj, comp): + """ + Mass per component balances for the top section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the top section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the mass balance per component in the top section of the column. + """ return ( (m.L[4, n_tray + 1, comp] if n_tray < m.con_tray else 0) @@ -1109,6 +2112,19 @@ def _top_mass_percomponent_balances(disj, comp): @disj.Constraint(doc="Top scetion 4 energy balances") def _top_energy_balances(disj): + """ + Energy balances for the top section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the top section in the column. + + Returns + ------- + Constraint + The constraint expression that enforces the energy balance for the top section in the column. + """ return ( sum( (m.L[4, n_tray + 1, comp] * m.hl[4, n_tray + 1, comp] @@ -1138,28 +2154,102 @@ def _top_energy_balances(disj): @disj.Constraint(m.comp, doc="Top section 4 liquid flowrate per component") def _top_liquid_percomponent(disj, comp): + """ + Liquid flowrate per component in the top section of the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the top section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid flowrate per component in the top section of the column is equal to the total liquid flowrate times the liquid composition. + """ return m.L[4, n_tray, comp] == m.Ltotal[4, n_tray] * m.x[4, n_tray, comp] @disj.Constraint(m.comp, doc="Top section 4 vapor flowrate per component") def _top_vapor_percomponent(disj, comp): + """ + Vapor flowrate per component in the top section of the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the top section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor flowrate per component in the top section of the column is equal to the total vapor flowrate times the vapor composition. + """ return m.V[4, n_tray, comp] == m.Vtotal[4, n_tray] * m.y[4, n_tray, comp] @disj.Constraint(doc="Top section 4 liquid composition equilibrium summation") def top_liquid_composition_summation(disj): + """ + Liquid composition equilibrium summation for the top section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the top section in the column. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid composition equilibrium summation for the top section in the column. + It ensures the sum of the liquid compositions is equal to 1 plus the error in the liquid composition. + """ return sum(m.x[4, n_tray, comp] for comp in m.comp) - 1 == m.errx[4, n_tray] @disj.Constraint(doc="Top section 4 vapor composition equilibrium summation") def top_vapor_composition_summation(disj): + """ + Vapor composition equilibrium summation for the top section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the top section in the column. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor composition equilibrium summation for the top section in the column. + It ensures the sum of the vapor compositions is equal to 1 plus the error in the vapor composition. + """ return sum(m.y[4, n_tray, comp] for comp in m.comp) - 1 == m.erry[4, n_tray] @disj.Constraint(m.comp, doc="Top scetion 4 vapor composition") def top_vapor_composition(disj, comp): + """ + Vapor composition for the top section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the top section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor composition for the top section in the column. + The equation is derived from the vapor-liquid equilibrium relationship. + """ return m.y[4, n_tray, comp] == m.x[4, n_tray, comp] * ( m.actv[4, n_tray, comp] * ( m.prop[comp, 'PC'] * exp( @@ -1182,6 +2272,21 @@ def top_vapor_composition(disj, comp): @disj.Constraint(m.comp, doc="Top section 4 liquid enthalpy") def top_liquid_enthalpy(disj, comp): + """ + Liquid enthalpy for the top section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the top section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid enthalpy for the top section in the column. + """ return m.hl[4, n_tray, comp] == ( m.cpc[1] * ( (m.T[4, n_tray] - m.Tref) * \ @@ -1199,6 +2304,21 @@ def top_liquid_enthalpy(disj, comp): @disj.Constraint(m.comp, doc="Top section 4 vapor enthalpy") def top_vapor_enthalpy(disj, comp): + """ + Vapor enthalpy for the top section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the top section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor enthalpy for the top section in the column. + """ return m.hv[4, n_tray, comp] == ( m.cpc[2] * ( (m.T[4, n_tray] - m.Tref) * \ @@ -1217,55 +2337,192 @@ def top_vapor_enthalpy(disj, comp): @disj.Constraint(m.comp, doc="Top section 4 liquid activity coefficient") def top_activity_coefficient(disj, comp): + """ + Liquid activity coefficient for the top section in the column equal to 1. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the top section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid activity coefficient for the top section for each component and tray in the column to be equal to 1. + This is an assumption for the top section, since the product is assumed to be ideal. + """ return m.actv[4, n_tray, comp] == 1 def _build_pass_through_eqns(disj, sec, n_tray): + """ + Build the equations for the pass through section in the column when a given tray in the disjunct is not active if it is the first or last tray. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the pass through section in the column. + sec : int + The section index. + n_tray : int + The tray index. + + Returns + ------- + None + """ m = disj.model() + # If the tray is the first or last tray, then the liquid and vapor flowrates, compositions, enthalpies, and temperature are passed through. if n_tray == 1 or n_tray == m.num_trays: return @disj.Constraint(m.comp, doc="Pass through liquid flowrate") def pass_through_liquid_flowrate(disj, comp): + """ + Pass through liquid flowrate for the given tray in the column. + The constraint enforces the liquid flowrate for the given tray is equal to the liquid flowrate for the tray above it. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the pass through when the tray is inactive. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid flowrate for the given tray is equal to the liquid flowrate for the tray above it. + """ return m.L[sec, n_tray, comp] == m.L[sec, n_tray + 1, comp] @disj.Constraint(m.comp, doc="Pass through vapor flowrate") def pass_through_vapor_flowrate(disj, comp): + """ + Pass through vapor flowrate for the given tray in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the pass through when the tray is inactive. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor flowrate for the given tray is equal to the vapor flowrate for the tray below it. + """ return m.V[sec, n_tray, comp] == m.V[sec, n_tray - 1, comp] @disj.Constraint(m.comp, doc="Pass through liquid composition") def pass_through_liquid_composition(disj, comp): + """ + Pass through liquid composition for the given tray in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the pass through when the tray is inactive. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid composition for the given tray is equal to the liquid composition for the tray above it. + """ return m.x[sec, n_tray, comp] == m.x[sec, n_tray + 1, comp] @disj.Constraint(m.comp, doc="Pass through vapor composition") def pass_through_vapor_composition(disj, comp): + """ + Pass through vapor composition for the given tray in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the pass through when the tray is inactive. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor composition for the given tray is equal to the vapor composition for the tray below it. + """ return m.y[sec, n_tray, comp] == m.y[sec, n_tray + 1, comp] @disj.Constraint(m.comp, doc="Pass through liquid enthalpy") def pass_through_liquid_enthalpy(disj, comp): + """ + Pass through liquid enthalpy for the given tray in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the pass through when the tray is inactive. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid enthalpy for the given tray is equal to the liquid enthalpy for the tray above it. + """ return m.hl[sec, n_tray, comp] == m.hl[sec, n_tray + 1, comp] @disj.Constraint(m.comp, doc="Pass through vapor enthalpy") def pass_through_vapor_enthalpy(disj, comp): + """ + Pass through vapor enthalpy for the given tray in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the pass through when the tray is inactive. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor enthalpy for the given tray is equal to the vapor enthalpy for the tray below it. + """ return m.hv[sec, n_tray, comp] == m.hv[sec, n_tray - 1, comp] @disj.Constraint(doc="Pass through temperature") def pass_through_temperature(disj): + """ + Pass through temperature for the given tray in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the pass through when the tray is inactive. + + Returns + ------- + Constraint + The constraint expression that enforces the temperature for the given tray is equal to the temperature for the tray below it. + """ return m.T[sec, n_tray] == m.T[sec, n_tray - 1] From ef1475f53463a2d3685581c31ccfb9309f3a9853 Mon Sep 17 00:00:00 2001 From: Carolina Tristan Date: Mon, 20 May 2024 07:38:08 -0400 Subject: [PATCH 15/79] Refactor main_gdp.py for clarity and documentation --- gdplib/kaibel/main_gdp.py | 52 ++++++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/gdplib/kaibel/main_gdp.py b/gdplib/kaibel/main_gdp.py index 8c8b32b..4c62e87 100644 --- a/gdplib/kaibel/main_gdp.py +++ b/gdplib/kaibel/main_gdp.py @@ -4,11 +4,12 @@ (written by E. Soraya Rawlings, esoraya@rwlngs.net, in collaboration with Qi Chen) - Case study: separation of a mixture of a quaternary mixture: +This is a dividing wall distillation column design problem to determine the optimal minimum number of trays and the optimal location of side streams for the separation of a quaternary mixture: 1 = methanol 2 = ethanol 3 = propanol 4 = butanol +while minimizing its capital and operating costs. The scheme of the Kaibel Column is shown in Figure 1: ____ @@ -32,9 +33,24 @@ ---|Reb |--- ---- Figure 1. Kaibel Column scheme -""" -from __future__ import division +Permanent trays: +- Reboiler and vapor distributor in the bottom section (sect 1) +- Liquid distributor and condenser in the top section (sect 4) +- Side feed tray for the feed side and dividing wall starting and and ening tray in the feed section (sect 2). +- Side product trays and dividing wall starting and ending tray in the product section (sect 3). + +The trays in each section are counted from bottom to top, being tray 1 the bottom tray in each section and tray np the top tray in each section, where np is a specified upper bound for the number of possible trays for each section. +Each section has the same number of possible trays. + +Six degrees of freedom: the reflux ratio, the product outlets (bottom, intermediate, and distillate product flowrates), and the liquid and vapor flowrates between the two sections of the dividing wall, controlled by a liquid and vapor distributor on the top and bottom section of the column, respectively. +including also the vapor and liquid flowrate and the energy consumption in the reboiler and condenser. +The vapor distributor is fixed and remain constant during the column operation. + +Source paper: +Rawlings, E. S., Chen, Q., Grossmann, I. E., & Caballero, J. A. (2019). Kaibel Column: Modeling, optimization, and conceptual design of multi-product dividing wall columns. *Computers and Chemical Engineering*, 125, 31–39. https://doi.org/10.1016/j.compchemeng.2019.03.006 + +""" from math import fabs @@ -45,14 +61,24 @@ def main(): + """ + This is the main function that executes the optimization process. + + It builds the model, fixes certain variables, sets initial values for tray existence or absence, + solves the model using the 'gdpopt' solver, and displays the results. + + Returns: + None + """ m = build_model() - m.F[1].fix(50) + # Fixing variables + m.F[1].fix(50) # feed flowrate in mol/s m.F[2].fix(50) m.F[3].fix(50) m.F[4].fix(50) - m.q.fix(m.q_init) - m.dv[2].fix(0.394299) + m.q.fix(m.q_init) # vapor fraction q_init from the feed set in the build_model function + m.dv[2].fix(0.394299) # vapor distributor in the feed section for sec in m.section: for n_tray in m.tray: @@ -116,6 +142,12 @@ def main(): def intro_message(m): + """ + Display the introduction message. + + + + """ print(""" If you use this model and/or initialization strategy, you may cite the following: @@ -129,6 +161,14 @@ def intro_message(m): def display_results(m): + """ + Display the results of the optimization process. + + Parameters + ---------- + m : Pyomo ConcreteModel + The Pyomo model object containing the results of the optimization process. + """ print('') print('Components:') print('1 methanol') From 0a4e827f6833003af0dd78a30537ef250260875a Mon Sep 17 00:00:00 2001 From: Carolina Tristan Date: Mon, 20 May 2024 07:41:02 -0400 Subject: [PATCH 16/79] Black formatted main_gdp.py --- gdplib/kaibel/main_gdp.py | 229 +++++++++++++++++++------------------- 1 file changed, 113 insertions(+), 116 deletions(-) diff --git a/gdplib/kaibel/main_gdp.py b/gdplib/kaibel/main_gdp.py index 4c62e87..45206f3 100644 --- a/gdplib/kaibel/main_gdp.py +++ b/gdplib/kaibel/main_gdp.py @@ -73,17 +73,19 @@ def main(): m = build_model() # Fixing variables - m.F[1].fix(50) # feed flowrate in mol/s + m.F[1].fix(50) # feed flowrate in mol/s m.F[2].fix(50) m.F[3].fix(50) m.F[4].fix(50) - m.q.fix(m.q_init) # vapor fraction q_init from the feed set in the build_model function - m.dv[2].fix(0.394299) # vapor distributor in the feed section + m.q.fix( + m.q_init + ) # vapor fraction q_init from the feed set in the build_model function + m.dv[2].fix(0.394299) # vapor distributor in the feed section for sec in m.section: for n_tray in m.tray: m.P[sec, n_tray].fix(m.Preb) - + ## Initial values for the tray existence or absence for n_tray in m.candidate_trays_main: for sec in m.section_main: @@ -96,59 +98,44 @@ def main(): m.tray_exists[3, n_tray].indicator_var.set_value(1) m.tray_absent[3, n_tray].indicator_var.set_value(0) - - intro_message(m) - - results = SolverFactory('gdpopt').solve(m, - strategy='LOA', - tee=True, - time_limit = 3600, - mip_solver='gams', - mip_solver_args=dict(solver='cplex') - ) - - m.calc_nt = ( - sum( - sum(m.tray_exists[sec, n_tray].indicator_var.value - for n_tray in m.tray) - for sec in m.section) - - sum(m.tray_exists[3, n_tray].indicator_var.value - for n_tray in m.tray) + results = SolverFactory('gdpopt').solve( + m, + strategy='LOA', + tee=True, + time_limit=3600, + mip_solver='gams', + mip_solver_args=dict(solver='cplex'), ) + + m.calc_nt = sum( + sum(m.tray_exists[sec, n_tray].indicator_var.value for n_tray in m.tray) + for sec in m.section + ) - sum(m.tray_exists[3, n_tray].indicator_var.value for n_tray in m.tray) m.dw_start = ( - sum(m.tray_exists[1, n_tray].indicator_var.value - for n_tray in m.tray) - + 1 - ) - m.dw_end = ( - sum(m.tray_exists[1, n_tray].indicator_var.value - for n_tray in m.tray) - + sum(m.tray_exists[2, n_tray].indicator_var.value - for n_tray in m.tray) + sum(m.tray_exists[1, n_tray].indicator_var.value for n_tray in m.tray) + 1 ) - + m.dw_end = sum( + m.tray_exists[1, n_tray].indicator_var.value for n_tray in m.tray + ) + sum(m.tray_exists[2, n_tray].indicator_var.value for n_tray in m.tray) display_results(m) - - print(' ', results) + print(' ', results) print(' Solver Status: ', results.solver.status) print(' Termination condition: ', results.solver.termination_condition) - - - def intro_message(m): """ Display the introduction message. - + """ - print(""" + print( + """ If you use this model and/or initialization strategy, you may cite the following: Rawlings, ES; Chen, Q; Grossmann, IE; Caballero, JA. Kaibel Column: Modeling, @@ -157,7 +144,8 @@ def intro_message(m): DOI: https://doi.org/10.1016/j.compchemeng.2019.03.006 - """) + """ + ) def display_results(m): @@ -181,71 +169,62 @@ def display_results(m): print('Dividing_wall_start: %s' % value(m.dw_start)) print('Dividing_wall_end: %s' % value(m.dw_end)) print(' ') - print('Qreb: {: >3.0f}kW B_1: {: > 2.0f} B_2: {: >2.0f} B_3: {: >2.0f} B_4: {: >2.0f} Btotal: {: >2.0f}' - .format(value(m.Qreb / m.Qscale ), - value(m.B[1]), - value(m.B[2]), - value(m.B[3]), - value(m.B[4]), - value(m.Btotal) - ) + print( + 'Qreb: {: >3.0f}kW B_1: {: > 2.0f} B_2: {: >2.0f} B_3: {: >2.0f} B_4: {: >2.0f} Btotal: {: >2.0f}'.format( + value(m.Qreb / m.Qscale), + value(m.B[1]), + value(m.B[2]), + value(m.B[3]), + value(m.B[4]), + value(m.Btotal), + ) ) - print('Qcon: {: >2.0f}kW D_1: {: >2.0f} D_2: {: >2.0f} D_3: {: >2.0f} D_4: {: >2.0f} Dtotal: {: >2.0f}' - .format(value(m.Qcon / m.Qscale), - value(m.D[1]), - value(m.D[2]), - value(m.D[3]), - value(m.D[4]), - value(m.Dtotal) - ) + print( + 'Qcon: {: >2.0f}kW D_1: {: >2.0f} D_2: {: >2.0f} D_3: {: >2.0f} D_4: {: >2.0f} Dtotal: {: >2.0f}'.format( + value(m.Qcon / m.Qscale), + value(m.D[1]), + value(m.D[2]), + value(m.D[3]), + value(m.D[4]), + value(m.Dtotal), + ) ) print(' ') - print('Reflux: {: >3.4f}' - .format(value(m.rr) - ) - ) - print('Reboil: {: >3.4f} ' - .format(value(m.bu) - ) - ) + print('Reflux: {: >3.4f}'.format(value(m.rr))) + print('Reboil: {: >3.4f} '.format(value(m.bu))) print(' ') print('Flowrates[mol/s]') - print('F_1: {: > 3.0f} F_2: {: >2.0f} F_3: {: >2.0f} F_4: {: >2.0f} Ftotal: {: >2.0f}' - .format(value(m.F[1]), - value(m.F[2]), - value(m.F[3]), - value(m.F[4]), - sum(value(m.F[comp]) for comp in m.comp) - ) + print( + 'F_1: {: > 3.0f} F_2: {: >2.0f} F_3: {: >2.0f} F_4: {: >2.0f} Ftotal: {: >2.0f}'.format( + value(m.F[1]), + value(m.F[2]), + value(m.F[3]), + value(m.F[4]), + sum(value(m.F[comp]) for comp in m.comp), + ) ) - print('S1_1: {: > 1.0f} S1_2: {: >2.0f} S1_3: {: >2.0f} S1_4: {: >2.0f} S1total: {: >2.0f}' - .format(value(m.S[1, 1]), - value(m.S[1, 2]), - value(m.S[1, 3]), - value(m.S[1, 4]), - sum(value(m.S[1, comp]) for comp in m.comp) - ) + print( + 'S1_1: {: > 1.0f} S1_2: {: >2.0f} S1_3: {: >2.0f} S1_4: {: >2.0f} S1total: {: >2.0f}'.format( + value(m.S[1, 1]), + value(m.S[1, 2]), + value(m.S[1, 3]), + value(m.S[1, 4]), + sum(value(m.S[1, comp]) for comp in m.comp), + ) ) - print('S2_1: {: > 1.0f} S2_2: {: >2.0f} S2_3: {: >2.0f} S2_4: {: >2.0f} S2total: {: >2.0f}' - .format(value(m.S[2, 1]), - value(m.S[2, 2]), - value(m.S[2, 3]), - value(m.S[2, 4]), - sum(value(m.S[2, comp]) for comp in m.comp) - ) + print( + 'S2_1: {: > 1.0f} S2_2: {: >2.0f} S2_3: {: >2.0f} S2_4: {: >2.0f} S2total: {: >2.0f}'.format( + value(m.S[2, 1]), + value(m.S[2, 2]), + value(m.S[2, 3]), + value(m.S[2, 4]), + sum(value(m.S[2, comp]) for comp in m.comp), + ) ) print(' ') print('Distributors:') - print('dl[2]: {: >3.4f} dl[3]: {: >3.4f}' - .format(value(m.dl[2]), - value(m.dl[3]) - ) - ) - print('dv[2]: {: >3.4f} dv[3]: {: >3.4f}' - .format(value(m.dv[2]), - value(m.dv[3]) - ) - ) + print('dl[2]: {: >3.4f} dl[3]: {: >3.4f}'.format(value(m.dl[2]), value(m.dl[3]))) + print('dv[2]: {: >3.4f} dv[3]: {: >3.4f}'.format(value(m.dv[2]), value(m.dv[3]))) print(' ') print(' ') print(' ') @@ -255,15 +234,21 @@ def display_results(m): print(' Tray Bottom Feed ') print('__________________________________________') for t in reversed(list(m.tray)): - print('[{: >2.0f}] {: >9.0g} {: >18.0g} F:{: >3.0f} ' - .format(t, - fabs(value(m.tray_exists[1, t].indicator_var)) - if t in m.candidate_trays_main else 1, - fabs(value(m.tray_exists[2, t].indicator_var)) - if t in m.candidate_trays_feed else 1, - sum(value(m.F[comp]) for comp in m.comp) - if t == m.feed_tray else 0, - ) + print( + '[{: >2.0f}] {: >9.0g} {: >18.0g} F:{: >3.0f} '.format( + t, + ( + fabs(value(m.tray_exists[1, t].indicator_var)) + if t in m.candidate_trays_main + else 1 + ), + ( + fabs(value(m.tray_exists[2, t].indicator_var)) + if t in m.candidate_trays_feed + else 1 + ), + sum(value(m.F[comp]) for comp in m.comp) if t == m.feed_tray else 0, + ) ) print(' ') print('__________________________________________') @@ -271,21 +256,33 @@ def display_results(m): print(' Product Top ') print('__________________________________________') for t in reversed(list(m.tray)): - print('[{: >2.0f}] {: >9.0g} S1:{: >2.0f} S2:{: >2.0f} {: >8.0g}' - .format(t, - fabs(value(m.tray_exists[3, t].indicator_var)) - if t in m.candidate_trays_product else 1, - sum(value(m.S[1, comp]) for comp in m.comp) - if t == m.sideout1_tray else 0, - sum(value(m.S[2, comp]) for comp in m.comp) - if t == m.sideout2_tray else 0, - fabs(value(m.tray_exists[4, t].indicator_var)) - if t in m.candidate_trays_main else 1 - ) + print( + '[{: >2.0f}] {: >9.0g} S1:{: >2.0f} S2:{: >2.0f} {: >8.0g}'.format( + t, + ( + fabs(value(m.tray_exists[3, t].indicator_var)) + if t in m.candidate_trays_product + else 1 + ), + ( + sum(value(m.S[1, comp]) for comp in m.comp) + if t == m.sideout1_tray + else 0 + ), + ( + sum(value(m.S[2, comp]) for comp in m.comp) + if t == m.sideout2_tray + else 0 + ), + ( + fabs(value(m.tray_exists[4, t].indicator_var)) + if t in m.candidate_trays_main + else 1 + ), + ) ) print(' 1 = trays exists, 0 = absent tray') - if __name__ == "__main__": main() From e8ad31dfdaf87a1fddf0e91b154c4042d317c5a9 Mon Sep 17 00:00:00 2001 From: Carolina Tristan Date: Mon, 20 May 2024 07:44:22 -0400 Subject: [PATCH 17/79] Black formatted kaibel_side_flash.py --- gdplib/kaibel/kaibel_side_flash.py | 152 +++++++++++++++-------------- 1 file changed, 81 insertions(+), 71 deletions(-) diff --git a/gdplib/kaibel/kaibel_side_flash.py b/gdplib/kaibel/kaibel_side_flash.py index b2bdee9..dfe38f4 100644 --- a/gdplib/kaibel/kaibel_side_flash.py +++ b/gdplib/kaibel/kaibel_side_flash.py @@ -1,7 +1,18 @@ """ Side feed flash """ from pyomo.environ import ( - ConcreteModel, Constraint, exp, minimize, NonNegativeReals, Objective, Param, RangeSet, SolverFactory, value, Var, ) + ConcreteModel, + Constraint, + exp, + minimize, + NonNegativeReals, + Objective, + Param, + RangeSet, + SolverFactory, + value, + Var, +) def calc_side_feed_flash(m): @@ -21,44 +32,50 @@ def calc_side_feed_flash(m): Pyomo ConcreteModel The updated Pyomo model with the calculated values, which include the vapor-liquid equilibrium, vapor pressure, and liquid and vapor compositions for the side feed. - """ - msf = ConcreteModel('SIDE FEED FLASH') # Main side feed flash model + """ + msf = ConcreteModel('SIDE FEED FLASH') # Main side feed flash model - msf.nc = RangeSet(1, m.c, doc='Number of components') - m.xfi = {} # Liquid composition in the side feed of the main model for each component. + m.xfi = ( + {} + ) # Liquid composition in the side feed of the main model for each component. for nc in msf.nc: m.xfi[nc] = 1 / m.c - msf.Tf = Param(doc='Side feed temperature in K', - initialize=m.Tf0) - msf.xf = Var(msf.nc, - doc='Side feed liquid composition', - domain=NonNegativeReals, - bounds=(0, 1), - initialize=m.xfi) - msf.yf = Var(msf.nc, - doc='Side feed vapor composition', - domain=NonNegativeReals, - bounds=(0, 1), - initialize=m.xfi) - msf.Keqf = Var(msf.nc, - doc='Vapor-liquid equilibrium constant', - domain=NonNegativeReals, - bounds=(0, 10), - initialize=0) - msf.Pvf = Var(msf.nc, - doc='Side feed vapor pressure in bar', - domain=NonNegativeReals, - bounds=(0, 10), - initialize=0) - msf.q = Var(doc='Vapor fraction', - bounds=(0, 1), - domain=NonNegativeReals, - initialize=0) - - + msf.Tf = Param(doc='Side feed temperature in K', initialize=m.Tf0) + msf.xf = Var( + msf.nc, + doc='Side feed liquid composition', + domain=NonNegativeReals, + bounds=(0, 1), + initialize=m.xfi, + ) + msf.yf = Var( + msf.nc, + doc='Side feed vapor composition', + domain=NonNegativeReals, + bounds=(0, 1), + initialize=m.xfi, + ) + msf.Keqf = Var( + msf.nc, + doc='Vapor-liquid equilibrium constant', + domain=NonNegativeReals, + bounds=(0, 10), + initialize=0, + ) + msf.Pvf = Var( + msf.nc, + doc='Side feed vapor pressure in bar', + domain=NonNegativeReals, + bounds=(0, 10), + initialize=0, + ) + msf.q = Var( + doc='Vapor fraction', bounds=(0, 1), domain=NonNegativeReals, initialize=0 + ) + @msf.Constraint(doc="Vapor fraction") def _algq(msf): """This function calculates the vapor fraction (q) in the side feed using the Peng-Robinson equation of state. @@ -72,14 +89,16 @@ def _algq(msf): ------- q : float The vapor fraction in the side feed. - """ - return sum(m.xfi[nc] * (1 - msf.Keqf[nc]) / \ - (1 + msf.q * (msf.Keqf[nc] - 1)) - for nc in msf.nc) == 0 - - - @msf.Constraint(msf.nc, - doc="Side feed liquid composition") + """ + return ( + sum( + m.xfi[nc] * (1 - msf.Keqf[nc]) / (1 + msf.q * (msf.Keqf[nc] - 1)) + for nc in msf.nc + ) + == 0 + ) + + @msf.Constraint(msf.nc, doc="Side feed liquid composition") def _algx(msf, nc): """Side feed liquid composition @@ -96,12 +115,10 @@ def _algx(msf, nc): ------- xf : float The liquid composition for the given component with Keqf, q, and xfi, which are the equilibrium constant, vapor fraction, and liquid composition in the side feed, respectively. - """ + """ return msf.xf[nc] * (1 + msf.q * (msf.Keqf[nc] - 1)) == m.xfi[nc] - - @msf.Constraint(msf.nc, - doc="Side feed vapor composition") + @msf.Constraint(msf.nc, doc="Side feed vapor composition") def _algy(msf, nc): """Side feed vapor composition @@ -118,13 +135,12 @@ def _algy(msf, nc): ------- yf : float The vapor composition for the given component given the liquid composition (xf) and the equilibrium constant (Keqf). - """ + """ return msf.yf[nc] == msf.xf[nc] * msf.Keqf[nc] + # TODO: Is it computed using the Peng-Robinson equation of state? - - @msf.Constraint(msf.nc, - doc="Vapor-liquid equilibrium constant") + @msf.Constraint(msf.nc, doc="Vapor-liquid equilibrium constant") def _algKeq(msf, nc): """Calculate the vapor-liquid equilibrium constant for a given component using the Peng-Robinson equation of state. @@ -139,12 +155,10 @@ def _algKeq(msf, nc): ------- Keqf : float The equilibrium constant for the component taking into account the vapor pressure (Pvf) and the liquid pressure (Pf). - """ + """ return msf.Keqf[nc] * m.Pf == msf.Pvf[nc] - - @msf.Constraint(msf.nc, - doc="Side feed vapor pressure") + @msf.Constraint(msf.nc, doc="Side feed vapor pressure") def _algPvf(msf, nc): """Calculate the vapor fraction for a given component. @@ -161,34 +175,30 @@ def _algPvf(msf, nc): ------- Pvf : float The vapor fraction for the given component considering the temperature (Tf) and the properties of the component set in the main model. - """ + """ return msf.Pvf[nc] == m.prop[nc, 'PC'] * exp( - m.prop[nc, 'TC'] / msf.Tf * ( - m.prop[nc, 'vpA'] * \ - (1 - msf.Tf / m.prop[nc, 'TC']) - + m.prop[nc, 'vpB'] * \ - (1 - msf.Tf / m.prop[nc, 'TC'])**1.5 - + m.prop[nc, 'vpC'] * \ - (1 - msf.Tf / m.prop[nc, 'TC'])**3 - + m.prop[nc, 'vpD'] * \ - (1 - msf.Tf / m.prop[nc, 'TC'])**6 + m.prop[nc, 'TC'] + / msf.Tf + * ( + m.prop[nc, 'vpA'] * (1 - msf.Tf / m.prop[nc, 'TC']) + + m.prop[nc, 'vpB'] * (1 - msf.Tf / m.prop[nc, 'TC']) ** 1.5 + + m.prop[nc, 'vpC'] * (1 - msf.Tf / m.prop[nc, 'TC']) ** 3 + + m.prop[nc, 'vpD'] * (1 - msf.Tf / m.prop[nc, 'TC']) ** 6 ) ) + # TODO: Is it computed using the Peng-Robinson equation of state? - msf.OBJ = Objective( - expr=1, - sense=minimize) + msf.OBJ = Objective(expr=1, sense=minimize) #### - SolverFactory('ipopt').solve(msf, - tee=False) + SolverFactory('ipopt').solve(msf, tee=False) # Update the main model with the calculated values - m.yfi = {} # Vapor composition + m.yfi = {} # Vapor composition for nc in msf.nc: m.yfi[nc] = value(msf.yf[nc]) - - m.q_init = value(msf.q) # Vapor fraction + + m.q_init = value(msf.q) # Vapor fraction return m From 093522eb0bd05bb7593a8ed2842556aeda2b4717 Mon Sep 17 00:00:00 2001 From: Carolina Tristan Date: Mon, 20 May 2024 07:46:13 -0400 Subject: [PATCH 18/79] Black formatted kaibel_solve_gdp.py --- gdplib/kaibel/kaibel_solve_gdp.py | 1919 +++++++++++++++-------------- 1 file changed, 1023 insertions(+), 896 deletions(-) diff --git a/gdplib/kaibel/kaibel_solve_gdp.py b/gdplib/kaibel/kaibel_solve_gdp.py index f324375..4125113 100644 --- a/gdplib/kaibel/kaibel_solve_gdp.py +++ b/gdplib/kaibel/kaibel_solve_gdp.py @@ -10,7 +10,16 @@ from math import copysign -from pyomo.environ import (Constraint, exp, minimize, NonNegativeReals, Objective, RangeSet, Set, Var) +from pyomo.environ import ( + Constraint, + exp, + minimize, + NonNegativeReals, + Objective, + RangeSet, + Set, + Var, +) from pyomo.gdp import Disjunct from gdplib.kaibel.kaibel_init import initialize_kaibel @@ -27,126 +36,108 @@ def build_model(): ------- ConcreteModel The constructed GDP Kaibel Column model. - """ + """ # Calculation of the theoretical minimum number of trays (Knmin) and initial temperature values (TB0, Tf0, TD0). m = initialize_kaibel() - # Side feed init. Returns side feed vapor composition yfi and vapor fraction q_init m = calc_side_feed_flash(m) - m.name = "GDP Kaibel Column" #### Calculated initial values - m.Treb = m.TB0 + 5 # Reboiler temperature in K - m.Tbot = m.TB0 # Bottom-most tray temperature in K - m.Ttop = m.TD0 # Top-most tray temperature in K - m.Tcon = m.TD0 - 5 # Condenser temperature in K + m.Treb = m.TB0 + 5 # Reboiler temperature in K + m.Tbot = m.TB0 # Bottom-most tray temperature in K + m.Ttop = m.TD0 # Top-most tray temperature in K + m.Tcon = m.TD0 - 5 # Condenser temperature in K - m.dv0 = {} # Initial vapor distributor value - m.dl0 = {} # Initial liquid distributor value + m.dv0 = {} # Initial vapor distributor value + m.dl0 = {} # Initial liquid distributor value m.dv0[2] = 0.516 m.dv0[3] = 1 - m.dv0[2] m.dl0[2] = 0.36 m.dl0[3] = 1 - m.dl0[2] - #### Calculated upper and lower bounds m.min_tray = m.Knmin * 0.8 # Lower bound on number of trays - m.Tlo = m.Tcon - 20 # Temperature lower bound - m.Tup = m.Treb + 20 # Temperature upper bound - - m.flow_max = 1e3 # Flowrates upper bound in mol/s - m.Qmax = 60 # Heat loads upper bound in J/s + m.Tlo = m.Tcon - 20 # Temperature lower bound + m.Tup = m.Treb + 20 # Temperature upper bound + m.flow_max = 1e3 # Flowrates upper bound in mol/s + m.Qmax = 60 # Heat loads upper bound in J/s #### Column tray details - m.num_trays = m.np # Trays per section. np = 25 - m.min_num_trays = 10 # Minimum number of trays per section - m.num_total = m.np * 3 # Total number of trays - m.feed_tray = 12 # Side feed tray - m.sideout1_tray = 8 # Side outlet 1 tray - m.sideout2_tray = 17 # Side outlet 2 tray - m.reb_tray = 1 # Reboiler tray. Dividing wall starting tray - m.con_tray = m.num_trays # Condenser tray. Dividing wall ending tray - - + m.num_trays = m.np # Trays per section. np = 25 + m.min_num_trays = 10 # Minimum number of trays per section + m.num_total = m.np * 3 # Total number of trays + m.feed_tray = 12 # Side feed tray + m.sideout1_tray = 8 # Side outlet 1 tray + m.sideout2_tray = 17 # Side outlet 2 tray + m.reb_tray = 1 # Reboiler tray. Dividing wall starting tray + m.con_tray = m.num_trays # Condenser tray. Dividing wall ending tray # ------------------------------------------------------------------ - + # Beginning of model - - # ------------------------------------------------------------------ + # ------------------------------------------------------------------ ## Sets - m.section = RangeSet(4, - doc="Column sections:1=top, 2=feed side, 3=prod side, 4=bot") - m.section_main = Set(initialize=[1, 4], - doc="Main sections of the column") - - m.tray = RangeSet(m.np, - doc="Potential trays in each section") - m.tray_total = RangeSet(m.num_total, - doc="Total trays in the column") - m.tray_below_feed = RangeSet(m.feed_tray, - doc="Trays below feed") - m.tray_below_so1 = RangeSet(m.sideout1_tray, - doc="Trays below side outlet 1") - m.tray_below_so2 = RangeSet(m.sideout2_tray, - doc="Trays below side outlet 2") - - m.comp = RangeSet(4, - doc="Components") - m.dw = RangeSet(2, 3, - doc="Dividing wall sections") - m.cplv = RangeSet(2, - doc="Heat capacity: 1=liquid, 2=vapor") - m.so = RangeSet(2, - doc="Side product outlets") - m.bounds = RangeSet(2, - doc="Number of boundary condition values") - - m.candidate_trays_main = Set(initialize=m.tray - - [m.con_tray, m.reb_tray], - doc="Candidate trays for top and \ - bottom sections 1 and 4") - m.candidate_trays_feed = Set(initialize=m.tray - - [m.con_tray, m.feed_tray, m.reb_tray], - doc="Candidate trays for feed section 2") - m.candidate_trays_product = Set(initialize=m.tray - - [m.con_tray, m.sideout1_tray, - m.sideout2_tray, m.reb_tray], - doc="Candidate trays for product section 3") - - + m.section = RangeSet( + 4, doc="Column sections:1=top, 2=feed side, 3=prod side, 4=bot" + ) + m.section_main = Set(initialize=[1, 4], doc="Main sections of the column") + + m.tray = RangeSet(m.np, doc="Potential trays in each section") + m.tray_total = RangeSet(m.num_total, doc="Total trays in the column") + m.tray_below_feed = RangeSet(m.feed_tray, doc="Trays below feed") + m.tray_below_so1 = RangeSet(m.sideout1_tray, doc="Trays below side outlet 1") + m.tray_below_so2 = RangeSet(m.sideout2_tray, doc="Trays below side outlet 2") + + m.comp = RangeSet(4, doc="Components") + m.dw = RangeSet(2, 3, doc="Dividing wall sections") + m.cplv = RangeSet(2, doc="Heat capacity: 1=liquid, 2=vapor") + m.so = RangeSet(2, doc="Side product outlets") + m.bounds = RangeSet(2, doc="Number of boundary condition values") + + m.candidate_trays_main = Set( + initialize=m.tray - [m.con_tray, m.reb_tray], + doc="Candidate trays for top and \ + bottom sections 1 and 4", + ) + m.candidate_trays_feed = Set( + initialize=m.tray - [m.con_tray, m.feed_tray, m.reb_tray], + doc="Candidate trays for feed section 2", + ) + m.candidate_trays_product = Set( + initialize=m.tray - [m.con_tray, m.sideout1_tray, m.sideout2_tray, m.reb_tray], + doc="Candidate trays for product section 3", + ) + ## Calculation of initial values - m.dHvap = {} # Heat of vaporization [J/mol] - - m.P0 = {} # Initial pressure [bar] - m.T0 = {} # Initial temperature [K] - m.L0 = {} # Initial individual liquid flowrate in mol/s - m.V0 = {} # Initial individual vapor flowrate in mol/s - m.Vtotal0 = {} # Initial total vapor flowrate in mol/s - m.Ltotal0 = {} # Initial liquid flowrate in mol/s - m.x0 = {} # Initial liquid composition - m.y0 = {} # Initial vapor composition - m.actv0 = {} # Initial activity coefficients - m.cpdT0 = {} # Initial heat capacity for liquid and vapor phases in J/mol K - m.hl0 = {} # Initial liquid enthalpy in J/mol - m.hv0 = {} # Initial vapor enthalpy in J/mol - m.Pi = m.Preb # Initial given pressure value in bar - m.Ti = {} # Initial known temperature values in K - - + m.dHvap = {} # Heat of vaporization [J/mol] + + m.P0 = {} # Initial pressure [bar] + m.T0 = {} # Initial temperature [K] + m.L0 = {} # Initial individual liquid flowrate in mol/s + m.V0 = {} # Initial individual vapor flowrate in mol/s + m.Vtotal0 = {} # Initial total vapor flowrate in mol/s + m.Ltotal0 = {} # Initial liquid flowrate in mol/s + m.x0 = {} # Initial liquid composition + m.y0 = {} # Initial vapor composition + m.actv0 = {} # Initial activity coefficients + m.cpdT0 = {} # Initial heat capacity for liquid and vapor phases in J/mol K + m.hl0 = {} # Initial liquid enthalpy in J/mol + m.hv0 = {} # Initial vapor enthalpy in J/mol + m.Pi = m.Preb # Initial given pressure value in bar + m.Ti = {} # Initial known temperature values in K + ## Initial values for pressure, temperature, flowrates, composition, and enthalpy for sec in m.section: for n_tray in m.tray: m.P0[sec, n_tray] = m.Pi - for sec in m.section: for n_tray in m.tray: for comp in m.comp: @@ -155,25 +146,17 @@ def build_model(): for sec in m.section: for n_tray in m.tray: - m.Ltotal0[sec, n_tray] = sum( - m.L0[sec, n_tray, comp] for comp in m.comp) - m.Vtotal0[sec, n_tray] = sum( - m.V0[sec, n_tray, comp] for comp in m.comp) + m.Ltotal0[sec, n_tray] = sum(m.L0[sec, n_tray, comp] for comp in m.comp) + m.Vtotal0[sec, n_tray] = sum(m.V0[sec, n_tray, comp] for comp in m.comp) - for n_tray in m.tray_total: if n_tray == m.reb_tray: m.Ti[n_tray] = m.Treb elif n_tray == m.num_total: m.Ti[n_tray] = m.Tcon else: - m.Ti[n_tray] = ( - m.Tbot - + (m.Ttop - m.Tbot) * \ - (n_tray - 2) / (m.num_total - 3) - ) + m.Ti[n_tray] = m.Tbot + (m.Ttop - m.Tbot) * (n_tray - 2) / (m.num_total - 3) - for n_tray in m.tray_total: if n_tray <= m.num_trays: m.T0[1, n_tray] = m.Ti[n_tray] @@ -181,7 +164,7 @@ def build_model(): m.T0[2, n_tray - m.num_trays] = m.Ti[n_tray] m.T0[3, n_tray - m.num_trays] = m.Ti[n_tray] elif n_tray >= m.num_trays * 2: - m.T0[4, n_tray - m.num_trays*2] = m.Ti[n_tray] + m.T0[4, n_tray - m.num_trays * 2] = m.Ti[n_tray] ## Initial vapor and liquid composition of the feed and activity coefficients for sec in m.section: @@ -190,40 +173,37 @@ def build_model(): m.x0[sec, n_tray, comp] = m.xfi[comp] m.actv0[sec, n_tray, comp] = 1 m.y0[sec, n_tray, comp] = m.xfi[comp] - - + ## Assigns the enthalpy boundary values, heat capacity, heat of vaporization calculation, temperature bounds, and light and heavy key components. - hlb = {} # Liquid enthalpy in J/mol - hvb = {} # Vapor enthalpy in J/mol - cpb = {} # Heact capacity in J/mol K - dHvapb = {} # Heat of vaporization in J/mol - Tbounds = {} # Temperature bounds in K - kc = {} # Light and heavy key components - Tbounds[1] = m.Tcon # Condenser temperature in K - Tbounds[2] = m.Treb # Reboiler temperature in K + hlb = {} # Liquid enthalpy in J/mol + hvb = {} # Vapor enthalpy in J/mol + cpb = {} # Heact capacity in J/mol K + dHvapb = {} # Heat of vaporization in J/mol + Tbounds = {} # Temperature bounds in K + kc = {} # Light and heavy key components + Tbounds[1] = m.Tcon # Condenser temperature in K + Tbounds[2] = m.Treb # Reboiler temperature in K kc[1] = m.lc kc[2] = m.hc ## Heat of vaporization calculation for each component in the feed. for comp in m.comp: dHvapb[comp] = -( - m.Rgas * m.prop[comp, 'TC'] * ( - m.prop[comp, 'vpA'] * \ - (1 - m.Tref / m.prop[comp, 'TC']) - + m.prop[comp, 'vpB'] * \ - (1 - m.Tref / m.prop[comp, 'TC'])**1.5 - + m.prop[comp, 'vpC'] * \ - (1 - m.Tref / m.prop[comp, 'TC'])**3 - + m.prop[comp, 'vpD'] * \ - (1 - m.Tref / m.prop[comp, 'TC'])**6) - + m.Rgas * m.Tref * ( + m.Rgas + * m.prop[comp, 'TC'] + * ( + m.prop[comp, 'vpA'] * (1 - m.Tref / m.prop[comp, 'TC']) + + m.prop[comp, 'vpB'] * (1 - m.Tref / m.prop[comp, 'TC']) ** 1.5 + + m.prop[comp, 'vpC'] * (1 - m.Tref / m.prop[comp, 'TC']) ** 3 + + m.prop[comp, 'vpD'] * (1 - m.Tref / m.prop[comp, 'TC']) ** 6 + ) + + m.Rgas + * m.Tref + * ( m.prop[comp, 'vpA'] - + 1.5 * m.prop[comp, 'vpB'] * \ - (1 - m.Tref / m.prop[comp, 'TC'])**0.5 - + 3 * m.prop[comp, 'vpC'] * \ - (1 - m.Tref / m.prop[comp, 'TC'])**2 - + 6 * m.prop[comp, 'vpD'] * \ - (1 - m.Tref / m.prop[comp, 'TC'])**5 + + 1.5 * m.prop[comp, 'vpB'] * (1 - m.Tref / m.prop[comp, 'TC']) ** 0.5 + + 3 * m.prop[comp, 'vpC'] * (1 - m.Tref / m.prop[comp, 'TC']) ** 2 + + 6 * m.prop[comp, 'vpD'] * (1 - m.Tref / m.prop[comp, 'TC']) ** 5 ) ) @@ -231,31 +211,34 @@ def build_model(): for b in m.bounds: for cp in m.cplv: cpb[b, cp] = m.cpc[cp] * ( - (Tbounds[b] - m.Tref) * \ - m.prop[kc[b], 'cpA', cp] - + (Tbounds[b]**2 - m.Tref**2) * \ - m.prop[kc[b], 'cpB', cp] * \ - m.cpc2['A', cp] / 2 - + (Tbounds[b]**3 - m.Tref**3) * \ - m.prop[kc[b], 'cpC', cp] * \ - m.cpc2['B', cp] / 3 - + (Tbounds[b]**4 - m.Tref**4) * \ - m.prop[kc[b], 'cpD', cp] / 4 + (Tbounds[b] - m.Tref) * m.prop[kc[b], 'cpA', cp] + + (Tbounds[b] ** 2 - m.Tref**2) + * m.prop[kc[b], 'cpB', cp] + * m.cpc2['A', cp] + / 2 + + (Tbounds[b] ** 3 - m.Tref**3) + * m.prop[kc[b], 'cpC', cp] + * m.cpc2['B', cp] + / 3 + + (Tbounds[b] ** 4 - m.Tref**4) * m.prop[kc[b], 'cpD', cp] / 4 ) - hlb[b] = ( - cpb[b, 1] - ) - hvb[b] = ( - cpb[b, 2] - + dHvapb[b] - ) - - m.hllo = (1 - copysign(0.2, hlb[1])) * hlb[1] / m.Hscale # Liquid enthalpy lower bound - m.hlup = (1 + copysign(0.2, hlb[2])) * hlb[2] / m.Hscale # Liquid enthalpy upper bound - m.hvlo = (1 - copysign(0.2, hvb[1])) * hvb[1] / m.Hscale # Vapor enthalpy lower bound - m.hvup = (1 + copysign(0.2, hvb[2])) * hvb[2] / m.Hscale # Vapor enthalpy upper bound + hlb[b] = cpb[b, 1] + hvb[b] = cpb[b, 2] + dHvapb[b] + + m.hllo = ( + (1 - copysign(0.2, hlb[1])) * hlb[1] / m.Hscale + ) # Liquid enthalpy lower bound + m.hlup = ( + (1 + copysign(0.2, hlb[2])) * hlb[2] / m.Hscale + ) # Liquid enthalpy upper bound + m.hvlo = ( + (1 - copysign(0.2, hvb[1])) * hvb[1] / m.Hscale + ) # Vapor enthalpy lower bound + m.hvup = ( + (1 + copysign(0.2, hvb[2])) * hvb[2] / m.Hscale + ) # Vapor enthalpy upper bound # copysign is a function that returns the first argument with the sign of the second argument - + ## Heat of vaporization for each component in the feed scaled by Hscale for comp in m.comp: m.dHvap[comp] = dHvapb[comp] / m.Hscale @@ -266,213 +249,301 @@ def build_model(): for comp in m.comp: for cp in m.cplv: m.cpdT0[sec, n_tray, comp, cp] = ( - m.cpc[cp] * ( - (m.T0[sec, n_tray] - m.Tref) * \ - m.prop[comp, 'cpA', cp] - + (m.T0[sec, n_tray]**2 - m.Tref**2) * \ - m.prop[comp, 'cpB', cp] * \ - m.cpc2['A', cp] / 2 - + (m.T0[sec, n_tray]**3 - m.Tref**3) * \ - m.prop[comp, 'cpC', cp] * \ - m.cpc2['B', cp] / 3 - + (m.T0[sec, n_tray]**4 - m.Tref**4) * \ - m.prop[comp, 'cpD', cp] / 4 - ) / m.Hscale + m.cpc[cp] + * ( + (m.T0[sec, n_tray] - m.Tref) * m.prop[comp, 'cpA', cp] + + (m.T0[sec, n_tray] ** 2 - m.Tref**2) + * m.prop[comp, 'cpB', cp] + * m.cpc2['A', cp] + / 2 + + (m.T0[sec, n_tray] ** 3 - m.Tref**3) + * m.prop[comp, 'cpC', cp] + * m.cpc2['B', cp] + / 3 + + (m.T0[sec, n_tray] ** 4 - m.Tref**4) + * m.prop[comp, 'cpD', cp] + / 4 + ) + / m.Hscale ) ## Liquid and vapor enthalpy calculation using Ruczika-D method for each component in the feed, section, and tray for sec in m.section: for n_tray in m.tray: for comp in m.comp: - m.hl0[sec, n_tray, comp] = ( - m.cpdT0[sec, n_tray, comp, 1] - ) - m.hv0[sec, n_tray, comp] = ( - m.cpdT0[sec, n_tray, comp, 2] - + m.dHvap[comp] - ) + m.hl0[sec, n_tray, comp] = m.cpdT0[sec, n_tray, comp, 1] + m.hv0[sec, n_tray, comp] = m.cpdT0[sec, n_tray, comp, 2] + m.dHvap[comp] #### Side feed - m.cpdTf = {} # Heat capacity for side feed [J/mol K] - m.hlf = {} # Liquid enthalpy for side feed [J/mol] - m.hvf = {} # Vapor enthalpy for side feed [J/mol] - m.F0 = {} # Side feed flowrate per component [mol/s] + m.cpdTf = {} # Heat capacity for side feed [J/mol K] + m.hlf = {} # Liquid enthalpy for side feed [J/mol] + m.hvf = {} # Vapor enthalpy for side feed [J/mol] + m.F0 = {} # Side feed flowrate per component [mol/s] ## Heat capacity in liquid and vapor phases for side feed for each component using Ruczika-D method for comp in m.comp: for cp in m.cplv: m.cpdTf[comp, cp] = ( - m.cpc[cp]*( - (m.Tf - m.Tref) * \ - m.prop[comp, 'cpA', cp] - + (m.Tf**2 - m.Tref**2) * \ - m.prop[comp, 'cpB', cp] * \ - m.cpc2['A', cp] / 2 - + (m.Tf**3 - m.Tref**3) * \ - m.prop[comp, 'cpC', cp] * \ - m.cpc2['B', cp] / 3 - + (m.Tf**4 - m.Tref**4) * \ - m.prop[comp, 'cpD', cp] / 4 - ) / m.Hscale + m.cpc[cp] + * ( + (m.Tf - m.Tref) * m.prop[comp, 'cpA', cp] + + (m.Tf**2 - m.Tref**2) + * m.prop[comp, 'cpB', cp] + * m.cpc2['A', cp] + / 2 + + (m.Tf**3 - m.Tref**3) + * m.prop[comp, 'cpC', cp] + * m.cpc2['B', cp] + / 3 + + (m.Tf**4 - m.Tref**4) * m.prop[comp, 'cpD', cp] / 4 + ) + / m.Hscale ) - ## Side feed flowrate and liquid and vapor enthalpy calculation using Ruczika-D method for each component in the feed + ## Side feed flowrate and liquid and vapor enthalpy calculation using Ruczika-D method for each component in the feed for comp in m.comp: - m.F0[comp] = m.xfi[comp] * m.Fi # Side feed flowrate per component computed from the feed composition and flowrate Fi - m.hlf[comp] = ( - m.cpdTf[comp, 1] - ) # Liquid enthalpy for side feed computed from the heat capacity for side feed and liquid phase + m.F0[comp] = ( + m.xfi[comp] * m.Fi + ) # Side feed flowrate per component computed from the feed composition and flowrate Fi + m.hlf[comp] = m.cpdTf[ + comp, 1 + ] # Liquid enthalpy for side feed computed from the heat capacity for side feed and liquid phase m.hvf[comp] = ( - m.cpdTf[comp, 2] - + m.dHvap[comp] - ) # Vapor enthalpy for side feed computed from the heat capacity for side feed and vapor phase and heat of vaporization - - m.P = Var(m.section, m.tray, - doc="Pressure at each potential tray in bars", - domain=NonNegativeReals, - bounds=(m.Pcon, m.Preb), - initialize=m.P0) - m.T = Var(m.section, m.tray, - doc="Temperature at each potential tray in K", - domain=NonNegativeReals, - bounds=(m.Tlo, m.Tup), - initialize=m.T0) - - m.x = Var(m.section, m.tray, m.comp, - doc="Liquid composition", - domain=NonNegativeReals, - bounds=(0, 1), - initialize=m.x0) - m.y = Var(m.section, m.tray, m.comp, - doc="Vapor composition", - domain=NonNegativeReals, - bounds=(0, 1), - initialize=m.y0) - - m.dl = Var(m.dw, - doc="Liquid distributor in the dividing wall sections", - bounds=(0.2, 0.8), - initialize=m.dl0) - m.dv = Var(m.dw, - doc="Vapor distributor in the dividing wall sections", - bounds=(0, 1), - domain=NonNegativeReals, - initialize=m.dv0) - - m.V = Var(m.section, m.tray, m.comp, - doc="Vapor flowrate in mol/s", - domain=NonNegativeReals, - bounds=(0, m.flow_max), - initialize=m.V0) - m.L = Var(m.section, m.tray, m.comp, - doc="Liquid flowrate in mol/s", - domain=NonNegativeReals, - bounds=(0, m.flow_max), - initialize=m.L0) - m.Vtotal = Var(m.section, m.tray, - doc="Total vapor flowrate in mol/s", - domain=NonNegativeReals, - bounds=(0, m.flow_max), - initialize=m.Vtotal0) - m.Ltotal = Var(m.section, m.tray, - doc="Total liquid flowrate in mol/s", - domain=NonNegativeReals, - bounds=(0, m.flow_max), - initialize=m.Ltotal0) - - m.D = Var(m.comp, - doc="Distillate flowrate in mol/s", - domain=NonNegativeReals, - bounds=(0, m.flow_max), - initialize=m.Ddes) - m.B = Var(m.comp, - doc="Bottoms flowrate in mol/s", - domain=NonNegativeReals, - bounds=(0, m.flow_max), - initialize=m.Bdes) - m.S = Var(m.so, m.comp, - doc="Product 2 and 3 flowrates in mol/s", - domain=NonNegativeReals, - bounds=(0, m.flow_max), - initialize=m.Sdes) - m.Dtotal = Var(doc="Distillate flowrate in mol/s", - domain=NonNegativeReals, - bounds=(0, m.flow_max), - initialize=m.Ddes) - m.Btotal = Var(doc="Bottoms flowrate in mol/s", - domain=NonNegativeReals, - bounds=(0, m.flow_max), - initialize=m.Bdes) - m.Stotal = Var(m.so, - doc="Total product 2 and 3 side flowrate in mol/s", - domain=NonNegativeReals, - bounds=(0, m.flow_max), - initialize=m.Sdes) - - m.hl = Var(m.section, m.tray, m.comp, - doc='Liquid enthalpy in J/mol', - bounds=(m.hllo, m.hlup), - initialize=m.hl0) - m.hv = Var(m.section, m.tray, m.comp, - doc='Vapor enthalpy in J/mol', - bounds=(m.hvlo, m.hvup), - initialize=m.hv0) - m.Qreb = Var(doc="Reboiler heat duty in J/s", - domain=NonNegativeReals, - bounds=(0, m.Qmax), - initialize=1) - m.Qcon = Var(doc="Condenser heat duty in J/s", - domain=NonNegativeReals, - bounds=(0, m.Qmax), - initialize=1) - - m.rr = Var(doc="Internal reflux ratio in the column", - domain=NonNegativeReals, - bounds=(0.7, 1), - initialize=m.rr0) - m.bu = Var(doc="Boilup rate in the reboiler", - domain=NonNegativeReals, - bounds=(0.7, 1), - initialize=m.bu0) - - m.F = Var(m.comp, - doc="Side feed flowrate in mol/s", - domain=NonNegativeReals, - bounds=(0, 50), - initialize=m.F0) - m.q = Var(doc="Vapor fraction in side feed", - domain=NonNegativeReals, - bounds=(0, 1), - initialize=1) - - m.actv = Var(m.section, m.tray, m.comp, - doc="Liquid activity coefficient", - domain=NonNegativeReals, - bounds=(0, 10), - initialize=m.actv0) - - m.errx = Var(m.section, m.tray, - doc="Error in liquid composition [mol/mol]", - bounds=(-1e-3, 1e-3), - initialize=0) - m.erry = Var(m.section, m.tray, - doc="Error in vapor composition [mol/mol]", - bounds=(-1e-3, 1e-3), initialize=0) - m.slack = Var(m.section, m.tray, m.comp, - doc="Slack variable", - bounds=(-1e-8, 1e-8), - initialize=0) - - m.tray_exists = Disjunct(m.section, m.tray, - doc="Disjunct that enforce the existence of each tray", - rule=_build_tray_equations) - m.tray_absent = Disjunct(m.section, m.tray, - doc="Disjunct that enforce the absence of each tray", - rule=_build_pass_through_eqns) - - - @m.Disjunction(m.section, m.tray, - doc="Disjunction between whether each tray exists or not") + m.cpdTf[comp, 2] + m.dHvap[comp] + ) # Vapor enthalpy for side feed computed from the heat capacity for side feed and vapor phase and heat of vaporization + + m.P = Var( + m.section, + m.tray, + doc="Pressure at each potential tray in bars", + domain=NonNegativeReals, + bounds=(m.Pcon, m.Preb), + initialize=m.P0, + ) + m.T = Var( + m.section, + m.tray, + doc="Temperature at each potential tray in K", + domain=NonNegativeReals, + bounds=(m.Tlo, m.Tup), + initialize=m.T0, + ) + + m.x = Var( + m.section, + m.tray, + m.comp, + doc="Liquid composition", + domain=NonNegativeReals, + bounds=(0, 1), + initialize=m.x0, + ) + m.y = Var( + m.section, + m.tray, + m.comp, + doc="Vapor composition", + domain=NonNegativeReals, + bounds=(0, 1), + initialize=m.y0, + ) + + m.dl = Var( + m.dw, + doc="Liquid distributor in the dividing wall sections", + bounds=(0.2, 0.8), + initialize=m.dl0, + ) + m.dv = Var( + m.dw, + doc="Vapor distributor in the dividing wall sections", + bounds=(0, 1), + domain=NonNegativeReals, + initialize=m.dv0, + ) + + m.V = Var( + m.section, + m.tray, + m.comp, + doc="Vapor flowrate in mol/s", + domain=NonNegativeReals, + bounds=(0, m.flow_max), + initialize=m.V0, + ) + m.L = Var( + m.section, + m.tray, + m.comp, + doc="Liquid flowrate in mol/s", + domain=NonNegativeReals, + bounds=(0, m.flow_max), + initialize=m.L0, + ) + m.Vtotal = Var( + m.section, + m.tray, + doc="Total vapor flowrate in mol/s", + domain=NonNegativeReals, + bounds=(0, m.flow_max), + initialize=m.Vtotal0, + ) + m.Ltotal = Var( + m.section, + m.tray, + doc="Total liquid flowrate in mol/s", + domain=NonNegativeReals, + bounds=(0, m.flow_max), + initialize=m.Ltotal0, + ) + + m.D = Var( + m.comp, + doc="Distillate flowrate in mol/s", + domain=NonNegativeReals, + bounds=(0, m.flow_max), + initialize=m.Ddes, + ) + m.B = Var( + m.comp, + doc="Bottoms flowrate in mol/s", + domain=NonNegativeReals, + bounds=(0, m.flow_max), + initialize=m.Bdes, + ) + m.S = Var( + m.so, + m.comp, + doc="Product 2 and 3 flowrates in mol/s", + domain=NonNegativeReals, + bounds=(0, m.flow_max), + initialize=m.Sdes, + ) + m.Dtotal = Var( + doc="Distillate flowrate in mol/s", + domain=NonNegativeReals, + bounds=(0, m.flow_max), + initialize=m.Ddes, + ) + m.Btotal = Var( + doc="Bottoms flowrate in mol/s", + domain=NonNegativeReals, + bounds=(0, m.flow_max), + initialize=m.Bdes, + ) + m.Stotal = Var( + m.so, + doc="Total product 2 and 3 side flowrate in mol/s", + domain=NonNegativeReals, + bounds=(0, m.flow_max), + initialize=m.Sdes, + ) + + m.hl = Var( + m.section, + m.tray, + m.comp, + doc='Liquid enthalpy in J/mol', + bounds=(m.hllo, m.hlup), + initialize=m.hl0, + ) + m.hv = Var( + m.section, + m.tray, + m.comp, + doc='Vapor enthalpy in J/mol', + bounds=(m.hvlo, m.hvup), + initialize=m.hv0, + ) + m.Qreb = Var( + doc="Reboiler heat duty in J/s", + domain=NonNegativeReals, + bounds=(0, m.Qmax), + initialize=1, + ) + m.Qcon = Var( + doc="Condenser heat duty in J/s", + domain=NonNegativeReals, + bounds=(0, m.Qmax), + initialize=1, + ) + + m.rr = Var( + doc="Internal reflux ratio in the column", + domain=NonNegativeReals, + bounds=(0.7, 1), + initialize=m.rr0, + ) + m.bu = Var( + doc="Boilup rate in the reboiler", + domain=NonNegativeReals, + bounds=(0.7, 1), + initialize=m.bu0, + ) + + m.F = Var( + m.comp, + doc="Side feed flowrate in mol/s", + domain=NonNegativeReals, + bounds=(0, 50), + initialize=m.F0, + ) + m.q = Var( + doc="Vapor fraction in side feed", + domain=NonNegativeReals, + bounds=(0, 1), + initialize=1, + ) + + m.actv = Var( + m.section, + m.tray, + m.comp, + doc="Liquid activity coefficient", + domain=NonNegativeReals, + bounds=(0, 10), + initialize=m.actv0, + ) + + m.errx = Var( + m.section, + m.tray, + doc="Error in liquid composition [mol/mol]", + bounds=(-1e-3, 1e-3), + initialize=0, + ) + m.erry = Var( + m.section, + m.tray, + doc="Error in vapor composition [mol/mol]", + bounds=(-1e-3, 1e-3), + initialize=0, + ) + m.slack = Var( + m.section, + m.tray, + m.comp, + doc="Slack variable", + bounds=(-1e-8, 1e-8), + initialize=0, + ) + + m.tray_exists = Disjunct( + m.section, + m.tray, + doc="Disjunct that enforce the existence of each tray", + rule=_build_tray_equations, + ) + m.tray_absent = Disjunct( + m.section, + m.tray, + doc="Disjunct that enforce the absence of each tray", + rule=_build_pass_through_eqns, + ) + + @m.Disjunction( + m.section, m.tray, doc="Disjunction between whether each tray exists or not" + ) def tray_exists_or_not(m, sec, n_tray): """ Disjunction between whether each tray exists or not. @@ -495,80 +566,97 @@ def tray_exists_or_not(m, sec, n_tray): @m.Constraint(m.section_main) def minimum_trays_main(m, sec): - """ - Constraint that ensures the minimum number of trays in the main section. - - Parameters - ---------- - m : Pyomo ConcreteModel - The model object for the GDP Kaibel Column. - sec : Set - The section index. - - Returns - ------- - Constraint - A constraint expression that enforces the minimum number of trays in the main section to be greater than or equal to the minimum number of trays. - """ - return sum(m.tray_exists[sec, n_tray].binary_indicator_var - for n_tray in m.candidate_trays_main) + 1 >= m.min_num_trays + """ + Constraint that ensures the minimum number of trays in the main section. + + Parameters + ---------- + m : Pyomo ConcreteModel + The model object for the GDP Kaibel Column. + sec : Set + The section index. + + Returns + ------- + Constraint + A constraint expression that enforces the minimum number of trays in the main section to be greater than or equal to the minimum number of trays. + """ + return ( + sum( + m.tray_exists[sec, n_tray].binary_indicator_var + for n_tray in m.candidate_trays_main + ) + + 1 + >= m.min_num_trays + ) @m.Constraint() def minimum_trays_feed(m): - """ - Constraint function that ensures the minimum number of trays in the feed section is met. - - Parameters - ---------- - m : Pyomo ConcreteModel - The Pyomo model object. - - Returns - ------- - Constraint - The constraint expression that enforces the minimum number of trays is greater than or equal to the minimum number of trays. - """ - return sum(m.tray_exists[2, n_tray].binary_indicator_var - for n_tray in m.candidate_trays_feed) + 1 >= m.min_num_trays - #TOCHECK: pyomo.GDP Syntax + """ + Constraint function that ensures the minimum number of trays in the feed section is met. + + Parameters + ---------- + m : Pyomo ConcreteModel + The Pyomo model object. + + Returns + ------- + Constraint + The constraint expression that enforces the minimum number of trays is greater than or equal to the minimum number of trays. + """ + return ( + sum( + m.tray_exists[2, n_tray].binary_indicator_var + for n_tray in m.candidate_trays_feed + ) + + 1 + >= m.min_num_trays + ) + + # TOCHECK: pyomo.GDP Syntax @m.Constraint() def minimum_trays_product(m): - """ - Constraint function to calculate the minimum number of trays in the product section. - - Parameters - ---------- - m : Pyomo ConcreteModel - The optimization model. - - Returns - ------- - Constraint - The constraint expression that enforces the minimum number of trays is greater than or equal to the minimum number of trays. - """ - return sum(m.tray_exists[3, n_tray].binary_indicator_var - for n_tray in m.candidate_trays_product) + 1 >= m.min_num_trays - - - ## Fixed trays - enforce_tray_exists(m, 1, 1) # reboiler - enforce_tray_exists(m, 1, m.num_trays) # vapor distributor - enforce_tray_exists(m, 2, 1) # dividing wall starting tray - enforce_tray_exists(m, 2, m.feed_tray) # feed tray - enforce_tray_exists(m, 2, m.num_trays) # dividing wall ending tray - enforce_tray_exists(m, 3, 1) # dividing wall starting tray - enforce_tray_exists(m, 3, m.sideout1_tray)# side outlet 1 for product 3 - enforce_tray_exists(m, 3, m.sideout2_tray)# side outlet 2 for product 2 - enforce_tray_exists(m, 3, m.num_trays) # dividing wall ending tray - enforce_tray_exists(m, 4, 1) # liquid distributor - enforce_tray_exists(m, 4, m.num_trays) # condenser + """ + Constraint function to calculate the minimum number of trays in the product section. + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + Returns + ------- + Constraint + The constraint expression that enforces the minimum number of trays is greater than or equal to the minimum number of trays. + """ + return ( + sum( + m.tray_exists[3, n_tray].binary_indicator_var + for n_tray in m.candidate_trays_product + ) + + 1 + >= m.min_num_trays + ) + ## Fixed trays + enforce_tray_exists(m, 1, 1) # reboiler + enforce_tray_exists(m, 1, m.num_trays) # vapor distributor + enforce_tray_exists(m, 2, 1) # dividing wall starting tray + enforce_tray_exists(m, 2, m.feed_tray) # feed tray + enforce_tray_exists(m, 2, m.num_trays) # dividing wall ending tray + enforce_tray_exists(m, 3, 1) # dividing wall starting tray + enforce_tray_exists(m, 3, m.sideout1_tray) # side outlet 1 for product 3 + enforce_tray_exists(m, 3, m.sideout2_tray) # side outlet 2 for product 2 + enforce_tray_exists(m, 3, m.num_trays) # dividing wall ending tray + enforce_tray_exists(m, 4, 1) # liquid distributor + enforce_tray_exists(m, 4, m.num_trays) # condenser #### Global constraints - @m.Constraint(m.dw, m.tray, doc="Monotonic temperature in the dividing wall sections") + @m.Constraint( + m.dw, m.tray, doc="Monotonic temperature in the dividing wall sections" + ) def monotonic_temperature(m, sec, n_tray): """This function returns a constraint object representing the monotonic temperature constraint. @@ -588,10 +676,9 @@ def monotonic_temperature(m, sec, n_tray): ------- Constraint The monotonic temperature constraint specifying that the temperature on each tray is less than or equal to the temperature on the top tray of section 1 which is the condenser. - """ + """ return m.T[sec, n_tray] <= m.T[1, m.num_trays] - @m.Constraint(doc="Liquid distributor") def liquid_distributor(m): """Defines the liquid distributor constraint. @@ -611,7 +698,6 @@ def liquid_distributor(m): """ return sum(m.dl[sec] for sec in m.dw) - 1 == 0 - @m.Constraint(doc="Vapor distributor") def vapor_distributor(m): """ @@ -626,10 +712,9 @@ def vapor_distributor(m): ------- Constraint The vapor distributor constraint. - """ + """ return sum(m.dv[sec] for sec in m.dw) - 1 == 0 - @m.Constraint(doc="Reboiler composition specification") def heavy_product(m): """ @@ -668,12 +753,12 @@ def light_product(m): def intermediate1_product(m): ''' This constraint ensures that the intermediate 1 final liquid composition is greater than or equal to the specified composition xspec_inter1, which is the final liquid composition for ethanol. - + Parameters ---------- m : Pyomo ConcreteModel. The optimization model. - + Returns ------- Constraint @@ -681,7 +766,6 @@ def intermediate1_product(m): ''' return m.x[3, m.sideout1_tray, 3] >= m.xspec_inter3 - @m.Constraint(doc="Side outlet 2 final liquid composition") def intermediate2_product(m): """ @@ -699,7 +783,6 @@ def intermediate2_product(m): """ return m.x[3, m.sideout2_tray, 2] >= m.xspec_inter2 - @m.Constraint(doc="Reboiler flowrate") def _heavy_product_flow(m): """ @@ -715,9 +798,8 @@ def _heavy_product_flow(m): Constraint The constraint that enforces the reboiler flowrate is greater than or equal to the specified flowrate Bdes, which is the flowrate of butanol. """ - return m.Btotal >= m.Bdes + return m.Btotal >= m.Bdes - @m.Constraint(doc="Condenser flowrate") def _light_product_flow(m): """ @@ -733,9 +815,8 @@ def _light_product_flow(m): Constraint The constraint that enforces the condenser flowrate is greater than or equal to the specified flowrate Ddes, which is the flowrate of ethanol. """ - return m.Dtotal >= m.Ddes + return m.Dtotal >= m.Ddes - @m.Constraint(m.so, doc="Intermediate flowrate") def _intermediate_product_flow(m, so): """ @@ -753,9 +834,8 @@ def _intermediate_product_flow(m, so): Constraint The constraint that enforces the intermediate flowrate is greater than or equal to the specified flowrate Sdes, which is the flowrate of the intermediate side product 2 and 3. """ - return m.Stotal[so] >= m.Sdes + return m.Stotal[so] >= m.Sdes - @m.Constraint(doc="Internal boilup ratio, V/L") def _internal_boilup_ratio(m): """ @@ -773,7 +853,6 @@ def _internal_boilup_ratio(m): """ return m.bu * m.Ltotal[1, m.reb_tray + 1] == m.Vtotal[1, m.reb_tray] - @m.Constraint(doc="Internal reflux ratio, L/V") def internal_reflux_ratio(m): """ @@ -791,7 +870,6 @@ def internal_reflux_ratio(m): """ return m.rr * m.Vtotal[4, m.con_tray - 1] == m.Ltotal[4, m.con_tray] - @m.Constraint(doc="External boilup ratio relation with bottoms") def _external_boilup_ratio(m): """ @@ -807,8 +885,7 @@ def _external_boilup_ratio(m): Constraint The constraint that enforces the external boilup ratio times the liquid flowrate on the reboiler tray is equal to the bottoms flowrate. """ - return m.Btotal == (1 - m.bu) * m.Ltotal[1, m.reb_tray + 1] - + return m.Btotal == (1 - m.bu) * m.Ltotal[1, m.reb_tray + 1] @m.Constraint(doc="External reflux ratio relation with distillate") def _external_reflux_ratio(m): @@ -825,9 +902,8 @@ def _external_reflux_ratio(m): Constraint The constraint that enforces the external reflux ratio times the vapor flowrate on the tray above the condenser tray is equal to the distillate flowrate. """ - return m.Dtotal == (1 - m.rr) * m.Vtotal[4, m.con_tray - 1] + return m.Dtotal == (1 - m.rr) * m.Vtotal[4, m.con_tray - 1] - @m.Constraint(m.section, m.tray, doc="Total vapor flowrate") def _total_vapor_flowrate(m, sec, n_tray): """ @@ -847,8 +923,7 @@ def _total_vapor_flowrate(m, sec, n_tray): Constraint The constraint that enforces the total vapor flowrate is equal to the sum of the vapor flowrates of each component on each tray on each section. """ - return sum(m.V[sec, n_tray, comp] for comp in m.comp) == m.Vtotal[sec, n_tray] - + return sum(m.V[sec, n_tray, comp] for comp in m.comp) == m.Vtotal[sec, n_tray] @m.Constraint(m.section, m.tray, doc="Total liquid flowrate") def _total_liquid_flowrate(m, sec, n_tray): @@ -869,8 +944,7 @@ def _total_liquid_flowrate(m, sec, n_tray): Constraint The constraint that enforces the total liquid flowrate is equal to the sum of the liquid flowrates of each component on each tray on each section. """ - return sum(m.L[sec, n_tray, comp] for comp in m.comp) == m.Ltotal[sec, n_tray] - + return sum(m.L[sec, n_tray, comp] for comp in m.comp) == m.Ltotal[sec, n_tray] @m.Constraint(m.comp, doc="Bottoms and liquid relation") def bottoms_equality(m, comp): @@ -891,7 +965,6 @@ def bottoms_equality(m, comp): """ return m.B[comp] == m.L[1, m.reb_tray, comp] - @m.Constraint(m.comp) def condenser_total(m, comp): """ @@ -908,9 +981,8 @@ def condenser_total(m, comp): Constraint The constraint that enforces the distillate flowrate is equal to zero in the condenser. """ - return m.V[4, m.con_tray, comp] == 0 + return m.V[4, m.con_tray, comp] == 0 - @m.Constraint() def total_bottoms_product(m): """ @@ -928,7 +1000,6 @@ def total_bottoms_product(m): """ return sum(m.B[comp] for comp in m.comp) == m.Btotal - @m.Constraint() def total_distillate_product(m): """ @@ -946,7 +1017,6 @@ def total_distillate_product(m): """ return sum(m.D[comp] for comp in m.comp) == m.Dtotal - @m.Constraint(m.so) def total_side_product(m, so): """ @@ -966,28 +1036,36 @@ def total_side_product(m, so): """ return sum(m.S[so, comp] for comp in m.comp) == m.Stotal[so] - - # Considers the number of existent trays and operating costs (condenser and reboiler heat duties) in the column. To ensure equal weights to the capital and operating costs, the number of existent trays is multiplied by a weight coefficient of 1000. m.obj = Objective( - expr= (m.Qcon + m.Qreb) * m.Hscale + 1e3 * ( + expr=(m.Qcon + m.Qreb) * m.Hscale + + 1e3 + * ( sum( - sum(m.tray_exists[sec, n_tray].binary_indicator_var - for n_tray in m.candidate_trays_main) - for sec in m.section_main) - + sum(m.tray_exists[2, n_tray].binary_indicator_var - for n_tray in m.candidate_trays_feed) - + sum(m.tray_exists[3, n_tray].binary_indicator_var - for n_tray in m.candidate_trays_product) - + 1), - sense=minimize, doc="Objective function to minimize the operating costs and number of existent trays in the column") - - - - - - @m.Constraint(m.section_main, m.candidate_trays_main, doc="Logic proposition for main section") + sum( + m.tray_exists[sec, n_tray].binary_indicator_var + for n_tray in m.candidate_trays_main + ) + for sec in m.section_main + ) + + sum( + m.tray_exists[2, n_tray].binary_indicator_var + for n_tray in m.candidate_trays_feed + ) + + sum( + m.tray_exists[3, n_tray].binary_indicator_var + for n_tray in m.candidate_trays_product + ) + + 1 + ), + sense=minimize, + doc="Objective function to minimize the operating costs and number of existent trays in the column", + ) + + @m.Constraint( + m.section_main, m.candidate_trays_main, doc="Logic proposition for main section" + ) def _logic_proposition_main(m, sec, n_tray): """ Apply a logic proposition constraint to the main section and candidate trays to specify the order of trays in the column is from bottom to top provided the condition is met. @@ -1008,38 +1086,45 @@ def _logic_proposition_main(m, sec, n_tray): """ if n_tray > m.reb_tray and (n_tray + 1) < m.num_trays: - return m.tray_exists[sec, n_tray].binary_indicator_var <= m.tray_exists[sec, n_tray + 1].binary_indicator_var + return ( + m.tray_exists[sec, n_tray].binary_indicator_var + <= m.tray_exists[sec, n_tray + 1].binary_indicator_var + ) else: return Constraint.NoConstraint # TOCHECK: Update the logic proposition constraint for the main section with the new pyomo.gdp syntax - @m.Constraint(m.candidate_trays_feed) def _logic_proposition_feed(m, n_tray): - """ + """ Apply a logic proposition constraint to the feed section and candidate trays to specify the order of trays in the column is from bottom to top provided the condition is met. - + Parameters ---------- m : Pyomo ConcreteModel The optimization model. n_tray : int The tray index. - + Returns ------- Constraint or NoConstraint The constraint expression or NoConstraint if the condition is not met. """ if n_tray > m.reb_tray and (n_tray + 1) < m.feed_tray: - return m.tray_exists[2, n_tray].binary_indicator_var <= m.tray_exists[2, n_tray + 1].binary_indicator_var + return ( + m.tray_exists[2, n_tray].binary_indicator_var + <= m.tray_exists[2, n_tray + 1].binary_indicator_var + ) elif n_tray > m.feed_tray and (n_tray + 1) < m.con_tray: - return m.tray_exists[2, n_tray + 1].binary_indicator_var <= m.tray_exists[2, n_tray].binary_indicator_var + return ( + m.tray_exists[2, n_tray + 1].binary_indicator_var + <= m.tray_exists[2, n_tray].binary_indicator_var + ) else: return Constraint.NoConstraint # TODO: Update the logic proposition constraint for the feed section with the new pyomo.gdp syntax - @m.Constraint(m.candidate_trays_product) def _logic_proposition_section3(m, n_tray): """ @@ -1047,7 +1132,7 @@ def _logic_proposition_section3(m, n_tray): Parameters ---------- - m : Pyomo ConcreteModel + m : Pyomo ConcreteModel The optimization model. n_tray : int The tray index. @@ -1058,12 +1143,14 @@ def _logic_proposition_section3(m, n_tray): The constraint expression or NoConstraint if the condition is not met. """ if n_tray > 1 and (n_tray + 1) < m.num_trays: - return m.tray_exists[3, n_tray].binary_indicator_var <= m.tray_exists[3, n_tray + 1].binary_indicator_var + return ( + m.tray_exists[3, n_tray].binary_indicator_var + <= m.tray_exists[3, n_tray + 1].binary_indicator_var + ) else: return Constraint.NoConstraint # TODO: Update the logic proposition constraint for the product section with the new pyomo.gdp syntax - @m.Constraint(m.tray) def equality_feed_product_side(m, n_tray): """ @@ -1081,9 +1168,12 @@ def equality_feed_product_side(m, n_tray): Constraint The constraint expression that enforces the equality of the binary indicator variables for the feed and product side trays. """ - return m.tray_exists[2, n_tray].binary_indicator_var == m.tray_exists[3, n_tray].binary_indicator_var - # TODO: Update the equality constraint for the feed and product side trays with the new pyomo.gdp syntax + return ( + m.tray_exists[2, n_tray].binary_indicator_var + == m.tray_exists[3, n_tray].binary_indicator_var + ) + # TODO: Update the equality constraint for the feed and product side trays with the new pyomo.gdp syntax @m.Constraint() def _existent_minimum_numbertrays(m): @@ -1101,14 +1191,17 @@ def _existent_minimum_numbertrays(m): The constraint expression that enforces the minimum number of trays in each section and each tray to be greater than or equal to the minimum number of trays. """ return sum( - sum(m.tray_exists[sec, n_tray].binary_indicator_var - for n_tray in m.tray) for sec in m.section) - sum(m.tray_exists[3, n_tray].binary_indicator_var for n_tray in m.tray) >= int(m.min_tray) - + sum(m.tray_exists[sec, n_tray].binary_indicator_var for n_tray in m.tray) + for sec in m.section + ) - sum( + m.tray_exists[3, n_tray].binary_indicator_var for n_tray in m.tray + ) >= int( + m.min_tray + ) return m - def enforce_tray_exists(m, sec, n_tray): """ Enforce the existence of a tray in the column. @@ -1139,7 +1232,7 @@ def _build_tray_equations(m, sec, n_tray): The section index. n_tray : int The tray index. - + Returns ------- None @@ -1148,12 +1241,11 @@ def _build_tray_equations(m, sec, n_tray): 1: _build_bottom_equations, 2: _build_feed_side_equations, 3: _build_product_side_equations, - 4: _build_top_equations + 4: _build_top_equations, } build_function[sec](m, n_tray) - def _build_bottom_equations(disj, n_tray): """ Build the equations for the bottom section in the column. @@ -1171,8 +1263,7 @@ def _build_bottom_equations(disj, n_tray): """ m = disj.model() - @disj.Constraint(m.comp, - doc="Bottom section 1 mass per component balances") + @disj.Constraint(m.comp, doc="Bottom section 1 mass per component balances") def _bottom_mass_percomponent_balances(disj, comp): """ Mass per component balances for the bottom section in the column. @@ -1189,28 +1280,24 @@ def _bottom_mass_percomponent_balances(disj, comp): Constraint The constraint expression that enforces the mass balance per component in the bottom section of the column. """ - return ( - (m.L[1, n_tray + 1, comp] - if n_tray < m.num_trays else 0) - + (m.L[2, 1, comp] - if n_tray == m.num_trays else 0) - + (m.L[3, 1, comp] - if n_tray == m.num_trays else 0) - - (m.L[1, n_tray, comp] - if n_tray > m.reb_tray else 0) - + (m.V[1, n_tray - 1, comp] - if n_tray > m.reb_tray else 0) - - (m.V[1, n_tray, comp] * m.dv[2] - if n_tray == m.num_trays else 0) - - (m.V[1, n_tray, comp] * m.dv[3] - if n_tray == m.num_trays else 0) - - (m.V[1, n_tray, comp] - if n_tray < m.num_trays else 0) - - (m.B[comp] - if n_tray == m.reb_tray else 0) - == m.slack[1, n_tray, comp] - ) - + return (m.L[1, n_tray + 1, comp] if n_tray < m.num_trays else 0) + ( + m.L[2, 1, comp] if n_tray == m.num_trays else 0 + ) + (m.L[3, 1, comp] if n_tray == m.num_trays else 0) - ( + m.L[1, n_tray, comp] if n_tray > m.reb_tray else 0 + ) + ( + m.V[1, n_tray - 1, comp] if n_tray > m.reb_tray else 0 + ) - ( + m.V[1, n_tray, comp] * m.dv[2] if n_tray == m.num_trays else 0 + ) - ( + m.V[1, n_tray, comp] * m.dv[3] if n_tray == m.num_trays else 0 + ) - ( + m.V[1, n_tray, comp] if n_tray < m.num_trays else 0 + ) - ( + m.B[comp] if n_tray == m.reb_tray else 0 + ) == m.slack[ + 1, n_tray, comp + ] + @disj.Constraint(doc="Bottom section 1 energy balances") def _bottom_energy_balances(disj): """ @@ -1228,32 +1315,47 @@ def _bottom_energy_balances(disj): """ return ( sum( - (m.L[1, n_tray + 1, comp] * m.hl[1, n_tray + 1, comp] - if n_tray < m.num_trays else 0) - + (m.L[2, 1, comp] * m.hl[2, 1, comp] - if n_tray == m.num_trays else 0) - + (m.L[3, 1, comp] * m.hl[3, 1, comp] - if n_tray == m.num_trays else 0) - - (m.L[1, n_tray, comp] * m.hl[1, n_tray, comp] - if n_tray > m.reb_tray else 0) - + (m.V[1, n_tray - 1, comp] * m.hv[1, n_tray - 1, comp] - if n_tray > m.reb_tray else 0) - - (m.V[1, n_tray, comp] * m.dv[2] * m.hv[1, n_tray, comp] - if n_tray == m.num_trays else 0) - - (m.V[1, n_tray, comp] * m.dv[3] * m.hv[1, n_tray, comp] - if n_tray == m.num_trays else 0) - - (m.V[1, n_tray, comp] * m.hv[1, n_tray, comp] - if n_tray < m.num_trays else 0) - - (m.B[comp] * m.hl[1, n_tray, comp] - if n_tray == m.reb_tray else 0) - for comp in m.comp) * m.Qscale - + (m.Qreb if n_tray == m.reb_tray else 0) - ==0 + ( + m.L[1, n_tray + 1, comp] * m.hl[1, n_tray + 1, comp] + if n_tray < m.num_trays + else 0 + ) + + (m.L[2, 1, comp] * m.hl[2, 1, comp] if n_tray == m.num_trays else 0) + + (m.L[3, 1, comp] * m.hl[3, 1, comp] if n_tray == m.num_trays else 0) + - ( + m.L[1, n_tray, comp] * m.hl[1, n_tray, comp] + if n_tray > m.reb_tray + else 0 + ) + + ( + m.V[1, n_tray - 1, comp] * m.hv[1, n_tray - 1, comp] + if n_tray > m.reb_tray + else 0 + ) + - ( + m.V[1, n_tray, comp] * m.dv[2] * m.hv[1, n_tray, comp] + if n_tray == m.num_trays + else 0 + ) + - ( + m.V[1, n_tray, comp] * m.dv[3] * m.hv[1, n_tray, comp] + if n_tray == m.num_trays + else 0 + ) + - ( + m.V[1, n_tray, comp] * m.hv[1, n_tray, comp] + if n_tray < m.num_trays + else 0 + ) + - (m.B[comp] * m.hl[1, n_tray, comp] if n_tray == m.reb_tray else 0) + for comp in m.comp + ) + * m.Qscale + + (m.Qreb if n_tray == m.reb_tray else 0) + == 0 ) - - @disj.Constraint(m.comp, - doc="Bottom section 1 liquid flowrate per component") + @disj.Constraint(m.comp, doc="Bottom section 1 liquid flowrate per component") def _bottom_liquid_percomponent(disj, comp): """ Liquid flowrate per component in the bottom section of the column. @@ -1272,9 +1374,7 @@ def _bottom_liquid_percomponent(disj, comp): """ return m.L[1, n_tray, comp] == m.Ltotal[1, n_tray] * m.x[1, n_tray, comp] - - @disj.Constraint(m.comp, - doc="Bottom section 1 vapor flowrate per component") + @disj.Constraint(m.comp, doc="Bottom section 1 vapor flowrate per component") def _bottom_vapor_percomponent(disj, comp): """ Vapor flowrate per component in the bottom section of the column. @@ -1291,8 +1391,7 @@ def _bottom_vapor_percomponent(disj, comp): Constraint The constraint expression that enforces the vapor flowrate per component in the bottom section of the column. """ - return m.V[1, n_tray, comp] == m.Vtotal[1, n_tray] * m.y[1, n_tray, comp] - + return m.V[1, n_tray, comp] == m.Vtotal[1, n_tray] * m.y[1, n_tray, comp] @disj.Constraint(doc="Bottom section 1 liquid composition equilibrium summation") def bottom_liquid_composition_summation(disj): @@ -1312,7 +1411,6 @@ def bottom_liquid_composition_summation(disj): """ return sum(m.x[1, n_tray, comp] for comp in m.comp) - 1 == m.errx[1, n_tray] - @disj.Constraint(doc="Bottom section 1 vapor composition equilibrium summation") def bottom_vapor_composition_summation(disj): """ @@ -1331,9 +1429,7 @@ def bottom_vapor_composition_summation(disj): """ return sum(m.y[1, n_tray, comp] for comp in m.comp) - 1 == m.erry[1, n_tray] - - @disj.Constraint(m.comp, - doc="Bottom section 1 vapor composition") + @disj.Constraint(m.comp, doc="Bottom section 1 vapor composition") def bottom_vapor_composition(disj, comp): """ Vapor composition for the bottom section in the column. @@ -1349,29 +1445,35 @@ def bottom_vapor_composition(disj, comp): ------- Constraint The constraint expression that enforces the vapor composition for the bottom section in the column. - The equation is derived from the vapor-liquid equilibrium relationship. - """ - return m.y[1, n_tray, comp] == m.x[1, n_tray, comp] * ( - m.actv[1, n_tray, comp] * ( - m.prop[comp, 'PC'] * exp( - m.prop[comp, 'TC'] / m.T[1, n_tray] * ( - m.prop[comp, 'vpA'] * \ - (1 - m.T[1, n_tray] / m.prop[comp, 'TC']) - + m.prop[comp, 'vpB'] * \ - (1 - m.T[1, n_tray]/m.prop[comp, 'TC'])**1.5 - + m.prop[comp, 'vpC'] * \ - (1 - m.T[1, n_tray]/m.prop[comp, 'TC'])**3 - + m.prop[comp, 'vpD'] * \ - (1 - m.T[1, n_tray]/m.prop[comp, 'TC'])**6 + The equation is derived from the vapor-liquid equilibrium relationship. + """ + return ( + m.y[1, n_tray, comp] + == m.x[1, n_tray, comp] + * ( + m.actv[1, n_tray, comp] + * ( + m.prop[comp, 'PC'] + * exp( + m.prop[comp, 'TC'] + / m.T[1, n_tray] + * ( + m.prop[comp, 'vpA'] + * (1 - m.T[1, n_tray] / m.prop[comp, 'TC']) + + m.prop[comp, 'vpB'] + * (1 - m.T[1, n_tray] / m.prop[comp, 'TC']) ** 1.5 + + m.prop[comp, 'vpC'] + * (1 - m.T[1, n_tray] / m.prop[comp, 'TC']) ** 3 + + m.prop[comp, 'vpD'] + * (1 - m.T[1, n_tray] / m.prop[comp, 'TC']) ** 6 + ) ) ) ) - ) / m.P[1, n_tray] - + / m.P[1, n_tray] + ) - - @disj.Constraint(m.comp, - doc="Bottom section 1 liquid enthalpy") + @disj.Constraint(m.comp, doc="Bottom section 1 liquid enthalpy") def bottom_liquid_enthalpy(disj, comp): """ Liquid enthalpy for the bottom section in the column. @@ -1389,21 +1491,23 @@ def bottom_liquid_enthalpy(disj, comp): The constraint expression that enforces the liquid enthalpy for the bottom section in the column. """ return m.hl[1, n_tray, comp] == ( - m.cpc[1] * ( - (m.T[1, n_tray] - m.Tref) * \ - m.prop[comp, 'cpA', 1] - + (m.T[1, n_tray]**2 - m.Tref**2) * \ - m.prop[comp, 'cpB', 1] * m.cpc2['A', 1] / 2 - + (m.T[1, n_tray]**3 - m.Tref**3) * \ - m.prop[comp, 'cpC', 1] * m.cpc2['B', 1] / 3 - + (m.T[1, n_tray]**4 - m.Tref**4) * \ - m.prop[comp, 'cpD', 1] / 4 - ) / m.Hscale - ) - - - @disj.Constraint(m.comp, - doc="Bottom section 1 vapor enthalpy") + m.cpc[1] + * ( + (m.T[1, n_tray] - m.Tref) * m.prop[comp, 'cpA', 1] + + (m.T[1, n_tray] ** 2 - m.Tref**2) + * m.prop[comp, 'cpB', 1] + * m.cpc2['A', 1] + / 2 + + (m.T[1, n_tray] ** 3 - m.Tref**3) + * m.prop[comp, 'cpC', 1] + * m.cpc2['B', 1] + / 3 + + (m.T[1, n_tray] ** 4 - m.Tref**4) * m.prop[comp, 'cpD', 1] / 4 + ) + / m.Hscale + ) + + @disj.Constraint(m.comp, doc="Bottom section 1 vapor enthalpy") def bottom_vapor_enthalpy(disj, comp): """ Vapor enthalpy for the bottom section in the column. @@ -1421,21 +1525,24 @@ def bottom_vapor_enthalpy(disj, comp): The constraint expression that enforces the vapor enthalpy for the bottom section in the column. """ return m.hv[1, n_tray, comp] == ( - m.cpc[2] * ( - (m.T[1, n_tray] - m.Tref) * \ - m.prop[comp, 'cpA', 2] - + (m.T[1, n_tray]**2 - m.Tref**2) * \ - m.prop[comp, 'cpB', 2] * m.cpc2['A', 2] / 2 - + (m.T[1, n_tray]**3 - m.Tref**3) * \ - m.prop[comp, 'cpC', 2] * m.cpc2['B', 2] / 3 - + (m.T[1, n_tray]**4 - m.Tref**4) * \ - m.prop[comp, 'cpD', 2] / 4 - ) / m.Hscale + m.cpc[2] + * ( + (m.T[1, n_tray] - m.Tref) * m.prop[comp, 'cpA', 2] + + (m.T[1, n_tray] ** 2 - m.Tref**2) + * m.prop[comp, 'cpB', 2] + * m.cpc2['A', 2] + / 2 + + (m.T[1, n_tray] ** 3 - m.Tref**3) + * m.prop[comp, 'cpC', 2] + * m.cpc2['B', 2] + / 3 + + (m.T[1, n_tray] ** 4 - m.Tref**4) * m.prop[comp, 'cpD', 2] / 4 + ) + / m.Hscale + m.dHvap[comp] - ) + ) - @disj.Constraint(m.comp, - doc="Bottom section 1 liquid activity coefficient") + @disj.Constraint(m.comp, doc="Bottom section 1 liquid activity coefficient") def bottom_activity_coefficient(disj, comp): """ Liquid activity coefficient for the bottom section in the column equal to 1. @@ -1452,12 +1559,9 @@ def bottom_activity_coefficient(disj, comp): Constraint The constraint expression that enforces the liquid activity coefficient for the bottom section for each component and tray in the column to be equal to 1. """ - return m.actv[1, n_tray, comp] == 1 - + return m.actv[1, n_tray, comp] == 1 - - def _build_feed_side_equations(disj, n_tray): """ Build the equations for the feed side section in the column. @@ -1475,8 +1579,7 @@ def _build_feed_side_equations(disj, n_tray): """ m = disj.model() - @disj.Constraint(m.comp, - doc="Feed section 2 mass per component balances") + @disj.Constraint(m.comp, doc="Feed section 2 mass per component balances") def _feedside_masspercomponent_balances(disj, comp): """ Mass per component balances for the feed side section in the column. @@ -1493,22 +1596,17 @@ def _feedside_masspercomponent_balances(disj, comp): Constraint The constraint expression that enforces the mass balance per component in the feed side section of the column. """ - return ( - (m.L[2, n_tray + 1, comp] - if n_tray < m.num_trays else 0) - + (m.L[4, 1, comp] * m.dl[2] - if n_tray == m.num_trays else 0) - - m.L[2, n_tray, comp] - + (m.V[1, m.num_trays, comp] * m.dv[2] - if n_tray == 1 else 0) - + (m.V[2, n_tray - 1, comp] - if n_tray > 1 else 0) - - m.V[2, n_tray, comp] - + (m.F[comp] - if n_tray == m.feed_tray else 0) - == 0 - ) - + return (m.L[2, n_tray + 1, comp] if n_tray < m.num_trays else 0) + ( + m.L[4, 1, comp] * m.dl[2] if n_tray == m.num_trays else 0 + ) - m.L[2, n_tray, comp] + ( + m.V[1, m.num_trays, comp] * m.dv[2] if n_tray == 1 else 0 + ) + ( + m.V[2, n_tray - 1, comp] if n_tray > 1 else 0 + ) - m.V[ + 2, n_tray, comp + ] + ( + m.F[comp] if n_tray == m.feed_tray else 0 + ) == 0 @disj.Constraint(doc="Feed section 2 energy balances") def _feedside_energy_balances(disj): @@ -1527,27 +1625,44 @@ def _feedside_energy_balances(disj): """ return ( sum( - (m.L[2, n_tray + 1, comp] * m.hl[2, n_tray + 1, comp] - if n_tray < m.num_trays else 0) - + (m.L[4, 1, comp] * m.dl[2] * m.hl[4, 1, comp] - if n_tray == m.num_trays else 0) + ( + m.L[2, n_tray + 1, comp] * m.hl[2, n_tray + 1, comp] + if n_tray < m.num_trays + else 0 + ) + + ( + m.L[4, 1, comp] * m.dl[2] * m.hl[4, 1, comp] + if n_tray == m.num_trays + else 0 + ) - m.L[2, n_tray, comp] * m.hl[2, n_tray, comp] - + (m.V[1, m.num_trays, comp] * m.dv[2] * m.hv[1, m.num_trays, comp] - if n_tray == 1 else 0) - + (m.V[2, n_tray - 1, comp] * m.hv[2, n_tray - 1, comp] - if n_tray > 1 else 0) + + ( + m.V[1, m.num_trays, comp] * m.dv[2] * m.hv[1, m.num_trays, comp] + if n_tray == 1 + else 0 + ) + + ( + m.V[2, n_tray - 1, comp] * m.hv[2, n_tray - 1, comp] + if n_tray > 1 + else 0 + ) - m.V[2, n_tray, comp] * m.hv[2, n_tray, comp] - for comp in m.comp) * m.Qscale + for comp in m.comp + ) + * m.Qscale + sum( - (m.F[comp] * (m.hlf[comp] * (1 - m.q) + m.hvf[comp] * m.q) - if n_tray == m.feed_tray else 0) - for comp in m.comp) * m.Qscale - ==0 + ( + m.F[comp] * (m.hlf[comp] * (1 - m.q) + m.hvf[comp] * m.q) + if n_tray == m.feed_tray + else 0 + ) + for comp in m.comp + ) + * m.Qscale + == 0 ) - - @disj.Constraint(m.comp, - doc="Feed section 2 liquid flowrate per component") + @disj.Constraint(m.comp, doc="Feed section 2 liquid flowrate per component") def _feedside_liquid_percomponent(disj, comp): """ Liquid flowrate per component in the feed side section of the column. @@ -1566,9 +1681,7 @@ def _feedside_liquid_percomponent(disj, comp): """ return m.L[2, n_tray, comp] == m.Ltotal[2, n_tray] * m.x[2, n_tray, comp] - - @disj.Constraint(m.comp, - doc="Feed section 2 vapor flowrate per component") + @disj.Constraint(m.comp, doc="Feed section 2 vapor flowrate per component") def _feedside_vapor_percomponent(disj, comp): """ Vapor flowrate per component in the feed side section of the column. @@ -1585,8 +1698,7 @@ def _feedside_vapor_percomponent(disj, comp): Constraint The constraint expression that enforces the vapor flowrate per component in the feed side section of the column is equal to the total vapor flowrate times the vapor composition. """ - return m.V[2, n_tray, comp] == m.Vtotal[2, n_tray] * m.y[2, n_tray, comp] - + return m.V[2, n_tray, comp] == m.Vtotal[2, n_tray] * m.y[2, n_tray, comp] @disj.Constraint(doc="Feed section 2 liquid composition equilibrium summation") def feedside_liquid_composition_summation(disj): @@ -1606,7 +1718,6 @@ def feedside_liquid_composition_summation(disj): """ return sum(m.x[2, n_tray, comp] for comp in m.comp) - 1 == m.errx[2, n_tray] - @disj.Constraint(doc="Feed section 2 vapor composition equilibrium summation") def feedside_vapor_composition_summation(disj): """ @@ -1625,9 +1736,7 @@ def feedside_vapor_composition_summation(disj): """ return sum(m.y[2, n_tray, comp] for comp in m.comp) - 1 == m.erry[2, n_tray] - - @disj.Constraint(m.comp, - doc="Feed section 2 vapor composition") + @disj.Constraint(m.comp, doc="Feed section 2 vapor composition") def feedside_vapor_composition(disj, comp): """ Vapor composition for the feed side section in the column. @@ -1645,27 +1754,33 @@ def feedside_vapor_composition(disj, comp): The constraint expression that enforces the vapor composition for the feed side section in the column. The equation is derived from the vapor-liquid equilibrium relationship. """ - return m.y[2, n_tray, comp] == m.x[2, n_tray, comp] * ( - m.actv[2, n_tray, comp] * ( - m.prop[comp, 'PC'] * exp( - m.prop[comp, 'TC'] / m.T[2, n_tray] * ( - m.prop[comp, 'vpA'] * \ - (1 - m.T[2, n_tray] / m.prop[comp, 'TC']) - + m.prop[comp, 'vpB'] * \ - (1 - m.T[2, n_tray] / m.prop[comp, 'TC'])**1.5 - + m.prop[comp, 'vpC'] * \ - (1 - m.T[2, n_tray] / m.prop[comp, 'TC'])**3 - + m.prop[comp, 'vpD'] * \ - (1 - m.T[2, n_tray] / m.prop[comp, 'TC'])**6 + return ( + m.y[2, n_tray, comp] + == m.x[2, n_tray, comp] + * ( + m.actv[2, n_tray, comp] + * ( + m.prop[comp, 'PC'] + * exp( + m.prop[comp, 'TC'] + / m.T[2, n_tray] + * ( + m.prop[comp, 'vpA'] + * (1 - m.T[2, n_tray] / m.prop[comp, 'TC']) + + m.prop[comp, 'vpB'] + * (1 - m.T[2, n_tray] / m.prop[comp, 'TC']) ** 1.5 + + m.prop[comp, 'vpC'] + * (1 - m.T[2, n_tray] / m.prop[comp, 'TC']) ** 3 + + m.prop[comp, 'vpD'] + * (1 - m.T[2, n_tray] / m.prop[comp, 'TC']) ** 6 + ) ) ) ) - ) / m.P[2, n_tray] - - + / m.P[2, n_tray] + ) - @disj.Constraint(m.comp, - doc="Feed section 2 liquid enthalpy") + @disj.Constraint(m.comp, doc="Feed section 2 liquid enthalpy") def feedside_liquid_enthalpy(disj, comp): """ Liquid enthalpy for the feed side section in the column. @@ -1683,21 +1798,23 @@ def feedside_liquid_enthalpy(disj, comp): The constraint expression that enforces the liquid enthalpy for the feed side section in the column. """ return m.hl[2, n_tray, comp] == ( - m.cpc[1] * ( - (m.T[2, n_tray] - m.Tref) * \ - m.prop[comp, 'cpA', 1] - + (m.T[2, n_tray]**2 - m.Tref**2) * \ - m.prop[comp, 'cpB', 1] * m.cpc2['A', 1] / 2 - + (m.T[2, n_tray]**3 - m.Tref**3) * \ - m.prop[comp, 'cpC', 1] * m.cpc2['B', 1] / 3 - + (m.T[2, n_tray]**4 - m.Tref**4) * \ - m.prop[comp, 'cpD', 1] / 4 - ) / m.Hscale - ) - - - @disj.Constraint(m.comp, - doc="Feed section 2 vapor enthalpy") + m.cpc[1] + * ( + (m.T[2, n_tray] - m.Tref) * m.prop[comp, 'cpA', 1] + + (m.T[2, n_tray] ** 2 - m.Tref**2) + * m.prop[comp, 'cpB', 1] + * m.cpc2['A', 1] + / 2 + + (m.T[2, n_tray] ** 3 - m.Tref**3) + * m.prop[comp, 'cpC', 1] + * m.cpc2['B', 1] + / 3 + + (m.T[2, n_tray] ** 4 - m.Tref**4) * m.prop[comp, 'cpD', 1] / 4 + ) + / m.Hscale + ) + + @disj.Constraint(m.comp, doc="Feed section 2 vapor enthalpy") def feedside_vapor_enthalpy(disj, comp): """ Vapor enthalpy for the feed side section in the column. @@ -1715,22 +1832,24 @@ def feedside_vapor_enthalpy(disj, comp): The constraint expression that enforces the vapor enthalpy for the feed side section in the column. """ return m.hv[2, n_tray, comp] == ( - m.cpc[2] * ( - (m.T[2, n_tray] - m.Tref) * \ - m.prop[comp, 'cpA', 2] - + (m.T[2, n_tray]**2 - m.Tref**2) * \ - m.prop[comp, 'cpB', 2] * m.cpc2['A', 2] / 2 - + (m.T[2, n_tray]**3 - m.Tref**3) * \ - m.prop[comp, 'cpC', 2] * m.cpc2['B', 2] / 3 - + (m.T[2, n_tray]**4 - m.Tref**4) * \ - m.prop[comp, 'cpD', 2] / 4 - ) / m.Hscale + m.cpc[2] + * ( + (m.T[2, n_tray] - m.Tref) * m.prop[comp, 'cpA', 2] + + (m.T[2, n_tray] ** 2 - m.Tref**2) + * m.prop[comp, 'cpB', 2] + * m.cpc2['A', 2] + / 2 + + (m.T[2, n_tray] ** 3 - m.Tref**3) + * m.prop[comp, 'cpC', 2] + * m.cpc2['B', 2] + / 3 + + (m.T[2, n_tray] ** 4 - m.Tref**4) * m.prop[comp, 'cpD', 2] / 4 + ) + / m.Hscale + m.dHvap[comp] - ) + ) - - @disj.Constraint(m.comp, - doc="Feed section 2 liquid activity coefficient") + @disj.Constraint(m.comp, doc="Feed section 2 liquid activity coefficient") def feedside_activity_coefficient(disj, comp): """ Liquid activity coefficient for the feed side section in the column equal to 1. @@ -1748,12 +1867,9 @@ def feedside_activity_coefficient(disj, comp): The constraint expression that enforces the liquid activity coefficient for the feed side section for each component and tray in the column to be equal to 1. This is an assumption for the feed side section, since the feed is assumed to be ideal. """ - return m.actv[2, n_tray, comp] == 1 - + return m.actv[2, n_tray, comp] == 1 - - def _build_product_side_equations(disj, n_tray): """ Build the equations for the product side section in the column. @@ -1771,8 +1887,7 @@ def _build_product_side_equations(disj, n_tray): """ m = disj.model() - @disj.Constraint(m.comp, - doc="Product section 3 mass per component balances") + @disj.Constraint(m.comp, doc="Product section 3 mass per component balances") def _productside_masspercomponent_balances(disj, comp): """ Mass per component balances for the product side section in the column. @@ -1789,25 +1904,20 @@ def _productside_masspercomponent_balances(disj, comp): Constraint The constraint expression that enforces the mass balance per component in the product side section of the column. """ - return ( - (m.L[3, n_tray + 1, comp] - if n_tray < m.num_trays else 0) - + (m.L[4, 1, comp] * m.dl[3] - if n_tray == m.num_trays else 0) - - m.L[3, n_tray, comp] - + (m.V[1, m.num_trays, comp] * m.dv[3] - if n_tray == 1 else 0) - + (m.V[3, n_tray - 1, comp] - if n_tray > 1 else 0) - - m.V[3, n_tray, comp] - - (m.S[1, comp] - if n_tray == m.sideout1_tray else 0) - - (m.S[2, comp] - if n_tray == m.sideout2_tray else 0) - ==0 - ) + return (m.L[3, n_tray + 1, comp] if n_tray < m.num_trays else 0) + ( + m.L[4, 1, comp] * m.dl[3] if n_tray == m.num_trays else 0 + ) - m.L[3, n_tray, comp] + ( + m.V[1, m.num_trays, comp] * m.dv[3] if n_tray == 1 else 0 + ) + ( + m.V[3, n_tray - 1, comp] if n_tray > 1 else 0 + ) - m.V[ + 3, n_tray, comp + ] - ( + m.S[1, comp] if n_tray == m.sideout1_tray else 0 + ) - ( + m.S[2, comp] if n_tray == m.sideout2_tray else 0 + ) == 0 - @disj.Constraint(doc="Product section 3 energy balances") def _productside_energy_balances(disj): """ @@ -1825,27 +1935,45 @@ def _productside_energy_balances(disj): """ return ( sum( - (m.L[3, n_tray + 1, comp] * m.hl[3, n_tray + 1, comp] - if n_tray < m.num_trays else 0) - + (m.L[4, 1, comp] * m.dl[3] * m.hl[4, 1, comp] - if n_tray == m.num_trays else 0) + ( + m.L[3, n_tray + 1, comp] * m.hl[3, n_tray + 1, comp] + if n_tray < m.num_trays + else 0 + ) + + ( + m.L[4, 1, comp] * m.dl[3] * m.hl[4, 1, comp] + if n_tray == m.num_trays + else 0 + ) - m.L[3, n_tray, comp] * m.hl[3, n_tray, comp] - + (m.V[1, m.num_trays, comp] * m.dv[3] * m.hv[1, m.num_trays, comp] - if n_tray == 1 else 0) - + (m.V[3, n_tray - 1, comp] * m.hv[3, n_tray - 1, comp] - if n_tray > 1 else 0) + + ( + m.V[1, m.num_trays, comp] * m.dv[3] * m.hv[1, m.num_trays, comp] + if n_tray == 1 + else 0 + ) + + ( + m.V[3, n_tray - 1, comp] * m.hv[3, n_tray - 1, comp] + if n_tray > 1 + else 0 + ) - m.V[3, n_tray, comp] * m.hv[3, n_tray, comp] - - (m.S[1, comp] * m.hl[3, n_tray, comp] - if n_tray == m.sideout1_tray else 0) - - (m.S[2, comp] * m.hl[3, n_tray, comp] - if n_tray == m.sideout2_tray else 0) - for comp in m.comp) * m.Qscale - ==0 + - ( + m.S[1, comp] * m.hl[3, n_tray, comp] + if n_tray == m.sideout1_tray + else 0 + ) + - ( + m.S[2, comp] * m.hl[3, n_tray, comp] + if n_tray == m.sideout2_tray + else 0 + ) + for comp in m.comp + ) + * m.Qscale + == 0 ) - - @disj.Constraint(m.comp, - doc="Product section 3 liquid flowrate per component") + @disj.Constraint(m.comp, doc="Product section 3 liquid flowrate per component") def _productside_liquid_percomponent(disj, comp): """ Liquid flowrate per component in the product side section of the column. @@ -1864,9 +1992,7 @@ def _productside_liquid_percomponent(disj, comp): """ return m.L[3, n_tray, comp] == m.Ltotal[3, n_tray] * m.x[3, n_tray, comp] - - @disj.Constraint(m.comp, - doc="Product section 3 vapor flowrate per component") + @disj.Constraint(m.comp, doc="Product section 3 vapor flowrate per component") def _productside_vapor_percomponent(disj, comp): """ Vapor flowrate per component in the product side section of the column. @@ -1883,8 +2009,7 @@ def _productside_vapor_percomponent(disj, comp): Constraint The constraint expression that enforces the vapor flowrate per component in the product side section of the column is equal to the total vapor flowrate times the vapor composition. """ - return m.V[3, n_tray, comp] == m.Vtotal[3, n_tray] * m.y[3, n_tray, comp] - + return m.V[3, n_tray, comp] == m.Vtotal[3, n_tray] * m.y[3, n_tray, comp] @disj.Constraint(doc="Product section 3 liquid composition equilibrium summation") def productside_liquid_composition_summation(disj): @@ -1904,7 +2029,6 @@ def productside_liquid_composition_summation(disj): """ return sum(m.x[3, n_tray, comp] for comp in m.comp) - 1 == m.errx[3, n_tray] - @disj.Constraint(doc="Product section 3 vapor composition equilibrium summation") def productside_vapor_composition_summation(disj): """ @@ -1923,9 +2047,7 @@ def productside_vapor_composition_summation(disj): """ return sum(m.y[3, n_tray, comp] for comp in m.comp) - 1 == m.erry[3, n_tray] - - @disj.Constraint(m.comp, - doc="Product section 3 vapor composition") + @disj.Constraint(m.comp, doc="Product section 3 vapor composition") def productside_vapor_composition(disj, comp): """ Vapor composition for the product side section in the column. @@ -1943,27 +2065,33 @@ def productside_vapor_composition(disj, comp): The constraint expression that enforces the vapor composition for the product side section in the column. The equation is derived from the vapor-liquid equilibrium relationship. """ - return m.y[3, n_tray, comp] == m.x[3, n_tray, comp] * ( - m.actv[3, n_tray, comp] * ( - m.prop[comp, 'PC'] * exp( - m.prop[comp, 'TC'] / m.T[3, n_tray] * ( - m.prop[comp, 'vpA'] * \ - (1 - m.T[3, n_tray]/m.prop[comp, 'TC']) - + m.prop[comp, 'vpB'] * \ - (1 - m.T[3, n_tray]/m.prop[comp, 'TC'])**1.5 - + m.prop[comp, 'vpC'] * \ - (1 - m.T[3, n_tray]/m.prop[comp, 'TC'])**3 - + m.prop[comp, 'vpD'] * \ - (1 - m.T[3, n_tray]/m.prop[comp, 'TC'])**6 + return ( + m.y[3, n_tray, comp] + == m.x[3, n_tray, comp] + * ( + m.actv[3, n_tray, comp] + * ( + m.prop[comp, 'PC'] + * exp( + m.prop[comp, 'TC'] + / m.T[3, n_tray] + * ( + m.prop[comp, 'vpA'] + * (1 - m.T[3, n_tray] / m.prop[comp, 'TC']) + + m.prop[comp, 'vpB'] + * (1 - m.T[3, n_tray] / m.prop[comp, 'TC']) ** 1.5 + + m.prop[comp, 'vpC'] + * (1 - m.T[3, n_tray] / m.prop[comp, 'TC']) ** 3 + + m.prop[comp, 'vpD'] + * (1 - m.T[3, n_tray] / m.prop[comp, 'TC']) ** 6 + ) ) ) ) - ) / m.P[3, n_tray] - - + / m.P[3, n_tray] + ) - @disj.Constraint(m.comp, - doc="Product section 3 liquid enthalpy") + @disj.Constraint(m.comp, doc="Product section 3 liquid enthalpy") def productside_liquid_enthalpy(disj, comp): """ Liquid enthalpy for the product side section in the column. @@ -1981,21 +2109,23 @@ def productside_liquid_enthalpy(disj, comp): The constraint expression that enforces the liquid enthalpy for the product side section in the column. """ return m.hl[3, n_tray, comp] == ( - m.cpc[1] * ( - (m.T[3, n_tray] - m.Tref) * \ - m.prop[comp, 'cpA', 1] - + (m.T[3, n_tray]**2 - m.Tref**2) * \ - m.prop[comp, 'cpB', 1] * m.cpc2['A', 1] / 2 - + (m.T[3, n_tray]**3 - m.Tref**3) * \ - m.prop[comp, 'cpC', 1] * m.cpc2['B', 1] / 3 - + (m.T[3, n_tray]**4 - m.Tref**4) * \ - m.prop[comp, 'cpD', 1] / 4 - ) / m.Hscale - ) - - - @disj.Constraint(m.comp, - doc="Product section 3 vapor enthalpy") + m.cpc[1] + * ( + (m.T[3, n_tray] - m.Tref) * m.prop[comp, 'cpA', 1] + + (m.T[3, n_tray] ** 2 - m.Tref**2) + * m.prop[comp, 'cpB', 1] + * m.cpc2['A', 1] + / 2 + + (m.T[3, n_tray] ** 3 - m.Tref**3) + * m.prop[comp, 'cpC', 1] + * m.cpc2['B', 1] + / 3 + + (m.T[3, n_tray] ** 4 - m.Tref**4) * m.prop[comp, 'cpD', 1] / 4 + ) + / m.Hscale + ) + + @disj.Constraint(m.comp, doc="Product section 3 vapor enthalpy") def productside_vapor_enthalpy(disj, comp): """ Vapor enthalpy for the product side section in the column. @@ -2013,22 +2143,24 @@ def productside_vapor_enthalpy(disj, comp): The constraint expression that enforces the vapor enthalpy for the product side section in the column. """ return m.hv[3, n_tray, comp] == ( - m.cpc[2] * ( - (m.T[3, n_tray] - m.Tref) * \ - m.prop[comp, 'cpA', 2] - + (m.T[3, n_tray]**2 - m.Tref**2) * \ - m.prop[comp, 'cpB', 2] * m.cpc2['A', 2] / 2 - + (m.T[3, n_tray]**3 - m.Tref**3) * \ - m.prop[comp, 'cpC', 2] * m.cpc2['B', 2] / 3 - + (m.T[3, n_tray]**4 - m.Tref**4) * \ - m.prop[comp, 'cpD', 2] / 4 - ) / m.Hscale + m.cpc[2] + * ( + (m.T[3, n_tray] - m.Tref) * m.prop[comp, 'cpA', 2] + + (m.T[3, n_tray] ** 2 - m.Tref**2) + * m.prop[comp, 'cpB', 2] + * m.cpc2['A', 2] + / 2 + + (m.T[3, n_tray] ** 3 - m.Tref**3) + * m.prop[comp, 'cpC', 2] + * m.cpc2['B', 2] + / 3 + + (m.T[3, n_tray] ** 4 - m.Tref**4) * m.prop[comp, 'cpD', 2] / 4 + ) + / m.Hscale + m.dHvap[comp] - ) - + ) - @disj.Constraint(m.comp, - doc="Product section 3 liquid activity coefficient") + @disj.Constraint(m.comp, doc="Product section 3 liquid activity coefficient") def productside_activity_coefficient(disj, comp): """ Liquid activity coefficient for the product side section in the column equal to 1. @@ -2046,12 +2178,9 @@ def productside_activity_coefficient(disj, comp): The constraint expression that enforces the liquid activity coefficient for the product side section for each component and tray in the column to be equal to 1. This is an assumption for the product side section, since the product is assumed to be ideal. """ - return m.actv[3, n_tray, comp] == 1 + return m.actv[3, n_tray, comp] == 1 - - - def _build_top_equations(disj, n_tray): """ Build the equations for the top section in the column. @@ -2069,8 +2198,7 @@ def _build_top_equations(disj, n_tray): """ m = disj.model() - @disj.Constraint(m.comp, - doc="Top section 4 mass per component balances") + @disj.Constraint(m.comp, doc="Top section 4 mass per component balances") def _top_mass_percomponent_balances(disj, comp): """ Mass per component balances for the top section in the column. @@ -2087,28 +2215,21 @@ def _top_mass_percomponent_balances(disj, comp): Constraint The constraint expression that enforces the mass balance per component in the top section of the column. """ - return ( - (m.L[4, n_tray + 1, comp] - if n_tray < m.con_tray else 0) - - (m.L[4, n_tray, comp] * m.dl[2] - if n_tray == 1 else 0) - - (m.L[4, n_tray, comp] * m.dl[3] - if n_tray == 1 else 0) - - (m.L[4, n_tray, comp] - if n_tray > 1 else 0) - + (m.V[2, m.num_trays, comp] - if n_tray == 1 else 0) - + (m.V[3, m.num_trays, comp] - if n_tray == 1 else 0) - + (m.V[4, n_tray - 1, comp] - if n_tray > 1 else 0) - - (m.V[4, n_tray, comp] - if n_tray < m.con_tray else 0) - - (m.D[comp] - if n_tray == m.con_tray else 0) - ==0 - ) - + return (m.L[4, n_tray + 1, comp] if n_tray < m.con_tray else 0) - ( + m.L[4, n_tray, comp] * m.dl[2] if n_tray == 1 else 0 + ) - (m.L[4, n_tray, comp] * m.dl[3] if n_tray == 1 else 0) - ( + m.L[4, n_tray, comp] if n_tray > 1 else 0 + ) + ( + m.V[2, m.num_trays, comp] if n_tray == 1 else 0 + ) + ( + m.V[3, m.num_trays, comp] if n_tray == 1 else 0 + ) + ( + m.V[4, n_tray - 1, comp] if n_tray > 1 else 0 + ) - ( + m.V[4, n_tray, comp] if n_tray < m.con_tray else 0 + ) - ( + m.D[comp] if n_tray == m.con_tray else 0 + ) == 0 @disj.Constraint(doc="Top scetion 4 energy balances") def _top_energy_balances(disj): @@ -2127,32 +2248,51 @@ def _top_energy_balances(disj): """ return ( sum( - (m.L[4, n_tray + 1, comp] * m.hl[4, n_tray + 1, comp] - if n_tray < m.con_tray else 0) - - (m.L[4, n_tray, comp] * m.dl[2] * m.hl[4, n_tray, comp] - if n_tray == 1 else 0) - - (m.L[4, n_tray, comp] * m.dl[3] * m.hl[4, n_tray, comp] - if n_tray == 1 else 0) - - (m.L[4, n_tray, comp] * m.hl[4, n_tray, comp] - if n_tray > 1 else 0) - + (m.V[2, m.num_trays, comp] * m.hv[2, m.num_trays, comp] - if n_tray == 1 else 0) - + (m.V[3, m.num_trays, comp] * m.hv[3, m.num_trays, comp] - if n_tray == 1 else 0) - + (m.V[4, n_tray - 1, comp] * m.hv[4, n_tray - 1, comp] - if n_tray > 1 else 0) - - (m.V[4, n_tray, comp] * m.hv[4, n_tray, comp] - if n_tray < m.con_tray else 0) - - (m.D[comp] * m.hl[4, n_tray, comp] - if n_tray == m.con_tray else 0) - for comp in m.comp) * m.Qscale + ( + m.L[4, n_tray + 1, comp] * m.hl[4, n_tray + 1, comp] + if n_tray < m.con_tray + else 0 + ) + - ( + m.L[4, n_tray, comp] * m.dl[2] * m.hl[4, n_tray, comp] + if n_tray == 1 + else 0 + ) + - ( + m.L[4, n_tray, comp] * m.dl[3] * m.hl[4, n_tray, comp] + if n_tray == 1 + else 0 + ) + - (m.L[4, n_tray, comp] * m.hl[4, n_tray, comp] if n_tray > 1 else 0) + + ( + m.V[2, m.num_trays, comp] * m.hv[2, m.num_trays, comp] + if n_tray == 1 + else 0 + ) + + ( + m.V[3, m.num_trays, comp] * m.hv[3, m.num_trays, comp] + if n_tray == 1 + else 0 + ) + + ( + m.V[4, n_tray - 1, comp] * m.hv[4, n_tray - 1, comp] + if n_tray > 1 + else 0 + ) + - ( + m.V[4, n_tray, comp] * m.hv[4, n_tray, comp] + if n_tray < m.con_tray + else 0 + ) + - (m.D[comp] * m.hl[4, n_tray, comp] if n_tray == m.con_tray else 0) + for comp in m.comp + ) + * m.Qscale - (m.Qcon if n_tray == m.con_tray else 0) - ==0 + == 0 ) - - @disj.Constraint(m.comp, - doc="Top section 4 liquid flowrate per component") + @disj.Constraint(m.comp, doc="Top section 4 liquid flowrate per component") def _top_liquid_percomponent(disj, comp): """ Liquid flowrate per component in the top section of the column. @@ -2171,9 +2311,7 @@ def _top_liquid_percomponent(disj, comp): """ return m.L[4, n_tray, comp] == m.Ltotal[4, n_tray] * m.x[4, n_tray, comp] - - @disj.Constraint(m.comp, - doc="Top section 4 vapor flowrate per component") + @disj.Constraint(m.comp, doc="Top section 4 vapor flowrate per component") def _top_vapor_percomponent(disj, comp): """ Vapor flowrate per component in the top section of the column. @@ -2190,8 +2328,7 @@ def _top_vapor_percomponent(disj, comp): Constraint The constraint expression that enforces the vapor flowrate per component in the top section of the column is equal to the total vapor flowrate times the vapor composition. """ - return m.V[4, n_tray, comp] == m.Vtotal[4, n_tray] * m.y[4, n_tray, comp] - + return m.V[4, n_tray, comp] == m.Vtotal[4, n_tray] * m.y[4, n_tray, comp] @disj.Constraint(doc="Top section 4 liquid composition equilibrium summation") def top_liquid_composition_summation(disj): @@ -2211,7 +2348,6 @@ def top_liquid_composition_summation(disj): """ return sum(m.x[4, n_tray, comp] for comp in m.comp) - 1 == m.errx[4, n_tray] - @disj.Constraint(doc="Top section 4 vapor composition equilibrium summation") def top_vapor_composition_summation(disj): """ @@ -2230,9 +2366,7 @@ def top_vapor_composition_summation(disj): """ return sum(m.y[4, n_tray, comp] for comp in m.comp) - 1 == m.erry[4, n_tray] - - @disj.Constraint(m.comp, - doc="Top scetion 4 vapor composition") + @disj.Constraint(m.comp, doc="Top scetion 4 vapor composition") def top_vapor_composition(disj, comp): """ Vapor composition for the top section in the column. @@ -2250,27 +2384,33 @@ def top_vapor_composition(disj, comp): The constraint expression that enforces the vapor composition for the top section in the column. The equation is derived from the vapor-liquid equilibrium relationship. """ - return m.y[4, n_tray, comp] == m.x[4, n_tray, comp] * ( - m.actv[4, n_tray, comp] * ( - m.prop[comp, 'PC'] * exp( - m.prop[comp, 'TC'] / m.T[4, n_tray] * ( - m.prop[comp, 'vpA'] * \ - (1 - m.T[4, n_tray]/m.prop[comp, 'TC']) - + m.prop[comp, 'vpB'] * \ - (1 - m.T[4, n_tray]/m.prop[comp, 'TC'])**1.5 - + m.prop[comp, 'vpC'] * \ - (1 - m.T[4, n_tray]/m.prop[comp, 'TC'])**3 - + m.prop[comp, 'vpD'] * \ - (1 - m.T[4, n_tray]/m.prop[comp, 'TC'])**6 + return ( + m.y[4, n_tray, comp] + == m.x[4, n_tray, comp] + * ( + m.actv[4, n_tray, comp] + * ( + m.prop[comp, 'PC'] + * exp( + m.prop[comp, 'TC'] + / m.T[4, n_tray] + * ( + m.prop[comp, 'vpA'] + * (1 - m.T[4, n_tray] / m.prop[comp, 'TC']) + + m.prop[comp, 'vpB'] + * (1 - m.T[4, n_tray] / m.prop[comp, 'TC']) ** 1.5 + + m.prop[comp, 'vpC'] + * (1 - m.T[4, n_tray] / m.prop[comp, 'TC']) ** 3 + + m.prop[comp, 'vpD'] + * (1 - m.T[4, n_tray] / m.prop[comp, 'TC']) ** 6 + ) ) ) ) - ) / m.P[4, n_tray] - - + / m.P[4, n_tray] + ) - @disj.Constraint(m.comp, - doc="Top section 4 liquid enthalpy") + @disj.Constraint(m.comp, doc="Top section 4 liquid enthalpy") def top_liquid_enthalpy(disj, comp): """ Liquid enthalpy for the top section in the column. @@ -2288,21 +2428,23 @@ def top_liquid_enthalpy(disj, comp): The constraint expression that enforces the liquid enthalpy for the top section in the column. """ return m.hl[4, n_tray, comp] == ( - m.cpc[1] * ( - (m.T[4, n_tray] - m.Tref) * \ - m.prop[comp, 'cpA', 1] - + (m.T[4, n_tray]**2 - m.Tref**2) * \ - m.prop[comp, 'cpB', 1] * m.cpc2['A', 1] / 2 - + (m.T[4, n_tray]**3 - m.Tref**3) * \ - m.prop[comp, 'cpC', 1] * m.cpc2['B', 1] / 3 - + (m.T[4, n_tray]**4 - m.Tref**4) * \ - m.prop[comp, 'cpD', 1] / 4 - ) / m.Hscale - ) - - - @disj.Constraint(m.comp, - doc="Top section 4 vapor enthalpy") + m.cpc[1] + * ( + (m.T[4, n_tray] - m.Tref) * m.prop[comp, 'cpA', 1] + + (m.T[4, n_tray] ** 2 - m.Tref**2) + * m.prop[comp, 'cpB', 1] + * m.cpc2['A', 1] + / 2 + + (m.T[4, n_tray] ** 3 - m.Tref**3) + * m.prop[comp, 'cpC', 1] + * m.cpc2['B', 1] + / 3 + + (m.T[4, n_tray] ** 4 - m.Tref**4) * m.prop[comp, 'cpD', 1] / 4 + ) + / m.Hscale + ) + + @disj.Constraint(m.comp, doc="Top section 4 vapor enthalpy") def top_vapor_enthalpy(disj, comp): """ Vapor enthalpy for the top section in the column. @@ -2320,22 +2462,24 @@ def top_vapor_enthalpy(disj, comp): The constraint expression that enforces the vapor enthalpy for the top section in the column. """ return m.hv[4, n_tray, comp] == ( - m.cpc[2] * ( - (m.T[4, n_tray] - m.Tref) * \ - m.prop[comp, 'cpA', 2] - + (m.T[4, n_tray]**2 - m.Tref**2) * \ - m.prop[comp, 'cpB', 2] * m.cpc2['A', 2] / 2 - + (m.T[4, n_tray]**3 - m.Tref**3) * \ - m.prop[comp, 'cpC', 2] * m.cpc2['B', 2] / 3 - + (m.T[4, n_tray]**4 - m.Tref**4) * \ - m.prop[comp, 'cpD', 2] / 4 - ) / m.Hscale + m.cpc[2] + * ( + (m.T[4, n_tray] - m.Tref) * m.prop[comp, 'cpA', 2] + + (m.T[4, n_tray] ** 2 - m.Tref**2) + * m.prop[comp, 'cpB', 2] + * m.cpc2['A', 2] + / 2 + + (m.T[4, n_tray] ** 3 - m.Tref**3) + * m.prop[comp, 'cpC', 2] + * m.cpc2['B', 2] + / 3 + + (m.T[4, n_tray] ** 4 - m.Tref**4) * m.prop[comp, 'cpD', 2] / 4 + ) + / m.Hscale + m.dHvap[comp] - ) + ) - - @disj.Constraint(m.comp, - doc="Top section 4 liquid activity coefficient") + @disj.Constraint(m.comp, doc="Top section 4 liquid activity coefficient") def top_activity_coefficient(disj, comp): """ Liquid activity coefficient for the top section in the column equal to 1. @@ -2353,11 +2497,9 @@ def top_activity_coefficient(disj, comp): The constraint expression that enforces the liquid activity coefficient for the top section for each component and tray in the column to be equal to 1. This is an assumption for the top section, since the product is assumed to be ideal. """ - return m.actv[4, n_tray, comp] == 1 + return m.actv[4, n_tray, comp] == 1 - - def _build_pass_through_eqns(disj, sec, n_tray): """ Build the equations for the pass through section in the column when a given tray in the disjunct is not active if it is the first or last tray. @@ -2379,10 +2521,9 @@ def _build_pass_through_eqns(disj, sec, n_tray): # If the tray is the first or last tray, then the liquid and vapor flowrates, compositions, enthalpies, and temperature are passed through. if n_tray == 1 or n_tray == m.num_trays: - return - - @disj.Constraint(m.comp, - doc="Pass through liquid flowrate") + return + + @disj.Constraint(m.comp, doc="Pass through liquid flowrate") def pass_through_liquid_flowrate(disj, comp): """ Pass through liquid flowrate for the given tray in the column. @@ -2402,9 +2543,7 @@ def pass_through_liquid_flowrate(disj, comp): """ return m.L[sec, n_tray, comp] == m.L[sec, n_tray + 1, comp] - - @disj.Constraint(m.comp, - doc="Pass through vapor flowrate") + @disj.Constraint(m.comp, doc="Pass through vapor flowrate") def pass_through_vapor_flowrate(disj, comp): """ Pass through vapor flowrate for the given tray in the column. @@ -2423,9 +2562,7 @@ def pass_through_vapor_flowrate(disj, comp): """ return m.V[sec, n_tray, comp] == m.V[sec, n_tray - 1, comp] - - @disj.Constraint(m.comp, - doc="Pass through liquid composition") + @disj.Constraint(m.comp, doc="Pass through liquid composition") def pass_through_liquid_composition(disj, comp): """ Pass through liquid composition for the given tray in the column. @@ -2444,9 +2581,7 @@ def pass_through_liquid_composition(disj, comp): """ return m.x[sec, n_tray, comp] == m.x[sec, n_tray + 1, comp] - - @disj.Constraint(m.comp, - doc="Pass through vapor composition") + @disj.Constraint(m.comp, doc="Pass through vapor composition") def pass_through_vapor_composition(disj, comp): """ Pass through vapor composition for the given tray in the column. @@ -2465,9 +2600,7 @@ def pass_through_vapor_composition(disj, comp): """ return m.y[sec, n_tray, comp] == m.y[sec, n_tray + 1, comp] - - @disj.Constraint(m.comp, - doc="Pass through liquid enthalpy") + @disj.Constraint(m.comp, doc="Pass through liquid enthalpy") def pass_through_liquid_enthalpy(disj, comp): """ Pass through liquid enthalpy for the given tray in the column. @@ -2486,9 +2619,7 @@ def pass_through_liquid_enthalpy(disj, comp): """ return m.hl[sec, n_tray, comp] == m.hl[sec, n_tray + 1, comp] - - @disj.Constraint(m.comp, - doc="Pass through vapor enthalpy") + @disj.Constraint(m.comp, doc="Pass through vapor enthalpy") def pass_through_vapor_enthalpy(disj, comp): """ Pass through vapor enthalpy for the given tray in the column. @@ -2507,7 +2638,6 @@ def pass_through_vapor_enthalpy(disj, comp): """ return m.hv[sec, n_tray, comp] == m.hv[sec, n_tray - 1, comp] - @disj.Constraint(doc="Pass through temperature") def pass_through_temperature(disj): """ @@ -2524,10 +2654,7 @@ def pass_through_temperature(disj): The constraint expression that enforces the temperature for the given tray is equal to the temperature for the tray below it. """ return m.T[sec, n_tray] == m.T[sec, n_tray - 1] - - - + if __name__ == "__main__": model = build_model() - From 0c500440cac7e011fc303714152cf9ed8323180a Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 21 May 2024 14:17:53 -0400 Subject: [PATCH 19/79] Managed the comments. --- gdplib/small_batch/gdp_small_batch.py | 85 +++------------------------ 1 file changed, 7 insertions(+), 78 deletions(-) diff --git a/gdplib/small_batch/gdp_small_batch.py b/gdplib/small_batch/gdp_small_batch.py index b9cdce5..314cdff 100644 --- a/gdplib/small_batch/gdp_small_batch.py +++ b/gdplib/small_batch/gdp_small_batch.py @@ -2,9 +2,11 @@ gdp_small_batch.py The gdp_small_batch.py module contains the GDP model for the small batch problem based on the Kocis and Grossmann (1988) paper. The problem is based on the Example 4 of the paper. +The objective is to minimize the investment cost of the batch units. References: - - Kocis, G. R.; Grossmann, I. E. Global Optimization of Nonconvex Mixed-Integer Nonlinear Programming (MINLP) Problems in Process Synthesis. Ind. Eng. Chem. Res. 1988, 27 (8), 1407–1421. + [1] Kocis, G. R.; Grossmann, I. E. Global Optimization of Nonconvex Mixed-Integer Nonlinear Programming (MINLP) Problems in Process Synthesis. Ind. Eng. Chem. Res. 1988, 27 (8), 1407-1421. https://doi.org/10.1021/ie00080a013 + [2] Ovalle, D., Liñán, D. A., Lee, A., Gómez, J. M., Ricardez-Sandoval, L., Grossmann, I. E., & Neira, D. E. B. (2024). Logic-Based Discrete-Steepest Descent: A Solution Method for Process Synthesis Generalized Disjunctive Programs. arXiv preprint arXiv:2405.05358. https://doi.org/10.48550/arXiv.2405.05358 """ import os @@ -22,7 +24,8 @@ def build_small_batch(): The function build the GDP model for the small batch problem. References: - - Kocis, G. R.; Grossmann, I. E. Global Optimization of Nonconvex Mixed-Integer Nonlinear Programming (MINLP) Problems in Process Synthesis. Ind. Eng. Chem. Res. 1988, 27 (8), 1407–1421. + [1] Kocis, G. R.; Grossmann, I. E. Global Optimization of Nonconvex Mixed-Integer Nonlinear Programming (MINLP) Problems in Process Synthesis. Ind. Eng. Chem. Res. 1988, 27 (8), 1407-1421. https://doi.org/10.1021/ie00080a013 + [2] Ovalle, D., Liñán, D. A., Lee, A., Gómez, J. M., Ricardez-Sandoval, L., Grossmann, I. E., & Neira, D. E. B. (2024). Logic-Based Discrete-Steepest Descent: A Solution Method for Process Synthesis Generalized Disjunctive Programs. arXiv preprint arXiv:2405.05358. https://doi.org/10.48550/arXiv.2405.05358 Args: None @@ -366,20 +369,8 @@ def external_ref(m, x, logic_expr=None): for j in m.j: if k == ext_var[j]: m.Y[k, j].fix(True) - # m.Y_exists[k, j].indicator_var.fix( - # True - # ) # Is this necessary?: m.Y_exists[k, j].indicator_var.fix(True). - # m.Y_not_exists[k, j].indicator_var.fix( - # False - # ) # Is this necessary?: m.Y_not_exists[k, j].indicator_var.fix(True), else: m.Y[k, j].fix(False) - # m.Y_exists[k, j].indicator_var.fix( - # False - # ) # Is this necessary?: m.Y_exists[k, j].indicator_var.fix(True), - # m.Y_not_exists[k, j].indicator_var.fix( - # True - # ) # Is this necessary?: m.Y_not_exists[k, j].indicator_var.fix(True), pe.TransformationFactory('core.logical_to_linear').apply_to(m) pe.TransformationFactory('gdp.fix_disjuncts').apply_to(m) @@ -390,71 +381,9 @@ def external_ref(m, x, logic_expr=None): return m -def solve_with_minlp(m, transformation='bigm', minlp='baron', timelimit=10): - """ - Solve the GDP optimization problem with a MINLP solver. - The function applies the big-M Reformulation on the GDP and solve the MINLP problem with BARON. - - Args: - m (pyomo.ConcreteModel): GDP optimization model - transformation (str, optional): Reformulation applied to the GDP. - minlp (str, optional): MINLP solver. - timelimit (float, optional): Time limit for the MINLP solver. - - Returns: - m (pyomo.ConcreteModel): GDP optimization model with the solution. - """ - # Transformation step - pe.TransformationFactory('core.logical_to_linear').apply_to(m) - transformation_string = 'gdp.' + transformation - pe.TransformationFactory(transformation_string).apply_to(m) - - # Solution step - dir_path = os.path.dirname(os.path.abspath(__file__)) - gams_path = os.path.join(dir_path, "gamsfiles/") - if not (os.path.exists(gams_path)): - print( - 'Directory for automatically generated files ' - + gams_path - + ' does not exist. We will create it' - ) - os.makedirs(gams_path) - - solvername = 'gams' - opt = SolverFactory(solvername, solver=minlp) - m.results = opt.solve( - m, - tee=True, - # Uncomment the following lines if you want to save GAMS models - # keepfiles=True, - # tmpdir=gams_path, - # symbolic_solver_labels=True, - add_options=[ - 'option reslim = ' + str(timelimit) + ';' - 'option optcr = 0.0;' - # Uncomment the following lines to setup IIS computation of BARON through option file - # 'GAMS_MODEL.optfile = 1;' - # '\n' - # '$onecho > baron.opt \n' - # 'CompIIS 1 \n' - # '$offecho' - # 'display(execError);' - ], - ) - update_boolean_vars_from_binary(m) - return m if __name__ == "__main__": m = build_small_batch() - m_solved = solve_with_minlp(m, transformation='bigm', minlp='baron', timelimit=120) - - # EXTERNAL REF TEST (this thest can be deleted) - newmodel = external_ref(m, [1, 2, 3], logic_expr=None) - # print('External Ref Test') - # print('Y[1, mixer] = ', newmodel.Y[1, 'mixer'].value) - # print('Y_exists[1, mixer] = ', newmodel.Y_exists[1, 'mixer'].indicator_var.value) - # print('Y_not_exists[1, mixer] = ', newmodel.Y_not_exists[1, 'mixer'].indicator_var.value) - # print('Y[2, mixer] = ', newmodel.Y[2, 'mixer'].value) - # print('Y_exists[2, mixer] = ', newmodel.Y_exists[2, 'mixer'].indicator_var.value) - # print('Y_not_exists[2, mixer] = ', newmodel.Y_not_exists[2, 'mixer'].indicator_var.value) + + From 80903ee7ffab0c49fe6b968e103d5786c22120ed Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 21 May 2024 15:57:33 -0400 Subject: [PATCH 20/79] Numpy Documentation Style --- gdplib/small_batch/gdp_small_batch.py | 324 ++++++++++++++------------ 1 file changed, 175 insertions(+), 149 deletions(-) diff --git a/gdplib/small_batch/gdp_small_batch.py b/gdplib/small_batch/gdp_small_batch.py index 314cdff..3961bdd 100644 --- a/gdplib/small_batch/gdp_small_batch.py +++ b/gdplib/small_batch/gdp_small_batch.py @@ -10,7 +10,7 @@ """ import os -import pyomo.environ as pe +import pyomo.environ as pyo from pyomo.core.base.misc import display from pyomo.core.plugins.transform.logical_to_linear import ( update_boolean_vars_from_binary, @@ -21,58 +21,58 @@ def build_small_batch(): """ - The function build the GDP model for the small batch problem. + Build the GDP model for the small batch problem. - References: - [1] Kocis, G. R.; Grossmann, I. E. Global Optimization of Nonconvex Mixed-Integer Nonlinear Programming (MINLP) Problems in Process Synthesis. Ind. Eng. Chem. Res. 1988, 27 (8), 1407-1421. https://doi.org/10.1021/ie00080a013 - [2] Ovalle, D., Liñán, D. A., Lee, A., Gómez, J. M., Ricardez-Sandoval, L., Grossmann, I. E., & Neira, D. E. B. (2024). Logic-Based Discrete-Steepest Descent: A Solution Method for Process Synthesis Generalized Disjunctive Programs. arXiv preprint arXiv:2405.05358. https://doi.org/10.48550/arXiv.2405.05358 + Returns + ------- + m : Pyomo.ConcreteModel + The GDP model for the small batch problem is created. - Args: - None - - Returns: - m (pyomo.ConcreteModel): The GDP model for the small batch problem is created. + References + ---------- + [1] Kocis, G. R.; Grossmann, I. E. (1988). Global Optimization of Nonconvex Mixed-Integer Nonlinear Programming (MINLP) Problems in Process Synthesis. Ind. Eng. Chem. Res., 27(8), 1407-1421. https://doi.org/10.1021/ie00080a013 + [2] Ovalle, D., Liñán, D. A., Lee, A., Gómez, J. M., Ricardez-Sandoval, L., Grossmann, I. E., & Neira, D. E. B. (2024). Logic-Based Discrete-Steepest Descent: A Solution Method for Process Synthesis Generalized Disjunctive Programs. arXiv preprint arXiv:2405.05358. https://doi.org/10.48550/arXiv.2405.05358 """ NK = 3 # Model - m = pe.ConcreteModel() + m = pyo.ConcreteModel() # Sets - m.i = pe.Set( + m.i = pyo.Set( initialize=['a', 'b'], doc='Set of products' ) # Set of products, i = a, b - m.j = pe.Set( + m.j = pyo.Set( initialize=['mixer', 'reactor', 'centrifuge'] ) # Set of stages, j = mixer, reactor, centrifuge - m.k = pe.RangeSet(NK) # Set of potential number of parallel units, k = 1, 2, 3 + m.k = pyo.RangeSet(NK, doc="Set of potential number of parallel units") # Set of potential number of parallel units, k = 1, 2, 3 # Parameters and Scalars - m.h = pe.Param( + m.h = pyo.Param( initialize=6000, doc='Horizon time [hr]' ) # Horizon time (available time) [hr] - m.vlow = pe.Param( + m.vlow = pyo.Param( initialize=250, doc='Lower bound for size of batch unit [L]' ) # Lower bound for size of batch unit [L] - m.vupp = pe.Param( + m.vupp = pyo.Param( initialize=2500, doc='Upper bound for size of batch unit [L]' ) # Upper bound for size of batch unit [L] # Demand of product i - m.q = pe.Param( + m.q = pyo.Param( m.i, initialize={'a': 200000, 'b': 150000}, doc='Production rate of the product [kg]', ) # Cost coefficient for batch units - m.alpha = pe.Param( + m.alpha = pyo.Param( m.j, initialize={'mixer': 250, 'reactor': 500, 'centrifuge': 340}, doc='Cost coefficient for batch units [$/L^beta*No. of units]]', ) # Cost exponent for batch units - m.beta = pe.Param( + m.beta = pyo.Param( m.j, initialize={'mixer': 0.6, 'reactor': 0.6, 'centrifuge': 0.6}, doc='Cost exponent for batch units', @@ -82,17 +82,22 @@ def coeff_init(m, k): """ Coefficient for number of parallel units. - Args: - m (pyomo.ConcreteModel): small batch GDP model - k (int): number of parallel units - - Returns: - Coefficient for number of parallel units. + Parameters + ---------- + m : Pyomo.ConcreteModel + The small batch GDP model. + k : int + The number of parallel units. + + Returns + ------- + pyomo.Param + Coefficient for number of parallel units. logarithm of k is applied to convexify the model. """ - return pe.log(k) + return pyo.log(k) # Represent number of parallel units - m.coeff = pe.Param( + m.coeff = pyo.Param( m.k, initialize=coeff_init, doc='Coefficient for number of parallel units' ) @@ -106,7 +111,7 @@ def coeff_init(m, k): } # Size factor for product i in stage j [kg/L] - m.s = pe.Param( + m.s = pyo.Param( m.i, m.j, initialize=s_init, doc='Size factor for product i in stage j [kg/L]' ) @@ -120,34 +125,34 @@ def coeff_init(m, k): } # Processing time of product i in batch j [hr] - m.t = pe.Param( + m.t = pyo.Param( m.i, m.j, initialize=t_init, doc='Processing time of product i in batch j [hr]' ) # Variables - m.Y = pe.BooleanVar(m.k, m.j, doc='Stage existence') # Stage existence - m.coeffval = pe.Var( + m.Y = pyo.BooleanVar(m.k, m.j, doc='Stage existence') # Stage existence + m.coeffval = pyo.Var( m.k, m.j, - within=pe.NonNegativeReals, - bounds=(0, pe.log(NK)), + within=pyo.NonNegativeReals, + bounds=(0, pyo.log(NK)), doc='Activation of Coefficient', ) # Activation of coeff - m.v = pe.Var( + m.v = pyo.Var( m.j, - within=pe.NonNegativeReals, - bounds=(pe.log(m.vlow), pe.log(m.vupp)), + within=pyo.NonNegativeReals, + bounds=(pyo.log(m.vlow), pyo.log(m.vupp)), doc='Colume of stage j [L]', ) # Volume of stage j [L] - m.b = pe.Var( - m.i, within=pe.NonNegativeReals, doc='Batch size of product i [L]' + m.b = pyo.Var( + m.i, within=pyo.NonNegativeReals, doc='Batch size of product i [L]' ) # Batch size of product i [L] - m.tl = pe.Var( - m.i, within=pe.NonNegativeReals, doc='Cycle time of product i [hr]' + m.tl = pyo.Var( + m.i, within=pyo.NonNegativeReals, doc='Cycle time of product i [hr]' ) # Cycle time of product i [hr] # Number of units in parallel stage j - m.n = pe.Var( - m.j, within=pe.NonNegativeReals, doc='Number of units in parallel stage j' + m.n = pyo.Var( + m.j, within=pyo.NonNegativeReals, doc='Number of units in parallel stage j' ) # Constraints @@ -160,15 +165,21 @@ def vol(m, i, j): Equation: v_j \geq log(s_ij) + b_i for i = a, b and j = mixer, reactor, centrifuge - Args: - m (pyomo.ConcreteModel): small batch GDP model - i (str): product - j (str): stage - - Returns: + Parameters + ---------- + m : pyomo.ConcreteModel + The small batch GDP model. + i : str + Index of Product. + j : str + Stage. + + Returns + ------- + Pyomo.Constraint Algebraic Constraint """ - return m.v[j] >= pe.log(m.s[i, j]) + m.b[i] + return m.v[j] >= pyo.log(m.s[i, j]) + m.b[i] # Cycle time for each product i @m.Constraint(m.i, m.j) @@ -178,15 +189,21 @@ def cycle(m, i, j): Equation: n_j + tl_i \geq log(t_ij) for i = a, b and j = mixer, reactor, centrifuge - Args: - m (pyomo.ConcreteModel): small batch GDP model - i (str): product - j (str): stage - - Returns: + Parameters + ---------- + m : pyomo.ConcreteModel + The small batch GDP model. + i : str + Index of Product. + j : str + Index of Stage. + + Returns + ------- + Pyomo.Constraint Algebraic Constraint """ - return m.n[j] + m.tl[i] >= pe.log(m.t[i, j]) + return m.n[j] + m.tl[i] >= pyo.log(m.t[i, j]) # Constraint for production time @m.Constraint() @@ -196,13 +213,17 @@ def time(m): Equation: \sum_{i \in I} q_i * \exp(tl_i - b_i) \leq h - Args: - m (pyomo.ConcreteModel): small batch GDP model + Parameters + ---------- + m : pyomo.ConcreteModel + The small batch GDP model. - Returns: + Returns + ------- + Pyomo.Constraint Algebraic Constraint """ - return sum(m.q[i] * pe.exp(m.tl[i] - m.b[i]) for i in m.i) <= m.h + return sum(m.q[i] * pyo.exp(m.tl[i] - m.b[i]) for i in m.i) <= m.h # Relating number of units to 0-1 variables @m.Constraint(m.j) @@ -212,12 +233,16 @@ def units(m, j): Equation: n_j = \sum_{k \in K} coeffval_{k,j} for j = mixer, reactor, centrifuge - Args: - m (pyomo.ConcreteModel): small batch GDP model - j (str): stage - k (int): number of parallel units + Parameters + ---------- + m : pyomo.ConcreteModel + The small batch GDP model. + j : str + Index of Stage. - Returns: + Returns + ------- + Pyomo.Constraint Algebraic Constraint """ return m.n[j] == sum(m.coeffval[k, j] for k in m.k) @@ -230,28 +255,39 @@ def lim(m, j): Equation: \sum_{k \in K} Y_{k,j} = 1 for j = mixer, reactor, centrifuge - Args: - m (pyomo.ConcreteModel): small batch GDP model - j (str): stage + Parameters + ---------- + m : pyomo.ConcreteModel + The small batch GDP model. + j : str + Index of Stage. - Returns: - Logical Constraint + Returns + ------- + Pyomo.LogicalConstraint + Algebraic Constraint """ - return pe.exactly(1, m.Y[1, j], m.Y[2, j], m.Y[3, j]) + return pyo.exactly(1, m.Y[1, j], m.Y[2, j], m.Y[3, j]) # _______ Disjunction_________ def build_existence_equations(disjunct, k, j): """ - Build the Logic Proposition (euqations) for the existence of the stage. - - Args: - disjunct (pyomo.gdp.Disjunct): Disjunct block - k (int): number of parallel units - j (str): stage - - Returns: - None, the proposition is built inside the function + Build the Logic Disjunct Constraints (equation) for the existence of the stage. + + Parameters + ---------- + disjunct : Pyomo.Disjunct + Disjunct block for the existence of the stage. + k : int + Number of parallel units. + j : str + Index of Stage. + + Returns + ------- + None + None, the constraints are built inside the disjunct. """ m = disjunct.model() @@ -262,25 +298,35 @@ def coeffval_act(disjunct): Coeffval activation. m.coeffval[k,j] = m.coeff[k] = log(k) - Args: - disjunct (pyomo.gdp.Disjunct): Disjunct block + Parameters + ---------- + disjunct : Pyomo.Disjunct + Disjunct block for the existence of the stage. - Returns: - Logical Constraint + Returns + ------- + Pyomo.Constraint + A algebraic constraint """ return m.coeffval[k, j] == m.coeff[k] def build_not_existence_equations(disjunct, k, j): """ - Build the Logic Proposition (euqations) for the unexistence of the stage. - - Args: - disjunct (pyomo.gdp.Disjunct): Disjunct block - k (int): number of parallel units - j (str): stage - - Returns: - None, the proposition is built inside the function. + Build the Logic Disjunct Constraints (equations) for the absence of the stage. + + Parameters + ---------- + disjunct : Pyomo.Disjunct + Disjunct block for the absence of the stage. + k : int + Number of parallel units. + j : str + Index of Stage. + + Returns + ------- + None + None, the constraints are built inside the disjunct.. """ m = disjunct.model() @@ -291,8 +337,10 @@ def coeffval_deact(disjunct): Coeffval deactivation. m.coeffval[k,j] = 0 - Args: - disjunct (pyomo.gdp.Disjunct): Disjunct block + Parameters + ---------- + disjunct : Pyomo.Disjunct + Disjunct block for the absence of the stage. Returns: Logical Constraint @@ -300,8 +348,8 @@ def coeffval_deact(disjunct): return m.coeffval[k, j] == 0 # Create disjunction block - m.Y_exists = Disjunct(m.k, m.j, rule=build_existence_equations) - m.Y_not_exists = Disjunct(m.k, m.j, rule=build_not_existence_equations) + m.Y_exists = Disjunct(m.k, m.j, rule=build_existence_equations, doc="Existence of the stage") + m.Y_not_exists = Disjunct(m.k, m.j, rule=build_not_existence_equations, doc="Absence of the stage") # Create disjunction @@ -310,13 +358,19 @@ def Y_exists_or_not(m, k, j): """ Build the Logical Disjunctions of the GDP model for the small batch problem. - Args: - m (pyomo.ConcreteModel): small batch GDP model - k (int): number of parallel units - j (str): stage - - Returns: - Y_exists_or_not (list): List of disjuncts + Parameters + ---------- + m : pyomo.ConcreteModel + The small batch GDP model. + k : int + Number of parallel units. + j : str + Index of Stage. + + Returns + ------- + list + List of disjuncts. The disjunction is between the existence and absence of the stage. """ return [m.Y_exists[k, j], m.Y_not_exists[k, j]] @@ -330,60 +384,32 @@ def Y_exists_or_not(m, k, j): # Objective def obj_rule(m): """ - Objective: mininimize the investment cost [$]. + Objective: minimize the investment cost [$]. + Equation: min z = sum(alpha[j] * exp(n[j] + beta[j]*v[j])) for j = mixer, reactor, centrifuge - Args: - m (pyomo.ConcreteModel): small batch GDP model + Parameters + ---------- + m : pyomo.ConcreteModel + The small batch GDP model. - Returns: - Objective function (pyomo.Objective): Objective function to minimize the investment cost [$]. + Returns + ------- + Pyomo.Objective + Objective function to minimize the investment cost [$]. """ - return sum(m.alpha[j] * (pe.exp(m.n[j] + m.beta[j] * m.v[j])) for j in m.j) - - m.obj = pe.Objective(rule=obj_rule, sense=pe.minimize) - - return m - - -def external_ref(m, x, logic_expr=None): - """ - Add the external variables to the GDP optimization problem. - - Args: - m (pyomo.ConcreteModel): GDP optimization model - x (list): External variables - logic_expr (list, optional): Logic expressions to be used in the disjunctive constraints + return sum(m.alpha[j] * (pyo.exp(m.n[j] + m.beta[j] * m.v[j])) for j in m.j) - Returns: - m (pyomo.ConcreteModel): GDP optimization model with the external variables - """ - ext_var = {} - p = 0 - for j in m.j: - ext_var[j] = x[p] - p = p + 1 - - for k in m.k: - for j in m.j: - if k == ext_var[j]: - m.Y[k, j].fix(True) - else: - m.Y[k, j].fix(False) - - pe.TransformationFactory('core.logical_to_linear').apply_to(m) - pe.TransformationFactory('gdp.fix_disjuncts').apply_to(m) - pe.TransformationFactory('contrib.deactivate_trivial_constraints').apply_to( - m, tmp=False, ignore_infeasible=True - ) + m.obj = pyo.Objective(rule=obj_rule, sense=pyo.minimize) return m - - if __name__ == "__main__": m = build_small_batch() - + pyo.TransformationFactory('core.logical_to_linear').apply_to(m) + pyo.TransformationFactory('gdp.bigm').apply_to(m) + pyo.SolverFactory('gams').solve(m, solver='baron', tee=True, add_options=['option optcr=1e-6;']) + display(m) From 0b75697193cca20d961322358f52cecd87f97bdf Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 21 May 2024 16:19:55 -0400 Subject: [PATCH 21/79] renew the documentation --- gdplib/small_batch/gdp_small_batch.py | 54 ++++++++++++++++----------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/gdplib/small_batch/gdp_small_batch.py b/gdplib/small_batch/gdp_small_batch.py index 3961bdd..853bbcc 100644 --- a/gdplib/small_batch/gdp_small_batch.py +++ b/gdplib/small_batch/gdp_small_batch.py @@ -162,8 +162,9 @@ def coeff_init(m, k): def vol(m, i, j): """ Volume Requirement for Stage j. - Equation: - v_j \geq log(s_ij) + b_i for i = a, b and j = mixer, reactor, centrifuge + Equation + -------- + v_j \geq log(s_ij) + b_i for i = a, b and j = mixer, reactor, centrifuge Parameters ---------- @@ -177,7 +178,7 @@ def vol(m, i, j): Returns ------- Pyomo.Constraint - Algebraic Constraint + A Pyomo constraint object representing the volume requirement for a given stage. """ return m.v[j] >= pyo.log(m.s[i, j]) + m.b[i] @@ -186,8 +187,10 @@ def vol(m, i, j): def cycle(m, i, j): """ Cycle time for each product i. - Equation: - n_j + tl_i \geq log(t_ij) for i = a, b and j = mixer, reactor, centrifuge + + Equation + -------- + n_j + tl_i \geq log(t_ij) for i = a, b and j = mixer, reactor, centrifuge Parameters ---------- @@ -201,7 +204,7 @@ def cycle(m, i, j): Returns ------- Pyomo.Constraint - Algebraic Constraint + A Pyomo constraint object representing the cycle time requirement for each product in each stage. """ return m.n[j] + m.tl[i] >= pyo.log(m.t[i, j]) @@ -211,7 +214,7 @@ def time(m): """ Production time constraint. Equation: - \sum_{i \in I} q_i * \exp(tl_i - b_i) \leq h + sum_{i \in I} q_i * \exp(tl_i - b_i) \leq h Parameters ---------- @@ -221,7 +224,7 @@ def time(m): Returns ------- Pyomo.Constraint - Algebraic Constraint + A Pyomo constraint object representing the production time constraint. """ return sum(m.q[i] * pyo.exp(m.tl[i] - m.b[i]) for i in m.i) <= m.h @@ -231,7 +234,7 @@ def units(m, j): """ Relating number of units to 0-1 variables. Equation: - n_j = \sum_{k \in K} coeffval_{k,j} for j = mixer, reactor, centrifuge + n_j = sum_{k \in K} coeffval_{k,j} for j = mixer, reactor, centrifuge Parameters ---------- @@ -243,7 +246,7 @@ def units(m, j): Returns ------- Pyomo.Constraint - Algebraic Constraint + A Pyomo constraint object representing the relationship between the number of units and the binary variables. """ return m.n[j] == sum(m.coeffval[k, j] for k in m.k) @@ -253,7 +256,7 @@ def lim(m, j): """ Only one choice for parallel units is feasible. Equation: - \sum_{k \in K} Y_{k,j} = 1 for j = mixer, reactor, centrifuge + sum_{k \in K} Y_{k,j} = 1 for j = mixer, reactor, centrifuge Parameters ---------- @@ -265,7 +268,7 @@ def lim(m, j): Returns ------- Pyomo.LogicalConstraint - Algebraic Constraint + A Pyomo logical constraint ensuring only one choice for parallel units is feasible. """ return pyo.exactly(1, m.Y[1, j], m.Y[2, j], m.Y[3, j]) @@ -291,11 +294,14 @@ def build_existence_equations(disjunct, k, j): """ m = disjunct.model() - # Coeffval activation + # Coefficient value activation @disjunct.Constraint() def coeffval_act(disjunct): """ - Coeffval activation. + Coefficien value activation. + + Equation + -------- m.coeffval[k,j] = m.coeff[k] = log(k) Parameters @@ -306,7 +312,7 @@ def coeffval_act(disjunct): Returns ------- Pyomo.Constraint - A algebraic constraint + A Pyomo constraint object representing the activation of the coefficient value. """ return m.coeffval[k, j] == m.coeff[k] @@ -330,11 +336,14 @@ def build_not_existence_equations(disjunct, k, j): """ m = disjunct.model() - # Coeffval deactivation + # Coefficient value deactivation @disjunct.Constraint() def coeffval_deact(disjunct): """ - Coeffval deactivation. + Coefficient value deactivation. + + Equation + -------- m.coeffval[k,j] = 0 Parameters @@ -342,8 +351,10 @@ def coeffval_deact(disjunct): disjunct : Pyomo.Disjunct Disjunct block for the absence of the stage. - Returns: - Logical Constraint + Returns + ------- + Pyomo.Constraint + A Pyomo constraint object representing the deactivation of the coefficient value. """ return m.coeffval[k, j] == 0 @@ -386,8 +397,9 @@ def obj_rule(m): """ Objective: minimize the investment cost [$]. - Equation: - min z = sum(alpha[j] * exp(n[j] + beta[j]*v[j])) for j = mixer, reactor, centrifuge + Equation + -------- + min z = sum(alpha[j] * exp(n[j] + beta[j]*v[j])) for j = mixer, reactor, centrifuge Parameters ---------- From 87186cbd702692ac7c4996570ab68f120af28946 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 21 May 2024 16:22:44 -0400 Subject: [PATCH 22/79] black format. --- gdplib/small_batch/gdp_small_batch.py | 85 +++++++++++++++------------ 1 file changed, 46 insertions(+), 39 deletions(-) diff --git a/gdplib/small_batch/gdp_small_batch.py b/gdplib/small_batch/gdp_small_batch.py index 853bbcc..ff910d8 100644 --- a/gdplib/small_batch/gdp_small_batch.py +++ b/gdplib/small_batch/gdp_small_batch.py @@ -40,42 +40,44 @@ def build_small_batch(): # Sets m.i = pyo.Set( - initialize=['a', 'b'], doc='Set of products' + initialize=["a", "b"], doc="Set of products" ) # Set of products, i = a, b m.j = pyo.Set( - initialize=['mixer', 'reactor', 'centrifuge'] + initialize=["mixer", "reactor", "centrifuge"] ) # Set of stages, j = mixer, reactor, centrifuge - m.k = pyo.RangeSet(NK, doc="Set of potential number of parallel units") # Set of potential number of parallel units, k = 1, 2, 3 + m.k = pyo.RangeSet( + NK, doc="Set of potential number of parallel units" + ) # Set of potential number of parallel units, k = 1, 2, 3 # Parameters and Scalars m.h = pyo.Param( - initialize=6000, doc='Horizon time [hr]' + initialize=6000, doc="Horizon time [hr]" ) # Horizon time (available time) [hr] m.vlow = pyo.Param( - initialize=250, doc='Lower bound for size of batch unit [L]' + initialize=250, doc="Lower bound for size of batch unit [L]" ) # Lower bound for size of batch unit [L] m.vupp = pyo.Param( - initialize=2500, doc='Upper bound for size of batch unit [L]' + initialize=2500, doc="Upper bound for size of batch unit [L]" ) # Upper bound for size of batch unit [L] # Demand of product i m.q = pyo.Param( m.i, - initialize={'a': 200000, 'b': 150000}, - doc='Production rate of the product [kg]', + initialize={"a": 200000, "b": 150000}, + doc="Production rate of the product [kg]", ) # Cost coefficient for batch units m.alpha = pyo.Param( m.j, - initialize={'mixer': 250, 'reactor': 500, 'centrifuge': 340}, - doc='Cost coefficient for batch units [$/L^beta*No. of units]]', + initialize={"mixer": 250, "reactor": 500, "centrifuge": 340}, + doc="Cost coefficient for batch units [$/L^beta*No. of units]]", ) # Cost exponent for batch units m.beta = pyo.Param( m.j, - initialize={'mixer': 0.6, 'reactor': 0.6, 'centrifuge': 0.6}, - doc='Cost exponent for batch units', + initialize={"mixer": 0.6, "reactor": 0.6, "centrifuge": 0.6}, + doc="Cost exponent for batch units", ) def coeff_init(m, k): @@ -98,61 +100,61 @@ def coeff_init(m, k): # Represent number of parallel units m.coeff = pyo.Param( - m.k, initialize=coeff_init, doc='Coefficient for number of parallel units' + m.k, initialize=coeff_init, doc="Coefficient for number of parallel units" ) s_init = { - ('a', 'mixer'): 2, - ('a', 'reactor'): 3, - ('a', 'centrifuge'): 4, - ('b', 'mixer'): 4, - ('b', 'reactor'): 6, - ('b', 'centrifuge'): 3, + ("a", "mixer"): 2, + ("a", "reactor"): 3, + ("a", "centrifuge"): 4, + ("b", "mixer"): 4, + ("b", "reactor"): 6, + ("b", "centrifuge"): 3, } # Size factor for product i in stage j [kg/L] m.s = pyo.Param( - m.i, m.j, initialize=s_init, doc='Size factor for product i in stage j [kg/L]' + m.i, m.j, initialize=s_init, doc="Size factor for product i in stage j [kg/L]" ) t_init = { - ('a', 'mixer'): 8, - ('a', 'reactor'): 20, - ('a', 'centrifuge'): 4, - ('b', 'mixer'): 10, - ('b', 'reactor'): 12, - ('b', 'centrifuge'): 3, + ("a", "mixer"): 8, + ("a", "reactor"): 20, + ("a", "centrifuge"): 4, + ("b", "mixer"): 10, + ("b", "reactor"): 12, + ("b", "centrifuge"): 3, } # Processing time of product i in batch j [hr] m.t = pyo.Param( - m.i, m.j, initialize=t_init, doc='Processing time of product i in batch j [hr]' + m.i, m.j, initialize=t_init, doc="Processing time of product i in batch j [hr]" ) # Variables - m.Y = pyo.BooleanVar(m.k, m.j, doc='Stage existence') # Stage existence + m.Y = pyo.BooleanVar(m.k, m.j, doc="Stage existence") # Stage existence m.coeffval = pyo.Var( m.k, m.j, within=pyo.NonNegativeReals, bounds=(0, pyo.log(NK)), - doc='Activation of Coefficient', + doc="Activation of Coefficient", ) # Activation of coeff m.v = pyo.Var( m.j, within=pyo.NonNegativeReals, bounds=(pyo.log(m.vlow), pyo.log(m.vupp)), - doc='Colume of stage j [L]', + doc="Colume of stage j [L]", ) # Volume of stage j [L] m.b = pyo.Var( - m.i, within=pyo.NonNegativeReals, doc='Batch size of product i [L]' + m.i, within=pyo.NonNegativeReals, doc="Batch size of product i [L]" ) # Batch size of product i [L] m.tl = pyo.Var( - m.i, within=pyo.NonNegativeReals, doc='Cycle time of product i [hr]' + m.i, within=pyo.NonNegativeReals, doc="Cycle time of product i [hr]" ) # Cycle time of product i [hr] # Number of units in parallel stage j m.n = pyo.Var( - m.j, within=pyo.NonNegativeReals, doc='Number of units in parallel stage j' + m.j, within=pyo.NonNegativeReals, doc="Number of units in parallel stage j" ) # Constraints @@ -359,8 +361,12 @@ def coeffval_deact(disjunct): return m.coeffval[k, j] == 0 # Create disjunction block - m.Y_exists = Disjunct(m.k, m.j, rule=build_existence_equations, doc="Existence of the stage") - m.Y_not_exists = Disjunct(m.k, m.j, rule=build_not_existence_equations, doc="Absence of the stage") + m.Y_exists = Disjunct( + m.k, m.j, rule=build_existence_equations, doc="Existence of the stage" + ) + m.Y_not_exists = Disjunct( + m.k, m.j, rule=build_not_existence_equations, doc="Absence of the stage" + ) # Create disjunction @@ -420,8 +426,9 @@ def obj_rule(m): if __name__ == "__main__": m = build_small_batch() - pyo.TransformationFactory('core.logical_to_linear').apply_to(m) - pyo.TransformationFactory('gdp.bigm').apply_to(m) - pyo.SolverFactory('gams').solve(m, solver='baron', tee=True, add_options=['option optcr=1e-6;']) + pyo.TransformationFactory("core.logical_to_linear").apply_to(m) + pyo.TransformationFactory("gdp.bigm").apply_to(m) + pyo.SolverFactory("gams").solve( + m, solver="baron", tee=True, add_options=["option optcr=1e-6;"] + ) display(m) - From 27063a3f9eb3b0c375a50c11133522ed3fea57ea Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 21 May 2024 16:26:17 -0400 Subject: [PATCH 23/79] fixed documentation --- gdplib/small_batch/gdp_small_batch.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gdplib/small_batch/gdp_small_batch.py b/gdplib/small_batch/gdp_small_batch.py index ff910d8..1922aed 100644 --- a/gdplib/small_batch/gdp_small_batch.py +++ b/gdplib/small_batch/gdp_small_batch.py @@ -4,12 +4,12 @@ The problem is based on the Example 4 of the paper. The objective is to minimize the investment cost of the batch units. -References: - [1] Kocis, G. R.; Grossmann, I. E. Global Optimization of Nonconvex Mixed-Integer Nonlinear Programming (MINLP) Problems in Process Synthesis. Ind. Eng. Chem. Res. 1988, 27 (8), 1407-1421. https://doi.org/10.1021/ie00080a013 - [2] Ovalle, D., Liñán, D. A., Lee, A., Gómez, J. M., Ricardez-Sandoval, L., Grossmann, I. E., & Neira, D. E. B. (2024). Logic-Based Discrete-Steepest Descent: A Solution Method for Process Synthesis Generalized Disjunctive Programs. arXiv preprint arXiv:2405.05358. https://doi.org/10.48550/arXiv.2405.05358 +References +---------- +[1] Kocis, G. R.; Grossmann, I. E. Global Optimization of Nonconvex Mixed-Integer Nonlinear Programming (MINLP) Problems in Process Synthesis. Ind. Eng. Chem. Res. 1988, 27 (8), 1407-1421. https://doi.org/10.1021/ie00080a013 +[2] Ovalle, D., Liñán, D. A., Lee, A., Gómez, J. M., Ricardez-Sandoval, L., Grossmann, I. E., & Neira, D. E. B. (2024). Logic-Based Discrete-Steepest Descent: A Solution Method for Process Synthesis Generalized Disjunctive Programs. arXiv preprint arXiv:2405.05358. https://doi.org/10.48550/arXiv.2405.05358 """ import os - import pyomo.environ as pyo from pyomo.core.base.misc import display from pyomo.core.plugins.transform.logical_to_linear import ( From 8e0cdc4b88164c6df13527282b91eed32935aac3 Mon Sep 17 00:00:00 2001 From: Carolina Tristan Date: Fri, 24 May 2024 16:20:27 -0400 Subject: [PATCH 24/79] Update units and import in kaibel model --- gdplib/kaibel/kaibel_init.py | 1 + gdplib/kaibel/kaibel_prop.py | 42 ++++++++--------- gdplib/kaibel/kaibel_solve_gdp.py | 77 ++++++++++++++++--------------- gdplib/kaibel/main_gdp.py | 4 +- 4 files changed, 64 insertions(+), 60 deletions(-) diff --git a/gdplib/kaibel/kaibel_init.py b/gdplib/kaibel/kaibel_init.py index 7bdac1d..921204c 100644 --- a/gdplib/kaibel/kaibel_init.py +++ b/gdplib/kaibel/kaibel_init.py @@ -28,6 +28,7 @@ from pyomo.environ import (exp, log10, minimize, NonNegativeReals, Objective, RangeSet, SolverFactory, value, Var) from gdplib.kaibel.kaibel_prop import get_model_with_properties +# from .kaibel_prop import get_model_with_properties def initialize_kaibel(): diff --git a/gdplib/kaibel/kaibel_prop.py b/gdplib/kaibel/kaibel_prop.py index b758901..dc853d4 100644 --- a/gdplib/kaibel/kaibel_prop.py +++ b/gdplib/kaibel/kaibel_prop.py @@ -27,30 +27,30 @@ def get_model_with_properties(): m.hc = 4 # Heavy component, butanol #### Constant parameters - m.Rgas = 8.314 # Ideal gas constant in J/mol K - m.Tref = 298.15 # Reference temperature in K + m.Rgas = 8.314 # Ideal gas constant [J/mol/K] + m.Tref = 298.15 # Reference temperature [K] #### Product specifications m.xspec_lc = 0.99 # Final liquid composition for methanol (1) m.xspec_hc = 0.99 # Fnal liquid composition for butanol (4) m.xspec_inter2 = 0.99 # Final liquid composition for ethanol (2) m.xspec_inter3 = 0.99 # Final liquid composition for propanol (3) - m.Ddes = 50 # Final flowrate in distillate in mol/s - m.Bdes = 50 # Final flowrate in bottoms in mol/s - m.Sdes = 50 # Final flowrate in side product streams in mol/s + m.Ddes = 50 # Final flowrate in distillate [mol/s] + m.Bdes = 50 # Final flowrate in bottoms [mol/s] + m.Sdes = 50 # Final flowrate in side product streams [mol/s] # #### Known initial values - m.Fi = m.Ddes + m.Bdes + 2 * m.Sdes # Side feed flowrate in mol/s - m.Vi = 400 # Initial value for vapor flowrate in mol/s - m.Li = 400 # Initial value for liquid flowrate in mol/s + m.Fi = m.Ddes + m.Bdes + 2 * m.Sdes # Side feed flowrate [mol/s] + m.Vi = 400 # Initial value for vapor flowrate [mol/s] + m.Li = 400 # Initial value for liquid flowrate [mol/s] - m.Tf = 358 # Side feed temperature in K + m.Tf = 358 # Side feed temperature [K] - m.Preb = 1.2 # Reboiler pressure in bar - m.Pbot = 1.12 # Bottom-most tray pressure in bar - m.Ptop = 1.08 # Top-most tray pressure in bar - m.Pcon = 1.05 # Condenser pressure in bar - m.Pf = 1.02 # Column pressure in bar + m.Preb = 1.2 # Reboiler pressure [bar] + m.Pbot = 1.12 # Bottom-most tray pressure [bar] + m.Ptop = 1.08 # Top-most tray pressure [bar] + m.Pcon = 1.05 # Condenser pressure [bar] + m.Pf = 1.02 # Column pressure [bar] m.rr0 = 0.893 # Internal reflux ratio initial value m.bu0 = 0.871 # Internal reflux ratio initial value @@ -73,15 +73,15 @@ def get_model_with_properties(): # Physical Properties # # Notation: - # MW ........................ molecular weight in g/gmol - # TB ........................ boiling point temperature in K - # TC ........................ critical temperature in K - # PC ........................ critical pressure in bar + # MW ........................ molecular weight [g/mol] + # TB ........................ boiling point temperature [K] + # TC ........................ critical temperature [K] + # PC ........................ critical pressure [bar] # w ........................ acentric factor - # lden ...................... liquid density g/m3, - # dHvap ..................... heat of vaporization in J/mol. + # lden ...................... liquid density [g/m3], + # dHvap ..................... heat of vaporization [J/mol]. # vpA, vpB, vpC, and vpD .... vapor pressure constants - # cpA, cpB, cpC, and cpD .... heat capacity constants J/mol: + # cpA, cpB, cpC, and cpD .... heat capacity constants [J/mol]: # 1 for liq and 2 for vapor phase # # Reference A: R.C. Reid, J.M. Prausnitz and B.E. Poling, diff --git a/gdplib/kaibel/kaibel_solve_gdp.py b/gdplib/kaibel/kaibel_solve_gdp.py index 4125113..310cb26 100644 --- a/gdplib/kaibel/kaibel_solve_gdp.py +++ b/gdplib/kaibel/kaibel_solve_gdp.py @@ -25,6 +25,7 @@ from gdplib.kaibel.kaibel_init import initialize_kaibel from gdplib.kaibel.kaibel_side_flash import calc_side_feed_flash +# from .kaibel_side_flash import calc_side_feed_flash def build_model(): @@ -47,10 +48,10 @@ def build_model(): m.name = "GDP Kaibel Column" #### Calculated initial values - m.Treb = m.TB0 + 5 # Reboiler temperature in K - m.Tbot = m.TB0 # Bottom-most tray temperature in K - m.Ttop = m.TD0 # Top-most tray temperature in K - m.Tcon = m.TD0 - 5 # Condenser temperature in K + m.Treb = m.TB0 + 5 # Reboiler temperature [K] + m.Tbot = m.TB0 # Bottom-most tray temperature [K] + m.Ttop = m.TD0 # Top-most tray temperature [K] + m.Tcon = m.TD0 - 5 # Condenser temperature [K] m.dv0 = {} # Initial vapor distributor value m.dl0 = {} # Initial liquid distributor value @@ -64,8 +65,8 @@ def build_model(): m.Tlo = m.Tcon - 20 # Temperature lower bound m.Tup = m.Treb + 20 # Temperature upper bound - m.flow_max = 1e3 # Flowrates upper bound in mol/s - m.Qmax = 60 # Heat loads upper bound in J/s + m.flow_max = 1e3 # Flowrates upper bound [mol/s] + m.Qmax = 60 # Heat loads upper bound [J/s] #### Column tray details m.num_trays = m.np # Trays per section. np = 25 @@ -120,18 +121,18 @@ def build_model(): m.P0 = {} # Initial pressure [bar] m.T0 = {} # Initial temperature [K] - m.L0 = {} # Initial individual liquid flowrate in mol/s - m.V0 = {} # Initial individual vapor flowrate in mol/s - m.Vtotal0 = {} # Initial total vapor flowrate in mol/s - m.Ltotal0 = {} # Initial liquid flowrate in mol/s + m.L0 = {} # Initial individual liquid flowrate [mol/s] + m.V0 = {} # Initial individual vapor flowrate [mol/s] + m.Vtotal0 = {} # Initial total vapor flowrate [mol/s] + m.Ltotal0 = {} # Initial liquid flowrate [mol/s] m.x0 = {} # Initial liquid composition m.y0 = {} # Initial vapor composition m.actv0 = {} # Initial activity coefficients - m.cpdT0 = {} # Initial heat capacity for liquid and vapor phases in J/mol K - m.hl0 = {} # Initial liquid enthalpy in J/mol - m.hv0 = {} # Initial vapor enthalpy in J/mol - m.Pi = m.Preb # Initial given pressure value in bar - m.Ti = {} # Initial known temperature values in K + m.cpdT0 = {} # Initial heat capacity for liquid and vapor phases [J/mol/K] + m.hl0 = {} # Initial liquid enthalpy [J/mol] + m.hv0 = {} # Initial vapor enthalpy [J/mol] + m.Pi = m.Preb # Initial given pressure value [bar] + m.Ti = {} # Initial known temperature values [K] ## Initial values for pressure, temperature, flowrates, composition, and enthalpy for sec in m.section: @@ -175,14 +176,14 @@ def build_model(): m.y0[sec, n_tray, comp] = m.xfi[comp] ## Assigns the enthalpy boundary values, heat capacity, heat of vaporization calculation, temperature bounds, and light and heavy key components. - hlb = {} # Liquid enthalpy in J/mol - hvb = {} # Vapor enthalpy in J/mol - cpb = {} # Heact capacity in J/mol K - dHvapb = {} # Heat of vaporization in J/mol - Tbounds = {} # Temperature bounds in K + hlb = {} # Liquid enthalpy [J/mol] + hvb = {} # Vapor enthalpy [J/mol] + cpb = {} # Heact capacity [J/mol/K] + dHvapb = {} # Heat of vaporization [J/mol] + Tbounds = {} # Temperature bounds [K] kc = {} # Light and heavy key components - Tbounds[1] = m.Tcon # Condenser temperature in K - Tbounds[2] = m.Treb # Reboiler temperature in K + Tbounds[1] = m.Tcon # Condenser temperature [K] + Tbounds[2] = m.Treb # Reboiler temperature [K] kc[1] = m.lc kc[2] = m.hc @@ -323,7 +324,7 @@ def build_model(): m.T = Var( m.section, m.tray, - doc="Temperature at each potential tray in K", + doc="Temperature at each potential tray [K]", domain=NonNegativeReals, bounds=(m.Tlo, m.Tup), initialize=m.T0, @@ -366,7 +367,7 @@ def build_model(): m.section, m.tray, m.comp, - doc="Vapor flowrate in mol/s", + doc="Vapor flowrate [mol/s]", domain=NonNegativeReals, bounds=(0, m.flow_max), initialize=m.V0, @@ -375,7 +376,7 @@ def build_model(): m.section, m.tray, m.comp, - doc="Liquid flowrate in mol/s", + doc="Liquid flowrate [mol/s]", domain=NonNegativeReals, bounds=(0, m.flow_max), initialize=m.L0, @@ -383,7 +384,7 @@ def build_model(): m.Vtotal = Var( m.section, m.tray, - doc="Total vapor flowrate in mol/s", + doc="Total vapor flowrate [mol/s]", domain=NonNegativeReals, bounds=(0, m.flow_max), initialize=m.Vtotal0, @@ -391,7 +392,7 @@ def build_model(): m.Ltotal = Var( m.section, m.tray, - doc="Total liquid flowrate in mol/s", + doc="Total liquid flowrate [mol/s]", domain=NonNegativeReals, bounds=(0, m.flow_max), initialize=m.Ltotal0, @@ -399,14 +400,14 @@ def build_model(): m.D = Var( m.comp, - doc="Distillate flowrate in mol/s", + doc="Distillate flowrate [mol/s]", domain=NonNegativeReals, bounds=(0, m.flow_max), initialize=m.Ddes, ) m.B = Var( m.comp, - doc="Bottoms flowrate in mol/s", + doc="Bottoms flowrate [mol/s]", domain=NonNegativeReals, bounds=(0, m.flow_max), initialize=m.Bdes, @@ -414,26 +415,26 @@ def build_model(): m.S = Var( m.so, m.comp, - doc="Product 2 and 3 flowrates in mol/s", + doc="Product 2 and 3 flowrates [mol/s]", domain=NonNegativeReals, bounds=(0, m.flow_max), initialize=m.Sdes, ) m.Dtotal = Var( - doc="Distillate flowrate in mol/s", + doc="Distillate flowrate [mol/s]", domain=NonNegativeReals, bounds=(0, m.flow_max), initialize=m.Ddes, ) m.Btotal = Var( - doc="Bottoms flowrate in mol/s", + doc="Bottoms flowrate [mol/s]", domain=NonNegativeReals, bounds=(0, m.flow_max), initialize=m.Bdes, ) m.Stotal = Var( m.so, - doc="Total product 2 and 3 side flowrate in mol/s", + doc="Total product 2 and 3 side flowrate [mol/s]", domain=NonNegativeReals, bounds=(0, m.flow_max), initialize=m.Sdes, @@ -443,7 +444,7 @@ def build_model(): m.section, m.tray, m.comp, - doc='Liquid enthalpy in J/mol', + doc='Liquid enthalpy [J/mol]', bounds=(m.hllo, m.hlup), initialize=m.hl0, ) @@ -451,18 +452,18 @@ def build_model(): m.section, m.tray, m.comp, - doc='Vapor enthalpy in J/mol', + doc='Vapor enthalpy [J/mol]', bounds=(m.hvlo, m.hvup), initialize=m.hv0, ) m.Qreb = Var( - doc="Reboiler heat duty in J/s", + doc="Reboiler heat duty [J/s]", domain=NonNegativeReals, bounds=(0, m.Qmax), initialize=1, ) m.Qcon = Var( - doc="Condenser heat duty in J/s", + doc="Condenser heat duty [J/s]", domain=NonNegativeReals, bounds=(0, m.Qmax), initialize=1, @@ -483,7 +484,7 @@ def build_model(): m.F = Var( m.comp, - doc="Side feed flowrate in mol/s", + doc="Side feed flowrate [mol/s]", domain=NonNegativeReals, bounds=(0, 50), initialize=m.F0, diff --git a/gdplib/kaibel/main_gdp.py b/gdplib/kaibel/main_gdp.py index 45206f3..58381ed 100644 --- a/gdplib/kaibel/main_gdp.py +++ b/gdplib/kaibel/main_gdp.py @@ -57,7 +57,9 @@ import matplotlib.pyplot as plt from pyomo.environ import * -from kaibel_solve_gdp import build_model +# from kaibel_solve_gdp import build_model +from gdplib.kaibel.kaibel_solve_gdp import build_model + def main(): From def1fef3803b355680607acf2ca7bc1e2f1290b4 Mon Sep 17 00:00:00 2001 From: Carolina Tristan Date: Fri, 24 May 2024 17:15:32 -0400 Subject: [PATCH 25/79] black format --- gdplib/kaibel/kaibel_init.py | 783 ++--- gdplib/kaibel/kaibel_solve_gdp.py | 5323 +++++++++++++++-------------- gdplib/kaibel/main_gdp.py | 579 ++-- 3 files changed, 3350 insertions(+), 3335 deletions(-) diff --git a/gdplib/kaibel/kaibel_init.py b/gdplib/kaibel/kaibel_init.py index 3e3cf86..5a510be 100644 --- a/gdplib/kaibel/kaibel_init.py +++ b/gdplib/kaibel/kaibel_init.py @@ -1,384 +1,399 @@ -""" - Calculation of the theoretical minimum number of trays and initial - temperature values. - (written by E. Soraya Rawlings, esoraya@rwlngs.net) - - The separation of four components require a sequence of at least three distillation - columns. Here, we calculate the minimum number of theoretical trays for the three - columns. The sequence is shown in Figure 2. - - COLUMN 1 COLUMN 2 COLUMN 3 - ----- ---- ----- - | | | | | | - ----- | A ----- | ----- | - | |<---> B -- | |<----> A -- | |<---> A - | | C | | | B | | | - A | | | | | | | | - B | | | | | | | | - C --->| | -->| | -->| | - D | | | | | | - | | | | | | - | |<- | |<- | |<- - ----- | ----- | ----- | - | | | | | | - -------> D -------> C -------> B - Figure 2. Sequence of columns for the separation of a quaternary mixture -""" - -from __future__ import division - -from pyomo.environ import ( - exp, - log10, - minimize, - NonNegativeReals, - Objective, - RangeSet, - SolverFactory, - value, - Var, -) - -from gdplib.kaibel.kaibel_prop import get_model_with_properties -# from .kaibel_prop import get_model_with_properties - - -def initialize_kaibel(): - """Initialize the Kaibel optimization model. - - This function initializes the Kaibel optimization model by setting up the operating conditions, - initial liquid compositions, and creating the necessary variables and constraints. - - Returns - ------- - None - """ - - ## Get the model with properties from kaibel_prop.py - m = get_model_with_properties() - - ## Operating conditions - m.Preb = 1.2 # Reboiler pressure in bar - m.Pcon = 1.05 # Condenser pressure in bar - m.Pf = 1.02 - - Pnmin = {} # Pressure in bars - Pnmin[1] = m.Preb # Reboiler pressure in bars - Pnmin[3] = m.Pcon # Distillate pressure in bars - Pnmin[2] = m.Pf # Side feed pressure in bars - - xi_nmin = {} # Initial liquid composition: first number = column and - # second number = 1 reboiler, 2 side feed, and - # 3 for condenser - - ## Column 1 - c_c1 = 4 # Components in Column 1 - lc_c1 = 3 # Light component in Column 1 - hc_c1 = 4 # Heavy component in Column 1 - inter1_c1 = 1 # Intermediate component in Column 1 - inter2_c1 = 2 # Intermediate component in Column 1 - - xi_nmin[1, 1, hc_c1] = 0.999 - xi_nmin[1, 1, lc_c1] = (1 - xi_nmin[1, 1, hc_c1]) / (c_c1 - 1) - xi_nmin[1, 1, inter1_c1] = (1 - xi_nmin[1, 1, hc_c1]) / (c_c1 - 1) - xi_nmin[1, 1, inter2_c1] = (1 - xi_nmin[1, 1, hc_c1]) / (c_c1 - 1) - xi_nmin[1, 3, lc_c1] = 0.33 - xi_nmin[1, 3, inter1_c1] = 0.33 - xi_nmin[1, 3, inter2_c1] = 0.33 - xi_nmin[1, 3, hc_c1] = 1 - ( - xi_nmin[1, 3, lc_c1] + xi_nmin[1, 3, inter1_c1] + xi_nmin[1, 3, inter2_c1] - ) - xi_nmin[1, 2, lc_c1] = 1 / c_c1 - xi_nmin[1, 2, inter1_c1] = 1 / c_c1 - xi_nmin[1, 2, inter2_c1] = 1 / c_c1 - xi_nmin[1, 2, hc_c1] = 1 / c_c1 - - ## Column 2 - c_c2 = 3 # Light components in Column 2 - lc_c2 = 2 # Light component in Column 2 - hc_c2 = 3 # Heavy component in Column 2 - inter_c2 = 1 # Intermediate component in Column 2 - - xi_nmin[2, 1, hc_c2] = 0.999 - xi_nmin[2, 1, lc_c2] = (1 - xi_nmin[2, 1, hc_c2]) / (c_c2 - 1) - xi_nmin[2, 1, inter_c2] = (1 - xi_nmin[2, 1, hc_c2]) / (c_c2 - 1) - xi_nmin[2, 3, lc_c2] = 0.499 - xi_nmin[2, 3, inter_c2] = 0.499 - xi_nmin[2, 3, hc_c2] = 1 - (xi_nmin[2, 3, lc_c2] + xi_nmin[2, 3, inter_c2]) - xi_nmin[2, 2, lc_c2] = 1 / c_c2 - xi_nmin[2, 2, inter_c2] = 1 / c_c2 - xi_nmin[2, 2, hc_c2] = 1 / c_c2 - - ## Column 3 - c_c3 = 2 # Components in Column 3 - lc_c3 = 1 # Light component in Column 3 - hc_c3 = 2 # Heavy component in Column 3 - - xi_nmin[3, 1, hc_c3] = 0.999 - xi_nmin[3, 1, lc_c3] = 1 - xi_nmin[3, 1, hc_c3] - xi_nmin[3, 3, lc_c3] = 0.999 - xi_nmin[3, 3, hc_c3] = 1 - xi_nmin[3, 3, lc_c3] - xi_nmin[3, 2, lc_c3] = 0.50 - xi_nmin[3, 2, hc_c3] = 0.50 - - #### - - mn = m.clone() # Clone the model to add the initialization code - - mn.name = "Initialization Code" - - mn.cols = RangeSet(3, doc='Number of columns ') - mn.sec = RangeSet(3, doc='Sections in column: 1 reb, 2 side feed, 3 cond') - mn.nc1 = RangeSet(c_c1, doc='Number of components in Column 1') - mn.nc2 = RangeSet(c_c2, doc='Number of components in Column 2') - mn.nc3 = RangeSet(c_c3, doc='Number of components in Column 3') - - mn.Tnmin = Var( - mn.cols, - mn.sec, - doc='Temperature in K', - bounds=(0, 500), - domain=NonNegativeReals, - ) - mn.Tr1nmin = Var( - mn.cols, - mn.sec, - mn.nc1, - doc='Temperature term for vapor pressure', - domain=NonNegativeReals, - bounds=(0, None), - ) - mn.Tr2nmin = Var( - mn.cols, - mn.sec, - mn.nc2, - doc='Temperature term for vapor pressure', - domain=NonNegativeReals, - bounds=(0, None), - ) - mn.Tr3nmin = Var( - mn.cols, - mn.sec, - mn.nc3, - doc='Temperature term for vapor pressure', - domain=NonNegativeReals, - bounds=(0, None), - ) - - @mn.Constraint(mn.cols, mn.sec, mn.nc1, doc="Temperature term for vapor pressure") - def _column1_reduced_temperature(mn, col, sec, nc): - """Calculate the reduced temperature for column 1. - - This function calculates the reduced temperature for column 1 based on the given parameters using the Peng-Robinson equation of state. - - Parameters - ---------- - mn : Pyomo ConcreteModel - The optimization model - col : int - The column index - sec : int - The section index - nc : int - The component index in column 1 - - Returns - ------- - Constraint - The constraint statement to calculate the reduced temperature. - """ - return mn.Tr1nmin[col, sec, nc] * mn.Tnmin[col, sec] == mn.prop[nc, 'TC'] - - @mn.Constraint(mn.cols, mn.sec, mn.nc2, doc="Temperature term for vapor pressure") - def _column2_reduced_temperature(mn, col, sec, nc): - """Calculate the reduced temperature for column 2. - - This function calculates the reduced temperature for column 2 based on the given parameters using the Peng-Robinson equation of state. - - Parameters - ---------- - mn : Pyomo ConcreteModel - The optimization model - col : int - The column index - sec : int - The section index - nc : int - The component index in column 2 - - Returns - ------- - Constraint - The constraint equation to calculate the reduced temperature - """ - return mn.Tr2nmin[col, sec, nc] * mn.Tnmin[col, sec] == mn.prop[nc, 'TC'] - - @mn.Constraint(mn.cols, mn.sec, mn.nc3, doc="Temperature term for vapor pressure") - def _column3_reduced_temperature(mn, col, sec, nc): - """Calculate the reduced temperature for column 3. - - This function calculates the reduced temperature for column 3 based on the given parameters. - - Parameters - ---------- - mn : Pyomo ConcreteModel - The optimization model - col : int - The column index - sec : int - The section index - nc : int - The component index in column 3 - - Returns - ------- - Constraint - The constraint equation to calculate the reduced temperature in column 3 - """ - return mn.Tr3nmin[col, sec, nc] * mn.Tnmin[col, sec] == mn.prop[nc, 'TC'] - - @mn.Constraint(mn.cols, mn.sec, doc="Boiling point temperature") - def _equilibrium_equation(mn, col, sec): - """Equilibrium equations for a given column and section. - - Parameters - ---------- - mn : Pyomo ConcreteModel - The optimization model object with properties - col : int - The column index - sec : int - The section index - - Returns - ------- - Constraint - The constraint equation to calculate the boiling point temperature using the Peng-Robinson equation of state - """ - if col == 1: - a = mn.Tr1nmin - b = mn.nc1 - elif col == 2: - a = mn.Tr2nmin - b = mn.nc2 - elif col == 3: - a = mn.Tr3nmin - b = mn.nc3 - return ( - sum( - xi_nmin[col, sec, nc] - * mn.prop[nc, 'PC'] - * exp( - a[col, sec, nc] - * ( - mn.prop[nc, 'vpA'] - * (1 - mn.Tnmin[col, sec] / mn.prop[nc, 'TC']) - + mn.prop[nc, 'vpB'] - * (1 - mn.Tnmin[col, sec] / mn.prop[nc, 'TC']) ** 1.5 - + mn.prop[nc, 'vpC'] - * (1 - mn.Tnmin[col, sec] / mn.prop[nc, 'TC']) ** 3 - + mn.prop[nc, 'vpD'] - * (1 - mn.Tnmin[col, sec] / mn.prop[nc, 'TC']) ** 6 - ) - ) - / Pnmin[sec] - for nc in b - ) - == 1 - ) - - mn.OBJ = Objective(expr=1, sense=minimize) - - #### - - SolverFactory('ipopt').solve(mn) - - yc = {} # Vapor composition - kl = {} # Light key component - kh = {} # Heavy key component - alpha = {} # Relative volatility of kl - ter = {} # Term to calculate the minimum number of trays - Nmin = {} # Minimum number of stages - Nminopt = {} # Total optimal minimum number of trays - Nfeed = {} # Side feed optimal location using Kirkbride's method: - # 1 = number of trays in rectifying section and - # 2 = number of trays in stripping section - side_feed = {} # Side feed location - av_alpha = {} # Average relative volatilities - xi_lhc = {} # Liquid composition in columns - rel = mn.Bdes / mn.Ddes # Ratio between products flowrates - ln = {} # Light component for the different columns - hn = {} # Heavy component for the different columns - ln[1] = lc_c1 - ln[2] = lc_c2 - ln[3] = lc_c3 - hn[1] = hc_c1 - hn[2] = hc_c2 - hn[3] = hc_c3 - - for col in mn.cols: - if col == 1: - b = mn.nc1 - elif col == 2: - b = mn.nc2 - else: - b = mn.nc3 - # For each component in the column and section calculate the vapor composition with the Peng-Robinson equation of state - for sec in mn.sec: - for nc in b: - yc[col, sec, nc] = ( - xi_nmin[col, sec, nc] - * mn.prop[nc, 'PC'] - * exp( - mn.prop[nc, 'TC'] - / value(mn.Tnmin[col, sec]) - * ( - mn.prop[nc, 'vpA'] - * (1 - value(mn.Tnmin[col, sec]) / mn.prop[nc, 'TC']) - + mn.prop[nc, 'vpB'] - * (1 - value(mn.Tnmin[col, sec]) / mn.prop[nc, 'TC']) ** 1.5 - + mn.prop[nc, 'vpC'] - * (1 - value(mn.Tnmin[col, sec]) / mn.prop[nc, 'TC']) ** 3 - + mn.prop[nc, 'vpD'] - * (1 - value(mn.Tnmin[col, sec]) / mn.prop[nc, 'TC']) ** 6 - ) - ) - ) / Pnmin[sec] # Vapor composition in the different sections for the different components in the columns - - for col in mn.cols: - # Calculate the relative volatility of the light and heavy components in the different sections for the different columns - xi_lhc[col, 4] = xi_nmin[col, 1, ln[col]] / \ - xi_nmin[col, 3, hn[col]] # Liquid composition in the different sections with the initial liquid composition of the components in the different sections and columns and ln and hn which are the light and heavy components in the different columns - for sec in mn.sec: - kl[col, sec] = yc[col, sec, ln[col]] / \ - xi_nmin[col, sec, ln[col]] # Light component in the different sections - kh[col, sec] = yc[col, sec, hn[col]] / \ - xi_nmin[col, sec, hn[col]] # Heavy component in the different sections - xi_lhc[col, sec] = xi_nmin[col, sec, hn[col]] / \ - xi_nmin[col, sec, ln[col]] # Liquid composition in the different sections - alpha[col, sec] = kl[col, sec] / kh[col, sec] # Relative volatility in the different sections - - for col in mn.cols: - # Calculate the average relative volatilities and the minimum number of trays with Fenske's and Kirkbride's method - av_alpha[col] = (alpha[col, 1] * alpha[col, 2] - * alpha[col, 3])**(1 / 3) # Average relative volatilities calculated with the relative volatilities of the components in the three sections - Nmin[col] = log10((1 / xi_lhc[col, 3]) * - xi_lhc[col, 1]) / log10(av_alpha[col]) # Minimum number of trays calculated with Fenske's method - ter[col] = (rel * xi_lhc[col, 2] * (xi_lhc[col, 4]**2))**0.206 # Term to calculate the minimum number of trays with Kirkbride's method - # Side feed optimal location using Kirkbride's method - Nfeed[1, col] = ter[col] * Nmin[col] / (1 + ter[col]) # Number of trays in rectifying section - Nfeed[2, col] = Nfeed[1, col] / ter[col] # Number of trays in stripping section - side_feed[col] = Nfeed[2, col] # Side feed location - - m.Nmintot = sum(Nmin[col] for col in mn.cols) # Total minimum number of trays - m.Knmin = int(m.Nmintot) + 1 # Total optimal minimum number of trays - - m.TB0 = value(mn.Tnmin[1, 1]) # Reboiler temperature in K in column 1 - m.Tf0 = value(mn.Tnmin[1, 2]) # Side feed temperature in K in column 1 - m.TD0 = value(mn.Tnmin[2, 3]) # Distillate temperature in K in column 2 - - return m - - -if __name__ == "__main__": - initialize_kaibel() +""" + Calculation of the theoretical minimum number of trays and initial + temperature values. + (written by E. Soraya Rawlings, esoraya@rwlngs.net) + + The separation of four components require a sequence of at least three distillation + columns. Here, we calculate the minimum number of theoretical trays for the three + columns. The sequence is shown in Figure 2. + + COLUMN 1 COLUMN 2 COLUMN 3 + ----- ---- ----- + | | | | | | + ----- | A ----- | ----- | + | |<---> B -- | |<----> A -- | |<---> A + | | C | | | B | | | + A | | | | | | | | + B | | | | | | | | + C --->| | -->| | -->| | + D | | | | | | + | | | | | | + | |<- | |<- | |<- + ----- | ----- | ----- | + | | | | | | + -------> D -------> C -------> B + Figure 2. Sequence of columns for the separation of a quaternary mixture +""" + +from __future__ import division + +from pyomo.environ import ( + exp, + log10, + minimize, + NonNegativeReals, + Objective, + RangeSet, + SolverFactory, + value, + Var, +) + +from gdplib.kaibel.kaibel_prop import get_model_with_properties + +# from .kaibel_prop import get_model_with_properties + + +def initialize_kaibel(): + """Initialize the Kaibel optimization model. + + This function initializes the Kaibel optimization model by setting up the operating conditions, + initial liquid compositions, and creating the necessary variables and constraints. + + Returns + ------- + None + """ + + ## Get the model with properties from kaibel_prop.py + m = get_model_with_properties() + + ## Operating conditions + m.Preb = 1.2 # Reboiler pressure in bar + m.Pcon = 1.05 # Condenser pressure in bar + m.Pf = 1.02 + + Pnmin = {} # Pressure in bars + Pnmin[1] = m.Preb # Reboiler pressure in bars + Pnmin[3] = m.Pcon # Distillate pressure in bars + Pnmin[2] = m.Pf # Side feed pressure in bars + + xi_nmin = {} # Initial liquid composition: first number = column and + # second number = 1 reboiler, 2 side feed, and + # 3 for condenser + + ## Column 1 + c_c1 = 4 # Components in Column 1 + lc_c1 = 3 # Light component in Column 1 + hc_c1 = 4 # Heavy component in Column 1 + inter1_c1 = 1 # Intermediate component in Column 1 + inter2_c1 = 2 # Intermediate component in Column 1 + + xi_nmin[1, 1, hc_c1] = 0.999 + xi_nmin[1, 1, lc_c1] = (1 - xi_nmin[1, 1, hc_c1]) / (c_c1 - 1) + xi_nmin[1, 1, inter1_c1] = (1 - xi_nmin[1, 1, hc_c1]) / (c_c1 - 1) + xi_nmin[1, 1, inter2_c1] = (1 - xi_nmin[1, 1, hc_c1]) / (c_c1 - 1) + xi_nmin[1, 3, lc_c1] = 0.33 + xi_nmin[1, 3, inter1_c1] = 0.33 + xi_nmin[1, 3, inter2_c1] = 0.33 + xi_nmin[1, 3, hc_c1] = 1 - ( + xi_nmin[1, 3, lc_c1] + xi_nmin[1, 3, inter1_c1] + xi_nmin[1, 3, inter2_c1] + ) + xi_nmin[1, 2, lc_c1] = 1 / c_c1 + xi_nmin[1, 2, inter1_c1] = 1 / c_c1 + xi_nmin[1, 2, inter2_c1] = 1 / c_c1 + xi_nmin[1, 2, hc_c1] = 1 / c_c1 + + ## Column 2 + c_c2 = 3 # Light components in Column 2 + lc_c2 = 2 # Light component in Column 2 + hc_c2 = 3 # Heavy component in Column 2 + inter_c2 = 1 # Intermediate component in Column 2 + + xi_nmin[2, 1, hc_c2] = 0.999 + xi_nmin[2, 1, lc_c2] = (1 - xi_nmin[2, 1, hc_c2]) / (c_c2 - 1) + xi_nmin[2, 1, inter_c2] = (1 - xi_nmin[2, 1, hc_c2]) / (c_c2 - 1) + xi_nmin[2, 3, lc_c2] = 0.499 + xi_nmin[2, 3, inter_c2] = 0.499 + xi_nmin[2, 3, hc_c2] = 1 - (xi_nmin[2, 3, lc_c2] + xi_nmin[2, 3, inter_c2]) + xi_nmin[2, 2, lc_c2] = 1 / c_c2 + xi_nmin[2, 2, inter_c2] = 1 / c_c2 + xi_nmin[2, 2, hc_c2] = 1 / c_c2 + + ## Column 3 + c_c3 = 2 # Components in Column 3 + lc_c3 = 1 # Light component in Column 3 + hc_c3 = 2 # Heavy component in Column 3 + + xi_nmin[3, 1, hc_c3] = 0.999 + xi_nmin[3, 1, lc_c3] = 1 - xi_nmin[3, 1, hc_c3] + xi_nmin[3, 3, lc_c3] = 0.999 + xi_nmin[3, 3, hc_c3] = 1 - xi_nmin[3, 3, lc_c3] + xi_nmin[3, 2, lc_c3] = 0.50 + xi_nmin[3, 2, hc_c3] = 0.50 + + #### + + mn = m.clone() # Clone the model to add the initialization code + + mn.name = "Initialization Code" + + mn.cols = RangeSet(3, doc='Number of columns ') + mn.sec = RangeSet(3, doc='Sections in column: 1 reb, 2 side feed, 3 cond') + mn.nc1 = RangeSet(c_c1, doc='Number of components in Column 1') + mn.nc2 = RangeSet(c_c2, doc='Number of components in Column 2') + mn.nc3 = RangeSet(c_c3, doc='Number of components in Column 3') + + mn.Tnmin = Var( + mn.cols, + mn.sec, + doc='Temperature in K', + bounds=(0, 500), + domain=NonNegativeReals, + ) + mn.Tr1nmin = Var( + mn.cols, + mn.sec, + mn.nc1, + doc='Temperature term for vapor pressure', + domain=NonNegativeReals, + bounds=(0, None), + ) + mn.Tr2nmin = Var( + mn.cols, + mn.sec, + mn.nc2, + doc='Temperature term for vapor pressure', + domain=NonNegativeReals, + bounds=(0, None), + ) + mn.Tr3nmin = Var( + mn.cols, + mn.sec, + mn.nc3, + doc='Temperature term for vapor pressure', + domain=NonNegativeReals, + bounds=(0, None), + ) + + @mn.Constraint(mn.cols, mn.sec, mn.nc1, doc="Temperature term for vapor pressure") + def _column1_reduced_temperature(mn, col, sec, nc): + """Calculate the reduced temperature for column 1. + + This function calculates the reduced temperature for column 1 based on the given parameters using the Peng-Robinson equation of state. + + Parameters + ---------- + mn : Pyomo ConcreteModel + The optimization model + col : int + The column index + sec : int + The section index + nc : int + The component index in column 1 + + Returns + ------- + Constraint + The constraint statement to calculate the reduced temperature. + """ + return mn.Tr1nmin[col, sec, nc] * mn.Tnmin[col, sec] == mn.prop[nc, 'TC'] + + @mn.Constraint(mn.cols, mn.sec, mn.nc2, doc="Temperature term for vapor pressure") + def _column2_reduced_temperature(mn, col, sec, nc): + """Calculate the reduced temperature for column 2. + + This function calculates the reduced temperature for column 2 based on the given parameters using the Peng-Robinson equation of state. + + Parameters + ---------- + mn : Pyomo ConcreteModel + The optimization model + col : int + The column index + sec : int + The section index + nc : int + The component index in column 2 + + Returns + ------- + Constraint + The constraint equation to calculate the reduced temperature + """ + return mn.Tr2nmin[col, sec, nc] * mn.Tnmin[col, sec] == mn.prop[nc, 'TC'] + + @mn.Constraint(mn.cols, mn.sec, mn.nc3, doc="Temperature term for vapor pressure") + def _column3_reduced_temperature(mn, col, sec, nc): + """Calculate the reduced temperature for column 3. + + This function calculates the reduced temperature for column 3 based on the given parameters. + + Parameters + ---------- + mn : Pyomo ConcreteModel + The optimization model + col : int + The column index + sec : int + The section index + nc : int + The component index in column 3 + + Returns + ------- + Constraint + The constraint equation to calculate the reduced temperature in column 3 + """ + return mn.Tr3nmin[col, sec, nc] * mn.Tnmin[col, sec] == mn.prop[nc, 'TC'] + + @mn.Constraint(mn.cols, mn.sec, doc="Boiling point temperature") + def _equilibrium_equation(mn, col, sec): + """Equilibrium equations for a given column and section. + + Parameters + ---------- + mn : Pyomo ConcreteModel + The optimization model object with properties + col : int + The column index + sec : int + The section index + + Returns + ------- + Constraint + The constraint equation to calculate the boiling point temperature using the Peng-Robinson equation of state + """ + if col == 1: + a = mn.Tr1nmin + b = mn.nc1 + elif col == 2: + a = mn.Tr2nmin + b = mn.nc2 + elif col == 3: + a = mn.Tr3nmin + b = mn.nc3 + return ( + sum( + xi_nmin[col, sec, nc] + * mn.prop[nc, 'PC'] + * exp( + a[col, sec, nc] + * ( + mn.prop[nc, 'vpA'] + * (1 - mn.Tnmin[col, sec] / mn.prop[nc, 'TC']) + + mn.prop[nc, 'vpB'] + * (1 - mn.Tnmin[col, sec] / mn.prop[nc, 'TC']) ** 1.5 + + mn.prop[nc, 'vpC'] + * (1 - mn.Tnmin[col, sec] / mn.prop[nc, 'TC']) ** 3 + + mn.prop[nc, 'vpD'] + * (1 - mn.Tnmin[col, sec] / mn.prop[nc, 'TC']) ** 6 + ) + ) + / Pnmin[sec] + for nc in b + ) + == 1 + ) + + mn.OBJ = Objective(expr=1, sense=minimize) + + #### + + SolverFactory('ipopt').solve(mn) + + yc = {} # Vapor composition + kl = {} # Light key component + kh = {} # Heavy key component + alpha = {} # Relative volatility of kl + ter = {} # Term to calculate the minimum number of trays + Nmin = {} # Minimum number of stages + Nminopt = {} # Total optimal minimum number of trays + Nfeed = {} # Side feed optimal location using Kirkbride's method: + # 1 = number of trays in rectifying section and + # 2 = number of trays in stripping section + side_feed = {} # Side feed location + av_alpha = {} # Average relative volatilities + xi_lhc = {} # Liquid composition in columns + rel = mn.Bdes / mn.Ddes # Ratio between products flowrates + ln = {} # Light component for the different columns + hn = {} # Heavy component for the different columns + ln[1] = lc_c1 + ln[2] = lc_c2 + ln[3] = lc_c3 + hn[1] = hc_c1 + hn[2] = hc_c2 + hn[3] = hc_c3 + + for col in mn.cols: + if col == 1: + b = mn.nc1 + elif col == 2: + b = mn.nc2 + else: + b = mn.nc3 + # For each component in the column and section calculate the vapor composition with the Peng-Robinson equation of state + for sec in mn.sec: + for nc in b: + yc[col, sec, nc] = ( + xi_nmin[col, sec, nc] + * mn.prop[nc, 'PC'] + * exp( + mn.prop[nc, 'TC'] + / value(mn.Tnmin[col, sec]) + * ( + mn.prop[nc, 'vpA'] + * (1 - value(mn.Tnmin[col, sec]) / mn.prop[nc, 'TC']) + + mn.prop[nc, 'vpB'] + * (1 - value(mn.Tnmin[col, sec]) / mn.prop[nc, 'TC']) ** 1.5 + + mn.prop[nc, 'vpC'] + * (1 - value(mn.Tnmin[col, sec]) / mn.prop[nc, 'TC']) ** 3 + + mn.prop[nc, 'vpD'] + * (1 - value(mn.Tnmin[col, sec]) / mn.prop[nc, 'TC']) ** 6 + ) + ) + ) / Pnmin[ + sec + ] # Vapor composition in the different sections for the different components in the columns + + for col in mn.cols: + # Calculate the relative volatility of the light and heavy components in the different sections for the different columns + xi_lhc[col, 4] = ( + xi_nmin[col, 1, ln[col]] / xi_nmin[col, 3, hn[col]] + ) # Liquid composition in the different sections with the initial liquid composition of the components in the different sections and columns and ln and hn which are the light and heavy components in the different columns + for sec in mn.sec: + kl[col, sec] = ( + yc[col, sec, ln[col]] / xi_nmin[col, sec, ln[col]] + ) # Light component in the different sections + kh[col, sec] = ( + yc[col, sec, hn[col]] / xi_nmin[col, sec, hn[col]] + ) # Heavy component in the different sections + xi_lhc[col, sec] = ( + xi_nmin[col, sec, hn[col]] / xi_nmin[col, sec, ln[col]] + ) # Liquid composition in the different sections + alpha[col, sec] = ( + kl[col, sec] / kh[col, sec] + ) # Relative volatility in the different sections + + for col in mn.cols: + # Calculate the average relative volatilities and the minimum number of trays with Fenske's and Kirkbride's method + av_alpha[col] = (alpha[col, 1] * alpha[col, 2] * alpha[col, 3]) ** ( + 1 / 3 + ) # Average relative volatilities calculated with the relative volatilities of the components in the three sections + Nmin[col] = log10((1 / xi_lhc[col, 3]) * xi_lhc[col, 1]) / log10( + av_alpha[col] + ) # Minimum number of trays calculated with Fenske's method + ter[col] = ( + rel * xi_lhc[col, 2] * (xi_lhc[col, 4] ** 2) + ) ** 0.206 # Term to calculate the minimum number of trays with Kirkbride's method + # Side feed optimal location using Kirkbride's method + Nfeed[1, col] = ( + ter[col] * Nmin[col] / (1 + ter[col]) + ) # Number of trays in rectifying section + Nfeed[2, col] = Nfeed[1, col] / ter[col] # Number of trays in stripping section + side_feed[col] = Nfeed[2, col] # Side feed location + + m.Nmintot = sum(Nmin[col] for col in mn.cols) # Total minimum number of trays + m.Knmin = int(m.Nmintot) + 1 # Total optimal minimum number of trays + + m.TB0 = value(mn.Tnmin[1, 1]) # Reboiler temperature in K in column 1 + m.Tf0 = value(mn.Tnmin[1, 2]) # Side feed temperature in K in column 1 + m.TD0 = value(mn.Tnmin[2, 3]) # Distillate temperature in K in column 2 + + return m + + +if __name__ == "__main__": + initialize_kaibel() diff --git a/gdplib/kaibel/kaibel_solve_gdp.py b/gdplib/kaibel/kaibel_solve_gdp.py index 310cb26..7aca6f6 100644 --- a/gdplib/kaibel/kaibel_solve_gdp.py +++ b/gdplib/kaibel/kaibel_solve_gdp.py @@ -1,2661 +1,2662 @@ -""" Kaibel Column model: GDP formulation. - -The solution requires the specification of certain parameters, such as the number trays, feed location, etc., and an initialization procedure, which consists of the next three steps: -(i) a preliminary design of the separation considering a sequence of indirect continuous distillation columns (CDCs) to obtain the minimum number of stages with Fenske Equation in the function initialize_kaibel in kaibel_init.py -(ii) flash calculation for the feed with the function calc_side_feed_flash in kaibel_side_flash.py -(iii) calculation of variable bounds by solving the NLP problem. - -After the initialization, the GDP model is built. -""" - -from math import copysign - -from pyomo.environ import ( - Constraint, - exp, - minimize, - NonNegativeReals, - Objective, - RangeSet, - Set, - Var, -) -from pyomo.gdp import Disjunct - -from gdplib.kaibel.kaibel_init import initialize_kaibel - -from gdplib.kaibel.kaibel_side_flash import calc_side_feed_flash -# from .kaibel_side_flash import calc_side_feed_flash - - -def build_model(): - """ - Build the GDP Kaibel Column model. - It combines the initialization of the model and the flash calculation for the side feed before the GDP formulation. - - Returns - ------- - ConcreteModel - The constructed GDP Kaibel Column model. - """ - - # Calculation of the theoretical minimum number of trays (Knmin) and initial temperature values (TB0, Tf0, TD0). - m = initialize_kaibel() - - # Side feed init. Returns side feed vapor composition yfi and vapor fraction q_init - m = calc_side_feed_flash(m) - - m.name = "GDP Kaibel Column" - - #### Calculated initial values - m.Treb = m.TB0 + 5 # Reboiler temperature [K] - m.Tbot = m.TB0 # Bottom-most tray temperature [K] - m.Ttop = m.TD0 # Top-most tray temperature [K] - m.Tcon = m.TD0 - 5 # Condenser temperature [K] - - m.dv0 = {} # Initial vapor distributor value - m.dl0 = {} # Initial liquid distributor value - m.dv0[2] = 0.516 - m.dv0[3] = 1 - m.dv0[2] - m.dl0[2] = 0.36 - m.dl0[3] = 1 - m.dl0[2] - - #### Calculated upper and lower bounds - m.min_tray = m.Knmin * 0.8 # Lower bound on number of trays - m.Tlo = m.Tcon - 20 # Temperature lower bound - m.Tup = m.Treb + 20 # Temperature upper bound - - m.flow_max = 1e3 # Flowrates upper bound [mol/s] - m.Qmax = 60 # Heat loads upper bound [J/s] - - #### Column tray details - m.num_trays = m.np # Trays per section. np = 25 - m.min_num_trays = 10 # Minimum number of trays per section - m.num_total = m.np * 3 # Total number of trays - m.feed_tray = 12 # Side feed tray - m.sideout1_tray = 8 # Side outlet 1 tray - m.sideout2_tray = 17 # Side outlet 2 tray - m.reb_tray = 1 # Reboiler tray. Dividing wall starting tray - m.con_tray = m.num_trays # Condenser tray. Dividing wall ending tray - - # ------------------------------------------------------------------ - - # Beginning of model - - # ------------------------------------------------------------------ - - ## Sets - m.section = RangeSet( - 4, doc="Column sections:1=top, 2=feed side, 3=prod side, 4=bot" - ) - m.section_main = Set(initialize=[1, 4], doc="Main sections of the column") - - m.tray = RangeSet(m.np, doc="Potential trays in each section") - m.tray_total = RangeSet(m.num_total, doc="Total trays in the column") - m.tray_below_feed = RangeSet(m.feed_tray, doc="Trays below feed") - m.tray_below_so1 = RangeSet(m.sideout1_tray, doc="Trays below side outlet 1") - m.tray_below_so2 = RangeSet(m.sideout2_tray, doc="Trays below side outlet 2") - - m.comp = RangeSet(4, doc="Components") - m.dw = RangeSet(2, 3, doc="Dividing wall sections") - m.cplv = RangeSet(2, doc="Heat capacity: 1=liquid, 2=vapor") - m.so = RangeSet(2, doc="Side product outlets") - m.bounds = RangeSet(2, doc="Number of boundary condition values") - - m.candidate_trays_main = Set( - initialize=m.tray - [m.con_tray, m.reb_tray], - doc="Candidate trays for top and \ - bottom sections 1 and 4", - ) - m.candidate_trays_feed = Set( - initialize=m.tray - [m.con_tray, m.feed_tray, m.reb_tray], - doc="Candidate trays for feed section 2", - ) - m.candidate_trays_product = Set( - initialize=m.tray - [m.con_tray, m.sideout1_tray, m.sideout2_tray, m.reb_tray], - doc="Candidate trays for product section 3", - ) - - ## Calculation of initial values - m.dHvap = {} # Heat of vaporization [J/mol] - - m.P0 = {} # Initial pressure [bar] - m.T0 = {} # Initial temperature [K] - m.L0 = {} # Initial individual liquid flowrate [mol/s] - m.V0 = {} # Initial individual vapor flowrate [mol/s] - m.Vtotal0 = {} # Initial total vapor flowrate [mol/s] - m.Ltotal0 = {} # Initial liquid flowrate [mol/s] - m.x0 = {} # Initial liquid composition - m.y0 = {} # Initial vapor composition - m.actv0 = {} # Initial activity coefficients - m.cpdT0 = {} # Initial heat capacity for liquid and vapor phases [J/mol/K] - m.hl0 = {} # Initial liquid enthalpy [J/mol] - m.hv0 = {} # Initial vapor enthalpy [J/mol] - m.Pi = m.Preb # Initial given pressure value [bar] - m.Ti = {} # Initial known temperature values [K] - - ## Initial values for pressure, temperature, flowrates, composition, and enthalpy - for sec in m.section: - for n_tray in m.tray: - m.P0[sec, n_tray] = m.Pi - - for sec in m.section: - for n_tray in m.tray: - for comp in m.comp: - m.L0[sec, n_tray, comp] = m.Li - m.V0[sec, n_tray, comp] = m.Vi - - for sec in m.section: - for n_tray in m.tray: - m.Ltotal0[sec, n_tray] = sum(m.L0[sec, n_tray, comp] for comp in m.comp) - m.Vtotal0[sec, n_tray] = sum(m.V0[sec, n_tray, comp] for comp in m.comp) - - for n_tray in m.tray_total: - if n_tray == m.reb_tray: - m.Ti[n_tray] = m.Treb - elif n_tray == m.num_total: - m.Ti[n_tray] = m.Tcon - else: - m.Ti[n_tray] = m.Tbot + (m.Ttop - m.Tbot) * (n_tray - 2) / (m.num_total - 3) - - for n_tray in m.tray_total: - if n_tray <= m.num_trays: - m.T0[1, n_tray] = m.Ti[n_tray] - elif n_tray >= m.num_trays and n_tray <= m.num_trays * 2: - m.T0[2, n_tray - m.num_trays] = m.Ti[n_tray] - m.T0[3, n_tray - m.num_trays] = m.Ti[n_tray] - elif n_tray >= m.num_trays * 2: - m.T0[4, n_tray - m.num_trays * 2] = m.Ti[n_tray] - - ## Initial vapor and liquid composition of the feed and activity coefficients - for sec in m.section: - for n_tray in m.tray: - for comp in m.comp: - m.x0[sec, n_tray, comp] = m.xfi[comp] - m.actv0[sec, n_tray, comp] = 1 - m.y0[sec, n_tray, comp] = m.xfi[comp] - - ## Assigns the enthalpy boundary values, heat capacity, heat of vaporization calculation, temperature bounds, and light and heavy key components. - hlb = {} # Liquid enthalpy [J/mol] - hvb = {} # Vapor enthalpy [J/mol] - cpb = {} # Heact capacity [J/mol/K] - dHvapb = {} # Heat of vaporization [J/mol] - Tbounds = {} # Temperature bounds [K] - kc = {} # Light and heavy key components - Tbounds[1] = m.Tcon # Condenser temperature [K] - Tbounds[2] = m.Treb # Reboiler temperature [K] - kc[1] = m.lc - kc[2] = m.hc - - ## Heat of vaporization calculation for each component in the feed. - for comp in m.comp: - dHvapb[comp] = -( - m.Rgas - * m.prop[comp, 'TC'] - * ( - m.prop[comp, 'vpA'] * (1 - m.Tref / m.prop[comp, 'TC']) - + m.prop[comp, 'vpB'] * (1 - m.Tref / m.prop[comp, 'TC']) ** 1.5 - + m.prop[comp, 'vpC'] * (1 - m.Tref / m.prop[comp, 'TC']) ** 3 - + m.prop[comp, 'vpD'] * (1 - m.Tref / m.prop[comp, 'TC']) ** 6 - ) - + m.Rgas - * m.Tref - * ( - m.prop[comp, 'vpA'] - + 1.5 * m.prop[comp, 'vpB'] * (1 - m.Tref / m.prop[comp, 'TC']) ** 0.5 - + 3 * m.prop[comp, 'vpC'] * (1 - m.Tref / m.prop[comp, 'TC']) ** 2 - + 6 * m.prop[comp, 'vpD'] * (1 - m.Tref / m.prop[comp, 'TC']) ** 5 - ) - ) - - ## Boundary values for heat capacity and enthalpy of liquid and vapor phases for light and heavy key components in the feed. - for b in m.bounds: - for cp in m.cplv: - cpb[b, cp] = m.cpc[cp] * ( - (Tbounds[b] - m.Tref) * m.prop[kc[b], 'cpA', cp] - + (Tbounds[b] ** 2 - m.Tref**2) - * m.prop[kc[b], 'cpB', cp] - * m.cpc2['A', cp] - / 2 - + (Tbounds[b] ** 3 - m.Tref**3) - * m.prop[kc[b], 'cpC', cp] - * m.cpc2['B', cp] - / 3 - + (Tbounds[b] ** 4 - m.Tref**4) * m.prop[kc[b], 'cpD', cp] / 4 - ) - hlb[b] = cpb[b, 1] - hvb[b] = cpb[b, 2] + dHvapb[b] - - m.hllo = ( - (1 - copysign(0.2, hlb[1])) * hlb[1] / m.Hscale - ) # Liquid enthalpy lower bound - m.hlup = ( - (1 + copysign(0.2, hlb[2])) * hlb[2] / m.Hscale - ) # Liquid enthalpy upper bound - m.hvlo = ( - (1 - copysign(0.2, hvb[1])) * hvb[1] / m.Hscale - ) # Vapor enthalpy lower bound - m.hvup = ( - (1 + copysign(0.2, hvb[2])) * hvb[2] / m.Hscale - ) # Vapor enthalpy upper bound - # copysign is a function that returns the first argument with the sign of the second argument - - ## Heat of vaporization for each component in the feed scaled by Hscale - for comp in m.comp: - m.dHvap[comp] = dHvapb[comp] / m.Hscale - - ## Heat capacity calculation for liquid and vapor phases using Ruczika-D method for each component in the feed, section, and tray - for sec in m.section: - for n_tray in m.tray: - for comp in m.comp: - for cp in m.cplv: - m.cpdT0[sec, n_tray, comp, cp] = ( - m.cpc[cp] - * ( - (m.T0[sec, n_tray] - m.Tref) * m.prop[comp, 'cpA', cp] - + (m.T0[sec, n_tray] ** 2 - m.Tref**2) - * m.prop[comp, 'cpB', cp] - * m.cpc2['A', cp] - / 2 - + (m.T0[sec, n_tray] ** 3 - m.Tref**3) - * m.prop[comp, 'cpC', cp] - * m.cpc2['B', cp] - / 3 - + (m.T0[sec, n_tray] ** 4 - m.Tref**4) - * m.prop[comp, 'cpD', cp] - / 4 - ) - / m.Hscale - ) - - ## Liquid and vapor enthalpy calculation using Ruczika-D method for each component in the feed, section, and tray - for sec in m.section: - for n_tray in m.tray: - for comp in m.comp: - m.hl0[sec, n_tray, comp] = m.cpdT0[sec, n_tray, comp, 1] - m.hv0[sec, n_tray, comp] = m.cpdT0[sec, n_tray, comp, 2] + m.dHvap[comp] - - #### Side feed - m.cpdTf = {} # Heat capacity for side feed [J/mol K] - m.hlf = {} # Liquid enthalpy for side feed [J/mol] - m.hvf = {} # Vapor enthalpy for side feed [J/mol] - m.F0 = {} # Side feed flowrate per component [mol/s] - - ## Heat capacity in liquid and vapor phases for side feed for each component using Ruczika-D method - for comp in m.comp: - for cp in m.cplv: - m.cpdTf[comp, cp] = ( - m.cpc[cp] - * ( - (m.Tf - m.Tref) * m.prop[comp, 'cpA', cp] - + (m.Tf**2 - m.Tref**2) - * m.prop[comp, 'cpB', cp] - * m.cpc2['A', cp] - / 2 - + (m.Tf**3 - m.Tref**3) - * m.prop[comp, 'cpC', cp] - * m.cpc2['B', cp] - / 3 - + (m.Tf**4 - m.Tref**4) * m.prop[comp, 'cpD', cp] / 4 - ) - / m.Hscale - ) - - ## Side feed flowrate and liquid and vapor enthalpy calculation using Ruczika-D method for each component in the feed - for comp in m.comp: - m.F0[comp] = ( - m.xfi[comp] * m.Fi - ) # Side feed flowrate per component computed from the feed composition and flowrate Fi - m.hlf[comp] = m.cpdTf[ - comp, 1 - ] # Liquid enthalpy for side feed computed from the heat capacity for side feed and liquid phase - m.hvf[comp] = ( - m.cpdTf[comp, 2] + m.dHvap[comp] - ) # Vapor enthalpy for side feed computed from the heat capacity for side feed and vapor phase and heat of vaporization - - m.P = Var( - m.section, - m.tray, - doc="Pressure at each potential tray in bars", - domain=NonNegativeReals, - bounds=(m.Pcon, m.Preb), - initialize=m.P0, - ) - m.T = Var( - m.section, - m.tray, - doc="Temperature at each potential tray [K]", - domain=NonNegativeReals, - bounds=(m.Tlo, m.Tup), - initialize=m.T0, - ) - - m.x = Var( - m.section, - m.tray, - m.comp, - doc="Liquid composition", - domain=NonNegativeReals, - bounds=(0, 1), - initialize=m.x0, - ) - m.y = Var( - m.section, - m.tray, - m.comp, - doc="Vapor composition", - domain=NonNegativeReals, - bounds=(0, 1), - initialize=m.y0, - ) - - m.dl = Var( - m.dw, - doc="Liquid distributor in the dividing wall sections", - bounds=(0.2, 0.8), - initialize=m.dl0, - ) - m.dv = Var( - m.dw, - doc="Vapor distributor in the dividing wall sections", - bounds=(0, 1), - domain=NonNegativeReals, - initialize=m.dv0, - ) - - m.V = Var( - m.section, - m.tray, - m.comp, - doc="Vapor flowrate [mol/s]", - domain=NonNegativeReals, - bounds=(0, m.flow_max), - initialize=m.V0, - ) - m.L = Var( - m.section, - m.tray, - m.comp, - doc="Liquid flowrate [mol/s]", - domain=NonNegativeReals, - bounds=(0, m.flow_max), - initialize=m.L0, - ) - m.Vtotal = Var( - m.section, - m.tray, - doc="Total vapor flowrate [mol/s]", - domain=NonNegativeReals, - bounds=(0, m.flow_max), - initialize=m.Vtotal0, - ) - m.Ltotal = Var( - m.section, - m.tray, - doc="Total liquid flowrate [mol/s]", - domain=NonNegativeReals, - bounds=(0, m.flow_max), - initialize=m.Ltotal0, - ) - - m.D = Var( - m.comp, - doc="Distillate flowrate [mol/s]", - domain=NonNegativeReals, - bounds=(0, m.flow_max), - initialize=m.Ddes, - ) - m.B = Var( - m.comp, - doc="Bottoms flowrate [mol/s]", - domain=NonNegativeReals, - bounds=(0, m.flow_max), - initialize=m.Bdes, - ) - m.S = Var( - m.so, - m.comp, - doc="Product 2 and 3 flowrates [mol/s]", - domain=NonNegativeReals, - bounds=(0, m.flow_max), - initialize=m.Sdes, - ) - m.Dtotal = Var( - doc="Distillate flowrate [mol/s]", - domain=NonNegativeReals, - bounds=(0, m.flow_max), - initialize=m.Ddes, - ) - m.Btotal = Var( - doc="Bottoms flowrate [mol/s]", - domain=NonNegativeReals, - bounds=(0, m.flow_max), - initialize=m.Bdes, - ) - m.Stotal = Var( - m.so, - doc="Total product 2 and 3 side flowrate [mol/s]", - domain=NonNegativeReals, - bounds=(0, m.flow_max), - initialize=m.Sdes, - ) - - m.hl = Var( - m.section, - m.tray, - m.comp, - doc='Liquid enthalpy [J/mol]', - bounds=(m.hllo, m.hlup), - initialize=m.hl0, - ) - m.hv = Var( - m.section, - m.tray, - m.comp, - doc='Vapor enthalpy [J/mol]', - bounds=(m.hvlo, m.hvup), - initialize=m.hv0, - ) - m.Qreb = Var( - doc="Reboiler heat duty [J/s]", - domain=NonNegativeReals, - bounds=(0, m.Qmax), - initialize=1, - ) - m.Qcon = Var( - doc="Condenser heat duty [J/s]", - domain=NonNegativeReals, - bounds=(0, m.Qmax), - initialize=1, - ) - - m.rr = Var( - doc="Internal reflux ratio in the column", - domain=NonNegativeReals, - bounds=(0.7, 1), - initialize=m.rr0, - ) - m.bu = Var( - doc="Boilup rate in the reboiler", - domain=NonNegativeReals, - bounds=(0.7, 1), - initialize=m.bu0, - ) - - m.F = Var( - m.comp, - doc="Side feed flowrate [mol/s]", - domain=NonNegativeReals, - bounds=(0, 50), - initialize=m.F0, - ) - m.q = Var( - doc="Vapor fraction in side feed", - domain=NonNegativeReals, - bounds=(0, 1), - initialize=1, - ) - - m.actv = Var( - m.section, - m.tray, - m.comp, - doc="Liquid activity coefficient", - domain=NonNegativeReals, - bounds=(0, 10), - initialize=m.actv0, - ) - - m.errx = Var( - m.section, - m.tray, - doc="Error in liquid composition [mol/mol]", - bounds=(-1e-3, 1e-3), - initialize=0, - ) - m.erry = Var( - m.section, - m.tray, - doc="Error in vapor composition [mol/mol]", - bounds=(-1e-3, 1e-3), - initialize=0, - ) - m.slack = Var( - m.section, - m.tray, - m.comp, - doc="Slack variable", - bounds=(-1e-8, 1e-8), - initialize=0, - ) - - m.tray_exists = Disjunct( - m.section, - m.tray, - doc="Disjunct that enforce the existence of each tray", - rule=_build_tray_equations, - ) - m.tray_absent = Disjunct( - m.section, - m.tray, - doc="Disjunct that enforce the absence of each tray", - rule=_build_pass_through_eqns, - ) - - @m.Disjunction( - m.section, m.tray, doc="Disjunction between whether each tray exists or not" - ) - def tray_exists_or_not(m, sec, n_tray): - """ - Disjunction between whether each tray exists or not. - - Parameters - ---------- - m : Pyomo ConcreteModel - The Pyomo model object. - sec : int - The section index. - n_tray : int - The tray index. - - Returns - ------- - Disjunction - The disjunction between whether each tray exists or not. - """ - return [m.tray_exists[sec, n_tray], m.tray_absent[sec, n_tray]] - - @m.Constraint(m.section_main) - def minimum_trays_main(m, sec): - """ - Constraint that ensures the minimum number of trays in the main section. - - Parameters - ---------- - m : Pyomo ConcreteModel - The model object for the GDP Kaibel Column. - sec : Set - The section index. - - Returns - ------- - Constraint - A constraint expression that enforces the minimum number of trays in the main section to be greater than or equal to the minimum number of trays. - """ - return ( - sum( - m.tray_exists[sec, n_tray].binary_indicator_var - for n_tray in m.candidate_trays_main - ) - + 1 - >= m.min_num_trays - ) - - @m.Constraint() - def minimum_trays_feed(m): - """ - Constraint function that ensures the minimum number of trays in the feed section is met. - - Parameters - ---------- - m : Pyomo ConcreteModel - The Pyomo model object. - - Returns - ------- - Constraint - The constraint expression that enforces the minimum number of trays is greater than or equal to the minimum number of trays. - """ - return ( - sum( - m.tray_exists[2, n_tray].binary_indicator_var - for n_tray in m.candidate_trays_feed - ) - + 1 - >= m.min_num_trays - ) - - # TOCHECK: pyomo.GDP Syntax - - @m.Constraint() - def minimum_trays_product(m): - """ - Constraint function to calculate the minimum number of trays in the product section. - - Parameters - ---------- - m : Pyomo ConcreteModel - The optimization model. - - Returns - ------- - Constraint - The constraint expression that enforces the minimum number of trays is greater than or equal to the minimum number of trays. - """ - return ( - sum( - m.tray_exists[3, n_tray].binary_indicator_var - for n_tray in m.candidate_trays_product - ) - + 1 - >= m.min_num_trays - ) - - ## Fixed trays - enforce_tray_exists(m, 1, 1) # reboiler - enforce_tray_exists(m, 1, m.num_trays) # vapor distributor - enforce_tray_exists(m, 2, 1) # dividing wall starting tray - enforce_tray_exists(m, 2, m.feed_tray) # feed tray - enforce_tray_exists(m, 2, m.num_trays) # dividing wall ending tray - enforce_tray_exists(m, 3, 1) # dividing wall starting tray - enforce_tray_exists(m, 3, m.sideout1_tray) # side outlet 1 for product 3 - enforce_tray_exists(m, 3, m.sideout2_tray) # side outlet 2 for product 2 - enforce_tray_exists(m, 3, m.num_trays) # dividing wall ending tray - enforce_tray_exists(m, 4, 1) # liquid distributor - enforce_tray_exists(m, 4, m.num_trays) # condenser - - #### Global constraints - @m.Constraint( - m.dw, m.tray, doc="Monotonic temperature in the dividing wall sections" - ) - def monotonic_temperature(m, sec, n_tray): - """This function returns a constraint object representing the monotonic temperature constraint. - - The monotonic temperature constraint ensures that the temperature on each tray in the distillation column - is less than or equal to the temperature on the top tray. - - Parameters - ---------- - m : Pyomo ConcreteModel - The Pyomo model object. - sec : Set - The set of sections in the dividing wall sections. - n_tray : Set - The set of trays in the distillation column. - - Returns - ------- - Constraint - The monotonic temperature constraint specifying that the temperature on each tray is less than or equal to the temperature on the top tray of section 1 which is the condenser. - """ - return m.T[sec, n_tray] <= m.T[1, m.num_trays] - - @m.Constraint(doc="Liquid distributor") - def liquid_distributor(m): - """Defines the liquid distributor constraint. - - This constraint ensures that the sum of the liquid distributors in all sections is equal to 1. - - Parameters - ---------- - m : Pyomo ConcreteModel - The optimization model. - - Returns - ------- - Constraint - The liquid distributor constraint that enforces the sum of the liquid flow rates in all sections is equal to 1. - - """ - return sum(m.dl[sec] for sec in m.dw) - 1 == 0 - - @m.Constraint(doc="Vapor distributor") - def vapor_distributor(m): - """ - Add a constraint to ensure that the sum of the vapor distributors is equal to 1. - - Parameters - ---------- - m : Pyomo ConcreteModel - The Pyomo model object. - - Returns - ------- - Constraint - The vapor distributor constraint. - """ - return sum(m.dv[sec] for sec in m.dw) - 1 == 0 - - @m.Constraint(doc="Reboiler composition specification") - def heavy_product(m): - """ - Reboiler composition specification for the heavy component in the feed. - - Parameters - ---------- - m : Pyomo ConcreteModel - The optimization model. - - Returns - ------- - Constraint - The constraint that enforces the reboiler composition is greater than or equal to the specified composition xspechc final liquid composition for butanol, the heavy component in the feed. - """ - return m.x[1, m.reb_tray, m.hc] >= m.xspec_hc - - @m.Constraint(doc="Condenser composition specification") - def light_product(m): - """ - Condenser composition specification for the light component in the feed. - - Parameters - ---------- - m : Model - The optimization model. - - Returns - ------- - Constraint - The constraint that enforces the condenser composition is greater than or equal to the specified final liquid composition for ethanol, xspeclc , the light component in the feed. - """ - return m.x[4, m.con_tray, m.lc] >= m.xspec_lc - - @m.Constraint(doc="Side outlet 1 final liquid composition") - def intermediate1_product(m): - ''' - This constraint ensures that the intermediate 1 final liquid composition is greater than or equal to the specified composition xspec_inter1, which is the final liquid composition for ethanol. - - Parameters - ---------- - m : Pyomo ConcreteModel. - The optimization model. - - Returns - ------- - Constraint - The constraint that enforces the intermediate 1 final liquid composition is greater than or equal to the specified composition xspec_inter1, which is the final liquid composition for ethanol. - ''' - return m.x[3, m.sideout1_tray, 3] >= m.xspec_inter3 - - @m.Constraint(doc="Side outlet 2 final liquid composition") - def intermediate2_product(m): - """ - This constraint ensures that the intermediate 2 final liquid composition is greater than or equal to the specified composition xspec_inter2, which is the final liquid composition for butanol. - - Parameters - ---------- - m : Pyomo ConcreteModel - The optimization model. - - Returns - ------- - Constraint - The constraint that enforces the intermediate 2 final liquid composition is greater than or equal to the specified composition xspec_inter2, which is the final liquid composition for butanol. - """ - return m.x[3, m.sideout2_tray, 2] >= m.xspec_inter2 - - @m.Constraint(doc="Reboiler flowrate") - def _heavy_product_flow(m): - """ - Reboiler flowrate constraint that ensures the reboiler flowrate is greater than or equal to the specified flowrate Bdes, which is the flowrate of butanol. - - Parameters - ---------- - m : Pyomo ConcreteModel - The optimization model. - - Returns - ------- - Constraint - The constraint that enforces the reboiler flowrate is greater than or equal to the specified flowrate Bdes, which is the flowrate of butanol. - """ - return m.Btotal >= m.Bdes - - @m.Constraint(doc="Condenser flowrate") - def _light_product_flow(m): - """ - Condenser flowrate constraint that ensures the condenser flowrate is greater than or equal to the specified flowrate Ddes, which is the flowrate of ethanol. - - Parameters - ---------- - m : Pyomo ConcreteModel - The optimization model. - - Returns - ------- - Constraint - The constraint that enforces the condenser flowrate is greater than or equal to the specified flowrate Ddes, which is the flowrate of ethanol. - """ - return m.Dtotal >= m.Ddes - - @m.Constraint(m.so, doc="Intermediate flowrate") - def _intermediate_product_flow(m, so): - """ - Intermediate flowrate constraint that ensures the intermediate flowrate is greater than or equal to the specified flowrate Sdes, which is the flowrate of the intermediate side product 2 and 3. - - Parameters - ---------- - m : Pyomo ConcreteModel - The optimization model. - so : int - The side product outlet index. - - Returns - ------- - Constraint - The constraint that enforces the intermediate flowrate is greater than or equal to the specified flowrate Sdes, which is the flowrate of the intermediate side product 2 and 3. - """ - return m.Stotal[so] >= m.Sdes - - @m.Constraint(doc="Internal boilup ratio, V/L") - def _internal_boilup_ratio(m): - """ - Internal boilup ratio constraint that ensures the internal boilup ratio is equal to the boilup rate times the liquid flowrate on the reboiler tray is equal to the vapor flowrate on the tray above the reboiler tray. - - Parameters - ---------- - m : Pyomo ConcreteModel - The optimization model. - - Returns - ------- - Constraint - The constraint that enforces the boilup rate times the liquid flowrate on the reboiler tray is equal to the vapor flowrate on the tray above the reboiler tray. - """ - return m.bu * m.Ltotal[1, m.reb_tray + 1] == m.Vtotal[1, m.reb_tray] - - @m.Constraint(doc="Internal reflux ratio, L/V") - def internal_reflux_ratio(m): - """ - Internal reflux ratio constraint that ensures the internal reflux ratio is equal to the reflux rate times the vapor flowrate on the tray above the condenser tray is equal to the liquid flowrate on the condenser tray. - - Parameters - ---------- - m : Pyomo ConcreteModel - The optimization model. - - Returns - ------- - Constraint - The constraint that enforces the reflux rate times the vapor flowrate on the tray above the condenser tray is equal to the liquid flowrate on the condenser tray. - """ - return m.rr * m.Vtotal[4, m.con_tray - 1] == m.Ltotal[4, m.con_tray] - - @m.Constraint(doc="External boilup ratio relation with bottoms") - def _external_boilup_ratio(m): - """ - External boilup ratio constraint that ensures the external boilup ratio times the liquid flowrate on the reboiler tray is equal to the bottoms flowrate. - - Parameters - ---------- - m : Pyomo ConcreteModel - The optimization model. - - Returns - ------- - Constraint - The constraint that enforces the external boilup ratio times the liquid flowrate on the reboiler tray is equal to the bottoms flowrate. - """ - return m.Btotal == (1 - m.bu) * m.Ltotal[1, m.reb_tray + 1] - - @m.Constraint(doc="External reflux ratio relation with distillate") - def _external_reflux_ratio(m): - """ - External reflux ratio constraint that ensures the external reflux ratio times the vapor flowrate on the tray above the condenser tray is equal to the distillate flowrate. - - Parameters - ---------- - m : Pyomo ConcreteModel - The optimization model. - - Returns - ------- - Constraint - The constraint that enforces the external reflux ratio times the vapor flowrate on the tray above the condenser tray is equal to the distillate flowrate. - """ - return m.Dtotal == (1 - m.rr) * m.Vtotal[4, m.con_tray - 1] - - @m.Constraint(m.section, m.tray, doc="Total vapor flowrate") - def _total_vapor_flowrate(m, sec, n_tray): - """ - Constraint that ensures the total vapor flowrate is equal to the sum of the vapor flowrates of each component on each tray. - - Parameters - ---------- - m : Pyomo ConcreteModel - The optimization model. - sec : int - The section index. - n_tray : int - The tray index. - - Returns - ------- - Constraint - The constraint that enforces the total vapor flowrate is equal to the sum of the vapor flowrates of each component on each tray on each section. - """ - return sum(m.V[sec, n_tray, comp] for comp in m.comp) == m.Vtotal[sec, n_tray] - - @m.Constraint(m.section, m.tray, doc="Total liquid flowrate") - def _total_liquid_flowrate(m, sec, n_tray): - """ - Constraint that ensures the total liquid flowrate is equal to the sum of the liquid flowrates of each component on each tray on each section. - - Parameters - ---------- - m : Pyomo ConcreteModel - The optimization model. - sec : int - The section index. - n_tray : int - The tray index. - - Returns - ------- - Constraint - The constraint that enforces the total liquid flowrate is equal to the sum of the liquid flowrates of each component on each tray on each section. - """ - return sum(m.L[sec, n_tray, comp] for comp in m.comp) == m.Ltotal[sec, n_tray] - - @m.Constraint(m.comp, doc="Bottoms and liquid relation") - def bottoms_equality(m, comp): - """ - Constraint that ensures the bottoms flowrate is equal to the liquid flowrate of each component on the reboiler tray. - - Parameters - ---------- - m : Pyomo ConcreteModel - The optimization model. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint that enforces the bottoms flowrate is equal to the liquid flowrate of each component on the reboiler tray. - """ - return m.B[comp] == m.L[1, m.reb_tray, comp] - - @m.Constraint(m.comp) - def condenser_total(m, comp): - """ - Constraint that ensures the distillate flowrate in the condenser is null. - - Parameters - ---------- - m : Pyomo ConcreteModel - The optimization model. - comp : int - The component index. - Returns - ------- - Constraint - The constraint that enforces the distillate flowrate is equal to zero in the condenser. - """ - return m.V[4, m.con_tray, comp] == 0 - - @m.Constraint() - def total_bottoms_product(m): - """ - Constraint that ensures the total bottoms flowrate is equal to the sum of the bottoms flowrates of each component. - - Parameters - ---------- - m : Pyomo ConcreteModel - The optimization model. - - Returns - ------- - Constraint - The constraint that enforces the total bottoms flowrate is equal to the sum of the bottoms flowrates of each component. - """ - return sum(m.B[comp] for comp in m.comp) == m.Btotal - - @m.Constraint() - def total_distillate_product(m): - """ - Constraint that ensures the total distillate flowrate is equal to the sum of the distillate flowrates of each component. - - Parameters - ---------- - m : Pyomo ConcreteModel - The optimization model. - - Returns - ------- - Constraint - The constraint that enforces the total distillate flowrate is equal to the sum of the distillate flowrates of each component. - """ - return sum(m.D[comp] for comp in m.comp) == m.Dtotal - - @m.Constraint(m.so) - def total_side_product(m, so): - """ - Constraint that ensures the total side product flowrate is equal to the sum of the side product flowrates of each component. - - Parameters - ---------- - m : Pyomo ConcreteModel - The optimization model. - so : int - The side product index, 2 or 3 for the intermediate side products. - - Returns - ------- - Constraint - The constraint that enforces the total side product flowrate is equal to the sum of the side product flowrates of each component. - """ - return sum(m.S[so, comp] for comp in m.comp) == m.Stotal[so] - - # Considers the number of existent trays and operating costs (condenser and reboiler heat duties) in the column. To ensure equal weights to the capital and operating costs, the number of existent trays is multiplied by a weight coefficient of 1000. - - m.obj = Objective( - expr=(m.Qcon + m.Qreb) * m.Hscale - + 1e3 - * ( - sum( - sum( - m.tray_exists[sec, n_tray].binary_indicator_var - for n_tray in m.candidate_trays_main - ) - for sec in m.section_main - ) - + sum( - m.tray_exists[2, n_tray].binary_indicator_var - for n_tray in m.candidate_trays_feed - ) - + sum( - m.tray_exists[3, n_tray].binary_indicator_var - for n_tray in m.candidate_trays_product - ) - + 1 - ), - sense=minimize, - doc="Objective function to minimize the operating costs and number of existent trays in the column", - ) - - @m.Constraint( - m.section_main, m.candidate_trays_main, doc="Logic proposition for main section" - ) - def _logic_proposition_main(m, sec, n_tray): - """ - Apply a logic proposition constraint to the main section and candidate trays to specify the order of trays in the column is from bottom to top provided the condition is met. - - Parameters - ---------- - m : Pyomo ConcreteModel - The optimization model. - sec : int - The section index. - n_tray : int - The tray index. - - Returns - ------- - Constraint or NoConstraint - The constraint expression or NoConstraint if the condition is not met. - """ - - if n_tray > m.reb_tray and (n_tray + 1) < m.num_trays: - return ( - m.tray_exists[sec, n_tray].binary_indicator_var - <= m.tray_exists[sec, n_tray + 1].binary_indicator_var - ) - else: - return Constraint.NoConstraint - # TOCHECK: Update the logic proposition constraint for the main section with the new pyomo.gdp syntax - - @m.Constraint(m.candidate_trays_feed) - def _logic_proposition_feed(m, n_tray): - """ - Apply a logic proposition constraint to the feed section and candidate trays to specify the order of trays in the column is from bottom to top provided the condition is met. - - Parameters - ---------- - m : Pyomo ConcreteModel - The optimization model. - n_tray : int - The tray index. - - Returns - ------- - Constraint or NoConstraint - The constraint expression or NoConstraint if the condition is not met. - """ - if n_tray > m.reb_tray and (n_tray + 1) < m.feed_tray: - return ( - m.tray_exists[2, n_tray].binary_indicator_var - <= m.tray_exists[2, n_tray + 1].binary_indicator_var - ) - elif n_tray > m.feed_tray and (n_tray + 1) < m.con_tray: - return ( - m.tray_exists[2, n_tray + 1].binary_indicator_var - <= m.tray_exists[2, n_tray].binary_indicator_var - ) - else: - return Constraint.NoConstraint - # TODO: Update the logic proposition constraint for the feed section with the new pyomo.gdp syntax - - @m.Constraint(m.candidate_trays_product) - def _logic_proposition_section3(m, n_tray): - """ - Apply a logic proposition constraint to the product section and candidate trays to specify the order of trays in the column is from bottom to top provided the condition is met. - - Parameters - ---------- - m : Pyomo ConcreteModel - The optimization model. - n_tray : int - The tray index. - - Returns - ------- - Constraint or NoConstraint - The constraint expression or NoConstraint if the condition is not met. - """ - if n_tray > 1 and (n_tray + 1) < m.num_trays: - return ( - m.tray_exists[3, n_tray].binary_indicator_var - <= m.tray_exists[3, n_tray + 1].binary_indicator_var - ) - else: - return Constraint.NoConstraint - # TODO: Update the logic proposition constraint for the product section with the new pyomo.gdp syntax - - @m.Constraint(m.tray) - def equality_feed_product_side(m, n_tray): - """ - Constraint that enforces the equality of the binary indicator variables for the feed and product side trays. - - Parameters - ---------- - m : Pyomo ConcreteModel - The optimization model. - n_tray : int - The tray index. - - Returns - ------- - Constraint - The constraint expression that enforces the equality of the binary indicator variables for the feed and product side trays. - """ - return ( - m.tray_exists[2, n_tray].binary_indicator_var - == m.tray_exists[3, n_tray].binary_indicator_var - ) - - # TODO: Update the equality constraint for the feed and product side trays with the new pyomo.gdp syntax - - @m.Constraint() - def _existent_minimum_numbertrays(m): - """ - Constraint that enforces the minimum number of trays in the column. - - Parameters - ---------- - m : Pyomo ConcreteModel - The optimization model. - - Returns - ------- - Constraint - The constraint expression that enforces the minimum number of trays in each section and each tray to be greater than or equal to the minimum number of trays. - """ - return sum( - sum(m.tray_exists[sec, n_tray].binary_indicator_var for n_tray in m.tray) - for sec in m.section - ) - sum( - m.tray_exists[3, n_tray].binary_indicator_var for n_tray in m.tray - ) >= int( - m.min_tray - ) - - return m - - -def enforce_tray_exists(m, sec, n_tray): - """ - Enforce the existence of a tray in the column. - - Parameters - ---------- - m : Pyomo ConcreteModel - The optimization model. - sec : int - The section index. - n_tray : int - The tray index. - """ - m.tray_exists[sec, n_tray].indicator_var.fix(True) - m.tray_absent[sec, n_tray].deactivate() - - -def _build_tray_equations(m, sec, n_tray): - """ - Build the equations for the tray in the column as a function of the section when the tray exists. - Points to the appropriate function to build the equations for the section in the column. - - Parameters - ---------- - m : Pyomo ConcreteModel - The optimization model. - sec : int - The section index. - n_tray : int - The tray index. - - Returns - ------- - None - """ - build_function = { - 1: _build_bottom_equations, - 2: _build_feed_side_equations, - 3: _build_product_side_equations, - 4: _build_top_equations, - } - build_function[sec](m, n_tray) - - -def _build_bottom_equations(disj, n_tray): - """ - Build the equations for the bottom section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the bottom section in the column. - n_tray : int - The tray index. - - Returns - ------- - None - """ - m = disj.model() - - @disj.Constraint(m.comp, doc="Bottom section 1 mass per component balances") - def _bottom_mass_percomponent_balances(disj, comp): - """ - Mass per component balances for the bottom section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the bottom section in the column. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the mass balance per component in the bottom section of the column. - """ - return (m.L[1, n_tray + 1, comp] if n_tray < m.num_trays else 0) + ( - m.L[2, 1, comp] if n_tray == m.num_trays else 0 - ) + (m.L[3, 1, comp] if n_tray == m.num_trays else 0) - ( - m.L[1, n_tray, comp] if n_tray > m.reb_tray else 0 - ) + ( - m.V[1, n_tray - 1, comp] if n_tray > m.reb_tray else 0 - ) - ( - m.V[1, n_tray, comp] * m.dv[2] if n_tray == m.num_trays else 0 - ) - ( - m.V[1, n_tray, comp] * m.dv[3] if n_tray == m.num_trays else 0 - ) - ( - m.V[1, n_tray, comp] if n_tray < m.num_trays else 0 - ) - ( - m.B[comp] if n_tray == m.reb_tray else 0 - ) == m.slack[ - 1, n_tray, comp - ] - - @disj.Constraint(doc="Bottom section 1 energy balances") - def _bottom_energy_balances(disj): - """ - Energy balances for the bottom section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the bottom section in the column. - - Returns - ------- - Constraint - The constraint expression that enforces the energy balance for the bottom section in the column. - """ - return ( - sum( - ( - m.L[1, n_tray + 1, comp] * m.hl[1, n_tray + 1, comp] - if n_tray < m.num_trays - else 0 - ) - + (m.L[2, 1, comp] * m.hl[2, 1, comp] if n_tray == m.num_trays else 0) - + (m.L[3, 1, comp] * m.hl[3, 1, comp] if n_tray == m.num_trays else 0) - - ( - m.L[1, n_tray, comp] * m.hl[1, n_tray, comp] - if n_tray > m.reb_tray - else 0 - ) - + ( - m.V[1, n_tray - 1, comp] * m.hv[1, n_tray - 1, comp] - if n_tray > m.reb_tray - else 0 - ) - - ( - m.V[1, n_tray, comp] * m.dv[2] * m.hv[1, n_tray, comp] - if n_tray == m.num_trays - else 0 - ) - - ( - m.V[1, n_tray, comp] * m.dv[3] * m.hv[1, n_tray, comp] - if n_tray == m.num_trays - else 0 - ) - - ( - m.V[1, n_tray, comp] * m.hv[1, n_tray, comp] - if n_tray < m.num_trays - else 0 - ) - - (m.B[comp] * m.hl[1, n_tray, comp] if n_tray == m.reb_tray else 0) - for comp in m.comp - ) - * m.Qscale - + (m.Qreb if n_tray == m.reb_tray else 0) - == 0 - ) - - @disj.Constraint(m.comp, doc="Bottom section 1 liquid flowrate per component") - def _bottom_liquid_percomponent(disj, comp): - """ - Liquid flowrate per component in the bottom section of the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the bottom section in the column. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the liquid flowrate per component in the bottom section of the column. - """ - return m.L[1, n_tray, comp] == m.Ltotal[1, n_tray] * m.x[1, n_tray, comp] - - @disj.Constraint(m.comp, doc="Bottom section 1 vapor flowrate per component") - def _bottom_vapor_percomponent(disj, comp): - """ - Vapor flowrate per component in the bottom section of the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the bottom section in the column. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the vapor flowrate per component in the bottom section of the column. - """ - return m.V[1, n_tray, comp] == m.Vtotal[1, n_tray] * m.y[1, n_tray, comp] - - @disj.Constraint(doc="Bottom section 1 liquid composition equilibrium summation") - def bottom_liquid_composition_summation(disj): - """ - Liquid composition equilibrium summation for the bottom section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the bottom section in the column. - - Returns - ------- - Constraint - The constraint expression that enforces the liquid composition equilibrium summation for the bottom section in the column. - It ensures the sum of the liquid compositions is equal to 1 plus the error in the liquid composition. - """ - return sum(m.x[1, n_tray, comp] for comp in m.comp) - 1 == m.errx[1, n_tray] - - @disj.Constraint(doc="Bottom section 1 vapor composition equilibrium summation") - def bottom_vapor_composition_summation(disj): - """ - Vapor composition equilibrium summation for the bottom section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the bottom section in the column. - - Returns - ------- - Constraint - The constraint expression that enforces the vapor composition equilibrium summation for the bottom section in the column. - It ensures the sum of the vapor compositions is equal to 1 plus the error in the vapor composition. - """ - return sum(m.y[1, n_tray, comp] for comp in m.comp) - 1 == m.erry[1, n_tray] - - @disj.Constraint(m.comp, doc="Bottom section 1 vapor composition") - def bottom_vapor_composition(disj, comp): - """ - Vapor composition for the bottom section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the bottom section in the column. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the vapor composition for the bottom section in the column. - The equation is derived from the vapor-liquid equilibrium relationship. - """ - return ( - m.y[1, n_tray, comp] - == m.x[1, n_tray, comp] - * ( - m.actv[1, n_tray, comp] - * ( - m.prop[comp, 'PC'] - * exp( - m.prop[comp, 'TC'] - / m.T[1, n_tray] - * ( - m.prop[comp, 'vpA'] - * (1 - m.T[1, n_tray] / m.prop[comp, 'TC']) - + m.prop[comp, 'vpB'] - * (1 - m.T[1, n_tray] / m.prop[comp, 'TC']) ** 1.5 - + m.prop[comp, 'vpC'] - * (1 - m.T[1, n_tray] / m.prop[comp, 'TC']) ** 3 - + m.prop[comp, 'vpD'] - * (1 - m.T[1, n_tray] / m.prop[comp, 'TC']) ** 6 - ) - ) - ) - ) - / m.P[1, n_tray] - ) - - @disj.Constraint(m.comp, doc="Bottom section 1 liquid enthalpy") - def bottom_liquid_enthalpy(disj, comp): - """ - Liquid enthalpy for the bottom section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the bottom section in the column. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the liquid enthalpy for the bottom section in the column. - """ - return m.hl[1, n_tray, comp] == ( - m.cpc[1] - * ( - (m.T[1, n_tray] - m.Tref) * m.prop[comp, 'cpA', 1] - + (m.T[1, n_tray] ** 2 - m.Tref**2) - * m.prop[comp, 'cpB', 1] - * m.cpc2['A', 1] - / 2 - + (m.T[1, n_tray] ** 3 - m.Tref**3) - * m.prop[comp, 'cpC', 1] - * m.cpc2['B', 1] - / 3 - + (m.T[1, n_tray] ** 4 - m.Tref**4) * m.prop[comp, 'cpD', 1] / 4 - ) - / m.Hscale - ) - - @disj.Constraint(m.comp, doc="Bottom section 1 vapor enthalpy") - def bottom_vapor_enthalpy(disj, comp): - """ - Vapor enthalpy for the bottom section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the bottom section in the column. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the vapor enthalpy for the bottom section in the column. - """ - return m.hv[1, n_tray, comp] == ( - m.cpc[2] - * ( - (m.T[1, n_tray] - m.Tref) * m.prop[comp, 'cpA', 2] - + (m.T[1, n_tray] ** 2 - m.Tref**2) - * m.prop[comp, 'cpB', 2] - * m.cpc2['A', 2] - / 2 - + (m.T[1, n_tray] ** 3 - m.Tref**3) - * m.prop[comp, 'cpC', 2] - * m.cpc2['B', 2] - / 3 - + (m.T[1, n_tray] ** 4 - m.Tref**4) * m.prop[comp, 'cpD', 2] / 4 - ) - / m.Hscale - + m.dHvap[comp] - ) - - @disj.Constraint(m.comp, doc="Bottom section 1 liquid activity coefficient") - def bottom_activity_coefficient(disj, comp): - """ - Liquid activity coefficient for the bottom section in the column equal to 1. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the bottom section in the column. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the liquid activity coefficient for the bottom section for each component and tray in the column to be equal to 1. - """ - return m.actv[1, n_tray, comp] == 1 - - -def _build_feed_side_equations(disj, n_tray): - """ - Build the equations for the feed side section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the feed side section in the column. - n_tray : int - The tray index. - - Returns - ------- - None - """ - m = disj.model() - - @disj.Constraint(m.comp, doc="Feed section 2 mass per component balances") - def _feedside_masspercomponent_balances(disj, comp): - """ - Mass per component balances for the feed side section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the feed side section in the column. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the mass balance per component in the feed side section of the column. - """ - return (m.L[2, n_tray + 1, comp] if n_tray < m.num_trays else 0) + ( - m.L[4, 1, comp] * m.dl[2] if n_tray == m.num_trays else 0 - ) - m.L[2, n_tray, comp] + ( - m.V[1, m.num_trays, comp] * m.dv[2] if n_tray == 1 else 0 - ) + ( - m.V[2, n_tray - 1, comp] if n_tray > 1 else 0 - ) - m.V[ - 2, n_tray, comp - ] + ( - m.F[comp] if n_tray == m.feed_tray else 0 - ) == 0 - - @disj.Constraint(doc="Feed section 2 energy balances") - def _feedside_energy_balances(disj): - """ - Energy balances for the feed side section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the feed side section in the column. - - Returns - ------- - Constraint - The constraint expression that enforces the energy balance for the feed side section in the column. - """ - return ( - sum( - ( - m.L[2, n_tray + 1, comp] * m.hl[2, n_tray + 1, comp] - if n_tray < m.num_trays - else 0 - ) - + ( - m.L[4, 1, comp] * m.dl[2] * m.hl[4, 1, comp] - if n_tray == m.num_trays - else 0 - ) - - m.L[2, n_tray, comp] * m.hl[2, n_tray, comp] - + ( - m.V[1, m.num_trays, comp] * m.dv[2] * m.hv[1, m.num_trays, comp] - if n_tray == 1 - else 0 - ) - + ( - m.V[2, n_tray - 1, comp] * m.hv[2, n_tray - 1, comp] - if n_tray > 1 - else 0 - ) - - m.V[2, n_tray, comp] * m.hv[2, n_tray, comp] - for comp in m.comp - ) - * m.Qscale - + sum( - ( - m.F[comp] * (m.hlf[comp] * (1 - m.q) + m.hvf[comp] * m.q) - if n_tray == m.feed_tray - else 0 - ) - for comp in m.comp - ) - * m.Qscale - == 0 - ) - - @disj.Constraint(m.comp, doc="Feed section 2 liquid flowrate per component") - def _feedside_liquid_percomponent(disj, comp): - """ - Liquid flowrate per component in the feed side section of the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the feed side section in the column. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the liquid flowrate per component in the feed side section of the column is equal to the total liquid flowrate times the liquid composition. - """ - return m.L[2, n_tray, comp] == m.Ltotal[2, n_tray] * m.x[2, n_tray, comp] - - @disj.Constraint(m.comp, doc="Feed section 2 vapor flowrate per component") - def _feedside_vapor_percomponent(disj, comp): - """ - Vapor flowrate per component in the feed side section of the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the feed side section in the column. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the vapor flowrate per component in the feed side section of the column is equal to the total vapor flowrate times the vapor composition. - """ - return m.V[2, n_tray, comp] == m.Vtotal[2, n_tray] * m.y[2, n_tray, comp] - - @disj.Constraint(doc="Feed section 2 liquid composition equilibrium summation") - def feedside_liquid_composition_summation(disj): - """ - Liquid composition equilibrium summation for the feed side section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the feed side section in the column. - - Returns - ------- - Constraint - The constraint expression that enforces the liquid composition equilibrium summation for the feed side section in the column. - It ensures the sum of the liquid compositions is equal to 1 plus the error in the liquid composition. - """ - return sum(m.x[2, n_tray, comp] for comp in m.comp) - 1 == m.errx[2, n_tray] - - @disj.Constraint(doc="Feed section 2 vapor composition equilibrium summation") - def feedside_vapor_composition_summation(disj): - """ - Vapor composition equilibrium summation for the feed side section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the feed side section in the column. - - Returns - ------- - Constraint - The constraint expression that enforces the vapor composition equilibrium summation for the feed side section in the column. - It ensures the sum of the vapor compositions is equal to 1 plus the error in the vapor composition. - """ - return sum(m.y[2, n_tray, comp] for comp in m.comp) - 1 == m.erry[2, n_tray] - - @disj.Constraint(m.comp, doc="Feed section 2 vapor composition") - def feedside_vapor_composition(disj, comp): - """ - Vapor composition for the feed side section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the feed side section in the column. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the vapor composition for the feed side section in the column. - The equation is derived from the vapor-liquid equilibrium relationship. - """ - return ( - m.y[2, n_tray, comp] - == m.x[2, n_tray, comp] - * ( - m.actv[2, n_tray, comp] - * ( - m.prop[comp, 'PC'] - * exp( - m.prop[comp, 'TC'] - / m.T[2, n_tray] - * ( - m.prop[comp, 'vpA'] - * (1 - m.T[2, n_tray] / m.prop[comp, 'TC']) - + m.prop[comp, 'vpB'] - * (1 - m.T[2, n_tray] / m.prop[comp, 'TC']) ** 1.5 - + m.prop[comp, 'vpC'] - * (1 - m.T[2, n_tray] / m.prop[comp, 'TC']) ** 3 - + m.prop[comp, 'vpD'] - * (1 - m.T[2, n_tray] / m.prop[comp, 'TC']) ** 6 - ) - ) - ) - ) - / m.P[2, n_tray] - ) - - @disj.Constraint(m.comp, doc="Feed section 2 liquid enthalpy") - def feedside_liquid_enthalpy(disj, comp): - """ - Liquid enthalpy for the feed side section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the feed side section in the column. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the liquid enthalpy for the feed side section in the column. - """ - return m.hl[2, n_tray, comp] == ( - m.cpc[1] - * ( - (m.T[2, n_tray] - m.Tref) * m.prop[comp, 'cpA', 1] - + (m.T[2, n_tray] ** 2 - m.Tref**2) - * m.prop[comp, 'cpB', 1] - * m.cpc2['A', 1] - / 2 - + (m.T[2, n_tray] ** 3 - m.Tref**3) - * m.prop[comp, 'cpC', 1] - * m.cpc2['B', 1] - / 3 - + (m.T[2, n_tray] ** 4 - m.Tref**4) * m.prop[comp, 'cpD', 1] / 4 - ) - / m.Hscale - ) - - @disj.Constraint(m.comp, doc="Feed section 2 vapor enthalpy") - def feedside_vapor_enthalpy(disj, comp): - """ - Vapor enthalpy for the feed side section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the feed side section in the column. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the vapor enthalpy for the feed side section in the column. - """ - return m.hv[2, n_tray, comp] == ( - m.cpc[2] - * ( - (m.T[2, n_tray] - m.Tref) * m.prop[comp, 'cpA', 2] - + (m.T[2, n_tray] ** 2 - m.Tref**2) - * m.prop[comp, 'cpB', 2] - * m.cpc2['A', 2] - / 2 - + (m.T[2, n_tray] ** 3 - m.Tref**3) - * m.prop[comp, 'cpC', 2] - * m.cpc2['B', 2] - / 3 - + (m.T[2, n_tray] ** 4 - m.Tref**4) * m.prop[comp, 'cpD', 2] / 4 - ) - / m.Hscale - + m.dHvap[comp] - ) - - @disj.Constraint(m.comp, doc="Feed section 2 liquid activity coefficient") - def feedside_activity_coefficient(disj, comp): - """ - Liquid activity coefficient for the feed side section in the column equal to 1. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the feed side section in the column. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the liquid activity coefficient for the feed side section for each component and tray in the column to be equal to 1. - This is an assumption for the feed side section, since the feed is assumed to be ideal. - """ - return m.actv[2, n_tray, comp] == 1 - - -def _build_product_side_equations(disj, n_tray): - """ - Build the equations for the product side section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the product side section in the column. - n_tray : int - The tray index. - - Returns - ------- - None - """ - m = disj.model() - - @disj.Constraint(m.comp, doc="Product section 3 mass per component balances") - def _productside_masspercomponent_balances(disj, comp): - """ - Mass per component balances for the product side section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the product side section in the column. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the mass balance per component in the product side section of the column. - """ - return (m.L[3, n_tray + 1, comp] if n_tray < m.num_trays else 0) + ( - m.L[4, 1, comp] * m.dl[3] if n_tray == m.num_trays else 0 - ) - m.L[3, n_tray, comp] + ( - m.V[1, m.num_trays, comp] * m.dv[3] if n_tray == 1 else 0 - ) + ( - m.V[3, n_tray - 1, comp] if n_tray > 1 else 0 - ) - m.V[ - 3, n_tray, comp - ] - ( - m.S[1, comp] if n_tray == m.sideout1_tray else 0 - ) - ( - m.S[2, comp] if n_tray == m.sideout2_tray else 0 - ) == 0 - - @disj.Constraint(doc="Product section 3 energy balances") - def _productside_energy_balances(disj): - """ - Energy balances for the product side section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the product side section in the column. - - Returns - ------- - Constraint - The constraint expression that enforces the energy balance for the product side section in the column. - """ - return ( - sum( - ( - m.L[3, n_tray + 1, comp] * m.hl[3, n_tray + 1, comp] - if n_tray < m.num_trays - else 0 - ) - + ( - m.L[4, 1, comp] * m.dl[3] * m.hl[4, 1, comp] - if n_tray == m.num_trays - else 0 - ) - - m.L[3, n_tray, comp] * m.hl[3, n_tray, comp] - + ( - m.V[1, m.num_trays, comp] * m.dv[3] * m.hv[1, m.num_trays, comp] - if n_tray == 1 - else 0 - ) - + ( - m.V[3, n_tray - 1, comp] * m.hv[3, n_tray - 1, comp] - if n_tray > 1 - else 0 - ) - - m.V[3, n_tray, comp] * m.hv[3, n_tray, comp] - - ( - m.S[1, comp] * m.hl[3, n_tray, comp] - if n_tray == m.sideout1_tray - else 0 - ) - - ( - m.S[2, comp] * m.hl[3, n_tray, comp] - if n_tray == m.sideout2_tray - else 0 - ) - for comp in m.comp - ) - * m.Qscale - == 0 - ) - - @disj.Constraint(m.comp, doc="Product section 3 liquid flowrate per component") - def _productside_liquid_percomponent(disj, comp): - """ - Liquid flowrate per component in the product side section of the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the product side section in the column. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the liquid flowrate per component in the product side section of the column is equal to the total liquid flowrate times the liquid composition. - """ - return m.L[3, n_tray, comp] == m.Ltotal[3, n_tray] * m.x[3, n_tray, comp] - - @disj.Constraint(m.comp, doc="Product section 3 vapor flowrate per component") - def _productside_vapor_percomponent(disj, comp): - """ - Vapor flowrate per component in the product side section of the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the product side section in the column. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the vapor flowrate per component in the product side section of the column is equal to the total vapor flowrate times the vapor composition. - """ - return m.V[3, n_tray, comp] == m.Vtotal[3, n_tray] * m.y[3, n_tray, comp] - - @disj.Constraint(doc="Product section 3 liquid composition equilibrium summation") - def productside_liquid_composition_summation(disj): - """ - Liquid composition equilibrium summation for the product side section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the product side section in the column. - - Returns - ------- - Constraint - The constraint expression that enforces the liquid composition equilibrium summation for the product side section in the column. - It ensures the sum of the liquid compositions is equal to 1 plus the error in the liquid composition. - """ - return sum(m.x[3, n_tray, comp] for comp in m.comp) - 1 == m.errx[3, n_tray] - - @disj.Constraint(doc="Product section 3 vapor composition equilibrium summation") - def productside_vapor_composition_summation(disj): - """ - Vapor composition equilibrium summation for the product side section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the product side section in the column. - - Returns - ------- - Constraint - The constraint expression that enforces the vapor composition equilibrium summation for the product side section in the column. - It ensures the sum of the vapor compositions is equal to 1 plus the error in the vapor composition. - """ - return sum(m.y[3, n_tray, comp] for comp in m.comp) - 1 == m.erry[3, n_tray] - - @disj.Constraint(m.comp, doc="Product section 3 vapor composition") - def productside_vapor_composition(disj, comp): - """ - Vapor composition for the product side section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the product side section in the column. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the vapor composition for the product side section in the column. - The equation is derived from the vapor-liquid equilibrium relationship. - """ - return ( - m.y[3, n_tray, comp] - == m.x[3, n_tray, comp] - * ( - m.actv[3, n_tray, comp] - * ( - m.prop[comp, 'PC'] - * exp( - m.prop[comp, 'TC'] - / m.T[3, n_tray] - * ( - m.prop[comp, 'vpA'] - * (1 - m.T[3, n_tray] / m.prop[comp, 'TC']) - + m.prop[comp, 'vpB'] - * (1 - m.T[3, n_tray] / m.prop[comp, 'TC']) ** 1.5 - + m.prop[comp, 'vpC'] - * (1 - m.T[3, n_tray] / m.prop[comp, 'TC']) ** 3 - + m.prop[comp, 'vpD'] - * (1 - m.T[3, n_tray] / m.prop[comp, 'TC']) ** 6 - ) - ) - ) - ) - / m.P[3, n_tray] - ) - - @disj.Constraint(m.comp, doc="Product section 3 liquid enthalpy") - def productside_liquid_enthalpy(disj, comp): - """ - Liquid enthalpy for the product side section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the product side section in the column. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the liquid enthalpy for the product side section in the column. - """ - return m.hl[3, n_tray, comp] == ( - m.cpc[1] - * ( - (m.T[3, n_tray] - m.Tref) * m.prop[comp, 'cpA', 1] - + (m.T[3, n_tray] ** 2 - m.Tref**2) - * m.prop[comp, 'cpB', 1] - * m.cpc2['A', 1] - / 2 - + (m.T[3, n_tray] ** 3 - m.Tref**3) - * m.prop[comp, 'cpC', 1] - * m.cpc2['B', 1] - / 3 - + (m.T[3, n_tray] ** 4 - m.Tref**4) * m.prop[comp, 'cpD', 1] / 4 - ) - / m.Hscale - ) - - @disj.Constraint(m.comp, doc="Product section 3 vapor enthalpy") - def productside_vapor_enthalpy(disj, comp): - """ - Vapor enthalpy for the product side section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the product side section in the column. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the vapor enthalpy for the product side section in the column. - """ - return m.hv[3, n_tray, comp] == ( - m.cpc[2] - * ( - (m.T[3, n_tray] - m.Tref) * m.prop[comp, 'cpA', 2] - + (m.T[3, n_tray] ** 2 - m.Tref**2) - * m.prop[comp, 'cpB', 2] - * m.cpc2['A', 2] - / 2 - + (m.T[3, n_tray] ** 3 - m.Tref**3) - * m.prop[comp, 'cpC', 2] - * m.cpc2['B', 2] - / 3 - + (m.T[3, n_tray] ** 4 - m.Tref**4) * m.prop[comp, 'cpD', 2] / 4 - ) - / m.Hscale - + m.dHvap[comp] - ) - - @disj.Constraint(m.comp, doc="Product section 3 liquid activity coefficient") - def productside_activity_coefficient(disj, comp): - """ - Liquid activity coefficient for the product side section in the column equal to 1. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the product side section in the column. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the liquid activity coefficient for the product side section for each component and tray in the column to be equal to 1. - This is an assumption for the product side section, since the product is assumed to be ideal. - """ - return m.actv[3, n_tray, comp] == 1 - - -def _build_top_equations(disj, n_tray): - """ - Build the equations for the top section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the top section in the column. - n_tray : int - The tray index. - - Returns - ------- - None - """ - m = disj.model() - - @disj.Constraint(m.comp, doc="Top section 4 mass per component balances") - def _top_mass_percomponent_balances(disj, comp): - """ - Mass per component balances for the top section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the top section in the column. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the mass balance per component in the top section of the column. - """ - return (m.L[4, n_tray + 1, comp] if n_tray < m.con_tray else 0) - ( - m.L[4, n_tray, comp] * m.dl[2] if n_tray == 1 else 0 - ) - (m.L[4, n_tray, comp] * m.dl[3] if n_tray == 1 else 0) - ( - m.L[4, n_tray, comp] if n_tray > 1 else 0 - ) + ( - m.V[2, m.num_trays, comp] if n_tray == 1 else 0 - ) + ( - m.V[3, m.num_trays, comp] if n_tray == 1 else 0 - ) + ( - m.V[4, n_tray - 1, comp] if n_tray > 1 else 0 - ) - ( - m.V[4, n_tray, comp] if n_tray < m.con_tray else 0 - ) - ( - m.D[comp] if n_tray == m.con_tray else 0 - ) == 0 - - @disj.Constraint(doc="Top scetion 4 energy balances") - def _top_energy_balances(disj): - """ - Energy balances for the top section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the top section in the column. - - Returns - ------- - Constraint - The constraint expression that enforces the energy balance for the top section in the column. - """ - return ( - sum( - ( - m.L[4, n_tray + 1, comp] * m.hl[4, n_tray + 1, comp] - if n_tray < m.con_tray - else 0 - ) - - ( - m.L[4, n_tray, comp] * m.dl[2] * m.hl[4, n_tray, comp] - if n_tray == 1 - else 0 - ) - - ( - m.L[4, n_tray, comp] * m.dl[3] * m.hl[4, n_tray, comp] - if n_tray == 1 - else 0 - ) - - (m.L[4, n_tray, comp] * m.hl[4, n_tray, comp] if n_tray > 1 else 0) - + ( - m.V[2, m.num_trays, comp] * m.hv[2, m.num_trays, comp] - if n_tray == 1 - else 0 - ) - + ( - m.V[3, m.num_trays, comp] * m.hv[3, m.num_trays, comp] - if n_tray == 1 - else 0 - ) - + ( - m.V[4, n_tray - 1, comp] * m.hv[4, n_tray - 1, comp] - if n_tray > 1 - else 0 - ) - - ( - m.V[4, n_tray, comp] * m.hv[4, n_tray, comp] - if n_tray < m.con_tray - else 0 - ) - - (m.D[comp] * m.hl[4, n_tray, comp] if n_tray == m.con_tray else 0) - for comp in m.comp - ) - * m.Qscale - - (m.Qcon if n_tray == m.con_tray else 0) - == 0 - ) - - @disj.Constraint(m.comp, doc="Top section 4 liquid flowrate per component") - def _top_liquid_percomponent(disj, comp): - """ - Liquid flowrate per component in the top section of the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the top section in the column. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the liquid flowrate per component in the top section of the column is equal to the total liquid flowrate times the liquid composition. - """ - return m.L[4, n_tray, comp] == m.Ltotal[4, n_tray] * m.x[4, n_tray, comp] - - @disj.Constraint(m.comp, doc="Top section 4 vapor flowrate per component") - def _top_vapor_percomponent(disj, comp): - """ - Vapor flowrate per component in the top section of the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the top section in the column. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the vapor flowrate per component in the top section of the column is equal to the total vapor flowrate times the vapor composition. - """ - return m.V[4, n_tray, comp] == m.Vtotal[4, n_tray] * m.y[4, n_tray, comp] - - @disj.Constraint(doc="Top section 4 liquid composition equilibrium summation") - def top_liquid_composition_summation(disj): - """ - Liquid composition equilibrium summation for the top section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the top section in the column. - - Returns - ------- - Constraint - The constraint expression that enforces the liquid composition equilibrium summation for the top section in the column. - It ensures the sum of the liquid compositions is equal to 1 plus the error in the liquid composition. - """ - return sum(m.x[4, n_tray, comp] for comp in m.comp) - 1 == m.errx[4, n_tray] - - @disj.Constraint(doc="Top section 4 vapor composition equilibrium summation") - def top_vapor_composition_summation(disj): - """ - Vapor composition equilibrium summation for the top section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the top section in the column. - - Returns - ------- - Constraint - The constraint expression that enforces the vapor composition equilibrium summation for the top section in the column. - It ensures the sum of the vapor compositions is equal to 1 plus the error in the vapor composition. - """ - return sum(m.y[4, n_tray, comp] for comp in m.comp) - 1 == m.erry[4, n_tray] - - @disj.Constraint(m.comp, doc="Top scetion 4 vapor composition") - def top_vapor_composition(disj, comp): - """ - Vapor composition for the top section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the top section in the column. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the vapor composition for the top section in the column. - The equation is derived from the vapor-liquid equilibrium relationship. - """ - return ( - m.y[4, n_tray, comp] - == m.x[4, n_tray, comp] - * ( - m.actv[4, n_tray, comp] - * ( - m.prop[comp, 'PC'] - * exp( - m.prop[comp, 'TC'] - / m.T[4, n_tray] - * ( - m.prop[comp, 'vpA'] - * (1 - m.T[4, n_tray] / m.prop[comp, 'TC']) - + m.prop[comp, 'vpB'] - * (1 - m.T[4, n_tray] / m.prop[comp, 'TC']) ** 1.5 - + m.prop[comp, 'vpC'] - * (1 - m.T[4, n_tray] / m.prop[comp, 'TC']) ** 3 - + m.prop[comp, 'vpD'] - * (1 - m.T[4, n_tray] / m.prop[comp, 'TC']) ** 6 - ) - ) - ) - ) - / m.P[4, n_tray] - ) - - @disj.Constraint(m.comp, doc="Top section 4 liquid enthalpy") - def top_liquid_enthalpy(disj, comp): - """ - Liquid enthalpy for the top section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the top section in the column. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the liquid enthalpy for the top section in the column. - """ - return m.hl[4, n_tray, comp] == ( - m.cpc[1] - * ( - (m.T[4, n_tray] - m.Tref) * m.prop[comp, 'cpA', 1] - + (m.T[4, n_tray] ** 2 - m.Tref**2) - * m.prop[comp, 'cpB', 1] - * m.cpc2['A', 1] - / 2 - + (m.T[4, n_tray] ** 3 - m.Tref**3) - * m.prop[comp, 'cpC', 1] - * m.cpc2['B', 1] - / 3 - + (m.T[4, n_tray] ** 4 - m.Tref**4) * m.prop[comp, 'cpD', 1] / 4 - ) - / m.Hscale - ) - - @disj.Constraint(m.comp, doc="Top section 4 vapor enthalpy") - def top_vapor_enthalpy(disj, comp): - """ - Vapor enthalpy for the top section in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the top section in the column. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the vapor enthalpy for the top section in the column. - """ - return m.hv[4, n_tray, comp] == ( - m.cpc[2] - * ( - (m.T[4, n_tray] - m.Tref) * m.prop[comp, 'cpA', 2] - + (m.T[4, n_tray] ** 2 - m.Tref**2) - * m.prop[comp, 'cpB', 2] - * m.cpc2['A', 2] - / 2 - + (m.T[4, n_tray] ** 3 - m.Tref**3) - * m.prop[comp, 'cpC', 2] - * m.cpc2['B', 2] - / 3 - + (m.T[4, n_tray] ** 4 - m.Tref**4) * m.prop[comp, 'cpD', 2] / 4 - ) - / m.Hscale - + m.dHvap[comp] - ) - - @disj.Constraint(m.comp, doc="Top section 4 liquid activity coefficient") - def top_activity_coefficient(disj, comp): - """ - Liquid activity coefficient for the top section in the column equal to 1. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the top section in the column. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the liquid activity coefficient for the top section for each component and tray in the column to be equal to 1. - This is an assumption for the top section, since the product is assumed to be ideal. - """ - return m.actv[4, n_tray, comp] == 1 - - -def _build_pass_through_eqns(disj, sec, n_tray): - """ - Build the equations for the pass through section in the column when a given tray in the disjunct is not active if it is the first or last tray. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the pass through section in the column. - sec : int - The section index. - n_tray : int - The tray index. - - Returns - ------- - None - """ - m = disj.model() - - # If the tray is the first or last tray, then the liquid and vapor flowrates, compositions, enthalpies, and temperature are passed through. - if n_tray == 1 or n_tray == m.num_trays: - return - - @disj.Constraint(m.comp, doc="Pass through liquid flowrate") - def pass_through_liquid_flowrate(disj, comp): - """ - Pass through liquid flowrate for the given tray in the column. - The constraint enforces the liquid flowrate for the given tray is equal to the liquid flowrate for the tray above it. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the pass through when the tray is inactive. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the liquid flowrate for the given tray is equal to the liquid flowrate for the tray above it. - """ - return m.L[sec, n_tray, comp] == m.L[sec, n_tray + 1, comp] - - @disj.Constraint(m.comp, doc="Pass through vapor flowrate") - def pass_through_vapor_flowrate(disj, comp): - """ - Pass through vapor flowrate for the given tray in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the pass through when the tray is inactive. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the vapor flowrate for the given tray is equal to the vapor flowrate for the tray below it. - """ - return m.V[sec, n_tray, comp] == m.V[sec, n_tray - 1, comp] - - @disj.Constraint(m.comp, doc="Pass through liquid composition") - def pass_through_liquid_composition(disj, comp): - """ - Pass through liquid composition for the given tray in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the pass through when the tray is inactive. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the liquid composition for the given tray is equal to the liquid composition for the tray above it. - """ - return m.x[sec, n_tray, comp] == m.x[sec, n_tray + 1, comp] - - @disj.Constraint(m.comp, doc="Pass through vapor composition") - def pass_through_vapor_composition(disj, comp): - """ - Pass through vapor composition for the given tray in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the pass through when the tray is inactive. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the vapor composition for the given tray is equal to the vapor composition for the tray below it. - """ - return m.y[sec, n_tray, comp] == m.y[sec, n_tray + 1, comp] - - @disj.Constraint(m.comp, doc="Pass through liquid enthalpy") - def pass_through_liquid_enthalpy(disj, comp): - """ - Pass through liquid enthalpy for the given tray in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the pass through when the tray is inactive. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the liquid enthalpy for the given tray is equal to the liquid enthalpy for the tray above it. - """ - return m.hl[sec, n_tray, comp] == m.hl[sec, n_tray + 1, comp] - - @disj.Constraint(m.comp, doc="Pass through vapor enthalpy") - def pass_through_vapor_enthalpy(disj, comp): - """ - Pass through vapor enthalpy for the given tray in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the pass through when the tray is inactive. - comp : int - The component index. - - Returns - ------- - Constraint - The constraint expression that enforces the vapor enthalpy for the given tray is equal to the vapor enthalpy for the tray below it. - """ - return m.hv[sec, n_tray, comp] == m.hv[sec, n_tray - 1, comp] - - @disj.Constraint(doc="Pass through temperature") - def pass_through_temperature(disj): - """ - Pass through temperature for the given tray in the column. - - Parameters - ---------- - disj : Disjunct - The disjunct object for the pass through when the tray is inactive. - - Returns - ------- - Constraint - The constraint expression that enforces the temperature for the given tray is equal to the temperature for the tray below it. - """ - return m.T[sec, n_tray] == m.T[sec, n_tray - 1] - - -if __name__ == "__main__": - model = build_model() +""" Kaibel Column model: GDP formulation. + +The solution requires the specification of certain parameters, such as the number trays, feed location, etc., and an initialization procedure, which consists of the next three steps: +(i) a preliminary design of the separation considering a sequence of indirect continuous distillation columns (CDCs) to obtain the minimum number of stages with Fenske Equation in the function initialize_kaibel in kaibel_init.py +(ii) flash calculation for the feed with the function calc_side_feed_flash in kaibel_side_flash.py +(iii) calculation of variable bounds by solving the NLP problem. + +After the initialization, the GDP model is built. +""" + +from math import copysign + +from pyomo.environ import ( + Constraint, + exp, + minimize, + NonNegativeReals, + Objective, + RangeSet, + Set, + Var, +) +from pyomo.gdp import Disjunct + +from gdplib.kaibel.kaibel_init import initialize_kaibel + +from gdplib.kaibel.kaibel_side_flash import calc_side_feed_flash + +# from .kaibel_side_flash import calc_side_feed_flash + + +def build_model(): + """ + Build the GDP Kaibel Column model. + It combines the initialization of the model and the flash calculation for the side feed before the GDP formulation. + + Returns + ------- + ConcreteModel + The constructed GDP Kaibel Column model. + """ + + # Calculation of the theoretical minimum number of trays (Knmin) and initial temperature values (TB0, Tf0, TD0). + m = initialize_kaibel() + + # Side feed init. Returns side feed vapor composition yfi and vapor fraction q_init + m = calc_side_feed_flash(m) + + m.name = "GDP Kaibel Column" + + #### Calculated initial values + m.Treb = m.TB0 + 5 # Reboiler temperature [K] + m.Tbot = m.TB0 # Bottom-most tray temperature [K] + m.Ttop = m.TD0 # Top-most tray temperature [K] + m.Tcon = m.TD0 - 5 # Condenser temperature [K] + + m.dv0 = {} # Initial vapor distributor value + m.dl0 = {} # Initial liquid distributor value + m.dv0[2] = 0.516 + m.dv0[3] = 1 - m.dv0[2] + m.dl0[2] = 0.36 + m.dl0[3] = 1 - m.dl0[2] + + #### Calculated upper and lower bounds + m.min_tray = m.Knmin * 0.8 # Lower bound on number of trays + m.Tlo = m.Tcon - 20 # Temperature lower bound + m.Tup = m.Treb + 20 # Temperature upper bound + + m.flow_max = 1e3 # Flowrates upper bound [mol/s] + m.Qmax = 60 # Heat loads upper bound [J/s] + + #### Column tray details + m.num_trays = m.np # Trays per section. np = 25 + m.min_num_trays = 10 # Minimum number of trays per section + m.num_total = m.np * 3 # Total number of trays + m.feed_tray = 12 # Side feed tray + m.sideout1_tray = 8 # Side outlet 1 tray + m.sideout2_tray = 17 # Side outlet 2 tray + m.reb_tray = 1 # Reboiler tray. Dividing wall starting tray + m.con_tray = m.num_trays # Condenser tray. Dividing wall ending tray + + # ------------------------------------------------------------------ + + # Beginning of model + + # ------------------------------------------------------------------ + + ## Sets + m.section = RangeSet( + 4, doc="Column sections:1=top, 2=feed side, 3=prod side, 4=bot" + ) + m.section_main = Set(initialize=[1, 4], doc="Main sections of the column") + + m.tray = RangeSet(m.np, doc="Potential trays in each section") + m.tray_total = RangeSet(m.num_total, doc="Total trays in the column") + m.tray_below_feed = RangeSet(m.feed_tray, doc="Trays below feed") + m.tray_below_so1 = RangeSet(m.sideout1_tray, doc="Trays below side outlet 1") + m.tray_below_so2 = RangeSet(m.sideout2_tray, doc="Trays below side outlet 2") + + m.comp = RangeSet(4, doc="Components") + m.dw = RangeSet(2, 3, doc="Dividing wall sections") + m.cplv = RangeSet(2, doc="Heat capacity: 1=liquid, 2=vapor") + m.so = RangeSet(2, doc="Side product outlets") + m.bounds = RangeSet(2, doc="Number of boundary condition values") + + m.candidate_trays_main = Set( + initialize=m.tray - [m.con_tray, m.reb_tray], + doc="Candidate trays for top and \ + bottom sections 1 and 4", + ) + m.candidate_trays_feed = Set( + initialize=m.tray - [m.con_tray, m.feed_tray, m.reb_tray], + doc="Candidate trays for feed section 2", + ) + m.candidate_trays_product = Set( + initialize=m.tray - [m.con_tray, m.sideout1_tray, m.sideout2_tray, m.reb_tray], + doc="Candidate trays for product section 3", + ) + + ## Calculation of initial values + m.dHvap = {} # Heat of vaporization [J/mol] + + m.P0 = {} # Initial pressure [bar] + m.T0 = {} # Initial temperature [K] + m.L0 = {} # Initial individual liquid flowrate [mol/s] + m.V0 = {} # Initial individual vapor flowrate [mol/s] + m.Vtotal0 = {} # Initial total vapor flowrate [mol/s] + m.Ltotal0 = {} # Initial liquid flowrate [mol/s] + m.x0 = {} # Initial liquid composition + m.y0 = {} # Initial vapor composition + m.actv0 = {} # Initial activity coefficients + m.cpdT0 = {} # Initial heat capacity for liquid and vapor phases [J/mol/K] + m.hl0 = {} # Initial liquid enthalpy [J/mol] + m.hv0 = {} # Initial vapor enthalpy [J/mol] + m.Pi = m.Preb # Initial given pressure value [bar] + m.Ti = {} # Initial known temperature values [K] + + ## Initial values for pressure, temperature, flowrates, composition, and enthalpy + for sec in m.section: + for n_tray in m.tray: + m.P0[sec, n_tray] = m.Pi + + for sec in m.section: + for n_tray in m.tray: + for comp in m.comp: + m.L0[sec, n_tray, comp] = m.Li + m.V0[sec, n_tray, comp] = m.Vi + + for sec in m.section: + for n_tray in m.tray: + m.Ltotal0[sec, n_tray] = sum(m.L0[sec, n_tray, comp] for comp in m.comp) + m.Vtotal0[sec, n_tray] = sum(m.V0[sec, n_tray, comp] for comp in m.comp) + + for n_tray in m.tray_total: + if n_tray == m.reb_tray: + m.Ti[n_tray] = m.Treb + elif n_tray == m.num_total: + m.Ti[n_tray] = m.Tcon + else: + m.Ti[n_tray] = m.Tbot + (m.Ttop - m.Tbot) * (n_tray - 2) / (m.num_total - 3) + + for n_tray in m.tray_total: + if n_tray <= m.num_trays: + m.T0[1, n_tray] = m.Ti[n_tray] + elif n_tray >= m.num_trays and n_tray <= m.num_trays * 2: + m.T0[2, n_tray - m.num_trays] = m.Ti[n_tray] + m.T0[3, n_tray - m.num_trays] = m.Ti[n_tray] + elif n_tray >= m.num_trays * 2: + m.T0[4, n_tray - m.num_trays * 2] = m.Ti[n_tray] + + ## Initial vapor and liquid composition of the feed and activity coefficients + for sec in m.section: + for n_tray in m.tray: + for comp in m.comp: + m.x0[sec, n_tray, comp] = m.xfi[comp] + m.actv0[sec, n_tray, comp] = 1 + m.y0[sec, n_tray, comp] = m.xfi[comp] + + ## Assigns the enthalpy boundary values, heat capacity, heat of vaporization calculation, temperature bounds, and light and heavy key components. + hlb = {} # Liquid enthalpy [J/mol] + hvb = {} # Vapor enthalpy [J/mol] + cpb = {} # Heact capacity [J/mol/K] + dHvapb = {} # Heat of vaporization [J/mol] + Tbounds = {} # Temperature bounds [K] + kc = {} # Light and heavy key components + Tbounds[1] = m.Tcon # Condenser temperature [K] + Tbounds[2] = m.Treb # Reboiler temperature [K] + kc[1] = m.lc + kc[2] = m.hc + + ## Heat of vaporization calculation for each component in the feed. + for comp in m.comp: + dHvapb[comp] = -( + m.Rgas + * m.prop[comp, 'TC'] + * ( + m.prop[comp, 'vpA'] * (1 - m.Tref / m.prop[comp, 'TC']) + + m.prop[comp, 'vpB'] * (1 - m.Tref / m.prop[comp, 'TC']) ** 1.5 + + m.prop[comp, 'vpC'] * (1 - m.Tref / m.prop[comp, 'TC']) ** 3 + + m.prop[comp, 'vpD'] * (1 - m.Tref / m.prop[comp, 'TC']) ** 6 + ) + + m.Rgas + * m.Tref + * ( + m.prop[comp, 'vpA'] + + 1.5 * m.prop[comp, 'vpB'] * (1 - m.Tref / m.prop[comp, 'TC']) ** 0.5 + + 3 * m.prop[comp, 'vpC'] * (1 - m.Tref / m.prop[comp, 'TC']) ** 2 + + 6 * m.prop[comp, 'vpD'] * (1 - m.Tref / m.prop[comp, 'TC']) ** 5 + ) + ) + + ## Boundary values for heat capacity and enthalpy of liquid and vapor phases for light and heavy key components in the feed. + for b in m.bounds: + for cp in m.cplv: + cpb[b, cp] = m.cpc[cp] * ( + (Tbounds[b] - m.Tref) * m.prop[kc[b], 'cpA', cp] + + (Tbounds[b] ** 2 - m.Tref**2) + * m.prop[kc[b], 'cpB', cp] + * m.cpc2['A', cp] + / 2 + + (Tbounds[b] ** 3 - m.Tref**3) + * m.prop[kc[b], 'cpC', cp] + * m.cpc2['B', cp] + / 3 + + (Tbounds[b] ** 4 - m.Tref**4) * m.prop[kc[b], 'cpD', cp] / 4 + ) + hlb[b] = cpb[b, 1] + hvb[b] = cpb[b, 2] + dHvapb[b] + + m.hllo = ( + (1 - copysign(0.2, hlb[1])) * hlb[1] / m.Hscale + ) # Liquid enthalpy lower bound + m.hlup = ( + (1 + copysign(0.2, hlb[2])) * hlb[2] / m.Hscale + ) # Liquid enthalpy upper bound + m.hvlo = ( + (1 - copysign(0.2, hvb[1])) * hvb[1] / m.Hscale + ) # Vapor enthalpy lower bound + m.hvup = ( + (1 + copysign(0.2, hvb[2])) * hvb[2] / m.Hscale + ) # Vapor enthalpy upper bound + # copysign is a function that returns the first argument with the sign of the second argument + + ## Heat of vaporization for each component in the feed scaled by Hscale + for comp in m.comp: + m.dHvap[comp] = dHvapb[comp] / m.Hscale + + ## Heat capacity calculation for liquid and vapor phases using Ruczika-D method for each component in the feed, section, and tray + for sec in m.section: + for n_tray in m.tray: + for comp in m.comp: + for cp in m.cplv: + m.cpdT0[sec, n_tray, comp, cp] = ( + m.cpc[cp] + * ( + (m.T0[sec, n_tray] - m.Tref) * m.prop[comp, 'cpA', cp] + + (m.T0[sec, n_tray] ** 2 - m.Tref**2) + * m.prop[comp, 'cpB', cp] + * m.cpc2['A', cp] + / 2 + + (m.T0[sec, n_tray] ** 3 - m.Tref**3) + * m.prop[comp, 'cpC', cp] + * m.cpc2['B', cp] + / 3 + + (m.T0[sec, n_tray] ** 4 - m.Tref**4) + * m.prop[comp, 'cpD', cp] + / 4 + ) + / m.Hscale + ) + + ## Liquid and vapor enthalpy calculation using Ruczika-D method for each component in the feed, section, and tray + for sec in m.section: + for n_tray in m.tray: + for comp in m.comp: + m.hl0[sec, n_tray, comp] = m.cpdT0[sec, n_tray, comp, 1] + m.hv0[sec, n_tray, comp] = m.cpdT0[sec, n_tray, comp, 2] + m.dHvap[comp] + + #### Side feed + m.cpdTf = {} # Heat capacity for side feed [J/mol K] + m.hlf = {} # Liquid enthalpy for side feed [J/mol] + m.hvf = {} # Vapor enthalpy for side feed [J/mol] + m.F0 = {} # Side feed flowrate per component [mol/s] + + ## Heat capacity in liquid and vapor phases for side feed for each component using Ruczika-D method + for comp in m.comp: + for cp in m.cplv: + m.cpdTf[comp, cp] = ( + m.cpc[cp] + * ( + (m.Tf - m.Tref) * m.prop[comp, 'cpA', cp] + + (m.Tf**2 - m.Tref**2) + * m.prop[comp, 'cpB', cp] + * m.cpc2['A', cp] + / 2 + + (m.Tf**3 - m.Tref**3) + * m.prop[comp, 'cpC', cp] + * m.cpc2['B', cp] + / 3 + + (m.Tf**4 - m.Tref**4) * m.prop[comp, 'cpD', cp] / 4 + ) + / m.Hscale + ) + + ## Side feed flowrate and liquid and vapor enthalpy calculation using Ruczika-D method for each component in the feed + for comp in m.comp: + m.F0[comp] = ( + m.xfi[comp] * m.Fi + ) # Side feed flowrate per component computed from the feed composition and flowrate Fi + m.hlf[comp] = m.cpdTf[ + comp, 1 + ] # Liquid enthalpy for side feed computed from the heat capacity for side feed and liquid phase + m.hvf[comp] = ( + m.cpdTf[comp, 2] + m.dHvap[comp] + ) # Vapor enthalpy for side feed computed from the heat capacity for side feed and vapor phase and heat of vaporization + + m.P = Var( + m.section, + m.tray, + doc="Pressure at each potential tray in bars", + domain=NonNegativeReals, + bounds=(m.Pcon, m.Preb), + initialize=m.P0, + ) + m.T = Var( + m.section, + m.tray, + doc="Temperature at each potential tray [K]", + domain=NonNegativeReals, + bounds=(m.Tlo, m.Tup), + initialize=m.T0, + ) + + m.x = Var( + m.section, + m.tray, + m.comp, + doc="Liquid composition", + domain=NonNegativeReals, + bounds=(0, 1), + initialize=m.x0, + ) + m.y = Var( + m.section, + m.tray, + m.comp, + doc="Vapor composition", + domain=NonNegativeReals, + bounds=(0, 1), + initialize=m.y0, + ) + + m.dl = Var( + m.dw, + doc="Liquid distributor in the dividing wall sections", + bounds=(0.2, 0.8), + initialize=m.dl0, + ) + m.dv = Var( + m.dw, + doc="Vapor distributor in the dividing wall sections", + bounds=(0, 1), + domain=NonNegativeReals, + initialize=m.dv0, + ) + + m.V = Var( + m.section, + m.tray, + m.comp, + doc="Vapor flowrate [mol/s]", + domain=NonNegativeReals, + bounds=(0, m.flow_max), + initialize=m.V0, + ) + m.L = Var( + m.section, + m.tray, + m.comp, + doc="Liquid flowrate [mol/s]", + domain=NonNegativeReals, + bounds=(0, m.flow_max), + initialize=m.L0, + ) + m.Vtotal = Var( + m.section, + m.tray, + doc="Total vapor flowrate [mol/s]", + domain=NonNegativeReals, + bounds=(0, m.flow_max), + initialize=m.Vtotal0, + ) + m.Ltotal = Var( + m.section, + m.tray, + doc="Total liquid flowrate [mol/s]", + domain=NonNegativeReals, + bounds=(0, m.flow_max), + initialize=m.Ltotal0, + ) + + m.D = Var( + m.comp, + doc="Distillate flowrate [mol/s]", + domain=NonNegativeReals, + bounds=(0, m.flow_max), + initialize=m.Ddes, + ) + m.B = Var( + m.comp, + doc="Bottoms flowrate [mol/s]", + domain=NonNegativeReals, + bounds=(0, m.flow_max), + initialize=m.Bdes, + ) + m.S = Var( + m.so, + m.comp, + doc="Product 2 and 3 flowrates [mol/s]", + domain=NonNegativeReals, + bounds=(0, m.flow_max), + initialize=m.Sdes, + ) + m.Dtotal = Var( + doc="Distillate flowrate [mol/s]", + domain=NonNegativeReals, + bounds=(0, m.flow_max), + initialize=m.Ddes, + ) + m.Btotal = Var( + doc="Bottoms flowrate [mol/s]", + domain=NonNegativeReals, + bounds=(0, m.flow_max), + initialize=m.Bdes, + ) + m.Stotal = Var( + m.so, + doc="Total product 2 and 3 side flowrate [mol/s]", + domain=NonNegativeReals, + bounds=(0, m.flow_max), + initialize=m.Sdes, + ) + + m.hl = Var( + m.section, + m.tray, + m.comp, + doc='Liquid enthalpy [J/mol]', + bounds=(m.hllo, m.hlup), + initialize=m.hl0, + ) + m.hv = Var( + m.section, + m.tray, + m.comp, + doc='Vapor enthalpy [J/mol]', + bounds=(m.hvlo, m.hvup), + initialize=m.hv0, + ) + m.Qreb = Var( + doc="Reboiler heat duty [J/s]", + domain=NonNegativeReals, + bounds=(0, m.Qmax), + initialize=1, + ) + m.Qcon = Var( + doc="Condenser heat duty [J/s]", + domain=NonNegativeReals, + bounds=(0, m.Qmax), + initialize=1, + ) + + m.rr = Var( + doc="Internal reflux ratio in the column", + domain=NonNegativeReals, + bounds=(0.7, 1), + initialize=m.rr0, + ) + m.bu = Var( + doc="Boilup rate in the reboiler", + domain=NonNegativeReals, + bounds=(0.7, 1), + initialize=m.bu0, + ) + + m.F = Var( + m.comp, + doc="Side feed flowrate [mol/s]", + domain=NonNegativeReals, + bounds=(0, 50), + initialize=m.F0, + ) + m.q = Var( + doc="Vapor fraction in side feed", + domain=NonNegativeReals, + bounds=(0, 1), + initialize=1, + ) + + m.actv = Var( + m.section, + m.tray, + m.comp, + doc="Liquid activity coefficient", + domain=NonNegativeReals, + bounds=(0, 10), + initialize=m.actv0, + ) + + m.errx = Var( + m.section, + m.tray, + doc="Error in liquid composition [mol/mol]", + bounds=(-1e-3, 1e-3), + initialize=0, + ) + m.erry = Var( + m.section, + m.tray, + doc="Error in vapor composition [mol/mol]", + bounds=(-1e-3, 1e-3), + initialize=0, + ) + m.slack = Var( + m.section, + m.tray, + m.comp, + doc="Slack variable", + bounds=(-1e-8, 1e-8), + initialize=0, + ) + + m.tray_exists = Disjunct( + m.section, + m.tray, + doc="Disjunct that enforce the existence of each tray", + rule=_build_tray_equations, + ) + m.tray_absent = Disjunct( + m.section, + m.tray, + doc="Disjunct that enforce the absence of each tray", + rule=_build_pass_through_eqns, + ) + + @m.Disjunction( + m.section, m.tray, doc="Disjunction between whether each tray exists or not" + ) + def tray_exists_or_not(m, sec, n_tray): + """ + Disjunction between whether each tray exists or not. + + Parameters + ---------- + m : Pyomo ConcreteModel + The Pyomo model object. + sec : int + The section index. + n_tray : int + The tray index. + + Returns + ------- + Disjunction + The disjunction between whether each tray exists or not. + """ + return [m.tray_exists[sec, n_tray], m.tray_absent[sec, n_tray]] + + @m.Constraint(m.section_main) + def minimum_trays_main(m, sec): + """ + Constraint that ensures the minimum number of trays in the main section. + + Parameters + ---------- + m : Pyomo ConcreteModel + The model object for the GDP Kaibel Column. + sec : Set + The section index. + + Returns + ------- + Constraint + A constraint expression that enforces the minimum number of trays in the main section to be greater than or equal to the minimum number of trays. + """ + return ( + sum( + m.tray_exists[sec, n_tray].binary_indicator_var + for n_tray in m.candidate_trays_main + ) + + 1 + >= m.min_num_trays + ) + + @m.Constraint() + def minimum_trays_feed(m): + """ + Constraint function that ensures the minimum number of trays in the feed section is met. + + Parameters + ---------- + m : Pyomo ConcreteModel + The Pyomo model object. + + Returns + ------- + Constraint + The constraint expression that enforces the minimum number of trays is greater than or equal to the minimum number of trays. + """ + return ( + sum( + m.tray_exists[2, n_tray].binary_indicator_var + for n_tray in m.candidate_trays_feed + ) + + 1 + >= m.min_num_trays + ) + + # TOCHECK: pyomo.GDP Syntax + + @m.Constraint() + def minimum_trays_product(m): + """ + Constraint function to calculate the minimum number of trays in the product section. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + + Returns + ------- + Constraint + The constraint expression that enforces the minimum number of trays is greater than or equal to the minimum number of trays. + """ + return ( + sum( + m.tray_exists[3, n_tray].binary_indicator_var + for n_tray in m.candidate_trays_product + ) + + 1 + >= m.min_num_trays + ) + + ## Fixed trays + enforce_tray_exists(m, 1, 1) # reboiler + enforce_tray_exists(m, 1, m.num_trays) # vapor distributor + enforce_tray_exists(m, 2, 1) # dividing wall starting tray + enforce_tray_exists(m, 2, m.feed_tray) # feed tray + enforce_tray_exists(m, 2, m.num_trays) # dividing wall ending tray + enforce_tray_exists(m, 3, 1) # dividing wall starting tray + enforce_tray_exists(m, 3, m.sideout1_tray) # side outlet 1 for product 3 + enforce_tray_exists(m, 3, m.sideout2_tray) # side outlet 2 for product 2 + enforce_tray_exists(m, 3, m.num_trays) # dividing wall ending tray + enforce_tray_exists(m, 4, 1) # liquid distributor + enforce_tray_exists(m, 4, m.num_trays) # condenser + + #### Global constraints + @m.Constraint( + m.dw, m.tray, doc="Monotonic temperature in the dividing wall sections" + ) + def monotonic_temperature(m, sec, n_tray): + """This function returns a constraint object representing the monotonic temperature constraint. + + The monotonic temperature constraint ensures that the temperature on each tray in the distillation column + is less than or equal to the temperature on the top tray. + + Parameters + ---------- + m : Pyomo ConcreteModel + The Pyomo model object. + sec : Set + The set of sections in the dividing wall sections. + n_tray : Set + The set of trays in the distillation column. + + Returns + ------- + Constraint + The monotonic temperature constraint specifying that the temperature on each tray is less than or equal to the temperature on the top tray of section 1 which is the condenser. + """ + return m.T[sec, n_tray] <= m.T[1, m.num_trays] + + @m.Constraint(doc="Liquid distributor") + def liquid_distributor(m): + """Defines the liquid distributor constraint. + + This constraint ensures that the sum of the liquid distributors in all sections is equal to 1. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + + Returns + ------- + Constraint + The liquid distributor constraint that enforces the sum of the liquid flow rates in all sections is equal to 1. + + """ + return sum(m.dl[sec] for sec in m.dw) - 1 == 0 + + @m.Constraint(doc="Vapor distributor") + def vapor_distributor(m): + """ + Add a constraint to ensure that the sum of the vapor distributors is equal to 1. + + Parameters + ---------- + m : Pyomo ConcreteModel + The Pyomo model object. + + Returns + ------- + Constraint + The vapor distributor constraint. + """ + return sum(m.dv[sec] for sec in m.dw) - 1 == 0 + + @m.Constraint(doc="Reboiler composition specification") + def heavy_product(m): + """ + Reboiler composition specification for the heavy component in the feed. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + + Returns + ------- + Constraint + The constraint that enforces the reboiler composition is greater than or equal to the specified composition xspechc final liquid composition for butanol, the heavy component in the feed. + """ + return m.x[1, m.reb_tray, m.hc] >= m.xspec_hc + + @m.Constraint(doc="Condenser composition specification") + def light_product(m): + """ + Condenser composition specification for the light component in the feed. + + Parameters + ---------- + m : Model + The optimization model. + + Returns + ------- + Constraint + The constraint that enforces the condenser composition is greater than or equal to the specified final liquid composition for ethanol, xspeclc , the light component in the feed. + """ + return m.x[4, m.con_tray, m.lc] >= m.xspec_lc + + @m.Constraint(doc="Side outlet 1 final liquid composition") + def intermediate1_product(m): + ''' + This constraint ensures that the intermediate 1 final liquid composition is greater than or equal to the specified composition xspec_inter1, which is the final liquid composition for ethanol. + + Parameters + ---------- + m : Pyomo ConcreteModel. + The optimization model. + + Returns + ------- + Constraint + The constraint that enforces the intermediate 1 final liquid composition is greater than or equal to the specified composition xspec_inter1, which is the final liquid composition for ethanol. + ''' + return m.x[3, m.sideout1_tray, 3] >= m.xspec_inter3 + + @m.Constraint(doc="Side outlet 2 final liquid composition") + def intermediate2_product(m): + """ + This constraint ensures that the intermediate 2 final liquid composition is greater than or equal to the specified composition xspec_inter2, which is the final liquid composition for butanol. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + + Returns + ------- + Constraint + The constraint that enforces the intermediate 2 final liquid composition is greater than or equal to the specified composition xspec_inter2, which is the final liquid composition for butanol. + """ + return m.x[3, m.sideout2_tray, 2] >= m.xspec_inter2 + + @m.Constraint(doc="Reboiler flowrate") + def _heavy_product_flow(m): + """ + Reboiler flowrate constraint that ensures the reboiler flowrate is greater than or equal to the specified flowrate Bdes, which is the flowrate of butanol. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + + Returns + ------- + Constraint + The constraint that enforces the reboiler flowrate is greater than or equal to the specified flowrate Bdes, which is the flowrate of butanol. + """ + return m.Btotal >= m.Bdes + + @m.Constraint(doc="Condenser flowrate") + def _light_product_flow(m): + """ + Condenser flowrate constraint that ensures the condenser flowrate is greater than or equal to the specified flowrate Ddes, which is the flowrate of ethanol. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + + Returns + ------- + Constraint + The constraint that enforces the condenser flowrate is greater than or equal to the specified flowrate Ddes, which is the flowrate of ethanol. + """ + return m.Dtotal >= m.Ddes + + @m.Constraint(m.so, doc="Intermediate flowrate") + def _intermediate_product_flow(m, so): + """ + Intermediate flowrate constraint that ensures the intermediate flowrate is greater than or equal to the specified flowrate Sdes, which is the flowrate of the intermediate side product 2 and 3. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + so : int + The side product outlet index. + + Returns + ------- + Constraint + The constraint that enforces the intermediate flowrate is greater than or equal to the specified flowrate Sdes, which is the flowrate of the intermediate side product 2 and 3. + """ + return m.Stotal[so] >= m.Sdes + + @m.Constraint(doc="Internal boilup ratio, V/L") + def _internal_boilup_ratio(m): + """ + Internal boilup ratio constraint that ensures the internal boilup ratio is equal to the boilup rate times the liquid flowrate on the reboiler tray is equal to the vapor flowrate on the tray above the reboiler tray. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + + Returns + ------- + Constraint + The constraint that enforces the boilup rate times the liquid flowrate on the reboiler tray is equal to the vapor flowrate on the tray above the reboiler tray. + """ + return m.bu * m.Ltotal[1, m.reb_tray + 1] == m.Vtotal[1, m.reb_tray] + + @m.Constraint(doc="Internal reflux ratio, L/V") + def internal_reflux_ratio(m): + """ + Internal reflux ratio constraint that ensures the internal reflux ratio is equal to the reflux rate times the vapor flowrate on the tray above the condenser tray is equal to the liquid flowrate on the condenser tray. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + + Returns + ------- + Constraint + The constraint that enforces the reflux rate times the vapor flowrate on the tray above the condenser tray is equal to the liquid flowrate on the condenser tray. + """ + return m.rr * m.Vtotal[4, m.con_tray - 1] == m.Ltotal[4, m.con_tray] + + @m.Constraint(doc="External boilup ratio relation with bottoms") + def _external_boilup_ratio(m): + """ + External boilup ratio constraint that ensures the external boilup ratio times the liquid flowrate on the reboiler tray is equal to the bottoms flowrate. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + + Returns + ------- + Constraint + The constraint that enforces the external boilup ratio times the liquid flowrate on the reboiler tray is equal to the bottoms flowrate. + """ + return m.Btotal == (1 - m.bu) * m.Ltotal[1, m.reb_tray + 1] + + @m.Constraint(doc="External reflux ratio relation with distillate") + def _external_reflux_ratio(m): + """ + External reflux ratio constraint that ensures the external reflux ratio times the vapor flowrate on the tray above the condenser tray is equal to the distillate flowrate. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + + Returns + ------- + Constraint + The constraint that enforces the external reflux ratio times the vapor flowrate on the tray above the condenser tray is equal to the distillate flowrate. + """ + return m.Dtotal == (1 - m.rr) * m.Vtotal[4, m.con_tray - 1] + + @m.Constraint(m.section, m.tray, doc="Total vapor flowrate") + def _total_vapor_flowrate(m, sec, n_tray): + """ + Constraint that ensures the total vapor flowrate is equal to the sum of the vapor flowrates of each component on each tray. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + sec : int + The section index. + n_tray : int + The tray index. + + Returns + ------- + Constraint + The constraint that enforces the total vapor flowrate is equal to the sum of the vapor flowrates of each component on each tray on each section. + """ + return sum(m.V[sec, n_tray, comp] for comp in m.comp) == m.Vtotal[sec, n_tray] + + @m.Constraint(m.section, m.tray, doc="Total liquid flowrate") + def _total_liquid_flowrate(m, sec, n_tray): + """ + Constraint that ensures the total liquid flowrate is equal to the sum of the liquid flowrates of each component on each tray on each section. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + sec : int + The section index. + n_tray : int + The tray index. + + Returns + ------- + Constraint + The constraint that enforces the total liquid flowrate is equal to the sum of the liquid flowrates of each component on each tray on each section. + """ + return sum(m.L[sec, n_tray, comp] for comp in m.comp) == m.Ltotal[sec, n_tray] + + @m.Constraint(m.comp, doc="Bottoms and liquid relation") + def bottoms_equality(m, comp): + """ + Constraint that ensures the bottoms flowrate is equal to the liquid flowrate of each component on the reboiler tray. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint that enforces the bottoms flowrate is equal to the liquid flowrate of each component on the reboiler tray. + """ + return m.B[comp] == m.L[1, m.reb_tray, comp] + + @m.Constraint(m.comp) + def condenser_total(m, comp): + """ + Constraint that ensures the distillate flowrate in the condenser is null. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + comp : int + The component index. + Returns + ------- + Constraint + The constraint that enforces the distillate flowrate is equal to zero in the condenser. + """ + return m.V[4, m.con_tray, comp] == 0 + + @m.Constraint() + def total_bottoms_product(m): + """ + Constraint that ensures the total bottoms flowrate is equal to the sum of the bottoms flowrates of each component. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + + Returns + ------- + Constraint + The constraint that enforces the total bottoms flowrate is equal to the sum of the bottoms flowrates of each component. + """ + return sum(m.B[comp] for comp in m.comp) == m.Btotal + + @m.Constraint() + def total_distillate_product(m): + """ + Constraint that ensures the total distillate flowrate is equal to the sum of the distillate flowrates of each component. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + + Returns + ------- + Constraint + The constraint that enforces the total distillate flowrate is equal to the sum of the distillate flowrates of each component. + """ + return sum(m.D[comp] for comp in m.comp) == m.Dtotal + + @m.Constraint(m.so) + def total_side_product(m, so): + """ + Constraint that ensures the total side product flowrate is equal to the sum of the side product flowrates of each component. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + so : int + The side product index, 2 or 3 for the intermediate side products. + + Returns + ------- + Constraint + The constraint that enforces the total side product flowrate is equal to the sum of the side product flowrates of each component. + """ + return sum(m.S[so, comp] for comp in m.comp) == m.Stotal[so] + + # Considers the number of existent trays and operating costs (condenser and reboiler heat duties) in the column. To ensure equal weights to the capital and operating costs, the number of existent trays is multiplied by a weight coefficient of 1000. + + m.obj = Objective( + expr=(m.Qcon + m.Qreb) * m.Hscale + + 1e3 + * ( + sum( + sum( + m.tray_exists[sec, n_tray].binary_indicator_var + for n_tray in m.candidate_trays_main + ) + for sec in m.section_main + ) + + sum( + m.tray_exists[2, n_tray].binary_indicator_var + for n_tray in m.candidate_trays_feed + ) + + sum( + m.tray_exists[3, n_tray].binary_indicator_var + for n_tray in m.candidate_trays_product + ) + + 1 + ), + sense=minimize, + doc="Objective function to minimize the operating costs and number of existent trays in the column", + ) + + @m.Constraint( + m.section_main, m.candidate_trays_main, doc="Logic proposition for main section" + ) + def _logic_proposition_main(m, sec, n_tray): + """ + Apply a logic proposition constraint to the main section and candidate trays to specify the order of trays in the column is from bottom to top provided the condition is met. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + sec : int + The section index. + n_tray : int + The tray index. + + Returns + ------- + Constraint or NoConstraint + The constraint expression or NoConstraint if the condition is not met. + """ + + if n_tray > m.reb_tray and (n_tray + 1) < m.num_trays: + return ( + m.tray_exists[sec, n_tray].binary_indicator_var + <= m.tray_exists[sec, n_tray + 1].binary_indicator_var + ) + else: + return Constraint.NoConstraint + # TOCHECK: Update the logic proposition constraint for the main section with the new pyomo.gdp syntax + + @m.Constraint(m.candidate_trays_feed) + def _logic_proposition_feed(m, n_tray): + """ + Apply a logic proposition constraint to the feed section and candidate trays to specify the order of trays in the column is from bottom to top provided the condition is met. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + n_tray : int + The tray index. + + Returns + ------- + Constraint or NoConstraint + The constraint expression or NoConstraint if the condition is not met. + """ + if n_tray > m.reb_tray and (n_tray + 1) < m.feed_tray: + return ( + m.tray_exists[2, n_tray].binary_indicator_var + <= m.tray_exists[2, n_tray + 1].binary_indicator_var + ) + elif n_tray > m.feed_tray and (n_tray + 1) < m.con_tray: + return ( + m.tray_exists[2, n_tray + 1].binary_indicator_var + <= m.tray_exists[2, n_tray].binary_indicator_var + ) + else: + return Constraint.NoConstraint + # TODO: Update the logic proposition constraint for the feed section with the new pyomo.gdp syntax + + @m.Constraint(m.candidate_trays_product) + def _logic_proposition_section3(m, n_tray): + """ + Apply a logic proposition constraint to the product section and candidate trays to specify the order of trays in the column is from bottom to top provided the condition is met. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + n_tray : int + The tray index. + + Returns + ------- + Constraint or NoConstraint + The constraint expression or NoConstraint if the condition is not met. + """ + if n_tray > 1 and (n_tray + 1) < m.num_trays: + return ( + m.tray_exists[3, n_tray].binary_indicator_var + <= m.tray_exists[3, n_tray + 1].binary_indicator_var + ) + else: + return Constraint.NoConstraint + # TODO: Update the logic proposition constraint for the product section with the new pyomo.gdp syntax + + @m.Constraint(m.tray) + def equality_feed_product_side(m, n_tray): + """ + Constraint that enforces the equality of the binary indicator variables for the feed and product side trays. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + n_tray : int + The tray index. + + Returns + ------- + Constraint + The constraint expression that enforces the equality of the binary indicator variables for the feed and product side trays. + """ + return ( + m.tray_exists[2, n_tray].binary_indicator_var + == m.tray_exists[3, n_tray].binary_indicator_var + ) + + # TODO: Update the equality constraint for the feed and product side trays with the new pyomo.gdp syntax + + @m.Constraint() + def _existent_minimum_numbertrays(m): + """ + Constraint that enforces the minimum number of trays in the column. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + + Returns + ------- + Constraint + The constraint expression that enforces the minimum number of trays in each section and each tray to be greater than or equal to the minimum number of trays. + """ + return sum( + sum(m.tray_exists[sec, n_tray].binary_indicator_var for n_tray in m.tray) + for sec in m.section + ) - sum( + m.tray_exists[3, n_tray].binary_indicator_var for n_tray in m.tray + ) >= int( + m.min_tray + ) + + return m + + +def enforce_tray_exists(m, sec, n_tray): + """ + Enforce the existence of a tray in the column. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + sec : int + The section index. + n_tray : int + The tray index. + """ + m.tray_exists[sec, n_tray].indicator_var.fix(True) + m.tray_absent[sec, n_tray].deactivate() + + +def _build_tray_equations(m, sec, n_tray): + """ + Build the equations for the tray in the column as a function of the section when the tray exists. + Points to the appropriate function to build the equations for the section in the column. + + Parameters + ---------- + m : Pyomo ConcreteModel + The optimization model. + sec : int + The section index. + n_tray : int + The tray index. + + Returns + ------- + None + """ + build_function = { + 1: _build_bottom_equations, + 2: _build_feed_side_equations, + 3: _build_product_side_equations, + 4: _build_top_equations, + } + build_function[sec](m, n_tray) + + +def _build_bottom_equations(disj, n_tray): + """ + Build the equations for the bottom section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the bottom section in the column. + n_tray : int + The tray index. + + Returns + ------- + None + """ + m = disj.model() + + @disj.Constraint(m.comp, doc="Bottom section 1 mass per component balances") + def _bottom_mass_percomponent_balances(disj, comp): + """ + Mass per component balances for the bottom section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the bottom section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the mass balance per component in the bottom section of the column. + """ + return (m.L[1, n_tray + 1, comp] if n_tray < m.num_trays else 0) + ( + m.L[2, 1, comp] if n_tray == m.num_trays else 0 + ) + (m.L[3, 1, comp] if n_tray == m.num_trays else 0) - ( + m.L[1, n_tray, comp] if n_tray > m.reb_tray else 0 + ) + ( + m.V[1, n_tray - 1, comp] if n_tray > m.reb_tray else 0 + ) - ( + m.V[1, n_tray, comp] * m.dv[2] if n_tray == m.num_trays else 0 + ) - ( + m.V[1, n_tray, comp] * m.dv[3] if n_tray == m.num_trays else 0 + ) - ( + m.V[1, n_tray, comp] if n_tray < m.num_trays else 0 + ) - ( + m.B[comp] if n_tray == m.reb_tray else 0 + ) == m.slack[ + 1, n_tray, comp + ] + + @disj.Constraint(doc="Bottom section 1 energy balances") + def _bottom_energy_balances(disj): + """ + Energy balances for the bottom section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the bottom section in the column. + + Returns + ------- + Constraint + The constraint expression that enforces the energy balance for the bottom section in the column. + """ + return ( + sum( + ( + m.L[1, n_tray + 1, comp] * m.hl[1, n_tray + 1, comp] + if n_tray < m.num_trays + else 0 + ) + + (m.L[2, 1, comp] * m.hl[2, 1, comp] if n_tray == m.num_trays else 0) + + (m.L[3, 1, comp] * m.hl[3, 1, comp] if n_tray == m.num_trays else 0) + - ( + m.L[1, n_tray, comp] * m.hl[1, n_tray, comp] + if n_tray > m.reb_tray + else 0 + ) + + ( + m.V[1, n_tray - 1, comp] * m.hv[1, n_tray - 1, comp] + if n_tray > m.reb_tray + else 0 + ) + - ( + m.V[1, n_tray, comp] * m.dv[2] * m.hv[1, n_tray, comp] + if n_tray == m.num_trays + else 0 + ) + - ( + m.V[1, n_tray, comp] * m.dv[3] * m.hv[1, n_tray, comp] + if n_tray == m.num_trays + else 0 + ) + - ( + m.V[1, n_tray, comp] * m.hv[1, n_tray, comp] + if n_tray < m.num_trays + else 0 + ) + - (m.B[comp] * m.hl[1, n_tray, comp] if n_tray == m.reb_tray else 0) + for comp in m.comp + ) + * m.Qscale + + (m.Qreb if n_tray == m.reb_tray else 0) + == 0 + ) + + @disj.Constraint(m.comp, doc="Bottom section 1 liquid flowrate per component") + def _bottom_liquid_percomponent(disj, comp): + """ + Liquid flowrate per component in the bottom section of the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the bottom section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid flowrate per component in the bottom section of the column. + """ + return m.L[1, n_tray, comp] == m.Ltotal[1, n_tray] * m.x[1, n_tray, comp] + + @disj.Constraint(m.comp, doc="Bottom section 1 vapor flowrate per component") + def _bottom_vapor_percomponent(disj, comp): + """ + Vapor flowrate per component in the bottom section of the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the bottom section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor flowrate per component in the bottom section of the column. + """ + return m.V[1, n_tray, comp] == m.Vtotal[1, n_tray] * m.y[1, n_tray, comp] + + @disj.Constraint(doc="Bottom section 1 liquid composition equilibrium summation") + def bottom_liquid_composition_summation(disj): + """ + Liquid composition equilibrium summation for the bottom section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the bottom section in the column. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid composition equilibrium summation for the bottom section in the column. + It ensures the sum of the liquid compositions is equal to 1 plus the error in the liquid composition. + """ + return sum(m.x[1, n_tray, comp] for comp in m.comp) - 1 == m.errx[1, n_tray] + + @disj.Constraint(doc="Bottom section 1 vapor composition equilibrium summation") + def bottom_vapor_composition_summation(disj): + """ + Vapor composition equilibrium summation for the bottom section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the bottom section in the column. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor composition equilibrium summation for the bottom section in the column. + It ensures the sum of the vapor compositions is equal to 1 plus the error in the vapor composition. + """ + return sum(m.y[1, n_tray, comp] for comp in m.comp) - 1 == m.erry[1, n_tray] + + @disj.Constraint(m.comp, doc="Bottom section 1 vapor composition") + def bottom_vapor_composition(disj, comp): + """ + Vapor composition for the bottom section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the bottom section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor composition for the bottom section in the column. + The equation is derived from the vapor-liquid equilibrium relationship. + """ + return ( + m.y[1, n_tray, comp] + == m.x[1, n_tray, comp] + * ( + m.actv[1, n_tray, comp] + * ( + m.prop[comp, 'PC'] + * exp( + m.prop[comp, 'TC'] + / m.T[1, n_tray] + * ( + m.prop[comp, 'vpA'] + * (1 - m.T[1, n_tray] / m.prop[comp, 'TC']) + + m.prop[comp, 'vpB'] + * (1 - m.T[1, n_tray] / m.prop[comp, 'TC']) ** 1.5 + + m.prop[comp, 'vpC'] + * (1 - m.T[1, n_tray] / m.prop[comp, 'TC']) ** 3 + + m.prop[comp, 'vpD'] + * (1 - m.T[1, n_tray] / m.prop[comp, 'TC']) ** 6 + ) + ) + ) + ) + / m.P[1, n_tray] + ) + + @disj.Constraint(m.comp, doc="Bottom section 1 liquid enthalpy") + def bottom_liquid_enthalpy(disj, comp): + """ + Liquid enthalpy for the bottom section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the bottom section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid enthalpy for the bottom section in the column. + """ + return m.hl[1, n_tray, comp] == ( + m.cpc[1] + * ( + (m.T[1, n_tray] - m.Tref) * m.prop[comp, 'cpA', 1] + + (m.T[1, n_tray] ** 2 - m.Tref**2) + * m.prop[comp, 'cpB', 1] + * m.cpc2['A', 1] + / 2 + + (m.T[1, n_tray] ** 3 - m.Tref**3) + * m.prop[comp, 'cpC', 1] + * m.cpc2['B', 1] + / 3 + + (m.T[1, n_tray] ** 4 - m.Tref**4) * m.prop[comp, 'cpD', 1] / 4 + ) + / m.Hscale + ) + + @disj.Constraint(m.comp, doc="Bottom section 1 vapor enthalpy") + def bottom_vapor_enthalpy(disj, comp): + """ + Vapor enthalpy for the bottom section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the bottom section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor enthalpy for the bottom section in the column. + """ + return m.hv[1, n_tray, comp] == ( + m.cpc[2] + * ( + (m.T[1, n_tray] - m.Tref) * m.prop[comp, 'cpA', 2] + + (m.T[1, n_tray] ** 2 - m.Tref**2) + * m.prop[comp, 'cpB', 2] + * m.cpc2['A', 2] + / 2 + + (m.T[1, n_tray] ** 3 - m.Tref**3) + * m.prop[comp, 'cpC', 2] + * m.cpc2['B', 2] + / 3 + + (m.T[1, n_tray] ** 4 - m.Tref**4) * m.prop[comp, 'cpD', 2] / 4 + ) + / m.Hscale + + m.dHvap[comp] + ) + + @disj.Constraint(m.comp, doc="Bottom section 1 liquid activity coefficient") + def bottom_activity_coefficient(disj, comp): + """ + Liquid activity coefficient for the bottom section in the column equal to 1. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the bottom section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid activity coefficient for the bottom section for each component and tray in the column to be equal to 1. + """ + return m.actv[1, n_tray, comp] == 1 + + +def _build_feed_side_equations(disj, n_tray): + """ + Build the equations for the feed side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the feed side section in the column. + n_tray : int + The tray index. + + Returns + ------- + None + """ + m = disj.model() + + @disj.Constraint(m.comp, doc="Feed section 2 mass per component balances") + def _feedside_masspercomponent_balances(disj, comp): + """ + Mass per component balances for the feed side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the feed side section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the mass balance per component in the feed side section of the column. + """ + return (m.L[2, n_tray + 1, comp] if n_tray < m.num_trays else 0) + ( + m.L[4, 1, comp] * m.dl[2] if n_tray == m.num_trays else 0 + ) - m.L[2, n_tray, comp] + ( + m.V[1, m.num_trays, comp] * m.dv[2] if n_tray == 1 else 0 + ) + ( + m.V[2, n_tray - 1, comp] if n_tray > 1 else 0 + ) - m.V[ + 2, n_tray, comp + ] + ( + m.F[comp] if n_tray == m.feed_tray else 0 + ) == 0 + + @disj.Constraint(doc="Feed section 2 energy balances") + def _feedside_energy_balances(disj): + """ + Energy balances for the feed side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the feed side section in the column. + + Returns + ------- + Constraint + The constraint expression that enforces the energy balance for the feed side section in the column. + """ + return ( + sum( + ( + m.L[2, n_tray + 1, comp] * m.hl[2, n_tray + 1, comp] + if n_tray < m.num_trays + else 0 + ) + + ( + m.L[4, 1, comp] * m.dl[2] * m.hl[4, 1, comp] + if n_tray == m.num_trays + else 0 + ) + - m.L[2, n_tray, comp] * m.hl[2, n_tray, comp] + + ( + m.V[1, m.num_trays, comp] * m.dv[2] * m.hv[1, m.num_trays, comp] + if n_tray == 1 + else 0 + ) + + ( + m.V[2, n_tray - 1, comp] * m.hv[2, n_tray - 1, comp] + if n_tray > 1 + else 0 + ) + - m.V[2, n_tray, comp] * m.hv[2, n_tray, comp] + for comp in m.comp + ) + * m.Qscale + + sum( + ( + m.F[comp] * (m.hlf[comp] * (1 - m.q) + m.hvf[comp] * m.q) + if n_tray == m.feed_tray + else 0 + ) + for comp in m.comp + ) + * m.Qscale + == 0 + ) + + @disj.Constraint(m.comp, doc="Feed section 2 liquid flowrate per component") + def _feedside_liquid_percomponent(disj, comp): + """ + Liquid flowrate per component in the feed side section of the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the feed side section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid flowrate per component in the feed side section of the column is equal to the total liquid flowrate times the liquid composition. + """ + return m.L[2, n_tray, comp] == m.Ltotal[2, n_tray] * m.x[2, n_tray, comp] + + @disj.Constraint(m.comp, doc="Feed section 2 vapor flowrate per component") + def _feedside_vapor_percomponent(disj, comp): + """ + Vapor flowrate per component in the feed side section of the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the feed side section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor flowrate per component in the feed side section of the column is equal to the total vapor flowrate times the vapor composition. + """ + return m.V[2, n_tray, comp] == m.Vtotal[2, n_tray] * m.y[2, n_tray, comp] + + @disj.Constraint(doc="Feed section 2 liquid composition equilibrium summation") + def feedside_liquid_composition_summation(disj): + """ + Liquid composition equilibrium summation for the feed side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the feed side section in the column. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid composition equilibrium summation for the feed side section in the column. + It ensures the sum of the liquid compositions is equal to 1 plus the error in the liquid composition. + """ + return sum(m.x[2, n_tray, comp] for comp in m.comp) - 1 == m.errx[2, n_tray] + + @disj.Constraint(doc="Feed section 2 vapor composition equilibrium summation") + def feedside_vapor_composition_summation(disj): + """ + Vapor composition equilibrium summation for the feed side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the feed side section in the column. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor composition equilibrium summation for the feed side section in the column. + It ensures the sum of the vapor compositions is equal to 1 plus the error in the vapor composition. + """ + return sum(m.y[2, n_tray, comp] for comp in m.comp) - 1 == m.erry[2, n_tray] + + @disj.Constraint(m.comp, doc="Feed section 2 vapor composition") + def feedside_vapor_composition(disj, comp): + """ + Vapor composition for the feed side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the feed side section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor composition for the feed side section in the column. + The equation is derived from the vapor-liquid equilibrium relationship. + """ + return ( + m.y[2, n_tray, comp] + == m.x[2, n_tray, comp] + * ( + m.actv[2, n_tray, comp] + * ( + m.prop[comp, 'PC'] + * exp( + m.prop[comp, 'TC'] + / m.T[2, n_tray] + * ( + m.prop[comp, 'vpA'] + * (1 - m.T[2, n_tray] / m.prop[comp, 'TC']) + + m.prop[comp, 'vpB'] + * (1 - m.T[2, n_tray] / m.prop[comp, 'TC']) ** 1.5 + + m.prop[comp, 'vpC'] + * (1 - m.T[2, n_tray] / m.prop[comp, 'TC']) ** 3 + + m.prop[comp, 'vpD'] + * (1 - m.T[2, n_tray] / m.prop[comp, 'TC']) ** 6 + ) + ) + ) + ) + / m.P[2, n_tray] + ) + + @disj.Constraint(m.comp, doc="Feed section 2 liquid enthalpy") + def feedside_liquid_enthalpy(disj, comp): + """ + Liquid enthalpy for the feed side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the feed side section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid enthalpy for the feed side section in the column. + """ + return m.hl[2, n_tray, comp] == ( + m.cpc[1] + * ( + (m.T[2, n_tray] - m.Tref) * m.prop[comp, 'cpA', 1] + + (m.T[2, n_tray] ** 2 - m.Tref**2) + * m.prop[comp, 'cpB', 1] + * m.cpc2['A', 1] + / 2 + + (m.T[2, n_tray] ** 3 - m.Tref**3) + * m.prop[comp, 'cpC', 1] + * m.cpc2['B', 1] + / 3 + + (m.T[2, n_tray] ** 4 - m.Tref**4) * m.prop[comp, 'cpD', 1] / 4 + ) + / m.Hscale + ) + + @disj.Constraint(m.comp, doc="Feed section 2 vapor enthalpy") + def feedside_vapor_enthalpy(disj, comp): + """ + Vapor enthalpy for the feed side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the feed side section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor enthalpy for the feed side section in the column. + """ + return m.hv[2, n_tray, comp] == ( + m.cpc[2] + * ( + (m.T[2, n_tray] - m.Tref) * m.prop[comp, 'cpA', 2] + + (m.T[2, n_tray] ** 2 - m.Tref**2) + * m.prop[comp, 'cpB', 2] + * m.cpc2['A', 2] + / 2 + + (m.T[2, n_tray] ** 3 - m.Tref**3) + * m.prop[comp, 'cpC', 2] + * m.cpc2['B', 2] + / 3 + + (m.T[2, n_tray] ** 4 - m.Tref**4) * m.prop[comp, 'cpD', 2] / 4 + ) + / m.Hscale + + m.dHvap[comp] + ) + + @disj.Constraint(m.comp, doc="Feed section 2 liquid activity coefficient") + def feedside_activity_coefficient(disj, comp): + """ + Liquid activity coefficient for the feed side section in the column equal to 1. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the feed side section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid activity coefficient for the feed side section for each component and tray in the column to be equal to 1. + This is an assumption for the feed side section, since the feed is assumed to be ideal. + """ + return m.actv[2, n_tray, comp] == 1 + + +def _build_product_side_equations(disj, n_tray): + """ + Build the equations for the product side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the product side section in the column. + n_tray : int + The tray index. + + Returns + ------- + None + """ + m = disj.model() + + @disj.Constraint(m.comp, doc="Product section 3 mass per component balances") + def _productside_masspercomponent_balances(disj, comp): + """ + Mass per component balances for the product side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the product side section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the mass balance per component in the product side section of the column. + """ + return (m.L[3, n_tray + 1, comp] if n_tray < m.num_trays else 0) + ( + m.L[4, 1, comp] * m.dl[3] if n_tray == m.num_trays else 0 + ) - m.L[3, n_tray, comp] + ( + m.V[1, m.num_trays, comp] * m.dv[3] if n_tray == 1 else 0 + ) + ( + m.V[3, n_tray - 1, comp] if n_tray > 1 else 0 + ) - m.V[ + 3, n_tray, comp + ] - ( + m.S[1, comp] if n_tray == m.sideout1_tray else 0 + ) - ( + m.S[2, comp] if n_tray == m.sideout2_tray else 0 + ) == 0 + + @disj.Constraint(doc="Product section 3 energy balances") + def _productside_energy_balances(disj): + """ + Energy balances for the product side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the product side section in the column. + + Returns + ------- + Constraint + The constraint expression that enforces the energy balance for the product side section in the column. + """ + return ( + sum( + ( + m.L[3, n_tray + 1, comp] * m.hl[3, n_tray + 1, comp] + if n_tray < m.num_trays + else 0 + ) + + ( + m.L[4, 1, comp] * m.dl[3] * m.hl[4, 1, comp] + if n_tray == m.num_trays + else 0 + ) + - m.L[3, n_tray, comp] * m.hl[3, n_tray, comp] + + ( + m.V[1, m.num_trays, comp] * m.dv[3] * m.hv[1, m.num_trays, comp] + if n_tray == 1 + else 0 + ) + + ( + m.V[3, n_tray - 1, comp] * m.hv[3, n_tray - 1, comp] + if n_tray > 1 + else 0 + ) + - m.V[3, n_tray, comp] * m.hv[3, n_tray, comp] + - ( + m.S[1, comp] * m.hl[3, n_tray, comp] + if n_tray == m.sideout1_tray + else 0 + ) + - ( + m.S[2, comp] * m.hl[3, n_tray, comp] + if n_tray == m.sideout2_tray + else 0 + ) + for comp in m.comp + ) + * m.Qscale + == 0 + ) + + @disj.Constraint(m.comp, doc="Product section 3 liquid flowrate per component") + def _productside_liquid_percomponent(disj, comp): + """ + Liquid flowrate per component in the product side section of the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the product side section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid flowrate per component in the product side section of the column is equal to the total liquid flowrate times the liquid composition. + """ + return m.L[3, n_tray, comp] == m.Ltotal[3, n_tray] * m.x[3, n_tray, comp] + + @disj.Constraint(m.comp, doc="Product section 3 vapor flowrate per component") + def _productside_vapor_percomponent(disj, comp): + """ + Vapor flowrate per component in the product side section of the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the product side section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor flowrate per component in the product side section of the column is equal to the total vapor flowrate times the vapor composition. + """ + return m.V[3, n_tray, comp] == m.Vtotal[3, n_tray] * m.y[3, n_tray, comp] + + @disj.Constraint(doc="Product section 3 liquid composition equilibrium summation") + def productside_liquid_composition_summation(disj): + """ + Liquid composition equilibrium summation for the product side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the product side section in the column. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid composition equilibrium summation for the product side section in the column. + It ensures the sum of the liquid compositions is equal to 1 plus the error in the liquid composition. + """ + return sum(m.x[3, n_tray, comp] for comp in m.comp) - 1 == m.errx[3, n_tray] + + @disj.Constraint(doc="Product section 3 vapor composition equilibrium summation") + def productside_vapor_composition_summation(disj): + """ + Vapor composition equilibrium summation for the product side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the product side section in the column. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor composition equilibrium summation for the product side section in the column. + It ensures the sum of the vapor compositions is equal to 1 plus the error in the vapor composition. + """ + return sum(m.y[3, n_tray, comp] for comp in m.comp) - 1 == m.erry[3, n_tray] + + @disj.Constraint(m.comp, doc="Product section 3 vapor composition") + def productside_vapor_composition(disj, comp): + """ + Vapor composition for the product side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the product side section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor composition for the product side section in the column. + The equation is derived from the vapor-liquid equilibrium relationship. + """ + return ( + m.y[3, n_tray, comp] + == m.x[3, n_tray, comp] + * ( + m.actv[3, n_tray, comp] + * ( + m.prop[comp, 'PC'] + * exp( + m.prop[comp, 'TC'] + / m.T[3, n_tray] + * ( + m.prop[comp, 'vpA'] + * (1 - m.T[3, n_tray] / m.prop[comp, 'TC']) + + m.prop[comp, 'vpB'] + * (1 - m.T[3, n_tray] / m.prop[comp, 'TC']) ** 1.5 + + m.prop[comp, 'vpC'] + * (1 - m.T[3, n_tray] / m.prop[comp, 'TC']) ** 3 + + m.prop[comp, 'vpD'] + * (1 - m.T[3, n_tray] / m.prop[comp, 'TC']) ** 6 + ) + ) + ) + ) + / m.P[3, n_tray] + ) + + @disj.Constraint(m.comp, doc="Product section 3 liquid enthalpy") + def productside_liquid_enthalpy(disj, comp): + """ + Liquid enthalpy for the product side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the product side section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid enthalpy for the product side section in the column. + """ + return m.hl[3, n_tray, comp] == ( + m.cpc[1] + * ( + (m.T[3, n_tray] - m.Tref) * m.prop[comp, 'cpA', 1] + + (m.T[3, n_tray] ** 2 - m.Tref**2) + * m.prop[comp, 'cpB', 1] + * m.cpc2['A', 1] + / 2 + + (m.T[3, n_tray] ** 3 - m.Tref**3) + * m.prop[comp, 'cpC', 1] + * m.cpc2['B', 1] + / 3 + + (m.T[3, n_tray] ** 4 - m.Tref**4) * m.prop[comp, 'cpD', 1] / 4 + ) + / m.Hscale + ) + + @disj.Constraint(m.comp, doc="Product section 3 vapor enthalpy") + def productside_vapor_enthalpy(disj, comp): + """ + Vapor enthalpy for the product side section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the product side section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor enthalpy for the product side section in the column. + """ + return m.hv[3, n_tray, comp] == ( + m.cpc[2] + * ( + (m.T[3, n_tray] - m.Tref) * m.prop[comp, 'cpA', 2] + + (m.T[3, n_tray] ** 2 - m.Tref**2) + * m.prop[comp, 'cpB', 2] + * m.cpc2['A', 2] + / 2 + + (m.T[3, n_tray] ** 3 - m.Tref**3) + * m.prop[comp, 'cpC', 2] + * m.cpc2['B', 2] + / 3 + + (m.T[3, n_tray] ** 4 - m.Tref**4) * m.prop[comp, 'cpD', 2] / 4 + ) + / m.Hscale + + m.dHvap[comp] + ) + + @disj.Constraint(m.comp, doc="Product section 3 liquid activity coefficient") + def productside_activity_coefficient(disj, comp): + """ + Liquid activity coefficient for the product side section in the column equal to 1. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the product side section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid activity coefficient for the product side section for each component and tray in the column to be equal to 1. + This is an assumption for the product side section, since the product is assumed to be ideal. + """ + return m.actv[3, n_tray, comp] == 1 + + +def _build_top_equations(disj, n_tray): + """ + Build the equations for the top section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the top section in the column. + n_tray : int + The tray index. + + Returns + ------- + None + """ + m = disj.model() + + @disj.Constraint(m.comp, doc="Top section 4 mass per component balances") + def _top_mass_percomponent_balances(disj, comp): + """ + Mass per component balances for the top section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the top section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the mass balance per component in the top section of the column. + """ + return (m.L[4, n_tray + 1, comp] if n_tray < m.con_tray else 0) - ( + m.L[4, n_tray, comp] * m.dl[2] if n_tray == 1 else 0 + ) - (m.L[4, n_tray, comp] * m.dl[3] if n_tray == 1 else 0) - ( + m.L[4, n_tray, comp] if n_tray > 1 else 0 + ) + ( + m.V[2, m.num_trays, comp] if n_tray == 1 else 0 + ) + ( + m.V[3, m.num_trays, comp] if n_tray == 1 else 0 + ) + ( + m.V[4, n_tray - 1, comp] if n_tray > 1 else 0 + ) - ( + m.V[4, n_tray, comp] if n_tray < m.con_tray else 0 + ) - ( + m.D[comp] if n_tray == m.con_tray else 0 + ) == 0 + + @disj.Constraint(doc="Top scetion 4 energy balances") + def _top_energy_balances(disj): + """ + Energy balances for the top section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the top section in the column. + + Returns + ------- + Constraint + The constraint expression that enforces the energy balance for the top section in the column. + """ + return ( + sum( + ( + m.L[4, n_tray + 1, comp] * m.hl[4, n_tray + 1, comp] + if n_tray < m.con_tray + else 0 + ) + - ( + m.L[4, n_tray, comp] * m.dl[2] * m.hl[4, n_tray, comp] + if n_tray == 1 + else 0 + ) + - ( + m.L[4, n_tray, comp] * m.dl[3] * m.hl[4, n_tray, comp] + if n_tray == 1 + else 0 + ) + - (m.L[4, n_tray, comp] * m.hl[4, n_tray, comp] if n_tray > 1 else 0) + + ( + m.V[2, m.num_trays, comp] * m.hv[2, m.num_trays, comp] + if n_tray == 1 + else 0 + ) + + ( + m.V[3, m.num_trays, comp] * m.hv[3, m.num_trays, comp] + if n_tray == 1 + else 0 + ) + + ( + m.V[4, n_tray - 1, comp] * m.hv[4, n_tray - 1, comp] + if n_tray > 1 + else 0 + ) + - ( + m.V[4, n_tray, comp] * m.hv[4, n_tray, comp] + if n_tray < m.con_tray + else 0 + ) + - (m.D[comp] * m.hl[4, n_tray, comp] if n_tray == m.con_tray else 0) + for comp in m.comp + ) + * m.Qscale + - (m.Qcon if n_tray == m.con_tray else 0) + == 0 + ) + + @disj.Constraint(m.comp, doc="Top section 4 liquid flowrate per component") + def _top_liquid_percomponent(disj, comp): + """ + Liquid flowrate per component in the top section of the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the top section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid flowrate per component in the top section of the column is equal to the total liquid flowrate times the liquid composition. + """ + return m.L[4, n_tray, comp] == m.Ltotal[4, n_tray] * m.x[4, n_tray, comp] + + @disj.Constraint(m.comp, doc="Top section 4 vapor flowrate per component") + def _top_vapor_percomponent(disj, comp): + """ + Vapor flowrate per component in the top section of the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the top section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor flowrate per component in the top section of the column is equal to the total vapor flowrate times the vapor composition. + """ + return m.V[4, n_tray, comp] == m.Vtotal[4, n_tray] * m.y[4, n_tray, comp] + + @disj.Constraint(doc="Top section 4 liquid composition equilibrium summation") + def top_liquid_composition_summation(disj): + """ + Liquid composition equilibrium summation for the top section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the top section in the column. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid composition equilibrium summation for the top section in the column. + It ensures the sum of the liquid compositions is equal to 1 plus the error in the liquid composition. + """ + return sum(m.x[4, n_tray, comp] for comp in m.comp) - 1 == m.errx[4, n_tray] + + @disj.Constraint(doc="Top section 4 vapor composition equilibrium summation") + def top_vapor_composition_summation(disj): + """ + Vapor composition equilibrium summation for the top section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the top section in the column. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor composition equilibrium summation for the top section in the column. + It ensures the sum of the vapor compositions is equal to 1 plus the error in the vapor composition. + """ + return sum(m.y[4, n_tray, comp] for comp in m.comp) - 1 == m.erry[4, n_tray] + + @disj.Constraint(m.comp, doc="Top scetion 4 vapor composition") + def top_vapor_composition(disj, comp): + """ + Vapor composition for the top section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the top section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor composition for the top section in the column. + The equation is derived from the vapor-liquid equilibrium relationship. + """ + return ( + m.y[4, n_tray, comp] + == m.x[4, n_tray, comp] + * ( + m.actv[4, n_tray, comp] + * ( + m.prop[comp, 'PC'] + * exp( + m.prop[comp, 'TC'] + / m.T[4, n_tray] + * ( + m.prop[comp, 'vpA'] + * (1 - m.T[4, n_tray] / m.prop[comp, 'TC']) + + m.prop[comp, 'vpB'] + * (1 - m.T[4, n_tray] / m.prop[comp, 'TC']) ** 1.5 + + m.prop[comp, 'vpC'] + * (1 - m.T[4, n_tray] / m.prop[comp, 'TC']) ** 3 + + m.prop[comp, 'vpD'] + * (1 - m.T[4, n_tray] / m.prop[comp, 'TC']) ** 6 + ) + ) + ) + ) + / m.P[4, n_tray] + ) + + @disj.Constraint(m.comp, doc="Top section 4 liquid enthalpy") + def top_liquid_enthalpy(disj, comp): + """ + Liquid enthalpy for the top section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the top section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid enthalpy for the top section in the column. + """ + return m.hl[4, n_tray, comp] == ( + m.cpc[1] + * ( + (m.T[4, n_tray] - m.Tref) * m.prop[comp, 'cpA', 1] + + (m.T[4, n_tray] ** 2 - m.Tref**2) + * m.prop[comp, 'cpB', 1] + * m.cpc2['A', 1] + / 2 + + (m.T[4, n_tray] ** 3 - m.Tref**3) + * m.prop[comp, 'cpC', 1] + * m.cpc2['B', 1] + / 3 + + (m.T[4, n_tray] ** 4 - m.Tref**4) * m.prop[comp, 'cpD', 1] / 4 + ) + / m.Hscale + ) + + @disj.Constraint(m.comp, doc="Top section 4 vapor enthalpy") + def top_vapor_enthalpy(disj, comp): + """ + Vapor enthalpy for the top section in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the top section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor enthalpy for the top section in the column. + """ + return m.hv[4, n_tray, comp] == ( + m.cpc[2] + * ( + (m.T[4, n_tray] - m.Tref) * m.prop[comp, 'cpA', 2] + + (m.T[4, n_tray] ** 2 - m.Tref**2) + * m.prop[comp, 'cpB', 2] + * m.cpc2['A', 2] + / 2 + + (m.T[4, n_tray] ** 3 - m.Tref**3) + * m.prop[comp, 'cpC', 2] + * m.cpc2['B', 2] + / 3 + + (m.T[4, n_tray] ** 4 - m.Tref**4) * m.prop[comp, 'cpD', 2] / 4 + ) + / m.Hscale + + m.dHvap[comp] + ) + + @disj.Constraint(m.comp, doc="Top section 4 liquid activity coefficient") + def top_activity_coefficient(disj, comp): + """ + Liquid activity coefficient for the top section in the column equal to 1. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the top section in the column. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid activity coefficient for the top section for each component and tray in the column to be equal to 1. + This is an assumption for the top section, since the product is assumed to be ideal. + """ + return m.actv[4, n_tray, comp] == 1 + + +def _build_pass_through_eqns(disj, sec, n_tray): + """ + Build the equations for the pass through section in the column when a given tray in the disjunct is not active if it is the first or last tray. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the pass through section in the column. + sec : int + The section index. + n_tray : int + The tray index. + + Returns + ------- + None + """ + m = disj.model() + + # If the tray is the first or last tray, then the liquid and vapor flowrates, compositions, enthalpies, and temperature are passed through. + if n_tray == 1 or n_tray == m.num_trays: + return + + @disj.Constraint(m.comp, doc="Pass through liquid flowrate") + def pass_through_liquid_flowrate(disj, comp): + """ + Pass through liquid flowrate for the given tray in the column. + The constraint enforces the liquid flowrate for the given tray is equal to the liquid flowrate for the tray above it. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the pass through when the tray is inactive. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid flowrate for the given tray is equal to the liquid flowrate for the tray above it. + """ + return m.L[sec, n_tray, comp] == m.L[sec, n_tray + 1, comp] + + @disj.Constraint(m.comp, doc="Pass through vapor flowrate") + def pass_through_vapor_flowrate(disj, comp): + """ + Pass through vapor flowrate for the given tray in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the pass through when the tray is inactive. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor flowrate for the given tray is equal to the vapor flowrate for the tray below it. + """ + return m.V[sec, n_tray, comp] == m.V[sec, n_tray - 1, comp] + + @disj.Constraint(m.comp, doc="Pass through liquid composition") + def pass_through_liquid_composition(disj, comp): + """ + Pass through liquid composition for the given tray in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the pass through when the tray is inactive. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid composition for the given tray is equal to the liquid composition for the tray above it. + """ + return m.x[sec, n_tray, comp] == m.x[sec, n_tray + 1, comp] + + @disj.Constraint(m.comp, doc="Pass through vapor composition") + def pass_through_vapor_composition(disj, comp): + """ + Pass through vapor composition for the given tray in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the pass through when the tray is inactive. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor composition for the given tray is equal to the vapor composition for the tray below it. + """ + return m.y[sec, n_tray, comp] == m.y[sec, n_tray + 1, comp] + + @disj.Constraint(m.comp, doc="Pass through liquid enthalpy") + def pass_through_liquid_enthalpy(disj, comp): + """ + Pass through liquid enthalpy for the given tray in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the pass through when the tray is inactive. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the liquid enthalpy for the given tray is equal to the liquid enthalpy for the tray above it. + """ + return m.hl[sec, n_tray, comp] == m.hl[sec, n_tray + 1, comp] + + @disj.Constraint(m.comp, doc="Pass through vapor enthalpy") + def pass_through_vapor_enthalpy(disj, comp): + """ + Pass through vapor enthalpy for the given tray in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the pass through when the tray is inactive. + comp : int + The component index. + + Returns + ------- + Constraint + The constraint expression that enforces the vapor enthalpy for the given tray is equal to the vapor enthalpy for the tray below it. + """ + return m.hv[sec, n_tray, comp] == m.hv[sec, n_tray - 1, comp] + + @disj.Constraint(doc="Pass through temperature") + def pass_through_temperature(disj): + """ + Pass through temperature for the given tray in the column. + + Parameters + ---------- + disj : Disjunct + The disjunct object for the pass through when the tray is inactive. + + Returns + ------- + Constraint + The constraint expression that enforces the temperature for the given tray is equal to the temperature for the tray below it. + """ + return m.T[sec, n_tray] == m.T[sec, n_tray - 1] + + +if __name__ == "__main__": + model = build_model() diff --git a/gdplib/kaibel/main_gdp.py b/gdplib/kaibel/main_gdp.py index 58381ed..3a66121 100644 --- a/gdplib/kaibel/main_gdp.py +++ b/gdplib/kaibel/main_gdp.py @@ -1,290 +1,289 @@ -""" - KAIBEL COLUMN: Modeling, Optimization, and - Conceptual Design of Multi-product Dividing Wall Columns - (written by E. Soraya Rawlings, esoraya@rwlngs.net, - in collaboration with Qi Chen) - -This is a dividing wall distillation column design problem to determine the optimal minimum number of trays and the optimal location of side streams for the separation of a quaternary mixture: - 1 = methanol - 2 = ethanol - 3 = propanol - 4 = butanol -while minimizing its capital and operating costs. - - The scheme of the Kaibel Column is shown in Figure 1: - ____ - --|Cond|--- - | ---- | - -------------- | - | sect 4 |<------> D mostly 1 - | ---------- | - | | - | ----- |----- | - | | |-------> S2 - Fj ------>| sect | sect | - | 2 | 3 | - | | |-------> S1 - | ----- |----- | - | | - | ---------- | - | sect 1 |<------> B mostly 4 - -------------- | - | ____ | - ---|Reb |--- - ---- - Figure 1. Kaibel Column scheme - -Permanent trays: -- Reboiler and vapor distributor in the bottom section (sect 1) -- Liquid distributor and condenser in the top section (sect 4) -- Side feed tray for the feed side and dividing wall starting and and ening tray in the feed section (sect 2). -- Side product trays and dividing wall starting and ending tray in the product section (sect 3). - -The trays in each section are counted from bottom to top, being tray 1 the bottom tray in each section and tray np the top tray in each section, where np is a specified upper bound for the number of possible trays for each section. -Each section has the same number of possible trays. - -Six degrees of freedom: the reflux ratio, the product outlets (bottom, intermediate, and distillate product flowrates), and the liquid and vapor flowrates between the two sections of the dividing wall, controlled by a liquid and vapor distributor on the top and bottom section of the column, respectively. -including also the vapor and liquid flowrate and the energy consumption in the reboiler and condenser. -The vapor distributor is fixed and remain constant during the column operation. - -Source paper: -Rawlings, E. S., Chen, Q., Grossmann, I. E., & Caballero, J. A. (2019). Kaibel Column: Modeling, optimization, and conceptual design of multi-product dividing wall columns. *Computers and Chemical Engineering*, 125, 31–39. https://doi.org/10.1016/j.compchemeng.2019.03.006 - -""" - -from math import fabs - -import matplotlib.pyplot as plt -from pyomo.environ import * - -# from kaibel_solve_gdp import build_model -from gdplib.kaibel.kaibel_solve_gdp import build_model - - - -def main(): - """ - This is the main function that executes the optimization process. - - It builds the model, fixes certain variables, sets initial values for tray existence or absence, - solves the model using the 'gdpopt' solver, and displays the results. - - Returns: - None - """ - m = build_model() - - # Fixing variables - m.F[1].fix(50) # feed flowrate in mol/s - m.F[2].fix(50) - m.F[3].fix(50) - m.F[4].fix(50) - m.q.fix( - m.q_init - ) # vapor fraction q_init from the feed set in the build_model function - m.dv[2].fix(0.394299) # vapor distributor in the feed section - - for sec in m.section: - for n_tray in m.tray: - m.P[sec, n_tray].fix(m.Preb) - - ## Initial values for the tray existence or absence - for n_tray in m.candidate_trays_main: - for sec in m.section_main: - m.tray_exists[sec, n_tray].indicator_var.set_value(1) - m.tray_absent[sec, n_tray].indicator_var.set_value(0) - for n_tray in m.candidate_trays_feed: - m.tray_exists[2, n_tray].indicator_var.set_value(1) - m.tray_absent[2, n_tray].indicator_var.set_value(0) - for n_tray in m.candidate_trays_product: - m.tray_exists[3, n_tray].indicator_var.set_value(1) - m.tray_absent[3, n_tray].indicator_var.set_value(0) - - intro_message(m) - - results = SolverFactory('gdpopt').solve( - m, - strategy='LOA', - tee=True, - time_limit=3600, - mip_solver='gams', - mip_solver_args=dict(solver='cplex'), - ) - - m.calc_nt = sum( - sum(m.tray_exists[sec, n_tray].indicator_var.value for n_tray in m.tray) - for sec in m.section - ) - sum(m.tray_exists[3, n_tray].indicator_var.value for n_tray in m.tray) - m.dw_start = ( - sum(m.tray_exists[1, n_tray].indicator_var.value for n_tray in m.tray) + 1 - ) - m.dw_end = sum( - m.tray_exists[1, n_tray].indicator_var.value for n_tray in m.tray - ) + sum(m.tray_exists[2, n_tray].indicator_var.value for n_tray in m.tray) - - display_results(m) - - print(' ', results) - print(' Solver Status: ', results.solver.status) - print(' Termination condition: ', results.solver.termination_condition) - - -def intro_message(m): - """ - Display the introduction message. - - - - """ - print( - """ - -If you use this model and/or initialization strategy, you may cite the following: -Rawlings, ES; Chen, Q; Grossmann, IE; Caballero, JA. Kaibel Column: Modeling, -optimization, and conceptual design of multi-product dividing wall columns. -Comp. and Chem. Eng., 2019, 125, 31-39. -DOI: https://doi.org/10.1016/j.compchemeng.2019.03.006 - - - """ - ) - - -def display_results(m): - """ - Display the results of the optimization process. - - Parameters - ---------- - m : Pyomo ConcreteModel - The Pyomo model object containing the results of the optimization process. - """ - print('') - print('Components:') - print('1 methanol') - print('2 ethanol') - print('3 butanol') - print('4 propanol') - print(' ') - print('Objective: %s' % value(m.obj)) - print('Trays: %s' % value(m.calc_nt)) - print('Dividing_wall_start: %s' % value(m.dw_start)) - print('Dividing_wall_end: %s' % value(m.dw_end)) - print(' ') - print( - 'Qreb: {: >3.0f}kW B_1: {: > 2.0f} B_2: {: >2.0f} B_3: {: >2.0f} B_4: {: >2.0f} Btotal: {: >2.0f}'.format( - value(m.Qreb / m.Qscale), - value(m.B[1]), - value(m.B[2]), - value(m.B[3]), - value(m.B[4]), - value(m.Btotal), - ) - ) - print( - 'Qcon: {: >2.0f}kW D_1: {: >2.0f} D_2: {: >2.0f} D_3: {: >2.0f} D_4: {: >2.0f} Dtotal: {: >2.0f}'.format( - value(m.Qcon / m.Qscale), - value(m.D[1]), - value(m.D[2]), - value(m.D[3]), - value(m.D[4]), - value(m.Dtotal), - ) - ) - print(' ') - print('Reflux: {: >3.4f}'.format(value(m.rr))) - print('Reboil: {: >3.4f} '.format(value(m.bu))) - print(' ') - print('Flowrates[mol/s]') - print( - 'F_1: {: > 3.0f} F_2: {: >2.0f} F_3: {: >2.0f} F_4: {: >2.0f} Ftotal: {: >2.0f}'.format( - value(m.F[1]), - value(m.F[2]), - value(m.F[3]), - value(m.F[4]), - sum(value(m.F[comp]) for comp in m.comp), - ) - ) - print( - 'S1_1: {: > 1.0f} S1_2: {: >2.0f} S1_3: {: >2.0f} S1_4: {: >2.0f} S1total: {: >2.0f}'.format( - value(m.S[1, 1]), - value(m.S[1, 2]), - value(m.S[1, 3]), - value(m.S[1, 4]), - sum(value(m.S[1, comp]) for comp in m.comp), - ) - ) - print( - 'S2_1: {: > 1.0f} S2_2: {: >2.0f} S2_3: {: >2.0f} S2_4: {: >2.0f} S2total: {: >2.0f}'.format( - value(m.S[2, 1]), - value(m.S[2, 2]), - value(m.S[2, 3]), - value(m.S[2, 4]), - sum(value(m.S[2, comp]) for comp in m.comp), - ) - ) - print(' ') - print('Distributors:') - print('dl[2]: {: >3.4f} dl[3]: {: >3.4f}'.format(value(m.dl[2]), value(m.dl[3]))) - print('dv[2]: {: >3.4f} dv[3]: {: >3.4f}'.format(value(m.dv[2]), value(m.dv[3]))) - print(' ') - print(' ') - print(' ') - print(' Kaibel Column Sections ') - print('__________________________________________') - print(' ') - print(' Tray Bottom Feed ') - print('__________________________________________') - for t in reversed(list(m.tray)): - print( - '[{: >2.0f}] {: >9.0g} {: >18.0g} F:{: >3.0f} '.format( - t, - ( - fabs(value(m.tray_exists[1, t].indicator_var)) - if t in m.candidate_trays_main - else 1 - ), - ( - fabs(value(m.tray_exists[2, t].indicator_var)) - if t in m.candidate_trays_feed - else 1 - ), - sum(value(m.F[comp]) for comp in m.comp) if t == m.feed_tray else 0, - ) - ) - print(' ') - print('__________________________________________') - print(' ') - print(' Product Top ') - print('__________________________________________') - for t in reversed(list(m.tray)): - print( - '[{: >2.0f}] {: >9.0g} S1:{: >2.0f} S2:{: >2.0f} {: >8.0g}'.format( - t, - ( - fabs(value(m.tray_exists[3, t].indicator_var)) - if t in m.candidate_trays_product - else 1 - ), - ( - sum(value(m.S[1, comp]) for comp in m.comp) - if t == m.sideout1_tray - else 0 - ), - ( - sum(value(m.S[2, comp]) for comp in m.comp) - if t == m.sideout2_tray - else 0 - ), - ( - fabs(value(m.tray_exists[4, t].indicator_var)) - if t in m.candidate_trays_main - else 1 - ), - ) - ) - print(' 1 = trays exists, 0 = absent tray') - - -if __name__ == "__main__": - main() +""" + KAIBEL COLUMN: Modeling, Optimization, and + Conceptual Design of Multi-product Dividing Wall Columns + (written by E. Soraya Rawlings, esoraya@rwlngs.net, + in collaboration with Qi Chen) + +This is a dividing wall distillation column design problem to determine the optimal minimum number of trays and the optimal location of side streams for the separation of a quaternary mixture: + 1 = methanol + 2 = ethanol + 3 = propanol + 4 = butanol +while minimizing its capital and operating costs. + + The scheme of the Kaibel Column is shown in Figure 1: + ____ + --|Cond|--- + | ---- | + -------------- | + | sect 4 |<------> D mostly 1 + | ---------- | + | | + | ----- |----- | + | | |-------> S2 + Fj ------>| sect | sect | + | 2 | 3 | + | | |-------> S1 + | ----- |----- | + | | + | ---------- | + | sect 1 |<------> B mostly 4 + -------------- | + | ____ | + ---|Reb |--- + ---- + Figure 1. Kaibel Column scheme + +Permanent trays: +- Reboiler and vapor distributor in the bottom section (sect 1) +- Liquid distributor and condenser in the top section (sect 4) +- Side feed tray for the feed side and dividing wall starting and and ening tray in the feed section (sect 2). +- Side product trays and dividing wall starting and ending tray in the product section (sect 3). + +The trays in each section are counted from bottom to top, being tray 1 the bottom tray in each section and tray np the top tray in each section, where np is a specified upper bound for the number of possible trays for each section. +Each section has the same number of possible trays. + +Six degrees of freedom: the reflux ratio, the product outlets (bottom, intermediate, and distillate product flowrates), and the liquid and vapor flowrates between the two sections of the dividing wall, controlled by a liquid and vapor distributor on the top and bottom section of the column, respectively. +including also the vapor and liquid flowrate and the energy consumption in the reboiler and condenser. +The vapor distributor is fixed and remain constant during the column operation. + +Source paper: +Rawlings, E. S., Chen, Q., Grossmann, I. E., & Caballero, J. A. (2019). Kaibel Column: Modeling, optimization, and conceptual design of multi-product dividing wall columns. *Computers and Chemical Engineering*, 125, 31–39. https://doi.org/10.1016/j.compchemeng.2019.03.006 + +""" + +from math import fabs + +import matplotlib.pyplot as plt +from pyomo.environ import * + +# from kaibel_solve_gdp import build_model +from gdplib.kaibel.kaibel_solve_gdp import build_model + + +def main(): + """ + This is the main function that executes the optimization process. + + It builds the model, fixes certain variables, sets initial values for tray existence or absence, + solves the model using the 'gdpopt' solver, and displays the results. + + Returns: + None + """ + m = build_model() + + # Fixing variables + m.F[1].fix(50) # feed flowrate in mol/s + m.F[2].fix(50) + m.F[3].fix(50) + m.F[4].fix(50) + m.q.fix( + m.q_init + ) # vapor fraction q_init from the feed set in the build_model function + m.dv[2].fix(0.394299) # vapor distributor in the feed section + + for sec in m.section: + for n_tray in m.tray: + m.P[sec, n_tray].fix(m.Preb) + + ## Initial values for the tray existence or absence + for n_tray in m.candidate_trays_main: + for sec in m.section_main: + m.tray_exists[sec, n_tray].indicator_var.set_value(1) + m.tray_absent[sec, n_tray].indicator_var.set_value(0) + for n_tray in m.candidate_trays_feed: + m.tray_exists[2, n_tray].indicator_var.set_value(1) + m.tray_absent[2, n_tray].indicator_var.set_value(0) + for n_tray in m.candidate_trays_product: + m.tray_exists[3, n_tray].indicator_var.set_value(1) + m.tray_absent[3, n_tray].indicator_var.set_value(0) + + intro_message(m) + + results = SolverFactory('gdpopt').solve( + m, + strategy='LOA', + tee=True, + time_limit=3600, + mip_solver='gams', + mip_solver_args=dict(solver='cplex'), + ) + + m.calc_nt = sum( + sum(m.tray_exists[sec, n_tray].indicator_var.value for n_tray in m.tray) + for sec in m.section + ) - sum(m.tray_exists[3, n_tray].indicator_var.value for n_tray in m.tray) + m.dw_start = ( + sum(m.tray_exists[1, n_tray].indicator_var.value for n_tray in m.tray) + 1 + ) + m.dw_end = sum( + m.tray_exists[1, n_tray].indicator_var.value for n_tray in m.tray + ) + sum(m.tray_exists[2, n_tray].indicator_var.value for n_tray in m.tray) + + display_results(m) + + print(' ', results) + print(' Solver Status: ', results.solver.status) + print(' Termination condition: ', results.solver.termination_condition) + + +def intro_message(m): + """ + Display the introduction message. + + + + """ + print( + """ + +If you use this model and/or initialization strategy, you may cite the following: +Rawlings, ES; Chen, Q; Grossmann, IE; Caballero, JA. Kaibel Column: Modeling, +optimization, and conceptual design of multi-product dividing wall columns. +Comp. and Chem. Eng., 2019, 125, 31-39. +DOI: https://doi.org/10.1016/j.compchemeng.2019.03.006 + + + """ + ) + + +def display_results(m): + """ + Display the results of the optimization process. + + Parameters + ---------- + m : Pyomo ConcreteModel + The Pyomo model object containing the results of the optimization process. + """ + print('') + print('Components:') + print('1 methanol') + print('2 ethanol') + print('3 butanol') + print('4 propanol') + print(' ') + print('Objective: %s' % value(m.obj)) + print('Trays: %s' % value(m.calc_nt)) + print('Dividing_wall_start: %s' % value(m.dw_start)) + print('Dividing_wall_end: %s' % value(m.dw_end)) + print(' ') + print( + 'Qreb: {: >3.0f}kW B_1: {: > 2.0f} B_2: {: >2.0f} B_3: {: >2.0f} B_4: {: >2.0f} Btotal: {: >2.0f}'.format( + value(m.Qreb / m.Qscale), + value(m.B[1]), + value(m.B[2]), + value(m.B[3]), + value(m.B[4]), + value(m.Btotal), + ) + ) + print( + 'Qcon: {: >2.0f}kW D_1: {: >2.0f} D_2: {: >2.0f} D_3: {: >2.0f} D_4: {: >2.0f} Dtotal: {: >2.0f}'.format( + value(m.Qcon / m.Qscale), + value(m.D[1]), + value(m.D[2]), + value(m.D[3]), + value(m.D[4]), + value(m.Dtotal), + ) + ) + print(' ') + print('Reflux: {: >3.4f}'.format(value(m.rr))) + print('Reboil: {: >3.4f} '.format(value(m.bu))) + print(' ') + print('Flowrates[mol/s]') + print( + 'F_1: {: > 3.0f} F_2: {: >2.0f} F_3: {: >2.0f} F_4: {: >2.0f} Ftotal: {: >2.0f}'.format( + value(m.F[1]), + value(m.F[2]), + value(m.F[3]), + value(m.F[4]), + sum(value(m.F[comp]) for comp in m.comp), + ) + ) + print( + 'S1_1: {: > 1.0f} S1_2: {: >2.0f} S1_3: {: >2.0f} S1_4: {: >2.0f} S1total: {: >2.0f}'.format( + value(m.S[1, 1]), + value(m.S[1, 2]), + value(m.S[1, 3]), + value(m.S[1, 4]), + sum(value(m.S[1, comp]) for comp in m.comp), + ) + ) + print( + 'S2_1: {: > 1.0f} S2_2: {: >2.0f} S2_3: {: >2.0f} S2_4: {: >2.0f} S2total: {: >2.0f}'.format( + value(m.S[2, 1]), + value(m.S[2, 2]), + value(m.S[2, 3]), + value(m.S[2, 4]), + sum(value(m.S[2, comp]) for comp in m.comp), + ) + ) + print(' ') + print('Distributors:') + print('dl[2]: {: >3.4f} dl[3]: {: >3.4f}'.format(value(m.dl[2]), value(m.dl[3]))) + print('dv[2]: {: >3.4f} dv[3]: {: >3.4f}'.format(value(m.dv[2]), value(m.dv[3]))) + print(' ') + print(' ') + print(' ') + print(' Kaibel Column Sections ') + print('__________________________________________') + print(' ') + print(' Tray Bottom Feed ') + print('__________________________________________') + for t in reversed(list(m.tray)): + print( + '[{: >2.0f}] {: >9.0g} {: >18.0g} F:{: >3.0f} '.format( + t, + ( + fabs(value(m.tray_exists[1, t].indicator_var)) + if t in m.candidate_trays_main + else 1 + ), + ( + fabs(value(m.tray_exists[2, t].indicator_var)) + if t in m.candidate_trays_feed + else 1 + ), + sum(value(m.F[comp]) for comp in m.comp) if t == m.feed_tray else 0, + ) + ) + print(' ') + print('__________________________________________') + print(' ') + print(' Product Top ') + print('__________________________________________') + for t in reversed(list(m.tray)): + print( + '[{: >2.0f}] {: >9.0g} S1:{: >2.0f} S2:{: >2.0f} {: >8.0g}'.format( + t, + ( + fabs(value(m.tray_exists[3, t].indicator_var)) + if t in m.candidate_trays_product + else 1 + ), + ( + sum(value(m.S[1, comp]) for comp in m.comp) + if t == m.sideout1_tray + else 0 + ), + ( + sum(value(m.S[2, comp]) for comp in m.comp) + if t == m.sideout2_tray + else 0 + ), + ( + fabs(value(m.tray_exists[4, t].indicator_var)) + if t in m.candidate_trays_main + else 1 + ), + ) + ) + print(' 1 = trays exists, 0 = absent tray') + + +if __name__ == "__main__": + main() From 8842a71d8a51ba7ca9ba31071c64ca1f704d4fd0 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 29 May 2024 16:11:05 -0400 Subject: [PATCH 26/79] update modprodnet --- gdplib/modprodnet/__init__.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/gdplib/modprodnet/__init__.py b/gdplib/modprodnet/__init__.py index b0866e5..2f8244d 100644 --- a/gdplib/modprodnet/__init__.py +++ b/gdplib/modprodnet/__init__.py @@ -1,17 +1,17 @@ -from functools import partial - from .model import build_model as _capacity_expansion from .distributed import build_modular_model as build_distributed_model from .quarter_distributed import build_modular_model as build_quarter_distributed_model -build_cap_expand_growth = partial(_capacity_expansion, case="Growth") -build_cap_expand_dip = partial(_capacity_expansion, case="Dip") -build_cap_expand_decay = partial(_capacity_expansion, case="Decay") -__all__ = [ - 'build_cap_expand_growth', - 'build_cap_expand_dip', - 'build_cap_expand_decay', - 'build_distributed_model', - 'build_quarter_distributed_model', -] +def build_model(case="Growth"): + if case in ["Growth", "Dip", "Decay"]: + return _capacity_expansion(case) + elif case == "Distributed": + return build_distributed_model() + elif case == "QuarterDistributed": + return build_quarter_distributed_model() + else: + raise ValueError("Invalid case: {}".format(case)) + + +__all__ = ['build_model'] From 7d09bf71bea83f9ba82dd3506e0413e257ff34c3 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 29 May 2024 16:11:35 -0400 Subject: [PATCH 27/79] update methanol --- gdplib/methanol/__init__.py | 4 ++-- gdplib/methanol/methanol.py | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/gdplib/methanol/__init__.py b/gdplib/methanol/__init__.py index f053993..e4732ba 100644 --- a/gdplib/methanol/__init__.py +++ b/gdplib/methanol/__init__.py @@ -1,3 +1,3 @@ -from .methanol import MethanolModel +from .methanol import build_model -__all__ = ['MethanolModel'] +__all__ = ['build_model'] diff --git a/gdplib/methanol/methanol.py b/gdplib/methanol/methanol.py index fe85918..1ae7f29 100644 --- a/gdplib/methanol/methanol.py +++ b/gdplib/methanol/methanol.py @@ -827,6 +827,20 @@ def enumerate_solutions(): return m +def build_model(): + m = MethanolModel().model + for _d in m.component_data_objects( + gdp.Disjunct, descend_into=True, active=True, sort=True + ): + _d.BigM = pe.Suffix() + for _c in _d.component_data_objects( + pe.Constraint, descend_into=True, active=True, sort=True + ): + lb, ub = compute_bounds_on_expr(_c.body) + _d.BigM[_c] = max(abs(lb), abs(ub)) + + return m + def solve_with_gdp_opt(): m = MethanolModel().model From 4cb97a6ab07da151dcf2dde5a289fcd233c5e9d4 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 29 May 2024 16:17:13 -0400 Subject: [PATCH 28/79] update mod_hens --- gdplib/mod_hens/__init__.py | 38 ++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/gdplib/mod_hens/__init__.py b/gdplib/mod_hens/__init__.py index 96bc5d3..d1adf64 100644 --- a/gdplib/mod_hens/__init__.py +++ b/gdplib/mod_hens/__init__.py @@ -1,5 +1,3 @@ -from functools import partial - from .conventional import build_conventional as _conv from .modular_discrete import ( build_modular_option as _disc_opt, @@ -12,21 +10,23 @@ build_single_module as _int_sing, ) -# These are the functions that we want to expose as public -build_conventional = partial(_conv, cafaro_approx=True, num_stages=4) -build_integer_single_module = partial(_int_sing, cafaro_approx=True, num_stages=4) -build_integer_require_modular = partial(_int_mod, cafaro_approx=True, num_stages=4) -build_integer_modular_option = partial(_int_opt, cafaro_approx=True, num_stages=4) -build_discrete_single_module = partial(_disc_sing, cafaro_approx=True, num_stages=4) -build_discrete_require_modular = partial(_disc_mod, cafaro_approx=True, num_stages=4) -build_discrete_modular_option = partial(_disc_opt, cafaro_approx=True, num_stages=4) -__all__ = [ - 'build_conventional', - 'build_integer_single_module', - 'build_integer_require_modular', - 'build_integer_modular_option', - 'build_discrete_single_module', - 'build_discrete_require_modular', - 'build_discrete_modular_option', -] +def build_model(case="conventional", cafaro_approx=True, num_stages=4): + # TODO: we might need to come up with better names for these cases. + if case == "conventional": + return _conv(cafaro_approx, num_stages) + elif case == "single_module_integer": + return _int_sing(cafaro_approx, num_stages) + elif case == "require_modular_integer": + return _int_mod(cafaro_approx, num_stages) + elif case == "modular_option_integer": + return _int_opt(cafaro_approx, num_stages) + elif case == "single_module_discrete": + return _disc_sing(cafaro_approx, num_stages) + elif case == "require_modular_discrete": + return _disc_mod(cafaro_approx, num_stages) + elif case == "modular_option_discrete": + return _disc_opt(cafaro_approx, num_stages) + + +__all__ = ['build_model'] From 4473c3061f291176952f885c64fa000f8f9ec9f3 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 29 May 2024 16:17:30 -0400 Subject: [PATCH 29/79] fix bug in mod_hens --- gdplib/mod_hens/conventional.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/gdplib/mod_hens/conventional.py b/gdplib/mod_hens/conventional.py index a6f8080..bb24853 100644 --- a/gdplib/mod_hens/conventional.py +++ b/gdplib/mod_hens/conventional.py @@ -68,10 +68,8 @@ def build_model(use_cafaro_approximation, num_stages): >= m.exchanger_area_cost_factor[hot, cold] * 1e-3 * m.cafaro_k - * log( - m.cafaro_b * m.exchanger_area[stg, hot, cold] + 1, - doc="Applies Cafaro's logarithmic cost scaling to area cost.", - ) + * log(m.cafaro_b * m.exchanger_area[stg, hot, cold] + 1), + doc="Applies Cafaro's logarithmic cost scaling to area cost.", ) m.BigM[disj.exchanger_area_cost] = 100 From f6bd622aafa0381e4e20fefae9f0beecef596d3c Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 29 May 2024 16:35:45 -0400 Subject: [PATCH 30/79] separate positioning and spectralog --- gdplib/logical/README.md | 49 ------------------- gdplib/logical/__init__.py | 4 -- gdplib/positioning/README.md | 24 +++++++++ gdplib/positioning/__init__.py | 3 ++ .../{logical => positioning}/positioning.py | 0 gdplib/spectralog/README.md | 24 +++++++++ gdplib/spectralog/__init__.py | 3 ++ gdplib/{logical => spectralog}/spectralog.py | 0 8 files changed, 54 insertions(+), 53 deletions(-) delete mode 100644 gdplib/logical/README.md delete mode 100644 gdplib/logical/__init__.py create mode 100644 gdplib/positioning/README.md create mode 100644 gdplib/positioning/__init__.py rename gdplib/{logical => positioning}/positioning.py (100%) create mode 100644 gdplib/spectralog/README.md create mode 100644 gdplib/spectralog/__init__.py rename gdplib/{logical => spectralog}/spectralog.py (100%) diff --git a/gdplib/logical/README.md b/gdplib/logical/README.md deleted file mode 100644 index ddaa71a..0000000 --- a/gdplib/logical/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# Logical expression system demo problems - -``positioning`` and ``spectralog`` are demonstration problems for the Pyomo.GDP logical expression system, adapted from their equivalents in LOGMIP. - -## Spectralog - -Source paper (Example 2): - -> Vecchietti, A., & Grossmann, I. E. (1999). LOGMIP: A disjunctive 0-1 non-linear optimizer for process system models. *Computers and Chemical Engineering*, 23(4–5), 555–565. https://doi.org/10.1016/S0098-1354(98)00293-2 - -### Problem Details - -#### Solution - -Optimal objective value: 12.0893 - -#### Size -- Variables: 128 - - Boolean: 60 - - Binary: 0 - - Integer: 0 - - Continuous: 68 -- Constraints: 158 - - Nonlinear: 8 -- Disjuncts: 60 -- Disjunctions: 30 - -## Optimal positioning - -Source paper (Example 4): - -> Duran, M. A., & Grossmann, I. E. (1986). An outer-approximation algorithm for a class of mixed-integer nonlinear programs. *Mathematical Programming*, 36(3), 307. https://doi.org/10.1007/BF02592064 - -### Problem Details - -#### Solution - -Optimal objective value: -8.06 - -#### Size -- Variables: 56 - - Boolean: 50 - - Binary: 0 - - Integer: 0 - - Continuous: 6 -- Constraints: 30 - - Nonlinear: 25 -- Disjuncts: 50 -- Disjunctions: 25 diff --git a/gdplib/logical/__init__.py b/gdplib/logical/__init__.py deleted file mode 100644 index 7d81d28..0000000 --- a/gdplib/logical/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .positioning import build_model as build_positioning_model -from .spectralog import build_model as build_spectralog_model - -__all__ = ['build_positioning_model', 'build_spectralog_model'] diff --git a/gdplib/positioning/README.md b/gdplib/positioning/README.md new file mode 100644 index 0000000..bf8292e --- /dev/null +++ b/gdplib/positioning/README.md @@ -0,0 +1,24 @@ +# Optimal positioning + +``positioning`` is a demonstration problems for the Pyomo.GDP logical expression system, adapted from its equivalent in LOGMIP. + +Source paper (Example 4): + +> Duran, M. A., & Grossmann, I. E. (1986). An outer-approximation algorithm for a class of mixed-integer nonlinear programs. *Mathematical Programming*, 36(3), 307. https://doi.org/10.1007/BF02592064 + +## Problem Details + +### Solution + +Optimal objective value: -8.06 + +### Size +- Variables: 56 + - Boolean: 50 + - Binary: 0 + - Integer: 0 + - Continuous: 6 +- Constraints: 30 + - Nonlinear: 25 +- Disjuncts: 50 +- Disjunctions: 25 diff --git a/gdplib/positioning/__init__.py b/gdplib/positioning/__init__.py new file mode 100644 index 0000000..bbe4b90 --- /dev/null +++ b/gdplib/positioning/__init__.py @@ -0,0 +1,3 @@ +from .positioning import build_model + +__all__ = ['build_model'] diff --git a/gdplib/logical/positioning.py b/gdplib/positioning/positioning.py similarity index 100% rename from gdplib/logical/positioning.py rename to gdplib/positioning/positioning.py diff --git a/gdplib/spectralog/README.md b/gdplib/spectralog/README.md new file mode 100644 index 0000000..763d3b5 --- /dev/null +++ b/gdplib/spectralog/README.md @@ -0,0 +1,24 @@ +# Spectralog + +``spectralog`` is a demonstration problems for the Pyomo.GDP logical expression system, adapted from its equivalent in LOGMIP. + +Source paper (Example 2): + +> Vecchietti, A., & Grossmann, I. E. (1999). LOGMIP: A disjunctive 0-1 non-linear optimizer for process system models. *Computers and Chemical Engineering*, 23(4–5), 555–565. https://doi.org/10.1016/S0098-1354(98)00293-2 + +## Problem Details + +### Solution + +Optimal objective value: 12.0893 + +### Size +- Variables: 128 + - Boolean: 60 + - Binary: 0 + - Integer: 0 + - Continuous: 68 +- Constraints: 158 + - Nonlinear: 8 +- Disjuncts: 60 +- Disjunctions: 30 diff --git a/gdplib/spectralog/__init__.py b/gdplib/spectralog/__init__.py new file mode 100644 index 0000000..c24e98a --- /dev/null +++ b/gdplib/spectralog/__init__.py @@ -0,0 +1,3 @@ +from .spectralog import build_model + +__all__ = ['build_model'] diff --git a/gdplib/logical/spectralog.py b/gdplib/spectralog/spectralog.py similarity index 100% rename from gdplib/logical/spectralog.py rename to gdplib/spectralog/spectralog.py From c568584b059111e7077f67bf358a31d37c11c203 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 29 May 2024 16:40:59 -0400 Subject: [PATCH 31/79] update __init__ --- gdplib/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gdplib/__init__.py b/gdplib/__init__.py index a3cb501..0d6fa2b 100644 --- a/gdplib/__init__.py +++ b/gdplib/__init__.py @@ -1,7 +1,8 @@ import gdplib.mod_hens import gdplib.modprodnet import gdplib.biofuel -import gdplib.logical # Requires logical expression system +import gdplib.positioning +import gdplib.spectralog import gdplib.stranded_gas # Requires logical expression system import gdplib.gdp_col import gdplib.hda From d11a66ea2c4f8eb3c06ca4c04a1e39e911176bd1 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 29 May 2024 16:42:47 -0400 Subject: [PATCH 32/79] unify hda build_model --- gdplib/hda/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gdplib/hda/__init__.py b/gdplib/hda/__init__.py index f1e81db..1c88c3e 100644 --- a/gdplib/hda/__init__.py +++ b/gdplib/hda/__init__.py @@ -1,3 +1,3 @@ -from .HDA_GDP_gdpopt import HDA_model +from .HDA_GDP_gdpopt import HDA_model as build_model -__all__ = ['HDA_model'] +__all__ = ['build_model'] From 8fcd8fd1fcdde188605a751f5d1ac9f44d4cd554 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 29 May 2024 16:45:55 -0400 Subject: [PATCH 33/79] methanol black format --- gdplib/methanol/methanol.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gdplib/methanol/methanol.py b/gdplib/methanol/methanol.py index 1ae7f29..d964c80 100644 --- a/gdplib/methanol/methanol.py +++ b/gdplib/methanol/methanol.py @@ -827,6 +827,7 @@ def enumerate_solutions(): return m + def build_model(): m = MethanolModel().model for _d in m.component_data_objects( From 0ef4d790c646fc468b1208b32e707b666ccb29db Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 29 May 2024 16:47:26 -0400 Subject: [PATCH 34/79] update benchmark.py --- benchmark.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/benchmark.py b/benchmark.py index 1bb9df8..21dac5d 100644 --- a/benchmark.py +++ b/benchmark.py @@ -79,7 +79,8 @@ def benchmark(model, strategy, timelimit, result_dir, subsolver="scip"): # "hda", "jobshop", # "kaibel", - # "logical", + # "positioning", + # "spectralog", # "med_term_purchasing", # "methanol", # "mod_hens", From 1303df2370b2e71726f31b22568bb4901e7d5274 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 29 May 2024 16:51:36 -0400 Subject: [PATCH 35/79] fix gdp_col build_model bug --- gdplib/gdp_col/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gdplib/gdp_col/__init__.py b/gdplib/gdp_col/__init__.py index b508512..127f4d2 100644 --- a/gdplib/gdp_col/__init__.py +++ b/gdplib/gdp_col/__init__.py @@ -23,6 +23,7 @@ def build_model(): m.BigM = Suffix(direction=Suffix.LOCAL) m.BigM[None] = 100 + return m __all__ = ['build_model'] From 67eafc355810d27852eba817387e3afe8d420b21 Mon Sep 17 00:00:00 2001 From: parkyr Date: Thu, 30 May 2024 13:48:58 -0400 Subject: [PATCH 36/79] making requested changes --- gdplib/hda/HDA_GDP_gdpopt.py | 172 +++++++++++++++++------------------ 1 file changed, 86 insertions(+), 86 deletions(-) diff --git a/gdplib/hda/HDA_GDP_gdpopt.py b/gdplib/hda/HDA_GDP_gdpopt.py index 5bb7505..dd34987 100644 --- a/gdplib/hda/HDA_GDP_gdpopt.py +++ b/gdplib/hda/HDA_GDP_gdpopt.py @@ -62,13 +62,13 @@ def HDA_model(): furnpdrop : float pressure drop of furnace heatvap : float - heat of vaporization [kj per kg-mol] + heat of vaporization [kJ/kg-mol] cppure : float - pure component heat capacities [kj per kg-mol-k] + pure component heat capacities [kJ/kg-mol-K] gcomp : float guess composition values cp : float - heat capacities [kj per kg-mol-k] + heat capacities [kJ/kg-mol-K] anta : float antoine coefficient A antb : float @@ -76,15 +76,15 @@ def HDA_model(): antc : float antoine coefficient C perm : float - permeability [kg-mole/m**2-min-mpa] + permeability [kg-mol/m**2-min-MPa] cbeta : float constant values (exp(beta)) in absorber aabs : float absorption factors eps1 : float - small number to avoid div. by zero + small number to avoid division by zero heatrxn : float - heat of reaction [kj per kg-mol] + heat of reaction [kJ/kg-mol] f1comp : float feedstock compositions (h2 feed) f66comp : float @@ -216,7 +216,7 @@ def strset(i): m.compon, initialize=Heatvap, default=0, - doc="heat of vaporization [kj per kg-mol]", + doc="heat of vaporization [kJ/kg-mol]", ) Cppure = {} # h2 'hydrogen', ch4 'methane', ben 'benzene', tol 'toluene', dip 'diphenyl' @@ -229,7 +229,7 @@ def strset(i): m.compon, initialize=Cppure, default=0, - doc="pure component heat capacities [kj per kg-mol-k]", + doc="pure component heat capacities [kJ/kg-mol-K]", ) Gcomp = {} Gcomp[7, "h2"] = 0.95 @@ -337,13 +337,13 @@ def strset(i): def cppara(compon, stream): """ - heat capacities [kj per kg-mol-k] + heat capacities [kJ/kg-mol-K] sum of heat capacities of all components in a stream, weighted by their composition """ return sum(m.cppure[compon] * m.gcomp[stream, compon] for compon in m.compon) m.cp = Param( - m.str, initialize=cppara, default=0, doc="heat capacities [kj per kg-mol-k]" + m.str, initialize=cppara, default=0, doc="heat capacities [kJ/kg-mol-K]" ) Anta = {} @@ -378,8 +378,8 @@ def cppara(compon, stream): def Permset(m, compon): """ - permeability [kg-mole/m**2-min-mpa] - converting unit for permeability from cc/cm**2-sec-cmHg to kg-mole/m**2-min-mpa + permeability [kg-mol/m**2-min-MPa] + converting unit for permeability from cc/cm**2-sec-cmHg to kg-mol/m**2-min-MPa """ return Perm[compon] * (1.0 / 22400.0) * 1.0e4 * 750.062 * 60.0 / 1000.0 @@ -387,7 +387,7 @@ def Permset(m, compon): m.compon, initialize=Permset, default=0, - doc="permeability [kg-mole/m**2-min-mpa]", + doc="permeability [kg-mol/m**2-min-MPa]", ) Cbeta = {} @@ -411,7 +411,7 @@ def Permset(m, compon): Heatrxn[1] = 50100.0 Heatrxn[2] = 50100.0 m.heatrxn = Param( - m.rct, initialize=Heatrxn, default=0, doc="heat of reaction [kj per kg-mol]" + m.rct, initialize=Heatrxn, default=0, doc="heat of reaction [kJ/kg-mol]" ) F1comp = {} @@ -488,13 +488,13 @@ def Permset(m, compon): ) m.dh = Set( initialize=[(1, "tol"), (1, "dip"), (2, "dip")], - doc="dist-key component matches", + doc="dist-Key component matches", ) i = list(m.dlkey) q = list(m.dhkey) dkeyset = i + q - m.dkey = Set(initialize=dkeyset, doc="dist-key component matches") + m.dkey = Set(initialize=dkeyset, doc="dist-Key component matches") m.iflsh = Set( initialize=[(1, 17), (2, 46), (3, 39)], doc="flsh-stream (inlet) matches" @@ -507,7 +507,7 @@ def Permset(m, compon): ) m.fkey = Set( initialize=[(1, "ch4"), (2, "ch4"), (3, "tol")], - doc="flash-key component matches", + doc="flash-Key component matches", ) m.ifurn = Set(initialize=[(1, 70)], doc="furn-stream (inlet) matches") @@ -615,7 +615,7 @@ def Permset(m, compon): m.irct = Set(initialize=[(1, 10), (2, 12)], doc="reactor-stream (inlet) matches") m.orct = Set(initialize=[(1, 11), (2, 13)], doc="reactor-stream (outlet) matches") m.rkey = Set( - initialize=[(1, "tol"), (2, "tol")], doc="reactor-key component matches" + initialize=[(1, "tol"), (2, "tol")], doc="reactor-Key component matches" ) m.ispl1 = Set( @@ -676,7 +676,7 @@ def Permset(m, compon): within=NonNegativeReals, bounds=(0, 100), initialize=1, - doc="electricity requirement [kw]", + doc="electricity requirement [kW]", ) m.presrat = Var( m.comp, @@ -705,7 +705,7 @@ def Permset(m, compon): within=NonNegativeReals, initialize=1, bounds=(0.1, 4.0), - doc="column pressure [mega-pascal]", + doc="column pressure [MPa]", ) m.avevlt = Var( m.dist, within=NonNegativeReals, initialize=1, doc="average volatility" @@ -713,13 +713,13 @@ def Permset(m, compon): # flash m.flsht = Var( - m.flsh, within=NonNegativeReals, initialize=1, doc="flash temperature [100 k]" + m.flsh, within=NonNegativeReals, initialize=1, doc="flash temperature [100 K]" ) m.flshp = Var( m.flsh, within=NonNegativeReals, initialize=1, - doc="flash pressure [mega-pascal]", + doc="flash pressure [MPa]", ) m.eflsh = Var( m.flsh, @@ -736,7 +736,7 @@ def Permset(m, compon): within=NonNegativeReals, bounds=(None, 10), initialize=1, - doc="heating required [1.e+12 kj per yr]", + doc="heating required [1.e+12 kJ/yr]", ) # cooler m.qc = Var( @@ -744,7 +744,7 @@ def Permset(m, compon): within=NonNegativeReals, bounds=(None, 10), initialize=1, - doc="utility requirement [1.e+12 kj per yr]", + doc="utility requirement [1.e+12 kJ/yr]", ) # heater m.qh = Var( @@ -752,7 +752,7 @@ def Permset(m, compon): within=NonNegativeReals, bounds=(None, 10), initialize=1, - doc="utility requirement [1.e+12 kj per yr]", + doc="utility requirement [1.e+12 kJ/yr]", ) # exchanger m.qexch = Var( @@ -760,7 +760,7 @@ def Permset(m, compon): within=NonNegativeReals, bounds=(None, 10), initialize=1, - doc="heat exchanged [1.e+12 kj per yr]", + doc="heat exchanged [1.e+12 kJ/yr]", ) # membrane m.a = Var( @@ -776,14 +776,14 @@ def Permset(m, compon): within=NonNegativeReals, bounds=(0.1, 4), initialize=0, - doc="mixer temperature [100 k]", + doc="mixer temperature [100 K]", ) m.mxr1t = Var( m.mxr1, within=NonNegativeReals, bounds=(3, 10), initialize=0, - doc="mixer pressure [mega-pascal]", + doc="mixer pressure [MPa]", ) # mixer m.mxrt = Var( @@ -791,33 +791,33 @@ def Permset(m, compon): within=NonNegativeReals, bounds=(3.0, 10), initialize=3, - doc="mixer temperature [100 k]", + doc="mixer temperature [100 K]", ) m.mxrp = Var( m.mxr, within=NonNegativeReals, bounds=(0.1, 4.0), initialize=3, - doc="mixer pressure [mega-pascal]", + doc="mixer pressure [MPa]", ) # reactor m.rctt = Var( m.rct, within=NonNegativeReals, bounds=(8.9427, 9.7760), - doc="reactor temperature [100 k]", + doc="reactor temperature [100 K]", ) m.rctp = Var( m.rct, within=NonNegativeReals, bounds=(3.4474, 3.4474), - doc="reactor pressure [mega-pascal]", + doc="reactor pressure [MPa]", ) m.rctvol = Var( m.rct, within=NonNegativeReals, bounds=(None, 200), - doc="reactor volume [cubic meter]", + doc="reactor volume [m**3]", ) m.krct = Var( m.rct, @@ -851,27 +851,27 @@ def Permset(m, compon): m.rct, within=NonNegativeReals, bounds=(0, 10000000000), - doc="heat removed [1.e+9 kj per yr]", + doc="heat removed [1.e+9 kJ/yr]", ) # splitter (1 output) m.spl1t = Var( m.spl1, within=PositiveReals, bounds=(3.00, 10.00), - doc="splitter temperature [100 k]", + doc="splitter temperature [100 K]", ) m.spl1p = Var( m.spl1, within=PositiveReals, bounds=(0.1, 4.0), - doc="splitter pressure [mega-pascal]", + doc="splitter pressure [MPa]", ) # splitter m.splp = Var( - m.spl, within=Reals, bounds=(0.1, 4.0), doc="splitter pressure [mega-pascal]" + m.spl, within=Reals, bounds=(0.1, 4.0), doc="splitter pressure [MPa]" ) m.splt = Var( - m.spl, within=Reals, bounds=(3.0, 10.0), doc="splitter temperature [100 k]" + m.spl, within=Reals, bounds=(3.0, 10.0), doc="splitter temperature [100 K]" ) # stream @@ -892,7 +892,7 @@ def bound_f(m, stream): within=NonNegativeReals, bounds=bound_f, initialize=1, - doc="stream flowrates [kg-mole per min]", + doc="stream flowrates [kg-mol/min]", ) def bound_fc(m, stream, compon): @@ -910,21 +910,21 @@ def bound_fc(m, stream, compon): within=Reals, bounds=bound_fc, initialize=1, - doc="component flowrates [kg-mole per min]", + doc="component flowrates [kg-mol/min]", ) m.p = Var( m.str, within=NonNegativeReals, bounds=(0.1, 4.0), initialize=3.0, - doc="stream pressure [mega-pascal]", + doc="stream pressure [MPa]", ) m.t = Var( m.str, within=NonNegativeReals, bounds=(3.0, 10.0), initialize=3.0, - doc="stream temperature [100 k]", + doc="stream temperature [100 K]", ) m.vp = Var( m.str, @@ -932,7 +932,7 @@ def bound_fc(m, stream, compon): within=NonNegativeReals, initialize=1, bounds=(0, 10), - doc="vapor pressure [mega-pascal]", + doc="vapor pressure [MPa]", ) def boundsofe(m): @@ -1596,7 +1596,7 @@ def Antdistb(_m, dist_, stream, compon): m.str, m.compon, rule=Antdistb, - doc="vapor pressure correlation (bot)", + doc="vapor pressure correlation (bottom)", ) def Antdistt(_m, dist_, stream, compon): @@ -1704,7 +1704,7 @@ def Fenske(_m, dist_): b.fenske = Constraint([dist], rule=Fenske, doc="minimum number of trays") def Acttray(_m, dist_): - # actual number of trays (gillilands approximation) + # actual number of trays (Gilliland approximation) if dist == dist_: return m.ndist[dist_] == m.nmin[dist_] * 2.0 / m.disteff return Constraint.Skip @@ -1957,7 +1957,7 @@ def Flshti(_m, flsh_, stream): return m.flsht[flsh_] == m.t[stream] return Constraint.Skip - b.flshti = Constraint([flsh], m.str, rule=Flshti, doc="inlet temp. relation") + b.flshti = Constraint([flsh], m.str, rule=Flshti, doc="inlet temperature relation") def Flshtl(_m, flsh_, stream): if (flsh_, stream) in m.lflsh and flsh_ == flsh: @@ -1965,7 +1965,7 @@ def Flshtl(_m, flsh_, stream): return Constraint.Skip b.flshtl = Constraint( - [flsh], m.str, rule=Flshtl, doc="outlet temp. relation (liquid)" + [flsh], m.str, rule=Flshtl, doc="outlet temperature relation (liquid)" ) def Flshtv(_m, flsh_, stream): @@ -1974,11 +1974,11 @@ def Flshtv(_m, flsh_, stream): return Constraint.Skip b.flshtv = Constraint( - [flsh], m.str, rule=Flshtv, doc="outlet temp. relation (vapor)" + [flsh], m.str, rule=Flshtv, doc="outlet temperature relation (vapor)" ) m.heat_unit_match = Param( - initialize=3600.0 * 8500.0 * 1.0e-12 / 60.0, doc="unit change on temp" + initialize=3600.0 * 8500.0 * 1.0e-12 / 60.0, doc="unit change on heat balance from [kJ/min] to [1e12kJ/yr]" ) def build_furnace(b, furnace): @@ -2365,7 +2365,7 @@ def Memtp(_m, memb, stream): return Constraint.Skip b.memtp = Constraint( - [membrane], m.str, rule=Memtp, doc="temp relation for permeate" + [membrane], m.str, rule=Memtp, doc="temperature relation for permeate" ) def Mempp(_m, memb, stream): @@ -2387,7 +2387,7 @@ def Memtn(_m, memb, stream): return Constraint.Skip b.Memtn = Constraint( - [membrane], m.str, rule=Memtn, doc="temp relation for non-permeate" + [membrane], m.str, rule=Memtn, doc="temperature relation for non-permeate" ) def Mempn(_m, memb, stream): @@ -2689,7 +2689,7 @@ def rctspec(_m, rct, stream): return Constraint.Skip b.Rctspec = Constraint( - [rct], m.str, rule=rctspec, doc="spec. on reactor feed stream" + [rct], m.str, rule=rctspec, doc="specification on reactor feed stream" ) def rxnrate(_m, rct): @@ -2737,7 +2737,7 @@ def rctcns(_m, rct, stream, compon): return Constraint.Skip b.Rctcns = Constraint( - [rct], m.str, m.compon, rule=rctcns, doc="consumption rate of key comp." + [rct], m.str, m.compon, rule=rctcns, doc="consumption rate of key components" ) def rctmbtol(_m, rct): @@ -2855,7 +2855,7 @@ def Rcthbiso(_m, rct): ) return Constraint.Skip - b.rcthbiso = Constraint([rct], rule=Rcthbiso, doc="temp relation (isothermal)") + b.rcthbiso = Constraint([rct], rule=Rcthbiso, doc="temperature relation (isothermal)") def Rctisot(_m, rct): if rct == 2: @@ -2864,7 +2864,7 @@ def Rctisot(_m, rct): ) == sum(m.t[stream] for (rct_, stream) in m.orct if rct_ == rct) return Constraint.Skip - b.rctisot = Constraint([rct], rule=Rctisot, doc="temp relation (isothermal)") + b.rctisot = Constraint([rct], rule=Rctisot, doc="temperature relation (isothermal)") def build_single_mixer(b, mixer): """ @@ -2985,7 +2985,7 @@ def inlet_treatment(m): m.multi_mixer_1 = Block(m.one, rule=build_multiple_mixer) m.furnace_1 = Block(m.one, rule=build_furnace) - # Second disjunction: Adiabatic or isothermal reactor + # second disjunction: adiabatic or isothermal reactor @m.Disjunct() def adiabatic_reactor(disj): disj.Adiabatic_reactor = Block(m.one, rule=build_reactor) @@ -3147,93 +3147,93 @@ def toluene_selection(m): ) m.electricity_cost = Param( initialize=0.04 * 24 * 365 / 1000, - doc="electricity cost, value is 0.04 with the unit of [kw/h], now is [kw/yr/k$]", + doc="electricity cost, value is 0.04 with the unit of [kW/h], now is [kW/yr/$1e3]", ) - m.meathane_purge_value = Param( - initialize=3.37, doc="heating value of meathane purge" + m.methane_purge_value = Param( + initialize=3.37, doc="heating value of methane purge" ) m.heating_cost = Param( - initialize=8000.0, doc="Heating cost (steam) with unit [1e6 kj]" + initialize=8000.0, doc="heating cost (steam) with unit [1e6 kJ]" ) m.cooling_cost = Param( - initialize=700.0, doc="heating cost (water) with unit [1e6 kj]" + initialize=700.0, doc="heating cost (water) with unit [1e6 kJ]" ) - m.fuel_cost = Param(initialize=4000.0, doc="fuel cost with unit [1e6 kj]") - m.abs_fixed_cost = Param(initialize=13, doc="fixed cost of absober [$1e3 per year]") + m.fuel_cost = Param(initialize=4000.0, doc="fuel cost with unit [1e6 kJ]") + m.abs_fixed_cost = Param(initialize=13, doc="fixed cost of absober [$1e3/yr]") m.abs_linear_coefficient = Param( initialize=1.2, - doc="linear coefficient of absorber (times tray number) [$1e3 per year]", + doc="linear coefficient of absorber (times tray number) [$1e3/yr]", ) m.compressor_fixed_cost = Param( - initialize=7.155, doc="compressor fixed cost [$1e3 per year]" + initialize=7.155, doc="compressor fixed cost [$1e3/yr]" ) m.compressor_fixed_cost_4 = Param( - initialize=4.866, doc="compressor fixed cost for compressor 4 [$1e3 per year]" + initialize=4.866, doc="compressor fixed cost for compressor 4 [$1e3/yr]" ) m.compressor_linear_coefficient = Param( initialize=0.815, - doc="compressor linear coefficient (vaporflow rate) [$1e3 per year]", + doc="compressor linear coefficient (vapor flow rate) [$1e3/yr]", ) m.compressor_linear_coefficient_4 = Param( initialize=0.887, - doc="compressor linear coefficient (vaporflow rate) [$1e3 per year]", + doc="compressor linear coefficient (vapor flow rate) [$1e3/yr]", ) m.stabilizing_column_fixed_cost = Param( - initialize=1.126, doc="stabilizing column fixed cost [$1e3 per year]" + initialize=1.126, doc="stabilizing column fixed cost [$1e3/yr]" ) m.stabilizing_column_linear_coefficient = Param( initialize=0.375, - doc="stabilizing column linear coefficient (times number of trays) [$1e3 per year]", + doc="stabilizing column linear coefficient (times number of trays) [$1e3/yr]", ) m.benzene_column_fixed_cost = Param( - initialize=16.3, doc="benzene column fixed cost [$1e3 per year]" + initialize=16.3, doc="benzene column fixed cost [$1e3/yr]" ) m.benzene_column_linear_coefficient = Param( initialize=1.55, - doc="benzene column linear coefficient (times number of trays) [$1e3 per year]", + doc="benzene column linear coefficient (times number of trays) [$1e3/yr]", ) m.toluene_column_fixed_cost = Param( - initialize=3.9, doc="toluene column fixed cost [$1e3 per year]" + initialize=3.9, doc="toluene column fixed cost [$1e3/yr]" ) m.toluene_column_linear_coefficient = Param( initialize=1.12, - doc="toluene column linear coefficient (times number of trays) [$1e3 per year]", + doc="toluene column linear coefficient (times number of trays) [$1e3/yr]", ) m.furnace_fixed_cost = Param( - initialize=6.20, doc="toluene column fixed cost [$1e3 per year]" + initialize=6.20, doc="toluene column fixed cost [$1e3/yr]" ) m.furnace_linear_coefficient = Param( initialize=1171.7, - doc="furnace column linear coefficient [1e9kj/yr] [$1e3 per year]", + doc="furnace column linear coefficient [$1e3/(1e12 kJ/yr)]", ) m.membrane_separator_fixed_cost = Param( - initialize=43.24, doc="membrane separator fixed cost [$1e3 per year]" + initialize=43.24, doc="membrane separator fixed cost [$1e3/yr]" ) m.membrane_separator_linear_coefficient = Param( initialize=49.0, - doc="furnace column linear coefficient (times inlet flowrate) [$1e3 per year]", + doc="furnace column linear coefficient (times inlet flowrate) [$1e3/yr]", ) m.adiabtic_reactor_fixed_cost = Param( - initialize=74.3, doc="adiabatic reactor fixed cost [$1e3 per year]" + initialize=74.3, doc="adiabatic reactor fixed cost [$1e3/yr]" ) m.adiabtic_reactor_linear_coefficient = Param( initialize=1.257, - doc="adiabatic reactor linear coefficient (times reactor volume) [$1e3 per year]", + doc="adiabatic reactor linear coefficient (times reactor volume) [$1e3/yr]", ) m.isothermal_reactor_fixed_cost = Param( - initialize=92.875, doc="isothermal reactor fixed cost [$1e3 per year]" + initialize=92.875, doc="isothermal reactor fixed cost [$1e3/yr]" ) m.isothermal_reactor_linear_coefficient = Param( initialize=1.57125, - doc="isothermal reactor linear coefficient (times reactor volume) [$1e3 per year]", + doc="isothermal reactor linear coefficient (times reactor volume) [$1e3/yr]", ) m.h2_feed_cost = Param(initialize=2.5, doc="h2 feed cost (95% h2,5% Ch4)") m.toluene_feed_cost = Param(initialize=14.0, doc="toluene feed cost (100% toluene)") m.benzene_product = Param( - initialize=19.9, doc="benzene product profit(benzene >= 99.97%)" + initialize=19.9, doc="benzene product profit (benzene >= 99.97%)" ) m.diphenyl_product = Param( - initialize=11.84, doc="diphenyl product profit(diphenyl = 100%)" + initialize=11.84, doc="diphenyl product profit (diphenyl = 100%)" ) def profits_from_paper(m): @@ -3246,7 +3246,7 @@ def profits_from_paper(m): + m.diphenyl_product * m.f[35] + m.hydrogen_purge_value * (m.fc[4, "h2"] + m.fc[28, "h2"] + m.fc[53, "h2"] + m.fc[55, "h2"]) - + m.meathane_purge_value + + m.methane_purge_value * (m.fc[4, "ch4"] + m.fc[28, "ch4"] + m.fc[53, "ch4"] + m.fc[55, "ch4"]) ) - m.compressor_linear_coefficient * (m.elec[1] + m.elec[2] + m.elec[3]) @@ -3307,7 +3307,7 @@ def profits_from_paper(m): # "there are several differences between the data from GAMS file and the paper: 1. all the compressor share the same fixed and linear cost in paper but in GAMS they have different fixed and linear cost in GAMS file. 2. the fixed cost for absorber in GAMS file is 3.0 but in the paper is 13.0, but they are getting the same results 3. the electricity cost is not the same" - # return 510. * (- m.h2_feed_cost * m.f[1] - m.toluene_feed_cost * (m.f[66] + m.f[67]) + m.benzene_product * m.f[31] + m.diphenyl_product * m.f[35] + m.hydrogen_purge_value * (m.fc[4, 'h2'] + m.fc[28, 'h2'] + m.fc[53, 'h2'] + m.fc[55, 'h2']) + m.meathane_purge_value * (m.fc[4, 'ch4'] + m.fc[28, 'ch4'] + m.fc[53, 'ch4'] + m.fc[55, 'ch4'])) - m.compressor_linear_coefficient * (m.elec[1] + m.elec[2] + m.elec[3]) - m.compressor_linear_coefficient_4 * m.elec[4] - m.compressor_fixed_cost * (m.purify_H2.binary_indicator_var + m.recycle_hydrogen.binary_indicator_var + m.absorber_hydrogen.binary_indicator_var) - m.compressor_fixed_cost_4 * m.recycle_methane_membrane.binary_indicator_var - sum((m.costelec * m.elec[comp]) for comp in m.comp) - (m.adiabtic_reactor_fixed_cost * m.adiabatic_reactor.binary_indicator_var + m.adiabtic_reactor_linear_coefficient * m.rctvol[1]) - (m.isothermal_reactor_fixed_cost * m.isothermal_reactor.binary_indicator_var + m.isothermal_reactor_linear_coefficient * m.rctvol[2]) - m.cooling_cost/1000 * m.q[2] - (m.stabilizing_column_fixed_cost * m.methane_distillation_column.binary_indicator_var +m.stabilizing_column_linear_coefficient * m.ndist[1]) - (m.benzene_column_fixed_cost + m.benzene_column_linear_coefficient * m.ndist[2]) - (m.toluene_column_fixed_cost * m.toluene_distillation_column.binary_indicator_var + m.toluene_column_linear_coefficient * m.ndist[3]) - (m.membrane_separator_fixed_cost * m.purify_H2.binary_indicator_var + m.membrane_separator_linear_coefficient * m.f[3]) - (m.membrane_separator_fixed_cost * m.recycle_methane_membrane.binary_indicator_var + m.membrane_separator_linear_coefficient * m.f[54]) - (3.0 * m.absorber_hydrogen.binary_indicator_var + m.abs_linear_coefficient * m.nabs[1]) - (m.fuel_cost * m.qfuel[1] + m.furnace_linear_coefficient* m.qfuel[1]) - sum(m.cooling_cost * m.qc[hec] for hec in m.hec) - sum(m.heating_cost * m.qh[heh] for heh in m.heh) - m.furnace_fixed_cost + # return 510. * (- m.h2_feed_cost * m.f[1] - m.toluene_feed_cost * (m.f[66] + m.f[67]) + m.benzene_product * m.f[31] + m.diphenyl_product * m.f[35] + m.hydrogen_purge_value * (m.fc[4, 'h2'] + m.fc[28, 'h2'] + m.fc[53, 'h2'] + m.fc[55, 'h2']) + m.methane_purge_value * (m.fc[4, 'ch4'] + m.fc[28, 'ch4'] + m.fc[53, 'ch4'] + m.fc[55, 'ch4'])) - m.compressor_linear_coefficient * (m.elec[1] + m.elec[2] + m.elec[3]) - m.compressor_linear_coefficient_4 * m.elec[4] - m.compressor_fixed_cost * (m.purify_H2.binary_indicator_var + m.recycle_hydrogen.binary_indicator_var + m.absorber_hydrogen.binary_indicator_var) - m.compressor_fixed_cost_4 * m.recycle_methane_membrane.binary_indicator_var - sum((m.costelec * m.elec[comp]) for comp in m.comp) - (m.adiabtic_reactor_fixed_cost * m.adiabatic_reactor.binary_indicator_var + m.adiabtic_reactor_linear_coefficient * m.rctvol[1]) - (m.isothermal_reactor_fixed_cost * m.isothermal_reactor.binary_indicator_var + m.isothermal_reactor_linear_coefficient * m.rctvol[2]) - m.cooling_cost/1000 * m.q[2] - (m.stabilizing_column_fixed_cost * m.methane_distillation_column.binary_indicator_var +m.stabilizing_column_linear_coefficient * m.ndist[1]) - (m.benzene_column_fixed_cost + m.benzene_column_linear_coefficient * m.ndist[2]) - (m.toluene_column_fixed_cost * m.toluene_distillation_column.binary_indicator_var + m.toluene_column_linear_coefficient * m.ndist[3]) - (m.membrane_separator_fixed_cost * m.purify_H2.binary_indicator_var + m.membrane_separator_linear_coefficient * m.f[3]) - (m.membrane_separator_fixed_cost * m.recycle_methane_membrane.binary_indicator_var + m.membrane_separator_linear_coefficient * m.f[54]) - (3.0 * m.absorber_hydrogen.binary_indicator_var + m.abs_linear_coefficient * m.nabs[1]) - (m.fuel_cost * m.qfuel[1] + m.furnace_linear_coefficient* m.qfuel[1]) - sum(m.cooling_cost * m.qc[hec] for hec in m.hec) - sum(m.heating_cost * m.qh[heh] for heh in m.heh) - m.furnace_fixed_cost # m.obj = Objective(rule=profits_GAMS_file, sense=maximize) return m From e1fbb81816264f9210da605d830d2559727cb2f6 Mon Sep 17 00:00:00 2001 From: parkyr Date: Thu, 30 May 2024 13:54:00 -0400 Subject: [PATCH 37/79] reformatting --- gdplib/hda/HDA_GDP_gdpopt.py | 52 ++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/gdplib/hda/HDA_GDP_gdpopt.py b/gdplib/hda/HDA_GDP_gdpopt.py index dd34987..863c371 100644 --- a/gdplib/hda/HDA_GDP_gdpopt.py +++ b/gdplib/hda/HDA_GDP_gdpopt.py @@ -213,10 +213,7 @@ def strset(i): Heatvap = {} Heatvap["tol"] = 30890.00 m.heatvap = Param( - m.compon, - initialize=Heatvap, - default=0, - doc="heat of vaporization [kJ/kg-mol]", + m.compon, initialize=Heatvap, default=0, doc="heat of vaporization [kJ/kg-mol]" ) Cppure = {} # h2 'hydrogen', ch4 'methane', ben 'benzene', tol 'toluene', dip 'diphenyl' @@ -716,10 +713,7 @@ def Permset(m, compon): m.flsh, within=NonNegativeReals, initialize=1, doc="flash temperature [100 K]" ) m.flshp = Var( - m.flsh, - within=NonNegativeReals, - initialize=1, - doc="flash pressure [MPa]", + m.flsh, within=NonNegativeReals, initialize=1, doc="flash pressure [MPa]" ) m.eflsh = Var( m.flsh, @@ -814,10 +808,7 @@ def Permset(m, compon): doc="reactor pressure [MPa]", ) m.rctvol = Var( - m.rct, - within=NonNegativeReals, - bounds=(None, 200), - doc="reactor volume [m**3]", + m.rct, within=NonNegativeReals, bounds=(None, 200), doc="reactor volume [m**3]" ) m.krct = Var( m.rct, @@ -861,15 +852,10 @@ def Permset(m, compon): doc="splitter temperature [100 K]", ) m.spl1p = Var( - m.spl1, - within=PositiveReals, - bounds=(0.1, 4.0), - doc="splitter pressure [MPa]", + m.spl1, within=PositiveReals, bounds=(0.1, 4.0), doc="splitter pressure [MPa]" ) # splitter - m.splp = Var( - m.spl, within=Reals, bounds=(0.1, 4.0), doc="splitter pressure [MPa]" - ) + m.splp = Var(m.spl, within=Reals, bounds=(0.1, 4.0), doc="splitter pressure [MPa]") m.splt = Var( m.spl, within=Reals, bounds=(3.0, 10.0), doc="splitter temperature [100 K]" ) @@ -1957,7 +1943,9 @@ def Flshti(_m, flsh_, stream): return m.flsht[flsh_] == m.t[stream] return Constraint.Skip - b.flshti = Constraint([flsh], m.str, rule=Flshti, doc="inlet temperature relation") + b.flshti = Constraint( + [flsh], m.str, rule=Flshti, doc="inlet temperature relation" + ) def Flshtl(_m, flsh_, stream): if (flsh_, stream) in m.lflsh and flsh_ == flsh: @@ -1978,7 +1966,8 @@ def Flshtv(_m, flsh_, stream): ) m.heat_unit_match = Param( - initialize=3600.0 * 8500.0 * 1.0e-12 / 60.0, doc="unit change on heat balance from [kJ/min] to [1e12kJ/yr]" + initialize=3600.0 * 8500.0 * 1.0e-12 / 60.0, + doc="unit change on heat balance from [kJ/min] to [1e12kJ/yr]", ) def build_furnace(b, furnace): @@ -2737,7 +2726,11 @@ def rctcns(_m, rct, stream, compon): return Constraint.Skip b.Rctcns = Constraint( - [rct], m.str, m.compon, rule=rctcns, doc="consumption rate of key components" + [rct], + m.str, + m.compon, + rule=rctcns, + doc="consumption rate of key components", ) def rctmbtol(_m, rct): @@ -2855,7 +2848,9 @@ def Rcthbiso(_m, rct): ) return Constraint.Skip - b.rcthbiso = Constraint([rct], rule=Rcthbiso, doc="temperature relation (isothermal)") + b.rcthbiso = Constraint( + [rct], rule=Rcthbiso, doc="temperature relation (isothermal)" + ) def Rctisot(_m, rct): if rct == 2: @@ -2864,7 +2859,9 @@ def Rctisot(_m, rct): ) == sum(m.t[stream] for (rct_, stream) in m.orct if rct_ == rct) return Constraint.Skip - b.rctisot = Constraint([rct], rule=Rctisot, doc="temperature relation (isothermal)") + b.rctisot = Constraint( + [rct], rule=Rctisot, doc="temperature relation (isothermal)" + ) def build_single_mixer(b, mixer): """ @@ -3149,9 +3146,7 @@ def toluene_selection(m): initialize=0.04 * 24 * 365 / 1000, doc="electricity cost, value is 0.04 with the unit of [kW/h], now is [kW/yr/$1e3]", ) - m.methane_purge_value = Param( - initialize=3.37, doc="heating value of methane purge" - ) + m.methane_purge_value = Param(initialize=3.37, doc="heating value of methane purge") m.heating_cost = Param( initialize=8000.0, doc="heating cost (steam) with unit [1e6 kJ]" ) @@ -3203,8 +3198,7 @@ def toluene_selection(m): initialize=6.20, doc="toluene column fixed cost [$1e3/yr]" ) m.furnace_linear_coefficient = Param( - initialize=1171.7, - doc="furnace column linear coefficient [$1e3/(1e12 kJ/yr)]", + initialize=1171.7, doc="furnace column linear coefficient [$1e3/(1e12 kJ/yr)]" ) m.membrane_separator_fixed_cost = Param( initialize=43.24, doc="membrane separator fixed cost [$1e3/yr]" From dffd9c799f8c0464c4bc8db2f2fb037989a19928 Mon Sep 17 00:00:00 2001 From: parkyr Date: Fri, 31 May 2024 17:00:04 -0400 Subject: [PATCH 38/79] fixing capitalization and adding more units to doc --- gdplib/hda/HDA_GDP_gdpopt.py | 45 +++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/gdplib/hda/HDA_GDP_gdpopt.py b/gdplib/hda/HDA_GDP_gdpopt.py index 863c371..fba558a 100644 --- a/gdplib/hda/HDA_GDP_gdpopt.py +++ b/gdplib/hda/HDA_GDP_gdpopt.py @@ -66,7 +66,7 @@ def HDA_model(): cppure : float pure component heat capacities [kJ/kg-mol-K] gcomp : float - guess composition values + guess composition values [mol/mol] cp : float heat capacities [kJ/kg-mol-K] anta : float @@ -86,11 +86,11 @@ def HDA_model(): heatrxn : float heat of reaction [kJ/kg-mol] f1comp : float - feedstock compositions (h2 feed) + feedstock compositions (h2 feed) [mol/mol] f66comp : float - feedstock compositions (tol feed) + feedstock compositions (tol feed) [mol/mol] f67comp : float - feedstock compositions (tol feed) + feedstock compositions (tol feed) [mol/mol] Sets ---- @@ -329,7 +329,11 @@ def strset(i): Gcomp[72, "h2"] = 0.50 Gcomp[72, "ch4"] = 0.50 m.gcomp = Param( - m.str, m.compon, initialize=Gcomp, default=0, doc="guess composition values" + m.str, + m.compon, + initialize=Gcomp, + default=0, + doc="guess composition values [mol/mol]", ) def cppara(compon, stream): @@ -376,7 +380,7 @@ def cppara(compon, stream): def Permset(m, compon): """ permeability [kg-mol/m**2-min-MPa] - converting unit for permeability from cc/cm**2-sec-cmHg to kg-mol/m**2-min-MPa + converting unit for permeability from [cc/cm**2-sec-cmHg] to [kg-mol/m**2-min-MPa] """ return Perm[compon] * (1.0 / 22400.0) * 1.0e4 * 750.062 * 60.0 / 1000.0 @@ -402,7 +406,7 @@ def Permset(m, compon): Aabs["ben"] = 1.4 Aabs["tol"] = 4.0 m.aabs = Param(m.compon, initialize=Aabs, default=0, doc="absorption factors") - m.eps1 = Param(initialize=1e-4, doc="small number to avoid div. by zero") + m.eps1 = Param(initialize=1e-4, doc="small number to avoid division by zero") Heatrxn = {} Heatrxn[1] = 50100.0 @@ -418,7 +422,10 @@ def Permset(m, compon): F1comp["ben"] = 0.00 F1comp["tol"] = 0.00 m.f1comp = Param( - m.compon, initialize=F1comp, default=0, doc="feedstock compositions (h2 feed)" + m.compon, + initialize=F1comp, + default=0, + doc="feedstock compositions (h2 feed) [mol/mol]", ) F66comp = {} @@ -428,7 +435,10 @@ def Permset(m, compon): F66comp["dip"] = 0.00 F66comp["ben"] = 0.00 m.f66comp = Param( - m.compon, initialize=F66comp, default=0, doc="feedstock compositions (tol feed)" + m.compon, + initialize=F66comp, + default=0, + doc="feedstock compositions (tol feed) [mol/mol]", ) F67comp = {} @@ -438,7 +448,10 @@ def Permset(m, compon): F67comp["dip"] = 0.00 F67comp["ben"] = 0.00 m.f67comp = Param( - m.compon, initialize=F67comp, default=0, doc="feedstock compositions (tol feed)" + m.compon, + initialize=F67comp, + default=0, + doc="feedstock compositions (tol feed) [mol/mol]", ) # # matching streams @@ -485,13 +498,13 @@ def Permset(m, compon): ) m.dh = Set( initialize=[(1, "tol"), (1, "dip"), (2, "dip")], - doc="dist-Key component matches", + doc="dist-key component matches", ) i = list(m.dlkey) q = list(m.dhkey) dkeyset = i + q - m.dkey = Set(initialize=dkeyset, doc="dist-Key component matches") + m.dkey = Set(initialize=dkeyset, doc="dist-key component matches") m.iflsh = Set( initialize=[(1, 17), (2, 46), (3, 39)], doc="flsh-stream (inlet) matches" @@ -504,7 +517,7 @@ def Permset(m, compon): ) m.fkey = Set( initialize=[(1, "ch4"), (2, "ch4"), (3, "tol")], - doc="flash-Key component matches", + doc="flash-key component matches", ) m.ifurn = Set(initialize=[(1, 70)], doc="furn-stream (inlet) matches") @@ -612,7 +625,7 @@ def Permset(m, compon): m.irct = Set(initialize=[(1, 10), (2, 12)], doc="reactor-stream (inlet) matches") m.orct = Set(initialize=[(1, 11), (2, 13)], doc="reactor-stream (outlet) matches") m.rkey = Set( - initialize=[(1, "tol"), (2, "tol")], doc="reactor-Key component matches" + initialize=[(1, "tol"), (2, "tol")], doc="reactor-key component matches" ) m.ispl1 = Set( @@ -842,7 +855,7 @@ def Permset(m, compon): m.rct, within=NonNegativeReals, bounds=(0, 10000000000), - doc="heat removed [1.e+9 kJ/yr]", + doc="heat removed [1.e+9 kJ/yr]", ) # splitter (1 output) m.spl1t = Var( @@ -863,7 +876,7 @@ def Permset(m, compon): # stream def bound_f(m, stream): """ - stream flowrates [kg-mole per min] + stream flowrates [kg-mol/min] setting appropriate bounds for stream flowrates """ if stream in range(8, 19): From 082b2baac55d57d2c02130e918488cd9c340895f Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Sat, 8 Jun 2024 17:21:20 -0400 Subject: [PATCH 39/79] chore: Add toy problem README with problem details --- gdplib/toy_problem/README.md | 11 ++ gdplib/toy_problem/toy_problem.py | 234 ++++++++++++++++++++++++++++++ 2 files changed, 245 insertions(+) create mode 100644 gdplib/toy_problem/README.md create mode 100644 gdplib/toy_problem/toy_problem.py diff --git a/gdplib/toy_problem/README.md b/gdplib/toy_problem/README.md new file mode 100644 index 0000000..34ffae5 --- /dev/null +++ b/gdplib/toy_problem/README.md @@ -0,0 +1,11 @@ +## toy_problem.py + +The toy problem is a simple optimization problem that involves two Boolean variables, two continuous variables, and a non-linear objective function. +The problem is formulated as a Generalized Disjunctive Programming (GDP) model. +The Boolean variables are associated with disjuncts that define the feasible regions of the continuous variables. +The problem also includes logical constraints that ensure that only one Boolean variable is true at a time. +The objective function is -0.9995999999999999 when the continuous variables are alpha = 0 (Y1[2]=True) and beta=-0.7 (Y2[3]=True). + +### References +[1] Liñán, D. A., & Ricardez-Sandoval, L. A. (2023). A Benders decomposition framework for the optimization of disjunctive superstructures with ordered discrete decisions. AIChE Journal, 69(5), e18008. https://doi.org/10.1002/aic.18008 +[2] Gomez, S., & Levy, A. V. (1982). The tunnelling method for solving the constrained global optimization problem with several non-connected feasible regions. In Numerical Analysis: Proceedings of the Third IIMAS Workshop Held at Cocoyoc, Mexico, January 1981 (pp. 34-47). Springer Berlin Heidelberg. https://doi.org/10.1007/BFb0092958 diff --git a/gdplib/toy_problem/toy_problem.py b/gdplib/toy_problem/toy_problem.py new file mode 100644 index 0000000..79ec98c --- /dev/null +++ b/gdplib/toy_problem/toy_problem.py @@ -0,0 +1,234 @@ +""" +toy_problem.py + +The toy problem is a simple optimization problem that involves two Boolean variables, two continuous variables, and a non-linear objective function. +The problem is formulated as a Generalized Disjunctive Programming (GDP) model. +The Boolean variables are associated with disjuncts that define the feasible regions of the continuous variables. +The problem also includes logical constraints that ensure that only one Boolean variable is true at a time. +The objective function is -0.9995999999999999 when the continuous variables are alpha = 0 (Y1[2]=True) and beta=-0.7 (Y2[3]=True). + +References +---------- +[1] Liñán, D. A., & Ricardez-Sandoval, L. A. (2023). A Benders decomposition framework for the optimization of disjunctive superstructures with ordered discrete decisions. AIChE Journal, 69(5), e18008. https://doi.org/10.1002/aic.18008 +[2] Gomez, S., & Levy, A. V. (1982). The tunnelling method for solving the constrained global optimization problem with several non-connected feasible regions. In Numerical Analysis: Proceedings of the Third IIMAS Workshop Held at Cocoyoc, Mexico, January 1981 (pp. 34-47). Springer Berlin Heidelberg. https://doi.org/10.1007/BFb0092958 +""" +import pyomo.environ as pyo +from pyomo.gdp import Disjunct, Disjunction + +def build_model(): + """ + Build the toy problem model + + Returns + ------- + Pyomo.ConcreteModel + Toy problem model + """ + + # Build Model + m = pyo.ConcreteModel() + + # Sets + m.set1 = pyo.RangeSet(1,5,doc= "set of first group of Boolean variables") + m.set2 = pyo.RangeSet(1,5,doc= "set of second group of Boolean variables") + + m.sub1 = pyo.Set(initialize=[3],within=m.set1) + + # Variables + m.Y1 = pyo.BooleanVar(m.set1,doc="Boolean variable associated to set 1") + m.Y2 = pyo.BooleanVar(m.set2,doc="Boolean variable associated to set 2") + + m.alpha = pyo.Var(within=pyo.Reals, bounds=(-0.1,0.4), doc="continuous variable alpha") + m.beta = pyo.Var(within=pyo.Reals, bounds=(-0.9,-0.5), doc="continuous variable beta") + + + # Objective Function + def obj_fun(m): + """ + Objective function + + Parameters + ---------- + m : Pyomo.ConcreteModel + Toy problem model + + Returns + ------- + Pyomo.Objective + Build the objective function of the toy problem + """ + return 4*(pow(m.alpha,2))-2.1*(pow(m.alpha,4))+(1/3)*(pow(m.alpha,6))+m.alpha*m.beta-4*(pow(m.beta,2))+4*(pow(m.beta,4)) + m.obj=pyo.Objective(rule=obj_fun,sense=pyo.minimize, doc="Objective function") + + # First Disjunction + def build_disjuncts1(m,set1): #Disjuncts for first Boolean variable + """ + Build disjuncts for the first Boolean variable + + Parameters + ---------- + m : Pyomo.ConcreteModel + Toy problem model + set1 : RangeSet + Set of first group of Boolean variables + """ + def constraint1(m): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + Toy problem model + + Returns + ------- + Pyomo.Constraint + Constraint that defines the value of alpha for each disjunct + """ + return m.model().alpha==-0.1+0.1*(set1-1) #.model() is required when writing constraints inside disjuncts + m.constraint1=pyo.Constraint(rule=constraint1) + + m.Y1_disjunct=Disjunct(m.set1,rule=build_disjuncts1, doc="each disjunct is defined over set 1") + + + def Disjunction1(m): # Disjunction for first Boolean variable + """ + Disjunction for first Boolean variable + + Parameters + ---------- + m : Pyomo.ConcreteModel + Toy problem model + + Returns + ------- + Pyomo.Disjunction + Build the disjunction for the first Boolean variable set + """ + return [m.Y1_disjunct[j] for j in m.set1] + m.Disjunction1=Disjunction(rule=Disjunction1,xor=False) + + # Associate boolean variables to disjuncts + for n1 in m.set1: + m.Y1[n1].associate_binary_var(m.Y1_disjunct[n1].indicator_var) + + # Second disjunction + def build_disjuncts2(m,set2): # Disjuncts for second Boolean variable + """ + Build disjuncts for the second Boolean variable + + Parameters + ---------- + m : Pyomo.ConcreteModel + Toy problem model + set2 : RangeSet + Set of second group of Boolean variables + """ + def constraint2(m): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + Toy problem model + + Returns + ------- + Pyomo.Constraint + Constraint that defines the value of beta for each disjunct + """ + return m.model().beta==-0.9+0.1*(set2-1) #.model() is required when writing constraints inside disjuncts + m.constraint2=pyo.Constraint(rule=constraint2) + + m.Y2_disjunct=Disjunct(m.set2,rule=build_disjuncts2,doc="each disjunct is defined over set 2") + + + def Disjunction2(m): # Disjunction for first Boolean variable + """ + Disjunction for second Boolean variable + + Parameters + ---------- + m : Pyomo.ConcreteModel + Toy problem model + + Returns + ------- + Pyomo.Disjunction + Build the disjunction for the second Boolean variable set + """ + return [m.Y2_disjunct[j] for j in m.set2] + m.Disjunction2=Disjunction(rule=Disjunction2,xor=False) + + + #Associate boolean variables to disjuncts + for n2 in m.set2: + m.Y2[n2].associate_binary_var(m.Y2_disjunct[n2].indicator_var) + + + # Logical constraints + + # Constraint that allow to apply the reformulation over Y1 + def select_one_Y1(m): + """ + Logical constraint that allows to apply the reformulation over Y1 + + Parameters + ---------- + m : Pyomo.ConcreteModel + Toy problem model + + Returns + ------- + Pyomo.LogicalConstraint + Logical constraint that make Y1 to be true for only one element + """ + return pyo.exactly(1,m.Y1) + m.oneY1=pyo.LogicalConstraint(rule=select_one_Y1) + + # Constraint that allow to apply the reformulation over Y2 + def select_one_Y2(m): + """ + Logical constraint that allows to apply the reformulation over Y2 + + Parameters + ---------- + m : Pyomo.ConcreteModel + Toy problem model + + Returns + ------- + Pyomo.LogicalConstraint + Logical constraint that make Y2 to be true for only one element + """ + return pyo.exactly(1,m.Y2) + m.oneY2=pyo.LogicalConstraint(rule=select_one_Y2) + + # Constraint that define an infeasible region with respect to Boolean variables + + def infeasR_rule(m): + """ + Logical constraint that defines an infeasible region with respect to Boolean variables + + Parameters + ---------- + m : Pyomo.ConcreteModel + Toy problem model + + Returns + ------- + Pyomo.LogicalConstraint + Logical constraint that defines an infeasible region on Y1[3] + """ + return pyo.land([pyo.lnot(m.Y1[j]) for j in m.sub1]) + m.infeasR=pyo.LogicalConstraint(rule=infeasR_rule) + + return m + +if __name__ == "__main__": + m = build_model() + pyo.TransformationFactory('gdp.bigm').apply_to(m) + solver = pyo.SolverFactory('gams') + solver.solve(m, solver='baron', tee=True) + print("Solution: alpha=",pyo.value(m.alpha)," beta=",pyo.value(m.beta)) + print("Objective function value: ",pyo.value(m.obj)) From 49619881a8f63bd16f17c89b632f2d61e2566936 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Sat, 8 Jun 2024 19:18:11 -0400 Subject: [PATCH 40/79] Added the bounds on the logarithmic batch size variables. --- gdplib/batch_processing/batch_processing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gdplib/batch_processing/batch_processing.py b/gdplib/batch_processing/batch_processing.py index f11610c..9acf0b1 100644 --- a/gdplib/batch_processing/batch_processing.py +++ b/gdplib/batch_processing/batch_processing.py @@ -217,7 +217,7 @@ def get_volume_bounds(model, j): model.STAGES, bounds=get_volume_bounds, doc='Logarithmic Volume of the Units' ) model.batchSize_log = Var( - model.PRODUCTS, model.STAGES, doc='Logarithmic Batch Size of the Products' + model.PRODUCTS, model.STAGES, bounds=(-10,10), doc='Logarithmic Batch Size of the Products' ) model.cycleTime_log = Var( model.PRODUCTS, doc='Logarithmic Cycle Time of the Products' @@ -701,4 +701,4 @@ def units_in_phase_xor_rule(model, j): SolverFactory('gams').solve( m, solver='baron', tee=True, add_options=['option optcr=1e-6;'] ) - m.min_cost.display() + m.min_cost.display() \ No newline at end of file From f7d1e16afc23cec01ef578642e1a1cc6f60058ad Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Sat, 8 Jun 2024 19:56:43 -0400 Subject: [PATCH 41/79] Removed the initialize argument --- gdplib/biofuel/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gdplib/biofuel/model.py b/gdplib/biofuel/model.py index 84820f4..5c9026f 100644 --- a/gdplib/biofuel/model.py +++ b/gdplib/biofuel/model.py @@ -52,7 +52,7 @@ def build_model(): [2] Chen, Q., & Grossmann, I. E. (2019). Effective generalized disjunctive programming models for modular process synthesis. Industrial & Engineering Chemistry Research, 58(15), 5873-5886. https://doi.org/10.1021/acs.iecr.8b04600 """ m = ConcreteModel('Biofuel processing network') - m.bigM = Suffix(direction=Suffix.LOCAL, initialize=7000) + m.bigM = Suffix(direction=Suffix.LOCAL)#, initialize=7000) # Removed the initialize argument m.time = RangeSet(0, 120, doc="months in 10 years") m.suppliers = RangeSet(10) # 10 suppliers m.markets = RangeSet(10) # 10 markets From fab491497235ebf43864c51f5cb70885c2990504 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Sat, 8 Jun 2024 20:10:49 -0400 Subject: [PATCH 42/79] Fixed Typo --- gdplib/med_term_purchasing/med_term_purchasing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gdplib/med_term_purchasing/med_term_purchasing.py b/gdplib/med_term_purchasing/med_term_purchasing.py index 73bb3c8..e15d363 100644 --- a/gdplib/med_term_purchasing/med_term_purchasing.py +++ b/gdplib/med_term_purchasing/med_term_purchasing.py @@ -141,7 +141,7 @@ def build_model(): # pd1(j, t) in GAMS model.RegPrice_Discount = Param( model.Streams, - model.TimePeriod, + model.TimePeriods, doc='Price for quantities less than min amount under discount contract', ) From e804fd46868deb6567ac390a85d44f6a39b28c43 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Sat, 8 Jun 2024 20:12:56 -0400 Subject: [PATCH 43/79] fixed the typo on the Parameter --- gdplib/med_term_purchasing/med_term_purchasing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gdplib/med_term_purchasing/med_term_purchasing.py b/gdplib/med_term_purchasing/med_term_purchasing.py index e15d363..5b85acc 100644 --- a/gdplib/med_term_purchasing/med_term_purchasing.py +++ b/gdplib/med_term_purchasing/med_term_purchasing.py @@ -339,7 +339,7 @@ def getCostUBs(model, j, t): model.Streams, model.TimePeriods, initialize=getCostUBs, - DOC='Upper bound on cost of discount contract', + doc='Upper bound on cost of discount contract', ) model.CostUB_Bulk = Param( model.Streams, From 171fc1c2eddcc4210ffbcc67d7ef980954e07cf1 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 12 Jun 2024 16:24:32 +0800 Subject: [PATCH 44/79] black format --- gdplib/small_batch/gdp_small_batch.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gdplib/small_batch/gdp_small_batch.py b/gdplib/small_batch/gdp_small_batch.py index 1922aed..67f7467 100644 --- a/gdplib/small_batch/gdp_small_batch.py +++ b/gdplib/small_batch/gdp_small_batch.py @@ -9,6 +9,7 @@ [1] Kocis, G. R.; Grossmann, I. E. Global Optimization of Nonconvex Mixed-Integer Nonlinear Programming (MINLP) Problems in Process Synthesis. Ind. Eng. Chem. Res. 1988, 27 (8), 1407-1421. https://doi.org/10.1021/ie00080a013 [2] Ovalle, D., Liñán, D. A., Lee, A., Gómez, J. M., Ricardez-Sandoval, L., Grossmann, I. E., & Neira, D. E. B. (2024). Logic-Based Discrete-Steepest Descent: A Solution Method for Process Synthesis Generalized Disjunctive Programs. arXiv preprint arXiv:2405.05358. https://doi.org/10.48550/arXiv.2405.05358 """ + import os import pyomo.environ as pyo from pyomo.core.base.misc import display From b97be336f06f09bc87c669830019f3e20cd80ccd Mon Sep 17 00:00:00 2001 From: parkyr Date: Mon, 24 Jun 2024 13:01:17 -0400 Subject: [PATCH 45/79] update the best known obj value and correct no.vars Updates were made to README.md file: - better obj value was found. - the number of variables and continuous variables in the original file were incorrectly listed. --- gdplib/hda/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gdplib/hda/README.md b/gdplib/hda/README.md index f0139a0..c9d7d71 100644 --- a/gdplib/hda/README.md +++ b/gdplib/hda/README.md @@ -14,14 +14,14 @@ This model was reimplemented by Yunshan Liu @Yunshan-Liu . ## Problem Details ### Solution -Best known objective value: 5966.51 +Best known objective value: 5801.27 ### Size | Problem | vars | Bool | bin | int | cont | cons | nl | disj | disjtn | |-----------|------|------|-----|-----|------|------|----|------|--------| -| HDA Model | 733 | 12 | 0 | 0 | 721 | 728 | 151 | 12 | 6 | +| HDA Model | 721 | 12 | 0 | 0 | 709 | 728 | 151 | 12 | 6 | - ``vars``: variables - ``Bool``: Boolean variables From 5daabb68720d8c9ed48b2fadd78ebee6d6e0d3e7 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 27 Jun 2024 15:38:59 -0400 Subject: [PATCH 46/79] Fixed the bounds of the batch_processing.py variables. --- gdplib/batch_processing/batch_processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gdplib/batch_processing/batch_processing.py b/gdplib/batch_processing/batch_processing.py index 9acf0b1..0a7e7a9 100644 --- a/gdplib/batch_processing/batch_processing.py +++ b/gdplib/batch_processing/batch_processing.py @@ -217,7 +217,7 @@ def get_volume_bounds(model, j): model.STAGES, bounds=get_volume_bounds, doc='Logarithmic Volume of the Units' ) model.batchSize_log = Var( - model.PRODUCTS, model.STAGES, bounds=(-10,10), doc='Logarithmic Batch Size of the Products' + model.PRODUCTS, model.STAGES, bounds=(0,10), doc='Logarithmic Batch Size of the Products' ) model.cycleTime_log = Var( model.PRODUCTS, doc='Logarithmic Cycle Time of the Products' From d5fef7c2cda1965383239a1a9243d85248b9b589 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 27 Jun 2024 15:41:15 -0400 Subject: [PATCH 47/79] black formatted. --- gdplib/batch_processing/batch_processing.py | 89 +++++++++++---------- 1 file changed, 46 insertions(+), 43 deletions(-) diff --git a/gdplib/batch_processing/batch_processing.py b/gdplib/batch_processing/batch_processing.py index 0a7e7a9..6cefcd3 100644 --- a/gdplib/batch_processing/batch_processing.py +++ b/gdplib/batch_processing/batch_processing.py @@ -69,9 +69,9 @@ def build_model(): # Sets - model.PRODUCTS = Set(doc='Set of Products') - model.STAGES = Set(doc='Set of Stages', ordered=True) - model.PARALLELUNITS = Set(doc='Set of Parallel Units', ordered=True) + model.PRODUCTS = Set(doc="Set of Products") + model.STAGES = Set(doc="Set of Stages", ordered=True) + model.PARALLELUNITS = Set(doc="Set of Parallel Units", ordered=True) # TODO: this seems like an over-complicated way to accomplish this task... def filter_out_last(model, j): @@ -101,27 +101,27 @@ def filter_out_last(model, j): # Parameters - model.HorizonTime = Param(doc='Horizon Time') - model.Alpha1 = Param(doc='Cost Parameter of the units') - model.Alpha2 = Param(doc='Cost Parameter of the intermediate storage tanks') - model.Beta1 = Param(doc='Exponent Parameter of the units') - model.Beta2 = Param(doc='Exponent Parameter of the intermediate storage tanks') + model.HorizonTime = Param(doc="Horizon Time") + model.Alpha1 = Param(doc="Cost Parameter of the units") + model.Alpha2 = Param(doc="Cost Parameter of the intermediate storage tanks") + model.Beta1 = Param(doc="Exponent Parameter of the units") + model.Beta2 = Param(doc="Exponent Parameter of the intermediate storage tanks") - model.ProductionAmount = Param(model.PRODUCTS, doc='Production Amount') + model.ProductionAmount = Param(model.PRODUCTS, doc="Production Amount") model.ProductSizeFactor = Param( - model.PRODUCTS, model.STAGES, doc='Product Size Factor' + model.PRODUCTS, model.STAGES, doc="Product Size Factor" ) - model.ProcessingTime = Param(model.PRODUCTS, model.STAGES, doc='Processing Time') + model.ProcessingTime = Param(model.PRODUCTS, model.STAGES, doc="Processing Time") # These are hard-coded in the GAMS file, hence the defaults model.StorageTankSizeFactor = Param( - model.STAGES, default=StorageTankSizeFactor, doc='Storage Tank Size Factor' + model.STAGES, default=StorageTankSizeFactor, doc="Storage Tank Size Factor" ) model.StorageTankSizeFactorByProd = Param( model.PRODUCTS, model.STAGES, default=StorageTankSizeFactorByProd, - doc='Storage Tank Size Factor by Product', + doc="Storage Tank Size Factor by Product", ) # TODO: bonmin wasn't happy and I think it might have something to do with this? @@ -148,27 +148,27 @@ def get_log_coeffs(model, k): return log(model.PARALLELUNITS.ord(k)) model.LogCoeffs = Param( - model.PARALLELUNITS, initialize=get_log_coeffs, doc='Logarithmic Coefficients' + model.PARALLELUNITS, initialize=get_log_coeffs, doc="Logarithmic Coefficients" ) # bounds model.volumeLB = Param( - model.STAGES, default=VolumeLB, doc='Lower Bound of Volume of the Units' + model.STAGES, default=VolumeLB, doc="Lower Bound of Volume of the Units" ) model.volumeUB = Param( - model.STAGES, default=VolumeUB, doc='Upper Bound of Volume of the Units' + model.STAGES, default=VolumeUB, doc="Upper Bound of Volume of the Units" ) model.storageTankSizeLB = Param( - model.STAGES, default=StorageTankSizeLB, doc='Lower Bound of Storage Tank Size' + model.STAGES, default=StorageTankSizeLB, doc="Lower Bound of Storage Tank Size" ) model.storageTankSizeUB = Param( - model.STAGES, default=StorageTankSizeUB, doc='Upper Bound of Storage Tank Size' + model.STAGES, default=StorageTankSizeUB, doc="Upper Bound of Storage Tank Size" ) model.unitsInPhaseUB = Param( - model.STAGES, default=UnitsInPhaseUB, doc='Upper Bound of Units in Phase' + model.STAGES, default=UnitsInPhaseUB, doc="Upper Bound of Units in Phase" ) model.unitsOutOfPhaseUB = Param( - model.STAGES, default=UnitsOutOfPhaseUB, doc='Upper Bound of Units Out of Phase' + model.STAGES, default=UnitsOutOfPhaseUB, doc="Upper Bound of Units Out of Phase" ) # Variables @@ -214,13 +214,16 @@ def get_volume_bounds(model, j): return (model.volumeLB[j], model.volumeUB[j]) model.volume_log = Var( - model.STAGES, bounds=get_volume_bounds, doc='Logarithmic Volume of the Units' + model.STAGES, bounds=get_volume_bounds, doc="Logarithmic Volume of the Units" ) model.batchSize_log = Var( - model.PRODUCTS, model.STAGES, bounds=(0,10), doc='Logarithmic Batch Size of the Products' + model.PRODUCTS, + model.STAGES, + bounds=(0, 10), + doc="Logarithmic Batch Size of the Products", ) model.cycleTime_log = Var( - model.PRODUCTS, doc='Logarithmic Cycle Time of the Products' + model.PRODUCTS, doc="Logarithmic Cycle Time of the Products" ) def get_unitsOutOfPhase_bounds(model, j): @@ -244,7 +247,7 @@ def get_unitsOutOfPhase_bounds(model, j): model.unitsOutOfPhase_log = Var( model.STAGES, bounds=get_unitsOutOfPhase_bounds, - doc='Logarithmic Units Out of Phase', + doc="Logarithmic Units Out of Phase", ) def get_unitsInPhase_bounds(model, j): @@ -266,7 +269,7 @@ def get_unitsInPhase_bounds(model, j): return (0, model.unitsInPhaseUB[j]) model.unitsInPhase_log = Var( - model.STAGES, bounds=get_unitsInPhase_bounds, doc='Logarithmic Units In Phase' + model.STAGES, bounds=get_unitsInPhase_bounds, doc="Logarithmic Units In Phase" ) def get_storageTankSize_bounds(model, j): @@ -291,15 +294,15 @@ def get_storageTankSize_bounds(model, j): model.storageTankSize_log = Var( model.STAGES, bounds=get_storageTankSize_bounds, - doc='Logarithmic Storage Tank Size', + doc="Logarithmic Storage Tank Size", ) # binary variables for deciding number of parallel units in and out of phase model.outOfPhase = Var( - model.STAGES, model.PARALLELUNITS, within=Binary, doc='Out of Phase Units' + model.STAGES, model.PARALLELUNITS, within=Binary, doc="Out of Phase Units" ) model.inPhase = Var( - model.STAGES, model.PARALLELUNITS, within=Binary, doc='In Phase Units' + model.STAGES, model.PARALLELUNITS, within=Binary, doc="In Phase Units" ) # Objective @@ -336,7 +339,7 @@ def get_cost_rule(model): ) model.min_cost = Objective( - rule=get_cost_rule, doc='Minimize the Total Cost of the Plant Design' + rule=get_cost_rule, doc="Minimize the Total Cost of the Plant Design" ) # Constraints @@ -371,7 +374,7 @@ def processing_capacity_rule(model, j, i): model.STAGES, model.PRODUCTS, rule=processing_capacity_rule, - doc='Processing Capacity', + doc="Processing Capacity", ) def processing_time_rule(model, j, i): @@ -402,7 +405,7 @@ def processing_time_rule(model, j, i): ) model.processing_time = Constraint( - model.STAGES, model.PRODUCTS, rule=processing_time_rule, doc='Processing Time' + model.STAGES, model.PRODUCTS, rule=processing_time_rule, doc="Processing Time" ) def finish_in_time_rule(model): @@ -424,7 +427,7 @@ def finish_in_time_rule(model): for i in model.PRODUCTS ) - model.finish_in_time = Constraint(rule=finish_in_time_rule, doc='Finish in Time') + model.finish_in_time = Constraint(rule=finish_in_time_rule, doc="Finish in Time") # Disjunctions @@ -554,7 +557,7 @@ def no_batch_rule(disjunct, i): [0, 1], model.STAGESExceptLast, rule=storage_tank_selection_disjunct_rule, - doc='Storage Tank Selection Disjunct', + doc="Storage Tank Selection Disjunct", ) def select_storage_tanks_rule(model, j): @@ -581,7 +584,7 @@ def select_storage_tanks_rule(model, j): model.select_storage_tanks = Disjunction( model.STAGESExceptLast, rule=select_storage_tanks_rule, - doc='Select Storage Tanks', + doc="Select Storage Tanks", ) # though this is a disjunction in the GAMs model, it is more efficiently formulated this way: @@ -612,7 +615,7 @@ def units_out_of_phase_rule(model, j): ) model.units_out_of_phase = Constraint( - model.STAGES, rule=units_out_of_phase_rule, doc='Units Out of Phase' + model.STAGES, rule=units_out_of_phase_rule, doc="Units Out of Phase" ) def units_in_phase_rule(model, j): @@ -641,7 +644,7 @@ def units_in_phase_rule(model, j): ) model.units_in_phase = Constraint( - model.STAGES, rule=units_in_phase_rule, doc='Units In Phase' + model.STAGES, rule=units_in_phase_rule, doc="Units In Phase" ) def units_out_of_phase_xor_rule(model, j): @@ -665,7 +668,7 @@ def units_out_of_phase_xor_rule(model, j): model.units_out_of_phase_xor = Constraint( model.STAGES, rule=units_out_of_phase_xor_rule, - doc='Exclusive OR for Units Out of Phase', + doc="Exclusive OR for Units Out of Phase", ) def units_in_phase_xor_rule(model, j): @@ -689,16 +692,16 @@ def units_in_phase_xor_rule(model, j): model.units_in_phase_xor = Constraint( model.STAGES, rule=units_in_phase_xor_rule, - doc='Exclusive OR for Units In Phase', + doc="Exclusive OR for Units In Phase", ) - return model.create_instance(join(this_file_dir(), 'batch_processing.dat')) + return model.create_instance(join(this_file_dir(), "batch_processing.dat")) if __name__ == "__main__": m = build_model() - TransformationFactory('gdp.bigm').apply_to(m) - SolverFactory('gams').solve( - m, solver='baron', tee=True, add_options=['option optcr=1e-6;'] + TransformationFactory("gdp.bigm").apply_to(m) + SolverFactory("gams").solve( + m, solver="baron", tee=True, add_options=["option optcr=1e-6;"] ) - m.min_cost.display() \ No newline at end of file + m.min_cost.display() From 5075489043c73470489a3500434a72e0707913db Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Wed, 14 Aug 2024 21:48:53 -0400 Subject: [PATCH 48/79] Changed the bigM parameter from the pyomo documentation --- gdplib/biofuel/model.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gdplib/biofuel/model.py b/gdplib/biofuel/model.py index 5c9026f..c989962 100644 --- a/gdplib/biofuel/model.py +++ b/gdplib/biofuel/model.py @@ -52,7 +52,8 @@ def build_model(): [2] Chen, Q., & Grossmann, I. E. (2019). Effective generalized disjunctive programming models for modular process synthesis. Industrial & Engineering Chemistry Research, 58(15), 5873-5886. https://doi.org/10.1021/acs.iecr.8b04600 """ m = ConcreteModel('Biofuel processing network') - m.bigM = Suffix(direction=Suffix.LOCAL)#, initialize=7000) # Removed the initialize argument + m.bigM = Suffix(direction=Suffix.LOCAL) + m.bigM[None] = 7000 m.time = RangeSet(0, 120, doc="months in 10 years") m.suppliers = RangeSet(10) # 10 suppliers m.markets = RangeSet(10) # 10 markets From 1a8b27b67e0b1e2bf76d55b27d8747fa785858c7 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Fri, 16 Aug 2024 15:02:15 -0400 Subject: [PATCH 49/79] chore: Update toy problem file name and add additional documentation of problem --- gdplib/toy_problem/README.md | 12 ++++++++---- .../toy_problem/{toy_problem.py => david_linan.py} | 0 2 files changed, 8 insertions(+), 4 deletions(-) rename gdplib/toy_problem/{toy_problem.py => david_linan.py} (100%) diff --git a/gdplib/toy_problem/README.md b/gdplib/toy_problem/README.md index 34ffae5..a546ba3 100644 --- a/gdplib/toy_problem/README.md +++ b/gdplib/toy_problem/README.md @@ -1,11 +1,15 @@ -## toy_problem.py +## david_linan.py -The toy problem is a simple optimization problem that involves two Boolean variables, two continuous variables, and a non-linear objective function. -The problem is formulated as a Generalized Disjunctive Programming (GDP) model. -The Boolean variables are associated with disjuncts that define the feasible regions of the continuous variables. +The toy problem is a simple optimization problem that involves two Boolean variables, two continuous variables, and a non-linear objective function. +The problem is formulated as a Generalized Disjunctive Programming (GDP) model. +The Boolean variables are associated with disjuncts that define the feasible regions of the continuous variables. The problem also includes logical constraints that ensure that only one Boolean variable is true at a time. The objective function is -0.9995999999999999 when the continuous variables are alpha = 0 (Y1[2]=True) and beta=-0.7 (Y2[3]=True). +The objective function of the model is orignated from Problem No. 6 of Gomez's paper and Linan introduced logic proposition, logic disjunctions and the following eqautinons as the constraints. + + ### References + [1] Liñán, D. A., & Ricardez-Sandoval, L. A. (2023). A Benders decomposition framework for the optimization of disjunctive superstructures with ordered discrete decisions. AIChE Journal, 69(5), e18008. https://doi.org/10.1002/aic.18008 [2] Gomez, S., & Levy, A. V. (1982). The tunnelling method for solving the constrained global optimization problem with several non-connected feasible regions. In Numerical Analysis: Proceedings of the Third IIMAS Workshop Held at Cocoyoc, Mexico, January 1981 (pp. 34-47). Springer Berlin Heidelberg. https://doi.org/10.1007/BFb0092958 diff --git a/gdplib/toy_problem/toy_problem.py b/gdplib/toy_problem/david_linan.py similarity index 100% rename from gdplib/toy_problem/toy_problem.py rename to gdplib/toy_problem/david_linan.py From 6f33c5203ce0dfb3e37a250cd501abf9391c14b9 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Fri, 16 Aug 2024 15:03:15 -0400 Subject: [PATCH 50/79] black formatted --- gdplib/toy_problem/david_linan.py | 92 ++++++++++++++++++++----------- 1 file changed, 59 insertions(+), 33 deletions(-) diff --git a/gdplib/toy_problem/david_linan.py b/gdplib/toy_problem/david_linan.py index 79ec98c..a4e0ec9 100644 --- a/gdplib/toy_problem/david_linan.py +++ b/gdplib/toy_problem/david_linan.py @@ -15,6 +15,7 @@ import pyomo.environ as pyo from pyomo.gdp import Disjunct, Disjunction + def build_model(): """ Build the toy problem model @@ -29,18 +30,21 @@ def build_model(): m = pyo.ConcreteModel() # Sets - m.set1 = pyo.RangeSet(1,5,doc= "set of first group of Boolean variables") - m.set2 = pyo.RangeSet(1,5,doc= "set of second group of Boolean variables") + m.set1 = pyo.RangeSet(1, 5, doc="set of first group of Boolean variables") + m.set2 = pyo.RangeSet(1, 5, doc="set of second group of Boolean variables") - m.sub1 = pyo.Set(initialize=[3],within=m.set1) + m.sub1 = pyo.Set(initialize=[3], within=m.set1) # Variables - m.Y1 = pyo.BooleanVar(m.set1,doc="Boolean variable associated to set 1") - m.Y2 = pyo.BooleanVar(m.set2,doc="Boolean variable associated to set 2") - - m.alpha = pyo.Var(within=pyo.Reals, bounds=(-0.1,0.4), doc="continuous variable alpha") - m.beta = pyo.Var(within=pyo.Reals, bounds=(-0.9,-0.5), doc="continuous variable beta") + m.Y1 = pyo.BooleanVar(m.set1, doc="Boolean variable associated to set 1") + m.Y2 = pyo.BooleanVar(m.set2, doc="Boolean variable associated to set 2") + m.alpha = pyo.Var( + within=pyo.Reals, bounds=(-0.1, 0.4), doc="continuous variable alpha" + ) + m.beta = pyo.Var( + within=pyo.Reals, bounds=(-0.9, -0.5), doc="continuous variable beta" + ) # Objective Function def obj_fun(m): @@ -57,11 +61,19 @@ def obj_fun(m): Pyomo.Objective Build the objective function of the toy problem """ - return 4*(pow(m.alpha,2))-2.1*(pow(m.alpha,4))+(1/3)*(pow(m.alpha,6))+m.alpha*m.beta-4*(pow(m.beta,2))+4*(pow(m.beta,4)) - m.obj=pyo.Objective(rule=obj_fun,sense=pyo.minimize, doc="Objective function") + return ( + 4 * (pow(m.alpha, 2)) + - 2.1 * (pow(m.alpha, 4)) + + (1 / 3) * (pow(m.alpha, 6)) + + m.alpha * m.beta + - 4 * (pow(m.beta, 2)) + + 4 * (pow(m.beta, 4)) + ) + + m.obj = pyo.Objective(rule=obj_fun, sense=pyo.minimize, doc="Objective function") # First Disjunction - def build_disjuncts1(m,set1): #Disjuncts for first Boolean variable + def build_disjuncts1(m, set1): # Disjuncts for first Boolean variable """ Build disjuncts for the first Boolean variable @@ -72,6 +84,7 @@ def build_disjuncts1(m,set1): #Disjuncts for first Boolean variable set1 : RangeSet Set of first group of Boolean variables """ + def constraint1(m): """_summary_ @@ -85,13 +98,17 @@ def constraint1(m): Pyomo.Constraint Constraint that defines the value of alpha for each disjunct """ - return m.model().alpha==-0.1+0.1*(set1-1) #.model() is required when writing constraints inside disjuncts - m.constraint1=pyo.Constraint(rule=constraint1) - - m.Y1_disjunct=Disjunct(m.set1,rule=build_disjuncts1, doc="each disjunct is defined over set 1") + return m.model().alpha == -0.1 + 0.1 * ( + set1 - 1 + ) # .model() is required when writing constraints inside disjuncts + + m.constraint1 = pyo.Constraint(rule=constraint1) + m.Y1_disjunct = Disjunct( + m.set1, rule=build_disjuncts1, doc="each disjunct is defined over set 1" + ) - def Disjunction1(m): # Disjunction for first Boolean variable + def Disjunction1(m): # Disjunction for first Boolean variable """ Disjunction for first Boolean variable @@ -106,14 +123,15 @@ def Disjunction1(m): # Disjunction for first Boolean variable Build the disjunction for the first Boolean variable set """ return [m.Y1_disjunct[j] for j in m.set1] - m.Disjunction1=Disjunction(rule=Disjunction1,xor=False) + + m.Disjunction1 = Disjunction(rule=Disjunction1, xor=False) # Associate boolean variables to disjuncts for n1 in m.set1: m.Y1[n1].associate_binary_var(m.Y1_disjunct[n1].indicator_var) # Second disjunction - def build_disjuncts2(m,set2): # Disjuncts for second Boolean variable + def build_disjuncts2(m, set2): # Disjuncts for second Boolean variable """ Build disjuncts for the second Boolean variable @@ -124,6 +142,7 @@ def build_disjuncts2(m,set2): # Disjuncts for second Boolean variable set2 : RangeSet Set of second group of Boolean variables """ + def constraint2(m): """_summary_ @@ -137,13 +156,17 @@ def constraint2(m): Pyomo.Constraint Constraint that defines the value of beta for each disjunct """ - return m.model().beta==-0.9+0.1*(set2-1) #.model() is required when writing constraints inside disjuncts - m.constraint2=pyo.Constraint(rule=constraint2) - - m.Y2_disjunct=Disjunct(m.set2,rule=build_disjuncts2,doc="each disjunct is defined over set 2") + return m.model().beta == -0.9 + 0.1 * ( + set2 - 1 + ) # .model() is required when writing constraints inside disjuncts + + m.constraint2 = pyo.Constraint(rule=constraint2) + m.Y2_disjunct = Disjunct( + m.set2, rule=build_disjuncts2, doc="each disjunct is defined over set 2" + ) - def Disjunction2(m): # Disjunction for first Boolean variable + def Disjunction2(m): # Disjunction for first Boolean variable """ Disjunction for second Boolean variable @@ -158,14 +181,13 @@ def Disjunction2(m): # Disjunction for first Boolean variable Build the disjunction for the second Boolean variable set """ return [m.Y2_disjunct[j] for j in m.set2] - m.Disjunction2=Disjunction(rule=Disjunction2,xor=False) + m.Disjunction2 = Disjunction(rule=Disjunction2, xor=False) - #Associate boolean variables to disjuncts + # Associate boolean variables to disjuncts for n2 in m.set2: m.Y2[n2].associate_binary_var(m.Y2_disjunct[n2].indicator_var) - # Logical constraints # Constraint that allow to apply the reformulation over Y1 @@ -183,8 +205,9 @@ def select_one_Y1(m): Pyomo.LogicalConstraint Logical constraint that make Y1 to be true for only one element """ - return pyo.exactly(1,m.Y1) - m.oneY1=pyo.LogicalConstraint(rule=select_one_Y1) + return pyo.exactly(1, m.Y1) + + m.oneY1 = pyo.LogicalConstraint(rule=select_one_Y1) # Constraint that allow to apply the reformulation over Y2 def select_one_Y2(m): @@ -201,8 +224,9 @@ def select_one_Y2(m): Pyomo.LogicalConstraint Logical constraint that make Y2 to be true for only one element """ - return pyo.exactly(1,m.Y2) - m.oneY2=pyo.LogicalConstraint(rule=select_one_Y2) + return pyo.exactly(1, m.Y2) + + m.oneY2 = pyo.LogicalConstraint(rule=select_one_Y2) # Constraint that define an infeasible region with respect to Boolean variables @@ -221,14 +245,16 @@ def infeasR_rule(m): Logical constraint that defines an infeasible region on Y1[3] """ return pyo.land([pyo.lnot(m.Y1[j]) for j in m.sub1]) - m.infeasR=pyo.LogicalConstraint(rule=infeasR_rule) + + m.infeasR = pyo.LogicalConstraint(rule=infeasR_rule) return m + if __name__ == "__main__": m = build_model() pyo.TransformationFactory('gdp.bigm').apply_to(m) solver = pyo.SolverFactory('gams') solver.solve(m, solver='baron', tee=True) - print("Solution: alpha=",pyo.value(m.alpha)," beta=",pyo.value(m.beta)) - print("Objective function value: ",pyo.value(m.obj)) + print("Solution: alpha=", pyo.value(m.alpha), " beta=", pyo.value(m.beta)) + print("Objective function value: ", pyo.value(m.obj)) From 5e6b0b3b17d63e72a4192855773eb38416145528 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Fri, 16 Aug 2024 15:05:28 -0400 Subject: [PATCH 51/79] black formatted --- gdplib/toy_problem/david_linan.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gdplib/toy_problem/david_linan.py b/gdplib/toy_problem/david_linan.py index a4e0ec9..d8a2a32 100644 --- a/gdplib/toy_problem/david_linan.py +++ b/gdplib/toy_problem/david_linan.py @@ -253,8 +253,8 @@ def infeasR_rule(m): if __name__ == "__main__": m = build_model() - pyo.TransformationFactory('gdp.bigm').apply_to(m) - solver = pyo.SolverFactory('gams') - solver.solve(m, solver='baron', tee=True) + pyo.TransformationFactory("gdp.bigm").apply_to(m) + solver = pyo.SolverFactory("gams") + solver.solve(m, solver="baron", tee=True) print("Solution: alpha=", pyo.value(m.alpha), " beta=", pyo.value(m.beta)) print("Objective function value: ", pyo.value(m.obj)) From ef03ad1ada1b62dae11a768cf5b3e48aa3eb5b34 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Fri, 16 Aug 2024 15:29:31 -0400 Subject: [PATCH 52/79] Changed the directory --- gdplib/{toy_problem => david_linan}/README.md | 0 gdplib/{toy_problem => david_linan}/david_linan.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename gdplib/{toy_problem => david_linan}/README.md (100%) rename gdplib/{toy_problem => david_linan}/david_linan.py (99%) diff --git a/gdplib/toy_problem/README.md b/gdplib/david_linan/README.md similarity index 100% rename from gdplib/toy_problem/README.md rename to gdplib/david_linan/README.md diff --git a/gdplib/toy_problem/david_linan.py b/gdplib/david_linan/david_linan.py similarity index 99% rename from gdplib/toy_problem/david_linan.py rename to gdplib/david_linan/david_linan.py index d8a2a32..2f1c9d8 100644 --- a/gdplib/toy_problem/david_linan.py +++ b/gdplib/david_linan/david_linan.py @@ -1,5 +1,5 @@ """ -toy_problem.py +david_linan.py The toy problem is a simple optimization problem that involves two Boolean variables, two continuous variables, and a non-linear objective function. The problem is formulated as a Generalized Disjunctive Programming (GDP) model. From 20a5b971ca0e254da4aabf73c3e8350f4851c0a8 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Fri, 16 Aug 2024 15:34:02 -0400 Subject: [PATCH 53/79] black format --- gdplib/david_linan/david_linan.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gdplib/david_linan/david_linan.py b/gdplib/david_linan/david_linan.py index 2f1c9d8..e4059ad 100644 --- a/gdplib/david_linan/david_linan.py +++ b/gdplib/david_linan/david_linan.py @@ -12,6 +12,7 @@ [1] Liñán, D. A., & Ricardez-Sandoval, L. A. (2023). A Benders decomposition framework for the optimization of disjunctive superstructures with ordered discrete decisions. AIChE Journal, 69(5), e18008. https://doi.org/10.1002/aic.18008 [2] Gomez, S., & Levy, A. V. (1982). The tunnelling method for solving the constrained global optimization problem with several non-connected feasible regions. In Numerical Analysis: Proceedings of the Third IIMAS Workshop Held at Cocoyoc, Mexico, January 1981 (pp. 34-47). Springer Berlin Heidelberg. https://doi.org/10.1007/BFb0092958 """ + import pyomo.environ as pyo from pyomo.gdp import Disjunct, Disjunction From 598c5df8f744d0b444095334f0d49f27afb937ca Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Fri, 16 Aug 2024 15:36:08 -0400 Subject: [PATCH 54/79] fixed typo --- gdplib/david_linan/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gdplib/david_linan/README.md b/gdplib/david_linan/README.md index a546ba3..cfc8f9c 100644 --- a/gdplib/david_linan/README.md +++ b/gdplib/david_linan/README.md @@ -6,8 +6,7 @@ The Boolean variables are associated with disjuncts that define the feasible reg The problem also includes logical constraints that ensure that only one Boolean variable is true at a time. The objective function is -0.9995999999999999 when the continuous variables are alpha = 0 (Y1[2]=True) and beta=-0.7 (Y2[3]=True). -The objective function of the model is orignated from Problem No. 6 of Gomez's paper and Linan introduced logic proposition, logic disjunctions and the following eqautinons as the constraints. - +The objective function of the model is originated from Problem No. 6 of Gomez's paper and Linan introduced logic proposition, logic disjunctions and the following eqautinons as the constraints. ### References From 7a05fb033de74f0e962286231b44207960d0d369 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Fri, 16 Aug 2024 15:51:56 -0400 Subject: [PATCH 55/79] Added README.md for gdp_small_batch.py --- gdplib/small_batch/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 gdplib/small_batch/README.md diff --git a/gdplib/small_batch/README.md b/gdplib/small_batch/README.md new file mode 100644 index 0000000..a583c06 --- /dev/null +++ b/gdplib/small_batch/README.md @@ -0,0 +1,15 @@ +## gdp_small_batch.py + +The gdp_small_batch.py module contains the GDP model for the small batch problem based on the Kocis and Grossmann (1988) paper. + +The problem is based on the Example 4 of the paper. + +The objective is to minimize the investment cost of the batch units. + +The solution is 167427.65711. + +### References + +[1] Kocis, G. R.; Grossmann, I. E. Global Optimization of Nonconvex Mixed-Integer Nonlinear Programming (MINLP) Problems in Process Synthesis. Ind. Eng. Chem. Res. 1988, 27 (8), 1407-1421. https://doi.org/10.1021/ie00080a013 + +[2] Ovalle, D., Liñán, D. A., Lee, A., Gómez, J. M., Ricardez-Sandoval, L., Grossmann, I. E., & Neira, D. E. B. (2024). Logic-Based Discrete-Steepest Descent: A Solution Method for Process Synthesis Generalized Disjunctive Programs. arXiv preprint arXiv:2405.05358. https://doi.org/10.48550/arXiv.2405.05358 From d2639f9aa829026fcff91db9abe8e405f3ea15e5 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Sat, 17 Aug 2024 12:19:32 -0400 Subject: [PATCH 56/79] update license --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index d8f2a15..6d5b46a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2020, Ignacio Grossmann Research Group +Copyright (c) 2020, SECQUOIA Research Group All rights reserved. Redistribution and use in source and binary forms, with or without From 493dc9b7e8f08d2f4527b1e005df0b4cad6f24ea Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Mon, 19 Aug 2024 12:18:21 -0400 Subject: [PATCH 57/79] Fixed the References. --- gdplib/small_batch/README.md | 2 +- gdplib/small_batch/gdp_small_batch.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gdplib/small_batch/README.md b/gdplib/small_batch/README.md index a583c06..837d76e 100644 --- a/gdplib/small_batch/README.md +++ b/gdplib/small_batch/README.md @@ -12,4 +12,4 @@ The solution is 167427.65711. [1] Kocis, G. R.; Grossmann, I. E. Global Optimization of Nonconvex Mixed-Integer Nonlinear Programming (MINLP) Problems in Process Synthesis. Ind. Eng. Chem. Res. 1988, 27 (8), 1407-1421. https://doi.org/10.1021/ie00080a013 -[2] Ovalle, D., Liñán, D. A., Lee, A., Gómez, J. M., Ricardez-Sandoval, L., Grossmann, I. E., & Neira, D. E. B. (2024). Logic-Based Discrete-Steepest Descent: A Solution Method for Process Synthesis Generalized Disjunctive Programs. arXiv preprint arXiv:2405.05358. https://doi.org/10.48550/arXiv.2405.05358 +[2] Ovalle, D., Liñán, D. A., Lee, A., Gómez, J. M., Ricardez-Sandoval, L., Grossmann, I. E., & Bernal Neira, D. E. (2024). Logic-Based Discrete-Steepest Descent: A Solution Method for Process Synthesis Generalized Disjunctive Programs. arXiv preprint arXiv:2405.05358. https://doi.org/10.48550/arXiv.2405.05358 diff --git a/gdplib/small_batch/gdp_small_batch.py b/gdplib/small_batch/gdp_small_batch.py index 67f7467..b7d2b48 100644 --- a/gdplib/small_batch/gdp_small_batch.py +++ b/gdplib/small_batch/gdp_small_batch.py @@ -7,7 +7,7 @@ References ---------- [1] Kocis, G. R.; Grossmann, I. E. Global Optimization of Nonconvex Mixed-Integer Nonlinear Programming (MINLP) Problems in Process Synthesis. Ind. Eng. Chem. Res. 1988, 27 (8), 1407-1421. https://doi.org/10.1021/ie00080a013 -[2] Ovalle, D., Liñán, D. A., Lee, A., Gómez, J. M., Ricardez-Sandoval, L., Grossmann, I. E., & Neira, D. E. B. (2024). Logic-Based Discrete-Steepest Descent: A Solution Method for Process Synthesis Generalized Disjunctive Programs. arXiv preprint arXiv:2405.05358. https://doi.org/10.48550/arXiv.2405.05358 +[2] Ovalle, D., Liñán, D. A., Lee, A., Gómez, J. M., Ricardez-Sandoval, L., Grossmann, I. E., & Bernal Neira, D. E. (2024). Logic-Based Discrete-Steepest Descent: A Solution Method for Process Synthesis Generalized Disjunctive Programs. arXiv preprint arXiv:2405.05358. https://doi.org/10.48550/arXiv.2405.05358 """ import os From ffb4ef6d936173fc7762bf57eb3e4302f004f88a Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Mon, 19 Aug 2024 13:20:48 -0400 Subject: [PATCH 58/79] Update toy problem file name and add additional documentation of problem --- .../ ex1_linan_2023.py} | 8 +++++--- gdplib/ ex1_linan_2023/README.md | 14 ++++++++++++++ gdplib/david_linan/README.md | 14 -------------- 3 files changed, 19 insertions(+), 17 deletions(-) rename gdplib/{david_linan/david_linan.py => ex1_linan_2023/ ex1_linan_2023.py} (93%) create mode 100644 gdplib/ ex1_linan_2023/README.md delete mode 100644 gdplib/david_linan/README.md diff --git a/gdplib/david_linan/david_linan.py b/gdplib/ ex1_linan_2023/ ex1_linan_2023.py similarity index 93% rename from gdplib/david_linan/david_linan.py rename to gdplib/ ex1_linan_2023/ ex1_linan_2023.py index e4059ad..b48a31b 100644 --- a/gdplib/david_linan/david_linan.py +++ b/gdplib/ ex1_linan_2023/ ex1_linan_2023.py @@ -1,10 +1,12 @@ """ -david_linan.py +ex1_linan_2023.py: Toy problem from Liñán and Ricardez-Sandoval (2023) [1] -The toy problem is a simple optimization problem that involves two Boolean variables, two continuous variables, and a non-linear objective function. +TThe ex1_linan.py file is a simple optimization problem that involves two Boolean variables, two continuous variables, and a nonlinear objective function. The problem is formulated as a Generalized Disjunctive Programming (GDP) model. The Boolean variables are associated with disjuncts that define the feasible regions of the continuous variables. -The problem also includes logical constraints that ensure that only one Boolean variable is true at a time. +The problem includes logical constraints that ensure that only one Boolean variable is true at a time. +Additionally, there are two disjunctions, one for each Boolean variable, where only one disjunct in each disjunction must be true. +A specific logical constraint also enforces that Y1[3] must be false, making this particular disjunct infeasible. The objective function is -0.9995999999999999 when the continuous variables are alpha = 0 (Y1[2]=True) and beta=-0.7 (Y2[3]=True). References diff --git a/gdplib/ ex1_linan_2023/README.md b/gdplib/ ex1_linan_2023/README.md new file mode 100644 index 0000000..781bbec --- /dev/null +++ b/gdplib/ ex1_linan_2023/README.md @@ -0,0 +1,14 @@ +## ex1_linan.py + +The `ex1_linan.py` file is a simple optimization problem that involves two Boolean variables, two continuous variables, and a nonlinear objective function. The problem is formulated as a Generalized Disjunctive Programming (GDP) model. + +The Boolean variables are associated with disjuncts that define the feasible regions of the continuous variables. The problem also includes logical constraints that ensure that only one Boolean variable is true at a time. Additionally, there are two disjunctions, one for each Boolean variable, where only one disjunct in each disjunction must be true. A specific logical constraint also enforces that `Y1[3]` must be false, making this particular disjunct infeasible. + +The objective function is -0.9995999999999999 when the continuous variables are alpha = 0 (`Y1[2]=True`) and beta=-0.7 (`Y2[3]=True`). + +The objective function originates from Problem No. 6 of Gomez's paper, and Liñán introduced logical propositions, logical disjunctions, and the following equations as constraints. + +### References + +[1] Liñán, D. A., & Ricardez-Sandoval, L. A. (2023). A Benders decomposition framework for the optimization of disjunctive superstructures with ordered discrete decisions. AIChE Journal, 69(5), e18008. https://doi.org/10.1002/aic.18008 +[2] Gomez, S., & Levy, A. V. (1982). The tunnelling method for solving the constrained global optimization problem with several non-connected feasible regions. In Numerical Analysis: Proceedings of the Third IIMAS Workshop Held at Cocoyoc, Mexico, January 1981 (pp. 34-47). Springer Berlin Heidelberg. https://doi.org/10.1007/BFb0092958 diff --git a/gdplib/david_linan/README.md b/gdplib/david_linan/README.md deleted file mode 100644 index cfc8f9c..0000000 --- a/gdplib/david_linan/README.md +++ /dev/null @@ -1,14 +0,0 @@ -## david_linan.py - -The toy problem is a simple optimization problem that involves two Boolean variables, two continuous variables, and a non-linear objective function. -The problem is formulated as a Generalized Disjunctive Programming (GDP) model. -The Boolean variables are associated with disjuncts that define the feasible regions of the continuous variables. -The problem also includes logical constraints that ensure that only one Boolean variable is true at a time. -The objective function is -0.9995999999999999 when the continuous variables are alpha = 0 (Y1[2]=True) and beta=-0.7 (Y2[3]=True). - -The objective function of the model is originated from Problem No. 6 of Gomez's paper and Linan introduced logic proposition, logic disjunctions and the following eqautinons as the constraints. - -### References - -[1] Liñán, D. A., & Ricardez-Sandoval, L. A. (2023). A Benders decomposition framework for the optimization of disjunctive superstructures with ordered discrete decisions. AIChE Journal, 69(5), e18008. https://doi.org/10.1002/aic.18008 -[2] Gomez, S., & Levy, A. V. (1982). The tunnelling method for solving the constrained global optimization problem with several non-connected feasible regions. In Numerical Analysis: Proceedings of the Third IIMAS Workshop Held at Cocoyoc, Mexico, January 1981 (pp. 34-47). Springer Berlin Heidelberg. https://doi.org/10.1007/BFb0092958 From 09d78a74aeeb4753f202151bfe731f241e988524 Mon Sep 17 00:00:00 2001 From: Zedong Date: Mon, 19 Aug 2024 13:45:27 -0400 Subject: [PATCH 59/79] Update LICENSE Co-authored-by: David Bernal --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 6d5b46a..71a9fa0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2020, SECQUOIA Research Group +Copyright (c) 2024, SECQUOIA Research Group All rights reserved. Redistribution and use in source and binary forms, with or without From c45b4c21ca8e5c7fba597da5a750f229fe725497 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Mon, 19 Aug 2024 13:54:02 -0400 Subject: [PATCH 60/79] Changed the documentation. --- gdplib/ ex1_linan_2023/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gdplib/ ex1_linan_2023/README.md b/gdplib/ ex1_linan_2023/README.md index 781bbec..1802e16 100644 --- a/gdplib/ ex1_linan_2023/README.md +++ b/gdplib/ ex1_linan_2023/README.md @@ -1,6 +1,6 @@ -## ex1_linan.py +## Example 1 Problem of Liñán (2023) -The `ex1_linan.py` file is a simple optimization problem that involves two Boolean variables, two continuous variables, and a nonlinear objective function. The problem is formulated as a Generalized Disjunctive Programming (GDP) model. +The Example 1 Problem of Liñán (2023) is a simple optimization problem that involves two Boolean variables, two continuous variables, and a nonlinear objective function. The problem is formulated as a Generalized Disjunctive Programming (GDP) model. The Boolean variables are associated with disjuncts that define the feasible regions of the continuous variables. The problem also includes logical constraints that ensure that only one Boolean variable is true at a time. Additionally, there are two disjunctions, one for each Boolean variable, where only one disjunct in each disjunction must be true. A specific logical constraint also enforces that `Y1[3]` must be false, making this particular disjunct infeasible. From 0c77c9b63ccf60281480b4dcf55af1fdbabe14ef Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Mon, 19 Aug 2024 16:38:50 -0400 Subject: [PATCH 61/79] update HDA GDP model --- gdplib/hda/HDA_GDP_gdpopt.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gdplib/hda/HDA_GDP_gdpopt.py b/gdplib/hda/HDA_GDP_gdpopt.py index fba558a..59a4331 100644 --- a/gdplib/hda/HDA_GDP_gdpopt.py +++ b/gdplib/hda/HDA_GDP_gdpopt.py @@ -39,7 +39,7 @@ def HDA_model(): compressor coefficient compeff : float compressor efficiency - gam : float + cp_cv_ratio : float ratio of cp to cv abseff : float absorber tray efficiency @@ -150,7 +150,7 @@ def HDA_model(): m.alpha = Param(initialize=0.3665, doc="compressor coefficient") m.compeff = Param(initialize=0.750, doc="compressor efficiency") - m.gam = Param(initialize=1.300, doc="ratio of cp to cv") + m.cp_cv_ratio = Param(initialize=1.300, doc="ratio of cp to cv") m.abseff = Param(initialize=0.333, doc="absorber tray efficiency") m.disteff = Param(initialize=0.5000, doc="column tray efficiency") m.uflow = Param(initialize=50, doc="upper bound - flow logicals") @@ -1535,7 +1535,7 @@ def Compelec(_m, comp_): * m.f[stream] / 60.0 * (1.0 / m.compeff) - * (m.gam / (m.gam - 1.0)) + * (m.cp_cv_ratio / (m.cp_cv_ratio - 1.0)) for (comp1, stream) in m.icomp if comp_ == comp1 ) @@ -1547,7 +1547,7 @@ def Compelec(_m, comp_): def Ratio(_m, comp_): if comp == comp_: - return m.presrat[comp_] ** (m.gam / (m.gam - 1.0)) == sum( + return m.presrat[comp_] ** (m.cp_cv_ratio / (m.cp_cv_ratio - 1.0)) == sum( m.p[stream] for (comp1, stream) in m.ocomp if comp_ == comp1 ) / sum(m.p[stream] for (comp1, stream) in m.icomp if comp1 == comp_) return Constraint.Skip @@ -2642,11 +2642,11 @@ def Valcmb(_m, valve, compon): def Valt(_m, valve): return sum( - m.t[stream] / (m.p[stream] ** ((m.gam - 1.0) / m.gam)) + m.t[stream] / (m.p[stream] ** ((m.cp_cv_ratio - 1.0) / m.cp_cv_ratio)) for (valv, stream) in m.oval if valv == valve ) == sum( - m.t[stream] / (m.p[stream] ** ((m.gam - 1.0) / m.gam)) + m.t[stream] / (m.p[stream] ** ((m.cp_cv_ratio - 1.0) / m.cp_cv_ratio)) for (valv, stream) in m.ival if valv == valve ) From 29c0ee5e9a3d300ec76284587fc1e54f94a1c562 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Mon, 19 Aug 2024 16:41:19 -0400 Subject: [PATCH 62/79] black format --- gdplib/hda/HDA_GDP_gdpopt.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gdplib/hda/HDA_GDP_gdpopt.py b/gdplib/hda/HDA_GDP_gdpopt.py index 59a4331..a34d6db 100644 --- a/gdplib/hda/HDA_GDP_gdpopt.py +++ b/gdplib/hda/HDA_GDP_gdpopt.py @@ -1547,9 +1547,13 @@ def Compelec(_m, comp_): def Ratio(_m, comp_): if comp == comp_: - return m.presrat[comp_] ** (m.cp_cv_ratio / (m.cp_cv_ratio - 1.0)) == sum( + return m.presrat[comp_] ** ( + m.cp_cv_ratio / (m.cp_cv_ratio - 1.0) + ) == sum( m.p[stream] for (comp1, stream) in m.ocomp if comp_ == comp1 - ) / sum(m.p[stream] for (comp1, stream) in m.icomp if comp1 == comp_) + ) / sum( + m.p[stream] for (comp1, stream) in m.icomp if comp1 == comp_ + ) return Constraint.Skip b.ratio = Constraint( From 8ffbcfaeedff78db7630ef94579f0dc3c4e102e2 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 20 Aug 2024 10:15:31 -0400 Subject: [PATCH 63/79] Fixed the ex1_linan_2023 directory and Fixed the references, add init in linan --- gdplib/{ ex1_linan_2023 => ex1_linan_2023}/ ex1_linan_2023.py | 0 gdplib/{ ex1_linan_2023 => ex1_linan_2023}/README.md | 3 +++ gdplib/ex1_linan_2023/__init__.py | 3 +++ 3 files changed, 6 insertions(+) rename gdplib/{ ex1_linan_2023 => ex1_linan_2023}/ ex1_linan_2023.py (100%) rename gdplib/{ ex1_linan_2023 => ex1_linan_2023}/README.md (99%) create mode 100644 gdplib/ex1_linan_2023/__init__.py diff --git a/gdplib/ ex1_linan_2023/ ex1_linan_2023.py b/gdplib/ex1_linan_2023/ ex1_linan_2023.py similarity index 100% rename from gdplib/ ex1_linan_2023/ ex1_linan_2023.py rename to gdplib/ex1_linan_2023/ ex1_linan_2023.py diff --git a/gdplib/ ex1_linan_2023/README.md b/gdplib/ex1_linan_2023/README.md similarity index 99% rename from gdplib/ ex1_linan_2023/README.md rename to gdplib/ex1_linan_2023/README.md index 1802e16..d0e4468 100644 --- a/gdplib/ ex1_linan_2023/README.md +++ b/gdplib/ex1_linan_2023/README.md @@ -11,4 +11,7 @@ The objective function originates from Problem No. 6 of Gomez's paper, and Liñ ### References [1] Liñán, D. A., & Ricardez-Sandoval, L. A. (2023). A Benders decomposition framework for the optimization of disjunctive superstructures with ordered discrete decisions. AIChE Journal, 69(5), e18008. https://doi.org/10.1002/aic.18008 + + + [2] Gomez, S., & Levy, A. V. (1982). The tunnelling method for solving the constrained global optimization problem with several non-connected feasible regions. In Numerical Analysis: Proceedings of the Third IIMAS Workshop Held at Cocoyoc, Mexico, January 1981 (pp. 34-47). Springer Berlin Heidelberg. https://doi.org/10.1007/BFb0092958 diff --git a/gdplib/ex1_linan_2023/__init__.py b/gdplib/ex1_linan_2023/__init__.py new file mode 100644 index 0000000..8599367 --- /dev/null +++ b/gdplib/ex1_linan_2023/__init__.py @@ -0,0 +1,3 @@ +from .ex1_linan_2023 import build_model + +__all__ = ['build_model'] From 8c4317b6fdaf8bd79809b12542008462b2be9442 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 20 Aug 2024 10:18:56 -0400 Subject: [PATCH 64/79] fixed __init__.py --- gdplib/__init__.py | 1 + gdplib/ex1_linan_2023/README.md | 2 -- gdplib/ex1_linan_2023/{ ex1_linan_2023.py => ex1_linan_2023.py} | 0 3 files changed, 1 insertion(+), 2 deletions(-) rename gdplib/ex1_linan_2023/{ ex1_linan_2023.py => ex1_linan_2023.py} (100%) diff --git a/gdplib/__init__.py b/gdplib/__init__.py index 0d6fa2b..06c398e 100644 --- a/gdplib/__init__.py +++ b/gdplib/__init__.py @@ -13,3 +13,4 @@ import gdplib.disease_model import gdplib.med_term_purchasing import gdplib.syngas +import gdplib.ex1_linan_2023 \ No newline at end of file diff --git a/gdplib/ex1_linan_2023/README.md b/gdplib/ex1_linan_2023/README.md index d0e4468..354a7b1 100644 --- a/gdplib/ex1_linan_2023/README.md +++ b/gdplib/ex1_linan_2023/README.md @@ -12,6 +12,4 @@ The objective function originates from Problem No. 6 of Gomez's paper, and Liñ [1] Liñán, D. A., & Ricardez-Sandoval, L. A. (2023). A Benders decomposition framework for the optimization of disjunctive superstructures with ordered discrete decisions. AIChE Journal, 69(5), e18008. https://doi.org/10.1002/aic.18008 - - [2] Gomez, S., & Levy, A. V. (1982). The tunnelling method for solving the constrained global optimization problem with several non-connected feasible regions. In Numerical Analysis: Proceedings of the Third IIMAS Workshop Held at Cocoyoc, Mexico, January 1981 (pp. 34-47). Springer Berlin Heidelberg. https://doi.org/10.1007/BFb0092958 diff --git a/gdplib/ex1_linan_2023/ ex1_linan_2023.py b/gdplib/ex1_linan_2023/ex1_linan_2023.py similarity index 100% rename from gdplib/ex1_linan_2023/ ex1_linan_2023.py rename to gdplib/ex1_linan_2023/ex1_linan_2023.py From dcced4122cd7a274dd787e7148269bd4be87d8db Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 20 Aug 2024 10:25:37 -0400 Subject: [PATCH 65/79] trying to separate the README.md references line --- gdplib/ex1_linan_2023/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gdplib/ex1_linan_2023/README.md b/gdplib/ex1_linan_2023/README.md index 354a7b1..c8eed02 100644 --- a/gdplib/ex1_linan_2023/README.md +++ b/gdplib/ex1_linan_2023/README.md @@ -12,4 +12,8 @@ The objective function originates from Problem No. 6 of Gomez's paper, and Liñ [1] Liñán, D. A., & Ricardez-Sandoval, L. A. (2023). A Benders decomposition framework for the optimization of disjunctive superstructures with ordered discrete decisions. AIChE Journal, 69(5), e18008. https://doi.org/10.1002/aic.18008 +--- + [2] Gomez, S., & Levy, A. V. (1982). The tunnelling method for solving the constrained global optimization problem with several non-connected feasible regions. In Numerical Analysis: Proceedings of the Third IIMAS Workshop Held at Cocoyoc, Mexico, January 1981 (pp. 34-47). Springer Berlin Heidelberg. https://doi.org/10.1007/BFb0092958 + +--- From 4deb2c3bba3ebef52d35ae8ef06a577cbcd94aaa Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 20 Aug 2024 10:42:05 -0400 Subject: [PATCH 66/79] Update README.md references and fix ex1_linan_2023 documentation --- gdplib/ex1_linan_2023/README.md | 7 ++----- gdplib/ex1_linan_2023/ex1_linan_2023.py | 3 ++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/gdplib/ex1_linan_2023/README.md b/gdplib/ex1_linan_2023/README.md index c8eed02..b071c04 100644 --- a/gdplib/ex1_linan_2023/README.md +++ b/gdplib/ex1_linan_2023/README.md @@ -10,10 +10,7 @@ The objective function originates from Problem No. 6 of Gomez's paper, and Liñ ### References -[1] Liñán, D. A., & Ricardez-Sandoval, L. A. (2023). A Benders decomposition framework for the optimization of disjunctive superstructures with ordered discrete decisions. AIChE Journal, 69(5), e18008. https://doi.org/10.1002/aic.18008 - ---- - -[2] Gomez, S., & Levy, A. V. (1982). The tunnelling method for solving the constrained global optimization problem with several non-connected feasible regions. In Numerical Analysis: Proceedings of the Third IIMAS Workshop Held at Cocoyoc, Mexico, January 1981 (pp. 34-47). Springer Berlin Heidelberg. https://doi.org/10.1007/BFb0092958 +1. Liñán, D. A., & Ricardez-Sandoval, L. A. (2023). A Benders decomposition framework for the optimization of disjunctive superstructures with ordered discrete decisions. AIChE Journal, 69(5), e18008. https://doi.org/10.1002/aic.18008 +2. Gomez, S., & Levy, A. V. (1982). The tunnelling method for solving the constrained global optimization problem with several non-connected feasible regions. In Numerical Analysis: Proceedings of the Third IIMAS Workshop Held at Cocoyoc, Mexico, January 1981 (pp. 34-47). Springer Berlin Heidelberg. https://doi.org/10.1007/BFb0092958 --- diff --git a/gdplib/ex1_linan_2023/ex1_linan_2023.py b/gdplib/ex1_linan_2023/ex1_linan_2023.py index b48a31b..ad0e439 100644 --- a/gdplib/ex1_linan_2023/ex1_linan_2023.py +++ b/gdplib/ex1_linan_2023/ex1_linan_2023.py @@ -1,7 +1,7 @@ """ ex1_linan_2023.py: Toy problem from Liñán and Ricardez-Sandoval (2023) [1] -TThe ex1_linan.py file is a simple optimization problem that involves two Boolean variables, two continuous variables, and a nonlinear objective function. +The ex1_linan.py file is a simple optimization problem that involves two Boolean variables, two continuous variables, and a nonlinear objective function. The problem is formulated as a Generalized Disjunctive Programming (GDP) model. The Boolean variables are associated with disjuncts that define the feasible regions of the continuous variables. The problem includes logical constraints that ensure that only one Boolean variable is true at a time. @@ -12,6 +12,7 @@ References ---------- [1] Liñán, D. A., & Ricardez-Sandoval, L. A. (2023). A Benders decomposition framework for the optimization of disjunctive superstructures with ordered discrete decisions. AIChE Journal, 69(5), e18008. https://doi.org/10.1002/aic.18008 + [2] Gomez, S., & Levy, A. V. (1982). The tunnelling method for solving the constrained global optimization problem with several non-connected feasible regions. In Numerical Analysis: Proceedings of the Third IIMAS Workshop Held at Cocoyoc, Mexico, January 1981 (pp. 34-47). Springer Berlin Heidelberg. https://doi.org/10.1007/BFb0092958 """ From 1e2e07a539f350eb21aa3087d5a9e807f73df3ca Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 20 Aug 2024 10:52:01 -0400 Subject: [PATCH 67/79] add small batch init.py --- gdplib/small_batch/__init__.py | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 gdplib/small_batch/__init__.py diff --git a/gdplib/small_batch/__init__.py b/gdplib/small_batch/__init__.py new file mode 100644 index 0000000..0a6b1ff --- /dev/null +++ b/gdplib/small_batch/__init__.py @@ -0,0 +1,3 @@ +from .gdp_small_batch import build_model + +__all__ = ['build_model'] From ddf3a2853888ee1c6873490c14e1d1b04be9f0ed Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 20 Aug 2024 10:58:08 -0400 Subject: [PATCH 68/79] change the function def build_small_batch into build_model --- gdplib/small_batch/gdp_small_batch.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gdplib/small_batch/gdp_small_batch.py b/gdplib/small_batch/gdp_small_batch.py index b7d2b48..afc5ef3 100644 --- a/gdplib/small_batch/gdp_small_batch.py +++ b/gdplib/small_batch/gdp_small_batch.py @@ -20,7 +20,7 @@ from pyomo.opt.base.solvers import SolverFactory -def build_small_batch(): +def build_model(): """ Build the GDP model for the small batch problem. @@ -32,6 +32,7 @@ def build_small_batch(): References ---------- [1] Kocis, G. R.; Grossmann, I. E. (1988). Global Optimization of Nonconvex Mixed-Integer Nonlinear Programming (MINLP) Problems in Process Synthesis. Ind. Eng. Chem. Res., 27(8), 1407-1421. https://doi.org/10.1021/ie00080a013 + [2] Ovalle, D., Liñán, D. A., Lee, A., Gómez, J. M., Ricardez-Sandoval, L., Grossmann, I. E., & Neira, D. E. B. (2024). Logic-Based Discrete-Steepest Descent: A Solution Method for Process Synthesis Generalized Disjunctive Programs. arXiv preprint arXiv:2405.05358. https://doi.org/10.48550/arXiv.2405.05358 """ NK = 3 @@ -426,7 +427,7 @@ def obj_rule(m): if __name__ == "__main__": - m = build_small_batch() + m = build_model() pyo.TransformationFactory("core.logical_to_linear").apply_to(m) pyo.TransformationFactory("gdp.bigm").apply_to(m) pyo.SolverFactory("gams").solve( From 72245e403a9f5d878a018834e8e0d8b7b7f2b6a1 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 20 Aug 2024 10:58:24 -0400 Subject: [PATCH 69/79] Add small_batch module to gdplib --- gdplib/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gdplib/__init__.py b/gdplib/__init__.py index 06c398e..a43a065 100644 --- a/gdplib/__init__.py +++ b/gdplib/__init__.py @@ -13,4 +13,5 @@ import gdplib.disease_model import gdplib.med_term_purchasing import gdplib.syngas -import gdplib.ex1_linan_2023 \ No newline at end of file +import gdplib.ex1_linan_2023 +import gdplib.small_batch \ No newline at end of file From bf1f19a05f78c3a1a33fa59f1f28e522f39c04ed Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 20 Aug 2024 11:24:05 -0400 Subject: [PATCH 70/79] added cstr path way init py --- gdplib/__init__.py | 3 ++- gdplib/cstr/__init__.py | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 gdplib/cstr/__init__.py diff --git a/gdplib/__init__.py b/gdplib/__init__.py index a43a065..a49a6ab 100644 --- a/gdplib/__init__.py +++ b/gdplib/__init__.py @@ -14,4 +14,5 @@ import gdplib.med_term_purchasing import gdplib.syngas import gdplib.ex1_linan_2023 -import gdplib.small_batch \ No newline at end of file +import gdplib.small_batch +import gdplib.cstr \ No newline at end of file diff --git a/gdplib/cstr/__init__.py b/gdplib/cstr/__init__.py new file mode 100644 index 0000000..cf26769 --- /dev/null +++ b/gdplib/cstr/__init__.py @@ -0,0 +1,3 @@ +from .gdp_reactor import build_model + +__all__ = ['build_model'] From 079964dd0750e0146003403a4427c90629c29d6f Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 20 Aug 2024 11:24:26 -0400 Subject: [PATCH 71/79] change build_cstrs into build_model --- gdplib/cstr/gdp_reactor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gdplib/cstr/gdp_reactor.py b/gdplib/cstr/gdp_reactor.py index b357384..a405acf 100644 --- a/gdplib/cstr/gdp_reactor.py +++ b/gdplib/cstr/gdp_reactor.py @@ -17,7 +17,7 @@ from pyomo.opt.base.solvers import SolverFactory -def build_cstrs(NT: int = 5) -> pyo.ConcreteModel(): +def build_model(NT: int = 5) -> pyo.ConcreteModel(): """ Build the CSTR superstructure model of size NT. NT is the number of reactors in series. @@ -915,7 +915,7 @@ def obj_rule(m): if __name__ == "__main__": - m = build_cstrs() + m = build_model() pyo.TransformationFactory("core.logical_to_linear").apply_to(m) pyo.TransformationFactory("gdp.bigm").apply_to(m) pyo.SolverFactory("gams").solve( From bdd85d0bf1974d6de31deb1164826d42236d62f7 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 20 Aug 2024 11:25:42 -0400 Subject: [PATCH 72/79] reformed the README.md --- gdplib/ex1_linan_2023/README.md | 8 +++++--- gdplib/small_batch/README.md | 8 ++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/gdplib/ex1_linan_2023/README.md b/gdplib/ex1_linan_2023/README.md index b071c04..b290964 100644 --- a/gdplib/ex1_linan_2023/README.md +++ b/gdplib/ex1_linan_2023/README.md @@ -4,13 +4,15 @@ The Example 1 Problem of Liñán (2023) is a simple optimization problem that in The Boolean variables are associated with disjuncts that define the feasible regions of the continuous variables. The problem also includes logical constraints that ensure that only one Boolean variable is true at a time. Additionally, there are two disjunctions, one for each Boolean variable, where only one disjunct in each disjunction must be true. A specific logical constraint also enforces that `Y1[3]` must be false, making this particular disjunct infeasible. -The objective function is -0.9995999999999999 when the continuous variables are alpha = 0 (`Y1[2]=True`) and beta=-0.7 (`Y2[3]=True`). +The objective function is `-0.9995999999999999` when the continuous variables are alpha = 0 (`Y1[2]=True `) and beta=-0.7 (`Y2[3]=True`). The objective function originates from Problem No. 6 of Gomez's paper, and Liñán introduced logical propositions, logical disjunctions, and the following equations as constraints. ### References -1. Liñán, D. A., & Ricardez-Sandoval, L. A. (2023). A Benders decomposition framework for the optimization of disjunctive superstructures with ordered discrete decisions. AIChE Journal, 69(5), e18008. https://doi.org/10.1002/aic.18008 -2. Gomez, S., & Levy, A. V. (1982). The tunnelling method for solving the constrained global optimization problem with several non-connected feasible regions. In Numerical Analysis: Proceedings of the Third IIMAS Workshop Held at Cocoyoc, Mexico, January 1981 (pp. 34-47). Springer Berlin Heidelberg. https://doi.org/10.1007/BFb0092958 +> [1] Liñán, D. A., & Ricardez-Sandoval, L. A. (2023). A Benders decomposition framework for the optimization of disjunctive superstructures with ordered discrete decisions. AIChE Journal, 69(5), e18008. https://doi.org/10.1002/aic.18008 +> +> [2] Gomez, S., & Levy, A. V. (1982). The tunnelling method for solving the constrained global optimization problem with several non-connected feasible regions. In Numerical Analysis: Proceedings of the Third IIMAS Workshop Held at Cocoyoc, Mexico, January 1981 (pp. 34-47). Springer Berlin Heidelberg. https://doi.org/10.1007/BFb0092958 + --- diff --git a/gdplib/small_batch/README.md b/gdplib/small_batch/README.md index 837d76e..8ebc750 100644 --- a/gdplib/small_batch/README.md +++ b/gdplib/small_batch/README.md @@ -1,4 +1,4 @@ -## gdp_small_batch.py +## Small Batch Scheduling Problem The gdp_small_batch.py module contains the GDP model for the small batch problem based on the Kocis and Grossmann (1988) paper. @@ -10,6 +10,6 @@ The solution is 167427.65711. ### References -[1] Kocis, G. R.; Grossmann, I. E. Global Optimization of Nonconvex Mixed-Integer Nonlinear Programming (MINLP) Problems in Process Synthesis. Ind. Eng. Chem. Res. 1988, 27 (8), 1407-1421. https://doi.org/10.1021/ie00080a013 - -[2] Ovalle, D., Liñán, D. A., Lee, A., Gómez, J. M., Ricardez-Sandoval, L., Grossmann, I. E., & Bernal Neira, D. E. (2024). Logic-Based Discrete-Steepest Descent: A Solution Method for Process Synthesis Generalized Disjunctive Programs. arXiv preprint arXiv:2405.05358. https://doi.org/10.48550/arXiv.2405.05358 +> [1] Kocis, G. R.; Grossmann, I. E. Global Optimization of Nonconvex Mixed-Integer Nonlinear Programming (MINLP) Problems in Process Synthesis. Ind. Eng. Chem. Res. 1988, 27 (8), 1407-1421. https://doi.org/10.1021/ie00080a013 +> +> [2] Ovalle, D., Liñán, D. A., Lee, A., Gómez, J. M., Ricardez-Sandoval, L., Grossmann, I. E., & Bernal Neira, D. E. (2024). Logic-Based Discrete-Steepest Descent: A Solution Method for Process Synthesis Generalized Disjunctive Programs. arXiv preprint arXiv:2405.05358. https://doi.org/10.48550/arXiv.2405.05358 From 40f8ffea1ea857c28f6498a1688b768c24c05934 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 20 Aug 2024 11:58:05 -0400 Subject: [PATCH 73/79] black format on gdp_small_batch --- gdplib/small_batch/gdp_small_batch.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gdplib/small_batch/gdp_small_batch.py b/gdplib/small_batch/gdp_small_batch.py index afc5ef3..e2d434d 100644 --- a/gdplib/small_batch/gdp_small_batch.py +++ b/gdplib/small_batch/gdp_small_batch.py @@ -7,6 +7,7 @@ References ---------- [1] Kocis, G. R.; Grossmann, I. E. Global Optimization of Nonconvex Mixed-Integer Nonlinear Programming (MINLP) Problems in Process Synthesis. Ind. Eng. Chem. Res. 1988, 27 (8), 1407-1421. https://doi.org/10.1021/ie00080a013 + [2] Ovalle, D., Liñán, D. A., Lee, A., Gómez, J. M., Ricardez-Sandoval, L., Grossmann, I. E., & Bernal Neira, D. E. (2024). Logic-Based Discrete-Steepest Descent: A Solution Method for Process Synthesis Generalized Disjunctive Programs. arXiv preprint arXiv:2405.05358. https://doi.org/10.48550/arXiv.2405.05358 """ @@ -32,7 +33,7 @@ def build_model(): References ---------- [1] Kocis, G. R.; Grossmann, I. E. (1988). Global Optimization of Nonconvex Mixed-Integer Nonlinear Programming (MINLP) Problems in Process Synthesis. Ind. Eng. Chem. Res., 27(8), 1407-1421. https://doi.org/10.1021/ie00080a013 - + [2] Ovalle, D., Liñán, D. A., Lee, A., Gómez, J. M., Ricardez-Sandoval, L., Grossmann, I. E., & Neira, D. E. B. (2024). Logic-Based Discrete-Steepest Descent: A Solution Method for Process Synthesis Generalized Disjunctive Programs. arXiv preprint arXiv:2405.05358. https://doi.org/10.48550/arXiv.2405.05358 """ NK = 3 From d106403572fde527ae2dba112147f53ebd214070 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 20 Aug 2024 13:22:45 -0400 Subject: [PATCH 74/79] gdp_small_batch.py description renew --- gdplib/small_batch/gdp_small_batch.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gdplib/small_batch/gdp_small_batch.py b/gdplib/small_batch/gdp_small_batch.py index e2d434d..4b53191 100644 --- a/gdplib/small_batch/gdp_small_batch.py +++ b/gdplib/small_batch/gdp_small_batch.py @@ -9,6 +9,7 @@ [1] Kocis, G. R.; Grossmann, I. E. Global Optimization of Nonconvex Mixed-Integer Nonlinear Programming (MINLP) Problems in Process Synthesis. Ind. Eng. Chem. Res. 1988, 27 (8), 1407-1421. https://doi.org/10.1021/ie00080a013 [2] Ovalle, D., Liñán, D. A., Lee, A., Gómez, J. M., Ricardez-Sandoval, L., Grossmann, I. E., & Bernal Neira, D. E. (2024). Logic-Based Discrete-Steepest Descent: A Solution Method for Process Synthesis Generalized Disjunctive Programs. arXiv preprint arXiv:2405.05358. https://doi.org/10.48550/arXiv.2405.05358 + """ import os From eaa872e58c8ce1d89a9dd6ba926f69c48270cacc Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 20 Aug 2024 13:24:27 -0400 Subject: [PATCH 75/79] black format init.py --- gdplib/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gdplib/__init__.py b/gdplib/__init__.py index a49a6ab..c45c052 100644 --- a/gdplib/__init__.py +++ b/gdplib/__init__.py @@ -15,4 +15,4 @@ import gdplib.syngas import gdplib.ex1_linan_2023 import gdplib.small_batch -import gdplib.cstr \ No newline at end of file +import gdplib.cstr From 69236fd9ee48467f882808683a4d69f85a435f04 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Sat, 24 Aug 2024 02:21:07 -0400 Subject: [PATCH 76/79] Commented out associate_binary_var --- gdplib/ex1_linan_2023/ex1_linan_2023.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gdplib/ex1_linan_2023/ex1_linan_2023.py b/gdplib/ex1_linan_2023/ex1_linan_2023.py index ad0e439..8fd3ef0 100644 --- a/gdplib/ex1_linan_2023/ex1_linan_2023.py +++ b/gdplib/ex1_linan_2023/ex1_linan_2023.py @@ -130,9 +130,9 @@ def Disjunction1(m): # Disjunction for first Boolean variable m.Disjunction1 = Disjunction(rule=Disjunction1, xor=False) - # Associate boolean variables to disjuncts - for n1 in m.set1: - m.Y1[n1].associate_binary_var(m.Y1_disjunct[n1].indicator_var) + # # Associate boolean variables to disjuncts + # for n1 in m.set1: + # m.Y1[n1].associate_binary_var(m.Y1_disjunct[n1].indicator_var) # Second disjunction def build_disjuncts2(m, set2): # Disjuncts for second Boolean variable @@ -188,9 +188,9 @@ def Disjunction2(m): # Disjunction for first Boolean variable m.Disjunction2 = Disjunction(rule=Disjunction2, xor=False) - # Associate boolean variables to disjuncts - for n2 in m.set2: - m.Y2[n2].associate_binary_var(m.Y2_disjunct[n2].indicator_var) + # # Associate boolean variables to disjuncts + # for n2 in m.set2: + # m.Y2[n2].associate_binary_var(m.Y2_disjunct[n2].indicator_var) # Logical constraints From ff0de95043b2f22eb5421d6f5ab19e48eae07311 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Sat, 24 Aug 2024 02:23:23 -0400 Subject: [PATCH 77/79] Remove m.Y1 and m.Y2 --- gdplib/ex1_linan_2023/ex1_linan_2023.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/gdplib/ex1_linan_2023/ex1_linan_2023.py b/gdplib/ex1_linan_2023/ex1_linan_2023.py index 8fd3ef0..4fd9114 100644 --- a/gdplib/ex1_linan_2023/ex1_linan_2023.py +++ b/gdplib/ex1_linan_2023/ex1_linan_2023.py @@ -40,9 +40,6 @@ def build_model(): m.sub1 = pyo.Set(initialize=[3], within=m.set1) # Variables - m.Y1 = pyo.BooleanVar(m.set1, doc="Boolean variable associated to set 1") - m.Y2 = pyo.BooleanVar(m.set2, doc="Boolean variable associated to set 2") - m.alpha = pyo.Var( within=pyo.Reals, bounds=(-0.1, 0.4), doc="continuous variable alpha" ) @@ -209,7 +206,7 @@ def select_one_Y1(m): Pyomo.LogicalConstraint Logical constraint that make Y1 to be true for only one element """ - return pyo.exactly(1, m.Y1) + return pyo.exactly(1, [m.Y1_disjunct[n].indicator_var for n in m.set1]) m.oneY1 = pyo.LogicalConstraint(rule=select_one_Y1) @@ -228,7 +225,7 @@ def select_one_Y2(m): Pyomo.LogicalConstraint Logical constraint that make Y2 to be true for only one element """ - return pyo.exactly(1, m.Y2) + return pyo.exactly(1,[m.Y2_disjunct[n].indicator_var for n in m.set2]) m.oneY2 = pyo.LogicalConstraint(rule=select_one_Y2) @@ -248,7 +245,7 @@ def infeasR_rule(m): Pyomo.LogicalConstraint Logical constraint that defines an infeasible region on Y1[3] """ - return pyo.land([pyo.lnot(m.Y1[j]) for j in m.sub1]) + return pyo.land([pyo.lnot(m.Y1_disjunct[j].indicator_var) for j in m.sub1]) m.infeasR = pyo.LogicalConstraint(rule=infeasR_rule) From dcdbce47c66a97ff6e4d48a86fbb9e9afb43cbbb Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Sat, 24 Aug 2024 02:24:03 -0400 Subject: [PATCH 78/79] Remove associate --- gdplib/ex1_linan_2023/ex1_linan_2023.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/gdplib/ex1_linan_2023/ex1_linan_2023.py b/gdplib/ex1_linan_2023/ex1_linan_2023.py index 4fd9114..92faa4d 100644 --- a/gdplib/ex1_linan_2023/ex1_linan_2023.py +++ b/gdplib/ex1_linan_2023/ex1_linan_2023.py @@ -127,10 +127,6 @@ def Disjunction1(m): # Disjunction for first Boolean variable m.Disjunction1 = Disjunction(rule=Disjunction1, xor=False) - # # Associate boolean variables to disjuncts - # for n1 in m.set1: - # m.Y1[n1].associate_binary_var(m.Y1_disjunct[n1].indicator_var) - # Second disjunction def build_disjuncts2(m, set2): # Disjuncts for second Boolean variable """ @@ -185,10 +181,6 @@ def Disjunction2(m): # Disjunction for first Boolean variable m.Disjunction2 = Disjunction(rule=Disjunction2, xor=False) - # # Associate boolean variables to disjuncts - # for n2 in m.set2: - # m.Y2[n2].associate_binary_var(m.Y2_disjunct[n2].indicator_var) - # Logical constraints # Constraint that allow to apply the reformulation over Y1 From 6349c63f71b523cd2cac3c21e563b0d7647ab7ac Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Sat, 24 Aug 2024 02:30:49 -0400 Subject: [PATCH 79/79] black formatting --- gdplib/ex1_linan_2023/ex1_linan_2023.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gdplib/ex1_linan_2023/ex1_linan_2023.py b/gdplib/ex1_linan_2023/ex1_linan_2023.py index 92faa4d..a069d95 100644 --- a/gdplib/ex1_linan_2023/ex1_linan_2023.py +++ b/gdplib/ex1_linan_2023/ex1_linan_2023.py @@ -217,7 +217,7 @@ def select_one_Y2(m): Pyomo.LogicalConstraint Logical constraint that make Y2 to be true for only one element """ - return pyo.exactly(1,[m.Y2_disjunct[n].indicator_var for n in m.set2]) + return pyo.exactly(1, [m.Y2_disjunct[n].indicator_var for n in m.set2]) m.oneY2 = pyo.LogicalConstraint(rule=select_one_Y2)