From 5af8182ae5c44e8ace1a4c51164914e40c83b344 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Fri, 19 Apr 2024 20:23:16 -0400 Subject: [PATCH 01/60] Added explanation of the matrices --- gdplib/logical/spectralog.py | 70 +++++++++++++++++++++++++++++++----- 1 file changed, 61 insertions(+), 9 deletions(-) diff --git a/gdplib/logical/spectralog.py b/gdplib/logical/spectralog.py index 9bb6b17..8427236 100644 --- a/gdplib/logical/spectralog.py +++ b/gdplib/logical/spectralog.py @@ -21,6 +21,25 @@ def build_model(): + """_Summary_ + + Parameters + ---------- + + Returns + ------- + Pyomo.ConcreteModel + A Pyomo model representing the IR spectroscopy parameter estimation problem. + + Notes + ----- + + References + ---------- + [1] Vecchietti, A., & Grossmann, I. E. (1997). LOGMIP: a disjunctive 0–1 nonlinear optimizer for process systems models. Computers & chemical engineering, 21, S427-S432. https://doi.org/10.1016/S0098-1354(97)87539-4 + [2] Brink, A., & Westerlund, T. (1995). The joint problem of model structure determination and parameter estimation in quantitative IR spectroscopy. Chemometrics and intelligent laboratory systems, 29(1), 29-36. https://doi.org/10.1016/0169-7439(95)00033-3 + """ + # Matrix of absorbance values across different wave numbers (rows) and spectra numbers (columns) spectroscopic_data = StringIO(""" 1 2 3 4 5 6 7 8 1 0.0003 0.0764 0.0318 0.0007 0.0534 0.0773 0.0536 0.0320 @@ -39,6 +58,8 @@ def build_model(): flat_spectro_data = spectroscopic_data_table.stack() spectro_data_dict = {(k[0], int(k[1])): v for k, v in flat_spectro_data.to_dict().items()} # column labels to integer + # Measured concentration data for each compound(row) and spectra number(column) + # Units for concentration for each component 1, 2, and 3 are ppm, ppm, and % for CO, NO, and CO2, respectively. c_data = StringIO(""" 1 2 3 4 5 6 7 8 1 502 204 353 702 0 1016 104 204 @@ -48,7 +69,7 @@ def build_model(): c_data_table = pd.read_csv(c_data, delimiter=r'\s+') c_data_dict = {(k[0], int(k[1])): v for k, v in c_data_table.stack().to_dict().items()} - # Covariance matrix + # Covariance matrix; It is assumed to be known that it is equal to the identity matrix at first problem iteration r_data = StringIO(""" 1 2 3 1 1 0 0 @@ -59,13 +80,13 @@ def build_model(): r_data_dict = {(k[0], int(k[1])): v for k, v in r_data_table.stack().to_dict().items()} m = ConcreteModel(name="IR spectroscopy parameter estimation") - m.wave_number = RangeSet(10) - m.spectra_data = RangeSet(8) - m.compounds = RangeSet(3) + m.wave_number = RangeSet(10) # 10 wave numbers + m.spectra_data = RangeSet(8) # 8 spectra data points + m.compounds = RangeSet(3) # 3 compounds; 1, 2, 3 refer to CO, NO, and CO2, respectively - m.A = Param(m.wave_number, m.spectra_data, initialize=spectro_data_dict) - m.C = Param(m.compounds, m.spectra_data, initialize=c_data_dict) - m.R = Param(m.compounds, m.compounds, initialize=r_data_dict) + m.A = Param(m.wave_number, m.spectra_data, initialize=spectro_data_dict, doc='Absorbance data') + m.C = Param(m.compounds, m.spectra_data, initialize=c_data_dict, doc='Concentration data') + m.R = Param(m.compounds, m.compounds, initialize=r_data_dict, doc='Covariance matrix') m.val = Var(m.spectra_data) m.ent = Var(m.compounds, m.wave_number, bounds=(0, 1)) @@ -74,9 +95,25 @@ def build_model(): @m.Disjunction(m.compounds, m.wave_number) def d(m, k, i): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + _description_ + k : int + Index of compounds. + i : int + Index of wave numbers. + + Returns + ------- + Pyomo.Disjunction + _description_ + """ return [ - [m.P[k, i] <= 1000, m.P[k, i] >= 0, m.ent[k, i] == 1], - [m.P[k, i] == 0, m.ent[k, i] == 0] + [m.P[k, i] <= 1000, m.P[k, i] >= 0, m.ent[k, i] == 1], # Conditions for the compound being active + [m.P[k, i] == 0, m.ent[k, i] == 0] # Conditions for the compound being inactive ] for k, i in m.compounds * m.wave_number: @@ -85,6 +122,21 @@ def d(m, k, i): @m.Constraint(m.spectra_data) def eq1(m, j): + """ + Defines a disjunction for each compound and wave number that determines whether a compound is active at a particular wave number based on the parameter estimates. + + Parameters + ---------- + m : Pyomo.ConcreteModel + _description_ + j : int + Index for the spectra data points, representing different experimental conditions. + + Returns + ------- + _type_ + _description_ + """ return m.val[j] == sum( sum((m.C[kk, j] / 100 - sum(m.P[kk, i] * m.A[i, j] for i in m.wave_number)) * m.R[kk, k] From 10666b5b51c04f5490a7cd253e242c0318b68684 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Fri, 19 Apr 2024 20:57:17 -0400 Subject: [PATCH 02/60] Added documentation of the IR parameter estimation problem. --- gdplib/logical/spectralog.py | 132 ++++++++++++++++++++++++----------- 1 file changed, 92 insertions(+), 40 deletions(-) diff --git a/gdplib/logical/spectralog.py b/gdplib/logical/spectralog.py index 8427236..c34496b 100644 --- a/gdplib/logical/spectralog.py +++ b/gdplib/logical/spectralog.py @@ -15,16 +15,16 @@ from pyomo.environ import * from pyomo.gdp import * from pyomo.core.expr.logical_expr import * -from pyomo.core.plugins.transform.logical_to_linear import update_boolean_vars_from_binary +from pyomo.core.plugins.transform.logical_to_linear import ( + update_boolean_vars_from_binary, +) from six import StringIO import pandas as pd def build_model(): - """_Summary_ - - Parameters - ---------- + """ + Constructs and returns a Pyomo Concrete Model for IR spectroscopy parameter estimation. Returns ------- @@ -33,6 +33,8 @@ def build_model(): Notes ----- + - The model uses a disjunctive programming approach where decision variables can trigger different sets of constraints, + representing different physcochemical conditions. References ---------- @@ -40,7 +42,8 @@ def build_model(): [2] Brink, A., & Westerlund, T. (1995). The joint problem of model structure determination and parameter estimation in quantitative IR spectroscopy. Chemometrics and intelligent laboratory systems, 29(1), 29-36. https://doi.org/10.1016/0169-7439(95)00033-3 """ # Matrix of absorbance values across different wave numbers (rows) and spectra numbers (columns) - spectroscopic_data = StringIO(""" + spectroscopic_data = StringIO( + """ 1 2 3 4 5 6 7 8 1 0.0003 0.0764 0.0318 0.0007 0.0534 0.0773 0.0536 0.0320 2 0.0007 0.0003 0.0004 0.0009 0.0005 0.0009 0.0005 0.0003 @@ -52,46 +55,84 @@ def build_model(): 8 0.0507 0.0361 0.0433 0.0635 0.0048 0.0891 0.0213 0.0310 9 0.0905 0.0600 0.0754 0.1098 0.0038 0.1443 0.0420 0.0574 10 0.0016 0.0209 0.0063 0.0010 0.0132 0.0203 0.0139 0.0057 - """) + """ + ) # Note: this could come from an external data file spectroscopic_data_table = pd.read_csv(spectroscopic_data, delimiter=r'\s+') flat_spectro_data = spectroscopic_data_table.stack() - spectro_data_dict = {(k[0], int(k[1])): v for k, v in flat_spectro_data.to_dict().items()} # column labels to integer + spectro_data_dict = { + (k[0], int(k[1])): v for k, v in flat_spectro_data.to_dict().items() + } # column labels to integer # Measured concentration data for each compound(row) and spectra number(column) # Units for concentration for each component 1, 2, and 3 are ppm, ppm, and % for CO, NO, and CO2, respectively. - c_data = StringIO(""" + c_data = StringIO( + """ 1 2 3 4 5 6 7 8 1 502 204 353 702 0 1016 104 204 2 97 351 351 351 700 0 201 97 3 0 22 8 0 14 22 14 8 - """) + """ + ) c_data_table = pd.read_csv(c_data, delimiter=r'\s+') - c_data_dict = {(k[0], int(k[1])): v for k, v in c_data_table.stack().to_dict().items()} + c_data_dict = { + (k[0], int(k[1])): v for k, v in c_data_table.stack().to_dict().items() + } # Covariance matrix; It is assumed to be known that it is equal to the identity matrix at first problem iteration - r_data = StringIO(""" + r_data = StringIO( + """ 1 2 3 1 1 0 0 2 0 1 0 3 0 0 1 - """) + """ + ) r_data_table = pd.read_csv(r_data, delimiter=r'\s+') - r_data_dict = {(k[0], int(k[1])): v for k, v in r_data_table.stack().to_dict().items()} + r_data_dict = { + (k[0], int(k[1])): v for k, v in r_data_table.stack().to_dict().items() + } m = ConcreteModel(name="IR spectroscopy parameter estimation") - m.wave_number = RangeSet(10) # 10 wave numbers - m.spectra_data = RangeSet(8) # 8 spectra data points - m.compounds = RangeSet(3) # 3 compounds; 1, 2, 3 refer to CO, NO, and CO2, respectively - - m.A = Param(m.wave_number, m.spectra_data, initialize=spectro_data_dict, doc='Absorbance data') - m.C = Param(m.compounds, m.spectra_data, initialize=c_data_dict, doc='Concentration data') - m.R = Param(m.compounds, m.compounds, initialize=r_data_dict, doc='Covariance matrix') - - m.val = Var(m.spectra_data) - m.ent = Var(m.compounds, m.wave_number, bounds=(0, 1)) - m.Y = BooleanVar(m.compounds, m.wave_number) - m.P = Var(m.compounds, m.wave_number, bounds=(0, 1000)) + m.wave_number = RangeSet(10) # 10 wave numbers + m.spectra_data = RangeSet(8) # 8 spectra data points + m.compounds = RangeSet( + 3 + ) # 3 compounds; 1, 2, 3 refer to CO, NO, and CO2, respectively + + m.A = Param( + m.wave_number, + m.spectra_data, + initialize=spectro_data_dict, + doc='Absorbance data', + ) + m.C = Param( + m.compounds, m.spectra_data, initialize=c_data_dict, doc='Concentration data' + ) + m.R = Param( + m.compounds, m.compounds, initialize=r_data_dict, doc='Covariance matrix' + ) + + m.val = Var( + m.spectra_data, doc='Calculated objective values for each spectra data point' + ) + m.ent = Var( + m.compounds, + m.wave_number, + bounds=(0, 1), + doc='Binary variables affecting the objective function and constraints.', + ) + m.Y = BooleanVar( + m.compounds, + m.wave_number, + doc='Boolean decisions for compound presence at each wave number.', + ) + m.P = Var( + m.compounds, + m.wave_number, + bounds=(0, 1000), + doc='Continuous variables estimating the concentration level of each compound at each wave number.', + ) @m.Disjunction(m.compounds, m.wave_number) def d(m, k, i): @@ -100,7 +141,7 @@ def d(m, k, i): Parameters ---------- m : Pyomo.ConcreteModel - _description_ + A Pyomo model representing the IR spectroscopy parameter estimation problem. k : int Index of compounds. i : int @@ -109,16 +150,23 @@ def d(m, k, i): Returns ------- Pyomo.Disjunction - _description_ + A disjunctive constraint that specifies the operational conditions for each compound-wave number pair based on the model's parameters. """ return [ - [m.P[k, i] <= 1000, m.P[k, i] >= 0, m.ent[k, i] == 1], # Conditions for the compound being active - [m.P[k, i] == 0, m.ent[k, i] == 0] # Conditions for the compound being inactive + [ + m.P[k, i] <= 1000, + m.P[k, i] >= 0, + m.ent[k, i] == 1, + ], # Conditions for the compound being active + [ + m.P[k, i] == 0, + m.ent[k, i] == 0, + ], # Conditions for the compound being inactive ] + # Associate each Boolean variable with a corresponding binary variable to handle logical conditions. for k, i in m.compounds * m.wave_number: - m.Y[k, i].associate_binary_var( - m.d[k, i].disjuncts[0].binary_indicator_var) + m.Y[k, i].associate_binary_var(m.d[k, i].disjuncts[0].binary_indicator_var) @m.Constraint(m.spectra_data) def eq1(m, j): @@ -128,25 +176,31 @@ def eq1(m, j): Parameters ---------- m : Pyomo.ConcreteModel - _description_ + A Pyomo model representing the IR spectroscopy parameter estimation problem. j : int Index for the spectra data points, representing different experimental conditions. Returns ------- - _type_ - _description_ + Pyomo.Constraint + An expression that equates the calculated value for each spectra data point with a mathematically derived expression from the model. """ return m.val[j] == sum( - sum((m.C[kk, j] / 100 - sum(m.P[kk, i] * m.A[i, j] for i in m.wave_number)) + sum( + (m.C[kk, j] / 100 - sum(m.P[kk, i] * m.A[i, j] for i in m.wave_number)) * m.R[kk, k] - for kk in m.compounds) + for kk in m.compounds + ) * (m.C[k, j] / 100 - sum(m.P[k, i] * m.A[i, j] for i in m.wave_number)) for k in m.compounds ) m.profit = Objective( - expr=sum(m.val[j] for j in m.spectra_data) + 2 * sum(m.ent[k, i] for k in m.compounds for i in m.wave_number)) + expr=sum(m.val[j] for j in m.spectra_data) + + 2 * sum(m.ent[k, i] for k in m.compounds for i in m.wave_number), + doc='Objective to maximize spectroscopic agreement and encourage compound presence.', + ) + # The first sum represents total spectroscopic value across data points, and the second weighted sum promotes activation of compound-wave number pairs. return m @@ -161,5 +215,3 @@ def eq1(m, j): m.profit.display() m.Y.display() m.P.display() - - From 64202f40904c973f7157907e649fb7ca2a27854e Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 23 Apr 2024 19:54:50 -0400 Subject: [PATCH 03/60] Set up the parameters of the distance and the coordinates of the sup mkt and site --- gdplib/biofuel/model.py | 734 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 728 insertions(+), 6 deletions(-) diff --git a/gdplib/biofuel/model.py b/gdplib/biofuel/model.py index 9ed8908..58a97b0 100644 --- a/gdplib/biofuel/model.py +++ b/gdplib/biofuel/model.py @@ -11,21 +11,43 @@ def build_model(): + """_summary_ + + Returns + ------- + Pyomo.ConcreteModel + The Pyomo concrete model which descibes the multiperiod location-allocation optimization model designed to determine the most cost-effective network layout and production allocation to meet market demands. + """ m = ConcreteModel() m.bigM = Suffix(direction=Suffix.LOCAL) m.time = RangeSet(0, 120, doc="months in 10 years") - m.suppliers = RangeSet(10) - m.markets = RangeSet(10) - m.potential_sites = RangeSet(12) - m.discount_rate = Param(initialize=0.08, doc="8%") + m.suppliers = RangeSet(10) # 10 suppliers + m.markets = RangeSet(10) # 10 markets + m.potential_sites = RangeSet(12) # 12 facility sites + m.discount_rate = Param(initialize=0.08, doc="discount rate [8%]") m.conv_setup_time = Param(initialize=12) m.modular_setup_time = Param(initialize=3) m.modular_teardown_time = Param(initialize=3) - m.teardown_value = Param(initialize=0.30, doc="30%") - m.conventional_salvage_value = Param(initialize=0.05, doc="5%") + m.teardown_value = Param(initialize=0.30, doc="tear down value [30%]") + m.conventional_salvage_value = Param(initialize=0.05, doc="salvage value [5%]") @m.Param(m.time) def discount_factor(m, t): + """ + Calculate the discount factor for a given time period 't', based on a monthly compounding interest rate. + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + t : int + Index of time in months from 0 to 120 (10 years) + + Returns + ------- + Pyomo.Parameter + The discount factor for month 't', calculated using the formula (1 + r/12)**(-t/12) where 'r' is the annual discount rate. + """ return (1 + m.discount_rate / 12) ** (-t / 12) xls_data = pd.read_excel( @@ -35,6 +57,23 @@ def discount_factor(m, t): @m.Param(m.markets, m.time, doc="Market demand [thousand ton/month]") def market_demand(m, mkt, t): + """ + Calculate the market demand for a given market 'mkt' at time 't', based on the demand data provided in the Excel file. + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + mkt : int + Index of the market from 1 to 10 + t : int + Index of time in months from 0 to 120 (10 years) + + Returns + ------- + Pyomo.Parameter + If the conversion setup time is less than or equal to 't' and 't' is less than the maximum time period minus 3 months, return the market demand in thousand tons per month, otherwise return 0. + """ if m.conv_setup_time <= t <= max(m.time) - 3: return float(xls_data["markets"]["demand"][mkt]) / 1000 / 12 else: @@ -42,6 +81,23 @@ def market_demand(m, mkt, t): @m.Param(m.suppliers, m.time, doc="Raw material supply [thousand ton/month]") def available_supply(m, sup, t): + """ + Calculate the available supply of raw materials for a given supplier 'sup' at time 't', based on the supply data provided in the Excel file. + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + sup : int + Index of the supplier from 1 to 10 + t : int + Index of time in months from 0 to 120 (10 years) + + Returns + ------- + Pyomo.Parameter + If 't' is before the growth period or after the decay period, return 0, otherwise return the available supply in thousand tons per month. + """ # If t is before supply available or after supply decayed, then no # supply if t < float(xls_data["sources"]["growth"][sup]): @@ -53,35 +109,159 @@ def available_supply(m, sup, t): @m.Param(m.suppliers) def supplier_x(m, sup): + """ + Get the x-coordinate of the supplier location in miles from the Excel data. + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + sup : int + Index of the supplier from 1 to 10 + + Returns + ------- + Pyomo.Parameter + x-coordinate of the supplier location in miles + """ return float(xls_data["sources"]["x"][sup]) @m.Param(m.suppliers) def supplier_y(m, sup): + """ + Get the y-coordinate of the supplier location in miles from the Excel data. + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + sup : int + Index of the supplier from 1 to 10 + + Returns + ------- + Pyomo.Parameter + y-coordinate of the supplier location in miles + """ return float(xls_data["sources"]["y"][sup]) @m.Param(m.markets) def market_x(m, mkt): + """ + Get the x-coordinate of the market location in miles from the Excel data. + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + mkt : int + Index of the market from 1 to 10 + + Returns + ------- + Pyomo.Parameter + x-coordinate of the market location in miles + """ return float(xls_data["markets"]["x"][mkt]) @m.Param(m.markets) def market_y(m, mkt): + """ + Get the y-coordinate of the market location in miles from the Excel data. + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + mkt : int + Index of the market from 1 to 10 + + Returns + ------- + Pyomo.Parameter + y-coordinate of the market location in miles + """ return float(xls_data["markets"]["y"][mkt]) @m.Param(m.potential_sites) def site_x(m, site): + """ + Get the x-coordinate of the facility site location in miles from the Excel data. + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + site : int + Index of the facility site from 1 to 12 + + Returns + ------- + Pyomo.Parameter + x-coordinate of the facility site location in miles + """ return float(xls_data["sites"]["x"][site]) @m.Param(m.potential_sites) def site_y(m, site): + """ + Get the y-coordinate of the facility site location in miles from the Excel data. + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + site : int + Index of the facility site from 1 to 12 + + Returns + ------- + Pyomo.Parameter + y-coordinate of the facility site location in miles + """ return float(xls_data["sites"]["y"][site]) @m.Param(m.suppliers, m.potential_sites, doc="Miles") def dist_supplier_to_site(m, sup, site): + """ + Calculate the distance in miles between a supplier 'sup' and a facility site 'site' using the Euclidean distance formula. + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + sup : int + Index of the supplier from 1 to 10 + site : int + Index of the facility site from 1 to 12 + + Returns + ------- + Pyomo.Parameter + The distance in miles between the supplier and the facility site + """ return sqrt((m.supplier_x[sup] - m.site_x[site]) ** 2 + (m.supplier_y[sup] - m.site_y[site]) ** 2) @m.Param(m.potential_sites, m.markets, doc="Miles") def dist_site_to_market(m, site, mkt): + """ + Calculate the distance in miles between a facility site 'site' and a market 'mkt' using the Euclidean distance formula. + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + site : int + Index of the facility site from 1 to 12 + mkt : int + Index of the market from 1 to 10 + + Returns + ------- + Pyomo.Parameter + The distance in miles between the facility site and the market + """ return sqrt((m.site_x[site] - m.market_x[mkt]) ** 2 + (m.site_y[site] - m.market_y[mkt]) ** 2) @@ -109,26 +289,110 @@ def dist_site_to_market(m, site, mkt): @m.Param(m.suppliers, m.time) def raw_material_unit_cost(m, sup, t): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + sup : _type_ + _description_ + t : int + Index of time in months from 0 to 120 (10 years) + + Returns + ------- + _type_ + _description_ + """ return float(xls_data["sources"]["cost"][sup]) * m.discount_factor[t] @m.Param(m.time) def module_unit_cost(m, t): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + t : int + Index of time in months from 0 to 120 (10 years) + + Returns + ------- + _type_ + _description_ + """ return m.module_base_cost * m.discount_factor[t] @m.Param(m.time, doc="$/ton") def unit_production_cost(m, t): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + _description_ + t : int + Index of time in months from 0 to 120 (10 years) + + Returns + ------- + _type_ + _description_ + """ return 300 * m.discount_factor[t] @m.Param(doc="thousand $") def transport_fixed_cost(m): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + + Returns + ------- + _type_ + _description_ + """ return 125 @m.Param(m.time, doc="$/ton-mile") def unit_product_transport_cost(m, t): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + t : int + Index of time in months from 0 to 120 (10 years) + + Returns + ------- + _type_ + _description_ + """ return 0.13 * m.discount_factor[t] @m.Param(m.time, doc="$/ton-mile") def unit_raw_material_transport_cost(m, t): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + t : int + Index of time in months from 0 to 120 (10 years) + + Returns + ------- + _type_ + _description_ + """ return 2 * m.discount_factor[t] m.supply_shipments = Var( @@ -140,21 +404,85 @@ def unit_raw_material_transport_cost(m, t): @m.Constraint(m.suppliers, m.time) def supply_limits(m, sup, t): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + sup : _type_ + _description_ + t : int + Index of time in months from 0 to 120 (10 years) + + Returns + ------- + _type_ + _description_ + """ return sum(m.supply_shipments[sup, site, t] for site in m.potential_sites) <= m.available_supply[sup, t] @m.Constraint(m.markets, m.time) def demand_satisfaction(m, mkt, t): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + mkt : _type_ + _description_ + t : int + Index of time in months from 0 to 120 (10 years) + + Returns + ------- + _type_ + _description_ + """ return sum(m.product_shipments[site, mkt, t] for site in m.potential_sites) == m.market_demand[mkt, t] @m.Constraint(m.potential_sites, m.time) def product_balance(m, site, t): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + site : _type_ + _description_ + t : int + Index of time in months from 0 to 120 (10 years) + + Returns + ------- + _type_ + _description_ + """ return m.production[site, t] == sum(m.product_shipments[site, mkt, t] for mkt in m.markets) @m.Constraint(m.potential_sites, m.time) def require_raw_materials(m, site, t): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + site : _type_ + _description_ + t : int + Index of time in months from 0 to 120 (10 years) + + Returns + ------- + _type_ + _description_ + """ return m.production[site, t] <= m.conversion * m.supply[site, t] m.modular = Disjunct(m.potential_sites, rule=_build_modular_disjunct) @@ -169,18 +497,80 @@ def require_raw_materials(m, site, t): @m.Disjunction(m.potential_sites) def site_type(m, site): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + site : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return [m.modular[site], m.conventional[site], m.site_inactive[site]] @m.Disjunction(m.suppliers, m.potential_sites) def supply_route_active_or_not(m, sup, site): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + sup : _type_ + _description_ + site : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return [m.supply_route_active[sup, site], m.supply_route_inactive[sup, site]] @m.Disjunction(m.potential_sites, m.markets) def product_route_active_or_not(m, site, mkt): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + site : _type_ + _description_ + mkt : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return [m.product_route_active[site, mkt], m.product_route_inactive[site, mkt]] @m.Expression(m.suppliers, m.potential_sites, doc="million $") def raw_material_transport_cost(m, sup, site): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + sup : _type_ + _description_ + site : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return sum( m.supply_shipments[sup, site, t] * m.unit_raw_material_transport_cost[t] @@ -189,6 +579,18 @@ def raw_material_transport_cost(m, sup, site): @m.Expression(doc="million $") def raw_material_fixed_transport_cost(m): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + + Returns + ------- + _type_ + _description_ + """ return ( sum(m.supply_route_active[sup, site].binary_indicator_var for sup in m.suppliers for site in m.potential_sites) @@ -196,6 +598,22 @@ def raw_material_fixed_transport_cost(m): @m.Expression(m.potential_sites, m.markets, doc="million $") def product_transport_cost(m, site, mkt): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + site : _type_ + _description_ + mkt : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return sum( m.product_shipments[site, mkt, t] * m.unit_product_transport_cost[t] @@ -204,6 +622,18 @@ def product_transport_cost(m, site, mkt): @m.Expression(doc="million $") def product_fixed_transport_cost(m): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + + Returns + ------- + _type_ + _description_ + """ return ( sum(m.product_route_active[site, mkt].binary_indicator_var for site in m.potential_sites for mkt in m.markets) @@ -211,14 +641,60 @@ def product_fixed_transport_cost(m): @m.Expression(m.potential_sites, m.time, doc="Cost of module setups in each month [million $]") def module_setup_cost(m, site, t): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + site : _type_ + _description_ + t : int + _description_ + + Returns + ------- + _type_ + _description_ + """ return m.modules_purchased[site, t] * m.module_unit_cost[t] @m.Expression(m.potential_sites, m.time, doc="Value of module teardowns in each month [million $]") def module_teardown_credit(m, site, t): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + site : _type_ + _description_ + t : int + Index of time in months from 0 to 120 (10 years) + + Returns + ------- + _type_ + _description_ + """ return m.modules_sold[site, t] * m.module_unit_cost[t] * m.teardown_value @m.Expression(m.potential_sites, doc="Conventional site salvage value") def conv_salvage_value(m, site): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo concrete model which descibes the multiperiod location-allocation optimization model + site : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return m.conv_build_cost[site] * m.discount_factor[m.time.last()] * m.conventional_salvage_value m.total_cost = Objective( @@ -238,22 +714,86 @@ def conv_salvage_value(m, site): def _build_site_inactive_disjunct(disj, site): + """_summary_ + + Parameters + ---------- + disj : _type_ + _description_ + site : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ m = disj.model() @disj.Constraint() def no_modules(disj): + """_summary_ + + Parameters + ---------- + disj : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return sum(m.num_modules[...]) + sum(m.modules_purchased[...]) + sum(m.modules_sold[...]) == 0 @disj.Constraint() def no_production(disj): + """_summary_ + + Parameters + ---------- + disj : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return sum(m.production[site, t] for t in m.time) == 0 @disj.Constraint() def no_supply(disj): + """_summary_ + + Parameters + ---------- + disj : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return sum(m.supply[site, t] for t in m.time) == 0 def _build_conventional_disjunct(disj, site): + """_summary_ + + Parameters + ---------- + disj : _type_ + _description_ + site : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ m = disj.model() disj.cost_calc = Constraint( @@ -263,11 +803,39 @@ def _build_conventional_disjunct(disj, site): @disj.Constraint(m.time) def supply_balance(disj, t): + """_summary_ + + Parameters + ---------- + disj : _type_ + _description_ + t : int + Index of time in months from 0 to 120 (10 years) + + Returns + ------- + _type_ + _description_ + """ return m.supply[site, t] == sum( m.supply_shipments[sup, site, t] for sup in m.suppliers) @disj.Constraint(m.time) def conv_production_limit(conv_disj, t): + """_summary_ + + Parameters + ---------- + conv_disj : _type_ + _description_ + t : int + Index of time in months from 0 to 120 (10 years) + + Returns + ------- + _type_ + _description_ + """ if t < m.conv_setup_time: return m.production[site, t] == 0 else: @@ -275,19 +843,73 @@ def conv_production_limit(conv_disj, t): @disj.Constraint() def no_modules(disj): + """_summary_ + + Parameters + ---------- + disj : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return sum(m.num_modules[...]) + sum(m.modules_purchased[...]) + sum(m.modules_sold[...]) == 0 def _build_modular_disjunct(disj, site): + """_summary_ + + Parameters + ---------- + disj : _type_ + _description_ + site : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ m = disj.model() @disj.Constraint(m.time) def supply_balance(disj, t): + """_summary_ + + Parameters + ---------- + disj : _type_ + _description_ + t : int + Index of time in months from 0 to 120 (10 years) + + Returns + ------- + _type_ + _description_ + """ return m.supply[site, t] == sum( m.supply_shipments[sup, site, t] for sup in m.suppliers) @disj.Constraint(m.time) def module_balance(disj, t): + """_summary_ + + Parameters + ---------- + disj : _type_ + _description_ + t : int + Index of time in months from 0 to 120 (10 years) + + Returns + ------- + _type_ + _description_ + """ existing_modules = 0 if t == m.time.first() else m.num_modules[site, t - 1] new_modules = 0 if t < m.modular_setup_time else m.modules_purchased[site, t - m.modular_setup_time] sold_modules = m.modules_sold[site, t] @@ -298,34 +920,134 @@ def module_balance(disj, t): @disj.Constraint(m.time) def modular_production_limit(mod_disj, t): + """_summary_ + + Parameters + ---------- + mod_disj : _type_ + _description_ + t : int + Index of time in months from 0 to 120 (10 years) + + Returns + ------- + _type_ + _description_ + """ return m.production[site, t] <= 10 * m.num_modules[site, t] def _build_supply_route_active(disj, sup, site): + """_summary_ + + Parameters + ---------- + disj : _type_ + _description_ + sup : _type_ + _description_ + site : _type_ + _description_ + """ m = disj.model() def _build_supply_route_inactive(disj, sup, site): + """_summary_ + + Parameters + ---------- + disj : _type_ + _description_ + sup : _type_ + _description_ + site : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ m = disj.model() @disj.Constraint() def no_supply(disj): + """_summary_ + + Parameters + ---------- + disj : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return sum(m.supply_shipments[sup, site, t] for t in m.time) == 0 def _build_product_route_active(disj, site, mkt): + """_summary_ + + Parameters + ---------- + disj : _type_ + _description_ + site : _type_ + _description_ + mkt : _type_ + _description_ + """ m = disj.model() def _build_product_route_inactive(disj, site, mkt): + """_summary_ + + Parameters + ---------- + disj : _type_ + _description_ + site : _type_ + _description_ + mkt : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ m = disj.model() @disj.Constraint() def no_product(disj): + """_summary_ + + Parameters + ---------- + disj : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return sum(m.product_shipments[site, mkt, t] for t in m.time) == 0 def print_nonzeros(var): + """ + Print the nonzero values of a Pyomo variable + + Parameters + ---------- + var : pyomo.Var + Pyomo variable of the model + """ for i in var: if var[i].value != 0: print("%7s : %10f : %10f : %10f" % (i, var[i].lb, var[i].value, var[i].ub)) From 8e6ed91e9ba85958f51741d4b1cd3d9faaf1d040 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 23 Apr 2024 21:12:48 -0400 Subject: [PATCH 04/60] Added documentation on the Parameter and References --- gdplib/biofuel/model.py | 59 ++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/gdplib/biofuel/model.py b/gdplib/biofuel/model.py index 58a97b0..305b20b 100644 --- a/gdplib/biofuel/model.py +++ b/gdplib/biofuel/model.py @@ -17,6 +17,11 @@ def build_model(): ------- Pyomo.ConcreteModel The Pyomo concrete model which descibes the multiperiod location-allocation optimization model designed to determine the most cost-effective network layout and production allocation to meet market demands. + + References + ---------- + [1] Lara, C. L., Trespalacios, F., & Grossmann, I. E. (2018). Global optimization algorithm for capacitated multi-facility continuous location-allocation problems. Journal of Global Optimization, 71(4), 871-889. https://doi.org/10.1007/s10898-018-0621-6 + [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() m.bigM = Suffix(direction=Suffix.LOCAL) @@ -278,9 +283,9 @@ def dist_site_to_market(m, site, mkt): m.supply = Var(m.potential_sites, m.time, bounds=(0, 120 / 12 / 0.26 * 10), doc="thousand ton/mo") m.production = Var(m.potential_sites, m.time, bounds=(0, 120 / 12 * 10), doc="thousand ton/mo") - m.num_modules = Var(m.potential_sites, m.time, domain=Integers, bounds=(0, 10)) - m.modules_purchased = Var(m.potential_sites, m.time, domain=Integers, bounds=(0, 10)) - m.modules_sold = Var(m.potential_sites, m.time, domain=Integers, bounds=(0, 10)) + m.num_modules = Var(m.potential_sites, m.time, domain=Integers, bounds=(0, 10), doc="Number of modules") + m.modules_purchased = Var(m.potential_sites, m.time, domain=Integers, bounds=(0, 10), doc="Modules purchased") + m.modules_sold = Var(m.potential_sites, m.time, domain=Integers, bounds=(0, 10), doc="Modules sold") m.conv_build_cost = Var( m.potential_sites, @@ -289,27 +294,29 @@ def dist_site_to_market(m, site, mkt): @m.Param(m.suppliers, m.time) def raw_material_unit_cost(m, sup, t): - """_summary_ + """ + Calculate the unit cost of raw materials for a given supplier 'sup' at time 't', based on the cost data provided in the Excel file. Parameters ---------- m : Pyomo.ConcreteModel Pyomo concrete model which descibes the multiperiod location-allocation optimization model - sup : _type_ - _description_ + sup : int + Index of the supplier from 1 to 10 t : int Index of time in months from 0 to 120 (10 years) Returns ------- - _type_ - _description_ + Pyomo.Parameter + The unit cost of raw materials for the supplier at time 't', calculated as the cost from the Excel data multiplied by the discount factor for time 't'. """ return float(xls_data["sources"]["cost"][sup]) * m.discount_factor[t] @m.Param(m.time) def module_unit_cost(m, t): - """_summary_ + """ + Calculate the unit cost of modules at time 't', based on the cost data provided in the Excel file. Parameters ---------- @@ -320,32 +327,34 @@ def module_unit_cost(m, t): Returns ------- - _type_ - _description_ + Pyomo.Parameter + The unit cost of modules at time 't', calculated as the cost from the Excel data multiplied by the discount factor for time 't'. """ return m.module_base_cost * m.discount_factor[t] @m.Param(m.time, doc="$/ton") def unit_production_cost(m, t): - """_summary_ + """ + Calculate the unit production cost at time 't', the production cost is 300 $/ton multiplied by the discount factor for time 't'. Parameters ---------- m : Pyomo.ConcreteModel - _description_ + Pyomo concrete model which descibes the multiperiod location-allocation optimization model t : int Index of time in months from 0 to 120 (10 years) Returns ------- - _type_ - _description_ + Pyomo.Parameter + The unit production cost at time 't', calculated as 300 $/ton multiplied by the discount factor for time 't'. """ return 300 * m.discount_factor[t] @m.Param(doc="thousand $") def transport_fixed_cost(m): - """_summary_ + """ + Fixed cost of transportation in thousand dollars. Parameters ---------- @@ -354,14 +363,15 @@ def transport_fixed_cost(m): Returns ------- - _type_ - _description_ + Pyomo.Parameter + The fixed cost of transportation in thousand dollars, the cost is 125 thousand dollars. """ return 125 @m.Param(m.time, doc="$/ton-mile") def unit_product_transport_cost(m, t): - """_summary_ + """ + Calculate the unit product transport cost at time 't', the cost is 0.13 $/ton-mile multiplied by the discount factor for time 't'. Parameters ---------- @@ -372,14 +382,15 @@ def unit_product_transport_cost(m, t): Returns ------- - _type_ - _description_ + Pyomo.Parameter + The unit product transport cost at time 't', calculated as 0.13 $/ton-mile multiplied by the discount factor for time 't'. """ return 0.13 * m.discount_factor[t] @m.Param(m.time, doc="$/ton-mile") def unit_raw_material_transport_cost(m, t): - """_summary_ + """ + Calculate the unit raw material transport cost at time 't', the cost is 2 $/ton-mile multiplied by the discount factor for time 't'. Parameters ---------- @@ -390,8 +401,8 @@ def unit_raw_material_transport_cost(m, t): Returns ------- - _type_ - _description_ + Pyomo.Parameter + The unit raw material transport cost at time 't', calculated as 2 $/ton-mile multiplied by the discount factor for time 't'. """ return 2 * m.discount_factor[t] From 2d0af2fa1d4d456eba57f3b7a359aefbb51df704 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 23 Apr 2024 22:03:31 -0400 Subject: [PATCH 05/60] Update documentation for supply and demand constraints in biofuel model --- gdplib/biofuel/model.py | 44 ++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/gdplib/biofuel/model.py b/gdplib/biofuel/model.py index 305b20b..72e217c 100644 --- a/gdplib/biofuel/model.py +++ b/gdplib/biofuel/model.py @@ -415,84 +415,88 @@ def unit_raw_material_transport_cost(m, t): @m.Constraint(m.suppliers, m.time) def supply_limits(m, sup, t): - """_summary_ + """ + Ensure that the total supply from a supplier 'sup' at time 't' does not exceed the available supply from the supplier. Parameters ---------- m : Pyomo.ConcreteModel Pyomo concrete model which descibes the multiperiod location-allocation optimization model - sup : _type_ - _description_ + sup : int + Index of the supplier from 1 to 10 t : int Index of time in months from 0 to 120 (10 years) Returns ------- - _type_ - _description_ + Pyomo.Constraint + The total supply from the supplier 'sup' at time 't' should not exceed the available supply from the supplier. """ return sum(m.supply_shipments[sup, site, t] for site in m.potential_sites) <= m.available_supply[sup, t] @m.Constraint(m.markets, m.time) def demand_satisfaction(m, mkt, t): - """_summary_ + """ + Ensure that the total product shipments to a market 'mkt' at time 't' meets the market demand for the product. Parameters ---------- m : Pyomo.ConcreteModel Pyomo concrete model which descibes the multiperiod location-allocation optimization model - mkt : _type_ - _description_ + mkt : int + Index of the market from 1 to 10 t : int Index of time in months from 0 to 120 (10 years) Returns ------- - _type_ - _description_ + Pyomo.Constraint + The total product shipments to the market 'mkt' at time 't' should meet the market demand for the product. """ return sum(m.product_shipments[site, mkt, t] for site in m.potential_sites) == m.market_demand[mkt, t] @m.Constraint(m.potential_sites, m.time) def product_balance(m, site, t): - """_summary_ + """ + Ensure that the total product shipments from a facility site 'site' at time 't' meets the production from the site. Parameters ---------- m : Pyomo.ConcreteModel Pyomo concrete model which descibes the multiperiod location-allocation optimization model - site : _type_ - _description_ + site : int + Index of the facility site from 1 to 12 t : int Index of time in months from 0 to 120 (10 years) Returns ------- - _type_ - _description_ + Pyomo.Constraint + The total product shipments from the facility site 'site' at time 't' should meet the production from the site. """ return m.production[site, t] == sum(m.product_shipments[site, mkt, t] for mkt in m.markets) @m.Constraint(m.potential_sites, m.time) def require_raw_materials(m, site, t): - """_summary_ + """ + Ensure that the raw materials required for production at a facility site 'site' at time 't' are available from the suppliers. Parameters ---------- m : Pyomo.ConcreteModel Pyomo concrete model which descibes the multiperiod location-allocation optimization model - site : _type_ - _description_ + site : int + Index of the facility site from 1 to 12 t : int Index of time in months from 0 to 120 (10 years) Returns ------- - _type_ - _description_ + Pyomo.Constraint + The production at the facility site 'site' at time 't' should not exceed the raw materials available from the suppliers which is the supply multiplied by the conversion factor. """ return m.production[site, t] <= m.conversion * m.supply[site, t] From 1a6995bec6fd8488a671c8fd2718f096805339f5 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 23 Apr 2024 22:03:52 -0400 Subject: [PATCH 06/60] Update documentation for supply and demand constraints in biofuel model --- gdplib/biofuel/model.py | 130 ++++++++++++++++++++-------------------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/gdplib/biofuel/model.py b/gdplib/biofuel/model.py index 72e217c..2f44757 100644 --- a/gdplib/biofuel/model.py +++ b/gdplib/biofuel/model.py @@ -500,15 +500,15 @@ def require_raw_materials(m, site, t): """ return m.production[site, t] <= m.conversion * m.supply[site, t] - m.modular = Disjunct(m.potential_sites, rule=_build_modular_disjunct) - m.conventional = Disjunct(m.potential_sites, rule=_build_conventional_disjunct) - m.site_inactive = Disjunct(m.potential_sites, rule=_build_site_inactive_disjunct) + m.modular = Disjunct(m.potential_sites, rule=_build_modular_disjunct, doc="Disjunct for modular site") + m.conventional = Disjunct(m.potential_sites, rule=_build_conventional_disjunct, doc="Disjunct for conventional site") + m.site_inactive = Disjunct(m.potential_sites, rule=_build_site_inactive_disjunct, doc="Disjunct for inactive site") - m.supply_route_active = Disjunct(m.suppliers, m.potential_sites, rule=_build_supply_route_active) - m.supply_route_inactive = Disjunct(m.suppliers, m.potential_sites, rule=_build_supply_route_inactive) + m.supply_route_active = Disjunct(m.suppliers, m.potential_sites, rule=_build_supply_route_active, doc="Disjunct for active supply route") + m.supply_route_inactive = Disjunct(m.suppliers, m.potential_sites, rule=_build_supply_route_inactive, doc="Disjunct for inactive supply route") - m.product_route_active = Disjunct(m.potential_sites, m.markets, rule=_build_product_route_active) - m.product_route_inactive = Disjunct(m.potential_sites, m.markets, rule=_build_product_route_inactive) + m.product_route_active = Disjunct(m.potential_sites, m.markets, rule=_build_product_route_active, doc="Disjunct for active product route") + m.product_route_inactive = Disjunct(m.potential_sites, m.markets, rule=_build_product_route_inactive, doc="Disjunct for inactive product route") @m.Disjunction(m.potential_sites) def site_type(m, site): @@ -518,8 +518,8 @@ def site_type(m, site): ---------- m : Pyomo.ConcreteModel Pyomo concrete model which descibes the multiperiod location-allocation optimization model - site : _type_ - _description_ + site : int + Index of the facility site from 1 to 12 Returns ------- @@ -536,10 +536,10 @@ def supply_route_active_or_not(m, sup, site): ---------- m : Pyomo.ConcreteModel Pyomo concrete model which descibes the multiperiod location-allocation optimization model - sup : _type_ - _description_ - site : _type_ - _description_ + sup : int + Index of the supplier from 1 to 10 + site : int + Index of the facility site from 1 to 12 Returns ------- @@ -556,9 +556,9 @@ def product_route_active_or_not(m, site, mkt): ---------- m : Pyomo.ConcreteModel Pyomo concrete model which descibes the multiperiod location-allocation optimization model - site : _type_ - _description_ - mkt : _type_ + site : int + Index of the facility site from 1 to 12 + mkt : int _description_ Returns @@ -578,8 +578,8 @@ def raw_material_transport_cost(m, sup, site): Pyomo concrete model which descibes the multiperiod location-allocation optimization model sup : _type_ _description_ - site : _type_ - _description_ + site : int + Index of the facility site from 1 to 12 Returns ------- @@ -603,7 +603,7 @@ def raw_material_fixed_transport_cost(m): Returns ------- - _type_ + Pyomo.Expression _description_ """ return ( @@ -619,9 +619,9 @@ def product_transport_cost(m, site, mkt): ---------- m : Pyomo.ConcreteModel Pyomo concrete model which descibes the multiperiod location-allocation optimization model - site : _type_ - _description_ - mkt : _type_ + site : int + Index of the facility site from 1 to 12 + mkt : int _description_ Returns @@ -662,10 +662,10 @@ def module_setup_cost(m, site, t): ---------- m : Pyomo.ConcreteModel Pyomo concrete model which descibes the multiperiod location-allocation optimization model - site : _type_ - _description_ + site : int + Index of the facility site from 1 to 12 t : int - _description_ + Index of time in months from 0 to 120 (10 years) Returns ------- @@ -682,8 +682,8 @@ def module_teardown_credit(m, site, t): ---------- m : Pyomo.ConcreteModel Pyomo concrete model which descibes the multiperiod location-allocation optimization model - site : _type_ - _description_ + site : int + Index of the facility site from 1 to 12 t : int Index of time in months from 0 to 120 (10 years) @@ -702,8 +702,8 @@ def conv_salvage_value(m, site): ---------- m : Pyomo.ConcreteModel Pyomo concrete model which descibes the multiperiod location-allocation optimization model - site : _type_ - _description_ + site : int + Index of the facility site from 1 to 12 Returns ------- @@ -733,10 +733,10 @@ def _build_site_inactive_disjunct(disj, site): Parameters ---------- - disj : _type_ - _description_ - site : _type_ + disj : Pyomo.Disjunct _description_ + site : int + Index of the facility site from 1 to 12 Returns ------- @@ -751,7 +751,7 @@ def no_modules(disj): Parameters ---------- - disj : _type_ + disj : Pyomo.Disjunct _description_ Returns @@ -767,7 +767,7 @@ def no_production(disj): Parameters ---------- - disj : _type_ + disj : Pyomo.Disjunct _description_ Returns @@ -783,7 +783,7 @@ def no_supply(disj): Parameters ---------- - disj : _type_ + disj : Pyomo.Disjunct _description_ Returns @@ -799,10 +799,10 @@ def _build_conventional_disjunct(disj, site): Parameters ---------- - disj : _type_ - _description_ - site : _type_ + disj : Pyomo.Disjunct _description_ + site : int + Index of the facility site from 1 to 12 Returns ------- @@ -822,7 +822,7 @@ def supply_balance(disj, t): Parameters ---------- - disj : _type_ + disj : Pyomo.Disjunct _description_ t : int Index of time in months from 0 to 120 (10 years) @@ -841,7 +841,7 @@ def conv_production_limit(conv_disj, t): Parameters ---------- - conv_disj : _type_ + conv_disj : Pyomo.Disjunct _description_ t : int Index of time in months from 0 to 120 (10 years) @@ -862,7 +862,7 @@ def no_modules(disj): Parameters ---------- - disj : _type_ + disj : Pyomo.Disjunct _description_ Returns @@ -878,10 +878,10 @@ def _build_modular_disjunct(disj, site): Parameters ---------- - disj : _type_ - _description_ - site : _type_ + disj : Pyomo.Disjunct _description_ + site : int + Index of the facility site from 1 to 12 Returns ------- @@ -896,7 +896,7 @@ def supply_balance(disj, t): Parameters ---------- - disj : _type_ + disj : Pyomo.Disjunct _description_ t : int Index of time in months from 0 to 120 (10 years) @@ -915,7 +915,7 @@ def module_balance(disj, t): Parameters ---------- - disj : _type_ + disj : Pyomo.Disjunct _description_ t : int Index of time in months from 0 to 120 (10 years) @@ -939,7 +939,7 @@ def modular_production_limit(mod_disj, t): Parameters ---------- - mod_disj : _type_ + mod_disj : Pyomo.Disjunct _description_ t : int Index of time in months from 0 to 120 (10 years) @@ -957,12 +957,12 @@ def _build_supply_route_active(disj, sup, site): Parameters ---------- - disj : _type_ + disj : Pyomo.Disjunct _description_ sup : _type_ _description_ - site : _type_ - _description_ + site : int + Index of the facility site from 1 to 12 """ m = disj.model() @@ -972,12 +972,12 @@ def _build_supply_route_inactive(disj, sup, site): Parameters ---------- - disj : _type_ + disj : Pyomo.Disjunct _description_ sup : _type_ _description_ - site : _type_ - _description_ + site : int + Index of the facility site from 1 to 12 Returns ------- @@ -992,7 +992,7 @@ def no_supply(disj): Parameters ---------- - disj : _type_ + disj : Pyomo.Disjunct _description_ Returns @@ -1008,12 +1008,12 @@ def _build_product_route_active(disj, site, mkt): Parameters ---------- - disj : _type_ - _description_ - site : _type_ - _description_ - mkt : _type_ + disj : Pyomo.Disjunct _description_ + site : int + Index of the facility site from 1 to 12 + mkt : int + Index of the market from 1 to 10 """ m = disj.model() @@ -1023,12 +1023,12 @@ def _build_product_route_inactive(disj, site, mkt): Parameters ---------- - disj : _type_ - _description_ - site : _type_ - _description_ - mkt : _type_ + disj : Pyomo.Disjunct _description_ + site : int + Index of the facility site from 1 to 12 + mkt : int + Index of the market from 1 to 10 Returns ------- @@ -1043,7 +1043,7 @@ def no_product(disj): Parameters ---------- - disj : _type_ + disj : Pyomo.Disjunct _description_ Returns From 1e0e806d2743e51da662f794c31463fc4df5c75c Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 23 Apr 2024 23:44:40 -0400 Subject: [PATCH 07/60] Add documentation of the disjunct and the disjunctions --- gdplib/biofuel/model.py | 253 ++++++++++++++++++++++------------------ 1 file changed, 141 insertions(+), 112 deletions(-) diff --git a/gdplib/biofuel/model.py b/gdplib/biofuel/model.py index 2f44757..0cfd8f1 100644 --- a/gdplib/biofuel/model.py +++ b/gdplib/biofuel/model.py @@ -512,7 +512,8 @@ def require_raw_materials(m, site, t): @m.Disjunction(m.potential_sites) def site_type(m, site): - """_summary_ + """ + Define the disjunction for the facility site type, which can be modular, conventional, or inactive. Parameters ---------- @@ -523,14 +524,15 @@ def site_type(m, site): Returns ------- - _type_ - _description_ + Pyomo.Disjunction + The disjunction for the facility site type, which can be modular, conventional, or inactive. """ return [m.modular[site], m.conventional[site], m.site_inactive[site]] @m.Disjunction(m.suppliers, m.potential_sites) def supply_route_active_or_not(m, sup, site): - """_summary_ + """ + Define the disjunction for the supply route between a supplier and a facility site, which can be active or inactive. Parameters ---------- @@ -543,14 +545,15 @@ def supply_route_active_or_not(m, sup, site): Returns ------- - _type_ - _description_ + Pyomo.Disjunction + The disjunction for the supply route between a supplier and a facility site, which can be active or inactive. """ return [m.supply_route_active[sup, site], m.supply_route_inactive[sup, site]] @m.Disjunction(m.potential_sites, m.markets) def product_route_active_or_not(m, site, mkt): - """_summary_ + """ + Define the disjunction for the product route between a facility site and a market, which can be active or inactive. Parameters ---------- @@ -559,42 +562,44 @@ def product_route_active_or_not(m, site, mkt): site : int Index of the facility site from 1 to 12 mkt : int - _description_ + Index of the market from 1 to 10 Returns ------- - _type_ - _description_ + Pyomo.Disjunction + The disjunction for the product route between a facility site and a market, which can be active or inactive. """ return [m.product_route_active[site, mkt], m.product_route_inactive[site, mkt]] @m.Expression(m.suppliers, m.potential_sites, doc="million $") def raw_material_transport_cost(m, sup, site): - """_summary_ + """ + Calculate the cost of transporting raw materials from a supplier 'sup' to a facility site 'site' at each time period using the unit raw material transport cost, the supply shipments, and the distance between the supplier and the site. Parameters ---------- m : Pyomo.ConcreteModel Pyomo concrete model which descibes the multiperiod location-allocation optimization model - sup : _type_ + sup : int _description_ site : int Index of the facility site from 1 to 12 Returns ------- - _type_ - _description_ + Pyomo.Expression + Total transportation cost considering the quantity of shipments, unit cost per time period, and distance between suppliers and sites. """ return sum( - m.supply_shipments[sup, site, t] - * m.unit_raw_material_transport_cost[t] - * m.dist_supplier_to_site[sup, site] / 1000 + m.supply_shipments[sup, site, t] # [1000 ton/month] + * m.unit_raw_material_transport_cost[t] # [$/ton-mile] + * m.dist_supplier_to_site[sup, site] / 1000 # [mile], [million/1000] for t in m.time) @m.Expression(doc="million $") def raw_material_fixed_transport_cost(m): - """_summary_ + """ + Calculate the fixed cost of transporting raw materials to the facility sites based on the total number of active supply routes and the fixed transportation cost. Parameters ---------- @@ -604,16 +609,17 @@ def raw_material_fixed_transport_cost(m): Returns ------- Pyomo.Expression - _description_ + Sum of fixed transport costs, accounting for the activation of each route. """ return ( sum(m.supply_route_active[sup, site].binary_indicator_var for sup in m.suppliers for site in m.potential_sites) - * m.transport_fixed_cost / 1000) + * m.transport_fixed_cost / 1000) # [thousand $] [million/1000] @m.Expression(m.potential_sites, m.markets, doc="million $") def product_transport_cost(m, site, mkt): - """_summary_ + """ + Calculate the cost of transporting products from a facility site 'site' to a market 'mkt' at each time period using the unit product transport cost, the product shipments, and the distance between the site and the market. Parameters ---------- @@ -626,18 +632,19 @@ def product_transport_cost(m, site, mkt): Returns ------- - _type_ - _description_ + Pyomo.Expression + Total transportation cost considering the quantity of shipments, unit cost per time period, and distance between sites and markets. """ return sum( - m.product_shipments[site, mkt, t] - * m.unit_product_transport_cost[t] - * m.dist_site_to_market[site, mkt] / 1000 + m.product_shipments[site, mkt, t] # [1000 ton/month] + * m.unit_product_transport_cost[t] # [$/ton-mile] + * m.dist_site_to_market[site, mkt] / 1000 # [mile], [million/1000] for t in m.time) @m.Expression(doc="million $") def product_fixed_transport_cost(m): - """_summary_ + """ + Calculate the fixed cost of transporting products to the markets based on the total number of active product routes and the fixed transportation cost. Parameters ---------- @@ -646,17 +653,18 @@ def product_fixed_transport_cost(m): Returns ------- - _type_ - _description_ + Pyomo.Expression + Sum of fixed transport costs, accounting for the activation of each route. """ return ( sum(m.product_route_active[site, mkt].binary_indicator_var for site in m.potential_sites for mkt in m.markets) - * m.transport_fixed_cost / 1000) + * m.transport_fixed_cost / 1000) # [thousand $] [million/1000] @m.Expression(m.potential_sites, m.time, doc="Cost of module setups in each month [million $]") def module_setup_cost(m, site, t): - """_summary_ + """ + Calculate the cost of setting up modules at a facility site 'site' at each time period using the unit module cost and the number of modules purchased. Parameters ---------- @@ -669,14 +677,15 @@ def module_setup_cost(m, site, t): Returns ------- - _type_ - _description_ + Pyomo.Expression + Total setup cost considering the quantity of modules purchased and the unit cost per time period. """ return m.modules_purchased[site, t] * m.module_unit_cost[t] @m.Expression(m.potential_sites, m.time, doc="Value of module teardowns in each month [million $]") def module_teardown_credit(m, site, t): - """_summary_ + """ + Calculate the value of tearing down modules at a facility site 'site' at each time period using the unit module cost and the number of modules sold. Parameters ---------- @@ -689,14 +698,15 @@ def module_teardown_credit(m, site, t): Returns ------- - _type_ - _description_ + Pyomo.Expression + Total teardown value considering the quantity of modules sold and the unit cost per time period. """ return m.modules_sold[site, t] * m.module_unit_cost[t] * m.teardown_value @m.Expression(m.potential_sites, doc="Conventional site salvage value") def conv_salvage_value(m, site): - """_summary_ + """ + Calculate the salvage value of a conventional facility site 'site' using the build cost, the discount factor for the last time period, and the conventional salvage value. Parameters ---------- @@ -707,8 +717,8 @@ def conv_salvage_value(m, site): Returns ------- - _type_ - _description_ + Pyomo.Expression + Salvage value of the conventional facility site 'site' considering the build cost, discount factor, and salvage value. """ return m.conv_build_cost[site] * m.discount_factor[m.time.last()] * m.conventional_salvage_value @@ -723,133 +733,140 @@ def conv_salvage_value(m, site): + summation(m.product_transport_cost) + summation(m.product_fixed_transport_cost) + 0, - sense=minimize) + sense=minimize, doc="Total cost [million $]") return m def _build_site_inactive_disjunct(disj, site): - """_summary_ + """ + Configure the disjunct for a facility site marked as inactive. Parameters ---------- disj : Pyomo.Disjunct - _description_ + Pyomo disjunct for inactive site site : int Index of the facility site from 1 to 12 Returns ------- - _type_ - _description_ + None + None, but adds constraints to the disjunct """ m = disj.model() @disj.Constraint() def no_modules(disj): - """_summary_ + """ + Ensure that there are no modules at the inactive site. Parameters ---------- disj : Pyomo.Disjunct - _description_ + The disjunct object defining constraints for the inactive site Returns ------- - _type_ - _description_ + Pyomo.Constraint + The constraint that there are no modules at the inactive site """ return sum(m.num_modules[...]) + sum(m.modules_purchased[...]) + sum(m.modules_sold[...]) == 0 @disj.Constraint() def no_production(disj): - """_summary_ + """ + Ensure that there is no production at the inactive site. Parameters ---------- disj : Pyomo.Disjunct - _description_ + The disjunct object defining constraints for the inactive site Returns ------- - _type_ - _description_ + Pyomo.Constraint + The constraint that there is no production at the inactive site. """ return sum(m.production[site, t] for t in m.time) == 0 @disj.Constraint() def no_supply(disj): - """_summary_ + """ + Ensure that there is no supply at the inactive site. Parameters ---------- disj : Pyomo.Disjunct - _description_ + The disjunct object defining constraints for the inactive site Returns ------- - _type_ - _description_ + Pyomo.Constraint + The constraint that there is no supply at the inactive site. """ return sum(m.supply[site, t] for t in m.time) == 0 def _build_conventional_disjunct(disj, site): - """_summary_ + """ + Configure the disjunct for a conventional facility site. Parameters ---------- disj : Pyomo.Disjunct - _description_ + The disjunct object associated with a conventional site. site : int Index of the facility site from 1 to 12 Returns ------- - _type_ - _description_ + None + None, but adds constraints to the disjunct """ m = disj.model() disj.cost_calc = Constraint( expr=m.conv_build_cost[site] == ( - m.conv_base_cost * (m.conv_site_size[site] / 10) ** m.conv_exponent)) + m.conv_base_cost * (m.conv_site_size[site] / 10) ** m.conv_exponent), doc="the build cost for the conventional facility") # m.bigM[disj.cost_calc] = 7000 @disj.Constraint(m.time) def supply_balance(disj, t): - """_summary_ + """ + Ensure that the supply at the conventional site meets the supply shipments from the suppliers. Parameters ---------- disj : Pyomo.Disjunct - _description_ + The disjunct object for a conventional site. t : int Index of time in months from 0 to 120 (10 years) Returns ------- - _type_ - _description_ + Pyomo.Constraint + A constraint that the total supply at the site during each time period equals the total shipments received. """ return m.supply[site, t] == sum( m.supply_shipments[sup, site, t] for sup in m.suppliers) @disj.Constraint(m.time) def conv_production_limit(conv_disj, t): - """_summary_ + """ + Limit the production at the site based on its capacity. No production is allowed before the setup time. Parameters ---------- conv_disj : Pyomo.Disjunct - _description_ + The disjunct object for a conventional site. t : int Index of time in months from 0 to 120 (10 years) Returns ------- - _type_ - _description_ + Pyomo.Constraint + A constraint that limits production to the site's capacity after setup and prohibits production before setup. """ if t < m.conv_setup_time: return m.production[site, t] == 0 @@ -858,109 +875,116 @@ def conv_production_limit(conv_disj, t): @disj.Constraint() def no_modules(disj): - """_summary_ + """ + Ensure no modular units are present, purchased, or sold at the conventional site. Parameters ---------- disj : Pyomo.Disjunct - _description_ + The disjunct object for a conventional site. Returns ------- - _type_ - _description_ + Pyomo.Constraint + A constraint that the number of modules (present, purchased, sold) at the site is zero. """ return sum(m.num_modules[...]) + sum(m.modules_purchased[...]) + sum(m.modules_sold[...]) == 0 def _build_modular_disjunct(disj, site): - """_summary_ + """ + Configure the disjunct for a modular facility site. Parameters ---------- disj : Pyomo.Disjunct - _description_ + The disjunct object associated with a modular site. site : int Index of the facility site from 1 to 12 Returns ------- - _type_ - _description_ + None + None, but adds constraints to the disjunct """ m = disj.model() @disj.Constraint(m.time) def supply_balance(disj, t): - """_summary_ + """ + Ensure that the supply at the modular site meets the supply shipments from the suppliers. Parameters ---------- disj : Pyomo.Disjunct - _description_ + The disjunct object for a modular site. t : int Index of time in months from 0 to 120 (10 years) Returns ------- - _type_ - _description_ + Pyomo.Constraint + A constraint that the total supply at the site during each time period equals the total shipments received. """ return m.supply[site, t] == sum( m.supply_shipments[sup, site, t] for sup in m.suppliers) @disj.Constraint(m.time) def module_balance(disj, t): - """_summary_ + """ + Ensure that the number of modules at the site is consistent with the number of modules purchased and sold. Parameters ---------- disj : Pyomo.Disjunct - _description_ + The disjunct object for a modular site. t : int Index of time in months from 0 to 120 (10 years) Returns ------- - _type_ - _description_ + Pyomo.Constraint + A constraint that maintains the number of modules based on previous balances, new purchases, and modules sold. """ existing_modules = 0 if t == m.time.first() else m.num_modules[site, t - 1] new_modules = 0 if t < m.modular_setup_time else m.modules_purchased[site, t - m.modular_setup_time] sold_modules = m.modules_sold[site, t] return m.num_modules[site, t] == existing_modules + new_modules - sold_modules + # Fix the number of modules to zero during the setup time for t in range(value(m.modular_setup_time)): m.num_modules[site, t].fix(0) @disj.Constraint(m.time) def modular_production_limit(mod_disj, t): - """_summary_ + """ + Limit the production at the site based on the number of modules present. No production is allowed before the setup time. Parameters ---------- mod_disj : Pyomo.Disjunct - _description_ + The disjunct object for a modular site. t : int Index of time in months from 0 to 120 (10 years) Returns ------- - _type_ - _description_ + Pyomo.Constraint + A constraint that limits production to the site's capacity after setup and prohibits production before setup. """ return m.production[site, t] <= 10 * m.num_modules[site, t] def _build_supply_route_active(disj, sup, site): - """_summary_ + """ + Build the disjunct for an active supply route from a supplier to a facility site. Parameters ---------- disj : Pyomo.Disjunct - _description_ - sup : _type_ - _description_ + The disjunct object for an active supply route + sup : int + Index of the supplier from 1 to 10 site : int Index of the facility site from 1 to 12 """ @@ -968,48 +992,51 @@ def _build_supply_route_active(disj, sup, site): def _build_supply_route_inactive(disj, sup, site): - """_summary_ + """ + Build the disjunct for an inactive supply route from a supplier to a facility site. Parameters ---------- disj : Pyomo.Disjunct - _description_ - sup : _type_ - _description_ + The disjunct object for an inactive supply route + sup : int + Index of the supplier from 1 to 10 site : int Index of the facility site from 1 to 12 Returns ------- - _type_ - _description_ + None + None, but adds constraints to the disjunct """ m = disj.model() @disj.Constraint() def no_supply(disj): - """_summary_ + """ + Ensure that there are no supply shipments from the supplier to the site. Parameters ---------- disj : Pyomo.Disjunct - _description_ + The disjunct object for an inactive supply route Returns ------- - _type_ - _description_ + Pyomo.Constraint + A constraint that there are no supply shipments from the supplier to the site. """ return sum(m.supply_shipments[sup, site, t] for t in m.time) == 0 def _build_product_route_active(disj, site, mkt): - """_summary_ + """ + Build the disjunct for an active product route from a facility site to a market. Parameters ---------- disj : Pyomo.Disjunct - _description_ + The disjunct object for an active product route site : int Index of the facility site from 1 to 12 mkt : int @@ -1019,12 +1046,13 @@ def _build_product_route_active(disj, site, mkt): def _build_product_route_inactive(disj, site, mkt): - """_summary_ + """ + Build the disjunct for an inactive product route from a facility site to a market. Parameters ---------- disj : Pyomo.Disjunct - _description_ + The disjunct object for an inactive product route site : int Index of the facility site from 1 to 12 mkt : int @@ -1032,14 +1060,15 @@ def _build_product_route_inactive(disj, site, mkt): Returns ------- - _type_ - _description_ + None + None, but adds constraints to the disjunct """ m = disj.model() @disj.Constraint() def no_product(disj): - """_summary_ + """ + Ensure that there are no product shipments from the site to the market. Parameters ---------- @@ -1048,8 +1077,8 @@ def no_product(disj): Returns ------- - _type_ - _description_ + Pyomo.Constraint + A constraint that there are no product shipments from the site to the market. """ return sum(m.product_shipments[site, mkt, t] for t in m.time) == 0 From 9c442c6a3486beeaf5b382ffacb73137a841a0f4 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 23 Apr 2024 23:50:44 -0400 Subject: [PATCH 08/60] Black Formatting --- gdplib/biofuel/model.py | 431 +++++++++++++++++++++++++++++----------- 1 file changed, 316 insertions(+), 115 deletions(-) diff --git a/gdplib/biofuel/model.py b/gdplib/biofuel/model.py index 0cfd8f1..05eeb16 100644 --- a/gdplib/biofuel/model.py +++ b/gdplib/biofuel/model.py @@ -5,8 +5,22 @@ import pandas as pd from pyomo.environ import ( - ConcreteModel, Constraint, Integers, minimize, NonNegativeReals, Objective, Param, RangeSet, SolverFactory, sqrt, - Suffix, summation, TransformationFactory, value, Var, ) + ConcreteModel, + Constraint, + Integers, + minimize, + NonNegativeReals, + Objective, + Param, + RangeSet, + SolverFactory, + sqrt, + Suffix, + summation, + TransformationFactory, + value, + Var, +) from pyomo.gdp import Disjunct @@ -26,9 +40,9 @@ def build_model(): m = ConcreteModel() m.bigM = Suffix(direction=Suffix.LOCAL) m.time = RangeSet(0, 120, doc="months in 10 years") - m.suppliers = RangeSet(10) # 10 suppliers - m.markets = RangeSet(10) # 10 markets - m.potential_sites = RangeSet(12) # 12 facility sites + m.suppliers = RangeSet(10) # 10 suppliers + m.markets = RangeSet(10) # 10 markets + m.potential_sites = RangeSet(12) # 12 facility sites m.discount_rate = Param(initialize=0.08, doc="discount rate [8%]") m.conv_setup_time = Param(initialize=12) m.modular_setup_time = Param(initialize=3) @@ -58,7 +72,8 @@ def discount_factor(m, t): xls_data = pd.read_excel( os.path.join(os.path.dirname(__file__), "problem_data.xlsx"), sheet_name=["sources", "markets", "sites", "growth", "decay"], - index_col=0) + index_col=0, + ) @m.Param(m.markets, m.time, doc="Market demand [thousand ton/month]") def market_demand(m, mkt, t): @@ -245,8 +260,10 @@ def dist_supplier_to_site(m, sup, site): Pyomo.Parameter The distance in miles between the supplier and the facility site """ - return sqrt((m.supplier_x[sup] - m.site_x[site]) ** 2 + - (m.supplier_y[sup] - m.site_y[site]) ** 2) + return sqrt( + (m.supplier_x[sup] - m.site_x[site]) ** 2 + + (m.supplier_y[sup] - m.site_y[site]) ** 2 + ) @m.Param(m.potential_sites, m.markets, doc="Miles") def dist_site_to_market(m, site, mkt): @@ -267,30 +284,61 @@ def dist_site_to_market(m, site, mkt): Pyomo.Parameter The distance in miles between the facility site and the market """ - return sqrt((m.site_x[site] - m.market_x[mkt]) ** 2 + - (m.site_y[site] - m.market_y[mkt]) ** 2) + return sqrt( + (m.site_x[site] - m.market_x[mkt]) ** 2 + + (m.site_y[site] - m.market_y[mkt]) ** 2 + ) m.conversion = Param(initialize=0.26, doc="overall conversion to product") m.conv_site_size = Var( m.potential_sites, - bounds=(120 / 12 / 10, 120 / 12), initialize=1, - doc="Product capacity of site [thousand ton/mo]") + bounds=(120 / 12 / 10, 120 / 12), + initialize=1, + doc="Product capacity of site [thousand ton/mo]", + ) - m.conv_base_cost = Param(initialize=268.4, doc="Cost for size 120k per year [million $]") - m.module_base_cost = Param(initialize=268.4, doc="Cost for size 120k per year [million $]") + m.conv_base_cost = Param( + initialize=268.4, doc="Cost for size 120k per year [million $]" + ) + m.module_base_cost = Param( + initialize=268.4, doc="Cost for size 120k per year [million $]" + ) m.conv_exponent = Param(initialize=0.7) - m.supply = Var(m.potential_sites, m.time, bounds=(0, 120 / 12 / 0.26 * 10), doc="thousand ton/mo") - m.production = Var(m.potential_sites, m.time, bounds=(0, 120 / 12 * 10), doc="thousand ton/mo") - m.num_modules = Var(m.potential_sites, m.time, domain=Integers, bounds=(0, 10), doc="Number of modules") - m.modules_purchased = Var(m.potential_sites, m.time, domain=Integers, bounds=(0, 10), doc="Modules purchased") - m.modules_sold = Var(m.potential_sites, m.time, domain=Integers, bounds=(0, 10), doc="Modules sold") + m.supply = Var( + m.potential_sites, + m.time, + bounds=(0, 120 / 12 / 0.26 * 10), + doc="thousand ton/mo", + ) + m.production = Var( + m.potential_sites, m.time, bounds=(0, 120 / 12 * 10), doc="thousand ton/mo" + ) + m.num_modules = Var( + m.potential_sites, + m.time, + domain=Integers, + bounds=(0, 10), + doc="Number of modules", + ) + m.modules_purchased = Var( + m.potential_sites, + m.time, + domain=Integers, + bounds=(0, 10), + doc="Modules purchased", + ) + m.modules_sold = Var( + m.potential_sites, m.time, domain=Integers, bounds=(0, 10), doc="Modules sold" + ) m.conv_build_cost = Var( m.potential_sites, doc="Cost of building conventional facility [milllion $]", - bounds=(0, 1350 * 10), initialize=0) + bounds=(0, 1350 * 10), + initialize=0, + ) @m.Param(m.suppliers, m.time) def raw_material_unit_cost(m, sup, t): @@ -407,11 +455,21 @@ def unit_raw_material_transport_cost(m, t): return 2 * m.discount_factor[t] m.supply_shipments = Var( - m.suppliers, m.potential_sites, m.time, domain=NonNegativeReals, - bounds=(0, 120 / 12 / 0.26), doc="thousand ton/mo") + m.suppliers, + m.potential_sites, + m.time, + domain=NonNegativeReals, + bounds=(0, 120 / 12 / 0.26), + doc="thousand ton/mo", + ) m.product_shipments = Var( - m.potential_sites, m.markets, m.time, domain=NonNegativeReals, - bounds=(0, 120 / 12), doc="thousand ton/mo") + m.potential_sites, + m.markets, + m.time, + domain=NonNegativeReals, + bounds=(0, 120 / 12), + doc="thousand ton/mo", + ) @m.Constraint(m.suppliers, m.time) def supply_limits(m, sup, t): @@ -432,8 +490,10 @@ def supply_limits(m, sup, t): Pyomo.Constraint The total supply from the supplier 'sup' at time 't' should not exceed the available supply from the supplier. """ - return sum(m.supply_shipments[sup, site, t] - for site in m.potential_sites) <= m.available_supply[sup, t] + return ( + sum(m.supply_shipments[sup, site, t] for site in m.potential_sites) + <= m.available_supply[sup, t] + ) @m.Constraint(m.markets, m.time) def demand_satisfaction(m, mkt, t): @@ -454,8 +514,10 @@ def demand_satisfaction(m, mkt, t): Pyomo.Constraint The total product shipments to the market 'mkt' at time 't' should meet the market demand for the product. """ - return sum(m.product_shipments[site, mkt, t] - for site in m.potential_sites) == m.market_demand[mkt, t] + return ( + sum(m.product_shipments[site, mkt, t] for site in m.potential_sites) + == m.market_demand[mkt, t] + ) @m.Constraint(m.potential_sites, m.time) def product_balance(m, site, t): @@ -476,8 +538,9 @@ def product_balance(m, site, t): Pyomo.Constraint The total product shipments from the facility site 'site' at time 't' should meet the production from the site. """ - return m.production[site, t] == sum(m.product_shipments[site, mkt, t] - for mkt in m.markets) + return m.production[site, t] == sum( + m.product_shipments[site, mkt, t] for mkt in m.markets + ) @m.Constraint(m.potential_sites, m.time) def require_raw_materials(m, site, t): @@ -500,15 +563,45 @@ def require_raw_materials(m, site, t): """ return m.production[site, t] <= m.conversion * m.supply[site, t] - m.modular = Disjunct(m.potential_sites, rule=_build_modular_disjunct, doc="Disjunct for modular site") - m.conventional = Disjunct(m.potential_sites, rule=_build_conventional_disjunct, doc="Disjunct for conventional site") - m.site_inactive = Disjunct(m.potential_sites, rule=_build_site_inactive_disjunct, doc="Disjunct for inactive site") + m.modular = Disjunct( + m.potential_sites, rule=_build_modular_disjunct, doc="Disjunct for modular site" + ) + m.conventional = Disjunct( + m.potential_sites, + rule=_build_conventional_disjunct, + doc="Disjunct for conventional site", + ) + m.site_inactive = Disjunct( + m.potential_sites, + rule=_build_site_inactive_disjunct, + doc="Disjunct for inactive site", + ) - m.supply_route_active = Disjunct(m.suppliers, m.potential_sites, rule=_build_supply_route_active, doc="Disjunct for active supply route") - m.supply_route_inactive = Disjunct(m.suppliers, m.potential_sites, rule=_build_supply_route_inactive, doc="Disjunct for inactive supply route") + m.supply_route_active = Disjunct( + m.suppliers, + m.potential_sites, + rule=_build_supply_route_active, + doc="Disjunct for active supply route", + ) + m.supply_route_inactive = Disjunct( + m.suppliers, + m.potential_sites, + rule=_build_supply_route_inactive, + doc="Disjunct for inactive supply route", + ) - m.product_route_active = Disjunct(m.potential_sites, m.markets, rule=_build_product_route_active, doc="Disjunct for active product route") - m.product_route_inactive = Disjunct(m.potential_sites, m.markets, rule=_build_product_route_inactive, doc="Disjunct for inactive product route") + m.product_route_active = Disjunct( + m.potential_sites, + m.markets, + rule=_build_product_route_active, + doc="Disjunct for active product route", + ) + m.product_route_inactive = Disjunct( + m.potential_sites, + m.markets, + rule=_build_product_route_inactive, + doc="Disjunct for inactive product route", + ) @m.Disjunction(m.potential_sites) def site_type(m, site): @@ -591,10 +684,12 @@ def raw_material_transport_cost(m, sup, site): Total transportation cost considering the quantity of shipments, unit cost per time period, and distance between suppliers and sites. """ return sum( - m.supply_shipments[sup, site, t] # [1000 ton/month] - * m.unit_raw_material_transport_cost[t] # [$/ton-mile] - * m.dist_supplier_to_site[sup, site] / 1000 # [mile], [million/1000] - for t in m.time) + m.supply_shipments[sup, site, t] # [1000 ton/month] + * m.unit_raw_material_transport_cost[t] # [$/ton-mile] + * m.dist_supplier_to_site[sup, site] + / 1000 # [mile], [million/1000] + for t in m.time + ) @m.Expression(doc="million $") def raw_material_fixed_transport_cost(m): @@ -612,9 +707,14 @@ def raw_material_fixed_transport_cost(m): Sum of fixed transport costs, accounting for the activation of each route. """ return ( - sum(m.supply_route_active[sup, site].binary_indicator_var - for sup in m.suppliers for site in m.potential_sites) - * m.transport_fixed_cost / 1000) # [thousand $] [million/1000] + sum( + m.supply_route_active[sup, site].binary_indicator_var + for sup in m.suppliers + for site in m.potential_sites + ) + * m.transport_fixed_cost + / 1000 + ) # [thousand $] [million/1000] @m.Expression(m.potential_sites, m.markets, doc="million $") def product_transport_cost(m, site, mkt): @@ -636,10 +736,12 @@ def product_transport_cost(m, site, mkt): Total transportation cost considering the quantity of shipments, unit cost per time period, and distance between sites and markets. """ return sum( - m.product_shipments[site, mkt, t] # [1000 ton/month] - * m.unit_product_transport_cost[t] # [$/ton-mile] - * m.dist_site_to_market[site, mkt] / 1000 # [mile], [million/1000] - for t in m.time) + m.product_shipments[site, mkt, t] # [1000 ton/month] + * m.unit_product_transport_cost[t] # [$/ton-mile] + * m.dist_site_to_market[site, mkt] + / 1000 # [mile], [million/1000] + for t in m.time + ) @m.Expression(doc="million $") def product_fixed_transport_cost(m): @@ -657,11 +759,18 @@ def product_fixed_transport_cost(m): Sum of fixed transport costs, accounting for the activation of each route. """ return ( - sum(m.product_route_active[site, mkt].binary_indicator_var - for site in m.potential_sites for mkt in m.markets) - * m.transport_fixed_cost / 1000) # [thousand $] [million/1000] - - @m.Expression(m.potential_sites, m.time, doc="Cost of module setups in each month [million $]") + sum( + m.product_route_active[site, mkt].binary_indicator_var + for site in m.potential_sites + for mkt in m.markets + ) + * m.transport_fixed_cost + / 1000 + ) # [thousand $] [million/1000] + + @m.Expression( + m.potential_sites, m.time, doc="Cost of module setups in each month [million $]" + ) def module_setup_cost(m, site, t): """ Calculate the cost of setting up modules at a facility site 'site' at each time period using the unit module cost and the number of modules purchased. @@ -682,7 +791,11 @@ def module_setup_cost(m, site, t): """ return m.modules_purchased[site, t] * m.module_unit_cost[t] - @m.Expression(m.potential_sites, m.time, doc="Value of module teardowns in each month [million $]") + @m.Expression( + m.potential_sites, + m.time, + doc="Value of module teardowns in each month [million $]", + ) def module_teardown_credit(m, site, t): """ Calculate the value of tearing down modules at a facility site 'site' at each time period using the unit module cost and the number of modules sold. @@ -720,7 +833,11 @@ def conv_salvage_value(m, site): Pyomo.Expression Salvage value of the conventional facility site 'site' considering the build cost, discount factor, and salvage value. """ - return m.conv_build_cost[site] * m.discount_factor[m.time.last()] * m.conventional_salvage_value + return ( + m.conv_build_cost[site] + * m.discount_factor[m.time.last()] + * m.conventional_salvage_value + ) m.total_cost = Objective( expr=0 @@ -733,7 +850,9 @@ def conv_salvage_value(m, site): + summation(m.product_transport_cost) + summation(m.product_fixed_transport_cost) + 0, - sense=minimize, doc="Total cost [million $]") + sense=minimize, + doc="Total cost [million $]", + ) return m @@ -771,7 +890,12 @@ def no_modules(disj): Pyomo.Constraint The constraint that there are no modules at the inactive site """ - return sum(m.num_modules[...]) + sum(m.modules_purchased[...]) + sum(m.modules_sold[...]) == 0 + return ( + sum(m.num_modules[...]) + + sum(m.modules_purchased[...]) + + sum(m.modules_sold[...]) + == 0 + ) @disj.Constraint() def no_production(disj): @@ -827,8 +951,10 @@ def _build_conventional_disjunct(disj, site): m = disj.model() disj.cost_calc = Constraint( - expr=m.conv_build_cost[site] == ( - m.conv_base_cost * (m.conv_site_size[site] / 10) ** m.conv_exponent), doc="the build cost for the conventional facility") + expr=m.conv_build_cost[site] + == (m.conv_base_cost * (m.conv_site_size[site] / 10) ** m.conv_exponent), + doc="the build cost for the conventional facility", + ) # m.bigM[disj.cost_calc] = 7000 @disj.Constraint(m.time) @@ -849,7 +975,8 @@ def supply_balance(disj, t): A constraint that the total supply at the site during each time period equals the total shipments received. """ return m.supply[site, t] == sum( - m.supply_shipments[sup, site, t] for sup in m.suppliers) + m.supply_shipments[sup, site, t] for sup in m.suppliers + ) @disj.Constraint(m.time) def conv_production_limit(conv_disj, t): @@ -888,7 +1015,12 @@ def no_modules(disj): Pyomo.Constraint A constraint that the number of modules (present, purchased, sold) at the site is zero. """ - return sum(m.num_modules[...]) + sum(m.modules_purchased[...]) + sum(m.modules_sold[...]) == 0 + return ( + sum(m.num_modules[...]) + + sum(m.modules_purchased[...]) + + sum(m.modules_sold[...]) + == 0 + ) def _build_modular_disjunct(disj, site): @@ -927,7 +1059,8 @@ def supply_balance(disj, t): A constraint that the total supply at the site during each time period equals the total shipments received. """ return m.supply[site, t] == sum( - m.supply_shipments[sup, site, t] for sup in m.suppliers) + m.supply_shipments[sup, site, t] for sup in m.suppliers + ) @disj.Constraint(m.time) def module_balance(disj, t): @@ -947,7 +1080,11 @@ def module_balance(disj, t): A constraint that maintains the number of modules based on previous balances, new purchases, and modules sold. """ existing_modules = 0 if t == m.time.first() else m.num_modules[site, t - 1] - new_modules = 0 if t < m.modular_setup_time else m.modules_purchased[site, t - m.modular_setup_time] + new_modules = ( + 0 + if t < m.modular_setup_time + else m.modules_purchased[site, t - m.modular_setup_time] + ) sold_modules = m.modules_sold[site, t] return m.num_modules[site, t] == existing_modules + new_modules - sold_modules @@ -1107,45 +1244,72 @@ def print_nonzeros(var): TransformationFactory('gdp.bigm').apply_to(m, bigM=7000) # res = SolverFactory('gurobi').solve(m, tee=True) res = SolverFactory('gams').solve( - m, tee=True, + m, + tee=True, solver='scip', # solver='gurobi', # add_options=['option reslim = 1200;', 'option optcr=0.0001;'], - add_options=[ - 'option reslim = 1200;', - 'OPTION threads=4;', - 'option optcr=0.01', - ], - ) + add_options=['option reslim = 1200;', 'OPTION threads=4;', 'option optcr=0.01'], + ) # res = SolverFactory('gdpopt').solve( # m, tee=True, # iterlim=2, # mip_solver='gams', # mip_solver_args=dict(add_options=['option reslim = 30;'])) - results = pd.DataFrame([ - ['Total Cost', value(m.total_cost)], - ['Conv Build Cost', value(summation(m.conv_build_cost))], - ['Conv Salvage Value', value(summation(m.conv_salvage_value))], - ['Module Build Cost', value(summation(m.module_setup_cost))], - ['Module Salvage Value', value(summation(m.module_teardown_credit))], - ['Raw Material Transport', value(summation(m.raw_material_transport_cost) + summation(m.raw_material_fixed_transport_cost))], - ['Product Transport', value(summation(m.product_transport_cost) + summation(m.product_fixed_transport_cost))] - ], columns=['Quantity', 'Value [million $]']).set_index('Quantity').round(0) + results = ( + pd.DataFrame( + [ + ['Total Cost', value(m.total_cost)], + ['Conv Build Cost', value(summation(m.conv_build_cost))], + ['Conv Salvage Value', value(summation(m.conv_salvage_value))], + ['Module Build Cost', value(summation(m.module_setup_cost))], + ['Module Salvage Value', value(summation(m.module_teardown_credit))], + [ + 'Raw Material Transport', + value( + summation(m.raw_material_transport_cost) + + summation(m.raw_material_fixed_transport_cost) + ), + ], + [ + 'Product Transport', + value( + summation(m.product_transport_cost) + + summation(m.product_fixed_transport_cost) + ), + ], + ], + columns=['Quantity', 'Value [million $]'], + ) + .set_index('Quantity') + .round(0) + ) print(results) - df = pd.DataFrame([ + df = pd.DataFrame( [ - site, t, - value(m.num_modules[site, t]), - value(m.modules_purchased[site, t]), - value(m.modules_sold[site, t]), - value(m.module_setup_cost[site, t]), - value(m.module_teardown_credit[site, t]), - value(m.production[site, t])] for site, t in m.potential_sites * m.time + [ + site, + t, + value(m.num_modules[site, t]), + value(m.modules_purchased[site, t]), + value(m.modules_sold[site, t]), + value(m.module_setup_cost[site, t]), + value(m.module_teardown_credit[site, t]), + value(m.production[site, t]), + ] + for site, t in m.potential_sites * m.time ], - columns=("Site", "Month", "Num Modules", "Buy Modules", - "Sell Modules", - "Setup Cost", "Teardown Credit", "Production") + columns=( + "Site", + "Month", + "Num Modules", + "Buy Modules", + "Sell Modules", + "Setup Cost", + "Teardown Credit", + "Production", + ), ) df.to_excel("facility_config.xlsx") @@ -1153,42 +1317,79 @@ def print_nonzeros(var): # exit() import matplotlib.pyplot as plt - plt.plot([x for x in m.site_x.values()], - [y for y in m.site_y.values()], 'k.', markersize=12) - plt.plot([x for x in m.market_x.values()], - [y for y in m.market_y.values()], 'b.', markersize=12) - plt.plot([x for x in m.supplier_x.values()], - [y for y in m.supplier_y.values()], 'r.', markersize=12) + plt.plot( + [x for x in m.site_x.values()], + [y for y in m.site_y.values()], + 'k.', + markersize=12, + ) + plt.plot( + [x for x in m.market_x.values()], + [y for y in m.market_y.values()], + 'b.', + markersize=12, + ) + plt.plot( + [x for x in m.supplier_x.values()], + [y for y in m.supplier_y.values()], + 'r.', + markersize=12, + ) for mkt in m.markets: - plt.annotate('m%s' % mkt, (m.market_x[mkt], m.market_y[mkt]), - (m.market_x[mkt] + 2, m.market_y[mkt] + 2), - fontsize='x-small') + plt.annotate( + 'm%s' % mkt, + (m.market_x[mkt], m.market_y[mkt]), + (m.market_x[mkt] + 2, m.market_y[mkt] + 2), + fontsize='x-small', + ) for site in m.potential_sites: if m.site_inactive[site].binary_indicator_var.value == 0: plt.annotate( - 'p%s' % site, (m.site_x[site], m.site_y[site]), + 'p%s' % site, + (m.site_x[site], m.site_y[site]), (m.site_x[site] + 2, m.site_y[site] + 2), - fontsize='x-small') + fontsize='x-small', + ) else: plt.annotate( - 'x%s' % site, (m.site_x[site], m.site_y[site]), + 'x%s' % site, + (m.site_x[site], m.site_y[site]), (m.site_x[site] + 2, m.site_y[site] + 2), - fontsize='x-small') + fontsize='x-small', + ) for sup in m.suppliers: plt.annotate( - 's%s' % sup, (m.supplier_x[sup], m.supplier_y[sup]), + 's%s' % sup, + (m.supplier_x[sup], m.supplier_y[sup]), (m.supplier_x[sup] + 2, m.supplier_y[sup] + 2), - fontsize='x-small') + fontsize='x-small', + ) for sup, site in m.suppliers * m.potential_sites: - if fabs(m.supply_route_active[sup, site].binary_indicator_var.value - 1) <= 1E-3: - plt.arrow(m.supplier_x[sup], m.supplier_y[sup], - m.site_x[site] - m.supplier_x[sup], - m.site_y[site] - m.supplier_y[sup], - width=0.8, length_includes_head=True, color='b') + if ( + fabs(m.supply_route_active[sup, site].binary_indicator_var.value - 1) + <= 1e-3 + ): + plt.arrow( + m.supplier_x[sup], + m.supplier_y[sup], + m.site_x[site] - m.supplier_x[sup], + m.site_y[site] - m.supplier_y[sup], + width=0.8, + length_includes_head=True, + color='b', + ) for site, mkt in m.potential_sites * m.markets: - if fabs(m.product_route_active[site, mkt].binary_indicator_var.value - 1) <= 1E-3: - plt.arrow(m.site_x[site], m.site_y[site], - m.market_x[mkt] - m.site_x[site], - m.market_y[mkt] - m.site_y[site], - width=0.8, length_includes_head=True, color='r') + if ( + fabs(m.product_route_active[site, mkt].binary_indicator_var.value - 1) + <= 1e-3 + ): + plt.arrow( + m.site_x[site], + m.site_y[site], + m.market_x[mkt] - m.site_x[site], + m.market_y[mkt] - m.site_y[site], + width=0.8, + length_includes_head=True, + color='r', + ) plt.show() From a43dcb20f1c58647b5050e16f3e126744945c80c Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Wed, 24 Apr 2024 12:12:33 -0400 Subject: [PATCH 09/60] Reform the documentation of cafaro_approx.py into NumPy Style. --- gdplib/mod_hens/cafaro_approx.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/gdplib/mod_hens/cafaro_approx.py b/gdplib/mod_hens/cafaro_approx.py index 89d4491..28e4518 100644 --- a/gdplib/mod_hens/cafaro_approx.py +++ b/gdplib/mod_hens/cafaro_approx.py @@ -1,7 +1,7 @@ """Cafaro approximation parameter estimation. Rather than use the cost relation (1), Cafaro & Grossmann, 2014 (DOI: -10.1016/j.compchemeng.2013.10.001) proposes using (2), which has much better +https://doi.org/10.1016/j.compchemeng.2013.10.001) proposes using (2), which has much better behaved derivative values near x=0. However, we need to use parameter estimation in order to derive the correct values of k and b. @@ -17,7 +17,8 @@ def calculate_cafaro_coefficients(area1, area2, exponent): - """Calculate the coefficients for the Cafaro approximation. + """ + Calculate the coefficients for the Cafaro approximation. Gives the coefficients k and b to approximate a function x^exponent such that at the given areas, the following relations apply: @@ -25,10 +26,23 @@ def calculate_cafaro_coefficients(area1, area2, exponent): area1 ^ exponent = k * ln(b * area1 + 1) area2 ^ exponent = k * ln(b * area2 + 1) - Args: - area1 (float): area to use as the first regression point - area2 (float): area to use as the second regression point - exponent (float): exponent to approximate + Parameters + ---------- + area1 : float + The area to use as the first regression point. + area2 : float + The area to use as the second regression point. + exponent : float + The exponent to approximate. + + Returns + ------- + tuple of float + A tuple containing the coefficients `k` and `b`. + + References + ---------- + [1] Cafaro, D. C., & Grossmann, I. E. (2014). Alternate approximation of concave cost functions for process design and supply chain optimization problems. Computers & chemical engineering, 60, 376-380. https://doi.org/10.1016/j.compchemeng.2013.10.001 """ m = ConcreteModel() m.k = Var(domain=NonNegativeReals) From 99f9460e25aac343f0af9fa472c303ddf51fdf5a Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 25 Apr 2024 12:12:52 -0400 Subject: [PATCH 10/60] Set up parameters documentatioin on common.py --- gdplib/mod_hens/common.py | 75 ++++++++++++++++++++++++++------- gdplib/mod_hens/conventional.py | 14 ++++++ 2 files changed, 73 insertions(+), 16 deletions(-) diff --git a/gdplib/mod_hens/common.py b/gdplib/mod_hens/common.py index c3203c7..e6c0634 100644 --- a/gdplib/mod_hens/common.py +++ b/gdplib/mod_hens/common.py @@ -17,21 +17,36 @@ def build_model(use_cafaro_approximation, num_stages): - """Build the model.""" + """ + Constructs a Pyomo concrete model for heat integration optimization. This model incorporates various components including process and utility streams, heat exchangers, and stages of heat exchange, with optional application of the Cafaro approximation for certain calculations. + + Parameters + ---------- + use_cafaro_approximation : bool + A Boolean flag indicating whether the Cafaro approximation method should be used + to calculate certain coefficients in the model + num_stages : int + The number of stages in the heat exchange model. + + Returns + ------- + Pyomo.ConcreteModel + A Pyomo concrete model representing the heat exchange model based on the specified number of stages and the use of Cafaro approximation, if applicable. The model is ready to be solved using an optimization solver to determine optimal heat integration strategies. + """ m = ConcreteModel() - m.hot_process_streams = Set(initialize=['H1', 'H2']) - m.cold_process_streams = Set(initialize=['C1', 'C2']) - m.process_streams = m.hot_process_streams | m.cold_process_streams - m.hot_utility_streams = Set(initialize=['steam']) - m.cold_utility_streams = Set(initialize=['water']) + m.hot_process_streams = Set(initialize=['H1', 'H2'], doc="Hot process streams") + m.cold_process_streams = Set(initialize=['C1', 'C2'], doc="Cold process streams") + m.process_streams = m.hot_process_streams | m.cold_process_streams # All process streams + m.hot_utility_streams = Set(initialize=['steam'], doc="Hot utility streams") + m.cold_utility_streams = Set(initialize=['water'], doc="Cold utility streams") m.hot_streams = Set( - initialize=m.hot_process_streams | m.hot_utility_streams) + initialize=m.hot_process_streams | m.hot_utility_streams, doc="Hot streams") m.cold_streams = Set( - initialize=m.cold_process_streams | m.cold_utility_streams) + initialize=m.cold_process_streams | m.cold_utility_streams, doc="Cold streams") m.utility_streams = Set( - initialize=m.hot_utility_streams | m.cold_utility_streams) + initialize=m.hot_utility_streams | m.cold_utility_streams, doc="Utility streams") m.streams = Set( - initialize=m.process_streams | m.utility_streams) + initialize=m.process_streams | m.utility_streams, doc="All streams") m.valid_matches = Set( initialize=(m.hot_process_streams * m.cold_streams) | (m.hot_utility_streams * m.cold_process_streams), @@ -42,7 +57,7 @@ def build_model(use_cafaro_approximation, num_stages): # Unused right now, but could be used for variable bound tightening # in the LMTD calculation. - m.stages = RangeSet(num_stages) + m.stages = RangeSet(num_stages, doc="Number of stages") m.T_in = Param( m.streams, doc="Inlet temperature of stream [K]", @@ -80,13 +95,13 @@ def build_model(use_cafaro_approximation, num_stages): domain=NonNegativeReals, initialize=1, bounds=(0, 5000)) m.stage_entry_T = Var( m.streams, m.stages, - doc="Temperature of stream at stage entry.", + doc="Temperature of stream at stage entry [K].", initialize=350, bounds=(293, 450) # TODO set to be equal to min and max temps ) m.stage_exit_T = Var( m.streams, m.stages, - doc="Temperature of stream at stage exit.", + doc="Temperature of stream at stage exit [K].", initialize=350, bounds=(293, 450) # TODO set to be equal to min and max temps ) @@ -117,7 +132,7 @@ def build_model(use_cafaro_approximation, num_stages): doc="Annual unit cost of utilities [$/kW]", initialize={'steam': 80, 'water': 20}) - m.module_sizes = Set(initialize=[10, 50, 100]) + m.module_sizes = Set(initialize=[10, 50, 100], doc="Available module sizes.") m.max_num_modules = Param(m.module_sizes, initialize={ # 5: 100, 10: 50, @@ -127,14 +142,14 @@ def build_model(use_cafaro_approximation, num_stages): }, doc="maximum number of each module size available.") m.exchanger_fixed_unit_cost = Param( - m.valid_matches, default=2000) + m.valid_matches, default=2000, doc="exchanger fixed cost [$/kW]",) m.exchanger_area_cost_factor = Param( m.valid_matches, default=1000, initialize={ ('steam', cold): 1200 for cold in m.cold_process_streams}, doc="1200 for heaters. 1000 for all other exchangers.") - m.area_cost_exponent = Param(default=0.6) + m.area_cost_exponent = Param(default=0.6, doc="Area cost exponent.") if use_cafaro_approximation: k, b = calculate_cafaro_coefficients(10, 500, m.area_cost_exponent) @@ -144,6 +159,24 @@ def build_model(use_cafaro_approximation, num_stages): @m.Param(m.valid_matches, m.module_sizes, doc="Area cost factor for modular exchangers.") def module_area_cost_factor(m, hot, cold, area): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + + hot : _type_ + _description_ + cold : _type_ + _description_ + area : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ if hot == 'steam': return 1300 else: @@ -397,6 +430,16 @@ def utility_cost(m, strm): def _fix_and_bound(var, val): + """ + Fix a Pyomo variable to a value and set bounds to that value. + + Parameters + ---------- + var : Pyomo.Var + The Pyomo variable to be fixed. + val : float + The value to fix the variable to. This value will also be used to set both the lower and upper bounds of the variable. + """ var.fix(val) var.setlb(val) var.setub(val) diff --git a/gdplib/mod_hens/conventional.py b/gdplib/mod_hens/conventional.py index 00046a9..9550d10 100644 --- a/gdplib/mod_hens/conventional.py +++ b/gdplib/mod_hens/conventional.py @@ -15,6 +15,20 @@ def build_conventional(cafaro_approx, num_stages): + """_summary_ + + Parameters + ---------- + cafaro_approx : _type_ + _description_ + num_stages : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return build_model(cafaro_approx, num_stages) From 56ff1d84f3fca9c289fd02a02b7bff2dca507717 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 25 Apr 2024 23:12:51 -0400 Subject: [PATCH 11/60] Update models to account for Pyomo logical expression system and Boolean indicator_vars --- gdplib/mod_hens/common.py | 300 ++++++++++++++++++++++++++++++++++---- 1 file changed, 273 insertions(+), 27 deletions(-) diff --git a/gdplib/mod_hens/common.py b/gdplib/mod_hens/common.py index e6c0634..3177227 100644 --- a/gdplib/mod_hens/common.py +++ b/gdplib/mod_hens/common.py @@ -151,7 +151,7 @@ def build_model(use_cafaro_approximation, num_stages): doc="1200 for heaters. 1000 for all other exchangers.") m.area_cost_exponent = Param(default=0.6, doc="Area cost exponent.") - if use_cafaro_approximation: + if use_cafaro_approximation: # Use Cafaro approximation for coefficients if True k, b = calculate_cafaro_coefficients(10, 500, m.area_cost_exponent) m.cafaro_k = Param(default=k) m.cafaro_b = Param(default=b) @@ -159,35 +159,55 @@ def build_model(use_cafaro_approximation, num_stages): @m.Param(m.valid_matches, m.module_sizes, doc="Area cost factor for modular exchangers.") def module_area_cost_factor(m, hot, cold, area): - """_summary_ + """ + Determines the area cost factor for modular exchangers within the heat integration model. The cost factor is based on the specified module size and stream pair, with different values for steam and other hot streams. The unit is [$/(m^2)^0.6]. Parameters ---------- m : Pyomo.ConcreteModel - - hot : _type_ - _description_ - cold : _type_ - _description_ - area : _type_ - _description_ + A Pyomo concrete model representing the heat exchange model. + hot : str + The index for the hot stream involved in the heat exchanger. + cold : str + The index for the cold stream involved in the heat exchanger. + area : float + The modular area size of the heat exchanger. Returns ------- - _type_ - _description_ + Pyomo.Parameter + The area cost factor for the specified module size and stream pair. It returns a higher value for steam (1300) compared to other hot streams (1100), reflecting specific cost adjustments based on utility type. """ if hot == 'steam': return 1300 else: return 1100 - m.module_fixed_unit_cost = Param(default=0) - m.module_area_cost_exponent = Param(default=0.6) + m.module_fixed_unit_cost = Param(default=0, doc="Fixed cost for a module.") + m.module_area_cost_exponent = Param(default=0.6, doc="Area cost exponent.") @m.Param(m.valid_matches, m.module_sizes, doc="Cost of a module with a particular area.") def module_area_cost(m, hot, cold, area): + """ + Determines the cost of a module with a specified area size for a given hot and cold stream pair. The cost is calculated based on the area cost factor and exponent for the module size and stream pair. The unit is [$]. + + Parameters + ---------- + m : Pyomo.ConcreteModel + A Pyomo concrete model representing the heat exchange model. + hot : str + The index for the hot stream involved in the heat exchanger. + cold : str + The index for the cold stream involved in the heat exchanger. + area : float + The modular area size of the heat exchanger. + + Returns + ------- + Pyomo.Parameter + The cost of a module with the specified area size for the given hot and cold stream pair. The cost is calculated based on the area cost factor and exponent for the module size and stream pair. + """ return (m.module_area_cost_factor[hot, cold, area] * area ** m.module_area_cost_exponent) @@ -198,18 +218,18 @@ def module_area_cost(m, hot, cold, area): ('steam', cold): 1.2 for cold in m.cold_process_streams}, doc="Overall heat transfer coefficient." - "1.2 for heaters. 0.8 for everything else.") + "1.2 for heaters. 0.8 for everything else. The unit is [kW/m^2/K].") m.exchanger_hot_side_approach_T = Var( m.valid_matches, m.stages, doc="Temperature difference between the hot stream inlet and cold " - "stream outlet of the exchanger.", + "stream outlet of the exchanger. The unit is [K].", bounds=(0.1, 500), initialize=10 ) m.exchanger_cold_side_approach_T = Var( m.valid_matches, m.stages, doc="Temperature difference between the hot stream outlet and cold " - "stream inlet of the exchanger.", + "stream inlet of the exchanger. The unit is [K].", bounds=(0.1, 500), initialize=10 ) m.LMTD = Var( @@ -252,18 +272,63 @@ def module_area_cost(m, hot, cold, area): @m.Constraint(m.hot_process_streams) def overall_hot_stream_heat_balance(m, strm): + """ + Enforces the heat balance for a hot process stream within the model. This constraint ensures that the total heat loss from the hot stream equals the sum of heat transferred to all paired cold streams across all stages. The heat loss is calculated based on the temperature difference between the stream outlet and inlet, multiplied by the overall flow times heat capacity of the stream. + + Parameters + ---------- + m : Pyomo.ConcreteModel + A Pyomo concrete model representing the heat exchange model. + strm : str + The index for the hot stream involved in the heat exchanger. + + Returns + ------- + Pyomo.Constraint + A constraint object that ensures the heat balance across the specified hot stream over all stages and cold stream interactions. + """ return (m.T_in[strm] - m.T_out[strm]) * m.overall_FCp[strm] == ( sum(m.heat_exchanged[strm, cold, stg] for cold in m.cold_streams for stg in m.stages)) @m.Constraint(m.cold_process_streams) def overall_cold_stream_heat_balance(m, strm): + """ + Enforces the heat balance for a cold process stream within the model. This constraint ensures that the total heat gain for the cold stream equals the sum of heat received from all paired hot streams across all stages. The heat gain is calculated based on the temperature difference between the stream outlet and inlet, multiplied by the overall flow times heat capacity of the stream. + + Parameters + ---------- + m : Pyomo.ConcreteModel + A Pyomo concrete model representing the heat exchange model. + strm : str + The index for the cold stream involved in the heat exchanger. + + Returns + ------- + Pyomo.Constraint + A constraint object that ensures the heat balance across the specified cold stream over all stages and hot stream interactions. + """ return (m.T_out[strm] - m.T_in[strm]) * m.overall_FCp[strm] == ( sum(m.heat_exchanged[hot, strm, stg] for hot in m.hot_streams for stg in m.stages)) @m.Constraint(m.utility_streams) def overall_utility_stream_usage(m, strm): + """ + Ensures the total utility usage for each utility stream matches the sum of heat exchanged involving that utility across all stages. This constraint separates the calculations for hot and cold utility streams. For cold utility streams, it sums the heat exchanged from all hot process streams to the utility, and for hot utility streams, it sums the heat exchanged from the utility to all cold process streams. + + Parameters + ---------- + m : Pyomo.ConcreteModel + A Pyomo concrete model representing the heat exchange model. + strm : str + The index for the utility stream involved in the heat exchanger. This can be a hot or cold utility, and the constraint dynamically adjusts to sum the appropriate heat transfers based on this classification. + + Returns + ------- + Pyomo.Constraint + A constraint object that ensures the total calculated utility usage for the specified utility stream accurately reflects the sum of relevant heat exchanges in the system. This helps maintain energy balance specifically for utility streams within the overall heat exchange model. + """ return m.utility_usage[strm] == ( sum(m.heat_exchanged[hot, strm, stg] for hot in m.hot_process_streams @@ -278,6 +343,23 @@ def overall_utility_stream_usage(m, strm): @m.Constraint(m.stages, m.hot_process_streams, doc="Hot side overall heat balance for a stage.") def hot_stage_overall_heat_balance(m, stg, strm): + """ + Establishes an overall heat balance for a specific hot stream within a particular stage of the heat exchange process. This constraint ensures that the heat loss from the hot stream, calculated as the product of the temperature drop across the stage and the flow capacity of the stream, equals the total heat transferred to all corresponding cold streams within the same stage. + + Parameters + ---------- + m : Pyomo.ConcreteModel + A Pyomo concrete model representing the heat exchange model. + stg : int + The index for the stage involved in the heat exchanger. + strm : str + The index for the hot stream involved in the heat exchanger. + + Returns + ------- + Pyomo.Constraint + A constraint object that enforces the heat balance for the specified hot stream at the given stage. This ensures that the heat output from this stream is appropriately accounted for and matched by heat intake by the cold streams, promoting efficient energy use. + """ return ((m.stage_entry_T[strm, stg] - m.stage_exit_T[strm, stg]) * m.overall_FCp[strm]) == sum( m.heat_exchanged[strm, cold, stg] @@ -286,6 +368,23 @@ def hot_stage_overall_heat_balance(m, stg, strm): @m.Constraint(m.stages, m.cold_process_streams, doc="Cold side overall heat balance for a stage.") def cold_stage_overall_heat_balance(m, stg, strm): + """ + Establishes an overall heat balance for a specific cold stream within a particular stage of the heat exchange process. This constraint ensures that the heat gain for the cold stream, calculated as the product of the temperature increase across the stage and the flow capacity of the stream, equals the total heat received from all corresponding hot streams within the same stage. + + Parameters + ---------- + m : Pyomo.ConcreteModel + A Pyomo concrete model representing the heat exchange model. + stg : int + The index for the stage involved in the heat exchanger. + strm : str + The index for the cold stream involved in the heat exchanger. + + Returns + ------- + Pyomo.Constraint + A constraint object that enforces the heat balance for the specified cold stream at the given stage. This ensures that the heat intake by this stream is appropriately accounted for and matched by heat output from the hot streams, promoting efficient energy use. + """ return ((m.stage_exit_T[strm, stg] - m.stage_entry_T[strm, stg]) * m.overall_FCp[strm]) == sum( m.heat_exchanged[hot, strm, stg] @@ -293,26 +392,113 @@ def cold_stage_overall_heat_balance(m, stg, strm): @m.Constraint(m.stages, m.hot_process_streams) def hot_stream_monotonic_T_decrease(m, stg, strm): + """ + Ensures that the temperature of a hot stream decreases monotonically across a given stage. This constraint is critical for modeling realistic heat exchange scenarios where hot streams naturally cool down as they transfer heat to colder streams. It enforces that the exit temperature of the hot stream from any stage is less than or equal to its entry temperature for that stage. + + Parameters + ---------- + m : Pyomo.ConcreteModel + A Pyomo concrete model representing the heat exchange model. + stg : int + The index for the stage involved in the heat exchanger. + strm : str + The index for the hot stream involved in the heat exchanger. + + Returns + ------- + Pyomo.Constraint + A constraint object that ensures the temperature of the hot stream does not increase as it passes through the stage, which is essential for maintaining the physical feasibility of the heat exchange process. + """ return m.stage_exit_T[strm, stg] <= m.stage_entry_T[strm, stg] @m.Constraint(m.stages, m.cold_process_streams) def cold_stream_monotonic_T_increase(m, stg, strm): + """ + Ensures that the temperature of a cold stream increases monotonically across a given stage. This constraint is essential for modeling realistic heat exchange scenarios where cold streams naturally warm up as they absorb heat from hotter streams. It enforces that the exit temperature of the cold stream from any stage is greater than or equal to its entry temperature for that stage. + + Parameters + ---------- + m : Pyomo.ConcreteModel + A Pyomo concrete model representing the heat exchange model. + stg : int + The index for the stage involved in the heat exchanger. + strm : str + The index for the cold stream involved in the heat exchanger. + + Returns + ------- + Pyomo.Constraint + A constraint object that ensures the temperature of the cold stream increases as it passes through the stage, reflecting the natural heat absorption process and maintaining the physical feasibility of the heat exchange model. + """ return m.stage_exit_T[strm, stg] >= m.stage_entry_T[strm, stg] @m.Constraint(m.stages, m.hot_process_streams) def hot_stream_stage_T_link(m, stg, strm): + """ + Links the exit temperature of a hot stream from one stage to the entry temperature of the same stream in the subsequent stage, ensuring continuity and consistency in temperature progression across stages. This constraint is vital for maintaining a coherent thermal profile within each hot stream as it progresses through the heat exchange stages. For the final stage, no constraint is applied since there is no subsequent stage. + + Parameters + ---------- + m : Pyomo.ConcreteModel + A Pyomo concrete model representing the heat exchange model. + stg : int + The index for the stage involved in the heat exchanger. + strm : str + The index for the hot stream involved in the heat exchanger. + + Returns + ------- + Pyomo.Constraint + A constraint object that ensures the exit temperature at the end of one stage matches the entry temperature at the beginning of the next stage for the hot streams. In the final stage, where there is no subsequent stage, no constraint is applied. + """ return ( m.stage_exit_T[strm, stg] == m.stage_entry_T[strm, stg + 1] ) if stg < num_stages else Constraint.NoConstraint @m.Constraint(m.stages, m.cold_process_streams) def cold_stream_stage_T_link(m, stg, strm): + """ + Ensures continuity in the temperature profiles of cold streams across stages in the heat exchange model by linking the exit temperature of a cold stream in one stage to its entry temperature in the following stage. This constraint is crucial for maintaining consistent and logical heat absorption sequences within the cold streams as they move through successive stages. For the final stage, no constraint is applied since there is no subsequent stage. + + Parameters + ---------- + m : Pyomo.ConcreteModel + A Pyomo concrete model representing the heat exchange model. + stg : int + The index for the stage involved in the heat exchanger. + strm : str + The index for the cold stream involved in the heat exchanger. + + Returns + ------- + Pyomo.Constraint + A constraint object that ensures the exit temperature at the end of one stage matches the entry temperature at the beginning of the next stage for cold streams. In the final stage, where there is no subsequent stage, no constraint is applied, reflecting the end of the process sequence. + """ return ( m.stage_entry_T[strm, stg] == m.stage_exit_T[strm, stg + 1] ) if stg < num_stages else Constraint.NoConstraint @m.Expression(m.valid_matches, m.stages) def exchanger_capacity(m, hot, cold, stg): + """ + Calculates the heat transfer capacity of an exchanger for a given hot stream, cold stream, and stage combination. This capacity is derived from the exchanger's area, the overall heat transfer coefficient, and the geometric mean of the approach temperatures at both sides of the exchanger. This expression is used to estimate the efficiency and effectiveness of heat transfer in each stage of the heat exchange process. + + Parameters + ---------- + m : Pyomo.ConcreteModel + A Pyomo concrete model representing the heat exchange model. + hot : str + The index for the hot stream involved in the heat exchanger. + cold : str + The index for the cold stream involved in the heat exchanger. + stg : int + The index for the stage involved in the heat exchanger. + + Returns + ------- + Pyomo.Expression + A Pyomo expression that quantifies the heat transfer capacity of the exchanger. This value is crucial for optimizing the heat exchange system, ensuring that each stage is designed to maximize heat recovery while adhering to operational constraints and physical laws. + """ return m.exchanger_area[stg, hot, cold] * ( m.U[hot, cold] * ( m.exchanger_hot_side_approach_T[hot, cold, stg] * @@ -322,6 +508,20 @@ def exchanger_capacity(m, hot, cold, stg): ) ** (1 / 3)) def _exchanger_exists(disj, hot, cold, stg): + """ + Defines the conditions and constraints for the existence of an exchanger between a specified hot and cold stream at a given stage. This function sets the disjunct's indicator variable to true and configures constraints that model the physical behavior of the heat exchanger, including the log mean temperature difference and approach temperatures. + + Parameters + ---------- + disj : Pyomo.Disjunct + The disjunct object representing a potential heat exchanger scenario between the specified hot and cold streams. + hot : str + The index for the hot stream involved in the heat exchanger. + cold : str + The index for the cold stream involved in the heat exchanger. + stg : int + The index for the stage involved in the heat exchanger. + """ disj.indicator_var.value = True # Log mean temperature difference calculation @@ -347,37 +547,51 @@ def _exchanger_exists(disj, hot, cold, stg): if hot in m.hot_utility_streams: disj.stage_hot_approach_temperature = Constraint( expr=m.exchanger_hot_side_approach_T[hot, cold, stg] <= - m.T_in[hot] - m.stage_exit_T[cold, stg]) + m.T_in[hot] - m.stage_exit_T[cold, stg], doc="Hot utility: hot side limit.") disj.stage_cold_approach_temperature = Constraint( expr=m.exchanger_cold_side_approach_T[hot, cold, stg] <= - m.T_out[hot] - m.stage_entry_T[cold, stg]) + m.T_out[hot] - m.stage_entry_T[cold, stg], doc="Hot utility: cold side limit.") elif cold in m.cold_utility_streams: disj.stage_hot_approach_temperature = Constraint( expr=m.exchanger_hot_side_approach_T[hot, cold, stg] <= - m.stage_entry_T[hot, stg] - m.T_out[cold]) + m.stage_entry_T[hot, stg] - m.T_out[cold], doc="Cold utility: hot side limit.") disj.stage_cold_approach_temperature = Constraint( expr=m.exchanger_cold_side_approach_T[hot, cold, stg] <= - m.stage_exit_T[hot, stg] - m.T_in[cold]) + m.stage_exit_T[hot, stg] - m.T_in[cold], doc="Cold utility: cold side limit.") else: disj.stage_hot_approach_temperature = Constraint( expr=m.exchanger_hot_side_approach_T[hot, cold, stg] <= m.stage_entry_T[hot, stg] - - m.stage_exit_T[cold, stg]) + - m.stage_exit_T[cold, stg], doc="Process stream: hot side limit.") disj.stage_cold_approach_temperature = Constraint( expr=m.exchanger_cold_side_approach_T[hot, cold, stg] <= m.stage_exit_T[hot, stg] - - m.stage_entry_T[cold, stg]) + - m.stage_entry_T[cold, stg], doc="Process stream: cold side limit.") def _exchanger_absent(disj, hot, cold, stg): + """ + Defines the conditions for the absence of a heat exchanger between a specified hot and cold stream at a given stage. This function sets the disjunct's indicator variable to false and ensures that all associated costs and heat exchanged values are set to zero, effectively removing the exchanger from the model for this configuration. + + Parameters + ---------- + disj : Pyomo.Disjunct + The disjunct object representing a scenario where no heat exchanger is present between the specified hot and cold streams at the given stage. + hot : str + The index for the hot stream involved in the heat exchanger. + cold : str + The index for the cold stream involved in the heat exchanger. + stg : int + The index for the stage involved in the heat exchanger. + """ disj.indicator_var.value = False disj.no_match_exchanger_cost = Constraint( - expr=m.exchanger_area_cost[stg, hot, cold] == 0) + expr=m.exchanger_area_cost[stg, hot, cold] == 0, doc="No exchanger cost.") disj.no_match_exchanger_area = Constraint( - expr=m.exchanger_area[stg, hot, cold] == 0) + expr=m.exchanger_area[stg, hot, cold] == 0, doc="No exchanger area.") disj.no_match_exchanger_fixed_cost = Constraint( - expr=m.exchanger_fixed_cost[stg, hot, cold] == 0) + expr=m.exchanger_fixed_cost[stg, hot, cold] == 0, doc="No exchanger fixed cost.") disj.no_heat_exchange = Constraint( - expr=m.heat_exchanged[hot, cold, stg] == 0) + expr=m.heat_exchanged[hot, cold, stg] == 0, doc="No heat exchange.") m.exchanger_exists = Disjunct( m.valid_matches, m.stages, @@ -389,6 +603,24 @@ def _exchanger_absent(disj, hot, cold, stg): "hot stream and a cold stream at a stage.", rule=_exchanger_absent) def _exchanger_exists_or_absent(m, hot, cold, stg): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + A Pyomo concrete model representing the heat exchange model. + hot : str + The index for the hot stream involved in the heat exchanger. + cold : str + The index for the cold stream involved in the heat exchanger. + stg : int + The index for the stage involved in the heat exchanger. + + Returns + ------- + _type_ + _description_ + """ return [m.exchanger_exists[hot, cold, stg], m.exchanger_absent[hot, cold, stg]] m.exchanger_exists_or_absent = Disjunction( @@ -413,6 +645,20 @@ def _exchanger_exists_or_absent(m, hot, cold, stg): @m.Expression(m.utility_streams) def utility_cost(m, strm): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + A Pyomo concrete model representing the heat exchange model. + strm : str + The index for the utility stream involved in the heat exchanger. This can be a hot or cold utility. + + Returns + ------- + Pyomo.Expression + _description_ + """ return m.utility_unit_cost[strm] * m.utility_usage[strm] m.total_cost = Objective( @@ -423,7 +669,7 @@ def utility_cost(m, strm): + sum(m.exchanger_area_cost[stg, hot, cold] for stg in m.stages for hot, cold in m.valid_matches), - sense=minimize + sense=minimize, doc="Total cost of the heat exchanger network." ) return m From 02d354fc1e1232ebfc2eb4674f1338bc4e632c8b Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 25 Apr 2024 23:16:27 -0400 Subject: [PATCH 12/60] Update models to account for Pyomo logical expression system and Boolean indicator_vars --- gdplib/mod_hens/common.py | 461 ++++++++++++++++++++++++-------------- 1 file changed, 292 insertions(+), 169 deletions(-) diff --git a/gdplib/mod_hens/common.py b/gdplib/mod_hens/common.py index 3177227..8374b87 100644 --- a/gdplib/mod_hens/common.py +++ b/gdplib/mod_hens/common.py @@ -10,7 +10,18 @@ from __future__ import division from pyomo.environ import ( - ConcreteModel, Constraint, minimize, NonNegativeReals, Objective, Param, RangeSet, Set, Suffix, value, Var, ) + ConcreteModel, + Constraint, + minimize, + NonNegativeReals, + Objective, + Param, + RangeSet, + Set, + Suffix, + value, + Var, +) from pyomo.gdp import Disjunct, Disjunction from .cafaro_approx import calculate_cafaro_coefficients @@ -36,22 +47,27 @@ def build_model(use_cafaro_approximation, num_stages): m = ConcreteModel() m.hot_process_streams = Set(initialize=['H1', 'H2'], doc="Hot process streams") m.cold_process_streams = Set(initialize=['C1', 'C2'], doc="Cold process streams") - m.process_streams = m.hot_process_streams | m.cold_process_streams # All process streams + m.process_streams = ( + m.hot_process_streams | m.cold_process_streams + ) # All process streams m.hot_utility_streams = Set(initialize=['steam'], doc="Hot utility streams") m.cold_utility_streams = Set(initialize=['water'], doc="Cold utility streams") m.hot_streams = Set( - initialize=m.hot_process_streams | m.hot_utility_streams, doc="Hot streams") + initialize=m.hot_process_streams | m.hot_utility_streams, doc="Hot streams" + ) m.cold_streams = Set( - initialize=m.cold_process_streams | m.cold_utility_streams, doc="Cold streams") + initialize=m.cold_process_streams | m.cold_utility_streams, doc="Cold streams" + ) m.utility_streams = Set( - initialize=m.hot_utility_streams | m.cold_utility_streams, doc="Utility streams") - m.streams = Set( - initialize=m.process_streams | m.utility_streams, doc="All streams") + initialize=m.hot_utility_streams | m.cold_utility_streams, doc="Utility streams" + ) + m.streams = Set(initialize=m.process_streams | m.utility_streams, doc="All streams") m.valid_matches = Set( - initialize=(m.hot_process_streams * m.cold_streams) | - (m.hot_utility_streams * m.cold_process_streams), + initialize=(m.hot_process_streams * m.cold_streams) + | (m.hot_utility_streams * m.cold_process_streams), doc="Match all hot streams to cold streams, but exclude " - "matches between hot and cold utilities.") + "matches between hot and cold utilities.", + ) # m.EMAT = Param(doc="Exchanger minimum approach temperature [K]", # initialize=1) # Unused right now, but could be used for variable bound tightening @@ -60,61 +76,71 @@ def build_model(use_cafaro_approximation, num_stages): m.stages = RangeSet(num_stages, doc="Number of stages") m.T_in = Param( - m.streams, doc="Inlet temperature of stream [K]", - initialize={'H1': 443, - 'H2': 423, - 'C1': 293, - 'C2': 353, - 'steam': 450, - 'water': 293}) + m.streams, + doc="Inlet temperature of stream [K]", + initialize={ + 'H1': 443, + 'H2': 423, + 'C1': 293, + 'C2': 353, + 'steam': 450, + 'water': 293, + }, + ) m.T_out = Param( - m.streams, doc="Outlet temperature of stream [K]", - initialize={'H1': 333, - 'H2': 303, - 'C1': 408, - 'C2': 413, - 'steam': 450, - 'water': 313}) + m.streams, + doc="Outlet temperature of stream [K]", + initialize={ + 'H1': 333, + 'H2': 303, + 'C1': 408, + 'C2': 413, + 'steam': 450, + 'water': 313, + }, + ) m.heat_exchanged = Var( - m.valid_matches, m.stages, + m.valid_matches, + m.stages, domain=NonNegativeReals, doc="Heat exchanged from hot stream to cold stream in stage [kW]", - initialize=1, bounds=(0, 5000)) + initialize=1, + bounds=(0, 5000), + ) m.overall_FCp = Param( m.process_streams, doc="Flow times heat capacity of stream [kW / K]", - initialize={'H1': 30, - 'H2': 15, - 'C1': 20, - 'C2': 40}) + initialize={'H1': 30, 'H2': 15, 'C1': 20, 'C2': 40}, + ) m.utility_usage = Var( m.utility_streams, doc="Hot or cold utility used [kW]", - domain=NonNegativeReals, initialize=1, bounds=(0, 5000)) + domain=NonNegativeReals, + initialize=1, + bounds=(0, 5000), + ) m.stage_entry_T = Var( - m.streams, m.stages, + m.streams, + m.stages, doc="Temperature of stream at stage entry [K].", initialize=350, - bounds=(293, 450) # TODO set to be equal to min and max temps + bounds=(293, 450), # TODO set to be equal to min and max temps ) m.stage_exit_T = Var( - m.streams, m.stages, + m.streams, + m.stages, doc="Temperature of stream at stage exit [K].", initialize=350, - bounds=(293, 450) # TODO set to be equal to min and max temps + bounds=(293, 450), # TODO set to be equal to min and max temps ) # Improve bounds on stage entry and exit temperatures for strm, stg in m.process_streams * m.stages: - m.stage_entry_T[strm, stg].setlb( - min(value(m.T_in[strm]), value(m.T_out[strm]))) - m.stage_exit_T[strm, stg].setlb( - min(value(m.T_in[strm]), value(m.T_out[strm]))) - m.stage_entry_T[strm, stg].setub( - max(value(m.T_in[strm]), value(m.T_out[strm]))) - m.stage_exit_T[strm, stg].setub( - max(value(m.T_in[strm]), value(m.T_out[strm]))) + m.stage_entry_T[strm, stg].setlb(min(value(m.T_in[strm]), value(m.T_out[strm]))) + m.stage_exit_T[strm, stg].setlb(min(value(m.T_in[strm]), value(m.T_out[strm]))) + m.stage_entry_T[strm, stg].setub(max(value(m.T_in[strm]), value(m.T_out[strm]))) + m.stage_exit_T[strm, stg].setub(max(value(m.T_in[strm]), value(m.T_out[strm]))) for strm, stg in m.utility_streams * m.stages: _fix_and_bound(m.stage_entry_T[strm, stg], m.T_in[strm]) _fix_and_bound(m.stage_exit_T[strm, stg], m.T_out[strm]) @@ -130,34 +156,41 @@ def build_model(use_cafaro_approximation, num_stages): m.utility_unit_cost = Param( m.utility_streams, doc="Annual unit cost of utilities [$/kW]", - initialize={'steam': 80, 'water': 20}) + initialize={'steam': 80, 'water': 20}, + ) m.module_sizes = Set(initialize=[10, 50, 100], doc="Available module sizes.") - m.max_num_modules = Param(m.module_sizes, initialize={ - # 5: 100, - 10: 50, - 50: 10, - 100: 5, - # 250: 2 - }, doc="maximum number of each module size available.") + m.max_num_modules = Param( + m.module_sizes, + initialize={ + # 5: 100, + 10: 50, + 50: 10, + 100: 5, + # 250: 2 + }, + doc="maximum number of each module size available.", + ) m.exchanger_fixed_unit_cost = Param( - m.valid_matches, default=2000, doc="exchanger fixed cost [$/kW]",) + m.valid_matches, default=2000, doc="exchanger fixed cost [$/kW]" + ) m.exchanger_area_cost_factor = Param( - m.valid_matches, default=1000, - initialize={ - ('steam', cold): 1200 - for cold in m.cold_process_streams}, - doc="1200 for heaters. 1000 for all other exchangers.") + m.valid_matches, + default=1000, + initialize={('steam', cold): 1200 for cold in m.cold_process_streams}, + doc="1200 for heaters. 1000 for all other exchangers.", + ) m.area_cost_exponent = Param(default=0.6, doc="Area cost exponent.") - if use_cafaro_approximation: # Use Cafaro approximation for coefficients if True + if use_cafaro_approximation: # Use Cafaro approximation for coefficients if True k, b = calculate_cafaro_coefficients(10, 500, m.area_cost_exponent) m.cafaro_k = Param(default=k) m.cafaro_b = Param(default=b) - @m.Param(m.valid_matches, m.module_sizes, - doc="Area cost factor for modular exchangers.") + @m.Param( + m.valid_matches, m.module_sizes, doc="Area cost factor for modular exchangers." + ) def module_area_cost_factor(m, hot, cold, area): """ Determines the area cost factor for modular exchangers within the heat integration model. The cost factor is based on the specified module size and stream pair, with different values for steam and other hot streams. The unit is [$/(m^2)^0.6]. @@ -186,8 +219,9 @@ def module_area_cost_factor(m, hot, cold, area): m.module_fixed_unit_cost = Param(default=0, doc="Fixed cost for a module.") m.module_area_cost_exponent = Param(default=0.6, doc="Area cost exponent.") - @m.Param(m.valid_matches, m.module_sizes, - doc="Cost of a module with a particular area.") + @m.Param( + m.valid_matches, m.module_sizes, doc="Cost of a module with a particular area." + ) def module_area_cost(m, hot, cold, area): """ Determines the cost of a module with a specified area size for a given hot and cold stream pair. The cost is calculated based on the area cost factor and exponent for the module size and stream pair. The unit is [$]. @@ -208,67 +242,90 @@ def module_area_cost(m, hot, cold, area): Pyomo.Parameter The cost of a module with the specified area size for the given hot and cold stream pair. The cost is calculated based on the area cost factor and exponent for the module size and stream pair. """ - return (m.module_area_cost_factor[hot, cold, area] - * area ** m.module_area_cost_exponent) + return ( + m.module_area_cost_factor[hot, cold, area] + * area**m.module_area_cost_exponent + ) m.U = Param( m.valid_matches, default=0.8, - initialize={ - ('steam', cold): 1.2 - for cold in m.cold_process_streams}, + initialize={('steam', cold): 1.2 for cold in m.cold_process_streams}, doc="Overall heat transfer coefficient." - "1.2 for heaters. 0.8 for everything else. The unit is [kW/m^2/K].") + "1.2 for heaters. 0.8 for everything else. The unit is [kW/m^2/K].", + ) m.exchanger_hot_side_approach_T = Var( - m.valid_matches, m.stages, + m.valid_matches, + m.stages, doc="Temperature difference between the hot stream inlet and cold " "stream outlet of the exchanger. The unit is [K].", - bounds=(0.1, 500), initialize=10 + bounds=(0.1, 500), + initialize=10, ) m.exchanger_cold_side_approach_T = Var( - m.valid_matches, m.stages, + m.valid_matches, + m.stages, doc="Temperature difference between the hot stream outlet and cold " "stream inlet of the exchanger. The unit is [K].", - bounds=(0.1, 500), initialize=10 + bounds=(0.1, 500), + initialize=10, ) m.LMTD = Var( - m.valid_matches, m.stages, + m.valid_matches, + m.stages, doc="Log mean temperature difference across the exchanger.", - bounds=(1, 500), initialize=10 + bounds=(1, 500), + initialize=10, ) # Improve LMTD bounds based on T values for hot, cold, stg in m.valid_matches * m.stages: - hot_side_dT_LB = max(0, value( - m.stage_entry_T[hot, stg].lb - m.stage_exit_T[cold, stg].ub)) - hot_side_dT_UB = max(0, value( - m.stage_entry_T[hot, stg].ub - m.stage_exit_T[cold, stg].lb)) - cold_side_dT_LB = max(0, value( - m.stage_exit_T[hot, stg].lb - m.stage_entry_T[cold, stg].ub)) - cold_side_dT_UB = max(0, value( - m.stage_exit_T[hot, stg].ub - m.stage_entry_T[cold, stg].lb)) - m.LMTD[hot, cold, stg].setlb(( - hot_side_dT_LB * cold_side_dT_LB * ( - hot_side_dT_LB + cold_side_dT_LB) / 2) ** (1 / 3) + hot_side_dT_LB = max( + 0, value(m.stage_entry_T[hot, stg].lb - m.stage_exit_T[cold, stg].ub) + ) + hot_side_dT_UB = max( + 0, value(m.stage_entry_T[hot, stg].ub - m.stage_exit_T[cold, stg].lb) ) - m.LMTD[hot, cold, stg].setub(( - hot_side_dT_UB * cold_side_dT_UB * ( - hot_side_dT_UB + cold_side_dT_UB) / 2) ** (1 / 3) + cold_side_dT_LB = max( + 0, value(m.stage_exit_T[hot, stg].lb - m.stage_entry_T[cold, stg].ub) + ) + cold_side_dT_UB = max( + 0, value(m.stage_exit_T[hot, stg].ub - m.stage_entry_T[cold, stg].lb) + ) + m.LMTD[hot, cold, stg].setlb( + (hot_side_dT_LB * cold_side_dT_LB * (hot_side_dT_LB + cold_side_dT_LB) / 2) + ** (1 / 3) + ) + m.LMTD[hot, cold, stg].setub( + (hot_side_dT_UB * cold_side_dT_UB * (hot_side_dT_UB + cold_side_dT_UB) / 2) + ** (1 / 3) ) m.exchanger_fixed_cost = Var( - m.stages, m.valid_matches, + m.stages, + m.valid_matches, doc="Fixed cost for an exchanger between a hot and cold stream.", - domain=NonNegativeReals, bounds=(0, 1E5), initialize=0) + domain=NonNegativeReals, + bounds=(0, 1e5), + initialize=0, + ) m.exchanger_area = Var( - m.stages, m.valid_matches, + m.stages, + m.valid_matches, doc="Area for an exchanger between a hot and cold stream.", - domain=NonNegativeReals, bounds=(0, 500), initialize=5) + domain=NonNegativeReals, + bounds=(0, 500), + initialize=5, + ) m.exchanger_area_cost = Var( - m.stages, m.valid_matches, + m.stages, + m.valid_matches, doc="Capital cost contribution from exchanger area.", - domain=NonNegativeReals, bounds=(0, 1E5), initialize=1000) + domain=NonNegativeReals, + bounds=(0, 1e5), + initialize=1000, + ) @m.Constraint(m.hot_process_streams) def overall_hot_stream_heat_balance(m, strm): @@ -288,8 +345,12 @@ def overall_hot_stream_heat_balance(m, strm): A constraint object that ensures the heat balance across the specified hot stream over all stages and cold stream interactions. """ return (m.T_in[strm] - m.T_out[strm]) * m.overall_FCp[strm] == ( - sum(m.heat_exchanged[strm, cold, stg] - for cold in m.cold_streams for stg in m.stages)) + sum( + m.heat_exchanged[strm, cold, stg] + for cold in m.cold_streams + for stg in m.stages + ) + ) @m.Constraint(m.cold_process_streams) def overall_cold_stream_heat_balance(m, strm): @@ -309,8 +370,12 @@ def overall_cold_stream_heat_balance(m, strm): A constraint object that ensures the heat balance across the specified cold stream over all stages and hot stream interactions. """ return (m.T_out[strm] - m.T_in[strm]) * m.overall_FCp[strm] == ( - sum(m.heat_exchanged[hot, strm, stg] - for hot in m.hot_streams for stg in m.stages)) + sum( + m.heat_exchanged[hot, strm, stg] + for hot in m.hot_streams + for stg in m.stages + ) + ) @m.Constraint(m.utility_streams) def overall_utility_stream_usage(m, strm): @@ -330,18 +395,27 @@ def overall_utility_stream_usage(m, strm): A constraint object that ensures the total calculated utility usage for the specified utility stream accurately reflects the sum of relevant heat exchanges in the system. This helps maintain energy balance specifically for utility streams within the overall heat exchange model. """ return m.utility_usage[strm] == ( - sum(m.heat_exchanged[hot, strm, stg] + sum( + m.heat_exchanged[hot, strm, stg] for hot in m.hot_process_streams for stg in m.stages - ) if strm in m.cold_utility_streams else 0 + - sum(m.heat_exchanged[strm, cold, stg] + ) + if strm in m.cold_utility_streams + else 0 + + sum( + m.heat_exchanged[strm, cold, stg] for cold in m.cold_process_streams for stg in m.stages - ) if strm in m.hot_utility_streams else 0 + ) + if strm in m.hot_utility_streams + else 0 ) - @m.Constraint(m.stages, m.hot_process_streams, - doc="Hot side overall heat balance for a stage.") + @m.Constraint( + m.stages, + m.hot_process_streams, + doc="Hot side overall heat balance for a stage.", + ) def hot_stage_overall_heat_balance(m, stg, strm): """ Establishes an overall heat balance for a specific hot stream within a particular stage of the heat exchange process. This constraint ensures that the heat loss from the hot stream, calculated as the product of the temperature drop across the stage and the flow capacity of the stream, equals the total heat transferred to all corresponding cold streams within the same stage. @@ -360,13 +434,16 @@ def hot_stage_overall_heat_balance(m, stg, strm): Pyomo.Constraint A constraint object that enforces the heat balance for the specified hot stream at the given stage. This ensures that the heat output from this stream is appropriately accounted for and matched by heat intake by the cold streams, promoting efficient energy use. """ - return ((m.stage_entry_T[strm, stg] - m.stage_exit_T[strm, stg]) - * m.overall_FCp[strm]) == sum( - m.heat_exchanged[strm, cold, stg] - for cold in m.cold_streams) - - @m.Constraint(m.stages, m.cold_process_streams, - doc="Cold side overall heat balance for a stage.") + return ( + (m.stage_entry_T[strm, stg] - m.stage_exit_T[strm, stg]) + * m.overall_FCp[strm] + ) == sum(m.heat_exchanged[strm, cold, stg] for cold in m.cold_streams) + + @m.Constraint( + m.stages, + m.cold_process_streams, + doc="Cold side overall heat balance for a stage.", + ) def cold_stage_overall_heat_balance(m, stg, strm): """ Establishes an overall heat balance for a specific cold stream within a particular stage of the heat exchange process. This constraint ensures that the heat gain for the cold stream, calculated as the product of the temperature increase across the stage and the flow capacity of the stream, equals the total heat received from all corresponding hot streams within the same stage. @@ -385,10 +462,10 @@ def cold_stage_overall_heat_balance(m, stg, strm): Pyomo.Constraint A constraint object that enforces the heat balance for the specified cold stream at the given stage. This ensures that the heat intake by this stream is appropriately accounted for and matched by heat output from the hot streams, promoting efficient energy use. """ - return ((m.stage_exit_T[strm, stg] - m.stage_entry_T[strm, stg]) - * m.overall_FCp[strm]) == sum( - m.heat_exchanged[hot, strm, stg] - for hot in m.hot_streams) + return ( + (m.stage_exit_T[strm, stg] - m.stage_entry_T[strm, stg]) + * m.overall_FCp[strm] + ) == sum(m.heat_exchanged[hot, strm, stg] for hot in m.hot_streams) @m.Constraint(m.stages, m.hot_process_streams) def hot_stream_monotonic_T_decrease(m, stg, strm): @@ -452,8 +529,10 @@ def hot_stream_stage_T_link(m, stg, strm): A constraint object that ensures the exit temperature at the end of one stage matches the entry temperature at the beginning of the next stage for the hot streams. In the final stage, where there is no subsequent stage, no constraint is applied. """ return ( - m.stage_exit_T[strm, stg] == m.stage_entry_T[strm, stg + 1] - ) if stg < num_stages else Constraint.NoConstraint + (m.stage_exit_T[strm, stg] == m.stage_entry_T[strm, stg + 1]) + if stg < num_stages + else Constraint.NoConstraint + ) @m.Constraint(m.stages, m.cold_process_streams) def cold_stream_stage_T_link(m, stg, strm): @@ -475,8 +554,10 @@ def cold_stream_stage_T_link(m, stg, strm): A constraint object that ensures the exit temperature at the end of one stage matches the entry temperature at the beginning of the next stage for cold streams. In the final stage, where there is no subsequent stage, no constraint is applied, reflecting the end of the process sequence. """ return ( - m.stage_entry_T[strm, stg] == m.stage_exit_T[strm, stg + 1] - ) if stg < num_stages else Constraint.NoConstraint + (m.stage_entry_T[strm, stg] == m.stage_exit_T[strm, stg + 1]) + if stg < num_stages + else Constraint.NoConstraint + ) @m.Expression(m.valid_matches, m.stages) def exchanger_capacity(m, hot, cold, stg): @@ -500,12 +581,18 @@ def exchanger_capacity(m, hot, cold, stg): A Pyomo expression that quantifies the heat transfer capacity of the exchanger. This value is crucial for optimizing the heat exchange system, ensuring that each stage is designed to maximize heat recovery while adhering to operational constraints and physical laws. """ return m.exchanger_area[stg, hot, cold] * ( - m.U[hot, cold] * ( - m.exchanger_hot_side_approach_T[hot, cold, stg] * - m.exchanger_cold_side_approach_T[hot, cold, stg] * - (m.exchanger_hot_side_approach_T[hot, cold, stg] + - m.exchanger_cold_side_approach_T[hot, cold, stg]) / 2 - ) ** (1 / 3)) + m.U[hot, cold] + * ( + m.exchanger_hot_side_approach_T[hot, cold, stg] + * m.exchanger_cold_side_approach_T[hot, cold, stg] + * ( + m.exchanger_hot_side_approach_T[hot, cold, stg] + + m.exchanger_cold_side_approach_T[hot, cold, stg] + ) + / 2 + ) + ** (1 / 3) + ) def _exchanger_exists(disj, hot, cold, stg): """ @@ -527,12 +614,17 @@ def _exchanger_exists(disj, hot, cold, stg): # Log mean temperature difference calculation disj.LMTD_calc = Constraint( doc="Log mean temperature difference", - expr=m.LMTD[hot, cold, stg] == ( - m.exchanger_hot_side_approach_T[hot, cold, stg] * - m.exchanger_cold_side_approach_T[hot, cold, stg] * - (m.exchanger_hot_side_approach_T[hot, cold, stg] + - m.exchanger_cold_side_approach_T[hot, cold, stg]) / 2 - ) ** (1 / 3) + expr=m.LMTD[hot, cold, stg] + == ( + m.exchanger_hot_side_approach_T[hot, cold, stg] + * m.exchanger_cold_side_approach_T[hot, cold, stg] + * ( + m.exchanger_hot_side_approach_T[hot, cold, stg] + + m.exchanger_cold_side_approach_T[hot, cold, stg] + ) + / 2 + ) + ** (1 / 3), ) m.BigM[disj.LMTD_calc] = 160 @@ -546,27 +638,37 @@ def _exchanger_exists(disj, hot, cold, stg): # Calculation of the approach temperatures if hot in m.hot_utility_streams: disj.stage_hot_approach_temperature = Constraint( - expr=m.exchanger_hot_side_approach_T[hot, cold, stg] <= - m.T_in[hot] - m.stage_exit_T[cold, stg], doc="Hot utility: hot side limit.") + expr=m.exchanger_hot_side_approach_T[hot, cold, stg] + <= m.T_in[hot] - m.stage_exit_T[cold, stg], + doc="Hot utility: hot side limit.", + ) disj.stage_cold_approach_temperature = Constraint( - expr=m.exchanger_cold_side_approach_T[hot, cold, stg] <= - m.T_out[hot] - m.stage_entry_T[cold, stg], doc="Hot utility: cold side limit.") + expr=m.exchanger_cold_side_approach_T[hot, cold, stg] + <= m.T_out[hot] - m.stage_entry_T[cold, stg], + doc="Hot utility: cold side limit.", + ) elif cold in m.cold_utility_streams: disj.stage_hot_approach_temperature = Constraint( - expr=m.exchanger_hot_side_approach_T[hot, cold, stg] <= - m.stage_entry_T[hot, stg] - m.T_out[cold], doc="Cold utility: hot side limit.") + expr=m.exchanger_hot_side_approach_T[hot, cold, stg] + <= m.stage_entry_T[hot, stg] - m.T_out[cold], + doc="Cold utility: hot side limit.", + ) disj.stage_cold_approach_temperature = Constraint( - expr=m.exchanger_cold_side_approach_T[hot, cold, stg] <= - m.stage_exit_T[hot, stg] - m.T_in[cold], doc="Cold utility: cold side limit.") + expr=m.exchanger_cold_side_approach_T[hot, cold, stg] + <= m.stage_exit_T[hot, stg] - m.T_in[cold], + doc="Cold utility: cold side limit.", + ) else: disj.stage_hot_approach_temperature = Constraint( - expr=m.exchanger_hot_side_approach_T[hot, cold, stg] <= - m.stage_entry_T[hot, stg] - - m.stage_exit_T[cold, stg], doc="Process stream: hot side limit.") + expr=m.exchanger_hot_side_approach_T[hot, cold, stg] + <= m.stage_entry_T[hot, stg] - m.stage_exit_T[cold, stg], + doc="Process stream: hot side limit.", + ) disj.stage_cold_approach_temperature = Constraint( - expr=m.exchanger_cold_side_approach_T[hot, cold, stg] <= - m.stage_exit_T[hot, stg] - - m.stage_entry_T[cold, stg], doc="Process stream: cold side limit.") + expr=m.exchanger_cold_side_approach_T[hot, cold, stg] + <= m.stage_exit_T[hot, stg] - m.stage_entry_T[cold, stg], + doc="Process stream: cold side limit.", + ) def _exchanger_absent(disj, hot, cold, stg): """ @@ -585,25 +687,37 @@ def _exchanger_absent(disj, hot, cold, stg): """ disj.indicator_var.value = False disj.no_match_exchanger_cost = Constraint( - expr=m.exchanger_area_cost[stg, hot, cold] == 0, doc="No exchanger cost.") + expr=m.exchanger_area_cost[stg, hot, cold] == 0, doc="No exchanger cost." + ) disj.no_match_exchanger_area = Constraint( - expr=m.exchanger_area[stg, hot, cold] == 0, doc="No exchanger area.") + expr=m.exchanger_area[stg, hot, cold] == 0, doc="No exchanger area." + ) disj.no_match_exchanger_fixed_cost = Constraint( - expr=m.exchanger_fixed_cost[stg, hot, cold] == 0, doc="No exchanger fixed cost.") + expr=m.exchanger_fixed_cost[stg, hot, cold] == 0, + doc="No exchanger fixed cost.", + ) disj.no_heat_exchange = Constraint( - expr=m.heat_exchanged[hot, cold, stg] == 0, doc="No heat exchange.") + expr=m.heat_exchanged[hot, cold, stg] == 0, doc="No heat exchange." + ) m.exchanger_exists = Disjunct( - m.valid_matches, m.stages, + m.valid_matches, + m.stages, doc="Disjunct for the presence of an exchanger between a " - "hot stream and a cold stream at a stage.", rule=_exchanger_exists) + "hot stream and a cold stream at a stage.", + rule=_exchanger_exists, + ) m.exchanger_absent = Disjunct( - m.valid_matches, m.stages, + m.valid_matches, + m.stages, doc="Disjunct for the absence of an exchanger between a " - "hot stream and a cold stream at a stage.", rule=_exchanger_absent) + "hot stream and a cold stream at a stage.", + rule=_exchanger_absent, + ) def _exchanger_exists_or_absent(m, hot, cold, stg): - """_summary_ + """ + Defines a disjunction to represent the decision between installing or not installing a heat exchanger between a specific hot and cold stream at a certain stage. Parameters ---------- @@ -618,16 +732,19 @@ def _exchanger_exists_or_absent(m, hot, cold, stg): Returns ------- - _type_ - _description_ + list + A list of Pyomo Disjunct objects, which includes the scenarios where the exchanger exists or is absent, allowing the model to explore different configurations for optimal energy use and cost efficiency. """ - return [m.exchanger_exists[hot, cold, stg], - m.exchanger_absent[hot, cold, stg]] + return [m.exchanger_exists[hot, cold, stg], m.exchanger_absent[hot, cold, stg]] + m.exchanger_exists_or_absent = Disjunction( - m.valid_matches, m.stages, + m.valid_matches, + m.stages, doc="Disjunction between presence or absence of an exchanger between " "a hot stream and a cold stream at a stage.", - rule=_exchanger_exists_or_absent, xor=True) + rule=_exchanger_exists_or_absent, + xor=True, + ) # Only hot utility matches in first stage and cold utility matches in last # stage for hot, cold in m.valid_matches: @@ -645,7 +762,8 @@ def _exchanger_exists_or_absent(m, hot, cold, stg): @m.Expression(m.utility_streams) def utility_cost(m, strm): - """_summary_ + """ + alculates the cost associated with the usage of a utility stream within the heat exchange model. Parameters ---------- @@ -657,19 +775,24 @@ def utility_cost(m, strm): Returns ------- Pyomo.Expression - _description_ + An expression representing the total cost of using the specified utility stream within the model, computed as the product of unit cost and usage. This helps in assessing the economic impact of utility choices in the heat exchange system. """ return m.utility_unit_cost[strm] * m.utility_usage[strm] m.total_cost = Objective( expr=sum(m.utility_cost[strm] for strm in m.utility_streams) - + sum(m.exchanger_fixed_cost[stg, hot, cold] - for stg in m.stages - for hot, cold in m.valid_matches) - + sum(m.exchanger_area_cost[stg, hot, cold] - for stg in m.stages - for hot, cold in m.valid_matches), - sense=minimize, doc="Total cost of the heat exchanger network." + + sum( + m.exchanger_fixed_cost[stg, hot, cold] + for stg in m.stages + for hot, cold in m.valid_matches + ) + + sum( + m.exchanger_area_cost[stg, hot, cold] + for stg in m.stages + for hot, cold in m.valid_matches + ), + sense=minimize, + doc="Total cost of the heat exchanger network.", ) return m From 25568b17a52e775b76de5266773873f977babd86 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 25 Apr 2024 23:35:12 -0400 Subject: [PATCH 13/60] Refactor conventional.py to improve code readability and add documentation --- gdplib/mod_hens/conventional.py | 64 +++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/gdplib/mod_hens/conventional.py b/gdplib/mod_hens/conventional.py index 9550d10..abf95bb 100644 --- a/gdplib/mod_hens/conventional.py +++ b/gdplib/mod_hens/conventional.py @@ -15,51 +15,77 @@ def build_conventional(cafaro_approx, num_stages): - """_summary_ + """ + Builds a conventional heat integration model based on specified parameters, delegating to the common build_model function. Parameters ---------- - cafaro_approx : _type_ - _description_ - num_stages : _type_ - _description_ + cafaro_approx : bool + Specifies whether to use the Cafaro approximation in the model. + num_stages : int + The number of stages in the heat integration model. Returns ------- - _type_ - _description_ + Pyomo.ConcreteModel + The constructed Pyomo concrete model for heat integration. """ return build_model(cafaro_approx, num_stages) def build_model(use_cafaro_approximation, num_stages): - """Build the model.""" + """_summary_ + + Parameters + ---------- + use_cafaro_approximation : bool + Flag to determine whether to use the Cafaro approximation for cost calculations. + num_stages : int + Number of stages in the heat exchange model. + + Returns + ------- + Pyomo.ConcreteModel + A fully configured heat integration model with additional conventional-specific constraints. + """ m = common.build_model(use_cafaro_approximation, num_stages) for hot, cold, stg in m.valid_matches * m.stages: disj = m.exchanger_exists[hot, cold, stg] if not use_cafaro_approximation: disj.exchanger_area_cost = Constraint( - expr=m.exchanger_area_cost[stg, hot, cold] * 1E-3 >= - m.exchanger_area_cost_factor[hot, cold] * 1E-3 * - m.exchanger_area[stg, hot, cold] ** m.area_cost_exponent) + expr=m.exchanger_area_cost[stg, hot, cold] * 1e-3 + >= m.exchanger_area_cost_factor[hot, cold] + * 1e-3 + * m.exchanger_area[stg, hot, cold] ** m.area_cost_exponent, + doc="Ensures area cost meets the standard cost scaling.", + ) else: disj.exchanger_area_cost = Constraint( - expr=m.exchanger_area_cost[stg, hot, cold] * 1E-3 >= - m.exchanger_area_cost_factor[hot, cold] * 1E-3 * m.cafaro_k - * log(m.cafaro_b * m.exchanger_area[stg, hot, cold] + 1) + expr=m.exchanger_area_cost[stg, hot, cold] * 1e-3 + >= 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.", + ) ) m.BigM[disj.exchanger_area_cost] = 100 disj.exchanger_fixed_cost = Constraint( - expr=m.exchanger_fixed_cost[stg, hot, cold] == - m.exchanger_fixed_unit_cost[hot, cold]) + expr=m.exchanger_fixed_cost[stg, hot, cold] + == m.exchanger_fixed_unit_cost[hot, cold], + doc="Sets fixed cost for the exchanger based on unit costs.", + ) # Area requirement disj.exchanger_required_area = Constraint( - expr=m.exchanger_area[stg, hot, cold] * ( - m.U[hot, cold] * m.LMTD[hot, cold, stg]) >= - m.heat_exchanged[hot, cold, stg]) + expr=m.exchanger_area[stg, hot, cold] + * (m.U[hot, cold] * m.LMTD[hot, cold, stg]) + >= m.heat_exchanged[hot, cold, stg], + doc="Calculates the required area based on heat exchanged and LMTD.", + ) m.BigM[disj.exchanger_required_area] = 5000 return m From 6c25c1dfc13faa073e0c1519503ac6c63a8719fe Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Fri, 26 Apr 2024 01:12:26 -0400 Subject: [PATCH 14/60] Update conventional.py to build and configure a heat integration model using standard calculations or the Cafaro approximation, with specific constraints for the conventional scenario --- gdplib/mod_hens/conventional.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gdplib/mod_hens/conventional.py b/gdplib/mod_hens/conventional.py index abf95bb..3eec0fb 100644 --- a/gdplib/mod_hens/conventional.py +++ b/gdplib/mod_hens/conventional.py @@ -34,7 +34,8 @@ def build_conventional(cafaro_approx, num_stages): def build_model(use_cafaro_approximation, num_stages): - """_summary_ + """ + Builds and configures a heat integration model using either standard calculations or the Cafaro approximation for specific costs and heat exchange calculations, supplemented by constraints specific to the conventional scenario. Parameters ---------- From e809a01d2f6b5499ec42101b61b022cfe325a2a2 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Fri, 26 Apr 2024 01:13:22 -0400 Subject: [PATCH 15/60] Update conventional.py to build and configure a heat integration model using standard calculations or the Cafaro approximation, with specific constraints for the conventional scenario --- .../modular_discrete_single_module.py | 259 +++++++++++++++--- 1 file changed, 221 insertions(+), 38 deletions(-) diff --git a/gdplib/mod_hens/modular_discrete_single_module.py b/gdplib/mod_hens/modular_discrete_single_module.py index be621be..c2ab435 100644 --- a/gdplib/mod_hens/modular_discrete_single_module.py +++ b/gdplib/mod_hens/modular_discrete_single_module.py @@ -21,11 +21,40 @@ def build_single_module(cafaro_approx, num_stages): + """ + Builds a heat integration model tailored to handle single module types, with the option to utilize Cafaro's approximation for cost and efficiency calculations. + + Parameters + ---------- + cafaro_approx : bool + Specifies whether to use the Cafaro approximation in the model. + num_stages : int + The number of stages in the heat integration model. + + Returns + ------- + Pyomo.ConcreteModel + A Pyomo model configured with constraints and parameters specific to the requirements of using single module types in a discretized format. + """ return build_model(cafaro_approx, num_stages) def build_model(use_cafaro_approximation, num_stages): - """Build the model.""" + """ + Extends a base heat integration model by incorporating a module configuration approach. It allows only single exchanger module types, optimizing the model for specific operational constraints and simplifying the nonlinear terms through discretization. + + Parameters + ---------- + cafaro_approx : bool + Specifies whether to use the Cafaro approximation in the model. + num_stages : int + The number of stages in the heat integration model. + + Returns + ------- + Pyomo.ConcreteModel + An enhanced heat integration model that supports module configurations with discretized area considerations to simplify calculations and improve optimization performance. + """ m = common.build_model(use_cafaro_approximation, num_stages) # list of tuples (num_modules, module_size) @@ -35,37 +64,101 @@ def build_model(use_cafaro_approximation, num_stages): configurations_list += configs # Map of config indx: (# modules, module size) - m.configurations_map = { - (k + 1): v for k, v in enumerate(configurations_list)} + m.configurations_map = {(k + 1): v for k, v in enumerate(configurations_list)} m.module_index_set = RangeSet(len(configurations_list)) m.module_config_active = Var( - m.valid_matches, m.stages, m.module_index_set, + m.valid_matches, + m.stages, + m.module_index_set, doc="Binary for if which module configuration is active for a match.", - domain=Binary, initialize=0) + domain=Binary, + initialize=0, + ) @m.Param(m.module_index_set, doc="Area of each configuration") def module_area(m, indx): + """ + Calculates the total area of a module configuration based on the number of modules and the size of each module. + + Parameters + ---------- + m : Pyomo.ConcreteModel + A Pyomo concrete model representing the heat exchange model modified to support discretization of area simplifying the nonlinear expressions, specialized to the case of allowing only a single exchanger module type (size). + indx : int + Index of the module configuration in the model. + + Returns + ------- + Pyomo.Parameter + The total area of the configuration corresponding to the given index. + """ num_modules, size = m.configurations_map[indx] return num_modules * size - @m.Param(m.valid_matches, m.module_index_set, - doc="Area cost for each modular configuration.") + @m.Param( + m.valid_matches, + m.module_index_set, + doc="Area cost for each modular configuration.", + ) def modular_size_cost(m, hot, cold, indx): + """ + Determines the cost associated with a specific modular configuration, taking into account the number of modules and their individual sizes. + + Parameters + ---------- + m : Pyomo.ConcreteModel + A Pyomo concrete model representing the heat exchange model modified for the case of allowing only a single exchanger module type (size). + hot : str + The index for the hot stream involved in the heat exchanger. + cold : str + The index for the cold stream involved in the heat exchanger. + indx : int + Index of the module configuration in the model. + + Returns + ------- + Pyomo.Parameter + Cost associated with the specified modular configuration. + """ num_modules, size = m.configurations_map[indx] return num_modules * m.module_area_cost[hot, cold, size] - @m.Param(m.valid_matches, m.module_index_set, - doc="Fixed cost for each modular exchanger size.") + @m.Param( + m.valid_matches, + m.module_index_set, + doc="Fixed cost for each modular exchanger size.", + ) def modular_fixed_cost(m, hot, cold, indx): + """ + Computes the fixed cost for a modular exchanger configuration, factoring in the number of modules and the set fixed cost per unit. + + Parameters + ---------- + m : Pyomo.ConcreteModel + A Pyomo concrete model representing the heat exchange model modified for the case of allowing only a single exchanger module type (size). + cold : str + The index for the cold stream involved in the heat exchanger. + indx : int + Index of the module configuration in the model. + + Returns + ------- + Pyomo.Parameter + Fixed cost for the given modular exchanger configuration. + """ num_modules, size = m.configurations_map[indx] return num_modules * m.module_fixed_unit_cost m.LMTD_discretize = Var( - m.hot_streams, m.cold_streams, m.stages, m.module_index_set, + m.hot_streams, + m.cold_streams, + m.stages, + m.module_index_set, doc="Discretized log mean temperature difference", - bounds=(0, 500), initialize=0 + bounds=(0, 500), + initialize=0, ) for hot, cold, stg in m.valid_matches * m.stages: @@ -73,72 +166,162 @@ def modular_fixed_cost(m, hot, cold, indx): disj.choose_one_config = Constraint( expr=sum( m.module_config_active[hot, cold, stg, indx] - for indx in m.module_index_set) == 1 + for indx in m.module_index_set + ) + == 1, + doc="Enforce a single active configuration per exchanger per stage.", ) disj.exchanger_area_cost = Constraint( - expr=m.exchanger_area_cost[stg, hot, cold] * 1E-3 == - sum(m.modular_size_cost[hot, cold, indx] * 1E-3 * - m.module_config_active[hot, cold, stg, indx] - for indx in m.module_index_set) + expr=m.exchanger_area_cost[stg, hot, cold] * 1e-3 + == sum( + m.modular_size_cost[hot, cold, indx] + * 1e-3 + * m.module_config_active[hot, cold, stg, indx] + for indx in m.module_index_set + ), + doc="Compute total area cost from active configurations.", ) disj.exchanger_fixed_cost = Constraint( - expr=m.exchanger_fixed_cost[stg, hot, cold] == - sum(m.modular_fixed_cost[hot, cold, indx] * 1E-3 * - m.module_config_active[hot, cold, stg, indx] - for indx in m.module_index_set)) + expr=m.exchanger_fixed_cost[stg, hot, cold] + == sum( + m.modular_fixed_cost[hot, cold, indx] + * 1e-3 + * m.module_config_active[hot, cold, stg, indx] + for indx in m.module_index_set + ), + doc="Sum fixed costs of active configurations for total investment.", + ) disj.discretize_area = Constraint( - expr=m.exchanger_area[stg, hot, cold] == sum( - m.module_area[indx] * - m.module_config_active[hot, cold, stg, indx] - for indx in m.module_index_set) + expr=m.exchanger_area[stg, hot, cold] + == sum( + m.module_area[indx] * m.module_config_active[hot, cold, stg, indx] + for indx in m.module_index_set + ), + doc="Match exchanger area with sum of active configuration areas.", ) disj.discretized_LMTD = Constraint( - expr=m.LMTD[hot, cold, stg] == sum( - m.LMTD_discretize[hot, cold, stg, indx] - for indx in m.module_index_set - ) + expr=m.LMTD[hot, cold, stg] + == sum( + m.LMTD_discretize[hot, cold, stg, indx] for indx in m.module_index_set + ), + doc="Aggregate LMTD from active configurations for thermal modeling.", ) @disj.Constraint(m.module_index_set) def discretized_LMTD_LB(disj, indx): + """ + Sets the lower bound on the discretized Log Mean Temperature Difference (LMTD) for each module configuration. + + Parameters + ---------- + disj : Pyomo.Disjunct + The disjunct object representing a specific module configuration. + indx : int + Index of the module configuration in the model. + + Returns + ------- + Pyomo.Constraint + A constraint ensuring that the discretized LMTD respects the specified lower bound for active configurations. + """ return ( - m.LMTD[hot, cold, stg].lb - * m.module_config_active[hot, cold, stg, indx] + m.LMTD[hot, cold, stg].lb * m.module_config_active[hot, cold, stg, indx] ) <= m.LMTD_discretize[hot, cold, stg, indx] @disj.Constraint(m.module_index_set) def discretized_LMTD_UB(disj, indx): + """ + Sets the upper bound on the discretized Log Mean Temperature Difference (LMTD) for each module configuration. + + Parameters + ---------- + disj : Pyomo.Disjunct + The disjunct object representing a specific module configuration. + indx : int + Index of the module configuration in the model. + + Returns + ------- + Pyomo.Constraint + A constraint ensuring that the discretized LMTD does not exceed the specified upper bound for active configurations. + """ return m.LMTD_discretize[hot, cold, stg, indx] <= ( - m.LMTD[hot, cold, stg].ub - * m.module_config_active[hot, cold, stg, indx] + m.LMTD[hot, cold, stg].ub * m.module_config_active[hot, cold, stg, indx] ) disj.exchanger_required_area = Constraint( - expr=m.U[hot, cold] * sum( + expr=m.U[hot, cold] + * sum( m.module_area[indx] * m.LMTD_discretize[hot, cold, stg, indx] - for indx in m.module_index_set) >= - m.heat_exchanged[hot, cold, stg]) + for indx in m.module_index_set + ) + >= m.heat_exchanged[hot, cold, stg], + doc="Ensures sufficient heat transfer capacity for required heat exchange.", + ) @m.Disjunct(m.module_sizes) def module_type(disj, size): - """Disjunct for selection of one module type.""" + """ + Disjunct for selecting a specific module size in the heat exchange model. This disjunct applies constraints to enforce that only the selected module size is active within any given configuration across all stages and matches. + + Parameters + ---------- + disj : Pyomo.Disjunct + The disjunct object associated with a specific module size. + size : int + The specific size of the module being considered in this disjunct. + + Returns + ------- + Pyomo.Disjunct + A Pyomo Disjunct object that contains constraints to limit the module configuration to a single size throughout the model. + """ + @disj.Constraint(m.valid_matches, m.stages, m.module_index_set) def no_other_module_types(_, hot, cold, stg, indx): + """ + Ensures only modules of the selected size are active, deactivating other sizes. + + Parameters + ---------- + _ : Pyomo.ConcreteModel + The Pyomo model instance, not used directly in the function. + hot : str + The index for the hot stream involved in the heat exchanger. + cold : str + The index for the cold stream involved in the heat exchanger. + stg : int + The index for the stage involved in the heat exchanger. + indx : int + Index of the module configuration in the model. + + Returns + ------- + Pyomo.Constraint + A constraint expression that ensures only modules of the specified size are active, effectively disabling other module sizes for the current configuration. + """ # num_modules, size = configurations_map[indx] if m.configurations_map[indx][1] != size: return m.module_config_active[hot, cold, stg, indx] == 0 else: return Constraint.NoConstraint + # disj.no_other_module_types = Constraint( # expr=sum( # m.module_config_active[hot, cold, stg, indx] # for indx in m.module_index_set - # if m.configurations_map[indx][1] != size) == 0 + # if m.configurations_map[indx][1] != size + # ) + # == 0, + # doc="Deactivates non-selected module sizes.", # ) + m.select_one_module_type = Disjunction( - expr=[m.module_type[area] for area in m.module_sizes]) + expr=[m.module_type[area] for area in m.module_sizes], + doc="Selects exactly one module size for use across all configurations.", + ) return m From 6ddf35a5b7df7d140f3ba74daaff6fb16cee31a5 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Fri, 26 Apr 2024 01:18:03 -0400 Subject: [PATCH 16/60] Refactor function parameter name in modular_discrete_single_module.py for clarity --- gdplib/mod_hens/modular_discrete_single_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gdplib/mod_hens/modular_discrete_single_module.py b/gdplib/mod_hens/modular_discrete_single_module.py index c2ab435..f3f863a 100644 --- a/gdplib/mod_hens/modular_discrete_single_module.py +++ b/gdplib/mod_hens/modular_discrete_single_module.py @@ -45,7 +45,7 @@ def build_model(use_cafaro_approximation, num_stages): Parameters ---------- - cafaro_approx : bool + use_cafaro_approximation : bool Specifies whether to use the Cafaro approximation in the model. num_stages : int The number of stages in the heat integration model. From 6ba6212d6be5c84ade19553bcbc9af900a513c1d Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Sat, 27 Apr 2024 15:56:17 -0400 Subject: [PATCH 17/60] Refactor function parameter name in modular_discrete_single_module.py for clarity --- gdplib/mod_hens/modular_discrete.py | 329 +++++++++++++++++++++++----- 1 file changed, 270 insertions(+), 59 deletions(-) diff --git a/gdplib/mod_hens/modular_discrete.py b/gdplib/mod_hens/modular_discrete.py index 66fdb46..66205b1 100644 --- a/gdplib/mod_hens/modular_discrete.py +++ b/gdplib/mod_hens/modular_discrete.py @@ -13,26 +13,47 @@ """ from __future__ import division -from pyomo.environ import (Binary, Constraint, log, Set, Var) +from pyomo.environ import Binary, Constraint, log, Set, Var from pyomo.gdp import Disjunct, Disjunction from . import common def build_require_modular(cafaro_approx, num_stages): + """ + Builds a heat integration model requiring all exchangers to use modular configurations. + + Parameters + ---------- + cafaro_approx : bool + Specifies whether to use the Cafaro approximation in the model. + num_stages : int + The number of stages in the heat integration model. + + Returns + ------- + Pyomo.ConcreteModel + A Pyomo model configured to use only modular heat exchanger configurations. + """ m = build_model(cafaro_approx, num_stages) # Require modular + # Enforce modular configuration for all valid matches and stages for hot, cold, stg in m.valid_matches * m.stages: disj = m.exchanger_exists[hot, cold, stg] disj.modular.indicator_var.fix(True) disj.conventional.deactivate() + # Optimize modular configurations based on cost for hot, cold in m.valid_matches: lowest_price = float('inf') + # Determine the least costly configuration for each size for size in sorted(m.possible_sizes, reverse=True): - current_size_cost = (m.modular_size_cost[hot, cold, size] + - m.modular_fixed_cost[hot, cold, size]) + current_size_cost = ( + m.modular_size_cost[hot, cold, size] + + m.modular_fixed_cost[hot, cold, size] + ) if current_size_cost > lowest_price: + # Deactivate configurations that are not the least costly for stg in m.stages: m.module_size_active[hot, cold, stg, size].fix(0) else: @@ -42,13 +63,31 @@ def build_require_modular(cafaro_approx, num_stages): def build_modular_option(cafaro_approx, num_stages): + """ + Constructs a heat integration model with the option for using modular configurations based on cost optimization. + + Parameters + ---------- + cafaro_approx : bool + Specifies whether to use the Cafaro approximation in the model. + num_stages : int + The number of stages in the heat integration model. + + Returns + ------- + Pyomo.ConcreteModel + A Pyomo model that considers modular exchanger options based on cost efficiencies. + """ m = build_model(cafaro_approx, num_stages) + # Optimize for the least cost configuration across all stages and matche for hot, cold in m.valid_matches: lowest_price = float('inf') for size in sorted(m.possible_sizes, reverse=True): - current_size_cost = (m.modular_size_cost[hot, cold, size] + - m.modular_fixed_cost[hot, cold, size]) + current_size_cost = ( + m.modular_size_cost[hot, cold, size] + + m.modular_fixed_cost[hot, cold, size] + ) if current_size_cost > lowest_price: for stg in m.stages: m.module_size_active[hot, cold, stg, size].fix(0) @@ -59,14 +98,35 @@ def build_modular_option(cafaro_approx, num_stages): def build_model(use_cafaro_approximation, num_stages): - """Build the model.""" + """ + Initializes a base Pyomo model for heat integration using the common building blocks, with additional configuration for modular sizing. + + Parameters + ---------- + use_cafaro_approximation : bool + Specifies whether to use the Cafaro approximation in the model. + num_stages : int + The number of stages in the heat integration model. + + Returns + ------- + Pyomo.ConcreteModel + A Pyomo model configured for heat integration with modular exchanger options. + """ m = common.build_model(use_cafaro_approximation, num_stages) - m.possible_sizes = Set(initialize=[10 * (i + 1) for i in range(50)]) + m.possible_sizes = Set( + initialize=[10 * (i + 1) for i in range(50)], + doc="Set of possible module sizes, ranging from 10 to 500 in increments of 10.", + ) m.module_size_active = Var( - m.valid_matches, m.stages, m.possible_sizes, + m.valid_matches, + m.stages, + m.possible_sizes, doc="Total area of modular exchangers for each match.", - domain=Binary, initialize=0) + domain=Binary, + initialize=0, + ) num_modules_required = {} for size in m.possible_sizes: @@ -79,30 +139,106 @@ def build_model(use_cafaro_approximation, num_stages): num_modules_required[size, area] = remaining_size // area remaining_size = remaining_size % area - @m.Param(m.valid_matches, m.possible_sizes, m.module_sizes, - doc="Number of exchangers of each area required to " - "yield a certain total size.") + @m.Param( + m.valid_matches, + m.possible_sizes, + m.module_sizes, + doc="Number of exchangers of each area required to " + "yield a certain total size.", + ) def modular_num_exchangers(m, hot, cold, size, area): + """ + Returns the number of exchangers required for a given total module size and area. + + Parameters + ---------- + m : Pyomo.ConcreteModel + A Pyomo model configured for heat integration with modular exchanger options. + hot : str + The index for the hot stream involved in the heat exchanger. + cold : str + The index for the cold stream involved in the heat exchanger. + size : int + Module size under consideration. + area : float + The modular area size of the heat exchanger. + + Returns + ------- + Pyomo.Parameter + Number of modules of the specified area required to achieve the total size. + """ return num_modules_required[size, area] - @m.Param(m.valid_matches, m.possible_sizes, - doc="Area cost for each modular exchanger size.") + @m.Param( + m.valid_matches, + m.possible_sizes, + doc="Area cost for each modular exchanger size.", + ) def modular_size_cost(m, hot, cold, size): - return sum(m.modular_num_exchangers[hot, cold, size, area] * - m.module_area_cost[hot, cold, area] - for area in m.module_sizes) + """ + Returns the total area cost for a specified module size by summing costs of all required module areas. + + Parameters + ---------- + m : Pyomo.ConcreteModel + A Pyomo model configured for heat integration with modular exchanger options. + hot : str + The index for the hot stream involved in the heat exchanger. + cold : str + The index for the cold stream involved in the heat exchanger. + size : int + Module size under consideration. + + Returns + ------- + Pyomo.Parameter + Total area cost for the specified module size. + """ + return sum( + m.modular_num_exchangers[hot, cold, size, area] + * m.module_area_cost[hot, cold, area] + for area in m.module_sizes + ) - @m.Param(m.valid_matches, m.possible_sizes, - doc="Fixed cost for each modular exchanger size.") + @m.Param( + m.valid_matches, + m.possible_sizes, + doc="Fixed cost for each modular exchanger size.", + ) def modular_fixed_cost(m, hot, cold, size): - return sum(m.modular_num_exchangers[hot, cold, size, area] * - m.module_fixed_unit_cost - for area in m.module_sizes) + """ + Returns the total fixed cost associated with a specific modular exchanger size. + + Parameters + ---------- + m : Pyomo.ConcreteModel + A Pyomo model configured for heat integration with modular exchanger options. + hot : str + The index for the hot stream involved in the heat exchanger. + cold : str + The index for the cold stream involved in the heat exchanger. + size : int + Module size under consideration. + + Returns + ------- + Pyomo.Parameter + Total fixed cost for the specified module size. + """ + return sum( + m.modular_num_exchangers[hot, cold, size, area] * m.module_fixed_unit_cost + for area in m.module_sizes + ) m.LMTD_discretize = Var( - m.hot_streams, m.cold_streams, m.stages, m.possible_sizes, + m.hot_streams, + m.cold_streams, + m.stages, + m.possible_sizes, doc="Discretized log mean temperature difference", - bounds=(0, 500), initialize=0 + bounds=(0, 500), + initialize=0, ) for hot, cold, stg in m.valid_matches * m.stages: @@ -110,84 +246,159 @@ def modular_fixed_cost(m, hot, cold, size): disj.conventional = Disjunct() if not use_cafaro_approximation: disj.conventional.exchanger_area_cost = Constraint( - expr=m.exchanger_area_cost[stg, hot, cold] * 1E-3 >= - m.exchanger_area_cost_factor[hot, cold] * 1E-3 * - m.exchanger_area[stg, hot, cold] ** m.area_cost_exponent) + expr=m.exchanger_area_cost[stg, hot, cold] * 1e-3 + >= m.exchanger_area_cost_factor[hot, cold] + * 1e-3 + * m.exchanger_area[stg, hot, cold] ** m.area_cost_exponent, + doc="Ensures area cost meets the standard cost scaling.", + ) else: disj.conventional.exchanger_area_cost = Constraint( - expr=m.exchanger_area_cost[stg, hot, cold] * 1E-3 >= - m.exchanger_area_cost_factor[hot, cold] * 1E-3 * m.cafaro_k - * log(m.cafaro_b * m.exchanger_area[stg, hot, cold] + 1) + expr=m.exchanger_area_cost[stg, hot, cold] * 1e-3 + >= m.exchanger_area_cost_factor[hot, cold] + * 1e-3 + * m.cafaro_k + * log(m.cafaro_b * m.exchanger_area[stg, hot, cold] + 1), + doc="Ensures area cost meets the Cafaro approximation.", ) m.BigM[disj.conventional.exchanger_area_cost] = 100 disj.conventional.exchanger_fixed_cost = Constraint( - expr=m.exchanger_fixed_cost[stg, hot, cold] == - m.exchanger_fixed_unit_cost[hot, cold]) + expr=m.exchanger_fixed_cost[stg, hot, cold] + == m.exchanger_fixed_unit_cost[hot, cold], + doc="Sets the fixed cost for conventional exchangers.", + ) @disj.conventional.Constraint(m.possible_sizes) def no_modules(_, size): + """ + Ensures that no modules are active in the conventional configuration. + + Parameters + ---------- + _ : Pyomo.ConcreteModel + The Pyomo model instance, not used directly in the function. + size : int + Module size under consideration. + + Returns + ------- + Pyomo.Constraint + A constraint that forces the module size active variables to zero, ensuring no modular units are mistakenly considered in conventional configurations. + """ return m.module_size_active[hot, cold, stg, size] == 0 # Area requirement disj.conventional.exchanger_required_area = Constraint( - expr=m.exchanger_area[stg, hot, cold] * - m.U[hot, cold] * m.LMTD[hot, cold, stg] >= - m.heat_exchanged[hot, cold, stg]) + expr=m.exchanger_area[stg, hot, cold] + * m.U[hot, cold] + * m.LMTD[hot, cold, stg] + >= m.heat_exchanged[hot, cold, stg], + doc="Calculates the required area based on heat exchanged and LMTD.", + ) m.BigM[disj.conventional.exchanger_required_area] = 5000 disj.modular = Disjunct() disj.modular.choose_one_config = Constraint( expr=sum( - m.module_size_active[hot, cold, stg, size] - for size in m.possible_sizes) == 1 + m.module_size_active[hot, cold, stg, size] for size in m.possible_sizes + ) + == 1, + doc="Only one module size can be active.", ) disj.modular.exchanger_area_cost = Constraint( - expr=m.exchanger_area_cost[stg, hot, cold] * 1E-3 == - sum(m.modular_size_cost[hot, cold, size] * 1E-3 * - m.module_size_active[hot, cold, stg, size] - for size in m.possible_sizes) + expr=m.exchanger_area_cost[stg, hot, cold] * 1e-3 + == sum( + m.modular_size_cost[hot, cold, size] + * 1e-3 + * m.module_size_active[hot, cold, stg, size] + for size in m.possible_sizes + ), + doc="Area cost for modular exchangers.", ) disj.modular.exchanger_fixed_cost = Constraint( - expr=m.exchanger_fixed_cost[stg, hot, cold] == - sum(m.modular_fixed_cost[hot, cold, size] * 1E-3 * - m.module_size_active[hot, cold, stg, size] - for size in m.possible_sizes)) + expr=m.exchanger_fixed_cost[stg, hot, cold] + == sum( + m.modular_fixed_cost[hot, cold, size] + * 1e-3 + * m.module_size_active[hot, cold, stg, size] + for size in m.possible_sizes + ), + doc="Fixed cost for modular exchangers.", + ) disj.modular.discretize_area = Constraint( - expr=m.exchanger_area[stg, hot, cold] == sum( + expr=m.exchanger_area[stg, hot, cold] + == sum( area * m.module_size_active[hot, cold, stg, area] - for area in m.possible_sizes) + for area in m.possible_sizes + ), + doc="Total area of modular exchangers for each match.", ) disj.modular.discretized_LMTD = Constraint( - expr=m.LMTD[hot, cold, stg] == sum( - m.LMTD_discretize[hot, cold, stg, size] - for size in m.possible_sizes - ) + expr=m.LMTD[hot, cold, stg] + == sum( + m.LMTD_discretize[hot, cold, stg, size] for size in m.possible_sizes + ), + doc="Discretized LMTD for each match.", ) @disj.modular.Constraint(m.possible_sizes) def discretized_LMTD_LB(disj, size): + """ + Sets the lower bound on the discretized Log Mean Temperature Difference (LMTD) for each possible size. + + Parameters + ---------- + disj : Pyomo.Disjunct + The disjunct object representing a specific module size. + size : int + Module size under consideration. + + Returns + ------- + Pyomo.Constraint + A constraint that sets the lower limit for the discretized LMTD based on the module size active in the configuration. + """ return ( - m.LMTD[hot, cold, stg].lb - * m.module_size_active[hot, cold, stg, size] + m.LMTD[hot, cold, stg].lb * m.module_size_active[hot, cold, stg, size] ) <= m.LMTD_discretize[hot, cold, stg, size] @disj.modular.Constraint(m.possible_sizes) def discretized_LMTD_UB(disj, size): + """ + Sets the upper bound on the discretized Log Mean Temperature Difference (LMTD) for each possible size. + + Parameters + ---------- + disj : Pyomo.Disjunct + The disjunct object representing a specific module size. + size : int + Module size under consideration. + + Returns + ------- + Pyomo.Constraint + A constraint that sets the upper limit for the discretized LMTD based on the module size active in the configuration. + """ return m.LMTD_discretize[hot, cold, stg, size] <= ( - m.LMTD[hot, cold, stg].ub - * m.module_size_active[hot, cold, stg, size] + m.LMTD[hot, cold, stg].ub * m.module_size_active[hot, cold, stg, size] ) disj.modular.exchanger_required_area = Constraint( - expr=m.U[hot, cold] * sum( + expr=m.U[hot, cold] + * sum( area * m.LMTD_discretize[hot, cold, stg, area] - for area in m.possible_sizes) >= - m.heat_exchanged[hot, cold, stg]) + for area in m.possible_sizes + ) + >= m.heat_exchanged[hot, cold, stg], + doc="Calculates the required area based on heat exchanged and LMTD.", + ) disj.modular_or_not = Disjunction( - expr=[disj.modular, disj.conventional]) + expr=[disj.modular, disj.conventional], + doc="Disjunction between modular and conventional configurations.", + ) return m From 8e49bb4f763c6be2e52cc7ad9c8081e67bf63e81 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Sat, 27 Apr 2024 16:43:39 -0400 Subject: [PATCH 18/60] Refactor function parameter name in modular_discrete_single_module.py for clarity --- gdplib/mod_hens/modular_integer.py | 183 +++++++++++++++++++++++------ 1 file changed, 150 insertions(+), 33 deletions(-) diff --git a/gdplib/mod_hens/modular_integer.py b/gdplib/mod_hens/modular_integer.py index 14dcf91..6034037 100644 --- a/gdplib/mod_hens/modular_integer.py +++ b/gdplib/mod_hens/modular_integer.py @@ -10,13 +10,28 @@ """ from __future__ import division -from pyomo.environ import (Constraint, Integers, log, Var) +from pyomo.environ import Constraint, Integers, log, Var from pyomo.gdp import Disjunct, Disjunction from gdplib.mod_hens import common def build_single_module(cafaro_approx, num_stages): + """ + Constructs a Pyomo model configured to exclusively use modular heat exchangers, forcing selection of a single module type per stage and stream match. This configuration utilizes the Cafaro approximation if specified. + + Parameters + ---------- + cafaro_approx : bool + Specifies whether to use the Cafaro approximation in the model. + num_stages : int + The number of stages in the heat integration model. + + Returns + ------- + Pyomo.ConcreteModel + A Pyomo ConcreteModel optimized with constraints for modular heat exchanger configurations, fixed to a single module type per valid match. + """ m = build_model(cafaro_approx, num_stages) # Require modular for hot, cold, stg in m.valid_matches * m.stages: @@ -27,20 +42,52 @@ def build_single_module(cafaro_approx, num_stages): # Must choose only one type of module @m.Disjunct(m.module_sizes) def module_type(disj, size): - """Disjunct for selection of one module type.""" + """ + Disjunct for selection of one module type. + + Parameters + ---------- + disj : Pyomo.Disjunct + _description_ + size : int + Module size under consideration. + """ disj.no_other_module_types = Constraint( - expr=sum(m.num_modules[hot, cold, stage, area] - for hot, cold in m.valid_matches - for stage in m.stages - for area in m.module_sizes - if area != size) == 0) + expr=sum( + m.num_modules[hot, cold, stage, area] + for hot, cold in m.valid_matches + for stage in m.stages + for area in m.module_sizes + if area != size + ) + == 0, + doc="Ensures no modules of other sizes are active when this size is selected.", + ) + m.select_one_module_type = Disjunction( - expr=[m.module_type[area] for area in m.module_sizes]) + expr=[m.module_type[area] for area in m.module_sizes], + doc="Select one module type", + ) return m def build_require_modular(cafaro_approx, num_stages): + """ + Builds a Pyomo model that requires the use of modular configurations for all heat exchangers within the model. This setup deactivates any conventional exchanger configurations. + + Parameters + ---------- + cafaro_approx : bool + Specifies whether to use the Cafaro approximation in the model. + num_stages : int + The number of stages in the heat integration model. + + Returns + ------- + Pyomo.ConcreteModel + A Pyomo model configured to require modular heat exchangers throughout the network. + """ m = build_model(cafaro_approx, num_stages) # Require modular for hot, cold, stg in m.valid_matches * m.stages: @@ -50,17 +97,51 @@ def build_require_modular(cafaro_approx, num_stages): def build_modular_option(cafaro_approx, num_stages): + """ + Builds a Pyomo model that can optionally use modular heat exchangers based on configuration decisions within the model. This function initializes a model using the Cafaro approximation as specified. + + Parameters + ---------- + cafaro_approx : bool + Specifies whether to use the Cafaro approximation in the model. + num_stages : int + The number of stages in the heat integration model. + + Returns + ------- + Pyomo.ConcreteModel + Returns a Pyomo model with the flexibility to choose modular heat exchanger configurations based on optimization results. + """ return build_model(cafaro_approx, num_stages) def build_model(use_cafaro_approximation, num_stages): - """Build the model.""" + """ + Base function for constructing a heat exchange network model with optional use of Cafaro approximation and integration of modular heat exchangers represented by integer variables. + + Parameters + ---------- + use_cafaro_approximation : bool + Specifies whether to use the Cafaro approximation in the model. + num_stages : int + The number of stages in the heat integration model. + + Returns + ------- + Pyomo.ConcreteModel + The initialized Pyomo model including both conventional and modular heat exchanger options. + """ m = common.build_model(use_cafaro_approximation, num_stages) m.num_modules = Var( - m.valid_matches, m.stages, m.module_sizes, + m.valid_matches, + m.stages, + m.module_sizes, doc="The number of modules of each size at each exchanger.", - domain=Integers, bounds=(0, 100), initialize=0) + domain=Integers, + bounds=(0, 100), + initialize=0, + ) # improve quality of bounds for size in m.module_sizes: for var in m.num_modules[:, :, :, size]: @@ -72,48 +153,84 @@ def build_model(use_cafaro_approximation, num_stages): disj.conventional = Disjunct() if not use_cafaro_approximation: disj.conventional.exchanger_area_cost = Constraint( - expr=m.exchanger_area_cost[stg, hot, cold] * 1E-3 >= - m.exchanger_area_cost_factor[hot, cold] * 1E-3 * - m.exchanger_area[stg, hot, cold] ** m.area_cost_exponent) + expr=m.exchanger_area_cost[stg, hot, cold] * 1e-3 + >= m.exchanger_area_cost_factor[hot, cold] + * 1e-3 + * m.exchanger_area[stg, hot, cold] ** m.area_cost_exponent, + doc="Ensures area cost meets the standard cost scaling.", + ) else: disj.conventional.exchanger_area_cost = Constraint( - expr=m.exchanger_area_cost[stg, hot, cold] * 1E-3 >= - m.exchanger_area_cost_factor[hot, cold] * 1E-3 * m.cafaro_k - * log(m.cafaro_b * m.exchanger_area[stg, hot, cold] + 1) + expr=m.exchanger_area_cost[stg, hot, cold] * 1e-3 + >= 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.", ) m.BigM[disj.conventional.exchanger_area_cost] = 100 disj.conventional.exchanger_fixed_cost = Constraint( - expr=m.exchanger_fixed_cost[stg, hot, cold] == - m.exchanger_fixed_unit_cost[hot, cold]) + expr=m.exchanger_fixed_cost[stg, hot, cold] + == m.exchanger_fixed_unit_cost[hot, cold], + doc="Sets fixed cost for the exchanger based on unit costs.", + ) @disj.conventional.Constraint(m.module_sizes) def no_modules(_, area): + """ + Ensures that no modules are active in the conventional configuration. + + Parameters + ---------- + _ : Pyomo.ConcreteModel + The Pyomo model instance, not used directly in the function + area : float + The modular area size of the heat exchanger + + Returns + ------- + Pyomo.Constraint + A constraint that forces the module size active variables to zero, ensuring no modular units are mistakenly considered in conventional configurations. + """ return m.num_modules[hot, cold, stg, area] == 0 disj.modular = Disjunct() disj.modular.exchanger_area_cost = Constraint( - expr=m.exchanger_area_cost[stg, hot, cold] * 1E-3 == - sum(m.module_area_cost[hot, cold, area] + expr=m.exchanger_area_cost[stg, hot, cold] * 1e-3 + == sum( + m.module_area_cost[hot, cold, area] * m.num_modules[hot, cold, stg, area] - for area in m.module_sizes) - * 1E-3) + for area in m.module_sizes + ) + * 1e-3, + doc="Area cost for modular exchanger", + ) disj.modular.exchanger_fixed_cost = Constraint( - expr=m.exchanger_fixed_cost[stg, hot, cold] == - m.module_fixed_unit_cost * sum(m.num_modules[hot, cold, stg, area] - for area in m.module_sizes)) + expr=m.exchanger_fixed_cost[stg, hot, cold] + == m.module_fixed_unit_cost + * sum(m.num_modules[hot, cold, stg, area] for area in m.module_sizes), + doc="Fixed cost for modular exchanger", + ) disj.modular.exchanger_area = Constraint( - expr=m.exchanger_area[stg, hot, cold] == - sum(area * m.num_modules[hot, cold, stg, area] - for area in m.module_sizes)) + expr=m.exchanger_area[stg, hot, cold] + == sum( + area * m.num_modules[hot, cold, stg, area] for area in m.module_sizes + ), + doc="Area for modular exchanger", + ) disj.modular_or_not = Disjunction( - expr=[disj.modular, disj.conventional]) + expr=[disj.modular, disj.conventional], + doc="Module or conventional exchanger", + ) # Area requirement disj.exchanger_required_area = Constraint( - expr=m.exchanger_area[stg, hot, cold] * ( - m.U[hot, cold] * m.LMTD[hot, cold, stg]) >= - m.heat_exchanged[hot, cold, stg]) + expr=m.exchanger_area[stg, hot, cold] + * (m.U[hot, cold] * m.LMTD[hot, cold, stg]) + >= m.heat_exchanged[hot, cold, stg], + doc="Area requirement for exchanger", + ) m.BigM[disj.exchanger_required_area] = 5000 return m From 788934be9c2756d0a61d46761817cb56b89a2c4b Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Sat, 27 Apr 2024 17:37:24 -0400 Subject: [PATCH 19/60] Refactor alphanumeric sorting functions in util.py --- gdplib/stranded_gas/util.py | 39 +++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/gdplib/stranded_gas/util.py b/gdplib/stranded_gas/util.py index b2822a7..e61a60b 100644 --- a/gdplib/stranded_gas/util.py +++ b/gdplib/stranded_gas/util.py @@ -3,6 +3,19 @@ # Alphanumeric sort, taken from https://nedbatchelder.com/blog/200712/human_sorting.html def tryint(s): + """ + Attempts to convert a string to an integer. If conversion fails, returns the original string. + + Parameters + ---------- + s : str + The string to convert to an integer. + + Returns + ------- + int or str + An integer if `s` can be converted, otherwise the original string. + """ try: return int(s) except ValueError: @@ -10,13 +23,35 @@ def tryint(s): def alphanum_key(s): - """ Turn a string into a list of string and number chunks. + """ + Turn a string into a list of string and number chunks. "z23a" -> ["z", 23, "a"] + + Parameters + ---------- + s : str + The string to split into chunks of strings and integers. + + Returns + ------- + list + A list of strings and integers extracted from the input string. """ return [tryint(c) for c in re.split('([0-9]+)', s)] def alphanum_sorted(l): - """ Sort the given list in the way that humans expect. + """ + Sort the given list in the way that humans expect. + + Parameters + ---------- + l : list + The list of strings to sort. + + Returns + ------- + list + The list sorted in human-like alphanumeric order. """ return sorted(l, key=alphanum_key) From 3a5a05f290a91b81c74e3297b34fd6d48fd16af7 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Sat, 27 Apr 2024 17:47:48 -0400 Subject: [PATCH 20/60] References added on the model.py --- gdplib/stranded_gas/model.py | 631 +++++++++++++++++++++++++++++++++++ 1 file changed, 631 insertions(+) diff --git a/gdplib/stranded_gas/model.py b/gdplib/stranded_gas/model.py index e563078..a43e583 100644 --- a/gdplib/stranded_gas/model.py +++ b/gdplib/stranded_gas/model.py @@ -14,6 +14,17 @@ def build_model(): + """_summary_ + + Returns + ------- + _type_ + _description_ + + References + ---------- + [1] Chen, Q., & Grossmann, I. E. (2019). Economies of numbers for a modular stranded gas processing network: Modeling and optimization. In Computer Aided Chemical Engineering (Vol. 47, pp. 257-262). Elsevier. DOI: 10.1016/B978-0-444-64241-7.50100-3 + """ m = ConcreteModel() m.BigM = Suffix(direction=Suffix.LOCAL) @@ -29,6 +40,20 @@ def build_model(): @m.Param(m.time) def discount_factor(m, t): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + t : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return (1 + m.discount_rate / m.periods_per_year) ** (-t / m.periods_per_year) xlsx_data = pd.read_excel(os.path.join(os.path.dirname(__file__), "data.xlsx"), sheet_name=None) @@ -37,18 +62,74 @@ def discount_factor(m, t): @m.Param(m.module_types) def module_base_cost(m, mtype): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + mtype : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return float(module_sheet[mtype]['Capital Cost [MM$]']) @m.Param(m.module_types, doc="Natural gas consumption per module of this type [MMSCF/d]") def unit_gas_consumption(m, mtype): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + mtype : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return float(module_sheet[mtype]['Nat Gas [MMSCF/d]']) @m.Param(m.module_types, doc="Gasoline production per module of this type [kBD]") def gasoline_production(m, mtype): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + mtype : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return float(module_sheet[mtype]['Gasoline [kBD]']) @m.Param(m.module_types, doc="Overall conversion of natural gas into gasoline per module of this type [kB/MMSCF]") def module_conversion(m, mtype): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + mtype : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return float(module_sheet[mtype]['Conversion [kB/MMSCF]']) site_sheet = xlsx_data['sites'].set_index('Potential site') @@ -60,10 +141,38 @@ def module_conversion(m, mtype): @m.Param(m.potential_sites) def site_x(m, site): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + site : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return float(site_sheet['x'][site]) @m.Param(m.potential_sites) def site_y(m, site): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + site : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return float(site_sheet['y'][site]) well_sheet = xlsx_data['wells'].set_index('Well') @@ -71,10 +180,38 @@ def site_y(m, site): @m.Param(m.well_clusters) def well_x(m, well): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + well : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return float(well_sheet['x'][well]) @m.Param(m.well_clusters) def well_y(m, well): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + well : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return float(well_sheet['y'][well]) sched_sheet = xlsx_data['well-schedule'] @@ -89,6 +226,22 @@ def well_y(m, well): @m.Param(m.well_clusters, m.time, doc="Supply of gas from well cluster [MMSCF/day]") def gas_supply(m, well, t): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + well : _type_ + _description_ + t : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return sum(well_profiles[well][t * 3:t * 3 + 2]) / 3 mkt_sheet = xlsx_data['markets'].set_index('Market') @@ -96,10 +249,38 @@ def gas_supply(m, well, t): @m.Param(m.markets) def mkt_x(m, mkt): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + mkt : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return float(mkt_sheet['x'][mkt]) @m.Param(m.markets) def mkt_y(m, mkt): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + mkt : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return float(mkt_sheet['y'][mkt]) @m.Param(m.markets, doc="Gasoline demand [kBD]") @@ -111,6 +292,22 @@ def mkt_demand(m, mkt): @m.Param(m.sources, m.destinations, doc="Distance [mi]") def distance(m, src, dest): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + src : _type_ + _description_ + dest : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ if src in m.well_clusters: src_x = m.well_x[src] src_y = m.well_y[src] @@ -142,22 +339,92 @@ def distance(m, src, dest): @m.Param(m.time, doc="Module transport cost per mile [M$/100 miles]") def module_transport_distance_cost(m, t): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + t : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return 50 * m.discount_factor[t] @m.Param(m.time, doc="Module transport cost per unit [MM$/module]") def module_transport_unit_cost(m, t): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + t : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return 3 * m.discount_factor[t] @m.Param(m.time, doc="Stranded gas price [$/MSCF]") def nat_gas_price(m, t): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + t : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return 5 * m.discount_factor[t] @m.Param(m.time, doc="Gasoline price [$/gal]") def gasoline_price(m, t): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + t : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return 2.5 * m.discount_factor[t] @m.Param(m.time, doc="Gasoline transport cost [$/gal/100 miles]") def gasoline_tranport_cost(m, t): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + t : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return 0.045 * m.discount_factor[t] m.gal_per_bbl = Param(initialize=42, doc="Gallons per barrel") @@ -170,6 +437,15 @@ def gasoline_tranport_cost(m, t): @m.Disjunct(m.module_types) def mtype_exists(disj, mtype): + """_summary_ + + Parameters + ---------- + disj : _type_ + _description_ + mtype : _type_ + _description_ + """ disj.learning_factor_calc = Constraint( expr=m.learning_factor[mtype] == (1 - m.learning_rate) ** ( log(sum(m.modules_purchased[mtype, :, :])) / log(2))) @@ -179,15 +455,54 @@ def mtype_exists(disj, mtype): @m.Disjunct(m.module_types) def mtype_absent(disj, mtype): + """_summary_ + + Parameters + ---------- + disj : _type_ + _description_ + mtype : _type_ + _description_ + """ disj.constant_learning_factor = Constraint( expr=m.learning_factor[mtype] == 1) @m.Disjunction(m.module_types) def mtype_existence(m, mtype): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + mtype : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return [m.mtype_exists[mtype], m.mtype_absent[mtype]] @m.Expression(m.module_types, m.time, doc="Module unit cost [MM$/module]") def module_unit_cost(m, mtype, t): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + mtype : _type_ + _description_ + t : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return m.module_base_cost[mtype] * m.learning_factor[mtype] * m.discount_factor[t] m.production = Var( @@ -210,37 +525,153 @@ def module_unit_cost(m, mtype, t): @m.Constraint(m.potential_sites, m.module_types, m.time) def consumption_capacity(m, site, mtype, t): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + site : _type_ + _description_ + mtype : _type_ + _description_ + t : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return m.gas_consumption[site, mtype, t] <= ( m.num_modules[mtype, site, t] * m.unit_gas_consumption[mtype]) @m.Constraint(m.potential_sites, m.time) def production_limit(m, site, t): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + site : _type_ + _description_ + t : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return m.production[site, t] <= sum( m.gas_consumption[site, mtype, t] * m.module_conversion[mtype] for mtype in m.module_types) @m.Expression(m.potential_sites, m.time) def capacity(m, site, t): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + site : _type_ + _description_ + t : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return sum( m.num_modules[mtype, site, t] * m.unit_gas_consumption[mtype] * m.module_conversion[mtype] for mtype in m.module_types) @m.Constraint(m.potential_sites, m.time) def gas_supply_meets_consumption(m, site, t): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + site : _type_ + _description_ + t : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return sum(m.gas_consumption[site, :, t]) == sum(m.gas_flows[:, site, t]) @m.Constraint(m.well_clusters, m.time) def gas_supply_limit(m, well, t): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + well : _type_ + _description_ + t : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return sum(m.gas_flows[well, site, t] for site in m.potential_sites) <= m.gas_supply[well, t] @m.Constraint(m.potential_sites, m.time) def gasoline_production_requirement(m, site, t): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + site : _type_ + _description_ + t : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return sum(m.product_flows[site, mkt, t] for mkt in m.markets) == m.production[site, t] @m.Constraint(m.potential_sites, m.module_types, m.time) def module_balance(m, site, mtype, t): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + site : _type_ + _description_ + mtype : _type_ + _description_ + t : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ if t >= m.module_setup_time: modules_added = m.modules_purchased[ mtype, site, t - m.module_setup_time] @@ -265,10 +696,28 @@ def module_balance(m, site, mtype, t): @m.Disjunct(m.potential_sites) def site_active(disj, site): + """_summary_ + + Parameters + ---------- + disj : _type_ + _description_ + site : _type_ + _description_ + """ pass @m.Disjunct(m.potential_sites) def site_inactive(disj, site): + """_summary_ + + Parameters + ---------- + disj : _type_ + _description_ + site : _type_ + _description_ + """ disj.no_production = Constraint( expr=sum(m.production[site, :]) == 0) disj.no_gas_consumption = Constraint( @@ -293,24 +742,90 @@ def site_inactive(disj, site): @m.Disjunction(m.potential_sites) def site_active_or_not(m, site): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + site : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return [m.site_active[site], m.site_inactive[site]] @m.Disjunct(m.well_clusters, m.potential_sites) def pipeline_exists(disj, well, site): + """_summary_ + + Parameters + ---------- + disj : _type_ + _description_ + well : _type_ + _description_ + site : _type_ + _description_ + """ pass @m.Disjunct(m.well_clusters, m.potential_sites) def pipeline_absent(disj, well, site): + """_summary_ + + Parameters + ---------- + disj : _type_ + _description_ + well : _type_ + _description_ + site : _type_ + _description_ + """ disj.no_natural_gas_flow = Constraint( expr=sum(m.gas_flows[well, site, t] for t in m.time) == 0) @m.Disjunction(m.well_clusters, m.potential_sites) def pipeline_existence(m, well, site): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + well : _type_ + _description_ + site : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return [m.pipeline_exists[well, site], m.pipeline_absent[well, site]] # Objective Function Construnction @m.Expression(m.potential_sites, doc="MM$") def product_revenue(m, site): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + site : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return sum( m.product_flows[site, mkt, t] # kBD * 1000 # bbl/kB @@ -322,6 +837,20 @@ def product_revenue(m, site): @m.Expression(m.potential_sites, doc="MM$") def raw_material_cost(m, site): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + site : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return sum( m.gas_consumption[site, mtype, t] * m.days_per_period / 1E6 # $ to MM$ @@ -333,6 +862,22 @@ def raw_material_cost(m, site): m.potential_sites, m.markets, doc="Aggregate cost to transport gasoline from a site to market [MM$]") def product_transport_cost(m, site, mkt): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + site : _type_ + _description_ + mkt : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return sum( m.product_flows[site, mkt, t] * m.gal_per_bbl * 1000 # bbl/kB @@ -342,12 +887,44 @@ def product_transport_cost(m, site, mkt): @m.Expression(m.well_clusters, m.potential_sites, doc="MM$") def pipeline_construction_cost(m, well, site): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + well : _type_ + _description_ + site : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return (m.pipeline_unit_cost * m.distance[well, site] * m.pipeline_exists[well, site].binary_indicator_var) # Module transport cost @m.Expression(m.site_pairs, doc="MM$") def module_relocation_cost(m, from_site, to_site): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + from_site : _type_ + _description_ + to_site : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return sum( m.modules_transferred[mtype, from_site, to_site, t] * m.distance[from_site, to_site] / 100 @@ -360,6 +937,20 @@ def module_relocation_cost(m, from_site, to_site): @m.Expression(m.potential_sites, doc="MM$") def module_purchase_cost(m, site): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + site : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return sum( m.module_unit_cost[mtype, t] * m.modules_purchased[mtype, site, t] for mtype in m.module_types @@ -367,6 +958,18 @@ def module_purchase_cost(m, site): @m.Expression(doc="MM$") def profit(m): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return ( summation(m.product_revenue) - summation(m.raw_material_cost) @@ -381,10 +984,38 @@ def profit(m): # Tightening constraints @m.Constraint(doc="Limit total module purchases over project span.") def restrict_module_purchases(m): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return sum(m.modules_purchased[...]) <= 5 @m.Constraint(m.site_pairs, doc="Limit transfers between any two sites") def restrict_module_transfers(m, from_site, to_site): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + from_site : _type_ + _description_ + to_site : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return sum(m.modules_transferred[:, from_site, to_site, :]) <= 5 return m From 03a1686734906b6e5439d36a48a8b936571832a8 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Sat, 27 Apr 2024 19:29:34 -0400 Subject: [PATCH 21/60] Document the parameters of the stranded gas models. --- gdplib/stranded_gas/model.py | 555 +++++++++++++++++++---------------- 1 file changed, 296 insertions(+), 259 deletions(-) diff --git a/gdplib/stranded_gas/model.py b/gdplib/stranded_gas/model.py index a43e583..6a47329 100644 --- a/gdplib/stranded_gas/model.py +++ b/gdplib/stranded_gas/model.py @@ -14,12 +14,13 @@ def build_model(): - """_summary_ + """ + Constructs a Pyomo ConcreteModel for optimizing a modular stranded gas processing network. The model is designed to convert stranded gas into gasoline using a modular and intensified GTL process. It incorporates the economic dynamics of module investments, gas processing, and product transportation. Returns ------- - _type_ - _description_ + Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL (Gas To Liquid) network. The model includes variables for the number of modules, their type and placement, as well as for production and transportation of gasoline. It aims to maximize the net present value (NPV) of the processing network. References ---------- @@ -40,100 +41,105 @@ def build_model(): @m.Param(m.time) def discount_factor(m, t): - """_summary_ + """ + Calculates the discount factor for a given time period in the model. Parameters ---------- - m : _type_ - _description_ - t : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + t : int + Set of time periods quarterly period within the 15 year project life. Returns ------- - _type_ - _description_ + Pyomo.Parameter + A float representing the discount factor for the given time period. """ return (1 + m.discount_rate / m.periods_per_year) ** (-t / m.periods_per_year) xlsx_data = pd.read_excel(os.path.join(os.path.dirname(__file__), "data.xlsx"), sheet_name=None) module_sheet = xlsx_data['modules'].set_index('Type') - m.module_types = Set(initialize=module_sheet.columns.tolist(),) + m.module_types = Set(initialize=module_sheet.columns.tolist(), doc="Module types") @m.Param(m.module_types) def module_base_cost(m, mtype): - """_summary_ + """ + Calculates the base cost of a module of a given type. Parameters ---------- - m : _type_ - _description_ - mtype : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + mtype : str + Index of the module type (A500, R500, S500, U500, A1000, R1000, S1000, U1000, A2000, R2000, S2000, U2000, A5000, R5000). Returns ------- - _type_ - _description_ + Pyomo.Parameter + A float representing the base cost of a module of the given type. """ return float(module_sheet[mtype]['Capital Cost [MM$]']) @m.Param(m.module_types, doc="Natural gas consumption per module of this type [MMSCF/d]") def unit_gas_consumption(m, mtype): - """_summary_ + """ + Calculates the natural gas consumption per module of a given type. Parameters ---------- - m : _type_ - _description_ - mtype : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + mtype : str + Index of the module type. Returns ------- - _type_ - _description_ + Pyomo.Parameter + A float representing the natural gas consumption per module of the given type. """ return float(module_sheet[mtype]['Nat Gas [MMSCF/d]']) @m.Param(m.module_types, doc="Gasoline production per module of this type [kBD]") def gasoline_production(m, mtype): - """_summary_ + """ + Calculates the gasoline production per module of a given type. Parameters ---------- - m : _type_ - _description_ - mtype : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + mtype : str + Index of the module type. Returns ------- - _type_ - _description_ + Pyomo.Parameter + A float representing the gasoline production per module of the given type. """ return float(module_sheet[mtype]['Gasoline [kBD]']) @m.Param(m.module_types, doc="Overall conversion of natural gas into gasoline per module of this type [kB/MMSCF]") def module_conversion(m, mtype): - """_summary_ + """ + Calculates the overall conversion of natural gas into gasoline per module of a given type. Parameters ---------- - m : _type_ - _description_ - mtype : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + mtype : str + Index of the module type. Returns ------- - _type_ - _description_ + Pyomo.Parameter + A float representing the overall conversion of natural gas into gasoline per module of the given type. """ return float(module_sheet[mtype]['Conversion [kB/MMSCF]']) site_sheet = xlsx_data['sites'].set_index('Potential site') - m.potential_sites = Set(initialize=site_sheet.index.tolist()) + m.potential_sites = Set(initialize=site_sheet.index.tolist(), doc="Potential sites") m.site_pairs = Set( doc="Pairs of potential sites", initialize=m.potential_sites * m.potential_sites, @@ -141,76 +147,80 @@ def module_conversion(m, mtype): @m.Param(m.potential_sites) def site_x(m, site): - """_summary_ + """ + Calculates the x-coordinate of a potential site. Parameters ---------- - m : _type_ - _description_ - site : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + site : str + The index for the potential site. Returns ------- - _type_ - _description_ + Pyomo.Parameter + An integer representing the x-coordinate of the potential site. """ return float(site_sheet['x'][site]) @m.Param(m.potential_sites) def site_y(m, site): - """_summary_ + """ + Calculates the y-coordinate of a potential site. Parameters ---------- - m : _type_ - _description_ - site : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + site : str + The index for the potential site. Returns ------- - _type_ - _description_ + Pyomo.Parameter + An integer representing the y-coordinate of the potential site. """ return float(site_sheet['y'][site]) well_sheet = xlsx_data['wells'].set_index('Well') - m.well_clusters = Set(initialize=well_sheet.index.tolist()) + m.well_clusters = Set(initialize=well_sheet.index.tolist(), doc="Well clusters") @m.Param(m.well_clusters) def well_x(m, well): - """_summary_ + """ + Calculates the x-coordinate of a well cluster. Parameters ---------- - m : _type_ - _description_ - well : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + well : str + The index for the well cluster. It starts from w1 and goes up to w12. Returns ------- - _type_ - _description_ + Pyomo.Parameter + An integer representing the x-coordinate of the well cluster. """ return float(well_sheet['x'][well]) @m.Param(m.well_clusters) def well_y(m, well): - """_summary_ + """ + Calculates the y-coordinate of a well cluster. Parameters ---------- - m : _type_ - _description_ - well : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + well : str + The index for the well cluster. Returns ------- - _type_ - _description_ + Pyomo.Parameter + An integer representing the y-coordinate of the well cluster. """ return float(well_sheet['y'][well]) @@ -226,87 +236,109 @@ def well_y(m, well): @m.Param(m.well_clusters, m.time, doc="Supply of gas from well cluster [MMSCF/day]") def gas_supply(m, well, t): - """_summary_ + """ + Calculates the supply of gas from a well cluster in a given time period. Parameters ---------- - m : _type_ - _description_ - well : _type_ - _description_ - t : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + well : str + The index for the well cluster. + t : int + Set of time periods quarterly period within the 15 year project life. Returns ------- - _type_ - _description_ + Pyomo.Parameter + A float representing the supply of gas from the well cluster in the given time period. """ return sum(well_profiles[well][t * 3:t * 3 + 2]) / 3 mkt_sheet = xlsx_data['markets'].set_index('Market') - m.markets = Set(initialize=mkt_sheet.index.tolist()) + m.markets = Set(initialize=mkt_sheet.index.tolist(), doc="Markets") @m.Param(m.markets) def mkt_x(m, mkt): - """_summary_ + """ + Calculates the x-coordinate of a market. Parameters ---------- - m : _type_ - _description_ - mkt : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + mkt : str + The index for the market. (m1, m2, m3) Returns ------- - _type_ - _description_ + Pyomo.Parameter + An integer representing the x-coordinate of the market. """ return float(mkt_sheet['x'][mkt]) @m.Param(m.markets) def mkt_y(m, mkt): - """_summary_ + """ + Calculates the y-coordinate of a market. Parameters ---------- - m : _type_ - _description_ - mkt : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + mkt : str + The index for the market. Returns ------- - _type_ - _description_ + Pyomo.Parameter + An integer representing the y-coordinate of the market. """ return float(mkt_sheet['y'][mkt]) @m.Param(m.markets, doc="Gasoline demand [kBD]") def mkt_demand(m, mkt): + """ + Calculates the demand for gasoline in a market in a given time period. + + Parameters + ---------- + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + mkt : str + The index for the market. + + Returns + ------- + Pyomo.Parameter + A float representing the demand for gasoline in the market in the given time period. + """ return float(mkt_sheet['demand [kBD]'][mkt]) - m.sources = Set(initialize=m.well_clusters | m.potential_sites) - m.destinations = Set(initialize=m.potential_sites | m.markets) + m.sources = Set(initialize=m.well_clusters | m.potential_sites, doc="Sources") + m.destinations = Set(initialize=m.potential_sites | m.markets, doc="Destinations") @m.Param(m.sources, m.destinations, doc="Distance [mi]") def distance(m, src, dest): - """_summary_ + """ + Calculates the Euclidean distance between a source and a destination within the gas processing network. + Assuming `src_x`, `src_y` for a source and `dest_x`, `dest_y` for a destination are defined within the model, the distance is calculated as follows: + + distance = sqrt((src_x - dest_x) ** 2 + (src_y - dest_y) ** 2) Parameters ---------- - m : _type_ - _description_ - src : _type_ - _description_ - dest : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + src : str + The identifier for the source, which could be a well cluster or a potential site. + dest : str + The identifier for the destination, which could be a potential site or a market. Returns ------- - _type_ - _description_ + Pyomo.Parameter + A parameter representing the Euclidean distance between the source and destination. """ if src in m.well_clusters: src_x = m.well_x[src] @@ -339,91 +371,96 @@ def distance(m, src, dest): @m.Param(m.time, doc="Module transport cost per mile [M$/100 miles]") def module_transport_distance_cost(m, t): - """_summary_ + """ + Calculates the module transport cost per mile in a given time period. Parameters ---------- - m : _type_ - _description_ - t : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + t : int + Set of time periods quarterly period within the 15 year project life. Returns ------- - _type_ - _description_ + Pyomo.Parameter + A float representing the module transport cost per mile in the given time period. """ return 50 * m.discount_factor[t] @m.Param(m.time, doc="Module transport cost per unit [MM$/module]") def module_transport_unit_cost(m, t): - """_summary_ + """ + Calculates the module transport cost per unit in a given time period. Parameters ---------- - m : _type_ - _description_ - t : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + t : int + Set of time periods quarterly period within the 15 year project life. Returns ------- - _type_ - _description_ + Pyomo.Parameter + A float representing the module transport cost per unit in the given time period. """ return 3 * m.discount_factor[t] @m.Param(m.time, doc="Stranded gas price [$/MSCF]") def nat_gas_price(m, t): - """_summary_ + """ + Calculates the price of stranded gas in a given time period. Parameters ---------- - m : _type_ - _description_ - t : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + t : int + Set of time periods quarterly period within the 15 year project life. Returns ------- - _type_ - _description_ + Pyomo.Parameter + A float representing the price of stranded gas in the given time period. """ return 5 * m.discount_factor[t] @m.Param(m.time, doc="Gasoline price [$/gal]") def gasoline_price(m, t): - """_summary_ + """ + Calculates the price of gasoline in a given time period. Parameters ---------- - m : _type_ - _description_ - t : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + t : int + Set of time periods quarterly period within the 15 year project life. Returns ------- - _type_ - _description_ + Pyomo.Parameter + A float representing the price of gasoline in the given time period. """ return 2.5 * m.discount_factor[t] @m.Param(m.time, doc="Gasoline transport cost [$/gal/100 miles]") def gasoline_tranport_cost(m, t): - """_summary_ + """ + Calculates the gasoline transport cost in a given time period. Parameters ---------- - m : _type_ - _description_ - t : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + t : int + Set of time periods quarterly period within the 15 year project life. Returns ------- - _type_ - _description_ + Pyomo.Parameter + A float representing the gasoline transport cost in the given time period. """ return 0.045 * m.discount_factor[t] @@ -441,10 +478,10 @@ def mtype_exists(disj, mtype): Parameters ---------- - disj : _type_ - _description_ - mtype : _type_ + disj : Pyomo.Disjunct _description_ + mtype : str + Index of the module type. """ disj.learning_factor_calc = Constraint( expr=m.learning_factor[mtype] == (1 - m.learning_rate) ** ( @@ -459,10 +496,10 @@ def mtype_absent(disj, mtype): Parameters ---------- - disj : _type_ - _description_ - mtype : _type_ + disj : Pyomo.Disjunct _description_ + mtype : str + Index of the module type. """ disj.constant_learning_factor = Constraint( expr=m.learning_factor[mtype] == 1) @@ -473,10 +510,10 @@ def mtype_existence(m, mtype): Parameters ---------- - m : _type_ - _description_ - mtype : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + mtype : str + Index of the module type. Returns ------- @@ -491,12 +528,12 @@ def module_unit_cost(m, mtype, t): Parameters ---------- - m : _type_ - _description_ - mtype : _type_ - _description_ - t : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + mtype : str + Index of the module type. + t : int + Set of time periods quarterly period within the 15 year project life. Returns ------- @@ -529,14 +566,14 @@ def consumption_capacity(m, site, mtype, t): Parameters ---------- - m : _type_ - _description_ - site : _type_ - _description_ - mtype : _type_ - _description_ - t : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + site : str + The index for the potential site. + mtype : str + Index of the module type. + t : int + Set of time periods quarterly period within the 15 year project life. Returns ------- @@ -552,12 +589,12 @@ def production_limit(m, site, t): Parameters ---------- - m : _type_ - _description_ - site : _type_ - _description_ - t : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + site : str + The index for the potential site. + t : int + Set of time periods quarterly period within the 15 year project life. Returns ------- @@ -574,12 +611,12 @@ def capacity(m, site, t): Parameters ---------- - m : _type_ - _description_ - site : _type_ - _description_ - t : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + site : str + The index for the potential site. + t : int + Set of time periods quarterly period within the 15 year project life. Returns ------- @@ -596,12 +633,12 @@ def gas_supply_meets_consumption(m, site, t): Parameters ---------- - m : _type_ - _description_ - site : _type_ - _description_ - t : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + site : str + The index for the potential site. + t : int + Set of time periods quarterly period within the 15 year project life. Returns ------- @@ -616,12 +653,12 @@ def gas_supply_limit(m, well, t): Parameters ---------- - m : _type_ - _description_ - well : _type_ - _description_ - t : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + well : str + The index for the well cluster. + t : int + Set of time periods quarterly period within the 15 year project life. Returns ------- @@ -637,12 +674,12 @@ def gasoline_production_requirement(m, site, t): Parameters ---------- - m : _type_ - _description_ - site : _type_ - _description_ - t : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + site : str + The index for the potential site. + t : int + Set of time periods quarterly period within the 15 year project life. Returns ------- @@ -658,14 +695,14 @@ def module_balance(m, site, mtype, t): Parameters ---------- - m : _type_ - _description_ - site : _type_ - _description_ - mtype : _type_ - _description_ - t : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + site : str + The index for the potential site. + mtype : str + Index of the module type. + t : int + Set of time periods quarterly period within the 15 year project life. Returns ------- @@ -700,10 +737,10 @@ def site_active(disj, site): Parameters ---------- - disj : _type_ - _description_ - site : _type_ + disj : Pyomo.Disjunct _description_ + site : str + The index for the potential site. """ pass @@ -713,10 +750,10 @@ def site_inactive(disj, site): Parameters ---------- - disj : _type_ - _description_ - site : _type_ + disj : Pyomo.Disjunct _description_ + site : str + The index for the potential site. """ disj.no_production = Constraint( expr=sum(m.production[site, :]) == 0) @@ -746,10 +783,10 @@ def site_active_or_not(m, site): Parameters ---------- - m : _type_ - _description_ - site : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + site : str + The index for the potential site. Returns ------- @@ -764,12 +801,12 @@ def pipeline_exists(disj, well, site): Parameters ---------- - disj : _type_ - _description_ - well : _type_ - _description_ - site : _type_ + disj : Pyomo.Disjunct _description_ + well : str + The index for the well cluster. + site : str + The index for the potential site. """ pass @@ -779,12 +816,12 @@ def pipeline_absent(disj, well, site): Parameters ---------- - disj : _type_ - _description_ - well : _type_ - _description_ - site : _type_ + disj : Pyomo.Disjunct _description_ + well : str + The index for the well cluster. + site : str + The index for the potential site. """ disj.no_natural_gas_flow = Constraint( expr=sum(m.gas_flows[well, site, t] for t in m.time) == 0) @@ -795,12 +832,12 @@ def pipeline_existence(m, well, site): Parameters ---------- - m : _type_ - _description_ - well : _type_ - _description_ - site : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + well : str + The index for the well cluster. + site : str + The index for the potential site. Returns ------- @@ -816,10 +853,10 @@ def product_revenue(m, site): Parameters ---------- - m : _type_ - _description_ - site : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + site : str + The index for the potential site. Returns ------- @@ -841,10 +878,10 @@ def raw_material_cost(m, site): Parameters ---------- - m : _type_ - _description_ - site : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + site : str + The index for the potential site. Returns ------- @@ -866,12 +903,12 @@ def product_transport_cost(m, site, mkt): Parameters ---------- - m : _type_ - _description_ - site : _type_ - _description_ - mkt : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + site : str + The index for the potential site. + mkt : str + The index for the market. Returns ------- @@ -891,12 +928,12 @@ def pipeline_construction_cost(m, well, site): Parameters ---------- - m : _type_ - _description_ - well : _type_ - _description_ - site : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + well : str + The index for the well cluster. + site : str + The index for the potential site. Returns ------- @@ -913,8 +950,8 @@ def module_relocation_cost(m, from_site, to_site): Parameters ---------- - m : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. from_site : _type_ _description_ to_site : _type_ @@ -941,10 +978,10 @@ def module_purchase_cost(m, site): Parameters ---------- - m : _type_ - _description_ - site : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. + site : str + The index for the potential site. Returns ------- @@ -962,8 +999,8 @@ def profit(m): Parameters ---------- - m : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. Returns ------- @@ -988,8 +1025,8 @@ def restrict_module_purchases(m): Parameters ---------- - m : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. Returns ------- @@ -1004,8 +1041,8 @@ def restrict_module_transfers(m, from_site, to_site): Parameters ---------- - m : _type_ - _description_ + m : Pyomo.ConcreteModel + A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. from_site : _type_ _description_ to_site : _type_ From 1240a91963bcf2ecbf68031501610c1483f16e91 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Sat, 27 Apr 2024 19:42:13 -0400 Subject: [PATCH 22/60] Documentation on the disjunctions for exist of module type. --- gdplib/stranded_gas/model.py | 43 ++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/gdplib/stranded_gas/model.py b/gdplib/stranded_gas/model.py index 6a47329..b9a7acc 100644 --- a/gdplib/stranded_gas/model.py +++ b/gdplib/stranded_gas/model.py @@ -474,39 +474,49 @@ def gasoline_tranport_cost(m, t): @m.Disjunct(m.module_types) def mtype_exists(disj, mtype): - """_summary_ + """ + Represents the scenario where a specific module type exists within the GTL network. Parameters ---------- disj : Pyomo.Disjunct - _description_ + A Pyomo Disjunct that represents the existence of a module type. mtype : str Index of the module type. + + Constraints + ------------ + learning_factor_calc : Pyomo Constraint + Captures the learning curve effect by adjusting the learning factor based on the total quantity of modules purchased. + require_module_purchases : Pyomo Constraint + Ensures that at least one module of this type is purchased, activating this disjunct. """ disj.learning_factor_calc = Constraint( expr=m.learning_factor[mtype] == (1 - m.learning_rate) ** ( - log(sum(m.modules_purchased[mtype, :, :])) / log(2))) + log(sum(m.modules_purchased[mtype, :, :])) / log(2)), doc="Learning factor calculation") m.BigM[disj.learning_factor_calc] = 1 disj.require_module_purchases = Constraint( - expr=sum(m.modules_purchased[mtype, :, :]) >= 1) + expr=sum(m.modules_purchased[mtype, :, :]) >= 1, doc="At least one module purchase") @m.Disjunct(m.module_types) def mtype_absent(disj, mtype): - """_summary_ + """ + Represents the scenario where a specific module type does not exist within the GTL network. Parameters ---------- disj : Pyomo.Disjunct - _description_ + A Pyomo Disjunct that represents the absence of a module type. mtype : str Index of the module type. """ disj.constant_learning_factor = Constraint( - expr=m.learning_factor[mtype] == 1) + expr=m.learning_factor[mtype] == 1, doc="Constant learning factor") @m.Disjunction(m.module_types) def mtype_existence(m, mtype): - """_summary_ + """ + A disjunction that determines whether a module type exists or is absent within the GTL network. Parameters ---------- @@ -517,14 +527,15 @@ def mtype_existence(m, mtype): Returns ------- - _type_ - _description_ + list of Pyomo.Disjunct + A list containing two disjuncts, one for the scenario where the module type exists and one for where it is absent. """ return [m.mtype_exists[mtype], m.mtype_absent[mtype]] @m.Expression(m.module_types, m.time, doc="Module unit cost [MM$/module]") def module_unit_cost(m, mtype, t): - """_summary_ + """ + Computes the unit cost of a module type at a specific time period, considering the base cost, the learning factor due to economies of numbers, and the time-based discount factor. Parameters ---------- @@ -537,8 +548,8 @@ def module_unit_cost(m, mtype, t): Returns ------- - _type_ - _description_ + Pyomo.Expression + A Pyomo Expression that calculates the total unit cost of a module for a given type and time period. """ return m.module_base_cost[mtype] * m.learning_factor[mtype] * m.discount_factor[t] @@ -577,7 +588,7 @@ def consumption_capacity(m, site, mtype, t): Returns ------- - _type_ + Pyomo.Constraint _description_ """ return m.gas_consumption[site, mtype, t] <= ( @@ -738,7 +749,7 @@ def site_active(disj, site): Parameters ---------- disj : Pyomo.Disjunct - _description_ + A Pyomo Disjunct that represents the active state of a potential site. site : str The index for the potential site. """ @@ -751,7 +762,7 @@ def site_inactive(disj, site): Parameters ---------- disj : Pyomo.Disjunct - _description_ + A Pyomo Disjunct that represents the inactive state of a potential site. site : str The index for the potential site. """ From 702ece7fa45c7456081fe5dc92663c0300df7faf Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Sat, 27 Apr 2024 22:11:49 -0400 Subject: [PATCH 23/60] Update documentation on disjunctions for exist of module type --- gdplib/stranded_gas/model.py | 160 ++++++++++++++++++++--------------- 1 file changed, 91 insertions(+), 69 deletions(-) diff --git a/gdplib/stranded_gas/model.py b/gdplib/stranded_gas/model.py index b9a7acc..b03a181 100644 --- a/gdplib/stranded_gas/model.py +++ b/gdplib/stranded_gas/model.py @@ -573,7 +573,8 @@ def module_unit_cost(m, mtype, t): @m.Constraint(m.potential_sites, m.module_types, m.time) def consumption_capacity(m, site, mtype, t): - """_summary_ + """ + Ensures that the natural gas consumption at any site for any module type does not exceed the production capacity of the modules present. Parameters ---------- @@ -589,14 +590,15 @@ def consumption_capacity(m, site, mtype, t): Returns ------- Pyomo.Constraint - _description_ + A constraint that limits the gas consumption per module type at each site, ensuring it does not exceed the capacity provided by the number of active modules of that type at the site during the time period. """ return m.gas_consumption[site, mtype, t] <= ( m.num_modules[mtype, site, t] * m.unit_gas_consumption[mtype]) @m.Constraint(m.potential_sites, m.time) def production_limit(m, site, t): - """_summary_ + """ + Limits the production of gasoline at each site to the maximum possible based on the gas consumed and the conversion efficiency of the modules. Parameters ---------- @@ -609,8 +611,8 @@ def production_limit(m, site, t): Returns ------- - _type_ - _description_ + Pyomo.Constraint + A constraint that ensures the production of gasoline at each site does not exceed the sum of the product of gas consumption and conversion rates for all module types at that site. """ return m.production[site, t] <= sum( m.gas_consumption[site, mtype, t] * m.module_conversion[mtype] @@ -618,7 +620,8 @@ def production_limit(m, site, t): @m.Expression(m.potential_sites, m.time) def capacity(m, site, t): - """_summary_ + """ + Calculates the total potential gasoline production capacity at each site for a given time period, based on the number of active modules, their gas consumption, and conversion efficiency. Parameters ---------- @@ -631,8 +634,8 @@ def capacity(m, site, t): Returns ------- - _type_ - _description_ + Pyomo.Expression + An expression that sums up the potential production capacity at a site, calculated as the product of the number of modules, their individual gas consumption rates, and their conversion efficiency. """ return sum( m.num_modules[mtype, site, t] * m.unit_gas_consumption[mtype] @@ -640,7 +643,8 @@ def capacity(m, site, t): @m.Constraint(m.potential_sites, m.time) def gas_supply_meets_consumption(m, site, t): - """_summary_ + """ + Ensures that the total gas consumed at a site is exactly matched by the gas supplied to it, reflecting a balance between supply and demand at any given time period. Parameters ---------- @@ -653,14 +657,15 @@ def gas_supply_meets_consumption(m, site, t): Returns ------- - _type_ - _description_ + Pyomo.Constraint + A constraint that balances the gas supply with the gas consumption at each site, ensuring that the total gas flow to the site equals the total consumption. """ return sum(m.gas_consumption[site, :, t]) == sum(m.gas_flows[:, site, t]) @m.Constraint(m.well_clusters, m.time) def gas_supply_limit(m, well, t): - """_summary_ + """ + Ensures that the total gas supplied from a well cluster does not exceed the available gas supply for that cluster during any given time period. Parameters ---------- @@ -673,15 +678,16 @@ def gas_supply_limit(m, well, t): Returns ------- - _type_ - _description_ + Pyomo.Constraint + A constraint that limits the total gas flow from a well cluster to various sites to not exceed the gas supply available at that well cluster for the given time period. """ return sum(m.gas_flows[well, site, t] for site in m.potential_sites) <= m.gas_supply[well, t] @m.Constraint(m.potential_sites, m.time) def gasoline_production_requirement(m, site, t): - """_summary_ + """ + Ensures that the total amount of gasoline shipped from a site matches the production at that site for each time period. Parameters ---------- @@ -694,15 +700,16 @@ def gasoline_production_requirement(m, site, t): Returns ------- - _type_ - _description_ + Pyomo.Constraint + A constraint that the sum of product flows (gasoline) from a site to various markets equals the total production at that site for the given period. """ return sum(m.product_flows[site, mkt, t] for mkt in m.markets) == m.production[site, t] @m.Constraint(m.potential_sites, m.module_types, m.time) def module_balance(m, site, mtype, t): - """_summary_ + """ + Balances the number of modules at a site across time periods by accounting for modules added, transferred, and previously existing. This ensures a consistent and accurate count of modules that reflects all transactions and changes over time. Parameters ---------- @@ -717,8 +724,8 @@ def module_balance(m, site, mtype, t): Returns ------- - _type_ - _description_ + Pyomo.Constraint + A constraint that maintains an accurate balance of module counts at each site, considering new purchases, transfers in, existing inventory, and transfers out. """ if t >= m.module_setup_time: modules_added = m.modules_purchased[ @@ -744,7 +751,8 @@ def module_balance(m, site, mtype, t): @m.Disjunct(m.potential_sites) def site_active(disj, site): - """_summary_ + """ + Represents the active state of a potential site within the GTL network. Parameters ---------- @@ -757,7 +765,8 @@ def site_active(disj, site): @m.Disjunct(m.potential_sites) def site_inactive(disj, site): - """_summary_ + """ + Represents the inactive state of a potential site within the GTL network. Parameters ---------- @@ -782,15 +791,16 @@ def site_inactive(disj, site): for mtypes in m.module_types for from_site, to_site in m.site_pairs for t in m.time - if from_site == site or to_site == site) == 0) + if from_site == site or to_site == site) == 0, doc="No modules transferred") disj.no_modules_purchased = Constraint( expr=sum( m.modules_purchased[mtype, site, t] - for mtype in m.module_types for t in m.time) == 0) + for mtype in m.module_types for t in m.time) == 0, doc="No modules purchased") @m.Disjunction(m.potential_sites) def site_active_or_not(m, site): - """_summary_ + """ + A disjunction that determines whether a potential site is active or inactive within the GTL network. Parameters ---------- @@ -801,14 +811,15 @@ def site_active_or_not(m, site): Returns ------- - _type_ - _description_ + list of Pyomo.Disjunct + A list containing two disjuncts, one for the scenario where the site is active and one for where it is inactive. """ return [m.site_active[site], m.site_inactive[site]] @m.Disjunct(m.well_clusters, m.potential_sites) def pipeline_exists(disj, well, site): - """_summary_ + """ + Represents the scenario where a pipeline exists between a well cluster and a potential site. Parameters ---------- @@ -823,7 +834,8 @@ def pipeline_exists(disj, well, site): @m.Disjunct(m.well_clusters, m.potential_sites) def pipeline_absent(disj, well, site): - """_summary_ + """ + Represents the scenario where a pipeline does not exist between a well cluster and a potential site. Parameters ---------- @@ -835,11 +847,12 @@ def pipeline_absent(disj, well, site): The index for the potential site. """ disj.no_natural_gas_flow = Constraint( - expr=sum(m.gas_flows[well, site, t] for t in m.time) == 0) + expr=sum(m.gas_flows[well, site, t] for t in m.time) == 0, doc="No natural gas flow") @m.Disjunction(m.well_clusters, m.potential_sites) def pipeline_existence(m, well, site): - """_summary_ + """ + A disjunction that determines whether a pipeline exists or is absent between a well cluster and a potential site. Parameters ---------- @@ -852,15 +865,16 @@ def pipeline_existence(m, well, site): Returns ------- - _type_ - _description_ + list of Pyomo.Disjunct + A list containing two disjuncts, one for the scenario where a pipeline exists and one for where it is absent. """ return [m.pipeline_exists[well, site], m.pipeline_absent[well, site]] # Objective Function Construnction @m.Expression(m.potential_sites, doc="MM$") def product_revenue(m, site): - """_summary_ + """ + Calculates the total revenue generated from the sale of gasoline produced at each site. This expression multiplies the volume of gasoline sold by the price per gallon, adjusted to millions of dollars for the entire production period. Parameters ---------- @@ -871,8 +885,8 @@ def product_revenue(m, site): Returns ------- - _type_ - _description_ + Pyomo.Expression + An expression representing the total revenue in million dollars from selling the gasoline produced at the site. """ return sum( m.product_flows[site, mkt, t] # kBD @@ -885,7 +899,8 @@ def product_revenue(m, site): @m.Expression(m.potential_sites, doc="MM$") def raw_material_cost(m, site): - """_summary_ + """ + Calculates the total cost of natural gas consumed as a raw material at each site, converted to millions of dollars. Parameters ---------- @@ -896,8 +911,8 @@ def raw_material_cost(m, site): Returns ------- - _type_ - _description_ + Pyomo.Expression + An expression calculating the total cost of natural gas used, taking into account the gas price and the conversion factor from MSCF to MMSCF. """ return sum( m.gas_consumption[site, mtype, t] * m.days_per_period @@ -910,7 +925,8 @@ def raw_material_cost(m, site): m.potential_sites, m.markets, doc="Aggregate cost to transport gasoline from a site to market [MM$]") def product_transport_cost(m, site, mkt): - """_summary_ + """ + Computes the cost of transporting gasoline from each production site to different markets, expressed in million dollars. Parameters ---------- @@ -923,8 +939,8 @@ def product_transport_cost(m, site, mkt): Returns ------- - _type_ - _description_ + Pyomo.Expression + The total transportation cost for shipping gasoline from a site to a market, adjusted for the distance and transportation rate. """ return sum( m.product_flows[site, mkt, t] * m.gal_per_bbl @@ -935,7 +951,8 @@ def product_transport_cost(m, site, mkt): @m.Expression(m.well_clusters, m.potential_sites, doc="MM$") def pipeline_construction_cost(m, well, site): - """_summary_ + """ + Calculates the cost of constructing pipelines from well clusters to potential sites, with costs dependent on the existence of a pipeline, distance, and unit cost per mile. Parameters ---------- @@ -948,8 +965,8 @@ def pipeline_construction_cost(m, well, site): Returns ------- - _type_ - _description_ + Pyomo.Expression + The cost of pipeline construction, in million dollars, if a pipeline is established between the well cluster and the site. """ return (m.pipeline_unit_cost * m.distance[well, site] * m.pipeline_exists[well, site].binary_indicator_var) @@ -957,21 +974,22 @@ def pipeline_construction_cost(m, well, site): # Module transport cost @m.Expression(m.site_pairs, doc="MM$") def module_relocation_cost(m, from_site, to_site): - """_summary_ + """ + Calculates the cost of relocating modules from one site to another, considering the distance, transport cost per mile, and unit cost per module. This cost includes the transportation costs based on distance and per-unit transport costs, accounting for all modules transferred between specified sites over the entire project duration. Parameters ---------- m : Pyomo.ConcreteModel A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. - from_site : _type_ - _description_ - to_site : _type_ - _description_ + from_site : str + Index for the originating site of the module transfer. + to_site : str + Index for the destination site of the module transfer. Returns ------- - _type_ - _description_ + Pyomo.Expression + An expression calculating the total relocation cost for modules moved between the two sites, factoring in the distance and both per-mile and per-unit costs, scaled to million dollars. """ return sum( m.modules_transferred[mtype, from_site, to_site, t] @@ -985,7 +1003,8 @@ def module_relocation_cost(m, from_site, to_site): @m.Expression(m.potential_sites, doc="MM$") def module_purchase_cost(m, site): - """_summary_ + """ + Computes the total cost of purchasing new modules for a specific site, considering the unit costs of modules, which may vary over time due to discounts and other factors. This expression aggregates the costs for all modules purchased across the project's timeframe. Parameters ---------- @@ -996,8 +1015,8 @@ def module_purchase_cost(m, site): Returns ------- - _type_ - _description_ + Pyomo.Expression + An expression representing the total cost of module purchases at the specified site, converted to million dollars. """ return sum( m.module_unit_cost[mtype, t] * m.modules_purchased[mtype, site, t] @@ -1006,7 +1025,8 @@ def module_purchase_cost(m, site): @m.Expression(doc="MM$") def profit(m): - """_summary_ + """ + Calculates the overall profit for the GTL network by subtracting all relevant costs from the total revenue. This is used as the objective function to be maximized (or minimize the negative profit). Parameters ---------- @@ -1015,8 +1035,8 @@ def profit(m): Returns ------- - _type_ - _description_ + Pyomo.Expression + The net profit expression, computed as the difference between total revenue and all accumulated costs across the network. """ return ( summation(m.product_revenue) @@ -1027,12 +1047,13 @@ def profit(m): - summation(m.module_purchase_cost) ) - m.neg_profit = Objective(expr=-m.profit) + m.neg_profit = Objective(expr=-m.profit, doc="Objective Function: Minimize Negative Profit") # Tightening constraints @m.Constraint(doc="Limit total module purchases over project span.") def restrict_module_purchases(m): - """_summary_ + """ + Enforces a limit on the total number of module purchases across all sites and module types throughout the entire project span. This constraint is crucial for controlling capital expenditure and ensuring that the module acquisition does not exceed a specified threshold, helping to maintain budget constraints. Parameters ---------- @@ -1041,28 +1062,29 @@ def restrict_module_purchases(m): Returns ------- - _type_ - _description_ + Pyomo.Constraint + A global constraint that limits the aggregate number of modules purchased across all sites to 5, ensuring that the total investment in module purchases remains within predefined limits. """ return sum(m.modules_purchased[...]) <= 5 @m.Constraint(m.site_pairs, doc="Limit transfers between any two sites") def restrict_module_transfers(m, from_site, to_site): - """_summary_ + """ + Imposes a limit on the number of modules that can be transferred between any two sites during the entire project timeline. This constraint helps manage logistics and ensures that module reallocation does not become overly frequent or excessive, which could lead to operational inefficiencies and increased costs. Parameters ---------- m : Pyomo.ConcreteModel A Pyomo ConcreteModel that represents the multi-period optimization problem of a GTL network. - from_site : _type_ - _description_ - to_site : _type_ - _description_ + from_site : str + Index for the origin site from which modules are being transferred. + to_site : str + Index for the destination site to which modules are being transferred. Returns ------- - _type_ - _description_ + Pyomo.Constraint + A constraint limiting the total number of modules transferred from one site to another to 5, providing a control mechanism on the frequency and volume of inter-site module movements. """ return sum(m.modules_transferred[:, from_site, to_site, :]) <= 5 From bffffdbd57ae11394b0a8f689ad8f24ec1e5cb5a Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Mon, 6 May 2024 17:01:01 -0400 Subject: [PATCH 24/60] split models in pyomo_examples --- gdplib/batch_processing/__init__.py | 3 +++ .../batch_processing.dat | 0 .../batch_processing.py | 0 gdplib/disease_model/__init__.py | 3 +++ .../disease_model.py | 0 gdplib/jobshop/__init__.py | 3 +++ gdplib/{pyomo_examples => jobshop}/jobshop-small.dat | 0 gdplib/{pyomo_examples => jobshop}/jobshop.py | 0 gdplib/med_term_purchasing/__init__.py | 3 +++ .../med_term_purchasing.dat | 0 .../med_term_purchasing.py | 0 gdplib/pyomo_examples/README.md | 4 ---- gdplib/pyomo_examples/__init__.py | 11 ----------- 13 files changed, 12 insertions(+), 15 deletions(-) create mode 100644 gdplib/batch_processing/__init__.py rename gdplib/{pyomo_examples => batch_processing}/batch_processing.dat (100%) rename gdplib/{pyomo_examples => batch_processing}/batch_processing.py (100%) create mode 100644 gdplib/disease_model/__init__.py rename gdplib/{pyomo_examples => disease_model}/disease_model.py (100%) create mode 100644 gdplib/jobshop/__init__.py rename gdplib/{pyomo_examples => jobshop}/jobshop-small.dat (100%) rename gdplib/{pyomo_examples => jobshop}/jobshop.py (100%) create mode 100644 gdplib/med_term_purchasing/__init__.py rename gdplib/{pyomo_examples => med_term_purchasing}/med_term_purchasing.dat (100%) rename gdplib/{pyomo_examples => med_term_purchasing}/med_term_purchasing.py (100%) delete mode 100644 gdplib/pyomo_examples/README.md delete mode 100644 gdplib/pyomo_examples/__init__.py diff --git a/gdplib/batch_processing/__init__.py b/gdplib/batch_processing/__init__.py new file mode 100644 index 0000000..58f319a --- /dev/null +++ b/gdplib/batch_processing/__init__.py @@ -0,0 +1,3 @@ +from .batch_processing import build_model + +__all__ = ['build_model'] diff --git a/gdplib/pyomo_examples/batch_processing.dat b/gdplib/batch_processing/batch_processing.dat similarity index 100% rename from gdplib/pyomo_examples/batch_processing.dat rename to gdplib/batch_processing/batch_processing.dat diff --git a/gdplib/pyomo_examples/batch_processing.py b/gdplib/batch_processing/batch_processing.py similarity index 100% rename from gdplib/pyomo_examples/batch_processing.py rename to gdplib/batch_processing/batch_processing.py diff --git a/gdplib/disease_model/__init__.py b/gdplib/disease_model/__init__.py new file mode 100644 index 0000000..7058d12 --- /dev/null +++ b/gdplib/disease_model/__init__.py @@ -0,0 +1,3 @@ +from .disease_model import build_model + +__all__ = ['build_model'] diff --git a/gdplib/pyomo_examples/disease_model.py b/gdplib/disease_model/disease_model.py similarity index 100% rename from gdplib/pyomo_examples/disease_model.py rename to gdplib/disease_model/disease_model.py diff --git a/gdplib/jobshop/__init__.py b/gdplib/jobshop/__init__.py new file mode 100644 index 0000000..6361ccd --- /dev/null +++ b/gdplib/jobshop/__init__.py @@ -0,0 +1,3 @@ +from .jobshop import build_model + +__all__ = ['build_model'] diff --git a/gdplib/pyomo_examples/jobshop-small.dat b/gdplib/jobshop/jobshop-small.dat similarity index 100% rename from gdplib/pyomo_examples/jobshop-small.dat rename to gdplib/jobshop/jobshop-small.dat diff --git a/gdplib/pyomo_examples/jobshop.py b/gdplib/jobshop/jobshop.py similarity index 100% rename from gdplib/pyomo_examples/jobshop.py rename to gdplib/jobshop/jobshop.py diff --git a/gdplib/med_term_purchasing/__init__.py b/gdplib/med_term_purchasing/__init__.py new file mode 100644 index 0000000..fb2e2da --- /dev/null +++ b/gdplib/med_term_purchasing/__init__.py @@ -0,0 +1,3 @@ +from .med_term_purchasing import build_model + +__all__ = ['build_model'] diff --git a/gdplib/pyomo_examples/med_term_purchasing.dat b/gdplib/med_term_purchasing/med_term_purchasing.dat similarity index 100% rename from gdplib/pyomo_examples/med_term_purchasing.dat rename to gdplib/med_term_purchasing/med_term_purchasing.dat diff --git a/gdplib/pyomo_examples/med_term_purchasing.py b/gdplib/med_term_purchasing/med_term_purchasing.py similarity index 100% rename from gdplib/pyomo_examples/med_term_purchasing.py rename to gdplib/med_term_purchasing/med_term_purchasing.py diff --git a/gdplib/pyomo_examples/README.md b/gdplib/pyomo_examples/README.md deleted file mode 100644 index a00148f..0000000 --- a/gdplib/pyomo_examples/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Models taken from Pyomo.GDP examples directory - -These models are among the oldest in the library. -They were primarily developed by Emma Savannah Johnson (esjohn@sandia.gov), and are simply reproduced here in importable form. diff --git a/gdplib/pyomo_examples/__init__.py b/gdplib/pyomo_examples/__init__.py deleted file mode 100644 index be668e9..0000000 --- a/gdplib/pyomo_examples/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -from .batch_processing import build_model as build_batch_processing_model -from .disease_model import build_model as build_disease_model -from .jobshop import build_small_concrete as build_jobshop_model -from .med_term_purchasing import build_concrete as build_med_term_purchasing_model - -__all__ = [ - 'build_batch_processing_model', - 'build_disease_model', - 'build_jobshop_model', - 'build_med_term_purchasing_model' -] From 353d69c7e9fb16898b134bbb549d67ca70a81ebb Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Mon, 6 May 2024 17:22:42 -0400 Subject: [PATCH 25/60] unify the function to build_model --- gdplib/jobshop/jobshop.py | 21 ++----------------- .../med_term_purchasing.py | 18 ++-------------- 2 files changed, 4 insertions(+), 35 deletions(-) diff --git a/gdplib/jobshop/jobshop.py b/gdplib/jobshop/jobshop.py index 25ef303..790488d 100644 --- a/gdplib/jobshop/jobshop.py +++ b/gdplib/jobshop/jobshop.py @@ -228,29 +228,12 @@ def _disj(model, I, K, J): model.makespan = Objective( expr=model.ms, doc='Objective Function: Minimize the makespan' ) + model.create_instance(join(this_file_dir(), 'jobshop-small.dat')) return model -def build_small_concrete(): - """ - Build a small jobshop scheduling model for testing purposes. - The AbstractModel is instantiated with the data from 'jobshop-small.dat' turning it into a ConcreteModel. - - Parameters - ---------- - None - However the data file jobshop-small.dat must be in the same directory as this file. - - Returns - ------- - ConcreteModel : Pyomo.ConcreteModel - A concrete instance of the jobshop scheduling model populated with data from 'jobshop-small.dat', ready for optimization. - """ - return build_model().create_instance(join(this_file_dir(), 'jobshop-small.dat')) - - if __name__ == "__main__": - m = build_small_concrete() + m = build_model() TransformationFactory('gdp.bigm').apply_to(m) SolverFactory('gams').solve( m, solver='baron', tee=True, add_options=['option optcr=1e-6;'] diff --git a/gdplib/med_term_purchasing/med_term_purchasing.py b/gdplib/med_term_purchasing/med_term_purchasing.py index 9377735..02a0961 100644 --- a/gdplib/med_term_purchasing/med_term_purchasing.py +++ b/gdplib/med_term_purchasing/med_term_purchasing.py @@ -1960,26 +1960,12 @@ def FD_contract(model, j, t): rule=FD_contract, doc='Fixed duration contract scenarios', ) - + model.create_instance(join(this_file_dir(), 'med_term_purchasing.dat')) return model -def build_concrete(): - """ - Build a concrete model applying the data of the medium-term purchasing contracts problem on build_model(). - - Returns - ------- - Pyomo.ConcreteModel - A concrete model for the medium-term purchasing contracts problem. - """ - return build_model().create_instance( - join(this_file_dir(), 'med_term_purchasing.dat') - ) - - if __name__ == "__main__": - m = build_concrete() + m = build_model() TransformationFactory('gdp.bigm').apply_to(m) SolverFactory('gams').solve( m, solver='baron', tee=True, add_options=['option optcr=1e-6;'] From e715e1e270bf07513c594b38fdce3b581b4539cd Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Mon, 6 May 2024 17:25:24 -0400 Subject: [PATCH 26/60] update __init__.py --- gdplib/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gdplib/__init__.py b/gdplib/__init__.py index 1812f52..a3cb501 100644 --- a/gdplib/__init__.py +++ b/gdplib/__init__.py @@ -2,9 +2,13 @@ import gdplib.modprodnet import gdplib.biofuel import gdplib.logical # Requires logical expression system -import gdplib.pyomo_examples import gdplib.stranded_gas # Requires logical expression system import gdplib.gdp_col import gdplib.hda import gdplib.kaibel import gdplib.methanol +import gdplib.batch_processing +import gdplib.jobshop +import gdplib.disease_model +import gdplib.med_term_purchasing +import gdplib.syngas From d3fcdf7d0da77ca9e94b1d5456cc368a23f369de Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Mon, 6 May 2024 20:03:49 -0400 Subject: [PATCH 27/60] fix abstract model generation bug --- gdplib/jobshop/jobshop.py | 2 +- gdplib/med_term_purchasing/med_term_purchasing.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gdplib/jobshop/jobshop.py b/gdplib/jobshop/jobshop.py index 790488d..1e58752 100644 --- a/gdplib/jobshop/jobshop.py +++ b/gdplib/jobshop/jobshop.py @@ -228,7 +228,7 @@ def _disj(model, I, K, J): model.makespan = Objective( expr=model.ms, doc='Objective Function: Minimize the makespan' ) - model.create_instance(join(this_file_dir(), 'jobshop-small.dat')) + model = model.create_instance(join(this_file_dir(), 'jobshop-small.dat')) return model diff --git a/gdplib/med_term_purchasing/med_term_purchasing.py b/gdplib/med_term_purchasing/med_term_purchasing.py index 02a0961..6d12e2d 100644 --- a/gdplib/med_term_purchasing/med_term_purchasing.py +++ b/gdplib/med_term_purchasing/med_term_purchasing.py @@ -1960,7 +1960,7 @@ def FD_contract(model, j, t): rule=FD_contract, doc='Fixed duration contract scenarios', ) - model.create_instance(join(this_file_dir(), 'med_term_purchasing.dat')) + model = model.create_instance(join(this_file_dir(), 'med_term_purchasing.dat')) return model From 1c2c28b5a747f3dfaef99c83e623d92eb3745cd8 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Mon, 6 May 2024 21:09:40 -0400 Subject: [PATCH 28/60] first working version of benchmark.py --- benchmark.py | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 benchmark.py diff --git a/benchmark.py b/benchmark.py new file mode 100644 index 0000000..5820997 --- /dev/null +++ b/benchmark.py @@ -0,0 +1,116 @@ +from pyomo.environ import * +import os +import json +import time +import sys +from datetime import datetime +from gdplib.jobshop.jobshop import build_model +from importlib import import_module +from pyomo.util.model_size import build_model_size_report +import pandas as pd + +subsolver = "scip" + + +def benchmark(model, strategy, timelimit, result_dir): + """Benchmark the model using the given strategy and subsolver. + + Parameters + ---------- + model : PyomoModel + the model to be solved + strategy : string + the strategy used to solve the model + timelimit : int + the time limit for the solver + result_dir : string + the directory to store the benchmark results + """ + model = model.clone() + stdout = sys.stdout + if strategy in ["gdp.bigm", "gdp.hull"]: + transformation_start_time = time.time() + TransformationFactory(strategy).apply_to(model) + transformation_end_time = time.time() + with open( + result_dir + "/" + strategy + "_" + subsolver + ".log", "w" + ) as sys.stdout: + results = SolverFactory("scip").solve(model, tee=True, timelimit=timelimit) + results.solver.transformation_time = ( + transformation_end_time - transformation_start_time + ) + print(results) + elif strategy in [ + "gdpopt.enumerate", + "gdpopt.loa", + "gdpopt.gloa", + "gdpopt.lbb", + "gdpopt.ric", + ]: + with open( + result_dir + "/" + strategy + "_" + subsolver + ".log", "w" + ) as sys.stdout: + results = SolverFactory(strategy).solve( + model, + tee=True, + nlp_solver="scip", + mip_solver="scip", + minlp_solver="scip", + local_minlp_solver="scip", + time_limit=timelimit, + ) + print(results) + + sys.stdout = stdout + with open(result_dir + "/" + strategy + "_" + subsolver + ".json", "w") as f: + json.dump(results.json_repn(), f) + + +if __name__ == "__main__": + instance_list = [ + # "batch_processing", + # "biofuel", + # "disease_model", + # "gdp_col", + # "hda", + "jobshop", + # "kaibel", + # "logical", + # "med_term_purchasing", + # "methanol", + # "mod_hens", + # "modprodnet", + # "stranded_gas", + # "syngas", + ] + strategy_list = [ + "gdp.bigm", + "gdp.hull", + "gdpopt.enumerate", + "gdpopt.loa", + "gdpopt.gloa", + "gdpopt.ric", + ] + current_time = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + timelimit = 600 + + for instance in instance_list: + print("Benchmarking instance: " + instance) + result_dir = "gdplib/" + instance + "/benchmark_result/" + current_time + os.makedirs(result_dir, exist_ok=True) + + model = import_module("gdplib." + instance).build_model() + report = build_model_size_report(model) + report_df = pd.DataFrame(report.overall, index=[0]).T + report_df.index.name = "Component" + report_df.columns = ["Number"] + # Generate the model size report (Markdown) + report_df.to_markdown("gdplib/" + instance + "/" + "model_size_report.md") + + # Generate the model size report (JSON) + # TODO: check if we need the json file. + with open("gdplib/" + instance + "/" + "model_size_report.json", "w") as f: + json.dump(report, f) + + for strategy in strategy_list: + benchmark(model, strategy, timelimit, result_dir) From 2e1e596ea1290c9024f38f98314dd4e13d35047f Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Mon, 6 May 2024 21:10:08 -0400 Subject: [PATCH 29/60] result example of jobshop --- .../2024-05-06_21-01-21/gdp.bigm_scip.json | 1 + .../2024-05-06_21-01-21/gdp.hull_scip.json | 1 + .../2024-05-06_21-01-21/gdpopt.enumerate_scip.json | 1 + .../2024-05-06_21-01-21/gdpopt.gloa_scip.json | 1 + .../2024-05-06_21-01-21/gdpopt.loa_scip.json | 1 + .../2024-05-06_21-01-21/gdpopt.ric_scip.json | 1 + gdplib/jobshop/model_size_report.json | 1 + gdplib/jobshop/model_size_report.md | 10 ++++++++++ 8 files changed, 17 insertions(+) create mode 100644 gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdp.bigm_scip.json create mode 100644 gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdp.hull_scip.json create mode 100644 gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.enumerate_scip.json create mode 100644 gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.gloa_scip.json create mode 100644 gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.loa_scip.json create mode 100644 gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.ric_scip.json create mode 100644 gdplib/jobshop/model_size_report.json create mode 100644 gdplib/jobshop/model_size_report.md diff --git a/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdp.bigm_scip.json b/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdp.bigm_scip.json new file mode 100644 index 0000000..04f3905 --- /dev/null +++ b/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdp.bigm_scip.json @@ -0,0 +1 @@ +{"Problem": [{"Lower bound": -Infinity, "Upper bound": 11.0, "Number of objectives": 1, "Number of constraints": 0, "Number of variables": 10, "Sense": "unknown"}], "Solver": [{"Status": "ok", "Message": "optimal solution found", "Termination condition": "optimal", "Id": 0, "Error rc": 0, "Time": 0.04, "Gap": 0.0, "Primal bound": 11.0, "Dual bound": 11.0, "Transformation time": 0.003754854202270508}], "Solution": [{"number of solutions": 0, "number of solutions displayed": 0}]} \ No newline at end of file diff --git a/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdp.hull_scip.json b/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdp.hull_scip.json new file mode 100644 index 0000000..c57115b --- /dev/null +++ b/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdp.hull_scip.json @@ -0,0 +1 @@ +{"Problem": [{"Lower bound": -Infinity, "Upper bound": 11.0, "Number of objectives": 1, "Number of constraints": 0, "Number of variables": 22, "Sense": "unknown"}], "Solver": [{"Status": "ok", "Message": "optimal solution found", "Termination condition": "optimal", "Id": 0, "Error rc": 0, "Time": 0.01, "Gap": 0.0, "Primal bound": 11.0, "Dual bound": 11.0, "Transformation time": 0.008002042770385742}], "Solution": [{"number of solutions": 0, "number of solutions displayed": 0}]} \ No newline at end of file diff --git a/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.enumerate_scip.json b/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.enumerate_scip.json new file mode 100644 index 0000000..b09a6f2 --- /dev/null +++ b/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.enumerate_scip.json @@ -0,0 +1 @@ +{"Problem": [{"Name": "unknown", "Lower bound": 11.0, "Upper bound": 11.0, "Number of objectives": 1, "Number of constraints": 9, "Number of variables": 10, "Number of binary variables": 6, "Number of integer variables": 0, "Number of continuous variables": 4, "Number of nonzeros": null, "Sense": 1, "Number of disjunctions": 3}], "Solver": [{"Name": "GDPopt (22, 5, 13) - enumerate", "Status": "ok", "User time": 0.3502802930015605, "Wallclock time": 0.3502802930015605, "Termination condition": "optimal", "Iterations": 8, "Timing": {"main_timer_start_time": 123407.445576859, "nlp": 0.34058500100218225, "total": 0.3502802930015605}}]} \ No newline at end of file diff --git a/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.gloa_scip.json b/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.gloa_scip.json new file mode 100644 index 0000000..7464bb2 --- /dev/null +++ b/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.gloa_scip.json @@ -0,0 +1 @@ +{"Problem": [{"Name": "unknown", "Lower bound": 11.0, "Upper bound": 11.0, "Number of objectives": 1, "Number of constraints": 9, "Number of variables": 10, "Number of binary variables": 6, "Number of integer variables": 0, "Number of continuous variables": 4, "Number of nonzeros": null, "Sense": 1, "Number of disjunctions": 3}], "Solver": [{"Name": "GDPopt (22, 5, 13) - GLOA", "Status": "ok", "User time": 0.1213786309963325, "Wallclock time": 0.1213786309963325, "Termination condition": "optimal", "Iterations": 1, "Timing": {"main_timer_start_time": 123407.944400965, "mip": 0.05432544200448319, "nlp": 0.05131585600611288, "integer cut generation": 0.000397921001422219, "total": 0.1213786309963325}}]} \ No newline at end of file diff --git a/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.loa_scip.json b/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.loa_scip.json new file mode 100644 index 0000000..06a1841 --- /dev/null +++ b/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.loa_scip.json @@ -0,0 +1 @@ +{"Problem": [{"Name": "unknown", "Lower bound": 11.0, "Upper bound": 11.0, "Number of objectives": 1, "Number of constraints": 9, "Number of variables": 10, "Number of binary variables": 6, "Number of integer variables": 0, "Number of continuous variables": 4, "Number of nonzeros": null, "Sense": 1, "Number of disjunctions": 3}], "Solver": [{"Name": "GDPopt (22, 5, 13) - LOA", "Status": "ok", "User time": 0.1308980710018659, "Wallclock time": 0.1308980710018659, "Termination condition": "optimal", "Iterations": 1, "Timing": {"main_timer_start_time": 123407.805129371, "mip": 0.06084998599544633, "nlp": 0.054532527006813325, "integer cut generation": 0.0005358900089049712, "total": 0.1308980710018659}}]} \ No newline at end of file diff --git a/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.ric_scip.json b/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.ric_scip.json new file mode 100644 index 0000000..fa7bad2 --- /dev/null +++ b/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.ric_scip.json @@ -0,0 +1 @@ +{"Problem": [{"Name": "unknown", "Lower bound": 11.0, "Upper bound": 11.0, "Number of objectives": 1, "Number of constraints": 9, "Number of variables": 10, "Number of binary variables": 6, "Number of integer variables": 0, "Number of continuous variables": 4, "Number of nonzeros": null, "Sense": 1, "Number of disjunctions": 3}], "Solver": [{"Name": "GDPopt (22, 5, 13) - RIC", "Status": "ok", "User time": 0.13380873500136659, "Wallclock time": 0.13380873500136659, "Termination condition": "optimal", "Iterations": 1, "Timing": {"main_timer_start_time": 123408.074596677, "mip": 0.05659815100079868, "nlp": 0.06265952499234118, "integer cut generation": 0.0004167870065430179, "total": 0.13380873500136659}}]} \ No newline at end of file diff --git a/gdplib/jobshop/model_size_report.json b/gdplib/jobshop/model_size_report.json new file mode 100644 index 0000000..138d2a1 --- /dev/null +++ b/gdplib/jobshop/model_size_report.json @@ -0,0 +1 @@ +{"activated": {"variables": 10, "binary_variables": 6, "integer_variables": 0, "continuous_variables": 4, "disjunctions": 3, "disjuncts": 6, "constraints": 9, "nonlinear_constraints": 0}, "overall": {"variables": 10, "binary_variables": 6, "integer_variables": 0, "continuous_variables": 4, "disjunctions": 3, "disjuncts": 6, "constraints": 9, "nonlinear_constraints": 0}, "warning": {"unassociated_disjuncts": 0}} \ No newline at end of file diff --git a/gdplib/jobshop/model_size_report.md b/gdplib/jobshop/model_size_report.md new file mode 100644 index 0000000..3d4d37e --- /dev/null +++ b/gdplib/jobshop/model_size_report.md @@ -0,0 +1,10 @@ +| Component | Number | +|:----------------------|---------:| +| variables | 10 | +| binary_variables | 6 | +| integer_variables | 0 | +| continuous_variables | 4 | +| disjunctions | 3 | +| disjuncts | 6 | +| constraints | 9 | +| nonlinear_constraints | 0 | \ No newline at end of file From 79ac9cd7d333e03c332792b30c2a0136dcd0c96f Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 7 May 2024 11:45:52 -0400 Subject: [PATCH 30/60] Documentation on the build_model from column.py --- gdplib/gdp_col/column.py | 928 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 917 insertions(+), 11 deletions(-) diff --git a/gdplib/gdp_col/column.py b/gdplib/gdp_col/column.py index 8b1bc4a..123851c 100644 --- a/gdplib/gdp_col/column.py +++ b/gdplib/gdp_col/column.py @@ -8,10 +8,28 @@ def build_column(min_trays, max_trays, xD, xB): - """Builds the column model.""" + """ + Build a Pyomo model of a distillation column for separation of benzene and toluene. + + Parameters + ---------- + min_trays : int + Minimum number of trays in the column + max_trays : int + Maximum number of trays in the column + xD : float + Distillate(benzene) purity + xB : float + Bottoms(toluene) purity + + Returns + ------- + Pyomo.ConcreteModel + A Pyomo model of the distillation column. for separation of benzene and toluene + """ m = ConcreteModel('benzene-toluene column') - m.comps = Set(initialize=['benzene', 'toluene']) - min_T, max_T = 300, 400 + m.comps = Set(initialize=['benzene', 'toluene'], doc='Set of components') + min_T, max_T = 300, 400 # Define temperature bounds [K] max_flow = 500 m.T_feed = Var( doc='Feed temperature [K]', domain=NonNegativeReals, @@ -37,11 +55,25 @@ def build_column(min_trays, max_trays, xD, xB): @m.Disjunction(m.conditional_trays, doc='Tray exists or does not') def tray_no_tray(b, t): + """_summary_ + + Parameters + ---------- + b : _type_ + _description_ + t : int + Index of tray in the distillation column model. Tray numbering ascends from the reboiler at the bottom (tray 1) to the condenser at the top (tray max_trays) + + Returns + ------- + List of Disjuncts + _description_ + """ return [b.tray[t], b.no_tray[t]] m.minimum_num_trays = Constraint( expr=sum(m.tray[t].binary_indicator_var for t in m.conditional_trays) + 1 # for feed tray - >= min_trays) + >= min_trays, doc='Minimum number of trays') m.x = Var(m.comps, m.trays, doc='Liquid mole fraction', bounds=(0, 1), domain=NonNegativeReals, initialize=0.5) @@ -93,29 +125,100 @@ def tray_no_tray(b, t): @m.Constraint(m.comps, doc="Bottoms flow is equal to liquid leaving reboiler.") def bottoms_mass_balance(m, c): + """ + Constraint that the bottoms flow is equal to the liquid leaving the reboiler. + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo model of the distillation column. + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + Pyomo.Constraint + Constraint that the bottoms flow is equal to the liquid leaving the reboiler. + """ return m.B[c] == m.L[c, m.reboil_tray] @m.Constraint() def boilup_frac_defn(m): + """ + Boilup fraction is the ratio between the bottoms flow and the liquid leaving the reboiler. + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo model of the distillation column. + + Returns + ------- + Pyomo.Constraint + Constraint that the boilup fraction is the ratio between the bottoms flow and the liquid leaving the reboiler. + """ return m.bot == (1 - m.boilup_frac) * m.liq[m.reboil_tray + 1] @m.Constraint() def reflux_frac_defn(m): + """ + Reflux fraction is the ratio between the distillate flow and the difference in vapor flow in the condenser tray. + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo model of the distillation column. + + Returns + ------- + Pyomo.Constraint + Constraint that the reflux fraction is the ratio between the distillate flow and the difference in vapor flow in the condenser tray. + """ return m.dis == (1 - m.reflux_frac) * ( m.vap[m.condens_tray - 1] - m.vap[m.condens_tray]) @m.Constraint(m.trays) def liquid_sum(m, t): + """ + Total liquid flow on each tray is the sum of all component liquid flows on the tray. + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo model of the distillation column. + t : int + Index of tray in the distillation column model. + + Returns + ------- + Pyomo.Constraint + Constraint that the total liquid flow on each tray is the sum of all component liquid flows on the tray. + """ return sum(m.L[c, t] for c in m.comps) == m.liq[t] @m.Constraint(m.trays) def vapor_sum(m, t): + """ + Total vapor flow on each tray is the sum of all component vapor flows on the tray. + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo model of the distillation column. + t : int + Index of tray in the distillation column model. + + Returns + ------- + Pyomo.Constraint + Constraint that the total vapor flow on each tray is the sum of all component vapor flows on the tray. + """ return sum(m.V[c, t] for c in m.comps) == m.vap[t] m.bottoms_sum = Constraint( - expr=sum(m.B[c] for c in m.comps) == m.bot) + expr=sum(m.B[c] for c in m.comps) == m.bot, doc="Total bottoms flow is the sum of all component flows at the bottom.") m.distil_sum = Constraint( - expr=sum(m.D[c] for c in m.comps) == m.dis) + expr=sum(m.D[c] for c in m.comps) == m.dis, doc="Total distillate flow is the sum of all component flows at the top.") """Phase Equilibrium relations""" m.Kc = Var( @@ -127,6 +230,21 @@ def vapor_sum(m, t): @m.Constraint(m.trays) def monotonoic_temperature(_, t): + """ + Temperature of tray t is greater than or equal to temperature of tray t+1. The temperature decreases as the trays ascend. + + Parameters + ---------- + _ : Pyomo.core.base.constraint.IndexedConstraint + An unused placeholder parameter required by Pyomo's constraint interface, representing each potential tray in the distillation column where the temperature constraint is applied. + t : int + Index of tray in the distillation column model. + + Returns + ------- + Pyomo.Constraint + Constraint that the temperature of tray t is greater than or equal to temperature of tray t+1. If t is the condenser tray, the constraint is skipped. + """ return m.T[t] >= m.T[t + 1] if t < max_trays else Constraint.Skip m.P = Var(doc='Pressure [bar]', @@ -194,12 +312,41 @@ def monotonoic_temperature(_, t): @m.Constraint() def distillate_req(m): + """ + Flow of benzene in the distillate meets the specified purity requirement. + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo model of the distillation column. + + Returns + ------- + Pyomo.Constraint + Constraint that the flow of benzene in the distillate meets the specified purity requirement. The flow of benzene in the distillate is greater than or equal to the distillate purity times the total distillate flow. + """ return m.D['benzene'] >= m.distillate_purity * m.dis @m.Constraint() def bottoms_req(m): + """ + Flow of toluene in the bottoms meets the specified purity requirement. + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo model of the distillation column. + + Returns + ------- + Pyomo.Constraint + Constraint that the flow of toluene in the bottoms meets the specified purity requirement. The flow of toluene in the bottoms is greater than or equal to the bottoms purity times the total bottoms flow. + """ return m.B['toluene'] >= m.bottoms_purity * m.bot + # Define the objective function as the sum of reboiler and condenser duty plus an indicator for tray activation + # The objective is to minimize the sum of condenser and reboiler duties, Qc and Qb, multiplied by 1E3 to convert units, + # and also the number of activated trays, which is obtained by summing up the indicator variables for the trays by 1E3 [$/No. of Trays]. # m.obj = Objective(expr=(m.Qc + m.Qb) * 1E-3, sense=minimize) m.obj = Objective( expr=(m.Qc + m.Qb) * 1E3 + 1E3 * ( sum(m.tray[t].binary_indicator_var for t in m.conditional_trays) + 1), @@ -209,15 +356,55 @@ def bottoms_req(m): @m.Constraint() def reflux_ratio_calc(m): + """ + Reflux ratio is the ratio between the distillate flow and the difference in vapor flow in the condenser tray. + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo model of the distillation column.. + + Returns + ------- + Pyomo.Constraint + Constraint that the reflux ratio is the ratio between the distillate flow and the difference in vapor flow in the condenser tray. + """ return m.reflux_frac * (m.reflux_ratio + 1) == m.reflux_ratio @m.Constraint() def reboil_ratio_calc(m): + """ + Reboil ratio is the ratio between the bottoms flow and the liquid leaving the reboiler. + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo model of the distillation column. + + Returns + ------- + Pyomo.Constraint + Constraint that the reboil ratio is the ratio between the bottoms flow and the liquid leaving the reboiler. + """ return m.boilup_frac * (m.reboil_ratio + 1) == m.reboil_ratio @m.Constraint(m.conditional_trays) def tray_ordering(m, t): - """Trays close to the feed should be activated first.""" + """ + Trays close to the feed should be activated first. + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo model of the distillation column. + t : int + Index of tray in the distillation column model. + + Returns + ------- + Pyomo.Constraint + Constraint that trays close to the feed should be activated first. + """ if t + 1 < m.condens_tray and t > m.feed_tray: return m.tray[t].binary_indicator_var >= \ m.tray[t + 1].binary_indicator_var @@ -231,13 +418,40 @@ def tray_ordering(m, t): def _build_conditional_tray_mass_balance(m, t, tray, no_tray): - """ - t = tray number - tray = tray exists disjunct - no_tray = tray absent disjunct + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo model of the distillation column. + t : int + Index of tray in the distillation column model. + tray : Pyomo.gdp.Disjunct + Disjunct representing the existence of the tray. + no_tray : Pyomo.gdp.Disjunct + Disjunct representing the absence of the tray. + + Returns + ------- + None + _description_ """ @tray.Constraint(m.comps) def mass_balance(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return ( # Feed in if feed tray (m.feed[c] if t == m.feed_tray else 0) @@ -256,34 +470,144 @@ def mass_balance(_, c): @tray.Constraint(m.comps) def tray_liquid_composition(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return m.L[c, t] == m.liq[t] * m.x[c, t] @tray.Constraint(m.comps) def tray_vapor_compositions(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return m.V[c, t] == m.vap[t] * m.y[c, t] @no_tray.Constraint(m.comps) def liq_comp_pass_through(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return m.x[c, t] == m.x[c, t + 1] @no_tray.Constraint(m.comps) def liq_flow_pass_through(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return m.L[c, t] == m.L[c, t + 1] @no_tray.Constraint(m.comps) def vap_comp_pass_through(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return m.y[c, t] == m.y[c, t - 1] @no_tray.Constraint(m.comps) def vap_flow_pass_through(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return m.V[c, t] == m.V[c, t - 1] def _build_feed_tray_mass_balance(m): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + _description_ + + Returns + ------- + _type_ + _description_ + """ t = m.feed_tray @m.Constraint(m.comps) def feed_mass_balance(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return ( m.feed[c] # Feed in - m.V[c, t] # Vapor from tray t @@ -294,18 +618,72 @@ def feed_mass_balance(_, c): @m.Constraint(m.comps) def feed_tray_liquid_composition(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return m.L[c, t] == m.liq[t] * m.x[c, t] @m.Constraint(m.comps) def feed_tray_vapor_composition(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return m.V[c, t] == m.vap[t] * m.y[c, t] def _build_condenser_mass_balance(m): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo model of the distillation column.. + + Returns + ------- + _type_ + _description_ + """ t = m.condens_tray @m.Constraint(m.comps) def condenser_mass_balance(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return ( - m.V[c, t] # Vapor from tray t - m.D[c] # Loss to distillate @@ -315,30 +693,124 @@ def condenser_mass_balance(_, c): @m.partial_cond.Constraint(m.comps) def condenser_liquid_composition(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return m.L[c, t] == m.liq[t] * m.x[c, t] @m.partial_cond.Constraint(m.comps) def condenser_vapor_composition(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return m.V[c, t] == m.vap[t] * m.y[c, t] @m.total_cond.Constraint(m.comps) def no_vapor_flow(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return m.V[c, t] == 0 @m.total_cond.Constraint() def no_total_vapor_flow(_): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return m.vap[t] == 0 @m.total_cond.Constraint(m.comps) def liquid_fraction_pass_through(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return m.x[c, t] == m.y[c, t - 1] @m.Constraint(m.comps) def condenser_distillate_composition(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return m.D[c] == m.dis * m.x[c, t] def _build_reboiler_mass_balance(m): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo model of the distillation column.. + + Returns + ------- + _type_ + _description_ + """ t = m.reboil_tray @m.Constraint(m.comps) @@ -352,25 +824,95 @@ def reboiler_mass_balance(_, c): @m.Constraint(m.comps) def reboiler_liquid_composition(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return m.L[c, t] == m.liq[t] * m.x[c, t] @m.Constraint(m.comps) def reboiler_vapor_composition(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return m.V[c, t] == m.vap[t] * m.y[c, t] def _build_tray_phase_equilibrium(m, t, tray): @tray.Constraint(m.comps) def raoults_law(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return m.y[c, t] == m.x[c, t] * m.Kc[c, t] @tray.Constraint(m.comps) def phase_equil_const(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return m.Kc[c, t] * m.P == ( m.gamma[c, t] * m.Pvap[c, t]) @tray.Constraint(m.comps) def Pvap_relation(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ k = m.pvap_const[c] x = m.Pvap_X[c, t] return (log(m.Pvap[c, t]) - log(k['Pc'])) * (1 - x) == ( @@ -381,17 +923,73 @@ def Pvap_relation(_, c): @tray.Constraint(m.comps) def Pvap_X_defn(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ k = m.pvap_const[c] return m.Pvap_X[c, t] == 1 - m.T[t] / k['Tc'] @tray.Constraint(m.comps) def gamma_calc(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return m.gamma[c, t] == 1 def _build_column_heat_relations(m): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo model of the distillation column.. + + Returns + ------- + _type_ + _description_ + """ @m.Expression(m.trays, m.comps) def liq_enthalpy_expr(_, t, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + t : int + Index of tray in the distillation column model. + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ k = m.liq_Cp_const[c] return ( k['A'] * (m.T[t] - m.T_ref) + @@ -402,6 +1000,22 @@ def liq_enthalpy_expr(_, t, c): @m.Expression(m.trays, m.comps) def vap_enthalpy_expr(_, t, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + t : int + Index of tray in the distillation column model. + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ k = m.vap_Cp_const[c] return ( m.dH_vap[c] + @@ -421,6 +1035,18 @@ def vap_enthalpy_expr(_, t, c): def _build_conditional_tray_energy_balance(m, t, tray, no_tray): @tray.Constraint() def energy_balance(_): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return sum( m.L[c, t + 1] * m.H_L[c, t + 1] # heat of liquid from tray above - m.L[c, t] * m.H_L[c, t] # heat of liquid to tray below @@ -430,26 +1056,106 @@ def energy_balance(_): @tray.Constraint(m.comps) def liq_enthalpy_calc(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + Index of component in the distillation column model. 'benzene' or 'toluene'. + """ return m.H_L[c, t] == m.liq_enthalpy_expr[t, c] @tray.Constraint(m.comps) def vap_enthalpy_calc(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return m.H_V[c, t] == m.vap_enthalpy_expr[t, c] @no_tray.Constraint(m.comps) def liq_enthalpy_pass_through(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return m.H_L[c, t] == m.H_L[c, t + 1] @no_tray.Constraint(m.comps) def vap_enthalpy_pass_through(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return m.H_V[c, t] == m.H_V[c, t - 1] def _build_feed_tray_energy_balance(m): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo model of the distillation column.. + + Returns + ------- + _type_ + _description_ + """ t = m.feed_tray @m.Constraint() def feed_tray_energy_balance(_): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return ( sum(m.feed[c] * ( m.H_L_spec_feed[c] * (1 - m.feed_vap_frac) + @@ -468,14 +1174,56 @@ def feed_tray_energy_balance(_): @m.Constraint(m.comps) def feed_tray_liq_enthalpy_calc(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return m.H_L[c, t] == m.liq_enthalpy_expr[t, c] @m.Constraint(m.comps) def feed_tray_vap_enthalpy_calc(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return m.H_V[c, t] == m.vap_enthalpy_expr[t, c] @m.Expression(m.comps) def feed_liq_enthalpy_expr(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ k = m.liq_Cp_const[c] return ( k['A'] * (m.T_feed - m.T_ref) + @@ -486,10 +1234,38 @@ def feed_liq_enthalpy_expr(_, c): @m.Constraint(m.comps) def feed_liq_enthalpy_calc(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return m.H_L_spec_feed[c] == m.feed_liq_enthalpy_expr[c] @m.Expression(m.comps) def feed_vap_enthalpy_expr(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ k = m.vap_Cp_const[c] return ( m.dH_vap[c] + @@ -501,14 +1277,52 @@ def feed_vap_enthalpy_expr(_, c): @m.Constraint(m.comps) def feed_vap_enthalpy_calc(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return m.H_V_spec_feed[c] == m.feed_vap_enthalpy_expr[c] def _build_condenser_energy_balance(m): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo model of the distillation column. + + Returns + ------- + _type_ + _description_ + """ t = m.condens_tray @m.partial_cond.Constraint() def partial_condenser_energy_balance(_): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return -m.Qc + sum( - m.D[c] * m.H_L[c, t] # heat of liquid distillate - m.L[c, t] * m.H_L[c, t] # heat of liquid to tray below @@ -518,6 +1332,18 @@ def partial_condenser_energy_balance(_): @m.total_cond.Constraint() def total_condenser_energy_balance(_): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return -m.Qc + sum( - m.D[c] * m.H_L[c, t] # heat of liquid distillate - m.L[c, t] * m.H_L[c, t] # heat of liquid to tray below @@ -526,18 +1352,70 @@ def total_condenser_energy_balance(_): @m.Constraint(m.comps) def condenser_liq_enthalpy_calc(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return m.H_L[c, t] == m.liq_enthalpy_expr[t, c] @m.partial_cond.Constraint(m.comps) def vap_enthalpy_calc(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return m.H_V[c, t] == m.vap_enthalpy_expr[t, c] def _build_reboiler_energy_balance(m): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo model of the distillation column. + + Returns + ------- + _type_ + _description_ + """ t = m.reboil_tray @m.Constraint() def reboiler_energy_balance(_): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return m.Qb + sum( m.L[c, t + 1] * m.H_L[c, t + 1] # Heat of liquid from tray above - m.B[c] * m.H_L[c, t] # heat of liquid bottoms if reboiler @@ -546,8 +1424,36 @@ def reboiler_energy_balance(_): @m.Constraint(m.comps) def reboiler_liq_enthalpy_calc(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return m.H_L[c, t] == m.liq_enthalpy_expr[t, c] @m.Constraint(m.comps) def reboiler_vap_enthalpy_calc(_, c): + """_summary_ + + Parameters + ---------- + _ : _type_ + _description_ + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + _type_ + _description_ + """ return m.H_V[c, t] == m.vap_enthalpy_expr[t, c] From 7fafd9a91390583ebf841f021e6fa01325674152 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 7 May 2024 14:07:23 -0400 Subject: [PATCH 31/60] add name for each model --- gdplib/batch_processing/batch_processing.py | 273 ++- gdplib/disease_model/disease_model.py | 1581 ++++++++++++++++- gdplib/jobshop/jobshop.py | 2 +- .../med_term_purchasing.py | 2 +- 4 files changed, 1769 insertions(+), 89 deletions(-) diff --git a/gdplib/batch_processing/batch_processing.py b/gdplib/batch_processing/batch_processing.py index 55cae74..f11610c 100644 --- a/gdplib/batch_processing/batch_processing.py +++ b/gdplib/batch_processing/batch_processing.py @@ -20,16 +20,16 @@ # tanks. The problem is convexified and has a nonlinear objective and global constraints # NOTE: When I refer to 'gams' in the comments, that is Batch101006_BM.gms for now. It's confusing -# because the _opt file is different (It has hard-coded bigM parameters so that each constraint +# because the _opt file is different (It has hard-coded bigM parameters so that each constraint # has the "optimal" bigM). def build_model(): """ - Constructs and initializes a Pyomo model for the batch processing problem. + Constructs and initializes a Pyomo model for the batch processing problem. The model is designed to minimize the total cost associated with the design and operation of a plant consisting of multiple - parallel processing units with intermediate storage tanks. + parallel processing units with intermediate storage tanks. It involves determining the optimal number and sizes of processing units, batch sizes for different products at various stages, and sizes and placements of storage tanks to ensure operational efficiency while meeting production requirements within a specified time horizon. @@ -40,7 +40,7 @@ def build_model(): Returns ------- Pyomo.ConcreteModel - An instance of the Pyomo ConcreteModel class representing the batch processing optimization model, + An instance of the Pyomo ConcreteModel class representing the batch processing optimization model, ready to be solved with an appropriate solver. References @@ -48,13 +48,12 @@ def build_model(): [1] Ravemark, E. Optimization models for design and operation of chemical batch processes. Ph.D. Thesis, ETH Zurich, 1995. https://doi.org/10.3929/ethz-a-001591449 [2] Vecchietti, A., & Grossmann, I. E. (1999). LOGMIP: a disjunctive 0–1 non-linear optimizer for process system models. Computers & chemical engineering, 23(4-5), 555-565. https://doi.org/10.1016/S0098-1354(97)87539-4 """ - model = AbstractModel() + model = AbstractModel("Batch Processing Optimization Problem") # TODO: it looks like they set a bigM for each j. Which I need to look up how to do... model.BigM = Suffix(direction=Suffix.LOCAL) model.BigM[None] = 1000 - # Constants from GAMS StorageTankSizeFactor = 10 StorageTankSizeFactorByProd = 3 @@ -77,7 +76,7 @@ def build_model(): # TODO: this seems like an over-complicated way to accomplish this task... def filter_out_last(model, j): """ - Filters out the last stage from the set of stages to avoid considering it in certain constraints + Filters out the last stage from the set of stages to avoid considering it in certain constraints or disjunctions where the next stage would be required but doesn't exist. Parameters @@ -94,8 +93,8 @@ def filter_out_last(model, j): Returns True if the stage is not the last one in the set, False otherwise. """ return j != model.STAGES.last() - model.STAGESExceptLast = Set(initialize=model.STAGES, filter=filter_out_last) + model.STAGESExceptLast = Set(initialize=model.STAGES, filter=filter_out_last) # TODO: these aren't in the formulation?? # model.STORAGE_TANKS = Set() @@ -109,13 +108,21 @@ def filter_out_last(model, j): model.Beta2 = Param(doc='Exponent Parameter of the intermediate storage tanks') model.ProductionAmount = Param(model.PRODUCTS, doc='Production Amount') - model.ProductSizeFactor = Param(model.PRODUCTS, model.STAGES, doc='Product Size Factor') + model.ProductSizeFactor = Param( + model.PRODUCTS, model.STAGES, doc='Product Size Factor' + ) 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.StorageTankSizeFactorByProd = Param(model.PRODUCTS, model.STAGES, - default=StorageTankSizeFactorByProd, doc='Storage Tank Size Factor by Product') + model.StorageTankSizeFactor = Param( + 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', + ) # TODO: bonmin wasn't happy and I think it might have something to do with this? # or maybe issues with convexity or a lack thereof... I don't know yet. @@ -123,7 +130,7 @@ def filter_out_last(model, j): # from 1, right? def get_log_coeffs(model, k): """ - Calculates the logarithmic coefficients used in the model, typically for transforming linear + Calculates the logarithmic coefficients used in the model, typically for transforming linear relationships into logarithmic form for optimization purposes. Parameters @@ -140,16 +147,29 @@ 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.LogCoeffs = Param( + 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.volumeUB = Param(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.storageTankSizeUB = Param(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.unitsOutOfPhaseUB = Param(model.STAGES, default=UnitsOutOfPhaseUB, doc='Upper Bound of Units Out of Phase') - + model.volumeLB = Param( + 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.storageTankSizeLB = Param( + 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.unitsInPhaseUB = Param( + 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' + ) # Variables @@ -192,9 +212,16 @@ def get_volume_bounds(model, j): A tuple containing the lower and upper bounds for the volume of processing units at stage 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.batchSize_log = Var(model.PRODUCTS, model.STAGES, doc='Logarithmic Batch Size of the Products') - model.cycleTime_log = Var(model.PRODUCTS, doc='Logarithmic Cycle Time of the Products') + + model.volume_log = Var( + 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.cycleTime_log = Var( + model.PRODUCTS, doc='Logarithmic Cycle Time of the Products' + ) def get_unitsOutOfPhase_bounds(model, j): """ @@ -213,7 +240,12 @@ def get_unitsOutOfPhase_bounds(model, j): A tuple containing the lower and upper bounds for the logarithmic representation of the number of units out of phase at stage j. """ return (0, model.unitsOutOfPhaseUB[j]) - model.unitsOutOfPhase_log = Var(model.STAGES, bounds=get_unitsOutOfPhase_bounds, doc='Logarithmic Units Out of Phase') + + model.unitsOutOfPhase_log = Var( + model.STAGES, + bounds=get_unitsOutOfPhase_bounds, + doc='Logarithmic Units Out of Phase', + ) def get_unitsInPhase_bounds(model, j): """ @@ -232,7 +264,10 @@ def get_unitsInPhase_bounds(model, j): A tuple containing the minimum and maximum bounds for the logarithmic number of units in phase at stage j, ensuring model constraints are met. """ return (0, model.unitsInPhaseUB[j]) - model.unitsInPhase_log = Var(model.STAGES, bounds=get_unitsInPhase_bounds, doc='Logarithmic Units In Phase') + + model.unitsInPhase_log = Var( + model.STAGES, bounds=get_unitsInPhase_bounds, doc='Logarithmic Units In Phase' + ) def get_storageTankSize_bounds(model, j): """ @@ -251,12 +286,21 @@ def get_storageTankSize_bounds(model, j): A tuple containing the lower and upper bounds for the storage tank size at the specified stage. """ return (model.storageTankSizeLB[j], model.storageTankSizeUB[j]) + # TODO: these bounds make it infeasible... - model.storageTankSize_log = Var(model.STAGES, bounds=get_storageTankSize_bounds, doc='Logarithmic Storage Tank Size') + model.storageTankSize_log = Var( + model.STAGES, + bounds=get_storageTankSize_bounds, + 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.inPhase = Var(model.STAGES, model.PARALLELUNITS, within=Binary, doc='In Phase Units') + model.outOfPhase = Var( + model.STAGES, model.PARALLELUNITS, within=Binary, doc='Out of Phase Units' + ) + model.inPhase = Var( + model.STAGES, model.PARALLELUNITS, within=Binary, doc='In Phase Units' + ) # Objective @@ -276,18 +320,29 @@ def get_cost_rule(model): Notes ----- - The cost is a function of the volume of processing units and the size of storage tanks, each scaled by respective cost + The cost is a function of the volume of processing units and the size of storage tanks, each scaled by respective cost parameters and exponentiated to reflect non-linear cost relationships. """ - return model.Alpha1 * sum(exp(model.unitsInPhase_log[j] + model.unitsOutOfPhase_log[j] + \ - model.Beta1 * model.volume_log[j]) for j in model.STAGES) +\ - model.Alpha2 * sum(exp(model.Beta2 * model.storageTankSize_log[j]) for j in model.STAGESExceptLast) - model.min_cost = Objective(rule=get_cost_rule, doc='Minimize the Total Cost of the Plant Design') + return model.Alpha1 * sum( + exp( + model.unitsInPhase_log[j] + + model.unitsOutOfPhase_log[j] + + model.Beta1 * model.volume_log[j] + ) + for j in model.STAGES + ) + model.Alpha2 * sum( + exp(model.Beta2 * model.storageTankSize_log[j]) + for j in model.STAGESExceptLast + ) + + model.min_cost = Objective( + rule=get_cost_rule, doc='Minimize the Total Cost of the Plant Design' + ) # Constraints def processing_capacity_rule(model, j, i): """ - Ensures that the volume of each processing unit at stage j is sufficient to accommodate the batch size of product i, + Ensures that the volume of each processing unit at stage j is sufficient to accommodate the batch size of product i, taking into account the size factor of the product and the number of units in phase at that stage. Parameters @@ -305,9 +360,19 @@ def processing_capacity_rule(model, j, i): Pyomo.Expression A Pyomo expression that defines the processing capacity constraint for product `i` at stage `j`. """ - return model.volume_log[j] >= log(model.ProductSizeFactor[i, j]) + model.batchSize_log[i, j] - \ - model.unitsInPhase_log[j] - model.processing_capacity = Constraint(model.STAGES, model.PRODUCTS, rule=processing_capacity_rule, doc='Processing Capacity') + return ( + model.volume_log[j] + >= log(model.ProductSizeFactor[i, j]) + + model.batchSize_log[i, j] + - model.unitsInPhase_log[j] + ) + + model.processing_capacity = Constraint( + model.STAGES, + model.PRODUCTS, + rule=processing_capacity_rule, + doc='Processing Capacity', + ) def processing_time_rule(model, j, i): """ @@ -329,9 +394,16 @@ def processing_time_rule(model, j, i): A Pyomo expression defining the constraint that the cycle time for processing product `i` at stage `j` must not exceed the maximum allowed, considering the batch size and the units out of phase at this stage. """ - return model.cycleTime_log[i] >= log(model.ProcessingTime[i, j]) - model.batchSize_log[i, j] - \ - model.unitsOutOfPhase_log[j] - model.processing_time = Constraint(model.STAGES, model.PRODUCTS, rule=processing_time_rule, doc='Processing Time') + return ( + model.cycleTime_log[i] + >= log(model.ProcessingTime[i, j]) + - model.batchSize_log[i, j] + - model.unitsOutOfPhase_log[j] + ) + + model.processing_time = Constraint( + model.STAGES, model.PRODUCTS, rule=processing_time_rule, doc='Processing Time' + ) def finish_in_time_rule(model): """ @@ -347,16 +419,18 @@ def finish_in_time_rule(model): Pyomo.Expression A Pyomo constraint expression ensuring the total production time does not exceed the time horizon for the plant. """ - return model.HorizonTime >= sum(model.ProductionAmount[i]*exp(model.cycleTime_log[i]) \ - for i in model.PRODUCTS) - model.finish_in_time = Constraint(rule=finish_in_time_rule, doc='Finish in Time') + return model.HorizonTime >= sum( + model.ProductionAmount[i] * exp(model.cycleTime_log[i]) + for i in model.PRODUCTS + ) + model.finish_in_time = Constraint(rule=finish_in_time_rule, doc='Finish in Time') # Disjunctions def storage_tank_selection_disjunct_rule(disjunct, selectStorageTank, j): """ - Defines the conditions under which a storage tank will be included or excluded between stages j and j+1. + Defines the conditions under which a storage tank will be included or excluded between stages j and j+1. This rule is applied to a disjunct, which is part of a disjunction representing this binary decision. Parameters @@ -371,12 +445,13 @@ def storage_tank_selection_disjunct_rule(disjunct, selectStorageTank, j): Returns ------- None - This function defines constraints within the disjunct based on the decision to include (selectStorageTank=1) or exclude (selectStorageTank=0) a storage tank. + This function defines constraints within the disjunct based on the decision to include (selectStorageTank=1) or exclude (selectStorageTank=0) a storage tank. Constraints ensure the storage tank's volume can accommodate the batch sizes at stage j and j+1 if included, or ensure batch size continuity if excluded. """ model = disjunct.model() + def volume_stage_j_rule(disjunct, i): - """ + """ Ensures the storage tank size between stages j and j+1 is sufficient to accommodate the batch size of product i at stage j, considering the storage tank size factor. Parameters @@ -392,9 +467,11 @@ def volume_stage_j_rule(disjunct, i): Pyomo.Constraint A constraint ensuring the storage tank size is sufficient for the batch size at stage j. """ - return model.storageTankSize_log[j] >= log(model.StorageTankSizeFactor[j]) + \ - model.batchSize_log[i, j] - + return ( + model.storageTankSize_log[j] + >= log(model.StorageTankSizeFactor[j]) + model.batchSize_log[i, j] + ) + def volume_stage_jPlus1_rule(disjunct, i): """ Ensures the storage tank size between stages j and j+1 is sufficient to accommodate the batch size of product i at stage j+1, considering the storage tank size factor. @@ -412,9 +489,11 @@ def volume_stage_jPlus1_rule(disjunct, i): Pyomo.Constraint A constraint ensuring the storage tank size is sufficient for the batch size at stage j+1. """ - return model.storageTankSize_log[j] >= log(model.StorageTankSizeFactor[j]) + \ - model.batchSize_log[i, j+1] - + return ( + model.storageTankSize_log[j] + >= log(model.StorageTankSizeFactor[j]) + model.batchSize_log[i, j + 1] + ) + def batch_size_rule(disjunct, i): """ Ensures the difference in batch sizes between stages j and j+1 for product i is within the acceptable limits defined by the storage tank size factor by product. @@ -432,10 +511,12 @@ def batch_size_rule(disjunct, i): Pyomo.Constraint A constraint enforcing acceptable batch size differences between stages j and j+1. """ - return inequality(-log(model.StorageTankSizeFactorByProd[i,j]), - model.batchSize_log[i,j] - model.batchSize_log[i, j+1], - log(model.StorageTankSizeFactorByProd[i,j])) - + return inequality( + -log(model.StorageTankSizeFactorByProd[i, j]), + model.batchSize_log[i, j] - model.batchSize_log[i, j + 1], + log(model.StorageTankSizeFactorByProd[i, j]), + ) + def no_batch_rule(disjunct, i): """ Enforces batch size continuity between stages j and j+1 for product i, applicable when no storage tank is selected. @@ -453,20 +534,28 @@ def no_batch_rule(disjunct, i): Pyomo.Constraint A constraint ensuring batch size continuity between stages j and j+1 """ - return model.batchSize_log[i,j] - model.batchSize_log[i,j+1] == 0 + return model.batchSize_log[i, j] - model.batchSize_log[i, j + 1] == 0 if selectStorageTank: - disjunct.volume_stage_j = Constraint(model.PRODUCTS, rule=volume_stage_j_rule) - disjunct.volume_stage_jPlus1 = Constraint(model.PRODUCTS, - rule=volume_stage_jPlus1_rule) + disjunct.volume_stage_j = Constraint( + model.PRODUCTS, rule=volume_stage_j_rule + ) + disjunct.volume_stage_jPlus1 = Constraint( + model.PRODUCTS, rule=volume_stage_jPlus1_rule + ) disjunct.batch_size = Constraint(model.PRODUCTS, rule=batch_size_rule) else: # The formulation says 0, but GAMS has this constant. # 04/04: Francisco says volume should be free: # disjunct.no_volume = Constraint(expr=model.storageTankSize_log[j] == MinFlow) disjunct.no_batch = Constraint(model.PRODUCTS, rule=no_batch_rule) - model.storage_tank_selection_disjunct = Disjunct([0,1], model.STAGESExceptLast, - rule=storage_tank_selection_disjunct_rule, doc='Storage Tank Selection Disjunct') + + model.storage_tank_selection_disjunct = Disjunct( + [0, 1], + model.STAGESExceptLast, + rule=storage_tank_selection_disjunct_rule, + doc='Storage Tank Selection Disjunct', + ) def select_storage_tanks_rule(model, j): """ @@ -484,8 +573,16 @@ def select_storage_tanks_rule(model, j): list A list of disjuncts representing the choices for including or not including a storage tank between stages j and j+1. """ - return [model.storage_tank_selection_disjunct[selectTank, j] for selectTank in [0,1]] - model.select_storage_tanks = Disjunction(model.STAGESExceptLast, rule=select_storage_tanks_rule, doc='Select Storage Tanks') + return [ + model.storage_tank_selection_disjunct[selectTank, j] + for selectTank in [0, 1] + ] + + model.select_storage_tanks = Disjunction( + model.STAGESExceptLast, + rule=select_storage_tanks_rule, + doc='Select Storage Tanks', + ) # though this is a disjunction in the GAMs model, it is more efficiently formulated this way: # TODO: what on earth is k? Number of Parallel units. @@ -503,16 +600,20 @@ def units_out_of_phase_rule(model, j): Returns ------- None - Adds a constraint to the Pyomo model representing the logarithmic sum of out-of-phase units at stage j. + Adds a constraint to the Pyomo model representing the logarithmic sum of out-of-phase units at stage j. This constraint is not returned but directly added to the model. Notes ----- These are not directly related to disjunctions but more to the logical modeling of unit operations. """ - return model.unitsOutOfPhase_log[j] == sum(model.LogCoeffs[k] * model.outOfPhase[j,k] \ - for k in model.PARALLELUNITS) - model.units_out_of_phase = Constraint(model.STAGES, rule=units_out_of_phase_rule, doc='Units Out of Phase') + return model.unitsOutOfPhase_log[j] == sum( + model.LogCoeffs[k] * model.outOfPhase[j, k] for k in model.PARALLELUNITS + ) + + model.units_out_of_phase = Constraint( + model.STAGES, rule=units_out_of_phase_rule, doc='Units Out of Phase' + ) def units_in_phase_rule(model, j): """_summary_ @@ -528,16 +629,20 @@ def units_in_phase_rule(model, j): Returns ------- None - Incorporates a constraint into the Pyomo model that corresponds to the logarithmic sum of in-phase units at stage j. + Incorporates a constraint into the Pyomo model that corresponds to the logarithmic sum of in-phase units at stage j. The constraint is directly applied to the model without an explicit return value. Notes ----- These are not directly related to disjunctions but more to the logical modeling of unit operations. """ - return model.unitsInPhase_log[j] == sum(model.LogCoeffs[k] * model.inPhase[j,k] \ - for k in model.PARALLELUNITS) - model.units_in_phase = Constraint(model.STAGES, rule=units_in_phase_rule, doc='Units In Phase') + return model.unitsInPhase_log[j] == sum( + model.LogCoeffs[k] * model.inPhase[j, k] for k in model.PARALLELUNITS + ) + + model.units_in_phase = Constraint( + model.STAGES, rule=units_in_phase_rule, doc='Units In Phase' + ) def units_out_of_phase_xor_rule(model, j): """ @@ -555,8 +660,13 @@ def units_out_of_phase_xor_rule(model, j): Pyomo.Constraint A Pyomo constraint expression calculating the logarithmic representation of the number of units out of phase at stage j """ - return sum(model.outOfPhase[j,k] for k in model.PARALLELUNITS) == 1 - model.units_out_of_phase_xor = Constraint(model.STAGES, rule=units_out_of_phase_xor_rule, doc='Exclusive OR for Units Out of Phase') + return sum(model.outOfPhase[j, k] for k in model.PARALLELUNITS) == 1 + + model.units_out_of_phase_xor = Constraint( + model.STAGES, + rule=units_out_of_phase_xor_rule, + doc='Exclusive OR for Units Out of Phase', + ) def units_in_phase_xor_rule(model, j): """ @@ -574,8 +684,13 @@ def units_in_phase_xor_rule(model, j): Pyomo.Constraint A Pyomo constraint expression enforcing the XOR condition for units out of phase at stage j. """ - return sum(model.inPhase[j,k] for k in model.PARALLELUNITS) == 1 - model.units_in_phase_xor = Constraint(model.STAGES, rule=units_in_phase_xor_rule, doc='Exclusive OR for Units In Phase') + return sum(model.inPhase[j, k] for k in model.PARALLELUNITS) == 1 + + model.units_in_phase_xor = Constraint( + model.STAGES, + rule=units_in_phase_xor_rule, + doc='Exclusive OR for Units In Phase', + ) return model.create_instance(join(this_file_dir(), 'batch_processing.dat')) @@ -583,5 +698,7 @@ def units_in_phase_xor_rule(model, j): 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;']) + SolverFactory('gams').solve( + m, solver='baron', tee=True, add_options=['option optcr=1e-6;'] + ) m.min_cost.display() diff --git a/gdplib/disease_model/disease_model.py b/gdplib/disease_model/disease_model.py index 47b7b81..1f60644 100644 --- a/gdplib/disease_model/disease_model.py +++ b/gdplib/disease_model/disease_model.py @@ -39,19 +39,1582 @@ def build_model(): # import data # Population Data - pop = [ 15.881351, 15.881339, 15.881320, 15.881294, 15.881261, 15.881223, 15.881180, 15.881132, 15.881079, 15.881022, 15.880961, 15.880898, 15.880832, 15.880764, 15.880695, 15.880624, 15.880553, 15.880480, 15.880409, 15.880340, 15.880270, 15.880203, 15.880138, 15.880076, 15.880016, 15.879960, 15.879907, 15.879852, 15.879799, 15.879746, 15.879693, 15.879638, 15.879585, 15.879531, 15.879477, 15.879423, 15.879370, 15.879315, 15.879262, 15.879209, 15.879155, 15.879101, 15.879048, 15.878994, 15.878940, 15.878886, 15.878833, 15.878778, 15.878725, 15.878672, 15.878618, 15.878564, 15.878510, 15.878457, 15.878402, 15.878349, 15.878295, 15.878242, 15.878187, 15.878134, 15.878081, 15.878026, 15.877973, 15.877919, 15.877864, 15.877811, 15.877758, 15.877704, 15.877650, 15.877596, 15.877543, 15.877488, 15.877435, 15.877381, 15.877326, 15.877273, 15.877220, 15.877166, 15.877111, 15.877058, 15.877005, 15.876950, 15.876896, 15.876843, 15.876789, 15.876735, 15.876681, 15.876628, 15.876573, 15.876520, 15.876466, 15.876411, 15.876358, 15.876304, 15.876251, 15.876196, 15.876143, 15.876089, 15.876034, 15.875981, 15.875927, 15.875872, 15.875819, 15.875765, 15.875712, 15.875657, 15.875604, 15.875550, 15.875495, 15.875442, 15.875388, 15.875335, 15.875280, 15.875226, 15.875173, 15.875118, 15.875064, 15.875011, 15.874956, 15.874902, 15.874849, 15.874795, 15.874740, 15.874687, 15.874633, 15.874578, 15.874525, 15.874471, 15.874416, 15.874363, 15.874309, 15.874256, 15.874201, 15.874147, 15.874094, 15.874039, 15.873985, 15.873931, 15.873878, 15.873823, 15.873769, 15.873716, 15.873661, 15.873607, 15.873554, 15.873499, 15.873445, 15.873391, 15.873338, 15.873283, 15.873229, 15.873175, 15.873121, 15.873067, 15.873013, 15.872960, 15.872905, 15.872851, 15.872797, 15.872742, 15.872689, 15.872635, 15.872580, 15.872526, 15.872473, 15.872419, 15.872364, 15.872310, 15.872256, 15.872202, 15.872148, 15.872094, 15.872039, 15.871985, 15.871932, 15.871878, 15.871823, 15.871769, 15.871715, 15.871660, 15.871607, 15.871553, 15.871499, 15.871444, 15.871390, 15.871337, 15.871282, 15.871228, 15.871174, 15.871119, 15.871065, 15.871012, 15.870958, 15.870903, 15.870849, 15.870795, 15.870740, 15.870686, 15.870633, 15.870577, 15.870524, 15.870470, 15.870416, 15.870361, 15.870307, 15.870253, 15.870198, 15.870144, 15.870091, 15.870037, 15.869982, 15.869928, 15.869874, 15.869819, 15.869765, 15.869711, 15.869656, 15.869602, 15.869548, 15.869495, 15.869439, 15.869386, 15.869332, 15.869277, 15.869223, 15.869169, 15.869114, 15.869060, 15.869006, 15.868952, 15.868897, 15.868843, 15.868789, 15.868734, 15.868679, 15.868618, 15.868556, 15.868489, 15.868421, 15.868351, 15.868280, 15.868208, 15.868134, 15.868063, 15.867991, 15.867921, 15.867852, 15.867785, 15.867721, 15.867659, 15.867601, 15.867549, 15.867499, 15.867455, 15.867416, 15.867383, 15.867357, 15.867338, 15.867327, 15.867321, 15.867327, 15.867338, 15.867359, 15.867386, 15.867419, 15.867459, 15.867505, 15.867555, 15.867610, 15.867671, 15.867734, 15.867801, 15.867869, 15.867941, 15.868012, 15.868087, 15.868161, 15.868236, 15.868310, 15.868384, 15.868457, 15.868527, 15.868595, 15.868661, 15.868722, 15.868780, 15.868837, 15.868892, 15.868948, 15.869005, 15.869061, 15.869116, 15.869173, 15.869229, 15.869284, 15.869341, 15.869397, 15.869452, 15.869509, 15.869565, 15.869620, 15.869677, 15.869733, 15.869788, 15.869845, 15.869901, 15.869956, 15.870012, 15.870069, 15.870124, 15.870180, 15.870237, 15.870292, 15.870348, 15.870405, 15.870461, 15.870516, 15.870572, 15.870629, 15.870684, 15.870740, 15.870796, 15.870851, 15.870908, 15.870964, 15.871019, 15.871076, 15.871132, 15.871187, 15.871243, 15.871300, 15.871355, 15.871411, 15.871467, 15.871522, 15.871579, 15.871635, 15.871691, 15.871746, 15.871802, 15.871859, 15.871914, 15.871970, 15.872026, 15.872081, 15.872138, 15.872194, 15.872249, 15.872305, 15.872361, 15.872416, 15.872473, 15.872529, 15.872584, 15.872640, 15.872696, 15.872751, 15.872807, 15.872864, 15.872919, 15.872975, 15.873031, 15.873087, 15.873142, 15.873198, 15.873255, 15.873310, 15.873366, 15.873422, 15.873477, 15.873533, 15.873589, 15.873644, 15.873700, 15.873757, 15.873811, 15.873868, 15.873924, 15.873979, 15.874035, 15.874091, 15.874146, 15.874202, 15.874258, 15.874313, 15.874369, 15.874425, 15.874481, 15.874536, 15.874592] - - logIstar = [7.943245, 8.269994, 8.517212, 8.814208, 9.151740, 9.478472, 9.559847, 9.664087, 9.735378, 9.852583, 9.692265, 9.498807, 9.097634, 8.388878, 7.870516, 7.012956, 6.484941, 5.825368, 5.346815, 5.548361, 5.706732, 5.712617, 5.709714, 5.696888, 5.530087, 5.826563, 6.643563, 7.004292, 7.044663, 7.190259, 7.335926, 7.516861, 7.831779, 8.188895, 8.450204, 8.801436, 8.818379, 8.787658, 8.601685, 8.258338, 7.943364, 7.425585, 7.062834, 6.658307, 6.339600, 6.526984, 6.679178, 6.988758, 7.367331, 7.746694, 8.260558, 8.676522, 9.235582, 9.607778, 9.841917, 10.081571, 10.216090, 10.350366, 10.289668, 10.248842, 10.039504, 9.846343, 9.510392, 9.190923, 8.662465, 7.743221, 7.128458, 5.967898, 5.373883, 5.097497, 4.836570, 5.203345, 5.544798, 5.443047, 5.181152, 5.508669, 6.144130, 6.413744, 6.610423, 6.748885, 6.729511, 6.789841, 6.941034, 7.093516, 7.307039, 7.541077, 7.644803, 7.769145, 7.760187, 7.708017, 7.656795, 7.664983, 7.483828, 6.887324, 6.551093, 6.457449, 6.346064, 6.486300, 6.612378, 6.778753, 6.909477, 7.360570, 8.150303, 8.549044, 8.897572, 9.239323, 9.538751, 9.876531, 10.260911, 10.613536, 10.621510, 10.661115, 10.392899, 10.065536, 9.920090, 9.933097, 9.561691, 8.807713, 8.263463, 7.252184, 6.669083, 5.877763, 5.331878, 5.356563, 5.328469, 5.631146, 6.027497, 6.250717, 6.453919, 6.718444, 7.071636, 7.348905, 7.531528, 7.798226, 8.197941, 8.578809, 8.722964, 8.901152, 8.904370, 8.889865, 8.881902, 8.958903, 8.721281, 8.211509, 7.810624, 7.164607, 6.733688, 6.268503, 5.905983, 5.900432, 5.846547, 6.245427, 6.786271, 7.088480, 7.474295, 7.650063, 7.636703, 7.830990, 8.231516, 8.584816, 8.886908, 9.225216, 9.472778, 9.765505, 9.928623, 10.153033, 10.048574, 9.892620, 9.538818, 8.896100, 8.437584, 7.819738, 7.362598, 6.505880, 5.914972, 6.264584, 6.555019, 6.589319, 6.552029, 6.809771, 7.187616, 7.513918, 8.017712, 8.224957, 8.084474, 8.079148, 8.180991, 8.274269, 8.413748, 8.559599, 8.756090, 9.017927, 9.032720, 9.047983, 8.826873, 8.366489, 8.011876, 7.500830, 7.140406, 6.812626, 6.538719, 6.552218, 6.540129, 6.659927, 6.728530, 7.179692, 7.989210, 8.399173, 8.781128, 9.122303, 9.396378, 9.698512, 9.990104, 10.276543, 10.357284, 10.465869, 10.253833, 10.018503, 9.738407, 9.484367, 9.087025, 8.526409, 8.041126, 7.147168, 6.626706, 6.209446, 5.867231, 5.697439, 5.536769, 5.421413, 5.238297, 5.470136, 5.863007, 6.183083, 6.603569, 6.906278, 7.092324, 7.326612, 7.576052, 7.823430, 7.922775, 8.041677, 8.063403, 8.073229, 8.099726, 8.168522, 8.099041, 8.011404, 7.753147, 6.945211, 6.524244, 6.557723, 6.497742, 6.256247, 5.988794, 6.268093, 6.583316, 7.106842, 8.053929, 8.508237, 8.938915, 9.311863, 9.619753, 9.931745, 10.182361, 10.420978, 10.390829, 10.389230, 10.079342, 9.741479, 9.444561, 9.237448, 8.777687, 7.976436, 7.451502, 6.742856, 6.271545, 5.782289, 5.403089, 5.341954, 5.243509, 5.522993, 5.897001, 6.047042, 6.100738, 6.361727, 6.849562, 7.112544, 7.185346, 7.309412, 7.423746, 7.532142, 7.510318, 7.480175, 7.726362, 8.061117, 8.127072, 8.206166, 8.029634, 7.592953, 7.304869, 7.005394, 6.750019, 6.461377, 6.226432, 6.287047, 6.306452, 6.783694, 7.450957, 7.861692, 8.441530, 8.739626, 8.921994, 9.168961, 9.428077, 9.711664, 10.032714, 10.349937, 10.483985, 10.647475, 10.574038, 10.522431, 10.192246, 9.756246, 9.342511, 8.872072, 8.414189, 7.606582, 7.084701, 6.149903, 5.517257, 5.839429, 6.098090, 6.268935, 6.475965, 6.560543, 6.598942, 6.693938, 6.802531, 6.934345, 7.078370, 7.267736, 7.569640, 7.872204, 8.083603, 8.331226, 8.527144, 8.773523, 8.836599, 8.894303, 8.808326, 8.641717, 8.397901, 7.849034, 7.482899, 7.050252, 6.714103, 6.900603, 7.050765, 7.322905, 7.637986, 8.024340, 8.614505, 8.933591, 9.244008, 9.427410, 9.401385, 9.457744, 9.585068, 9.699673, 9.785478, 9.884559, 9.769732, 9.655075, 9.423071, 9.210198, 8.786654, 8.061787, 7.560976, 6.855829, 6.390707, 5.904006, 5.526631, 5.712303, 5.867027, 5.768367, 5.523352, 5.909118, 6.745543, 6.859218 ] - - deltaS = [ 9916.490263 ,12014.263380 ,13019.275755 ,12296.373612 ,8870.995603 ,1797.354574 ,-6392.880771 ,-16150.825387 ,-27083.245106 ,-40130.421462 ,-50377.169958 ,-57787.717468 ,-60797.223427 ,-59274.041897 ,-55970.213230 ,-51154.650927 ,-45877.841034 ,-40278.553775 ,-34543.967175 ,-28849.633641 ,-23192.776605 ,-17531.130740 ,-11862.021829 ,-6182.456792 ,-450.481090 ,5201.184400 ,10450.773882 ,15373.018272 ,20255.699431 ,24964.431669 ,29470.745887 ,33678.079947 ,37209.808930 ,39664.432393 ,41046.735479 ,40462.982011 ,39765.070209 ,39270.815830 ,39888.077002 ,42087.276604 ,45332.012929 ,49719.128772 ,54622.190928 ,59919.718626 ,65436.341097 ,70842.911460 ,76143.747430 ,81162.358574 ,85688.102884 ,89488.917734 ,91740.108470 ,91998.787916 ,87875.986012 ,79123.877908 ,66435.611045 ,48639.250610 ,27380.282817 ,2166.538464 ,-21236.428084 ,-43490.803535 ,-60436.624080 ,-73378.401966 ,-80946.278268 ,-84831.969493 ,-84696.627286 ,-81085.365407 ,-76410.847049 ,-70874.415387 ,-65156.276464 ,-59379.086883 ,-53557.267619 ,-47784.164830 ,-42078.001172 ,-36340.061427 ,-30541.788202 ,-24805.281435 ,-19280.817165 ,-13893.690606 ,-8444.172221 ,-3098.160839 ,2270.908649 ,7594.679295 ,12780.079247 ,17801.722109 ,22543.091206 ,26897.369814 ,31051.285734 ,34933.809557 ,38842.402859 ,42875.230152 ,47024.395356 ,51161.516122 ,55657.298307 ,60958.155424 ,66545.635029 ,72202.930397 ,77934.761905 ,83588.207792 ,89160.874522 ,94606.115027 ,99935.754968 ,104701.404975 ,107581.670606 ,108768.440311 ,107905.700480 ,104062.148863 ,96620.281684 ,83588.443029 ,61415.088182 ,27124.031692 ,-7537.285321 ,-43900.451653 ,-70274.062783 ,-87573.481475 ,-101712.148408 ,-116135.719087 ,-124187.225446 ,-124725.278371 ,-122458.145590 ,-117719.918256 ,-112352.138605 ,-106546.806030 ,-100583.803012 ,-94618.253238 ,-88639.090897 ,-82725.009842 ,-76938.910669 ,-71248.957807 ,-65668.352795 ,-60272.761991 ,-55179.538428 ,-50456.021161 ,-46037.728058 ,-42183.912670 ,-39522.184006 ,-38541.255303 ,-38383.665728 ,-39423.998130 ,-40489.466130 ,-41450.406768 ,-42355.156592 ,-43837.562085 ,-43677.262972 ,-41067.896944 ,-37238.628465 ,-32230.392026 ,-26762.766062 ,-20975.163308 ,-15019.218554 ,-9053.105545 ,-3059.663132 ,2772.399618 ,8242.538397 ,13407.752291 ,18016.047539 ,22292.125752 ,26616.583347 ,30502.564253 ,33153.890890 ,34216.684448 ,33394.220786 ,29657.417791 ,23064.375405 ,12040.831532 ,-2084.921068 ,-21390.235970 ,-38176.615985 ,-51647.714482 ,-59242.564959 ,-60263.150854 ,-58599.245165 ,-54804.972560 ,-50092.112608 ,-44465.812552 ,-38533.096297 ,-32747.104307 ,-27130.082610 ,-21529.632955 ,-15894.611939 ,-10457.566933 ,-5429.042583 ,-903.757828 ,2481.947589 ,5173.789976 ,8358.768202 ,11565.584635 ,14431.147931 ,16951.619820 ,18888.807708 ,20120.884465 ,20222.141242 ,18423.168124 ,16498.668271 ,14442.624242 ,14070.038273 ,16211.370808 ,19639.815904 ,24280.360465 ,29475.380079 ,35030.793540 ,40812.325095 ,46593.082382 ,52390.906885 ,58109.310860 ,63780.896094 ,68984.456561 ,72559.442320 ,74645.487900 ,74695.219755 ,72098.143876 ,66609.929889 ,56864.971296 ,41589.295266 ,19057.032104 ,-5951.329863 ,-34608.796853 ,-56603.801584 ,-72678.838057 ,-83297.070856 ,-90127.593511 ,-92656.040614 ,-91394.995510 ,-88192.056842 ,-83148.833075 ,-77582.587173 ,-71750.440823 ,-65765.369857 ,-59716.101820 ,-53613.430067 ,-47473.832358 ,-41287.031890 ,-35139.919259 ,-29097.671507 ,-23178.836760 ,-17486.807388 ,-12046.775779 ,-6802.483422 ,-1867.556171 ,2644.380534 ,6615.829501 ,10332.557518 ,13706.737038 ,17017.991307 ,20303.136670 ,23507.386461 ,26482.194102 ,29698.585356 ,33196.305757 ,37385.914179 ,42872.996212 ,48725.617879 ,54564.488527 ,60453.841604 ,66495.146265 ,72668.620416 ,78723.644870 ,84593.136677 ,89974.936239 ,93439.798630 ,95101.207834 ,94028.126381 ,89507.925620 ,80989.846001 ,66944.274744 ,47016.422041 ,19932.783790 ,-6198.433172 ,-32320.379400 ,-49822.852084 ,-60517.553414 ,-66860.548269 ,-70849.714105 ,-71058.721556 ,-67691.947812 ,-63130.703822 ,-57687.607311 ,-51916.952488 ,-45932.054982 ,-39834.909941 ,-33714.535713 ,-27564.443333 ,-21465.186188 ,-15469.326408 ,-9522.358787 ,-3588.742161 ,2221.802073 ,7758.244339 ,13020.269708 ,18198.562827 ,23211.338588 ,28051.699645 ,32708.577247 ,37413.795242 ,42181.401920 ,46462.499633 ,49849.582315 ,53026.578940 ,55930.600705 ,59432.642178 ,64027.356857 ,69126.843653 ,74620.328837 ,80372.056070 ,86348.152766 ,92468.907239 ,98568.998246 ,104669.511588 ,110445.790143 ,115394.348973 ,119477.553152 ,121528.574511 ,121973.674087 ,121048.017786 ,118021.473181 ,112151.993711 ,102195.999157 ,85972.731130 ,61224.719621 ,31949.279603 ,-3726.022971 ,-36485.298619 ,-67336.469799 ,-87799.366129 ,-98865.713558 ,-104103.651120 ,-105068.402300 ,-103415.820781 ,-99261.356633 ,-94281.850081 ,-88568.701325 ,-82625.711921 ,-76766.776770 ,-70998.803524 ,-65303.404499 ,-59719.198305 ,-54182.230439 ,-48662.904657 ,-43206.731668 ,-37732.701095 ,-32375.478519 ,-27167.508567 ,-22197.211891 ,-17722.869502 ,-13925.135219 ,-10737.893027 ,-8455.327914 ,-7067.008358 ,-7086.991191 ,-7527.693561 ,-8378.025732 ,-8629.383998 ,-7854.586079 ,-5853.040657 ,-1973.225485 ,2699.850783 ,8006.098287 ,13651.734934 ,19139.318072 ,24476.645420 ,29463.480336 ,33899.078820 ,37364.528796 ,38380.214949 ,37326.585649 ,33428.470616 ,27441.000494 ,21761.126583 ,15368.408081 ,7224.234078 ,-2702.217396 ,-14109.682505 ,-27390.915614 ,-38569.562393 ,-47875.155339 ,-53969.121872 ,-57703.473001 ,-57993.198171 ,-54908.391840 ,-50568.410328 ,-45247.622563 ,-39563.224328 ,-33637.786521 ,-27585.345413 ,-21572.074797 ,-15597.363909 ,-9577.429076 ,-3475.770622 ,2520.378408 ,8046.881775 ,13482.345595 ] - - beta_set = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26] - + pop = [ + 15.881351, + 15.881339, + 15.881320, + 15.881294, + 15.881261, + 15.881223, + 15.881180, + 15.881132, + 15.881079, + 15.881022, + 15.880961, + 15.880898, + 15.880832, + 15.880764, + 15.880695, + 15.880624, + 15.880553, + 15.880480, + 15.880409, + 15.880340, + 15.880270, + 15.880203, + 15.880138, + 15.880076, + 15.880016, + 15.879960, + 15.879907, + 15.879852, + 15.879799, + 15.879746, + 15.879693, + 15.879638, + 15.879585, + 15.879531, + 15.879477, + 15.879423, + 15.879370, + 15.879315, + 15.879262, + 15.879209, + 15.879155, + 15.879101, + 15.879048, + 15.878994, + 15.878940, + 15.878886, + 15.878833, + 15.878778, + 15.878725, + 15.878672, + 15.878618, + 15.878564, + 15.878510, + 15.878457, + 15.878402, + 15.878349, + 15.878295, + 15.878242, + 15.878187, + 15.878134, + 15.878081, + 15.878026, + 15.877973, + 15.877919, + 15.877864, + 15.877811, + 15.877758, + 15.877704, + 15.877650, + 15.877596, + 15.877543, + 15.877488, + 15.877435, + 15.877381, + 15.877326, + 15.877273, + 15.877220, + 15.877166, + 15.877111, + 15.877058, + 15.877005, + 15.876950, + 15.876896, + 15.876843, + 15.876789, + 15.876735, + 15.876681, + 15.876628, + 15.876573, + 15.876520, + 15.876466, + 15.876411, + 15.876358, + 15.876304, + 15.876251, + 15.876196, + 15.876143, + 15.876089, + 15.876034, + 15.875981, + 15.875927, + 15.875872, + 15.875819, + 15.875765, + 15.875712, + 15.875657, + 15.875604, + 15.875550, + 15.875495, + 15.875442, + 15.875388, + 15.875335, + 15.875280, + 15.875226, + 15.875173, + 15.875118, + 15.875064, + 15.875011, + 15.874956, + 15.874902, + 15.874849, + 15.874795, + 15.874740, + 15.874687, + 15.874633, + 15.874578, + 15.874525, + 15.874471, + 15.874416, + 15.874363, + 15.874309, + 15.874256, + 15.874201, + 15.874147, + 15.874094, + 15.874039, + 15.873985, + 15.873931, + 15.873878, + 15.873823, + 15.873769, + 15.873716, + 15.873661, + 15.873607, + 15.873554, + 15.873499, + 15.873445, + 15.873391, + 15.873338, + 15.873283, + 15.873229, + 15.873175, + 15.873121, + 15.873067, + 15.873013, + 15.872960, + 15.872905, + 15.872851, + 15.872797, + 15.872742, + 15.872689, + 15.872635, + 15.872580, + 15.872526, + 15.872473, + 15.872419, + 15.872364, + 15.872310, + 15.872256, + 15.872202, + 15.872148, + 15.872094, + 15.872039, + 15.871985, + 15.871932, + 15.871878, + 15.871823, + 15.871769, + 15.871715, + 15.871660, + 15.871607, + 15.871553, + 15.871499, + 15.871444, + 15.871390, + 15.871337, + 15.871282, + 15.871228, + 15.871174, + 15.871119, + 15.871065, + 15.871012, + 15.870958, + 15.870903, + 15.870849, + 15.870795, + 15.870740, + 15.870686, + 15.870633, + 15.870577, + 15.870524, + 15.870470, + 15.870416, + 15.870361, + 15.870307, + 15.870253, + 15.870198, + 15.870144, + 15.870091, + 15.870037, + 15.869982, + 15.869928, + 15.869874, + 15.869819, + 15.869765, + 15.869711, + 15.869656, + 15.869602, + 15.869548, + 15.869495, + 15.869439, + 15.869386, + 15.869332, + 15.869277, + 15.869223, + 15.869169, + 15.869114, + 15.869060, + 15.869006, + 15.868952, + 15.868897, + 15.868843, + 15.868789, + 15.868734, + 15.868679, + 15.868618, + 15.868556, + 15.868489, + 15.868421, + 15.868351, + 15.868280, + 15.868208, + 15.868134, + 15.868063, + 15.867991, + 15.867921, + 15.867852, + 15.867785, + 15.867721, + 15.867659, + 15.867601, + 15.867549, + 15.867499, + 15.867455, + 15.867416, + 15.867383, + 15.867357, + 15.867338, + 15.867327, + 15.867321, + 15.867327, + 15.867338, + 15.867359, + 15.867386, + 15.867419, + 15.867459, + 15.867505, + 15.867555, + 15.867610, + 15.867671, + 15.867734, + 15.867801, + 15.867869, + 15.867941, + 15.868012, + 15.868087, + 15.868161, + 15.868236, + 15.868310, + 15.868384, + 15.868457, + 15.868527, + 15.868595, + 15.868661, + 15.868722, + 15.868780, + 15.868837, + 15.868892, + 15.868948, + 15.869005, + 15.869061, + 15.869116, + 15.869173, + 15.869229, + 15.869284, + 15.869341, + 15.869397, + 15.869452, + 15.869509, + 15.869565, + 15.869620, + 15.869677, + 15.869733, + 15.869788, + 15.869845, + 15.869901, + 15.869956, + 15.870012, + 15.870069, + 15.870124, + 15.870180, + 15.870237, + 15.870292, + 15.870348, + 15.870405, + 15.870461, + 15.870516, + 15.870572, + 15.870629, + 15.870684, + 15.870740, + 15.870796, + 15.870851, + 15.870908, + 15.870964, + 15.871019, + 15.871076, + 15.871132, + 15.871187, + 15.871243, + 15.871300, + 15.871355, + 15.871411, + 15.871467, + 15.871522, + 15.871579, + 15.871635, + 15.871691, + 15.871746, + 15.871802, + 15.871859, + 15.871914, + 15.871970, + 15.872026, + 15.872081, + 15.872138, + 15.872194, + 15.872249, + 15.872305, + 15.872361, + 15.872416, + 15.872473, + 15.872529, + 15.872584, + 15.872640, + 15.872696, + 15.872751, + 15.872807, + 15.872864, + 15.872919, + 15.872975, + 15.873031, + 15.873087, + 15.873142, + 15.873198, + 15.873255, + 15.873310, + 15.873366, + 15.873422, + 15.873477, + 15.873533, + 15.873589, + 15.873644, + 15.873700, + 15.873757, + 15.873811, + 15.873868, + 15.873924, + 15.873979, + 15.874035, + 15.874091, + 15.874146, + 15.874202, + 15.874258, + 15.874313, + 15.874369, + 15.874425, + 15.874481, + 15.874536, + 15.874592, + ] + + logIstar = [ + 7.943245, + 8.269994, + 8.517212, + 8.814208, + 9.151740, + 9.478472, + 9.559847, + 9.664087, + 9.735378, + 9.852583, + 9.692265, + 9.498807, + 9.097634, + 8.388878, + 7.870516, + 7.012956, + 6.484941, + 5.825368, + 5.346815, + 5.548361, + 5.706732, + 5.712617, + 5.709714, + 5.696888, + 5.530087, + 5.826563, + 6.643563, + 7.004292, + 7.044663, + 7.190259, + 7.335926, + 7.516861, + 7.831779, + 8.188895, + 8.450204, + 8.801436, + 8.818379, + 8.787658, + 8.601685, + 8.258338, + 7.943364, + 7.425585, + 7.062834, + 6.658307, + 6.339600, + 6.526984, + 6.679178, + 6.988758, + 7.367331, + 7.746694, + 8.260558, + 8.676522, + 9.235582, + 9.607778, + 9.841917, + 10.081571, + 10.216090, + 10.350366, + 10.289668, + 10.248842, + 10.039504, + 9.846343, + 9.510392, + 9.190923, + 8.662465, + 7.743221, + 7.128458, + 5.967898, + 5.373883, + 5.097497, + 4.836570, + 5.203345, + 5.544798, + 5.443047, + 5.181152, + 5.508669, + 6.144130, + 6.413744, + 6.610423, + 6.748885, + 6.729511, + 6.789841, + 6.941034, + 7.093516, + 7.307039, + 7.541077, + 7.644803, + 7.769145, + 7.760187, + 7.708017, + 7.656795, + 7.664983, + 7.483828, + 6.887324, + 6.551093, + 6.457449, + 6.346064, + 6.486300, + 6.612378, + 6.778753, + 6.909477, + 7.360570, + 8.150303, + 8.549044, + 8.897572, + 9.239323, + 9.538751, + 9.876531, + 10.260911, + 10.613536, + 10.621510, + 10.661115, + 10.392899, + 10.065536, + 9.920090, + 9.933097, + 9.561691, + 8.807713, + 8.263463, + 7.252184, + 6.669083, + 5.877763, + 5.331878, + 5.356563, + 5.328469, + 5.631146, + 6.027497, + 6.250717, + 6.453919, + 6.718444, + 7.071636, + 7.348905, + 7.531528, + 7.798226, + 8.197941, + 8.578809, + 8.722964, + 8.901152, + 8.904370, + 8.889865, + 8.881902, + 8.958903, + 8.721281, + 8.211509, + 7.810624, + 7.164607, + 6.733688, + 6.268503, + 5.905983, + 5.900432, + 5.846547, + 6.245427, + 6.786271, + 7.088480, + 7.474295, + 7.650063, + 7.636703, + 7.830990, + 8.231516, + 8.584816, + 8.886908, + 9.225216, + 9.472778, + 9.765505, + 9.928623, + 10.153033, + 10.048574, + 9.892620, + 9.538818, + 8.896100, + 8.437584, + 7.819738, + 7.362598, + 6.505880, + 5.914972, + 6.264584, + 6.555019, + 6.589319, + 6.552029, + 6.809771, + 7.187616, + 7.513918, + 8.017712, + 8.224957, + 8.084474, + 8.079148, + 8.180991, + 8.274269, + 8.413748, + 8.559599, + 8.756090, + 9.017927, + 9.032720, + 9.047983, + 8.826873, + 8.366489, + 8.011876, + 7.500830, + 7.140406, + 6.812626, + 6.538719, + 6.552218, + 6.540129, + 6.659927, + 6.728530, + 7.179692, + 7.989210, + 8.399173, + 8.781128, + 9.122303, + 9.396378, + 9.698512, + 9.990104, + 10.276543, + 10.357284, + 10.465869, + 10.253833, + 10.018503, + 9.738407, + 9.484367, + 9.087025, + 8.526409, + 8.041126, + 7.147168, + 6.626706, + 6.209446, + 5.867231, + 5.697439, + 5.536769, + 5.421413, + 5.238297, + 5.470136, + 5.863007, + 6.183083, + 6.603569, + 6.906278, + 7.092324, + 7.326612, + 7.576052, + 7.823430, + 7.922775, + 8.041677, + 8.063403, + 8.073229, + 8.099726, + 8.168522, + 8.099041, + 8.011404, + 7.753147, + 6.945211, + 6.524244, + 6.557723, + 6.497742, + 6.256247, + 5.988794, + 6.268093, + 6.583316, + 7.106842, + 8.053929, + 8.508237, + 8.938915, + 9.311863, + 9.619753, + 9.931745, + 10.182361, + 10.420978, + 10.390829, + 10.389230, + 10.079342, + 9.741479, + 9.444561, + 9.237448, + 8.777687, + 7.976436, + 7.451502, + 6.742856, + 6.271545, + 5.782289, + 5.403089, + 5.341954, + 5.243509, + 5.522993, + 5.897001, + 6.047042, + 6.100738, + 6.361727, + 6.849562, + 7.112544, + 7.185346, + 7.309412, + 7.423746, + 7.532142, + 7.510318, + 7.480175, + 7.726362, + 8.061117, + 8.127072, + 8.206166, + 8.029634, + 7.592953, + 7.304869, + 7.005394, + 6.750019, + 6.461377, + 6.226432, + 6.287047, + 6.306452, + 6.783694, + 7.450957, + 7.861692, + 8.441530, + 8.739626, + 8.921994, + 9.168961, + 9.428077, + 9.711664, + 10.032714, + 10.349937, + 10.483985, + 10.647475, + 10.574038, + 10.522431, + 10.192246, + 9.756246, + 9.342511, + 8.872072, + 8.414189, + 7.606582, + 7.084701, + 6.149903, + 5.517257, + 5.839429, + 6.098090, + 6.268935, + 6.475965, + 6.560543, + 6.598942, + 6.693938, + 6.802531, + 6.934345, + 7.078370, + 7.267736, + 7.569640, + 7.872204, + 8.083603, + 8.331226, + 8.527144, + 8.773523, + 8.836599, + 8.894303, + 8.808326, + 8.641717, + 8.397901, + 7.849034, + 7.482899, + 7.050252, + 6.714103, + 6.900603, + 7.050765, + 7.322905, + 7.637986, + 8.024340, + 8.614505, + 8.933591, + 9.244008, + 9.427410, + 9.401385, + 9.457744, + 9.585068, + 9.699673, + 9.785478, + 9.884559, + 9.769732, + 9.655075, + 9.423071, + 9.210198, + 8.786654, + 8.061787, + 7.560976, + 6.855829, + 6.390707, + 5.904006, + 5.526631, + 5.712303, + 5.867027, + 5.768367, + 5.523352, + 5.909118, + 6.745543, + 6.859218, + ] + + deltaS = [ + 9916.490263, + 12014.263380, + 13019.275755, + 12296.373612, + 8870.995603, + 1797.354574, + -6392.880771, + -16150.825387, + -27083.245106, + -40130.421462, + -50377.169958, + -57787.717468, + -60797.223427, + -59274.041897, + -55970.213230, + -51154.650927, + -45877.841034, + -40278.553775, + -34543.967175, + -28849.633641, + -23192.776605, + -17531.130740, + -11862.021829, + -6182.456792, + -450.481090, + 5201.184400, + 10450.773882, + 15373.018272, + 20255.699431, + 24964.431669, + 29470.745887, + 33678.079947, + 37209.808930, + 39664.432393, + 41046.735479, + 40462.982011, + 39765.070209, + 39270.815830, + 39888.077002, + 42087.276604, + 45332.012929, + 49719.128772, + 54622.190928, + 59919.718626, + 65436.341097, + 70842.911460, + 76143.747430, + 81162.358574, + 85688.102884, + 89488.917734, + 91740.108470, + 91998.787916, + 87875.986012, + 79123.877908, + 66435.611045, + 48639.250610, + 27380.282817, + 2166.538464, + -21236.428084, + -43490.803535, + -60436.624080, + -73378.401966, + -80946.278268, + -84831.969493, + -84696.627286, + -81085.365407, + -76410.847049, + -70874.415387, + -65156.276464, + -59379.086883, + -53557.267619, + -47784.164830, + -42078.001172, + -36340.061427, + -30541.788202, + -24805.281435, + -19280.817165, + -13893.690606, + -8444.172221, + -3098.160839, + 2270.908649, + 7594.679295, + 12780.079247, + 17801.722109, + 22543.091206, + 26897.369814, + 31051.285734, + 34933.809557, + 38842.402859, + 42875.230152, + 47024.395356, + 51161.516122, + 55657.298307, + 60958.155424, + 66545.635029, + 72202.930397, + 77934.761905, + 83588.207792, + 89160.874522, + 94606.115027, + 99935.754968, + 104701.404975, + 107581.670606, + 108768.440311, + 107905.700480, + 104062.148863, + 96620.281684, + 83588.443029, + 61415.088182, + 27124.031692, + -7537.285321, + -43900.451653, + -70274.062783, + -87573.481475, + -101712.148408, + -116135.719087, + -124187.225446, + -124725.278371, + -122458.145590, + -117719.918256, + -112352.138605, + -106546.806030, + -100583.803012, + -94618.253238, + -88639.090897, + -82725.009842, + -76938.910669, + -71248.957807, + -65668.352795, + -60272.761991, + -55179.538428, + -50456.021161, + -46037.728058, + -42183.912670, + -39522.184006, + -38541.255303, + -38383.665728, + -39423.998130, + -40489.466130, + -41450.406768, + -42355.156592, + -43837.562085, + -43677.262972, + -41067.896944, + -37238.628465, + -32230.392026, + -26762.766062, + -20975.163308, + -15019.218554, + -9053.105545, + -3059.663132, + 2772.399618, + 8242.538397, + 13407.752291, + 18016.047539, + 22292.125752, + 26616.583347, + 30502.564253, + 33153.890890, + 34216.684448, + 33394.220786, + 29657.417791, + 23064.375405, + 12040.831532, + -2084.921068, + -21390.235970, + -38176.615985, + -51647.714482, + -59242.564959, + -60263.150854, + -58599.245165, + -54804.972560, + -50092.112608, + -44465.812552, + -38533.096297, + -32747.104307, + -27130.082610, + -21529.632955, + -15894.611939, + -10457.566933, + -5429.042583, + -903.757828, + 2481.947589, + 5173.789976, + 8358.768202, + 11565.584635, + 14431.147931, + 16951.619820, + 18888.807708, + 20120.884465, + 20222.141242, + 18423.168124, + 16498.668271, + 14442.624242, + 14070.038273, + 16211.370808, + 19639.815904, + 24280.360465, + 29475.380079, + 35030.793540, + 40812.325095, + 46593.082382, + 52390.906885, + 58109.310860, + 63780.896094, + 68984.456561, + 72559.442320, + 74645.487900, + 74695.219755, + 72098.143876, + 66609.929889, + 56864.971296, + 41589.295266, + 19057.032104, + -5951.329863, + -34608.796853, + -56603.801584, + -72678.838057, + -83297.070856, + -90127.593511, + -92656.040614, + -91394.995510, + -88192.056842, + -83148.833075, + -77582.587173, + -71750.440823, + -65765.369857, + -59716.101820, + -53613.430067, + -47473.832358, + -41287.031890, + -35139.919259, + -29097.671507, + -23178.836760, + -17486.807388, + -12046.775779, + -6802.483422, + -1867.556171, + 2644.380534, + 6615.829501, + 10332.557518, + 13706.737038, + 17017.991307, + 20303.136670, + 23507.386461, + 26482.194102, + 29698.585356, + 33196.305757, + 37385.914179, + 42872.996212, + 48725.617879, + 54564.488527, + 60453.841604, + 66495.146265, + 72668.620416, + 78723.644870, + 84593.136677, + 89974.936239, + 93439.798630, + 95101.207834, + 94028.126381, + 89507.925620, + 80989.846001, + 66944.274744, + 47016.422041, + 19932.783790, + -6198.433172, + -32320.379400, + -49822.852084, + -60517.553414, + -66860.548269, + -70849.714105, + -71058.721556, + -67691.947812, + -63130.703822, + -57687.607311, + -51916.952488, + -45932.054982, + -39834.909941, + -33714.535713, + -27564.443333, + -21465.186188, + -15469.326408, + -9522.358787, + -3588.742161, + 2221.802073, + 7758.244339, + 13020.269708, + 18198.562827, + 23211.338588, + 28051.699645, + 32708.577247, + 37413.795242, + 42181.401920, + 46462.499633, + 49849.582315, + 53026.578940, + 55930.600705, + 59432.642178, + 64027.356857, + 69126.843653, + 74620.328837, + 80372.056070, + 86348.152766, + 92468.907239, + 98568.998246, + 104669.511588, + 110445.790143, + 115394.348973, + 119477.553152, + 121528.574511, + 121973.674087, + 121048.017786, + 118021.473181, + 112151.993711, + 102195.999157, + 85972.731130, + 61224.719621, + 31949.279603, + -3726.022971, + -36485.298619, + -67336.469799, + -87799.366129, + -98865.713558, + -104103.651120, + -105068.402300, + -103415.820781, + -99261.356633, + -94281.850081, + -88568.701325, + -82625.711921, + -76766.776770, + -70998.803524, + -65303.404499, + -59719.198305, + -54182.230439, + -48662.904657, + -43206.731668, + -37732.701095, + -32375.478519, + -27167.508567, + -22197.211891, + -17722.869502, + -13925.135219, + -10737.893027, + -8455.327914, + -7067.008358, + -7086.991191, + -7527.693561, + -8378.025732, + -8629.383998, + -7854.586079, + -5853.040657, + -1973.225485, + 2699.850783, + 8006.098287, + 13651.734934, + 19139.318072, + 24476.645420, + 29463.480336, + 33899.078820, + 37364.528796, + 38380.214949, + 37326.585649, + 33428.470616, + 27441.000494, + 21761.126583, + 15368.408081, + 7224.234078, + -2702.217396, + -14109.682505, + -27390.915614, + -38569.562393, + -47875.155339, + -53969.121872, + -57703.473001, + -57993.198171, + -54908.391840, + -50568.410328, + -45247.622563, + -39563.224328, + -33637.786521, + -27585.345413, + -21572.074797, + -15597.363909, + -9577.429076, + -3475.770622, + 2520.378408, + 8046.881775, + 13482.345595, + ] + + beta_set = [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + ] # from new_data_set import * # Uncomment this line to use new data set # declare model name - model = ConcreteModel() + model = ConcreteModel('SIR Disease Model') # declare constants bpy = 26 # biweeks per year diff --git a/gdplib/jobshop/jobshop.py b/gdplib/jobshop/jobshop.py index 1e58752..d4d0dd6 100644 --- a/gdplib/jobshop/jobshop.py +++ b/gdplib/jobshop/jobshop.py @@ -56,7 +56,7 @@ def build_model(): Raman & Grossmann, Modelling and computational techniques for logic based integer programming, Computers and Chemical Engineering 18, 7, p.563-578, 1994. Aldo Vecchietti, LogMIP User's Manual, http://www.logmip.ceride.gov.ar/, 2007 """ - model = AbstractModel() + model = AbstractModel('Jobshop Scheduling Model') model.JOBS = Set(ordered=True, doc='Set of jobs') model.STAGES = Set(ordered=True, doc='Set of stages') diff --git a/gdplib/med_term_purchasing/med_term_purchasing.py b/gdplib/med_term_purchasing/med_term_purchasing.py index 6d12e2d..16ad6f8 100644 --- a/gdplib/med_term_purchasing/med_term_purchasing.py +++ b/gdplib/med_term_purchasing/med_term_purchasing.py @@ -46,7 +46,7 @@ def build_model(): [1] Vecchietti, A., & Grossmann, I. (2004). Computational experience with logmip solving linear and nonlinear disjunctive programming problems. Proc. of FOCAPD, 587-590. [2] Park, M., Park, S., Mele, F. D., & Grossmann, I. E. (2006). Modeling of purchase and sales contracts in supply chain optimization. Industrial and Engineering Chemistry Research, 45(14), 5013-5026. DOI: 10.1021/ie0513144 """ - model = AbstractModel() + model = AbstractModel("Medium-term Purchasing Contracts Problem") # Constants (data that was hard-coded in GAMS model) AMOUNT_UB = 1000 From c8ec67e2107c619f3816cf2a3adcd6b37b8cd3b4 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 7 May 2024 15:49:45 -0400 Subject: [PATCH 32/60] conditional tray mass balance documentation --- gdplib/gdp_col/column.py | 115 +++++++++++++++++++++------------------ 1 file changed, 62 insertions(+), 53 deletions(-) diff --git a/gdplib/gdp_col/column.py b/gdplib/gdp_col/column.py index 123851c..7f807d9 100644 --- a/gdplib/gdp_col/column.py +++ b/gdplib/gdp_col/column.py @@ -235,7 +235,7 @@ def monotonoic_temperature(_, t): Parameters ---------- - _ : Pyomo.core.base.constraint.IndexedConstraint + _ : Pyomo.ConcreteModel An unused placeholder parameter required by Pyomo's constraint interface, representing each potential tray in the distillation column where the temperature constraint is applied. t : int Index of tray in the distillation column model. @@ -418,7 +418,8 @@ def tray_ordering(m, t): def _build_conditional_tray_mass_balance(m, t, tray, no_tray): - """_summary_ + """ + Builds the constraints for mass balance, liquid and vapor composition for a given tray (t) in the distillation column. Parameters ---------- @@ -434,23 +435,24 @@ def _build_conditional_tray_mass_balance(m, t, tray, no_tray): Returns ------- None - _description_ + None, but the mass balance constraints for the conditional tray are added to the Pyomo model. """ @tray.Constraint(m.comps) def mass_balance(_, c): - """_summary_ + """ + Mass balance on each component on a tray. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + An unused placeholder, typically used to represent the model instance when defining constraints within a method, but not utilized in this specific constraint function. This placeholder denotes the conditional trays in the distillation column model. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Constraint that the mass balance on each component on a tray is equal to the sum of the feed in, vapor from the tray, liquid from the tray above, liquid to the tray below, and vapor from the tray below. """ return ( # Feed in if feed tray @@ -470,109 +472,115 @@ def mass_balance(_, c): @tray.Constraint(m.comps) def tray_liquid_composition(_, c): - """_summary_ + """ + Liquid composition constraint for the tray. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + An unused placeholder denoting the conditional trays in the distillation column model. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Constraint that the liquid flow rate for each component is equal to the liquid flow rate on the tray times the liquid composition on the tray. """ return m.L[c, t] == m.liq[t] * m.x[c, t] @tray.Constraint(m.comps) def tray_vapor_compositions(_, c): - """_summary_ + """ + Vapor composition constraint for the tray. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + An unused placeholder denoting the conditional trays in the distillation column model. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Constraint that the vapor flow rate for each component is equal to the vapor flow rate on the tray times the vapor composition on the tray. """ return m.V[c, t] == m.vap[t] * m.y[c, t] @no_tray.Constraint(m.comps) def liq_comp_pass_through(_, c): - """_summary_ + """ + Liquid composition constraint for the case when the tray does not exist. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + An unused placeholder denoting the conditional trays in the distillation column model. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Constraint that the liquid composition is equal to the liquid composition on the tray above, when the tray is not present. """ return m.x[c, t] == m.x[c, t + 1] @no_tray.Constraint(m.comps) def liq_flow_pass_through(_, c): - """_summary_ + """ + Liquid flow rate constraint for the case when the tray does not exist. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + An unused placeholder denoting the conditional trays in the distillation column model. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Constraint that the liquid flow rate is equal to the liquid flow rate on the tray above, when the tray is not present. """ return m.L[c, t] == m.L[c, t + 1] @no_tray.Constraint(m.comps) def vap_comp_pass_through(_, c): - """_summary_ + """ + Vapor composition constraint for the case when the tray does not exist. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + An unused placeholder denoting the conditional trays in the distillation column model. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Constraint that the vapor composition is equal to the vapor composition on the tray below, when the tray is not present. """ return m.y[c, t] == m.y[c, t - 1] @no_tray.Constraint(m.comps) def vap_flow_pass_through(_, c): - """_summary_ + """ + Vapor flow rate constraint for the case when the tray does not exist. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + An unused placeholder denoting the conditional trays in the distillation column model. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Constraint that the vapor flow rate is equal to the vapor flow rate on the tray below, when the tray is not present. """ return m.V[c, t] == m.V[c, t - 1] @@ -1128,7 +1136,8 @@ def vap_enthalpy_pass_through(_, c): def _build_feed_tray_energy_balance(m): - """_summary_ + """ + Energy balance for the feed tray. Parameters ---------- @@ -1137,8 +1146,8 @@ def _build_feed_tray_energy_balance(m): Returns ------- - _type_ - _description_ + None + None, but adds constraints, which are energy balances for the feed tray, to the Pyomo model of the distillation column """ t = m.feed_tray @@ -1153,7 +1162,7 @@ def feed_tray_energy_balance(_): Returns ------- - _type_ + Pyomo.Constraint _description_ """ return ( @@ -1185,7 +1194,7 @@ def feed_tray_liq_enthalpy_calc(_, c): Returns ------- - _type_ + Pyomo.Constraint _description_ """ return m.H_L[c, t] == m.liq_enthalpy_expr[t, c] @@ -1203,7 +1212,7 @@ def feed_tray_vap_enthalpy_calc(_, c): Returns ------- - _type_ + Pyomo.Constraint _description_ """ return m.H_V[c, t] == m.vap_enthalpy_expr[t, c] @@ -1221,7 +1230,7 @@ def feed_liq_enthalpy_expr(_, c): Returns ------- - _type_ + Pyomo.Expression _description_ """ k = m.liq_Cp_const[c] @@ -1245,7 +1254,7 @@ def feed_liq_enthalpy_calc(_, c): Returns ------- - _type_ + Pyomo.Constraint _description_ """ return m.H_L_spec_feed[c] == m.feed_liq_enthalpy_expr[c] @@ -1263,7 +1272,7 @@ def feed_vap_enthalpy_expr(_, c): Returns ------- - _type_ + Pyomo.Expression _description_ """ k = m.vap_Cp_const[c] @@ -1288,7 +1297,7 @@ def feed_vap_enthalpy_calc(_, c): Returns ------- - _type_ + Pyomo.Constraint _description_ """ return m.H_V_spec_feed[c] == m.feed_vap_enthalpy_expr[c] @@ -1397,8 +1406,8 @@ def _build_reboiler_energy_balance(m): Returns ------- - _type_ - _description_ + None + None, but adds constraints, which are energy balances for the reboiler, to the Pyomo model of the distillation column """ t = m.reboil_tray @@ -1413,7 +1422,7 @@ def reboiler_energy_balance(_): Returns ------- - _type_ + Pyomo.Constraint _description_ """ return m.Qb + sum( @@ -1435,7 +1444,7 @@ def reboiler_liq_enthalpy_calc(_, c): Returns ------- - _type_ + Pyomo.Constraint _description_ """ return m.H_L[c, t] == m.liq_enthalpy_expr[t, c] @@ -1453,7 +1462,7 @@ def reboiler_vap_enthalpy_calc(_, c): Returns ------- - _type_ + Pyomo.Constraint _description_ """ return m.H_V[c, t] == m.vap_enthalpy_expr[t, c] From 99d74e771fac9c4096d20a60c35414011683fd64 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 7 May 2024 17:09:37 -0400 Subject: [PATCH 33/60] add github Lint workflow --- .github/workflows/black.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/black.yml diff --git a/.github/workflows/black.yml b/.github/workflows/black.yml new file mode 100644 index 0000000..481c5d9 --- /dev/null +++ b/.github/workflows/black.yml @@ -0,0 +1,17 @@ +name: Lint + +on: [push, pull_request] + +jobs: + lint: + name: lint/style-and-typos + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Black Formatting Check + uses: psf/black@stable + with: + args: . -S -C --check --diff + - name: Spell Check + uses: crate-ci/typos@master + From faca3164c5ce4f2f01a44944c24274f14309ba7f Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 7 May 2024 17:21:03 -0400 Subject: [PATCH 34/60] rename black.yml to lint.yml --- .github/workflows/{black.yml => lint.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{black.yml => lint.yml} (100%) diff --git a/.github/workflows/black.yml b/.github/workflows/lint.yml similarity index 100% rename from .github/workflows/black.yml rename to .github/workflows/lint.yml From 731ed1a2beffe00dd46fa9630b5cc1f5c1f19dd5 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 7 May 2024 17:30:49 -0400 Subject: [PATCH 35/60] separate benchmark and mode size report code --- benchmark.py | 11 ----------- generate_model_size_report.py | 36 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 11 deletions(-) create mode 100644 generate_model_size_report.py diff --git a/benchmark.py b/benchmark.py index 5820997..381ab5b 100644 --- a/benchmark.py +++ b/benchmark.py @@ -100,17 +100,6 @@ def benchmark(model, strategy, timelimit, result_dir): os.makedirs(result_dir, exist_ok=True) model = import_module("gdplib." + instance).build_model() - report = build_model_size_report(model) - report_df = pd.DataFrame(report.overall, index=[0]).T - report_df.index.name = "Component" - report_df.columns = ["Number"] - # Generate the model size report (Markdown) - report_df.to_markdown("gdplib/" + instance + "/" + "model_size_report.md") - - # Generate the model size report (JSON) - # TODO: check if we need the json file. - with open("gdplib/" + instance + "/" + "model_size_report.json", "w") as f: - json.dump(report, f) for strategy in strategy_list: benchmark(model, strategy, timelimit, result_dir) diff --git a/generate_model_size_report.py b/generate_model_size_report.py new file mode 100644 index 0000000..96ddac0 --- /dev/null +++ b/generate_model_size_report.py @@ -0,0 +1,36 @@ +from datetime import datetime +from importlib import import_module +from pyomo.util.model_size import build_model_size_report +import pandas as pd + + +if __name__ == "__main__": + instance_list = [ + # "batch_processing", + # "biofuel", + # "disease_model", + # "gdp_col", + # "hda", + "jobshop", + # "kaibel", + # "logical", + # "med_term_purchasing", + # "methanol", + # "mod_hens", + # "modprodnet", + # "stranded_gas", + # "syngas", + ] + current_time = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + timelimit = 600 + + for instance in instance_list: + print("Generating model size report: " + instance) + + model = import_module("gdplib." + instance).build_model() + report = build_model_size_report(model) + report_df = pd.DataFrame(report.overall, index=[0]).T + report_df.index.name = "Component" + report_df.columns = ["Number"] + # Generate the model size report (Markdown) + report_df.to_markdown("gdplib/" + instance + "/" + "model_size_report.md") From 6ed81c21bff47cb2b0c488c55ad1993ec0d15088 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 7 May 2024 17:41:14 -0400 Subject: [PATCH 36/60] update benchmark code --- benchmark.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/benchmark.py b/benchmark.py index 381ab5b..95983d7 100644 --- a/benchmark.py +++ b/benchmark.py @@ -1,20 +1,17 @@ -from pyomo.environ import * import os import json import time import sys from datetime import datetime -from gdplib.jobshop.jobshop import build_model from importlib import import_module -from pyomo.util.model_size import build_model_size_report -import pandas as pd - -subsolver = "scip" +from pyomo.environ import * -def benchmark(model, strategy, timelimit, result_dir): +def benchmark(model, strategy, timelimit, result_dir, subsolver="scip"): """Benchmark the model using the given strategy and subsolver. + The result files include the solver output and the JSON representation of the results. + Parameters ---------- model : PyomoModel @@ -25,6 +22,10 @@ def benchmark(model, strategy, timelimit, result_dir): the time limit for the solver result_dir : string the directory to store the benchmark results + + Returns + ------- + None """ model = model.clone() stdout = sys.stdout @@ -35,7 +36,9 @@ def benchmark(model, strategy, timelimit, result_dir): with open( result_dir + "/" + strategy + "_" + subsolver + ".log", "w" ) as sys.stdout: - results = SolverFactory("scip").solve(model, tee=True, timelimit=timelimit) + results = SolverFactory(subsolver).solve( + model, tee=True, timelimit=timelimit + ) results.solver.transformation_time = ( transformation_end_time - transformation_start_time ) @@ -53,10 +56,10 @@ def benchmark(model, strategy, timelimit, result_dir): results = SolverFactory(strategy).solve( model, tee=True, - nlp_solver="scip", - mip_solver="scip", - minlp_solver="scip", - local_minlp_solver="scip", + nlp_solver=subsolver, + mip_solver=subsolver, + minlp_solver=subsolver, + local_minlp_solver=subsolver, time_limit=timelimit, ) print(results) @@ -64,6 +67,7 @@ def benchmark(model, strategy, timelimit, result_dir): sys.stdout = stdout with open(result_dir + "/" + strategy + "_" + subsolver + ".json", "w") as f: json.dump(results.json_repn(), f) + return None if __name__ == "__main__": From 450e7ef3db779808fa0ed04a88842cc07863b58c Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 8 May 2024 09:21:26 -0400 Subject: [PATCH 37/60] update benchmark code --- benchmark.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark.py b/benchmark.py index 95983d7..1bb9df8 100644 --- a/benchmark.py +++ b/benchmark.py @@ -100,7 +100,7 @@ def benchmark(model, strategy, timelimit, result_dir, subsolver="scip"): for instance in instance_list: print("Benchmarking instance: " + instance) - result_dir = "gdplib/" + instance + "/benchmark_result/" + current_time + result_dir = "gdplib/" + instance + "/benchmark_result/" os.makedirs(result_dir, exist_ok=True) model = import_module("gdplib." + instance).build_model() From 79ba308900cdf8befa668ef5a96cc4f7d5c87cd1 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 8 May 2024 09:23:30 -0400 Subject: [PATCH 38/60] remove example result file --- .gitignore | 2 ++ .../2024-05-06_21-01-21/gdp.bigm_scip.json | 1 - .../2024-05-06_21-01-21/gdp.hull_scip.json | 1 - .../2024-05-06_21-01-21/gdpopt.enumerate_scip.json | 1 - .../2024-05-06_21-01-21/gdpopt.gloa_scip.json | 1 - .../2024-05-06_21-01-21/gdpopt.loa_scip.json | 1 - .../2024-05-06_21-01-21/gdpopt.ric_scip.json | 1 - gdplib/jobshop/model_size_report.json | 1 - gdplib/jobshop/model_size_report.md | 10 ---------- 9 files changed, 2 insertions(+), 17 deletions(-) delete mode 100644 gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdp.bigm_scip.json delete mode 100644 gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdp.hull_scip.json delete mode 100644 gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.enumerate_scip.json delete mode 100644 gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.gloa_scip.json delete mode 100644 gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.loa_scip.json delete mode 100644 gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.ric_scip.json delete mode 100644 gdplib/jobshop/model_size_report.json delete mode 100644 gdplib/jobshop/model_size_report.md diff --git a/.gitignore b/.gitignore index 9b8da19..b9c4f32 100644 --- a/.gitignore +++ b/.gitignore @@ -130,3 +130,5 @@ dmypy.json # Pycharm .idea/ + +gdplib/*/benchmark_result/ diff --git a/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdp.bigm_scip.json b/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdp.bigm_scip.json deleted file mode 100644 index 04f3905..0000000 --- a/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdp.bigm_scip.json +++ /dev/null @@ -1 +0,0 @@ -{"Problem": [{"Lower bound": -Infinity, "Upper bound": 11.0, "Number of objectives": 1, "Number of constraints": 0, "Number of variables": 10, "Sense": "unknown"}], "Solver": [{"Status": "ok", "Message": "optimal solution found", "Termination condition": "optimal", "Id": 0, "Error rc": 0, "Time": 0.04, "Gap": 0.0, "Primal bound": 11.0, "Dual bound": 11.0, "Transformation time": 0.003754854202270508}], "Solution": [{"number of solutions": 0, "number of solutions displayed": 0}]} \ No newline at end of file diff --git a/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdp.hull_scip.json b/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdp.hull_scip.json deleted file mode 100644 index c57115b..0000000 --- a/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdp.hull_scip.json +++ /dev/null @@ -1 +0,0 @@ -{"Problem": [{"Lower bound": -Infinity, "Upper bound": 11.0, "Number of objectives": 1, "Number of constraints": 0, "Number of variables": 22, "Sense": "unknown"}], "Solver": [{"Status": "ok", "Message": "optimal solution found", "Termination condition": "optimal", "Id": 0, "Error rc": 0, "Time": 0.01, "Gap": 0.0, "Primal bound": 11.0, "Dual bound": 11.0, "Transformation time": 0.008002042770385742}], "Solution": [{"number of solutions": 0, "number of solutions displayed": 0}]} \ No newline at end of file diff --git a/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.enumerate_scip.json b/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.enumerate_scip.json deleted file mode 100644 index b09a6f2..0000000 --- a/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.enumerate_scip.json +++ /dev/null @@ -1 +0,0 @@ -{"Problem": [{"Name": "unknown", "Lower bound": 11.0, "Upper bound": 11.0, "Number of objectives": 1, "Number of constraints": 9, "Number of variables": 10, "Number of binary variables": 6, "Number of integer variables": 0, "Number of continuous variables": 4, "Number of nonzeros": null, "Sense": 1, "Number of disjunctions": 3}], "Solver": [{"Name": "GDPopt (22, 5, 13) - enumerate", "Status": "ok", "User time": 0.3502802930015605, "Wallclock time": 0.3502802930015605, "Termination condition": "optimal", "Iterations": 8, "Timing": {"main_timer_start_time": 123407.445576859, "nlp": 0.34058500100218225, "total": 0.3502802930015605}}]} \ No newline at end of file diff --git a/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.gloa_scip.json b/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.gloa_scip.json deleted file mode 100644 index 7464bb2..0000000 --- a/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.gloa_scip.json +++ /dev/null @@ -1 +0,0 @@ -{"Problem": [{"Name": "unknown", "Lower bound": 11.0, "Upper bound": 11.0, "Number of objectives": 1, "Number of constraints": 9, "Number of variables": 10, "Number of binary variables": 6, "Number of integer variables": 0, "Number of continuous variables": 4, "Number of nonzeros": null, "Sense": 1, "Number of disjunctions": 3}], "Solver": [{"Name": "GDPopt (22, 5, 13) - GLOA", "Status": "ok", "User time": 0.1213786309963325, "Wallclock time": 0.1213786309963325, "Termination condition": "optimal", "Iterations": 1, "Timing": {"main_timer_start_time": 123407.944400965, "mip": 0.05432544200448319, "nlp": 0.05131585600611288, "integer cut generation": 0.000397921001422219, "total": 0.1213786309963325}}]} \ No newline at end of file diff --git a/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.loa_scip.json b/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.loa_scip.json deleted file mode 100644 index 06a1841..0000000 --- a/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.loa_scip.json +++ /dev/null @@ -1 +0,0 @@ -{"Problem": [{"Name": "unknown", "Lower bound": 11.0, "Upper bound": 11.0, "Number of objectives": 1, "Number of constraints": 9, "Number of variables": 10, "Number of binary variables": 6, "Number of integer variables": 0, "Number of continuous variables": 4, "Number of nonzeros": null, "Sense": 1, "Number of disjunctions": 3}], "Solver": [{"Name": "GDPopt (22, 5, 13) - LOA", "Status": "ok", "User time": 0.1308980710018659, "Wallclock time": 0.1308980710018659, "Termination condition": "optimal", "Iterations": 1, "Timing": {"main_timer_start_time": 123407.805129371, "mip": 0.06084998599544633, "nlp": 0.054532527006813325, "integer cut generation": 0.0005358900089049712, "total": 0.1308980710018659}}]} \ No newline at end of file diff --git a/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.ric_scip.json b/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.ric_scip.json deleted file mode 100644 index fa7bad2..0000000 --- a/gdplib/jobshop/benchmark_result/2024-05-06_21-01-21/gdpopt.ric_scip.json +++ /dev/null @@ -1 +0,0 @@ -{"Problem": [{"Name": "unknown", "Lower bound": 11.0, "Upper bound": 11.0, "Number of objectives": 1, "Number of constraints": 9, "Number of variables": 10, "Number of binary variables": 6, "Number of integer variables": 0, "Number of continuous variables": 4, "Number of nonzeros": null, "Sense": 1, "Number of disjunctions": 3}], "Solver": [{"Name": "GDPopt (22, 5, 13) - RIC", "Status": "ok", "User time": 0.13380873500136659, "Wallclock time": 0.13380873500136659, "Termination condition": "optimal", "Iterations": 1, "Timing": {"main_timer_start_time": 123408.074596677, "mip": 0.05659815100079868, "nlp": 0.06265952499234118, "integer cut generation": 0.0004167870065430179, "total": 0.13380873500136659}}]} \ No newline at end of file diff --git a/gdplib/jobshop/model_size_report.json b/gdplib/jobshop/model_size_report.json deleted file mode 100644 index 138d2a1..0000000 --- a/gdplib/jobshop/model_size_report.json +++ /dev/null @@ -1 +0,0 @@ -{"activated": {"variables": 10, "binary_variables": 6, "integer_variables": 0, "continuous_variables": 4, "disjunctions": 3, "disjuncts": 6, "constraints": 9, "nonlinear_constraints": 0}, "overall": {"variables": 10, "binary_variables": 6, "integer_variables": 0, "continuous_variables": 4, "disjunctions": 3, "disjuncts": 6, "constraints": 9, "nonlinear_constraints": 0}, "warning": {"unassociated_disjuncts": 0}} \ No newline at end of file diff --git a/gdplib/jobshop/model_size_report.md b/gdplib/jobshop/model_size_report.md deleted file mode 100644 index 3d4d37e..0000000 --- a/gdplib/jobshop/model_size_report.md +++ /dev/null @@ -1,10 +0,0 @@ -| Component | Number | -|:----------------------|---------:| -| variables | 10 | -| binary_variables | 6 | -| integer_variables | 0 | -| continuous_variables | 4 | -| disjunctions | 3 | -| disjuncts | 6 | -| constraints | 9 | -| nonlinear_constraints | 0 | \ No newline at end of file From 15eb16943ddf329512e0aaac014389060e957f05 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Wed, 8 May 2024 20:27:22 -0400 Subject: [PATCH 39/60] Documented the constraints of the column.py model. --- gdplib/gdp_col/column.py | 492 +++++++++++++++++++++++---------------- 1 file changed, 292 insertions(+), 200 deletions(-) diff --git a/gdplib/gdp_col/column.py b/gdplib/gdp_col/column.py index 7f807d9..d663461 100644 --- a/gdplib/gdp_col/column.py +++ b/gdplib/gdp_col/column.py @@ -427,9 +427,9 @@ def _build_conditional_tray_mass_balance(m, t, tray, no_tray): Pyomo model of the distillation column. t : int Index of tray in the distillation column model. - tray : Pyomo.gdp.Disjunct + tray : Pyomo.Disjunct Disjunct representing the existence of the tray. - no_tray : Pyomo.gdp.Disjunct + no_tray : Pyomo.Disjunct Disjunct representing the absence of the tray. Returns @@ -586,35 +586,37 @@ def vap_flow_pass_through(_, c): def _build_feed_tray_mass_balance(m): - """_summary_ + """ + Constructs the mass balance and composition constraints for the feed tray in the distillation column. Parameters ---------- m : Pyomo.ConcreteModel - _description_ + Pyomo model of the distillation column. Returns ------- - _type_ - _description_ + None + None, but the mass balance constraints for the feed tray are added to the Pyomo model. """ t = m.feed_tray @m.Constraint(m.comps) def feed_mass_balance(_, c): - """_summary_ + """ + Mass balance on each component on a tray. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder for the model instance, required for defining constraints within the Pyomo framework. It specifies the context in which the feed tray conditions are applied. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Constraint that the mass balance on each component on a tray is equal to the sum of the feed in, vapor from the tray, liquid from the tray above, liquid to the tray below, and vapor from the tray below. """ return ( m.feed[c] # Feed in @@ -626,43 +628,46 @@ def feed_mass_balance(_, c): @m.Constraint(m.comps) def feed_tray_liquid_composition(_, c): - """_summary_ + """ + Liquid composition constraint for the feed tray. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder for the model instance, required for defining constraints within the Pyomo framework. It specifies the context in which the feed tray conditions are applied. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Constraint that the liquid flow rate for each component is equal to the liquid flow rate on the tray times the liquid composition on the tray. """ return m.L[c, t] == m.liq[t] * m.x[c, t] @m.Constraint(m.comps) def feed_tray_vapor_composition(_, c): - """_summary_ + """ + Vapor composition on each component on a tray. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder for the model instance, required for defining constraints within the Pyomo framework. It specifies the context in which the feed tray conditions are applied. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Constraint that the vapor flow rate for each component is equal to the vapor flow rate on the tray times the vapor composition on the tray. """ return m.V[c, t] == m.vap[t] * m.y[c, t] def _build_condenser_mass_balance(m): - """_summary_ + """ + Constructs the mass balance equations for the condenser tray in a distillation column. Parameters ---------- @@ -671,26 +676,27 @@ def _build_condenser_mass_balance(m): Returns ------- - _type_ - _description_ + None + None, but the mass balance constraints for the condenser are added to the Pyomo model. """ t = m.condens_tray @m.Constraint(m.comps) def condenser_mass_balance(_, c): - """_summary_ + """ + Mass balance for each component in the condenser tray. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the mass balance and composition constraints to the condenser tray in the distillation column. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Constraint that the mass balance for each component in the condenser tray is equal to the sum of the vapor from the tray, loss to distillate, liquid to the tray below, and vapor from the tray below. """ return ( - m.V[c, t] # Vapor from tray t @@ -701,113 +707,120 @@ def condenser_mass_balance(_, c): @m.partial_cond.Constraint(m.comps) def condenser_liquid_composition(_, c): - """_summary_ + """ + Liquid composition constraint for the condenser tray. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the mass balance and composition constraints to the condenser tray in the distillation column. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Constraint that the liquid flow rate for each component is equal to the liquid flow rate on the tray times the liquid composition on the tray. """ return m.L[c, t] == m.liq[t] * m.x[c, t] @m.partial_cond.Constraint(m.comps) def condenser_vapor_composition(_, c): - """_summary_ + """ + Vapor composition constraint for the condenser tray. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the mass balance and composition constraints to the condenser tray in the distillation column. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Constraint that the vapor flow rate for each component is equal to the vapor flow rate on the tray times the vapor composition on the tray. """ return m.V[c, t] == m.vap[t] * m.y[c, t] @m.total_cond.Constraint(m.comps) def no_vapor_flow(_, c): - """_summary_ + """ + No vapor flow for each component in the case of total condensation. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the mass balance and composition constraints to the condenser tray in the distillation column. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Constraint that there is no vapor flow for each component in the case of total condensation. """ return m.V[c, t] == 0 @m.total_cond.Constraint() def no_total_vapor_flow(_): - """_summary_ + """ + No total vapor flow for the condenser tray. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the mass balance and composition constraints to the condenser tray in the distillation column. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Constraint that there is no total vapor flow for the condenser tray. """ return m.vap[t] == 0 @m.total_cond.Constraint(m.comps) def liquid_fraction_pass_through(_, c): - """_summary_ + """ + Liquid fraction pass-through for each component in the case of total condensation. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the mass balance and composition constraints to the condenser tray in the distillation column. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Constraint that the liquid fraction is equal to the vapor fraction on the tray below in the case of total condensation. """ return m.x[c, t] == m.y[c, t - 1] @m.Constraint(m.comps) def condenser_distillate_composition(_, c): - """_summary_ + """ + Distillate composition constraint for the condenser tray. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the mass balance and composition constraints to the condenser tray in the distillation column. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Constraint that the distillate flow rate for each component is equal to the distillate flow rate times the distillate composition. """ return m.D[c] == m.dis * m.x[c, t] def _build_reboiler_mass_balance(m): - """_summary_ + """ + Constructs the mass balance equations for the reboiler tray in a distillation column. Parameters ---------- @@ -816,13 +829,28 @@ def _build_reboiler_mass_balance(m): Returns ------- - _type_ - _description_ + None + None, but the mass balance constraints for the reboiler are added to the Pyomo model. """ t = m.reboil_tray @m.Constraint(m.comps) def reboiler_mass_balance(_, c): + """ + Mass balance for each component in the reboiler tray. + + Parameters + ---------- + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the mass balance and composition constraints to the reboiler tray in the distillation column. + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + + Returns + ------- + Pyomo.Constraint + Constraint that the mass balance for each component in the reboiler tray is equal to the sum of the vapor from the tray, liquid from the tray above, and loss to bottoms. + """ t = m.reboil_tray return ( - m.V[c, t] # Vapor from tray t @@ -832,94 +860,115 @@ def reboiler_mass_balance(_, c): @m.Constraint(m.comps) def reboiler_liquid_composition(_, c): - """_summary_ + """ + Liquid composition constraint for the reboiler tray. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the mass balance and composition constraints to the reboiler tray in the distillation column. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Constraint that the liquid flow rate for each component is equal to the liquid flow rate on the tray times the liquid composition on the tray. """ return m.L[c, t] == m.liq[t] * m.x[c, t] @m.Constraint(m.comps) def reboiler_vapor_composition(_, c): - """_summary_ + """ + Vapor composition constraint for the reboiler tray. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the mass balance and composition constraints to the reboiler tray in the distillation column. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Constraint that the vapor flow rate for each component is equal to the vapor flow rate on the tray times the vapor composition on the tray. """ return m.V[c, t] == m.vap[t] * m.y[c, t] def _build_tray_phase_equilibrium(m, t, tray): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo model of the distillation column. + t : int + Index of tray in the distillation column model. + tray : Pyomo.Disjunct + Disjunct representing the existence of the tray. + + Returns + ------- + None + None, but the phase equilibrium constraints for the tray are added to the Pyomo model. + """ @tray.Constraint(m.comps) def raoults_law(_, c): - """_summary_ + """ + Raoult's law for each component in a tray. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the phase equilibrium constraints to the trays in the distillation column. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- - _type_ - _description_ + Pyomo.Constraint + The Raoult's law for each component in trays is calculated as the product of the liquid mole fraction and the phase equilibrium constant. The product is equal to the vapor mole fraction. """ return m.y[c, t] == m.x[c, t] * m.Kc[c, t] @tray.Constraint(m.comps) def phase_equil_const(_, c): - """_summary_ + """ + Phase equilibrium constraint for each component in a tray. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the phase equilibrium constraints to the trays in the distillation column. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- - _type_ - _description_ + Pyomo.Constraint + The phase equilibrium constant for each component in a tray multiplied with the pressure is equal to the product of the activity coefficient and the pure component vapor pressure. """ return m.Kc[c, t] * m.P == ( m.gamma[c, t] * m.Pvap[c, t]) @tray.Constraint(m.comps) def Pvap_relation(_, c): - """_summary_ + """ + Antoine's equation for the vapor pressure of each component in a tray. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the phase equilibrium constraints to the trays in the distillation column. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Antoine's equation for the vapor pressure of each component in a tray is calculated as the logarithm of the vapor pressure minus the logarithm of the critical pressure times one minus the fraction of critical temperature. The equation is equal to the sum of the Antoine coefficients times the fraction of critical temperature raised to different powers. """ k = m.pvap_const[c] x = m.Pvap_X[c, t] @@ -931,44 +980,47 @@ def Pvap_relation(_, c): @tray.Constraint(m.comps) def Pvap_X_defn(_, c): - """_summary_ + """ + Defines the relationship between the one minus the reduced temperature variable (Pvap_X) for each component in a tray, and the actual temperature of the tray, normalized by the critical temperature of the component (Tc). Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the phase equilibrium constraints to the trays in the distillation column. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- - _type_ - _description_ + Pyomo.Constraint + The relationship between the one minus the reduced temperature variable (Pvap_X) for each component in a tray, and the actual temperature of the tray, normalized by the critical temperature of the component (Tc). """ k = m.pvap_const[c] return m.Pvap_X[c, t] == 1 - m.T[t] / k['Tc'] @tray.Constraint(m.comps) def gamma_calc(_, c): - """_summary_ + """ + Calculates the activity coefficient for each component in a tray. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the phase equilibrium constraints to the trays in the distillation column. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- - _type_ - _description_ + Pyomo.Constraint + The activity coefficient for each component in a tray is calculated as 1. """ return m.gamma[c, t] == 1 def _build_column_heat_relations(m): - """_summary_ + """ + Constructs the enthalpy relations for both liquid and vapor phases in each tray of a distillation column. Parameters ---------- @@ -977,17 +1029,18 @@ def _build_column_heat_relations(m): Returns ------- - _type_ - _description_ + None + None, but the energy balance constraints for the distillation column are added to the Pyomo model. """ @m.Expression(m.trays, m.comps) def liq_enthalpy_expr(_, t, c): - """_summary_ + """ + Liquid phase enthalpy based on the heat capacity coefficients and the temperature difference from a reference temperature [kJ/mol]. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the energy balance constraints to the distillation column. t : int Index of tray in the distillation column model. c : str @@ -995,8 +1048,8 @@ def liq_enthalpy_expr(_, t, c): Returns ------- - _type_ - _description_ + Pyomo.Expression + Enthalpy based on the heat capacity coefficients and the temperature difference from a reference temperature [kJ/mol]. """ k = m.liq_Cp_const[c] return ( @@ -1004,16 +1057,17 @@ def liq_enthalpy_expr(_, t, c): k['B'] * (m.T[t] ** 2 - m.T_ref ** 2) / 2 + k['C'] * (m.T[t] ** 3 - m.T_ref ** 3) / 3 + k['D'] * (m.T[t] ** 4 - m.T_ref ** 4) / 4 + - k['E'] * (m.T[t] ** 5 - m.T_ref ** 5) / 5) * 1E-6 + k['E'] * (m.T[t] ** 5 - m.T_ref ** 5) / 5) * 1E-6 # Convert [J/mol] to [MJ/mol] @m.Expression(m.trays, m.comps) def vap_enthalpy_expr(_, t, c): - """_summary_ + """ + Vapor phase enthalpy based on the heat capacity coefficients and the temperature difference from a reference temperature [kJ/mol]. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the energy balance constraints to the distillation column. t : int Index of tray in the distillation column model. c : str @@ -1021,8 +1075,8 @@ def vap_enthalpy_expr(_, t, c): Returns ------- - _type_ - _description_ + Pyomo.Expression + Enthalpy based on the heat capacity coefficients and the temperature difference from a reference temperature [kJ/mol]. """ k = m.vap_Cp_const[c] return ( @@ -1031,7 +1085,7 @@ def vap_enthalpy_expr(_, t, c): k['B'] * (m.T[t] ** 2 - m.T_ref ** 2) / 2 + k['C'] * (m.T[t] ** 3 - m.T_ref ** 3) / 3 + k['D'] * (m.T[t] ** 4 - m.T_ref ** 4) / 4 + - k['E'] * (m.T[t] ** 5 - m.T_ref ** 5) / 5) * 1E-3 + k['E'] * (m.T[t] ** 5 - m.T_ref ** 5) / 5) * 1E-3 # Convert [J/mol] to [kJ/mol] for t in m.conditional_trays: _build_conditional_tray_energy_balance(m, t, m.tray[t], m.no_tray[t]) @@ -1041,18 +1095,37 @@ def vap_enthalpy_expr(_, t, c): def _build_conditional_tray_energy_balance(m, t, tray, no_tray): + """ + Constructs the energy balance constraints for a specific tray in a distillation column, considering both active and inactive (pass-through) scenarios. + + Parameters + ---------- + m : Pyomo.ConcreteModel + Pyomo model of the distillation column. + t : int + Index of tray in the distillation column model. + tray : Pyomo.Disjunct + Disjunct representing the existence of the tray. + no_tray : Pyomo.Disjunct + Disjunct representing the absence of the tray. + + Returns + ------- + None + None, but the energy balance constraints for the conditional tray are added to the Pyomo model. + """ @tray.Constraint() def energy_balance(_): """_summary_ Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the energy balance constraints to the trays in the distillation column. Returns ------- - _type_ + Pyomo.Constraint _description_ """ return sum( @@ -1064,73 +1137,77 @@ def energy_balance(_): @tray.Constraint(m.comps) def liq_enthalpy_calc(_, c): - """_summary_ + """ + Liquid enthalpy calculation for each component in a tray. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the energy balance constraints to the trays in the distillation column. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- - _type_ - Index of component in the distillation column model. 'benzene' or 'toluene'. + Pyomo.Constraint + Constraint that the liquid enthalpy for each component is equal to the liquid enthalpy expression. """ return m.H_L[c, t] == m.liq_enthalpy_expr[t, c] @tray.Constraint(m.comps) def vap_enthalpy_calc(_, c): - """_summary_ + """ + Vapor enthalpy calculation for each component in a tray. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the energy balance constraints to the trays in the distillation column. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Constraint that the vapor enthalpy for each component is equal to the vapor enthalpy expression. """ return m.H_V[c, t] == m.vap_enthalpy_expr[t, c] @no_tray.Constraint(m.comps) def liq_enthalpy_pass_through(_, c): - """_summary_ + """ + Liquid enthalpy pass-through for each component in the case of no tray. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the energy balance constraints to the trays in the distillation column. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Constraint that the liquid enthalpy is equal to the liquid enthalpy on the tray below, when the tray is not present. """ return m.H_L[c, t] == m.H_L[c, t + 1] @no_tray.Constraint(m.comps) def vap_enthalpy_pass_through(_, c): - """_summary_ + """ + Vapor enthalpy pass-through for each component in the case of no tray. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the energy balance constraints to the trays in the distillation column. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Constraint that the vapor enthalpy is equal to the vapor enthalpy on the tray above, when the tray is not present. """ return m.H_V[c, t] == m.H_V[c, t - 1] @@ -1153,17 +1230,18 @@ def _build_feed_tray_energy_balance(m): @m.Constraint() def feed_tray_energy_balance(_): - """_summary_ + """ + Energy balance for the feed tray. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the energy balance constraints to the feed tray in the distillation column. Returns ------- Pyomo.Constraint - _description_ + Constraint that the sum of the heat of the feed and the heat of the liquid and vapor streams is equal to zero. """ return ( sum(m.feed[c] * ( @@ -1183,55 +1261,58 @@ def feed_tray_energy_balance(_): @m.Constraint(m.comps) def feed_tray_liq_enthalpy_calc(_, c): - """_summary_ + """ + Liquid enthalpy calculation for the feed tray. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the energy balance constraints to the feed tray in the distillation column. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- Pyomo.Constraint - _description_ + Constraint that the liquid enthalpy is equal to the liquid enthalpy expression. """ return m.H_L[c, t] == m.liq_enthalpy_expr[t, c] @m.Constraint(m.comps) def feed_tray_vap_enthalpy_calc(_, c): - """_summary_ + """ + Vapor enthalpy calculation for the feed tray. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the energy balance constraints to the feed tray in the distillation column. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- Pyomo.Constraint - _description_ + Constraint that the vapor enthalpy is equal to the vapor enthalpy expression. """ return m.H_V[c, t] == m.vap_enthalpy_expr[t, c] @m.Expression(m.comps) def feed_liq_enthalpy_expr(_, c): - """_summary_ + """ + Liquid enthalpy expression for the feed tray. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the energy balance constraints to the feed tray in the distillation column. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- Pyomo.Expression - _description_ + Liquid enthalpy expression for the feed tray. """ k = m.liq_Cp_const[c] return ( @@ -1239,7 +1320,7 @@ def feed_liq_enthalpy_expr(_, c): k['B'] * (m.T_feed ** 2 - m.T_ref ** 2) / 2 + k['C'] * (m.T_feed ** 3 - m.T_ref ** 3) / 3 + k['D'] * (m.T_feed ** 4 - m.T_ref ** 4) / 4 + - k['E'] * (m.T_feed ** 5 - m.T_ref ** 5) / 5) * 1E-6 + k['E'] * (m.T_feed ** 5 - m.T_ref ** 5) / 5) * 1E-6 # Convert the result from [J/mol] to [MJ/mol] @m.Constraint(m.comps) def feed_liq_enthalpy_calc(_, c): @@ -1247,8 +1328,8 @@ def feed_liq_enthalpy_calc(_, c): Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the energy balance constraints to the feed tray in the distillation column. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. @@ -1261,19 +1342,20 @@ def feed_liq_enthalpy_calc(_, c): @m.Expression(m.comps) def feed_vap_enthalpy_expr(_, c): - """_summary_ + """ + Vapor enthalpy expression for the feed tray. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the energy balance constraints to the feed tray in the distillation column. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- Pyomo.Expression - _description_ + Vapor enthalpy expression for the feed tray. """ k = m.vap_Cp_const[c] return ( @@ -1282,29 +1364,31 @@ def feed_vap_enthalpy_expr(_, c): k['B'] * (m.T_feed ** 2 - m.T_ref ** 2) / 2 + k['C'] * (m.T_feed ** 3 - m.T_ref ** 3) / 3 + k['D'] * (m.T_feed ** 4 - m.T_ref ** 4) / 4 + - k['E'] * (m.T_feed ** 5 - m.T_ref ** 5) / 5) * 1E-3 + k['E'] * (m.T_feed ** 5 - m.T_ref ** 5) / 5) * 1E-3 # Convert the result from [J/mol] to [kJ/mol] @m.Constraint(m.comps) def feed_vap_enthalpy_calc(_, c): - """_summary_ + """ + Vapor enthalpy calculation for the feed tray. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the energy balance constraints to the feed tray in the distillation column. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- Pyomo.Constraint - _description_ + Constraint that the vapor enthalpy is equal to the vapor enthalpy expression. """ return m.H_V_spec_feed[c] == m.feed_vap_enthalpy_expr[c] def _build_condenser_energy_balance(m): - """_summary_ + """ + Energy balance for the condenser. Parameters ---------- @@ -1313,45 +1397,47 @@ def _build_condenser_energy_balance(m): Returns ------- - _type_ - _description_ + None + None, but adds constraints, which are energy balances for the condenser, to the Pyomo model of the distillation column """ t = m.condens_tray @m.partial_cond.Constraint() def partial_condenser_energy_balance(_): - """_summary_ + """ + Energy balance for the partial condenser. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the energy balance constraints to the condenser in the distillation column. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Constraint that the sum of the heat of the liquid distillate, the heat of the liquid to the tray below, the heat of the vapor from the tray below, and the heat of the vapor from the partial condenser is equal to zero. """ return -m.Qc + sum( - m.D[c] * m.H_L[c, t] # heat of liquid distillate - m.L[c, t] * m.H_L[c, t] # heat of liquid to tray below + m.V[c, t - 1] * m.H_V[c, t - 1] # heat of vapor from tray below - m.V[c, t] * m.H_V[c, t] # heat of vapor from partial condenser - for c in m.comps) * 1E-3 == 0 + for c in m.comps) * 1E-3 == 0 # Convert the result from [kJ/mol] to [MJ/mol] @m.total_cond.Constraint() def total_condenser_energy_balance(_): - """_summary_ + """ + Energy balance for the total condenser. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the energy balance constraints to the condenser in the distillation column. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Constraint that the sum of the heat of the liquid distillate, the heat of the liquid to the tray below, and the heat of the vapor from the tray below is equal to zero. """ return -m.Qc + sum( - m.D[c] * m.H_L[c, t] # heat of liquid distillate @@ -1361,43 +1447,46 @@ def total_condenser_energy_balance(_): @m.Constraint(m.comps) def condenser_liq_enthalpy_calc(_, c): - """_summary_ + """ + Liquid enthalpy calculation for each component in the condenser. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the energy balance constraints to the condenser in the distillation column. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Constraint that the liquid enthalpy for each component is equal to the liquid enthalpy expression. """ return m.H_L[c, t] == m.liq_enthalpy_expr[t, c] @m.partial_cond.Constraint(m.comps) def vap_enthalpy_calc(_, c): - """_summary_ + """ + Vapor enthalpy calculation for each component in the condenser. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the energy balance constraints to the condenser in the distillation column. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Constraint that the vapor enthalpy for each component is equal to the vapor enthalpy expression. """ return m.H_V[c, t] == m.vap_enthalpy_expr[t, c] def _build_reboiler_energy_balance(m): - """_summary_ + """ + Energy balance for the reboiler. Parameters ---------- @@ -1413,17 +1502,18 @@ def _build_reboiler_energy_balance(m): @m.Constraint() def reboiler_energy_balance(_): - """_summary_ + """ + Energy balance for the reboiler. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the energy balance constraints to the reboiler in the distillation column. Returns ------- Pyomo.Constraint - _description_ + Constraint that the sum of the heat of the liquid bottoms, the heat of the liquid from the tray above, the heat of the vapor to the tray above, and the heat of the vapor from the reboiler is equal to zero. """ return m.Qb + sum( m.L[c, t + 1] * m.H_L[c, t + 1] # Heat of liquid from tray above @@ -1433,36 +1523,38 @@ def reboiler_energy_balance(_): @m.Constraint(m.comps) def reboiler_liq_enthalpy_calc(_, c): - """_summary_ + """ + Liquid enthalpy calculation for each component in the reboiler. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the energy balance constraints to the reboiler in the distillation column. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- Pyomo.Constraint - _description_ + Constraint that the liquid enthalpy for each component is equal to the liquid enthalpy expression. """ return m.H_L[c, t] == m.liq_enthalpy_expr[t, c] @m.Constraint(m.comps) def reboiler_vap_enthalpy_calc(_, c): - """_summary_ + """ + Vapor enthalpy calculation for each component in the reboiler. Parameters ---------- - _ : _type_ - _description_ + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the energy balance constraints to the reboiler in the distillation column. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. Returns ------- Pyomo.Constraint - _description_ + Constraint that the vapor enthalpy for each component is equal to the vapor enthalpy expression. """ return m.H_V[c, t] == m.vap_enthalpy_expr[t, c] From 60cbee9d313a820568d98835a63e361e8035e8ec Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Wed, 8 May 2024 20:36:55 -0400 Subject: [PATCH 40/60] Documentation of the main.py and fix grammar error of column.py --- gdplib/gdp_col/column.py | 2 +- gdplib/gdp_col/main.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/gdplib/gdp_col/column.py b/gdplib/gdp_col/column.py index d663461..af9c6fb 100644 --- a/gdplib/gdp_col/column.py +++ b/gdplib/gdp_col/column.py @@ -25,7 +25,7 @@ def build_column(min_trays, max_trays, xD, xB): Returns ------- Pyomo.ConcreteModel - A Pyomo model of the distillation column. for separation of benzene and toluene + A Pyomo model of the distillation column for separation of benzene and toluene. """ m = ConcreteModel('benzene-toluene column') m.comps = Set(initialize=['benzene', 'toluene'], doc='Set of components') diff --git a/gdplib/gdp_col/main.py b/gdplib/gdp_col/main.py index 08de52d..ba3e615 100644 --- a/gdplib/gdp_col/main.py +++ b/gdplib/gdp_col/main.py @@ -12,6 +12,14 @@ def main(): + """ + Solve the distillation column model. + + Returns + ------- + m : Pyomo.ConcreteModel + The distillation column model solution obtained by using GDPopt, Logic-based Outer Approximation. + """ m = build_column(min_trays=8, max_trays=17, xD=0.95, xB=0.95) # Fix feed conditions m.feed['benzene'].fix(50) @@ -43,6 +51,14 @@ def main(): def display_column(m): + """ + Display the distillation column model solution. + + Parameters + ---------- + m : Pyomo.ConcreteModel + The distillation column model solution obtained by using GDPopt, Logic-based Outer Approximation. + """ print('Objective: %s' % value(m.obj)) print('Qc: {: >3.0f}kW DB: {: >3.0f} DT: {: >3.0f} dis: {: >3.0f}' .format(value(m.Qc * 1E3), From e92101b62d9aae441eeb725500b473716fc00f9a Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Wed, 8 May 2024 20:39:48 -0400 Subject: [PATCH 41/60] Filled missing documentation --- gdplib/gdp_col/column.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/gdplib/gdp_col/column.py b/gdplib/gdp_col/column.py index af9c6fb..9143b73 100644 --- a/gdplib/gdp_col/column.py +++ b/gdplib/gdp_col/column.py @@ -55,19 +55,20 @@ def build_column(min_trays, max_trays, xD, xB): @m.Disjunction(m.conditional_trays, doc='Tray exists or does not') def tray_no_tray(b, t): - """_summary_ + """ + Disjunction for tray existence or absence. Parameters ---------- - b : _type_ - _description_ + b : Pyomo.Disjunct + Pyomo disjunct representing the existence or absence of a tray in the distillation column model. t : int Index of tray in the distillation column model. Tray numbering ascends from the reboiler at the bottom (tray 1) to the condenser at the top (tray max_trays) Returns ------- List of Disjuncts - _description_ + List of disjuncts representing the existence or absence of a tray in the distillation column model. """ return [b.tray[t], b.no_tray[t]] m.minimum_num_trays = Constraint( From f8e80da78ede1e74b54c9fe2f7679901d58356a0 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Wed, 8 May 2024 20:50:34 -0400 Subject: [PATCH 42/60] docs: Add initialization documentation to initialize.py --- gdplib/gdp_col/initialize.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/gdplib/gdp_col/initialize.py b/gdplib/gdp_col/initialize.py index b48ddcc..e0648ab 100644 --- a/gdplib/gdp_col/initialize.py +++ b/gdplib/gdp_col/initialize.py @@ -10,6 +10,16 @@ def initialize(m, excel_file=None): + """ + Initializes the distillation column model using data from an Excel file or default settings. + + Parameters + ---------- + m : Pyomo.ConcreteModel + A Pyomo model of the distillation column for separation of benzene and toluene. + excel_file : str, optional + The file path to an Excel file containing the initialization data. Defaults to 'init.xlsx' if not provided. + """ m.reflux_frac.set_value(value( m.reflux_ratio / (1 + m.reflux_ratio))) m.boilup_frac.set_value(value( @@ -23,7 +33,16 @@ def initialize(m, excel_file=None): sheet_name=None) def set_value_if_not_fixed(var, val): - """Set variable to the value if it is not fixed.""" + """ + Set variable to the value if it is not fixed. + + Parameters + ---------- + var : Pyomo.Var + The Pyomo variable to potentially modify. + val : float + The value to assign to the variable if it is not fixed. + """ if not var.fixed: var.set_value(val) From 700aeb343871e0748878ee70e0733845faec7641 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Wed, 8 May 2024 21:21:24 -0400 Subject: [PATCH 43/60] Add documentation of Fenske equation calculation to calculate_Fenske function --- gdplib/gdp_col/fenske.py | 111 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/gdplib/gdp_col/fenske.py b/gdplib/gdp_col/fenske.py index 08f3864..c93ed00 100644 --- a/gdplib/gdp_col/fenske.py +++ b/gdplib/gdp_col/fenske.py @@ -5,6 +5,21 @@ def calculate_Fenske(xD, xB): + """ + Calculate the minimum number of plates required for a given separation using the Fenske equation. + + Parameters + ---------- + xD : float + Distillate(benzene) purity + xB : float + Bottoms(toluene) purity + + Returns + ------- + None + None, but prints the Fenske equation calculating the minimum number of plates required for a given separation. + """ m = ConcreteModel() min_T, max_T = 300, 400 m.comps = Set(initialize=['benzene', 'toluene']) @@ -42,11 +57,45 @@ def calculate_Fenske(xD, xB): @m.Constraint(m.comps, m.trays) def phase_equil_const(_, c, t): + """ + Phase equilibrium constraint for each component in a tray. + + Parameters + ---------- + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the phase equilibrium constraints to the trays in the distillation column. + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + t : int + Index of tray in the distillation column model. + + Returns + ------- + Pyomo.Constraint + The phase equilibrium constant for each component in a tray multiplied with the pressure is equal to the product of the activity coefficient and the pure component vapor pressure. + """ return m.Kc[c, t] * m.P == ( m.gamma[c, t] * m.Pvap[c, t]) @m.Constraint(m.comps, m.trays) def Pvap_relation(_, c, t): + """ + Antoine's equation for the vapor pressure of each component in a tray. + + Parameters + ---------- + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the phase equilibrium constraints to the trays in the distillation column. + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + t : int + Index of tray in the distillation column model. + + Returns + ------- + Pyomo.Constraint + Antoine's equation for the vapor pressure of each component in a tray is calculated as the logarithm of the vapor pressure minus the logarithm of the critical pressure times one minus the fraction of critical temperature. The equation is equal to the sum of the Antoine coefficients times the fraction of critical temperature raised to different powers. + """ k = m.pvap_const[c] x = m.Pvap_X[c, t] return (log(m.Pvap[c, t]) - log(k['Pc'])) * (1 - x) == ( @@ -57,22 +106,84 @@ def Pvap_relation(_, c, t): @m.Constraint(m.comps, m.trays) def Pvap_X_defn(_, c, t): + """ + Defines the relationship between the one minus the reduced temperature variable (Pvap_X) for each component in a tray, and the actual temperature of the tray, normalized by the critical temperature of the component (Tc). + + Parameters + ---------- + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the phase equilibrium constraints to the trays in the distillation column. + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + t : int + Index of tray in the distillation column model. + + Returns + ------- + Pyomo.Constraint + The relationship between the one minus the reduced temperature variable (Pvap_X) for each component in a tray, and the actual temperature of the tray, normalized by the critical temperature of the component (Tc). + """ k = m.pvap_const[c] return m.Pvap_X[c, t] == 1 - m.T[t] / k['Tc'] @m.Constraint(m.comps, m.trays) def gamma_calc(_, c, t): + """ + Activity coefficient calculation. + + Parameters + ---------- + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the phase equilibrium constraints to the trays in the distillation column. + c : str + Index of component in the distillation column model. 'benzene' or 'toluene'. + t : int + Index of tray in the distillation column model. + + Returns + ------- + Pyomo.Constraint + Set the activity coefficient of the component on the tray as 1. + """ return m.gamma[c, t] == 1 m.relative_volatility = Var(m.trays, domain=NonNegativeReals) @m.Constraint(m.trays) def relative_volatility_calc(_, t): + """ + Relative volatility calculation. + + Parameters + ---------- + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the phase equilibrium constraints to the trays in the distillation column. + t : int + Index of tray in the distillation column model. + + Returns + ------- + Pyomo.Constraint + The relative volatility of benzene to toluene is the ratio of the phase equilibrium constants of benzene to toluene on the tray. + """ return m.Kc['benzene', t] == ( m.Kc['toluene', t] * m.relative_volatility[t]) @m.Expression() def fenske(_): + """ + Fenske equation for minimum number of plates. + + Parameters + ---------- + _ : Pyomo.ConcreteModel + A placeholder representing the Pyomo model instance. It specifies the context for applying the phase equilibrium constraints to the trays in the distillation column. + + Returns + ------- + Pyomo.Expression + The Fenske equation calculating the minimum number of plates required for a given separation. + """ return log((xD / (1 - xD)) * (xB / (1 - xB))) / ( log(sqrt(m.relative_volatility['condenser'] * m.relative_volatility['reboiler']))) From c275ae8998ec79ac08e886cd0be16e7be7be87e4 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Wed, 8 May 2024 23:07:08 -0400 Subject: [PATCH 44/60] Managed Comments and added the title of the code inside ConcreteModel() --- gdplib/biofuel/model.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/gdplib/biofuel/model.py b/gdplib/biofuel/model.py index 05eeb16..348ff52 100644 --- a/gdplib/biofuel/model.py +++ b/gdplib/biofuel/model.py @@ -1,4 +1,11 @@ -from __future__ import division +""" +model.py +This model describes a cost minimization for a multi-period biofuel processing network. + +References: + [1] Lara, C. L., Trespalacios, F., & Grossmann, I. E. (2018). Global optimization algorithm for capacitated multi-facility continuous location-allocation problems. Journal of Global Optimization, 71(4), 871-889. https://doi.org/10.1007/s10898-018-0621-6 + [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 +""" import os from math import fabs @@ -25,7 +32,8 @@ def build_model(): - """_summary_ + """ + Build a concrete model that describes a cost minimization for a multi-period biofuel processing network. Returns ------- @@ -37,7 +45,7 @@ def build_model(): [1] Lara, C. L., Trespalacios, F., & Grossmann, I. E. (2018). Global optimization algorithm for capacitated multi-facility continuous location-allocation problems. Journal of Global Optimization, 71(4), 871-889. https://doi.org/10.1007/s10898-018-0621-6 [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() + m = ConcreteModel('Biofuel processing network') m.bigM = Suffix(direction=Suffix.LOCAL) m.time = RangeSet(0, 120, doc="months in 10 years") m.suppliers = RangeSet(10) # 10 suppliers From 54d3b7d94b6d52d3da9724aceda1f762c0d7e169 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Wed, 8 May 2024 23:20:47 -0400 Subject: [PATCH 45/60] Set the default value of the bigM --- gdplib/biofuel/model.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/gdplib/biofuel/model.py b/gdplib/biofuel/model.py index 348ff52..a568c6c 100644 --- a/gdplib/biofuel/model.py +++ b/gdplib/biofuel/model.py @@ -2,6 +2,12 @@ model.py This model describes a cost minimization for a multi-period biofuel processing network. +The model enforces constraints to ensure that raw material supplies do not exceed available amounts, product shipments meet market demands exactly, and production at each site matches outgoing shipments and available resources. +It also optimizes transportation costs by managing both variable and fixed costs associated with active transportation routes. +The disjunctions in the model define the operational modes for facility sites (modular, conventional, or inactive) and the activity status of supply and product routes (active or inactive). +These elements allow the model to simulate different operational scenarios and strategic decisions, optimizing the network's layout and logistics based on economic and market conditions. +The objective of the model is to optimize the network layout and production allocation to minimize total costs, which include setup and teardown of facilities, production costs, and transportation costs. + References: [1] Lara, C. L., Trespalacios, F., & Grossmann, I. E. (2018). Global optimization algorithm for capacitated multi-facility continuous location-allocation problems. Journal of Global Optimization, 71(4), 871-889. https://doi.org/10.1007/s10898-018-0621-6 [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 @@ -46,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) + m.bigM = Suffix(direction=Suffix.LOCAL, initialize=7000) m.time = RangeSet(0, 120, doc="months in 10 years") m.suppliers = RangeSet(10) # 10 suppliers m.markets = RangeSet(10) # 10 markets From 700e010043ecbf0f4826656fbf753bc02708541b Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 9 May 2024 00:04:18 -0400 Subject: [PATCH 46/60] Modified the header and fix the documentation of the objective function. --- gdplib/logical/spectralog.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/gdplib/logical/spectralog.py b/gdplib/logical/spectralog.py index c34496b..ea0c7f3 100644 --- a/gdplib/logical/spectralog.py +++ b/gdplib/logical/spectralog.py @@ -1,16 +1,14 @@ -# coding: utf-8 +""" +spectrolog.py +IR Spectroscopy Parameter Estimation -# # [Pyomo.GDP](./index.ipynb) Logical Expression System Demo - IR Spectroscopy Parameter Estimation -# -# This is a reproduction of the IR spectroscopy parameter estimation problem found in: -# -# > Vecchietti A. & Grossmann I. E. -# > LOGMIP: A disjunctive 0-1 non-linear optimizer for process system models, -# > *Comp. & Chem Eng.* 23, p. 555-565, 1999. -# -# This code relies on the logic-v1 branch at https://github.com/qtothec/pyomo/tree/logic-v1 +This is a reproduction of the IR spectroscopy parameter estimation problem found in: -# Optimal value: 12.0893 +[1] Vecchietti, A., & Grossmann, I. E. (1997). LOGMIP: a disjunctive 0-1 nonlinear optimizer for process systems models. Computers & chemical engineering, 21, S427-S432. https://doi.org/10.1016/S0098-1354(97)87539-4 +[2] Brink, A., & Westerlund, T. (1995). The joint problem of model structure determination and parameter estimation in quantitative IR spectroscopy. Chemometrics and intelligent laboratory systems, 29(1), 29-36. https://doi.org/10.1016/0169-7439(95)00033-3 + +Optimal value: 12.0893 +""" from pyomo.environ import * from pyomo.gdp import * @@ -38,7 +36,7 @@ def build_model(): References ---------- - [1] Vecchietti, A., & Grossmann, I. E. (1997). LOGMIP: a disjunctive 0–1 nonlinear optimizer for process systems models. Computers & chemical engineering, 21, S427-S432. https://doi.org/10.1016/S0098-1354(97)87539-4 + [1] Vecchietti, A., & Grossmann, I. E. (1997). LOGMIP: a disjunctive 0-1 nonlinear optimizer for process systems models. Computers & chemical engineering, 21, S427-S432. https://doi.org/10.1016/S0098-1354(97)87539-4 [2] Brink, A., & Westerlund, T. (1995). The joint problem of model structure determination and parameter estimation in quantitative IR spectroscopy. Chemometrics and intelligent laboratory systems, 29(1), 29-36. https://doi.org/10.1016/0169-7439(95)00033-3 """ # Matrix of absorbance values across different wave numbers (rows) and spectra numbers (columns) @@ -198,7 +196,7 @@ def eq1(m, j): m.profit = Objective( expr=sum(m.val[j] for j in m.spectra_data) + 2 * sum(m.ent[k, i] for k in m.compounds for i in m.wave_number), - doc='Objective to maximize spectroscopic agreement and encourage compound presence.', + doc='Maximizes the total spectroscopic agreement across data points and promotes the activation of compound-wave number pairs.', ) # The first sum represents total spectroscopic value across data points, and the second weighted sum promotes activation of compound-wave number pairs. From 4e74928e06d27a44a66a19dbf3770d57a2a0cd53 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 9 May 2024 00:23:27 -0400 Subject: [PATCH 47/60] remove the import division and add the name of the concrete model. --- gdplib/stranded_gas/model.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/gdplib/stranded_gas/model.py b/gdplib/stranded_gas/model.py index b03a181..020a3d4 100644 --- a/gdplib/stranded_gas/model.py +++ b/gdplib/stranded_gas/model.py @@ -1,4 +1,7 @@ -from __future__ import division +""" +model.py + +""" import os @@ -26,7 +29,7 @@ def build_model(): ---------- [1] Chen, Q., & Grossmann, I. E. (2019). Economies of numbers for a modular stranded gas processing network: Modeling and optimization. In Computer Aided Chemical Engineering (Vol. 47, pp. 257-262). Elsevier. DOI: 10.1016/B978-0-444-64241-7.50100-3 """ - m = ConcreteModel() + m = ConcreteModel('Stranded gas production') m.BigM = Suffix(direction=Suffix.LOCAL) m.periods_per_year = Param(initialize=4, doc="Quarters per year") From e06863de8f854e23c79debf1d589fa84d66ff640 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 9 May 2024 00:29:57 -0400 Subject: [PATCH 48/60] Added Header. --- gdplib/stranded_gas/model.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/gdplib/stranded_gas/model.py b/gdplib/stranded_gas/model.py index 020a3d4..1989d7d 100644 --- a/gdplib/stranded_gas/model.py +++ b/gdplib/stranded_gas/model.py @@ -1,6 +1,15 @@ """ model.py +Pyomo ConcreteModel for optimizing a modular stranded gas processing network. +The model is designed to convert stranded gas into gasoline using a modular and intensified GTL process. +It incorporates the economic dynamics of module investments, gas processing, and product transportation. +Constraints manage the balance of gas supply and consumption, module availability and movement, and production capacities at potential sites. +Disjunctions delineate operational scenarios, such as the existence or absence of pipelines and the activation status of sites, enabling dynamic and flexible system configuration. +The objective function aims to maximize the network's net profit by optimizing revenue from gasoline sales while minimizing operational and capital expenditures across the network. + +References: + [1] Chen, Q., & Grossmann, I. E. (2019). Economies of numbers for a modular stranded gas processing network: Modeling and optimization. In Computer Aided Chemical Engineering (Vol. 47, pp. 257-262). Elsevier. DOI: 10.1016/B978-0-444-64241-7.50100-3 """ import os From d2daa3570487c3e672754bd6446491098c06ab82 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 9 May 2024 13:46:08 -0400 Subject: [PATCH 49/60] Add documentation on the common.py for explaining the common constraints and the disjunction of the model. --- gdplib/mod_hens/common.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/gdplib/mod_hens/common.py b/gdplib/mod_hens/common.py index 8374b87..04722cb 100644 --- a/gdplib/mod_hens/common.py +++ b/gdplib/mod_hens/common.py @@ -1,11 +1,17 @@ -"""Heat integration case study. +""" +Heat integration case study. + +This is example 1 of the Yee & Grossmann, 1990 paper "Simultaneous optimization models for heat integration--II". DOI: 10.1016/0098-1354(90)85010-8 -This is example 1 of the Yee & Grossmann, 1990 paper "Simultaneous optimization -models for heat integration--II". -DOI: 10.1016/0098-1354(90)85010-8 +This file provides common modeling elements of the heat exchanger network. +The model utilizes sets to organize hot and cold process streams, utility streams, and stages of heat exchange, with parameters defining the essential properties like temperatures and flow capacities. This structure facilitates detailed modeling of the heat transfer process across different stages and stream types. +Disjunctions are employed to model the binary decision of either installing or not installing a heat exchanger between specific stream pairs at each stage, enhancing the model's flexibility and ability to find an optimal solution that balances cost and efficiency. +The objective function aims to minimize the total cost of the heat exchanger network, which includes the costs associated with utility usage and the capital and operational expenses of the heat exchangers, ensuring economic feasibility alongside energy optimization. -This file provides common modeling elements. +Given the common.py, the model can be shown as the conventional model, or can be modified into single module type, integer or discretized formulation, and other various formulations. +References: + Yee, T. F., & Grossmann, I. E. (1990). Simultaneous optimization models for heat integration—II. Heat exchanger network synthesis. Computers & Chemical Engineering, 14(10), 1165–1184. https://doi.org/10.1016/0098-1354(90)85010-8 """ from __future__ import division From 321dd351e1b3281974dd6f88528bfcc7627e319a Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 16 May 2024 16:36:44 -0400 Subject: [PATCH 50/60] fixed the units into mol/s and add units on the missing documentation --- gdplib/gdp_col/column.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/gdplib/gdp_col/column.py b/gdplib/gdp_col/column.py index 9143b73..2a30767 100644 --- a/gdplib/gdp_col/column.py +++ b/gdplib/gdp_col/column.py @@ -30,7 +30,7 @@ def build_column(min_trays, max_trays, xD, xB): m = ConcreteModel('benzene-toluene column') m.comps = Set(initialize=['benzene', 'toluene'], doc='Set of components') min_T, max_T = 300, 400 # Define temperature bounds [K] - max_flow = 500 + max_flow = 500 # maximum flow rate [mol/s] m.T_feed = Var( doc='Feed temperature [K]', domain=NonNegativeReals, bounds=(min_T, max_T), initialize=368) @@ -81,29 +81,29 @@ def tray_no_tray(b, t): m.y = Var(m.comps, m.trays, doc='Vapor mole fraction', bounds=(0, 1), domain=NonNegativeReals, initialize=0.5) m.L = Var(m.comps, m.trays, - doc='component liquid flows from tray in kmol', + doc='component liquid flows from tray in mol/s', domain=NonNegativeReals, bounds=(0, max_flow), initialize=50) m.V = Var(m.comps, m.trays, - doc='component vapor flows from tray in kmol', + doc='component vapor flows from tray in mol/s', domain=NonNegativeReals, bounds=(0, max_flow), initialize=50) m.liq = Var(m.trays, domain=NonNegativeReals, - doc='liquid flows from tray in kmol', initialize=100, + doc='liquid flows from tray in mol/s', initialize=100, bounds=(0, max_flow)) m.vap = Var(m.trays, domain=NonNegativeReals, - doc='vapor flows from tray in kmol', initialize=100, + doc='vapor flows from tray in mol/s', initialize=100, bounds=(0, max_flow)) m.B = Var(m.comps, domain=NonNegativeReals, - doc='bottoms component flows in kmol', + doc='bottoms component flows in mol/s', bounds=(0, max_flow), initialize=50) m.D = Var(m.comps, domain=NonNegativeReals, - doc='distillate component flows in kmol', + doc='distillate component flows in mol/s', bounds=(0, max_flow), initialize=50) m.bot = Var(domain=NonNegativeReals, initialize=50, bounds=(0, 100), - doc='bottoms flow in kmol') + doc='bottoms flow in mol/s') m.dis = Var(domain=NonNegativeReals, initialize=50, - doc='distillate flow in kmol', bounds=(0, 100)) + doc='distillate flow in mol/s', bounds=(0, 100)) m.reflux_ratio = Var(domain=NonNegativeReals, bounds=(0.5, 4), doc='reflux ratio', initialize=0.8329) m.reboil_ratio = Var(domain=NonNegativeReals, bounds=(0.5, 4), From 19dfcb047086255ec9ef67c80ef04a919ea71a7e Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 16 May 2024 16:40:12 -0400 Subject: [PATCH 51/60] black format column.py --- gdplib/gdp_col/column.py | 496 ++++++++++++++++++++++++++------------- 1 file changed, 328 insertions(+), 168 deletions(-) diff --git a/gdplib/gdp_col/column.py b/gdplib/gdp_col/column.py index 2a30767..762a95a 100644 --- a/gdplib/gdp_col/column.py +++ b/gdplib/gdp_col/column.py @@ -3,7 +3,17 @@ from __future__ import division from pyomo.environ import ( - Block, ConcreteModel, Constraint, log, minimize, NonNegativeReals, Objective, RangeSet, Set, Var, ) + Block, + ConcreteModel, + Constraint, + log, + minimize, + NonNegativeReals, + Objective, + RangeSet, + Set, + Var, +) from pyomo.gdp import Disjunct, Disjunction @@ -29,16 +39,16 @@ def build_column(min_trays, max_trays, xD, xB): """ m = ConcreteModel('benzene-toluene column') m.comps = Set(initialize=['benzene', 'toluene'], doc='Set of components') - min_T, max_T = 300, 400 # Define temperature bounds [K] - max_flow = 500 # maximum flow rate [mol/s] + min_T, max_T = 300, 400 # Define temperature bounds [K] + max_flow = 500 # maximum flow rate [mol/s] m.T_feed = Var( - doc='Feed temperature [K]', domain=NonNegativeReals, - bounds=(min_T, max_T), initialize=368) - m.feed_vap_frac = Var( - doc='Vapor fraction of feed', - initialize=0, bounds=(0, 1)) - m.feed = Var( - m.comps, doc='Total component feed flow [mol/s]', initialize=50) + doc='Feed temperature [K]', + domain=NonNegativeReals, + bounds=(min_T, max_T), + initialize=368, + ) + m.feed_vap_frac = Var(doc='Vapor fraction of feed', initialize=0, bounds=(0, 1)) + m.feed = Var(m.comps, doc='Total component feed flow [mol/s]', initialize=50) m.condens_tray = max_trays m.feed_tray = int(round(max_trays / 2)) @@ -49,7 +59,8 @@ def build_column(min_trays, max_trays, xD, xB): m.trays = RangeSet(max_trays, doc='Set of potential trays') m.conditional_trays = Set( initialize=m.trays - [m.condens_tray, m.feed_tray, m.reboil_tray], - doc="Trays that may be turned on and off.") + doc="Trays that may be turned on and off.", + ) m.tray = Disjunct(m.conditional_trays, doc='Disjunct for tray existence') m.no_tray = Disjunct(m.conditional_trays, doc='Disjunct for tray absence') @@ -71,47 +82,98 @@ def tray_no_tray(b, t): List of disjuncts representing the existence or absence of a tray in the distillation column model. """ return [b.tray[t], b.no_tray[t]] + m.minimum_num_trays = Constraint( - expr=sum(m.tray[t].binary_indicator_var - for t in m.conditional_trays) + 1 # for feed tray - >= min_trays, doc='Minimum number of trays') - - m.x = Var(m.comps, m.trays, doc='Liquid mole fraction', - bounds=(0, 1), domain=NonNegativeReals, initialize=0.5) - m.y = Var(m.comps, m.trays, doc='Vapor mole fraction', - bounds=(0, 1), domain=NonNegativeReals, initialize=0.5) - m.L = Var(m.comps, m.trays, - doc='component liquid flows from tray in mol/s', - domain=NonNegativeReals, bounds=(0, max_flow), - initialize=50) - m.V = Var(m.comps, m.trays, - doc='component vapor flows from tray in mol/s', - domain=NonNegativeReals, bounds=(0, max_flow), - initialize=50) - m.liq = Var(m.trays, domain=NonNegativeReals, - doc='liquid flows from tray in mol/s', initialize=100, - bounds=(0, max_flow)) - m.vap = Var(m.trays, domain=NonNegativeReals, - doc='vapor flows from tray in mol/s', initialize=100, - bounds=(0, max_flow)) - m.B = Var(m.comps, domain=NonNegativeReals, - doc='bottoms component flows in mol/s', - bounds=(0, max_flow), initialize=50) - m.D = Var(m.comps, domain=NonNegativeReals, - doc='distillate component flows in mol/s', - bounds=(0, max_flow), initialize=50) - m.bot = Var(domain=NonNegativeReals, initialize=50, bounds=(0, 100), - doc='bottoms flow in mol/s') - m.dis = Var(domain=NonNegativeReals, initialize=50, - doc='distillate flow in mol/s', bounds=(0, 100)) - m.reflux_ratio = Var(domain=NonNegativeReals, bounds=(0.5, 4), - doc='reflux ratio', initialize=0.8329) - m.reboil_ratio = Var(domain=NonNegativeReals, bounds=(0.5, 4), - doc='reboil ratio', initialize=0.9527) - m.reflux_frac = Var(domain=NonNegativeReals, bounds=(0, 1 - 1E-6), - doc='reflux fractions') - m.boilup_frac = Var(domain=NonNegativeReals, bounds=(0, 1 - 1E-6), - doc='boilup fraction') + expr=sum(m.tray[t].binary_indicator_var for t in m.conditional_trays) + + 1 # for feed tray + >= min_trays, + doc='Minimum number of trays', + ) + + m.x = Var( + m.comps, + m.trays, + doc='Liquid mole fraction', + bounds=(0, 1), + domain=NonNegativeReals, + initialize=0.5, + ) + m.y = Var( + m.comps, + m.trays, + doc='Vapor mole fraction', + bounds=(0, 1), + domain=NonNegativeReals, + initialize=0.5, + ) + m.L = Var( + m.comps, + m.trays, + doc='component liquid flows from tray in mol/s', + domain=NonNegativeReals, + bounds=(0, max_flow), + initialize=50, + ) + m.V = Var( + m.comps, + m.trays, + doc='component vapor flows from tray in mol/s', + domain=NonNegativeReals, + bounds=(0, max_flow), + initialize=50, + ) + m.liq = Var( + m.trays, + domain=NonNegativeReals, + doc='liquid flows from tray in mol/s', + initialize=100, + bounds=(0, max_flow), + ) + m.vap = Var( + m.trays, + domain=NonNegativeReals, + doc='vapor flows from tray in mol/s', + initialize=100, + bounds=(0, max_flow), + ) + m.B = Var( + m.comps, + domain=NonNegativeReals, + doc='bottoms component flows in mol/s', + bounds=(0, max_flow), + initialize=50, + ) + m.D = Var( + m.comps, + domain=NonNegativeReals, + doc='distillate component flows in mol/s', + bounds=(0, max_flow), + initialize=50, + ) + m.bot = Var( + domain=NonNegativeReals, + initialize=50, + bounds=(0, 100), + doc='bottoms flow in mol/s', + ) + m.dis = Var( + domain=NonNegativeReals, + initialize=50, + doc='distillate flow in mol/s', + bounds=(0, 100), + ) + m.reflux_ratio = Var( + domain=NonNegativeReals, bounds=(0.5, 4), doc='reflux ratio', initialize=0.8329 + ) + m.reboil_ratio = Var( + domain=NonNegativeReals, bounds=(0.5, 4), doc='reboil ratio', initialize=0.9527 + ) + m.reflux_frac = Var( + domain=NonNegativeReals, bounds=(0, 1 - 1e-6), doc='reflux fractions' + ) + m.boilup_frac = Var( + domain=NonNegativeReals, bounds=(0, 1 - 1e-6), doc='boilup fraction' + ) m.partial_cond = Disjunct() m.total_cond = Disjunct() @@ -123,8 +185,7 @@ def tray_no_tray(b, t): _build_condenser_mass_balance(m) _build_reboiler_mass_balance(m) - @m.Constraint(m.comps, - doc="Bottoms flow is equal to liquid leaving reboiler.") + @m.Constraint(m.comps, doc="Bottoms flow is equal to liquid leaving reboiler.") def bottoms_mass_balance(m, c): """ Constraint that the bottoms flow is equal to the liquid leaving the reboiler. @@ -176,7 +237,8 @@ def reflux_frac_defn(m): Constraint that the reflux fraction is the ratio between the distillate flow and the difference in vapor flow in the condenser tray. """ return m.dis == (1 - m.reflux_frac) * ( - m.vap[m.condens_tray - 1] - m.vap[m.condens_tray]) + m.vap[m.condens_tray - 1] - m.vap[m.condens_tray] + ) @m.Constraint(m.trays) def liquid_sum(m, t): @@ -217,17 +279,26 @@ def vapor_sum(m, t): return sum(m.V[c, t] for c in m.comps) == m.vap[t] m.bottoms_sum = Constraint( - expr=sum(m.B[c] for c in m.comps) == m.bot, doc="Total bottoms flow is the sum of all component flows at the bottom.") + expr=sum(m.B[c] for c in m.comps) == m.bot, + doc="Total bottoms flow is the sum of all component flows at the bottom.", + ) m.distil_sum = Constraint( - expr=sum(m.D[c] for c in m.comps) == m.dis, doc="Total distillate flow is the sum of all component flows at the top.") + expr=sum(m.D[c] for c in m.comps) == m.dis, + doc="Total distillate flow is the sum of all component flows at the top.", + ) """Phase Equilibrium relations""" m.Kc = Var( - m.comps, m.trays, doc='Phase equilibrium constant', - domain=NonNegativeReals, initialize=1, bounds=(0, 1000)) - m.T = Var(m.trays, doc='Temperature [K]', - domain=NonNegativeReals, - bounds=(min_T, max_T)) + m.comps, + m.trays, + doc='Phase equilibrium constant', + domain=NonNegativeReals, + initialize=1, + bounds=(0, 1000), + ) + m.T = Var( + m.trays, doc='Temperature [K]', domain=NonNegativeReals, bounds=(min_T, max_T) + ) @m.Constraint(m.trays) def monotonoic_temperature(_, t): @@ -248,28 +319,51 @@ def monotonoic_temperature(_, t): """ return m.T[t] >= m.T[t + 1] if t < max_trays else Constraint.Skip - m.P = Var(doc='Pressure [bar]', - bounds=(0, 5)) + m.P = Var(doc='Pressure [bar]', bounds=(0, 5)) m.P.fix(1.01) m.T_ref = 298.15 m.gamma = Var( - m.comps, m.trays, + m.comps, + m.trays, doc='liquid activity coefficent of component on tray', - domain=NonNegativeReals, bounds=(0, 10), initialize=1) + domain=NonNegativeReals, + bounds=(0, 10), + initialize=1, + ) m.Pvap = Var( - m.comps, m.trays, + m.comps, + m.trays, doc='pure component vapor pressure of component on tray in bar', - domain=NonNegativeReals, bounds=(1E-3, 5), initialize=0.4) + domain=NonNegativeReals, + bounds=(1e-3, 5), + initialize=0.4, + ) m.Pvap_X = Var( - m.comps, m.trays, + m.comps, + m.trays, doc='Related to fraction of critical temperature (1 - T/Tc)', - bounds=(0.25, 0.5), initialize=0.4) + bounds=(0.25, 0.5), + initialize=0.4, + ) m.pvap_const = { - 'benzene': {'A': -6.98273, 'B': 1.33213, 'C': -2.62863, - 'D': -3.33399, 'Tc': 562.2, 'Pc': 48.9}, - 'toluene': {'A': -7.28607, 'B': 1.38091, 'C': -2.83433, - 'D': -2.79168, 'Tc': 591.8, 'Pc': 41.0}} + 'benzene': { + 'A': -6.98273, + 'B': 1.33213, + 'C': -2.62863, + 'D': -3.33399, + 'Tc': 562.2, + 'Pc': 48.9, + }, + 'toluene': { + 'A': -7.28607, + 'B': 1.38091, + 'C': -2.83433, + 'D': -2.79168, + 'Tc': 591.8, + 'Pc': 41.0, + }, + } for t in m.conditional_trays: _build_tray_phase_equilibrium(m, t, m.tray[t]) @@ -281,33 +375,60 @@ def monotonoic_temperature(_, t): _build_tray_phase_equilibrium(m, m.condens_tray, m.condenser_phase_eq) m.H_L = Var( - m.comps, m.trays, bounds=(0.1, 16), - doc='Liquid molar enthalpy of component in tray (kJ/mol)') + m.comps, + m.trays, + bounds=(0.1, 16), + doc='Liquid molar enthalpy of component in tray (kJ/mol)', + ) m.H_V = Var( - m.comps, m.trays, bounds=(30, 16 + 40), - doc='Vapor molar enthalpy of component in tray (kJ/mol)') + m.comps, + m.trays, + bounds=(30, 16 + 40), + doc='Vapor molar enthalpy of component in tray (kJ/mol)', + ) m.H_L_spec_feed = Var( - m.comps, doc='Component liquid molar enthalpy in feed [kJ/mol]', - initialize=0, bounds=(0.1, 16)) + m.comps, + doc='Component liquid molar enthalpy in feed [kJ/mol]', + initialize=0, + bounds=(0.1, 16), + ) m.H_V_spec_feed = Var( - m.comps, doc='Component vapor molar enthalpy in feed [kJ/mol]', - initialize=0, bounds=(30, 16 + 40)) - m.Qb = Var(domain=NonNegativeReals, doc='reboiler duty (MJ/s)', - initialize=1, bounds=(0, 8)) - m.Qc = Var(domain=NonNegativeReals, doc='condenser duty (MJ/s)', - initialize=1, bounds=(0, 8)) + m.comps, + doc='Component vapor molar enthalpy in feed [kJ/mol]', + initialize=0, + bounds=(30, 16 + 40), + ) + m.Qb = Var( + domain=NonNegativeReals, doc='reboiler duty (MJ/s)', initialize=1, bounds=(0, 8) + ) + m.Qc = Var( + domain=NonNegativeReals, + doc='condenser duty (MJ/s)', + initialize=1, + bounds=(0, 8), + ) m.vap_Cp_const = { - 'benzene': {'A': -3.392E1, 'B': 4.739E-1, 'C': -3.017E-4, - 'D': 7.130E-8, 'E': 0}, - 'toluene': {'A': -2.435E1, 'B': 5.125E-1, 'C': -2.765E-4, - 'D': 4.911E-8, 'E': 0}} + 'benzene': { + 'A': -3.392e1, + 'B': 4.739e-1, + 'C': -3.017e-4, + 'D': 7.130e-8, + 'E': 0, + }, + 'toluene': { + 'A': -2.435e1, + 'B': 5.125e-1, + 'C': -2.765e-4, + 'D': 4.911e-8, + 'E': 0, + }, + } m.liq_Cp_const = { - 'benzene': {'A': 1.29E5, 'B': -1.7E2, 'C': 6.48E-1, - 'D': 0, 'E': 0}, - 'toluene': {'A': 1.40E5, 'B': -1.52E2, 'C': 6.95E-1, - 'D': 0, 'E': 0}} - m.dH_vap = {'benzene': 33.770E3, 'toluene': 38.262E3} # J/mol + 'benzene': {'A': 1.29e5, 'B': -1.7e2, 'C': 6.48e-1, 'D': 0, 'E': 0}, + 'toluene': {'A': 1.40e5, 'B': -1.52e2, 'C': 6.95e-1, 'D': 0, 'E': 0}, + } + m.dH_vap = {'benzene': 33.770e3, 'toluene': 38.262e3} # J/mol _build_column_heat_relations(m) @@ -349,9 +470,11 @@ def bottoms_req(m): # The objective is to minimize the sum of condenser and reboiler duties, Qc and Qb, multiplied by 1E3 to convert units, # and also the number of activated trays, which is obtained by summing up the indicator variables for the trays by 1E3 [$/No. of Trays]. # m.obj = Objective(expr=(m.Qc + m.Qb) * 1E-3, sense=minimize) - m.obj = Objective( expr=(m.Qc + m.Qb) * 1E3 + 1E3 * ( - sum(m.tray[t].binary_indicator_var for t in m.conditional_trays) + 1), - sense=minimize) + m.obj = Objective( + expr=(m.Qc + m.Qb) * 1e3 + + 1e3 * (sum(m.tray[t].binary_indicator_var for t in m.conditional_trays) + 1), + sense=minimize, + ) # m.obj = Objective( # expr=sum(m.tray[t].indicator_var for t in m.conditional_trays) + 1) @@ -407,11 +530,9 @@ def tray_ordering(m, t): Constraint that trays close to the feed should be activated first. """ if t + 1 < m.condens_tray and t > m.feed_tray: - return m.tray[t].binary_indicator_var >= \ - m.tray[t + 1].binary_indicator_var + return m.tray[t].binary_indicator_var >= m.tray[t + 1].binary_indicator_var elif t > m.reboil_tray and t + 1 < m.feed_tray: - return m.tray[t + 1].binary_indicator_var >= \ - m.tray[t].binary_indicator_var + return m.tray[t + 1].binary_indicator_var >= m.tray[t].binary_indicator_var else: return Constraint.NoConstraint @@ -438,6 +559,7 @@ def _build_conditional_tray_mass_balance(m, t, tray, no_tray): None None, but the mass balance constraints for the conditional tray are added to the Pyomo model. """ + @tray.Constraint(m.comps) def mass_balance(_, c): """ @@ -469,7 +591,9 @@ def mass_balance(_, c): # Liquid to tray below if not reboiler - (m.L[c, t] if t > m.reboil_tray else 0) # Vapor from tray below if not reboiler - + (m.V[c, t - 1] if t > m.reboil_tray else 0) == 0) + + (m.V[c, t - 1] if t > m.reboil_tray else 0) + == 0 + ) @tray.Constraint(m.comps) def tray_liquid_composition(_, c): @@ -620,12 +744,13 @@ def feed_mass_balance(_, c): Constraint that the mass balance on each component on a tray is equal to the sum of the feed in, vapor from the tray, liquid from the tray above, liquid to the tray below, and vapor from the tray below. """ return ( - m.feed[c] # Feed in - - m.V[c, t] # Vapor from tray t + m.feed[c] # Feed in + - m.V[c, t] # Vapor from tray t + m.L[c, t + 1] # Liquid from tray above - - m.L[c, t] # Liquid to tray below + - m.L[c, t] # Liquid to tray below + m.V[c, t - 1] # Vapor from tray below - == 0) + == 0 + ) @m.Constraint(m.comps) def feed_tray_liquid_composition(_, c): @@ -700,11 +825,12 @@ def condenser_mass_balance(_, c): Constraint that the mass balance for each component in the condenser tray is equal to the sum of the vapor from the tray, loss to distillate, liquid to the tray below, and vapor from the tray below. """ return ( - - m.V[c, t] # Vapor from tray t - - m.D[c] # Loss to distillate - - m.L[c, t] # Liquid to tray below + -m.V[c, t] # Vapor from tray t + - m.D[c] # Loss to distillate + - m.L[c, t] # Liquid to tray below + m.V[c, t - 1] # Vapor from tray below - == 0) + == 0 + ) @m.partial_cond.Constraint(m.comps) def condenser_liquid_composition(_, c): @@ -854,10 +980,11 @@ def reboiler_mass_balance(_, c): """ t = m.reboil_tray return ( - - m.V[c, t] # Vapor from tray t + -m.V[c, t] # Vapor from tray t + m.L[c, t + 1] # Liquid from tray above - - m.B[c] # Loss to bottoms - == 0) + - m.B[c] # Loss to bottoms + == 0 + ) @m.Constraint(m.comps) def reboiler_liquid_composition(_, c): @@ -915,6 +1042,7 @@ def _build_tray_phase_equilibrium(m, t, tray): None None, but the phase equilibrium constraints for the tray are added to the Pyomo model. """ + @tray.Constraint(m.comps) def raoults_law(_, c): """ @@ -951,8 +1079,7 @@ def phase_equil_const(_, c): Pyomo.Constraint The phase equilibrium constant for each component in a tray multiplied with the pressure is equal to the product of the activity coefficient and the pure component vapor pressure. """ - return m.Kc[c, t] * m.P == ( - m.gamma[c, t] * m.Pvap[c, t]) + return m.Kc[c, t] * m.P == (m.gamma[c, t] * m.Pvap[c, t]) @tray.Constraint(m.comps) def Pvap_relation(_, c): @@ -974,10 +1101,8 @@ def Pvap_relation(_, c): k = m.pvap_const[c] x = m.Pvap_X[c, t] return (log(m.Pvap[c, t]) - log(k['Pc'])) * (1 - x) == ( - k['A'] * x + - k['B'] * x ** 1.5 + - k['C'] * x ** 3 + - k['D'] * x ** 6) + k['A'] * x + k['B'] * x**1.5 + k['C'] * x**3 + k['D'] * x**6 + ) @tray.Constraint(m.comps) def Pvap_X_defn(_, c): @@ -1033,6 +1158,7 @@ def _build_column_heat_relations(m): None None, but the energy balance constraints for the distillation column are added to the Pyomo model. """ + @m.Expression(m.trays, m.comps) def liq_enthalpy_expr(_, t, c): """ @@ -1054,11 +1180,12 @@ def liq_enthalpy_expr(_, t, c): """ k = m.liq_Cp_const[c] return ( - k['A'] * (m.T[t] - m.T_ref) + - k['B'] * (m.T[t] ** 2 - m.T_ref ** 2) / 2 + - k['C'] * (m.T[t] ** 3 - m.T_ref ** 3) / 3 + - k['D'] * (m.T[t] ** 4 - m.T_ref ** 4) / 4 + - k['E'] * (m.T[t] ** 5 - m.T_ref ** 5) / 5) * 1E-6 # Convert [J/mol] to [MJ/mol] + k['A'] * (m.T[t] - m.T_ref) + + k['B'] * (m.T[t] ** 2 - m.T_ref**2) / 2 + + k['C'] * (m.T[t] ** 3 - m.T_ref**3) / 3 + + k['D'] * (m.T[t] ** 4 - m.T_ref**4) / 4 + + k['E'] * (m.T[t] ** 5 - m.T_ref**5) / 5 + ) * 1e-6 # Convert [J/mol] to [MJ/mol] @m.Expression(m.trays, m.comps) def vap_enthalpy_expr(_, t, c): @@ -1081,12 +1208,13 @@ def vap_enthalpy_expr(_, t, c): """ k = m.vap_Cp_const[c] return ( - m.dH_vap[c] + - k['A'] * (m.T[t] - m.T_ref) + - k['B'] * (m.T[t] ** 2 - m.T_ref ** 2) / 2 + - k['C'] * (m.T[t] ** 3 - m.T_ref ** 3) / 3 + - k['D'] * (m.T[t] ** 4 - m.T_ref ** 4) / 4 + - k['E'] * (m.T[t] ** 5 - m.T_ref ** 5) / 5) * 1E-3 # Convert [J/mol] to [kJ/mol] + m.dH_vap[c] + + k['A'] * (m.T[t] - m.T_ref) + + k['B'] * (m.T[t] ** 2 - m.T_ref**2) / 2 + + k['C'] * (m.T[t] ** 3 - m.T_ref**3) / 3 + + k['D'] * (m.T[t] ** 4 - m.T_ref**4) / 4 + + k['E'] * (m.T[t] ** 5 - m.T_ref**5) / 5 + ) * 1e-3 # Convert [J/mol] to [kJ/mol] for t in m.conditional_trays: _build_conditional_tray_energy_balance(m, t, m.tray[t], m.no_tray[t]) @@ -1115,6 +1243,7 @@ def _build_conditional_tray_energy_balance(m, t, tray, no_tray): None None, but the energy balance constraints for the conditional tray are added to the Pyomo model. """ + @tray.Constraint() def energy_balance(_): """_summary_ @@ -1129,12 +1258,17 @@ def energy_balance(_): Pyomo.Constraint _description_ """ - return sum( - m.L[c, t + 1] * m.H_L[c, t + 1] # heat of liquid from tray above - - m.L[c, t] * m.H_L[c, t] # heat of liquid to tray below - + m.V[c, t - 1] * m.H_V[c, t - 1] # heat of vapor from tray below - - m.V[c, t] * m.H_V[c, t] # heat of vapor to tray above - for c in m.comps) * 1E-3 == 0 + return ( + sum( + m.L[c, t + 1] * m.H_L[c, t + 1] # heat of liquid from tray above + - m.L[c, t] * m.H_L[c, t] # heat of liquid to tray below + + m.V[c, t - 1] * m.H_V[c, t - 1] # heat of vapor from tray below + - m.V[c, t] * m.H_V[c, t] # heat of vapor to tray above + for c in m.comps + ) + * 1e-3 + == 0 + ) @tray.Constraint(m.comps) def liq_enthalpy_calc(_, c): @@ -1245,11 +1379,15 @@ def feed_tray_energy_balance(_): Constraint that the sum of the heat of the feed and the heat of the liquid and vapor streams is equal to zero. """ return ( - sum(m.feed[c] * ( - m.H_L_spec_feed[c] * (1 - m.feed_vap_frac) + - m.H_V_spec_feed[c] * m.feed_vap_frac) - for c in m.comps) + sum( + m.feed[c] + * ( + m.H_L_spec_feed[c] * (1 - m.feed_vap_frac) + + m.H_V_spec_feed[c] * m.feed_vap_frac + ) + for c in m.comps + ) + + sum( # Heat of liquid from tray above m.L[c, t + 1] * m.H_L[c, t + 1] # heat of liquid to tray below @@ -1258,7 +1396,9 @@ def feed_tray_energy_balance(_): + m.V[c, t - 1] * m.H_V[c, t - 1] # heat of vapor to tray above - m.V[c, t] * m.H_V[c, t] - for c in m.comps)) * 1E-3 == 0 + for c in m.comps + ) + ) * 1e-3 == 0 @m.Constraint(m.comps) def feed_tray_liq_enthalpy_calc(_, c): @@ -1317,11 +1457,12 @@ def feed_liq_enthalpy_expr(_, c): """ k = m.liq_Cp_const[c] return ( - k['A'] * (m.T_feed - m.T_ref) + - k['B'] * (m.T_feed ** 2 - m.T_ref ** 2) / 2 + - k['C'] * (m.T_feed ** 3 - m.T_ref ** 3) / 3 + - k['D'] * (m.T_feed ** 4 - m.T_ref ** 4) / 4 + - k['E'] * (m.T_feed ** 5 - m.T_ref ** 5) / 5) * 1E-6 # Convert the result from [J/mol] to [MJ/mol] + k['A'] * (m.T_feed - m.T_ref) + + k['B'] * (m.T_feed**2 - m.T_ref**2) / 2 + + k['C'] * (m.T_feed**3 - m.T_ref**3) / 3 + + k['D'] * (m.T_feed**4 - m.T_ref**4) / 4 + + k['E'] * (m.T_feed**5 - m.T_ref**5) / 5 + ) * 1e-6 # Convert the result from [J/mol] to [MJ/mol] @m.Constraint(m.comps) def feed_liq_enthalpy_calc(_, c): @@ -1360,12 +1501,13 @@ def feed_vap_enthalpy_expr(_, c): """ k = m.vap_Cp_const[c] return ( - m.dH_vap[c] + - k['A'] * (m.T_feed - m.T_ref) + - k['B'] * (m.T_feed ** 2 - m.T_ref ** 2) / 2 + - k['C'] * (m.T_feed ** 3 - m.T_ref ** 3) / 3 + - k['D'] * (m.T_feed ** 4 - m.T_ref ** 4) / 4 + - k['E'] * (m.T_feed ** 5 - m.T_ref ** 5) / 5) * 1E-3 # Convert the result from [J/mol] to [kJ/mol] + m.dH_vap[c] + + k['A'] * (m.T_feed - m.T_ref) + + k['B'] * (m.T_feed**2 - m.T_ref**2) / 2 + + k['C'] * (m.T_feed**3 - m.T_ref**3) / 3 + + k['D'] * (m.T_feed**4 - m.T_ref**4) / 4 + + k['E'] * (m.T_feed**5 - m.T_ref**5) / 5 + ) * 1e-3 # Convert the result from [J/mol] to [kJ/mol] @m.Constraint(m.comps) def feed_vap_enthalpy_calc(_, c): @@ -1418,12 +1560,18 @@ def partial_condenser_energy_balance(_): Pyomo.Constraint Constraint that the sum of the heat of the liquid distillate, the heat of the liquid to the tray below, the heat of the vapor from the tray below, and the heat of the vapor from the partial condenser is equal to zero. """ - return -m.Qc + sum( - - m.D[c] * m.H_L[c, t] # heat of liquid distillate - - m.L[c, t] * m.H_L[c, t] # heat of liquid to tray below - + m.V[c, t - 1] * m.H_V[c, t - 1] # heat of vapor from tray below - - m.V[c, t] * m.H_V[c, t] # heat of vapor from partial condenser - for c in m.comps) * 1E-3 == 0 # Convert the result from [kJ/mol] to [MJ/mol] + return ( + -m.Qc + + sum( + -m.D[c] * m.H_L[c, t] # heat of liquid distillate + - m.L[c, t] * m.H_L[c, t] # heat of liquid to tray below + + m.V[c, t - 1] * m.H_V[c, t - 1] # heat of vapor from tray below + - m.V[c, t] * m.H_V[c, t] # heat of vapor from partial condenser + for c in m.comps + ) + * 1e-3 + == 0 + ) # Convert the result from [kJ/mol] to [MJ/mol] @m.total_cond.Constraint() def total_condenser_energy_balance(_): @@ -1440,11 +1588,17 @@ def total_condenser_energy_balance(_): Pyomo.Constraint Constraint that the sum of the heat of the liquid distillate, the heat of the liquid to the tray below, and the heat of the vapor from the tray below is equal to zero. """ - return -m.Qc + sum( - - m.D[c] * m.H_L[c, t] # heat of liquid distillate - - m.L[c, t] * m.H_L[c, t] # heat of liquid to tray below - + m.V[c, t - 1] * m.H_V[c, t - 1] # heat of vapor from tray below - for c in m.comps) * 1E-3 == 0 + return ( + -m.Qc + + sum( + -m.D[c] * m.H_L[c, t] # heat of liquid distillate + - m.L[c, t] * m.H_L[c, t] # heat of liquid to tray below + + m.V[c, t - 1] * m.H_V[c, t - 1] # heat of vapor from tray below + for c in m.comps + ) + * 1e-3 + == 0 + ) @m.Constraint(m.comps) def condenser_liq_enthalpy_calc(_, c): @@ -1516,11 +1670,17 @@ def reboiler_energy_balance(_): Pyomo.Constraint Constraint that the sum of the heat of the liquid bottoms, the heat of the liquid from the tray above, the heat of the vapor to the tray above, and the heat of the vapor from the reboiler is equal to zero. """ - return m.Qb + sum( - m.L[c, t + 1] * m.H_L[c, t + 1] # Heat of liquid from tray above - - m.B[c] * m.H_L[c, t] # heat of liquid bottoms if reboiler - - m.V[c, t] * m.H_V[c, t] # heat of vapor to tray above - for c in m.comps) * 1E-3 == 0 + return ( + m.Qb + + sum( + m.L[c, t + 1] * m.H_L[c, t + 1] # Heat of liquid from tray above + - m.B[c] * m.H_L[c, t] # heat of liquid bottoms if reboiler + - m.V[c, t] * m.H_V[c, t] # heat of vapor to tray above + for c in m.comps + ) + * 1e-3 + == 0 + ) @m.Constraint(m.comps) def reboiler_liq_enthalpy_calc(_, c): From 935a0ab24577a336717cf6ed7cf4b57870ec8697 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 16 May 2024 16:40:29 -0400 Subject: [PATCH 52/60] black format fenske.py --- gdplib/gdp_col/fenske.py | 99 +++++++++++++++++++++++++++------------- 1 file changed, 68 insertions(+), 31 deletions(-) diff --git a/gdplib/gdp_col/fenske.py b/gdplib/gdp_col/fenske.py index c93ed00..10e9813 100644 --- a/gdplib/gdp_col/fenske.py +++ b/gdplib/gdp_col/fenske.py @@ -1,7 +1,14 @@ from __future__ import division -from pyomo.environ import (ConcreteModel, NonNegativeReals, Set, SolverFactory, - Var, log, sqrt) +from pyomo.environ import ( + ConcreteModel, + NonNegativeReals, + Set, + SolverFactory, + Var, + log, + sqrt, +) def calculate_Fenske(xD, xB): @@ -25,35 +32,63 @@ def calculate_Fenske(xD, xB): m.comps = Set(initialize=['benzene', 'toluene']) m.trays = Set(initialize=['condenser', 'reboiler']) m.Kc = Var( - m.comps, m.trays, doc='Phase equilibrium constant', - domain=NonNegativeReals, initialize=1, bounds=(0, 1000)) - m.T = Var(m.trays, doc='Temperature [K]', - domain=NonNegativeReals, - bounds=(min_T, max_T)) + m.comps, + m.trays, + doc='Phase equilibrium constant', + domain=NonNegativeReals, + initialize=1, + bounds=(0, 1000), + ) + m.T = Var( + m.trays, doc='Temperature [K]', domain=NonNegativeReals, bounds=(min_T, max_T) + ) m.T['condenser'].fix(82 + 273.15) m.T['reboiler'].fix(108 + 273.15) - m.P = Var(doc='Pressure [bar]', - bounds=(0, 5)) + m.P = Var(doc='Pressure [bar]', bounds=(0, 5)) m.P.fix(1.01) m.T_ref = 298.15 m.gamma = Var( - m.comps, m.trays, + m.comps, + m.trays, doc='liquid activity coefficent of component on tray', - domain=NonNegativeReals, bounds=(0, 10), initialize=1) + domain=NonNegativeReals, + bounds=(0, 10), + initialize=1, + ) m.Pvap = Var( - m.comps, m.trays, + m.comps, + m.trays, doc='pure component vapor pressure of component on tray in bar', - domain=NonNegativeReals, bounds=(1E-3, 5), initialize=0.4) + domain=NonNegativeReals, + bounds=(1e-3, 5), + initialize=0.4, + ) m.Pvap_X = Var( - m.comps, m.trays, + m.comps, + m.trays, doc='Related to fraction of critical temperature (1 - T/Tc)', - bounds=(0.25, 0.5), initialize=0.4) + bounds=(0.25, 0.5), + initialize=0.4, + ) m.pvap_const = { - 'benzene': {'A': -6.98273, 'B': 1.33213, 'C': -2.62863, - 'D': -3.33399, 'Tc': 562.2, 'Pc': 48.9}, - 'toluene': {'A': -7.28607, 'B': 1.38091, 'C': -2.83433, - 'D': -2.79168, 'Tc': 591.8, 'Pc': 41.0}} + 'benzene': { + 'A': -6.98273, + 'B': 1.33213, + 'C': -2.62863, + 'D': -3.33399, + 'Tc': 562.2, + 'Pc': 48.9, + }, + 'toluene': { + 'A': -7.28607, + 'B': 1.38091, + 'C': -2.83433, + 'D': -2.79168, + 'Tc': 591.8, + 'Pc': 41.0, + }, + } @m.Constraint(m.comps, m.trays) def phase_equil_const(_, c, t): @@ -63,7 +98,7 @@ def phase_equil_const(_, c, t): Parameters ---------- _ : Pyomo.ConcreteModel - A placeholder representing the Pyomo model instance. It specifies the context for applying the phase equilibrium constraints to the trays in the distillation column. + A placeholder representing the Pyomo model instance. It specifies the context for applying the phase equilibrium constraints to the trays in the distillation column. c : str Index of component in the distillation column model. 'benzene' or 'toluene'. t : int @@ -74,8 +109,7 @@ def phase_equil_const(_, c, t): Pyomo.Constraint The phase equilibrium constant for each component in a tray multiplied with the pressure is equal to the product of the activity coefficient and the pure component vapor pressure. """ - return m.Kc[c, t] * m.P == ( - m.gamma[c, t] * m.Pvap[c, t]) + return m.Kc[c, t] * m.P == (m.gamma[c, t] * m.Pvap[c, t]) @m.Constraint(m.comps, m.trays) def Pvap_relation(_, c, t): @@ -99,10 +133,8 @@ def Pvap_relation(_, c, t): k = m.pvap_const[c] x = m.Pvap_X[c, t] return (log(m.Pvap[c, t]) - log(k['Pc'])) * (1 - x) == ( - k['A'] * x + - k['B'] * x ** 1.5 + - k['C'] * x ** 3 + - k['D'] * x ** 6) + k['A'] * x + k['B'] * x**1.5 + k['C'] * x**3 + k['D'] * x**6 + ) @m.Constraint(m.comps, m.trays) def Pvap_X_defn(_, c, t): @@ -166,8 +198,7 @@ def relative_volatility_calc(_, t): Pyomo.Constraint The relative volatility of benzene to toluene is the ratio of the phase equilibrium constants of benzene to toluene on the tray. """ - return m.Kc['benzene', t] == ( - m.Kc['toluene', t] * m.relative_volatility[t]) + return m.Kc['benzene', t] == (m.Kc['toluene', t] * m.relative_volatility[t]) @m.Expression() def fenske(_): @@ -185,12 +216,18 @@ def fenske(_): The Fenske equation calculating the minimum number of plates required for a given separation. """ return log((xD / (1 - xD)) * (xB / (1 - xB))) / ( - log(sqrt(m.relative_volatility['condenser'] * - m.relative_volatility['reboiler']))) + log( + sqrt( + m.relative_volatility['condenser'] + * m.relative_volatility['reboiler'] + ) + ) + ) SolverFactory('ipopt').solve(m, tee=True) from pyomo.util.infeasible import log_infeasible_constraints - log_infeasible_constraints(m, tol=1E-3) + + log_infeasible_constraints(m, tol=1e-3) m.fenske.display() From 9d0cf7ad9115460115623f640300a8b4c286a689 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 16 May 2024 16:40:41 -0400 Subject: [PATCH 53/60] black initialize.py --- gdplib/gdp_col/initialize.py | 167 +++++++++++++++++++---------------- 1 file changed, 91 insertions(+), 76 deletions(-) diff --git a/gdplib/gdp_col/initialize.py b/gdplib/gdp_col/initialize.py index e0648ab..2ae7c44 100644 --- a/gdplib/gdp_col/initialize.py +++ b/gdplib/gdp_col/initialize.py @@ -11,7 +11,7 @@ def initialize(m, excel_file=None): """ - Initializes the distillation column model using data from an Excel file or default settings. + Initializes the distillation column model using data from an Excel file or default settings. Parameters ---------- @@ -20,17 +20,14 @@ def initialize(m, excel_file=None): excel_file : str, optional The file path to an Excel file containing the initialization data. Defaults to 'init.xlsx' if not provided. """ - m.reflux_frac.set_value(value( - m.reflux_ratio / (1 + m.reflux_ratio))) - m.boilup_frac.set_value(value( - m.reboil_ratio / (1 + m.reboil_ratio))) - + m.reflux_frac.set_value(value(m.reflux_ratio / (1 + m.reflux_ratio))) + m.boilup_frac.set_value(value(m.reboil_ratio / (1 + m.reboil_ratio))) + if excel_file is None: excel_file = 'init.xlsx' print(gdp_col_dir) - _excel_sheets = pandas.read_excel("%s/init.xlsx" % gdp_col_dir, - sheet_name=None) + _excel_sheets = pandas.read_excel("%s/init.xlsx" % gdp_col_dir, sheet_name=None) def set_value_if_not_fixed(var, val): """ @@ -47,9 +44,11 @@ def set_value_if_not_fixed(var, val): var.set_value(val) active_trays = [ - t for t in m.trays - if t not in m.conditional_trays or - fabs(value(m.tray[t].binary_indicator_var - 1)) <= 1E-3] + t + for t in m.trays + if t not in m.conditional_trays + or fabs(value(m.tray[t].binary_indicator_var - 1)) <= 1e-3 + ] num_active_trays = len(active_trays) feed_tray = m.feed_tray @@ -59,11 +58,9 @@ def set_value_if_not_fixed(var, val): tray_indexed_data.set_index('tray', inplace=True) comp_and_tray_indexed_data = _excel_sheets['comps_and_trays'] - comp_and_tray_indexed_data.sort_values(by=['comp', 'tray'], - inplace=True) + comp_and_tray_indexed_data.sort_values(by=['comp', 'tray'], inplace=True) comp_and_tray_indexed_data.set_index(['comp', 'tray'], inplace=True) - comp_slices = {c: comp_and_tray_indexed_data.loc[c, :] - for c in m.comps} + comp_slices = {c: comp_and_tray_indexed_data.loc[c, :] for c in m.comps} num_data_trays = tray_indexed_data.index.size if num_active_trays < num_data_trays: @@ -71,43 +68,56 @@ def set_value_if_not_fixed(var, val): # do averaging new_indices = [1] + [ 1 + (num_data_trays - 1) / (num_active_trays - 1) * i - for i in range(1, num_active_trays)] + for i in range(1, num_active_trays) + ] for tray in range(2, num_active_trays): indx = new_indices[tray - 1] lower = floor(indx) frac_above = indx - lower # Take linear combination of values tray_indexed_data.loc[tray] = ( - tray_indexed_data.loc[lower] * (1 - frac_above) + - tray_indexed_data.loc[lower + 1] * frac_above) + tray_indexed_data.loc[lower] * (1 - frac_above) + + tray_indexed_data.loc[lower + 1] * frac_above + ) for c in m.comps: comp_slices[c].loc[tray] = ( - comp_slices[c].loc[lower] * (1 - frac_above) + - comp_slices[c].loc[lower + 1] * frac_above) - tray_indexed_data.loc[num_active_trays] = \ - tray_indexed_data.loc[num_data_trays] + comp_slices[c].loc[lower] * (1 - frac_above) + + comp_slices[c].loc[lower + 1] * frac_above + ) + tray_indexed_data.loc[num_active_trays] = tray_indexed_data.loc[num_data_trays] tray_indexed_data = tray_indexed_data.head(num_active_trays) for c in m.comps: - comp_slices[c].loc[num_active_trays] = \ - comp_slices[c].loc[num_data_trays] + comp_slices[c].loc[num_active_trays] = comp_slices[c].loc[num_data_trays] comp_slices[c] = comp_slices[c].head(num_active_trays) else: # Stretch the data out and do interpolation tray_indexed_data.index = pandas.Index( - [1] + [int(round(num_active_trays / num_data_trays * i)) - for i in range(2, num_data_trays + 1)], name='tray') + [1] + + [ + int(round(num_active_trays / num_data_trays * i)) + for i in range(2, num_data_trays + 1) + ], + name='tray', + ) tray_indexed_data = tray_indexed_data.reindex( - [i for i in range(1, num_active_trays + 1)]).interpolate() + [i for i in range(1, num_active_trays + 1)] + ).interpolate() for c in m.comps: comp_slices[c].index = pandas.Index( - [1] + [int(round(num_active_trays / num_data_trays * i)) - for i in range(2, num_data_trays + 1)], name='tray') + [1] + + [ + int(round(num_active_trays / num_data_trays * i)) + for i in range(2, num_data_trays + 1) + ], + name='tray', + ) # special handling necessary for V near top of column and L # near column bottom. Do not want to interpolate with one end # being potentially 0. (ie. V from total condenser). Instead, # use back fill and forward fill. comp_slices[c] = comp_slices[c].reindex( - [i for i in range(1, num_active_trays + 1)]) + [i for i in range(1, num_active_trays + 1)] + ) tray_below_condenser = sorted(active_trays, reverse=True)[1] if pandas.isna(comp_slices[c]['V'][tray_below_condenser]): # V of the tray below the condenser is N/A. Find a valid @@ -115,8 +125,8 @@ def set_value_if_not_fixed(var, val): val = next( comp_slices[c]['V'][t] for t in reversed(list(m.trays)) - if pandas.notna(comp_slices[c]['V'][t]) - and not t == m.condens_tray) + if pandas.notna(comp_slices[c]['V'][t]) and not t == m.condens_tray + ) comp_slices[c]['V'][tray_below_condenser] = val if pandas.isna(comp_slices[c]['L'][m.reboil_tray + 1]): # L of the tray above the reboiler is N/A. Find a valid @@ -124,22 +134,19 @@ def set_value_if_not_fixed(var, val): val = next( comp_slices[c]['L'][t] for t in m.trays - if pandas.notna(comp_slices[c]['L'][t]) - and not t == m.reboil_tray) + if pandas.notna(comp_slices[c]['L'][t]) and not t == m.reboil_tray + ) comp_slices[c]['L'][m.reboil_tray + 1] = val comp_slices[c] = comp_slices[c].interpolate() - tray_indexed_data.index = pandas.Index(sorted(active_trays), - name='tray') - tray_indexed_data = tray_indexed_data.reindex(sorted(m.trays), - method='bfill') + tray_indexed_data.index = pandas.Index(sorted(active_trays), name='tray') + tray_indexed_data = tray_indexed_data.reindex(sorted(m.trays), method='bfill') for t in m.trays: set_value_if_not_fixed(m.T[t], tray_indexed_data['T [K]'][t]) for c in m.comps: - comp_slices[c].index = pandas.Index(sorted(active_trays), - name='tray') + comp_slices[c].index = pandas.Index(sorted(active_trays), name='tray') comp_slices[c] = comp_slices[c].reindex(sorted(m.trays)) comp_slices[c][['L', 'x']] = comp_slices[c][['L', 'x']].bfill() comp_slices[c][['V', 'y']] = comp_slices[c][['V', 'y']].ffill() @@ -147,14 +154,10 @@ def set_value_if_not_fixed(var, val): comp_and_tray_indexed_data = pandas.concat(comp_slices) for c, t in m.comps * m.trays: - set_value_if_not_fixed(m.L[c, t], - comp_and_tray_indexed_data['L'][c, t]) - set_value_if_not_fixed(m.V[c, t], - comp_and_tray_indexed_data['V'][c, t]) - set_value_if_not_fixed(m.x[c, t], - comp_and_tray_indexed_data['x'][c, t]) - set_value_if_not_fixed(m.y[c, t], - comp_and_tray_indexed_data['y'][c, t]) + set_value_if_not_fixed(m.L[c, t], comp_and_tray_indexed_data['L'][c, t]) + set_value_if_not_fixed(m.V[c, t], comp_and_tray_indexed_data['V'][c, t]) + set_value_if_not_fixed(m.x[c, t], comp_and_tray_indexed_data['x'][c, t]) + set_value_if_not_fixed(m.y[c, t], comp_and_tray_indexed_data['y'][c, t]) for c in m.comps: m.H_L_spec_feed[c].set_value(value(m.feed_liq_enthalpy_expr[c])) @@ -167,14 +170,22 @@ def set_value_if_not_fixed(var, val): x.set_value(value(1 - m.T[t] / k['Tc'])) - m.Pvap[c, t].set_value(value(exp(( - k['A'] * x + - k['B'] * x ** 1.5 + - k['C'] * x ** 3 + - k['D'] * x ** 6) / (1 - x)) * k['Pc'])) - - m.Kc[c, t].set_value(value( - m.gamma[c, t] * m.Pvap[c, t] / m.P)) + m.Pvap[c, t].set_value( + value( + exp( + ( + k['A'] * x + + k['B'] * x**1.5 + + k['C'] * x**3 + + k['D'] * x**6 + ) + / (1 - x) + ) + * k['Pc'] + ) + ) + + m.Kc[c, t].set_value(value(m.gamma[c, t] * m.Pvap[c, t] / m.P)) m.H_L[c, t].set_value(value(m.liq_enthalpy_expr[t, c])) m.H_V[c, t].set_value(value(m.vap_enthalpy_expr[t, c])) @@ -185,18 +196,18 @@ def set_value_if_not_fixed(var, val): m.B['toluene'].set_value(44.56072) m.L['benzene', m.reboil_tray].set_value(7.67928) m.L['toluene', m.reboil_tray].set_value(44.56072) - m.V['benzene', m.reboil_tray].set_value(value( - m.L['benzene', m.reboil_tray + 1] - - m.L['benzene', m.reboil_tray])) - m.V['toluene', m.reboil_tray].set_value(value( - m.L['toluene', m.reboil_tray + 1] - - m.L['toluene', m.reboil_tray])) - m.L['benzene', m.condens_tray].set_value(value( - m.V['benzene', m.condens_tray - 1] - - m.D['benzene'])) - m.L['toluene', m.condens_tray].set_value(value( - m.V['toluene', m.condens_tray - 1] - - m.D['toluene'])) + m.V['benzene', m.reboil_tray].set_value( + value(m.L['benzene', m.reboil_tray + 1] - m.L['benzene', m.reboil_tray]) + ) + m.V['toluene', m.reboil_tray].set_value( + value(m.L['toluene', m.reboil_tray + 1] - m.L['toluene', m.reboil_tray]) + ) + m.L['benzene', m.condens_tray].set_value( + value(m.V['benzene', m.condens_tray - 1] - m.D['benzene']) + ) + m.L['toluene', m.condens_tray].set_value( + value(m.V['toluene', m.condens_tray - 1] - m.D['toluene']) + ) for t in m.trays: m.liq[t].set_value(value(sum(m.L[c, t] for c in m.comps))) @@ -204,13 +215,17 @@ def set_value_if_not_fixed(var, val): m.bot.set_value(52.24) m.dis.set_value(47.7599) for c in m.comps: - m.x[c, m.reboil_tray].set_value(value( - m.L[c, m.reboil_tray] / m.liq[m.reboil_tray])) - m.y[c, m.reboil_tray].set_value(value( - m.V[c, m.reboil_tray] / m.vap[m.reboil_tray])) - m.x[c, m.condens_tray].set_value(value( - m.L[c, m.condens_tray] / m.liq[m.condens_tray])) - m.y[c, m.condens_tray].set_value(value( - m.x[c, m.condens_tray] * m.Kc[c, m.condens_tray])) + m.x[c, m.reboil_tray].set_value( + value(m.L[c, m.reboil_tray] / m.liq[m.reboil_tray]) + ) + m.y[c, m.reboil_tray].set_value( + value(m.V[c, m.reboil_tray] / m.vap[m.reboil_tray]) + ) + m.x[c, m.condens_tray].set_value( + value(m.L[c, m.condens_tray] / m.liq[m.condens_tray]) + ) + m.y[c, m.condens_tray].set_value( + value(m.x[c, m.condens_tray] * m.Kc[c, m.condens_tray]) + ) m.Qb.set_value(2.307873115) m.Qc.set_value(3.62641882) From aa1f07ccc41b3bb07a2e1d24c1f5ef58ce7d079a Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 16 May 2024 16:40:50 -0400 Subject: [PATCH 54/60] black main.py --- gdplib/gdp_col/main.py | 102 ++++++++++++++++++++++------------------- 1 file changed, 56 insertions(+), 46 deletions(-) diff --git a/gdplib/gdp_col/main.py b/gdplib/gdp_col/main.py index ba3e615..e9dce0f 100644 --- a/gdplib/gdp_col/main.py +++ b/gdplib/gdp_col/main.py @@ -43,9 +43,9 @@ def main(): m.BigM[None] = 100 SolverFactory('gdpopt').solve( - m, tee=True, strategy='LOA', init_strategy='fix_disjuncts', - mip_solver='glpk') - log_infeasible_constraints(m, tol=1E-3) + m, tee=True, strategy='LOA', init_strategy='fix_disjuncts', mip_solver='glpk' + ) + log_infeasible_constraints(m, tol=1e-3) display_column(m) return m @@ -60,51 +60,61 @@ def display_column(m): The distillation column model solution obtained by using GDPopt, Logic-based Outer Approximation. """ print('Objective: %s' % value(m.obj)) - print('Qc: {: >3.0f}kW DB: {: >3.0f} DT: {: >3.0f} dis: {: >3.0f}' - .format(value(m.Qc * 1E3), - value(m.D['benzene']), - value(m.D['toluene']), - value(m.dis))) + print( + 'Qc: {: >3.0f}kW DB: {: >3.0f} DT: {: >3.0f} dis: {: >3.0f}'.format( + value(m.Qc * 1e3), + value(m.D['benzene']), + value(m.D['toluene']), + value(m.dis), + ) + ) for t in reversed(list(m.trays)): - print('T{: >2.0f}-{:1.0g} T: {: >3.0f} ' - 'F: {: >4.0f} ' - 'L: {: >4.0f} V: {: >4.0f} ' - 'xB: {: >3.0f} xT: {: >3.0f} yB: {: >3.0f} yT: {: >3.0f}' - .format(t, - fabs(value(m.tray[t].indicator_var)) - if t in m.conditional_trays else 1, - value(m.T[t]) - 273.15, - value(sum(m.feed[c] for c in m.comps)) - if t == m.feed_tray else 0, - value(m.liq[t]), - value(m.vap[t]), - value(m.x['benzene', t]) * 100, - value(m.x['toluene', t]) * 100, - value(m.y['benzene', t]) * 100, - value(m.y['toluene', t]) * 100 - )) - print('Qb: {: >3.0f}kW BB: {: > 3.0f} BT: {: >3.0f} bot: {: >3.0f}' - .format(value(m.Qb * 1E3), - value(m.B['benzene']), - value(m.B['toluene']), - value(m.bot))) + print( + 'T{: >2.0f}-{:1.0g} T: {: >3.0f} ' + 'F: {: >4.0f} ' + 'L: {: >4.0f} V: {: >4.0f} ' + 'xB: {: >3.0f} xT: {: >3.0f} yB: {: >3.0f} yT: {: >3.0f}'.format( + t, + fabs(value(m.tray[t].indicator_var)) if t in m.conditional_trays else 1, + value(m.T[t]) - 273.15, + value(sum(m.feed[c] for c in m.comps)) if t == m.feed_tray else 0, + value(m.liq[t]), + value(m.vap[t]), + value(m.x['benzene', t]) * 100, + value(m.x['toluene', t]) * 100, + value(m.y['benzene', t]) * 100, + value(m.y['toluene', t]) * 100, + ) + ) + print( + 'Qb: {: >3.0f}kW BB: {: > 3.0f} BT: {: >3.0f} bot: {: >3.0f}'.format( + value(m.Qb * 1e3), + value(m.B['benzene']), + value(m.B['toluene']), + value(m.bot), + ) + ) for t in reversed(list(m.trays)): - print('T{: >2.0f}-{:1.0g} ' - 'FB: {: >3.0f} FT: {: >3.0f} ' - 'LB: {: >4.0f} LT: {: >4.0f} VB: {: >4.0f} VT: {: >4.0f}' - .format(t, - fabs(value(m.tray[t].indicator_var)) - if t in m.conditional_trays else 1, - value(m.feed['benzene']) if t == m.feed_tray else 0, - value(m.feed['toluene']) if t == m.feed_tray else 0, - value(m.L['benzene', t]), - value(m.L['toluene', t]), - value(m.V['benzene', t]), - value(m.V['toluene', t]) - )) - print('RF: {: >3.2f} RB: {: >3.2f}' - .format(value(m.reflux_frac / (1 - m.reflux_frac)), - value(m.boilup_frac / (1 - m.boilup_frac)))) + print( + 'T{: >2.0f}-{:1.0g} ' + 'FB: {: >3.0f} FT: {: >3.0f} ' + 'LB: {: >4.0f} LT: {: >4.0f} VB: {: >4.0f} VT: {: >4.0f}'.format( + t, + fabs(value(m.tray[t].indicator_var)) if t in m.conditional_trays else 1, + value(m.feed['benzene']) if t == m.feed_tray else 0, + value(m.feed['toluene']) if t == m.feed_tray else 0, + value(m.L['benzene', t]), + value(m.L['toluene', t]), + value(m.V['benzene', t]), + value(m.V['toluene', t]), + ) + ) + print( + 'RF: {: >3.2f} RB: {: >3.2f}'.format( + value(m.reflux_frac / (1 - m.reflux_frac)), + value(m.boilup_frac / (1 - m.boilup_frac)), + ) + ) if __name__ == "__main__": From 62a869d93103df7c97c36379d9035de3a4abe855 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 16 May 2024 17:14:00 -0400 Subject: [PATCH 55/60] update black format workflow --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 481c5d9..642b1a5 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -11,7 +11,7 @@ jobs: - name: Black Formatting Check uses: psf/black@stable with: - args: . -S -C --check --diff + options: "-S -C --check --diff" - name: Spell Check uses: crate-ci/typos@master From 6b486cd41c56c44959b9ed449d27c42e05009f30 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 16 May 2024 17:46:28 -0400 Subject: [PATCH 56/60] apply black format to the whole repository --- gdplib/gdp_col/initialize.py | 8 +- gdplib/hda/HDA_GDP_gdpopt.py | 2514 ++++++++++++----- gdplib/kaibel/kaibel_init.py | 261 +- gdplib/kaibel/kaibel_prop.py | 153 +- gdplib/kaibel/kaibel_side_flash.py | 127 +- gdplib/kaibel/kaibel_solve_gdp.py | 1739 ++++++------ gdplib/kaibel/main_gdp.py | 219 +- gdplib/methanol/methanol.py | 384 ++- gdplib/mod_hens/__init__.py | 23 +- gdplib/mod_hens/cafaro_approx.py | 19 +- gdplib/mod_hens/common.py | 17 +- gdplib/mod_hens/conventional.py | 1 + gdplib/mod_hens/modular_discrete.py | 1 + .../modular_discrete_single_module.py | 1 + gdplib/mod_hens/modular_integer.py | 1 + gdplib/modprodnet/__init__.py | 9 +- gdplib/modprodnet/distributed.py | 360 ++- gdplib/modprodnet/model.py | 134 +- gdplib/modprodnet/quarter_distributed.py | 401 ++- gdplib/stranded_gas/model.py | 280 +- gdplib/syngas/syngas_adapted.py | 859 ++++-- setup.py | 8 +- 22 files changed, 4863 insertions(+), 2656 deletions(-) diff --git a/gdplib/gdp_col/initialize.py b/gdplib/gdp_col/initialize.py index 2ae7c44..a5b0140 100644 --- a/gdplib/gdp_col/initialize.py +++ b/gdplib/gdp_col/initialize.py @@ -1,4 +1,5 @@ """Initialization routine for distillation column""" + from __future__ import division import pandas @@ -173,12 +174,7 @@ def set_value_if_not_fixed(var, val): m.Pvap[c, t].set_value( value( exp( - ( - k['A'] * x - + k['B'] * x**1.5 - + k['C'] * x**3 - + k['D'] * x**6 - ) + (k['A'] * x + k['B'] * x**1.5 + k['C'] * x**3 + k['D'] * x**6) / (1 - x) ) * k['Pc'] diff --git a/gdplib/hda/HDA_GDP_gdpopt.py b/gdplib/hda/HDA_GDP_gdpopt.py index ef9565b..f90c165 100644 --- a/gdplib/hda/HDA_GDP_gdpopt.py +++ b/gdplib/hda/HDA_GDP_gdpopt.py @@ -18,7 +18,6 @@ def HDA_model(): # ## 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,7 +34,6 @@ def HDA_model(): # ## sets - def strset(i): s = [] i = 1 @@ -47,9 +45,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) @@ -67,22 +67,28 @@ 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 +189,16 @@ 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 kgmole-k)' + ) Anta = {} Anta['h2'] = 13.6333 @@ -197,24 +206,21 @@ 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') 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') 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') Perm = {} for i in m.compon: Perm[i] = 0 @@ -222,282 +228,540 @@ def cppara(compon, stream): Perm['ch4'] = 2.3e-06 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 ') + 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 ') 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['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.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)') + 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)') + 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) 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): 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 +770,8 @@ 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') @@ -517,12 +781,12 @@ def boundsofe(m): 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.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) @@ -530,11 +794,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) @@ -549,61 +813,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.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]) + 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]) + 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 +969,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) @@ -641,16 +998,14 @@ 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 @@ -677,7 +1032,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]) @@ -691,7 +1046,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]) @@ -700,12 +1055,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]) @@ -736,680 +1089,1187 @@ 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]) m.specprod = Constraint(expr=m.fc[31, 'ben'] >= 0.9997 * m.f[31]) 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) def H2feed(m, compon): return m.fc[1, compon] == m.f[1] * m.f1comp[compon] + m.h2feed = Constraint(m.compon, rule=H2feed) def Tolfeed(_m, compon): return m.fc[66, compon] == m.f[66] * m.f66comp[compon] + m.tolfeed = Constraint(m.compon, rule=Tolfeed) def Tolabs(_m, compon): return m.fc[67, compon] == m.f[67] * m.f67comp[compon] + m.tolabs = Constraint(m.compon, rule=Tolabs) def build_absorber(b, absorber): - " Function for absorber" + "Function for absorber" + 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='absorbption factor equation') + [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 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: - 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 def Abssvrec(_m, i, compon): 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 def Absrec(_m, i, compon): 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 def abssimp(_m, absorb, compon): 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 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') + 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' + ) 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') + 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') 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') + 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' + ) 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') + 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') def Abspin(_m, i): - return sum(m.p[stream] for (_, stream) in m.ilabs) == sum(m.p[stream] for (_, stream) in m.ivabs) + 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) 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') + 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') + [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): 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 for compressor') + [comp], m.compon, rule=Compcmb, doc='component balance for 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 for compressor') + + b.comphb = Constraint([comp], rule=Comphb, doc='heat balance for 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") + 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 for 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)') + b.ratio = Constraint([comp], rule=Ratio, doc='pressure ratio (out to in)') m.vapor_pressure_unit_match = Param( - initialize=7500.6168, doc="unit match coeffieicnt for vapor pressure calculation") - m.actual_reflux_ratio = Param( - initialize=1.2, doc="actual reflux ratio coeffieicnt") + initialize=7500.6168, + doc="unit match coeffieicnt 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=0.05, doc="recovery specification coeffieicnt" + ) def build_distillation(b, dist): 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 volatilty') + + b.relvol = Constraint([dist], rule=Relvol, doc='average relative volatilty') def Undwood(_m, dist_): 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_): 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_): 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_): 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_coeffieicnt * 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_coeffieicnt * 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') - + b.distcmb = Constraint( + [dist], m.compon, rule=Distcmb, doc='component mass balance' + ) def build_flash(b, flsh): 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') + [flsh], m.compon, rule=Flshcmb, doc='component mass balance' + ) 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): 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') 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') + + b.furncmb = Constraint( + [furnace], m.compon, rule=Furncmb, doc='component mass balance' + ) 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 ') - + b.furnp = Constraint([furnace], rule=Furnp, doc=' pressure relation ') 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') + 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') 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') + 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') 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') + 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') def build_heater(b, 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 for heater') + Set(initialize=[heater]), rule=Hehhb, doc='heat balance for 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): 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 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)') + + b.exchcmbc = Constraint( + [exchanger], m.compon, rule=Exchcmbc, doc='component balance (cold)' + ) 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 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)') + + b.exchcmbh = Constraint( + [exchanger], m.compon, rule=Exchcmbh, doc='component balance (hot)' + ) 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') + + b.exchhbc = Constraint( + [exchanger], rule=Exchhbc, doc='heat balance for cold stream' + ) 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') + 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' + ) 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)') + 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)') 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)') + 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)') - m.membrane_recovery_sepc = Param(initialize=0.50) m.membrane_purity_sepc = Param(initialize=0.50) def build_membrane(b, 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') + + b.memcmb = Constraint( + [membrane], m.str, m.compon, rule=Memcmb, doc='component mass balance' + ) 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') + + b.flux = Constraint( + [membrane], m.str, m.compon, rule=Flux, doc='mass flux relation' + ) 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)') + + b.simp = Constraint( + [membrane], + m.str, + m.compon, + rule=Simp, + doc='mass flux relation (simplified)', + ) 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') 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') - + b.pure = Constraint([membrane], m.str, rule=Pure, doc='purity spec') def build_multiple_mixer(b, multiple_mxr): 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_): 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') + + b.pumpcmb = Constraint([pump_], m.compon, rule=Pumpcmb, doc='component balance') 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') 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') - + b.pumppr = Constraint([pump_], rule=Pumppr, doc='pressure relation') def build_multiple_splitter(b, multi_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') + + b.esum = Constraint([multi_splitter], rule=Esum, doc='split fraction relation') 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') + + b.splpi = Constraint( + [multi_splitter], m.str, rule=Splpi, doc='inlet pressure relation' + ) 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') + + b.splpo = Constraint( + [multi_splitter], m.str, rule=Splpo, doc='outlet pressure relation' + ) 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') + + b.splti = Constraint( + [multi_splitter], m.str, rule=Splti, doc='inlet temperature relation' + ) 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') - + b.splto = Constraint( + [multi_splitter], m.str, rule=Splto, doc='outlet temperature relation' + ) 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_) + 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') 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) + 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') 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') + 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') m.Prereference_factor = Param( - initialize=6.3e+10, doc="Pre-reference factor for reaction rate constant") - m.Ea_R = Param(initialize=-26167.) + initialize=6.3e10, doc="Pre-reference factor for reaction rate constant" + ) + m.Ea_R = Param(initialize=-26167.0) m.pressure_drop = Param(initialize=0.20684) m.selectivity_1 = Param(initialize=0.0036) m.selectivity_2 = Param(initialize=-1.544) @@ -1418,150 +2278,250 @@ def Valp(_m, valve): def build_reactor(b, rct): 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] + 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) 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 + 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) 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) + 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) 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): 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): 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') + + b.spl1pi = Constraint( + [splitter], m.str, rule=Spl1pi, doc='inlet pressure relation' + ) 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') + + b.spl1po = Constraint( + [splitter], m.str, rule=Spl1po, doc='outlet pressure relation' + ) 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') + + b.spl1ti = Constraint( + [splitter], m.str, rule=Spl1ti, doc='inlet temperature relation' + ) 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') + + b.spl1to = Constraint( + [splitter], m.str, rule=Spl1to, doc='outlet temperature relation' + ) + 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]) @@ -1569,7 +2529,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): @@ -1590,7 +2549,7 @@ 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) @@ -1615,9 +2574,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) @@ -1626,7 +2583,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) @@ -1642,10 +2599,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) @@ -1676,16 +2632,11 @@ 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) - # fifth disjunction: methane stabilizing selection @m.Disjunct() def methane_distillation_column(disj): @@ -1728,11 +2679,8 @@ 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) - # sixth disjunction: toluene stabilizing selection @m.Disjunct() def toluene_distillation_column(disj): @@ -1760,47 +2708,172 @@ 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.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.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="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.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): # "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" @@ -1813,50 +2886,51 @@ def profits_from_paper(m): # %% + def solve_with_gdpopt(m): ''' This function solves model m using GDPOpt ''' 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 - ) + 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 + This function solves model m using minlp transformation by either Big-M or convex hull ''' 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;' - ] - ); + m, solver='baron', tee=True, add_options=['option reslim=120;'] + ) return result - # %% + def infeasible_constraints(m): ''' This function checks infeasible constraint in the model @@ -1864,20 +2938,19 @@ def infeasible_constraints(m): log_infeasible_constraints(m) - # %% # enumeration each possible route selection by fixing binary variable values in every disjunctions -def enumerate_solutions(m): - 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'] +def enumerate_solutions(m): + 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: @@ -1922,20 +2995,39 @@ def enumerate_solutions(m): 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))) + 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): ''' @@ -1965,6 +3057,8 @@ def show_decision(m): print("toluene_column") else: print("toluene_flash") + + # %% diff --git a/gdplib/kaibel/kaibel_init.py b/gdplib/kaibel/kaibel_init.py index 82400e7..70883b5 100644 --- a/gdplib/kaibel/kaibel_init.py +++ b/gdplib/kaibel/kaibel_init.py @@ -27,7 +27,17 @@ from __future__ import division -from pyomo.environ import (exp, log10, minimize, NonNegativeReals, Objective, RangeSet, SolverFactory, value, Var) +from pyomo.environ import ( + exp, + log10, + minimize, + NonNegativeReals, + Objective, + RangeSet, + SolverFactory, + value, + Var, +) from gdplib.kaibel.kaibel_prop import get_model_with_properties @@ -35,27 +45,26 @@ def initialize_kaibel(): m = get_model_with_properties() - ## Operating conditions - m.Preb = 1.2 # Reboiler pressure in bar - m.Pcon = 1.05 # Condenser pressure in bar + 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 + 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 + 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 # Ligh 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 + c_c1 = 4 # Components in Column 1 + lc_c1 = 3 # Ligh 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) @@ -64,18 +73,19 @@ def initialize_kaibel(): 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, 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 + 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) @@ -87,11 +97,10 @@ def initialize_kaibel(): 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 + 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] @@ -100,60 +109,62 @@ def initialize_kaibel(): xi_nmin[3, 2, lc_c3] = 0.50 xi_nmin[3, 2, hc_c3] = 0.50 - - #### mn = m.clone() 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") + 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): 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") + @mn.Constraint(mn.cols, mn.sec, mn.nc2, doc="Temperature term for vapor pressure") def _column2_reduced_temperature(mn, col, sec, nc): 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") + @mn.Constraint(mn.cols, mn.sec, mn.nc3, doc="Temperature term for vapor pressure") def _column3_reduced_temperature(mn, col, sec, nc): 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): if col == 1: @@ -165,46 +176,51 @@ def _equilibrium_equation(mn, col, sec): 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 + 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 + / 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 + 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 @@ -212,7 +228,6 @@ def _equilibrium_equation(mn, col, sec): hn[2] = hc_c2 hn[3] = hc_c3 - for col in mn.cols: if col == 1: b = mn.nc1 @@ -222,37 +237,38 @@ def _equilibrium_equation(mn, col, sec): b = mn.nc3 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 + 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] + / Pnmin[sec] + ) for col in mn.cols: - xi_lhc[col, 4] = xi_nmin[col, 1, ln[col]] / \ - xi_nmin[col, 3, hn[col]] + xi_lhc[col, 4] = xi_nmin[col, 1, ln[col]] / xi_nmin[col, 3, hn[col]] for sec in mn.sec: - kl[col, sec] = yc[col, sec, ln[col]] / \ - xi_nmin[col, sec, ln[col]] - kh[col, sec] = yc[col, sec, hn[col]] / \ - xi_nmin[col, sec, hn[col]] - xi_lhc[col, sec] = xi_nmin[col, sec, hn[col]] / \ - xi_nmin[col, sec, ln[col]] + kl[col, sec] = yc[col, sec, ln[col]] / xi_nmin[col, sec, ln[col]] + kh[col, sec] = yc[col, sec, hn[col]] / xi_nmin[col, sec, hn[col]] + 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] for col in mn.cols: - av_alpha[col] = (alpha[col, 1] * alpha[col, 2] - * alpha[col, 3])**(1 / 3) - 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 + av_alpha[col] = (alpha[col, 1] * alpha[col, 2] * alpha[col, 3]) ** (1 / 3) + 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] @@ -264,7 +280,6 @@ def _equilibrium_equation(mn, col, sec): m.Tf0 = value(mn.Tnmin[1, 2]) m.TD0 = value(mn.Tnmin[2, 3]) - return m diff --git a/gdplib/kaibel/kaibel_prop.py b/gdplib/kaibel/kaibel_prop.py index bd43032..9243a9d 100644 --- a/gdplib/kaibel/kaibel_prop.py +++ b/gdplib/kaibel/kaibel_prop.py @@ -6,66 +6,62 @@ def get_model_with_properties(): - """Attach properties to the model.""" - + m = ConcreteModel() # ------------------------------------------------------------------ # 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 tays + m.c = 4 # Number of components + m.lc = 1 # Light component + m.hc = 4 # Heavy component #### 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.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.Tf = 358 # Side feed temperature in 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.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 - m.rr0 = 0.893 # Internal reflux ratio initial value - m.bu0 = 0.871 # Internal reflux ratio initial value - + 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 # @@ -86,9 +82,9 @@ def get_model_with_properties(): # # ------------------------------------------------------------------ - m.prop = {} # Properties of components: - cpL = {} # Ruczika-D method for liquid heat capacity calculation - # (Reference A, page 6.20) + m.prop = {} # Properties of components: + cpL = {} # Ruczika-D method for liquid heat capacity calculation + # (Reference A, page 6.20) sumA = {} sumB = {} sumC = {} @@ -107,45 +103,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 @@ -168,7 +167,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 @@ -190,7 +188,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 @@ -212,7 +209,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 @@ -234,5 +230,4 @@ def get_model_with_properties(): m.prop[4, 'cpC', 2] = -2.242e-4 m.prop[4, 'cpD', 2] = 4.685e-8 - return m diff --git a/gdplib/kaibel/kaibel_side_flash.py b/gdplib/kaibel/kaibel_side_flash.py index 014d447..ca2960c 100644 --- a/gdplib/kaibel/kaibel_side_flash.py +++ b/gdplib/kaibel/kaibel_side_flash.py @@ -1,103 +1,108 @@ """ Side feed flash """ - from __future__ import division 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): msf = ConcreteModel('SIDE FEED FLASH') - msf.nc = RangeSet(1, m.c, doc='Number of components') m.xfi = {} 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): - return sum(m.xfi[nc] * (1 - msf.Keqf[nc]) / \ - (1 + msf.q * (msf.Keqf[nc] - 1)) - for nc in msf.nc) == 0 + 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") + @msf.Constraint(msf.nc, doc="Side feed liquid composition") def _algx(msf, nc): 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): return msf.yf[nc] == msf.xf[nc] * msf.Keqf[nc] - - @msf.Constraint(msf.nc, - doc="Vapor-liquid equilibrium constant") + @msf.Constraint(msf.nc, doc="Vapor-liquid equilibrium constant") def _algKeq(msf, nc): 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): 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 ) ) - 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) m.yfi = {} for nc in msf.nc: m.yfi[nc] = value(msf.yf[nc]) - + m.q_init = value(msf.q) return m diff --git a/gdplib/kaibel/kaibel_solve_gdp.py b/gdplib/kaibel/kaibel_solve_gdp.py index 9c1592f..354bd50 100644 --- a/gdplib/kaibel/kaibel_solve_gdp.py +++ b/gdplib/kaibel/kaibel_solve_gdp.py @@ -1,11 +1,19 @@ """ Kaibel Column model: GDP formulation """ - from __future__ import division 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 @@ -17,119 +25,102 @@ def build_model(): m = initialize_kaibel() - # Side feed 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 - m.Qmax = 60 # Heat loads upper bound + 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 #### Column tray details - m.num_trays = m.np # Trays per section - 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.num_trays = m.np # Trays per section + 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 # ------------------------------------------------------------------ - + # Beginning of model - - # ------------------------------------------------------------------ + # ------------------------------------------------------------------ ## Sets - m.section = RangeSet(4, - doc="Column sections:1=top, 2=feed side, 3=prod side, 4=bot") + 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.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 1") - - 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.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 1") + + 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 - - m.P0 = {} # Initial pressure - m.T0 = {} # Initial temperature - m.L0 = {} # Initial individual liquid flowrate in mol/s - m.V0 = {} # Initial individual vapor flowrate - 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.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.dHvap = {} # Heat of vaporization + + m.P0 = {} # Initial pressure + m.T0 = {} # Initial temperature + m.L0 = {} # Initial individual liquid flowrate in mol/s + m.V0 = {} # Initial individual vapor flowrate + 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.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 + 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: @@ -138,25 +129,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] @@ -164,7 +147,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] for sec in m.section: for n_tray in m.tray: @@ -172,338 +155,391 @@ 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] - - + ## Enthalpy boundary values - hlb = {} # Liquid enthalpy - hvb = {} # Vapor enthalpy - cpb = {} # Heact capacity - dHvapb = {} # Heat of vaporization - Tbounds = {} # Temperature bounds - kc = {} # Light and heavy key components + hlb = {} # Liquid enthalpy + hvb = {} # Vapor enthalpy + cpb = {} # Heact capacity + dHvapb = {} # Heat of vaporization + Tbounds = {} # Temperature bounds + kc = {} # Light and heavy key components Tbounds[1] = m.Tcon Tbounds[2] = m.Treb kc[1] = m.lc kc[2] = m.hc - 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 ) ) - 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] - ) + hlb[b] = cpb[b, 1] + hvb[b] = cpb[b, 2] + 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 - for comp in m.comp: m.dHvap[comp] = dHvapb[comp] / m.Hscale - 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 + 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 ) - 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 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 in J/mol + m.hvf = {} # Vapor enthalpy for side feed in J/mol + m.F0 = {} # Side feed flowrate per component in mol/s 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 ) - + for comp in m.comp: m.F0[comp] = m.xfi[comp] * m.Fi - m.hlf[comp] = ( - m.cpdTf[comp, 1] - ) - m.hvf[comp] = ( - m.cpdTf[comp, 2] - + m.dHvap[comp] - ) - - 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", - bounds=(0.2, 0.8), - initialize=m.dl0) - m.dv = Var(m.dw, - doc="Vapor distributor", - 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", - domain=NonNegativeReals, - bounds=(0.7, 1), - initialize=m.rr0) - m.bu = Var(doc="Boilup rate", - 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, - bounds=(-1e-3, 1e-3), - initialize=0) - m.erry = Var(m.section, m.tray, - 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, - rule=_build_tray_equations) - m.tray_absent = Disjunct(m.section, m.tray, - rule=_build_pass_through_eqns) - - - @m.Disjunction(m.section, m.tray, - doc="Disjunction between whether each tray exists or not") + m.hlf[comp] = m.cpdTf[comp, 1] + m.hvf[comp] = m.cpdTf[comp, 2] + m.dHvap[comp] + + 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", bounds=(0.2, 0.8), initialize=m.dl0) + m.dv = Var( + m.dw, + doc="Vapor distributor", + 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", + domain=NonNegativeReals, + bounds=(0.7, 1), + initialize=m.rr0, + ) + m.bu = Var( + doc="Boilup rate", 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, bounds=(-1e-3, 1e-3), initialize=0) + m.erry = Var(m.section, m.tray, 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, rule=_build_tray_equations) + m.tray_absent = Disjunct(m.section, m.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): 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 + 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 + return ( + sum( + m.tray_exists[2, n_tray].binary_indicator_var + for n_tray in m.candidate_trays_feed + ) + + 1 + >= m.min_num_trays + ) @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 + 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 - - - + 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") def monotonic_temperature(m, sec, n_tray): return m.T[sec, n_tray] <= m.T[1, m.num_trays] - @m.Constraint(doc="Liquid distributor") 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): return sum(m.dv[sec] for sec in m.dw) - 1 == 0 - @m.Constraint(doc="Reboiler composition specification") def heavy_product(m): return m.x[1, m.reb_tray, m.hc] >= m.xspec_hc - @m.Constraint(doc="Condenser composition specification") def light_product(m): return m.x[4, m.con_tray, m.lc] >= m.xspec_lc @@ -512,143 +548,146 @@ def light_product(m): 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): return m.x[3, m.sideout2_tray, 2] >= m.xspec_inter2 - @m.Constraint(doc="Reboiler flowrate") def _heavy_product_flow(m): - return m.Btotal >= m.Bdes + return m.Btotal >= m.Bdes - @m.Constraint(doc="Condenser flowrate") def _light_product_flow(m): - return m.Dtotal >= m.Ddes + return m.Dtotal >= m.Ddes - @m.Constraint(m.so, doc="Intermediate flowrate") def _intermediate_product_flow(m, so): - 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): 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): 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): - 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): - 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): - 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): - 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): return m.B[comp] == m.L[1, m.reb_tray, comp] - @m.Constraint(m.comp) def condenser_total(m, comp): - 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): return sum(m.B[comp] for comp in m.comp) == m.Btotal - @m.Constraint() 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): return sum(m.S[so, comp] for comp in m.comp) == m.Stotal[so] - - - 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) - - - - + 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, + ) @m.Constraint(m.section_main, m.candidate_trays_main) 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 - @m.Constraint(m.candidate_trays_feed) def _logic_proposition_feed(m, n_tray): 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 - @m.Constraint(m.candidate_trays_product) def _logic_proposition_section3(m, n_tray): 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 - @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 - + return ( + m.tray_exists[2, n_tray].binary_indicator_var + == m.tray_exists[3, n_tray].binary_indicator_var + ) @m.Constraint() def _existent_minimum_numbertrays(m): 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): m.tray_exists[sec, n_tray].indicator_var.fix(True) m.tray_absent[sec, n_tray].deactivate() @@ -659,618 +698,652 @@ 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): 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): - 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): 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): 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): - 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): 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): 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): - 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 + 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): 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): 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): - return m.actv[1, n_tray, comp] == 1 - + return m.actv[1, n_tray, comp] == 1 - - 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): - 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): 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): 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): - 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): 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): 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): - 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): 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): 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): - return m.actv[2, n_tray, comp] == 1 + return m.actv[2, n_tray, comp] == 1 - - - 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): - 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): 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): 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): - 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): 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): 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): - 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): 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): 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): - return m.actv[3, n_tray, comp] == 1 - + return m.actv[3, n_tray, comp] == 1 - - 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): - 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): 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): 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): - 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): 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): 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): - 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): 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): 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): - 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): m = disj.model() 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): 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): 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): 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): 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): 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): 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): 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 8c8b32b..5b130eb 100644 --- a/gdplib/kaibel/main_gdp.py +++ b/gdplib/kaibel/main_gdp.py @@ -57,7 +57,7 @@ def main(): 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: @@ -70,53 +70,38 @@ 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): - 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, @@ -125,7 +110,8 @@ def intro_message(m): DOI: https://doi.org/10.1016/j.compchemeng.2019.03.006 - """) + """ + ) def display_results(m): @@ -141,71 +127,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(' ') @@ -215,15 +192,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('__________________________________________') @@ -231,21 +214,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() diff --git a/gdplib/methanol/methanol.py b/gdplib/methanol/methanol.py index dd7702c..fe85918 100644 --- a/gdplib/methanol/methanol.py +++ b/gdplib/methanol/methanol.py @@ -22,9 +22,13 @@ def fix_vars_with_equal_bounds(m, tol=1e-8): if lb is None or ub is None: continue if lb > ub + tol: - raise InfeasibleError('Variable lb is larger than ub: {0} lb: {1} ub: {2}'.format(v.name, lb, ub)) + raise InfeasibleError( + 'Variable lb is larger than ub: {0} lb: {1} ub: {2}'.format( + v.name, lb, ub + ) + ) elif abs(ub - lb) <= tol: - v.fix(0.5*(lb+ub)) + v.fix(0.5 * (lb + ub)) class MethanolModel(object): @@ -44,8 +48,8 @@ def __init__(self): self.electricity_cost = 0.255 self.cooling_cost = 700 self.heating_cost = 8000 - self.purity_demand = 0.9 #purity demand in product stream - self.demand = 1.0 # flowrate restriction on product flow + self.purity_demand = 0.9 # purity demand in product stream + self.demand = 1.0 # flowrate restriction on product flow self.flow_feed_lb = 0.5 self.flow_feed_ub = 5 self.flow_feed_temp = 3 @@ -58,7 +62,7 @@ def __init__(self): self.cheap_reactor_variable_cost = 5 self.expensive_reactor_fixed_cost = 250 self.expensive_reactor_variable_cost = 10 - self.heat_unit_match = 0.00306 + self.heat_unit_match = 0.00306 self.capacity_redundancy = 1.2 self.antoine_unit_trans = 7500.6168 self.K = 0.415 @@ -66,7 +70,7 @@ def __init__(self): self.reactor_relation = 0.9 self.purity_demand = 0.9 self.fix_electricity_cost = 175 - self.two_stage_fix_cost = 50 + self.two_stage_fix_cost = 50 m.streams = pe.Set(initialize=list(range(1, 34)), ordered=True) m.components = pe.Set(initialize=['H2', 'CO', 'CH3OH', 'CH4'], ordered=True) @@ -79,12 +83,12 @@ def __init__(self): flow_1['H2'] = 0.6 flow_1['CO'] = 0.25 flow_1['CH4'] = 0.15 - m.flow_1_composition = pe.Param(m.components,initialize = flow_1,default = 0) + m.flow_1_composition = pe.Param(m.components, initialize=flow_1, default=0) flow_2 = dict() flow_2['H2'] = 0.65 flow_2['CO'] = 0.30 flow_2['CH4'] = 0.05 - m.flow_2_composition = pe.Param(m.components,initialize = flow_2,default = 0) + m.flow_2_composition = pe.Param(m.components, initialize=flow_2, default=0) m.pressures[13].setlb(2.5) m.temps[13].setlb(4.23) @@ -158,11 +162,15 @@ def __init__(self): self.liquid_outlets[13] = 22 def _total_flow(_m, _s): - return _m.flows[_s] == sum(_m.component_flows[_s, _c] for _c in _m.components) + return _m.flows[_s] == sum( + _m.component_flows[_s, _c] for _c in _m.components + ) + m.total_flow_con = pe.Constraint(m.streams, rule=_total_flow) - m.purity_con = pe.Constraint(expr=m.component_flows[23, 'CH3OH'] >= self.purity_demand * m.flows[23]) - + m.purity_con = pe.Constraint( + expr=m.component_flows[23, 'CH3OH'] >= self.purity_demand * m.flows[23] + ) # ************************************ # Feed @@ -172,7 +180,7 @@ def _total_flow(_m, _s): self.build_stream_doesnt_exist_con(m.cheap_feed_disjunct, 2) m.cheap_feed_disjunct.feed_cons = c = pe.ConstraintList() c.add(m.component_flows[1, 'H2'] == m.flow_1_composition['H2'] * m.flows[1]) - c.add(m.component_flows[1, 'CO'] == m.flow_1_composition['CO']* m.flows[1]) + c.add(m.component_flows[1, 'CO'] == m.flow_1_composition['CO'] * m.flows[1]) c.add(m.component_flows[1, 'CH4'] == m.flow_1_composition['CH4'] * m.flows[1]) c.add(m.flows[1] >= self.flow_feed_lb) c.add(m.flows[1] <= self.flow_feed_ub) @@ -191,7 +199,9 @@ def _total_flow(_m, _s): c.add(m.temps[2] == self.flow_feed_temp) c.add(m.pressures[2] == self.flow_feed_pressure) - m.feed_disjunctions = gdp.Disjunction(expr=[m.cheap_feed_disjunct, m.expensive_feed_disjunct]) + m.feed_disjunctions = gdp.Disjunction( + expr=[m.cheap_feed_disjunct, m.expensive_feed_disjunct] + ) # ************************************ # Feed compressors @@ -213,11 +223,21 @@ def _total_flow(_m, _s): self.build_compressor(m.two_stage_feed_compressor_disjunct, 4) self.build_cooler(m.two_stage_feed_compressor_disjunct, 5) self.build_compressor(m.two_stage_feed_compressor_disjunct, 6) - m.two_stage_feed_compressor_disjunct.equal_electric_requirements = pe.Constraint(expr=m.two_stage_feed_compressor_disjunct.compressor_4.electricity_requirement == m.two_stage_feed_compressor_disjunct.compressor_6.electricity_requirement) + m.two_stage_feed_compressor_disjunct.equal_electric_requirements = pe.Constraint( + expr=m.two_stage_feed_compressor_disjunct.compressor_4.electricity_requirement + == m.two_stage_feed_compressor_disjunct.compressor_6.electricity_requirement + ) m.two_stage_feed_compressor_disjunct.exists = pe.Var(bounds=(0, 1)) - m.two_stage_feed_compressor_disjunct.exists_con = pe.Constraint(expr=m.two_stage_feed_compressor_disjunct.exists == 1) + m.two_stage_feed_compressor_disjunct.exists_con = pe.Constraint( + expr=m.two_stage_feed_compressor_disjunct.exists == 1 + ) - m.feed_compressor_disjunction = gdp.Disjunction(expr=[m.single_stage_feed_compressor_disjunct, m.two_stage_feed_compressor_disjunct]) + m.feed_compressor_disjunction = gdp.Disjunction( + expr=[ + m.single_stage_feed_compressor_disjunct, + m.two_stage_feed_compressor_disjunct, + ] + ) self.build_mixer(m, 'recycle_feed_mixer') self.build_cooler(m, 7) @@ -233,7 +253,9 @@ def _total_flow(_m, _s): self.build_stream_doesnt_exist_con(m.expensive_reactor, 16) self.build_reactor(m.expensive_reactor, 9) m.expensive_reactor.exists = pe.Var(bounds=(0, 1)) - m.expensive_reactor.exists_con = pe.Constraint(expr=m.expensive_reactor.exists == 1) + m.expensive_reactor.exists_con = pe.Constraint( + expr=m.expensive_reactor.exists == 1 + ) m.expensive_reactor.composition_cons = c = pe.ConstraintList() for _comp in m.components: c.add(m.component_flows[17, _comp] >= 0.01) @@ -250,7 +272,9 @@ def _total_flow(_m, _s): for _comp in m.components: c.add(m.component_flows[16, _comp] >= 0.01) - m.reactor_disjunction = gdp.Disjunction(expr=[m.expensive_reactor, m.cheap_reactor]) + m.reactor_disjunction = gdp.Disjunction( + expr=[m.expensive_reactor, m.cheap_reactor] + ) self.build_expansion_valve(m, 11) self.build_cooler(m, 12) @@ -265,10 +289,18 @@ def _total_flow(_m, _s): m.single_stage_recycle_compressor_disjunct = gdp.Disjunct() self.build_equal_streams(m.single_stage_recycle_compressor_disjunct, 26, 27) self.build_equal_streams(m.single_stage_recycle_compressor_disjunct, 29, 33) - self.build_stream_doesnt_exist_con(m.single_stage_recycle_compressor_disjunct, 28) - self.build_stream_doesnt_exist_con(m.single_stage_recycle_compressor_disjunct, 30) - self.build_stream_doesnt_exist_con(m.single_stage_recycle_compressor_disjunct, 31) - self.build_stream_doesnt_exist_con(m.single_stage_recycle_compressor_disjunct, 32) + self.build_stream_doesnt_exist_con( + m.single_stage_recycle_compressor_disjunct, 28 + ) + self.build_stream_doesnt_exist_con( + m.single_stage_recycle_compressor_disjunct, 30 + ) + self.build_stream_doesnt_exist_con( + m.single_stage_recycle_compressor_disjunct, 31 + ) + self.build_stream_doesnt_exist_con( + m.single_stage_recycle_compressor_disjunct, 32 + ) self.build_compressor(m.single_stage_recycle_compressor_disjunct, 16) m.two_stage_recycle_compressor_disjunct = gdp.Disjunct() @@ -279,35 +311,74 @@ def _total_flow(_m, _s): self.build_compressor(m.two_stage_recycle_compressor_disjunct, 17) self.build_cooler(m.two_stage_recycle_compressor_disjunct, 18) self.build_compressor(m.two_stage_recycle_compressor_disjunct, 19) - m.two_stage_recycle_compressor_disjunct.equal_electric_requirements = pe.Constraint(expr=m.two_stage_recycle_compressor_disjunct.compressor_17.electricity_requirement == m.two_stage_recycle_compressor_disjunct.compressor_19.electricity_requirement) + m.two_stage_recycle_compressor_disjunct.equal_electric_requirements = pe.Constraint( + expr=m.two_stage_recycle_compressor_disjunct.compressor_17.electricity_requirement + == m.two_stage_recycle_compressor_disjunct.compressor_19.electricity_requirement + ) m.two_stage_recycle_compressor_disjunct.exists = pe.Var(bounds=(0, 1)) - m.two_stage_recycle_compressor_disjunct.exists_con = pe.Constraint(expr=m.two_stage_recycle_compressor_disjunct.exists == 1) + m.two_stage_recycle_compressor_disjunct.exists_con = pe.Constraint( + expr=m.two_stage_recycle_compressor_disjunct.exists == 1 + ) - m.recycle_compressor_disjunction = gdp.Disjunction(expr=[m.single_stage_recycle_compressor_disjunct, m.two_stage_recycle_compressor_disjunct]) + m.recycle_compressor_disjunction = gdp.Disjunction( + expr=[ + m.single_stage_recycle_compressor_disjunct, + m.two_stage_recycle_compressor_disjunct, + ] + ) # ************************************ # Objective # ************************************ - + e = 0 e -= self.cost_flow_1 * m.flows[1] e -= self.cost_flow_2 * m.flows[2] e += self.price_of_product * m.flows[23] e += self.price_of_byproduct * m.flows[25] - e -= self.cheap_reactor_variable_cost * self.reactor_volume * m.cheap_reactor.exists + e -= ( + self.cheap_reactor_variable_cost + * self.reactor_volume + * m.cheap_reactor.exists + ) e -= self.cheap_reactor_fixed_cost * m.cheap_reactor.exists - e -= self.expensive_reactor_variable_cost * self.reactor_volume * m.expensive_reactor.exists + e -= ( + self.expensive_reactor_variable_cost + * self.reactor_volume + * m.expensive_reactor.exists + ) e -= self.expensive_reactor_fixed_cost * m.expensive_reactor.exists - e -= ( self.fix_electricity_cost+ self.electricity_cost) * m.single_stage_feed_compressor_disjunct.compressor_3.electricity_requirement + e -= ( + (self.fix_electricity_cost + self.electricity_cost) + * m.single_stage_feed_compressor_disjunct.compressor_3.electricity_requirement + ) e -= self.two_stage_fix_cost * m.two_stage_feed_compressor_disjunct.exists - e -= (self.fix_electricity_cost + self.electricity_cost) * m.two_stage_feed_compressor_disjunct.compressor_4.electricity_requirement - e -= (self.fix_electricity_cost + self.electricity_cost) * m.two_stage_feed_compressor_disjunct.compressor_6.electricity_requirement + e -= ( + (self.fix_electricity_cost + self.electricity_cost) + * m.two_stage_feed_compressor_disjunct.compressor_4.electricity_requirement + ) + e -= ( + (self.fix_electricity_cost + self.electricity_cost) + * m.two_stage_feed_compressor_disjunct.compressor_6.electricity_requirement + ) e -= self.cooling_cost * m.two_stage_feed_compressor_disjunct.cooler_5.heat_duty - e -= (self.fix_electricity_cost + self.electricity_cost) * m.single_stage_recycle_compressor_disjunct.compressor_16.electricity_requirement + e -= ( + (self.fix_electricity_cost + self.electricity_cost) + * m.single_stage_recycle_compressor_disjunct.compressor_16.electricity_requirement + ) e -= self.two_stage_fix_cost * m.two_stage_recycle_compressor_disjunct.exists - e -= (self.fix_electricity_cost + self.electricity_cost) * m.two_stage_recycle_compressor_disjunct.compressor_17.electricity_requirement - e -= (self.fix_electricity_cost + self.electricity_cost) * m.two_stage_recycle_compressor_disjunct.compressor_19.electricity_requirement - e -= self.cooling_cost * m.two_stage_recycle_compressor_disjunct.cooler_18.heat_duty + e -= ( + (self.fix_electricity_cost + self.electricity_cost) + * m.two_stage_recycle_compressor_disjunct.compressor_17.electricity_requirement + ) + e -= ( + (self.fix_electricity_cost + self.electricity_cost) + * m.two_stage_recycle_compressor_disjunct.compressor_19.electricity_requirement + ) + e -= ( + self.cooling_cost + * m.two_stage_recycle_compressor_disjunct.cooler_18.heat_duty + ) e -= self.cooling_cost * m.cooler_7.heat_duty e -= self.heating_cost * m.heater_8.heat_duty e -= self.cooling_cost * m.cooler_12.heat_duty @@ -324,18 +395,28 @@ def build_compressor(self, block, unit_number): out_stream = self.outlet_streams[u] b = pe.Block() - setattr(block, 'compressor_'+str(u), b) + setattr(block, 'compressor_' + str(u), b) b.p_ratio = pe.Var(bounds=(0, 1.74)) b.electricity_requirement = pe.Var(bounds=(0, 50)) def _component_balances(_b, _c): return m.component_flows[out_stream, _c] == m.component_flows[in_stream, _c] + b.component_balances = pe.Constraint(m.components, rule=_component_balances) b.t_ratio_con = pe.Constraint(expr=t[out_stream] == b.p_ratio * t[in_stream]) - b.electricity_requirement_con = pe.Constraint(expr=(b.electricity_requirement == self.alpha * - (b.p_ratio - 1) * t[in_stream] * m.flows[in_stream] / - (10.0 * self.eta * self.gamma))) - b.p_ratio_con = pe.Constraint(expr=p[out_stream]**self.gamma == b.p_ratio * p[in_stream]**self.gamma) + b.electricity_requirement_con = pe.Constraint( + expr=( + b.electricity_requirement + == self.alpha + * (b.p_ratio - 1) + * t[in_stream] + * m.flows[in_stream] + / (10.0 * self.eta * self.gamma) + ) + ) + b.p_ratio_con = pe.Constraint( + expr=p[out_stream] ** self.gamma == b.p_ratio * p[in_stream] ** self.gamma + ) def build_expansion_valve(self, block, unit_number): u = unit_number @@ -345,12 +426,16 @@ def build_expansion_valve(self, block, unit_number): in_stream = self.inlet_streams[u] out_stream = self.outlet_streams[u] b = pe.Block() - setattr(block, 'expansion_valve_'+str(u), b) + setattr(block, 'expansion_valve_' + str(u), b) def _component_balances(_b, _c): return m.component_flows[out_stream, _c] == m.component_flows[in_stream, _c] + b.component_balances = pe.Constraint(m.components, rule=_component_balances) - b.ratio_con = pe.Constraint(expr=t[out_stream] * p[in_stream] ** self.gamma == t[in_stream] * p[out_stream] ** self.gamma) + b.ratio_con = pe.Constraint( + expr=t[out_stream] * p[in_stream] ** self.gamma + == t[in_stream] * p[out_stream] ** self.gamma + ) b.expansion_con = pe.Constraint(expr=p[out_stream] <= p[in_stream]) def build_cooler(self, block, unit_number): @@ -362,13 +447,19 @@ def build_cooler(self, block, unit_number): in_stream = self.inlet_streams[u] out_stream = self.outlet_streams[u] b = pe.Block() - setattr(block, 'cooler_'+str(u), b) + setattr(block, 'cooler_' + str(u), b) b.heat_duty = pe.Var(bounds=(0, 50)) def _component_balances(_b, _c): return m.component_flows[out_stream, _c] == m.component_flows[in_stream, _c] + b.component_balances = pe.Constraint(m.components, rule=_component_balances) - b.heat_duty_con = pe.Constraint(expr=b.heat_duty == self.heat_unit_match * self.cp * (f[in_stream] * t[in_stream] - f[out_stream] * t[out_stream])) + b.heat_duty_con = pe.Constraint( + expr=b.heat_duty + == self.heat_unit_match + * self.cp + * (f[in_stream] * t[in_stream] - f[out_stream] * t[out_stream]) + ) b.pressure_con = pe.Constraint(expr=p[out_stream] == p[in_stream]) def build_heater(self, block, unit_number): @@ -380,13 +471,19 @@ def build_heater(self, block, unit_number): in_stream = self.inlet_streams[u] out_stream = self.outlet_streams[u] b = pe.Block() - setattr(block, 'heater_'+str(u), b) + setattr(block, 'heater_' + str(u), b) b.heat_duty = pe.Var(bounds=(0, 50)) def _component_balances(_b, _c): return m.component_flows[out_stream, _c] == m.component_flows[in_stream, _c] + b.component_balances = pe.Constraint(m.components, rule=_component_balances) - b.heat_duty_con = pe.Constraint(expr=b.heat_duty == self.heat_unit_match * self.cp * (f[out_stream] * t[out_stream] - f[in_stream] * t[in_stream])) + b.heat_duty_con = pe.Constraint( + expr=b.heat_duty + == self.heat_unit_match + * self.cp + * (f[out_stream] * t[out_stream] - f[in_stream] * t[in_stream]) + ) b.pressure_con = pe.Constraint(expr=p[out_stream] == p[in_stream]) def build_mixer(self, block, unit_number): @@ -398,13 +495,21 @@ def build_mixer(self, block, unit_number): in_stream1, in_stream2 = self.inlet_streams[u] out_stream = self.outlet_streams[u] b = pe.Block() - setattr(block, 'mixer_'+str(u), b) + setattr(block, 'mixer_' + str(u), b) def _component_balances(_b, _c): - return m.component_flows[out_stream, _c] == m.component_flows[in_stream1, _c] + m.component_flows[in_stream2, _c] + return ( + m.component_flows[out_stream, _c] + == m.component_flows[in_stream1, _c] + m.component_flows[in_stream2, _c] + ) + b.component_balances = pe.Constraint(m.components, rule=_component_balances) - b.average_temp = pe.Constraint(expr=(t[out_stream] * f[out_stream] == (t[in_stream1] * f[in_stream1] + - t[in_stream2] * f[in_stream2]))) + b.average_temp = pe.Constraint( + expr=( + t[out_stream] * f[out_stream] + == (t[in_stream1] * f[in_stream1] + t[in_stream2] * f[in_stream2]) + ) + ) b.pressure_con1 = pe.Constraint(expr=p[in_stream1] == p[out_stream]) b.pressure_con2 = pe.Constraint(expr=p[in_stream2] == p[out_stream]) @@ -416,18 +521,27 @@ def build_splitter(self, block, unit_number): in_stream = self.inlet_streams[u] out_stream1, out_stream2 = self.outlet_streams[u] b = pe.Block() - setattr(block, 'splitter_'+str(u), b) + setattr(block, 'splitter_' + str(u), b) b.split_fraction = pe.Var(bounds=(0, 1)) if unit_number == 'purge_splitter': b.split_fraction.setlb(0.01) b.split_fraction.setub(0.99) def _split_frac_rule(_b, _c): - return m.component_flows[out_stream1, _c] == b.split_fraction * m.component_flows[in_stream, _c] + return ( + m.component_flows[out_stream1, _c] + == b.split_fraction * m.component_flows[in_stream, _c] + ) + b.split_frac_con = pe.Constraint(m.components, rule=_split_frac_rule) def _component_balances(_b, _c): - return m.component_flows[in_stream, _c] == m.component_flows[out_stream1, _c] + m.component_flows[out_stream2, _c] + return ( + m.component_flows[in_stream, _c] + == m.component_flows[out_stream1, _c] + + m.component_flows[out_stream2, _c] + ) + b.component_balances = pe.Constraint(m.components, rule=_component_balances) b.temp_con1 = pe.Constraint(expr=t[in_stream] == t[out_stream1]) b.temp_con2 = pe.Constraint(expr=t[in_stream] == t[out_stream2]) @@ -443,6 +557,7 @@ def build_equal_streams(self, block, stream1, stream2): def _component_balances(_b, _c): return m.component_flows[stream2, _c] == m.component_flows[stream1, _c] + b.component_balances = pe.Constraint(m.components, rule=_component_balances) b.temp_con = pe.Constraint(expr=t[stream1] == t[stream2]) b.pressure_con = pe.Constraint(expr=p[stream1] == p[stream2]) @@ -458,7 +573,7 @@ def build_reactor(self, block, unit_number): out_stream = self.outlet_streams[u] b = pe.Block() - setattr(block, 'reactor_'+str(u), b) + setattr(block, 'reactor_' + str(u), b) b.consumption_rate = pe.Var(bounds=(0, 5)) b.conversion = pe.Var(bounds=(0, 0.42)) b.equilibrium_conversion = pe.Var(bounds=(0, 0.42)) @@ -473,25 +588,46 @@ def build_reactor(self, block, unit_number): b.t_inv_con = pe.Constraint(expr=b.temp * b.t_inv == 1) fbbt(b.p_sq_inv_con) # just getting bounds on p_sq_inv fbbt(b.t_inv_con) # just getting bounds on t_inv - b.conversion_consumption_con = pe.Constraint(expr=b.consumption_rate == b.conversion * component_f[in_stream, - key]) - b.energy_balance = pe.Constraint(expr=(f[in_stream]*t[in_stream] - f[out_stream]*t[ - out_stream])*self.cp == 0.01*self.heat_of_reaction*b.consumption_rate) - b.H2_balance = pe.Constraint(expr=component_f[out_stream,'H2'] == component_f[in_stream,'H2'] - - b.consumption_rate) - b.CO_balance = pe.Constraint(expr=component_f[out_stream,'CO'] == component_f[in_stream,'CO'] - - 0.5*b.consumption_rate) - b.CH3OH_balance = pe.Constraint(expr=component_f[out_stream,'CH3OH'] == component_f[in_stream,'CH3OH'] + - 0.5*b.consumption_rate) - b.CH4_balance = pe.Constraint(expr=component_f[out_stream,'CH4'] == component_f[in_stream,'CH4']) - b.eq_conversion_con = pe.Constraint(expr=b.equilibrium_conversion == self.K * - (1 - (self.delta_H*pe.exp(-18*b.t_inv)*b.p_sq_inv))) - b.conversion_con = pe.Constraint(expr=b.conversion * f[in_stream] == b.equilibrium_conversion * - (1-pe.exp(-self.volume_conversion[u]*self.reactor_volume)) * - (component_f[in_stream,'H2'] + component_f[in_stream, 'CO'] + - component_f[in_stream, 'CH3OH'])) + b.conversion_consumption_con = pe.Constraint( + expr=b.consumption_rate == b.conversion * component_f[in_stream, key] + ) + b.energy_balance = pe.Constraint( + expr=(f[in_stream] * t[in_stream] - f[out_stream] * t[out_stream]) * self.cp + == 0.01 * self.heat_of_reaction * b.consumption_rate + ) + b.H2_balance = pe.Constraint( + expr=component_f[out_stream, 'H2'] + == component_f[in_stream, 'H2'] - b.consumption_rate + ) + b.CO_balance = pe.Constraint( + expr=component_f[out_stream, 'CO'] + == component_f[in_stream, 'CO'] - 0.5 * b.consumption_rate + ) + b.CH3OH_balance = pe.Constraint( + expr=component_f[out_stream, 'CH3OH'] + == component_f[in_stream, 'CH3OH'] + 0.5 * b.consumption_rate + ) + b.CH4_balance = pe.Constraint( + expr=component_f[out_stream, 'CH4'] == component_f[in_stream, 'CH4'] + ) + b.eq_conversion_con = pe.Constraint( + expr=b.equilibrium_conversion + == self.K * (1 - (self.delta_H * pe.exp(-18 * b.t_inv) * b.p_sq_inv)) + ) + b.conversion_con = pe.Constraint( + expr=b.conversion * f[in_stream] + == b.equilibrium_conversion + * (1 - pe.exp(-self.volume_conversion[u] * self.reactor_volume)) + * ( + component_f[in_stream, 'H2'] + + component_f[in_stream, 'CO'] + + component_f[in_stream, 'CH3OH'] + ) + ) b.pressure_con1 = pe.Constraint(expr=b.pressure == p[in_stream]) - b.pressure_con2 = pe.Constraint(expr=p[out_stream] == self.reactor_relation*b.pressure) + b.pressure_con2 = pe.Constraint( + expr=p[out_stream] == self.reactor_relation * b.pressure + ) b.temp_con = pe.Constraint(expr=b.temp == t[out_stream]) def build_flash(self, block, unit_number): @@ -504,7 +640,7 @@ def build_flash(self, block, unit_number): vapor_stream = self.vapor_outlets[u] liquid_stream = self.liquid_outlets[u] b = pe.Block() - setattr(block, 'flash_'+str(u), b) + setattr(block, 'flash_' + str(u), b) b.vapor_pressure = pe.Var(m.components, bounds=(0.001, 80)) b.flash_t = pe.Var(bounds=(3, 5)) @@ -528,23 +664,50 @@ def build_flash(self, block, unit_number): b.antoine_C['CH4'] = -7.16 def _component_balances(_b, _c): - return m.component_flows[in_stream, _c] == m.component_flows[vapor_stream, _c] + m.component_flows[liquid_stream, _c] + return ( + m.component_flows[in_stream, _c] + == m.component_flows[vapor_stream, _c] + + m.component_flows[liquid_stream, _c] + ) + b.component_balances = pe.Constraint(m.components, rule=_component_balances) def _antoine(_b, _c): - return (_b.antoine_A[_c] - pe.log(self.antoine_unit_trans * _b.vapor_pressure[_c])) * (100 * _b.flash_t - _b.antoine_C[_c]) == _b.antoine_B[_c] + return ( + _b.antoine_A[_c] + - pe.log(self.antoine_unit_trans * _b.vapor_pressure[_c]) + ) * (100 * _b.flash_t - _b.antoine_C[_c]) == _b.antoine_B[_c] + b.antoine_con = pe.Constraint(m.components, rule=_antoine) def _vle(_b, _c): - return _b.vapor_recovery['H2'] * (_b.vapor_recovery[_c] * _b.vapor_pressure['H2'] + (1 - _b.vapor_recovery[_c]) * _b.vapor_pressure[_c]) == _b.vapor_pressure['H2'] * _b.vapor_recovery[_c] + return ( + _b.vapor_recovery['H2'] + * ( + _b.vapor_recovery[_c] * _b.vapor_pressure['H2'] + + (1 - _b.vapor_recovery[_c]) * _b.vapor_pressure[_c] + ) + == _b.vapor_pressure['H2'] * _b.vapor_recovery[_c] + ) + b.vle_set = pe.Set(initialize=['CO', 'CH3OH', 'CH4'], ordered=True) b.vle_con = pe.Constraint(b.vle_set, rule=_vle) def _vapor_recovery(_b, _c): - return m.component_flows[vapor_stream, _c] == _b.vapor_recovery[_c] * m.component_flows[in_stream, _c] + return ( + m.component_flows[vapor_stream, _c] + == _b.vapor_recovery[_c] * m.component_flows[in_stream, _c] + ) + b.vapor_recovery_con = pe.Constraint(m.components, rule=_vapor_recovery) - b.total_p_con = pe.Constraint(expr=b.flash_p*f[liquid_stream] == sum(b.vapor_pressure[_c] * m.component_flows[liquid_stream, _c] for _c in m.components)) + b.total_p_con = pe.Constraint( + expr=b.flash_p * f[liquid_stream] + == sum( + b.vapor_pressure[_c] * m.component_flows[liquid_stream, _c] + for _c in m.components + ) + ) b.flash_p_con = pe.ConstraintList() b.flash_p_con.add(b.flash_p == p[in_stream]) @@ -561,22 +724,35 @@ def build_stream_doesnt_exist_con(self, block, stream): b = pe.Block() setattr(block, 'stream_doesnt_exist_con_' + str(stream), b) b.zero_flow_con = pe.Constraint(expr=m.flows[stream] == 0) + def _zero_component_flows(_b, _c): return m.component_flows[stream, _c] == 0 - b.zero_component_flows_con = pe.Constraint(m.components, rule=_zero_component_flows) + + b.zero_component_flows_con = pe.Constraint( + m.components, rule=_zero_component_flows + ) b.fixed_temp_con = pe.Constraint(expr=m.temps[stream] == 3) b.fixed_pressure_con = pe.Constraint(expr=m.pressures[stream] == 1) def enumerate_solutions(): import time + feed_choices = ['cheap', 'expensive'] feed_compressor_choices = ['single_stage', 'two_stage'] reactor_choices = ['cheap', 'expensive'] recycle_compressor_choices = ['single_stage', 'two_stage'] - print('{0:<20}{1:<20}{2:<20}{3:<20}{4:<20}{5:<20}'.format('feed choice', 'feed compressor', 'reactor choice', - 'recycle compressor', 'termination cond', 'profit')) + print( + '{0:<20}{1:<20}{2:<20}{3:<20}{4:<20}{5:<20}'.format( + 'feed choice', + 'feed compressor', + 'reactor choice', + 'recycle compressor', + 'termination cond', + 'profit', + ) + ) since = time.time() for feed_choice in feed_choices: for feed_compressor_choice in feed_compressor_choices: @@ -584,13 +760,16 @@ def enumerate_solutions(): for recycle_compressor_choice in recycle_compressor_choices: m = MethanolModel() m = m.model - for _d in m.component_data_objects(gdp.Disjunct, descend_into=True, active=True, sort=True): + 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): + 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)) - if feed_choice == 'cheap': m.cheap_feed_disjunct.indicator_var.fix(1) m.expensive_feed_disjunct.indicator_var.fix(0) @@ -633,25 +812,38 @@ def enumerate_solutions(): opt = pe.SolverFactory('ipopt') res = opt.solve(m, tee=False) - print('{0:<20}{1:<20}{2:<20}{3:<20}{4:<20}{5:<20}'.format(feed_choice, feed_compressor_choice, - reactor_choice, recycle_compressor_choice, - str(res.solver.termination_condition), - str(-pe.value(m.objective)))) - time_elapsed = time.time() - since + print( + '{0:<20}{1:<20}{2:<20}{3:<20}{4:<20}{5:<20}'.format( + feed_choice, + feed_compressor_choice, + reactor_choice, + recycle_compressor_choice, + str(res.solver.termination_condition), + str(-pe.value(m.objective)), + ) + ) + time_elapsed = time.time() - since print('The code run {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60)) return m - + + def solve_with_gdp_opt(): m = MethanolModel().model - for _d in m.component_data_objects(gdp.Disjunct, descend_into=True, active=True, sort=True): + 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): + 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)) opt = pe.SolverFactory('gdpopt') - res = opt.solve(m, algorithm='LOA', mip_solver='gams',nlp_solver='gams',tee=True) - for d in m.component_data_objects(ctype=gdp.Disjunct, active=True, sort=True, descend_into=True): + res = opt.solve(m, algorithm='LOA', mip_solver='gams', nlp_solver='gams', tee=True) + for d in m.component_data_objects( + ctype=gdp.Disjunct, active=True, sort=True, descend_into=True + ): if d.indicator_var.value == 1: print(d.name) print(res) @@ -661,8 +853,8 @@ def solve_with_gdp_opt(): if __name__ == '__main__': logging.basicConfig(level=logging.DEBUG, filename='out.log') - from pyomo.util.model_size import * + from pyomo.util.model_size import * + # m = enumerate() m = solve_with_gdp_opt() print(build_model_size_report(m)) - \ No newline at end of file diff --git a/gdplib/mod_hens/__init__.py b/gdplib/mod_hens/__init__.py index 7ca419a..96bc5d3 100644 --- a/gdplib/mod_hens/__init__.py +++ b/gdplib/mod_hens/__init__.py @@ -1,11 +1,16 @@ from functools import partial from .conventional import build_conventional as _conv -from .modular_discrete import build_modular_option as _disc_opt, build_require_modular as _disc_mod +from .modular_discrete import ( + build_modular_option as _disc_opt, + build_require_modular as _disc_mod, +) from .modular_discrete_single_module import build_single_module as _disc_sing from .modular_integer import ( - build_modular_option as _int_opt, build_require_modular as _int_mod, - build_single_module as _int_sing, ) + build_modular_option as _int_opt, + build_require_modular as _int_mod, + 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) @@ -16,6 +21,12 @@ 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'] +__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', +] diff --git a/gdplib/mod_hens/cafaro_approx.py b/gdplib/mod_hens/cafaro_approx.py index 28e4518..236544f 100644 --- a/gdplib/mod_hens/cafaro_approx.py +++ b/gdplib/mod_hens/cafaro_approx.py @@ -11,9 +11,18 @@ (2) cost = factor * k * ln(bx + 1) """ + from __future__ import division -from pyomo.environ import (ConcreteModel, Constraint, log, NonNegativeReals, SolverFactory, value, Var) +from pyomo.environ import ( + ConcreteModel, + Constraint, + log, + NonNegativeReals, + SolverFactory, + value, + Var, +) def calculate_cafaro_coefficients(area1, area2, exponent): @@ -39,7 +48,7 @@ def calculate_cafaro_coefficients(area1, area2, exponent): ------- tuple of float A tuple containing the coefficients `k` and `b`. - + References ---------- [1] Cafaro, D. C., & Grossmann, I. E. (2014). Alternate approximation of concave cost functions for process design and supply chain optimization problems. Computers & chemical engineering, 60, 376-380. https://doi.org/10.1016/j.compchemeng.2013.10.001 @@ -48,10 +57,8 @@ def calculate_cafaro_coefficients(area1, area2, exponent): m.k = Var(domain=NonNegativeReals) m.b = Var(domain=NonNegativeReals) - m.c1 = Constraint( - expr=area1 ** exponent == m.k * log(m.b * area1 + 1)) - m.c2 = Constraint( - expr=area2 ** exponent == m.k * log(m.b * area2 + 1)) + m.c1 = Constraint(expr=area1**exponent == m.k * log(m.b * area1 + 1)) + m.c2 = Constraint(expr=area2**exponent == m.k * log(m.b * area2 + 1)) SolverFactory('ipopt').solve(m) diff --git a/gdplib/mod_hens/common.py b/gdplib/mod_hens/common.py index 04722cb..2bc7c5f 100644 --- a/gdplib/mod_hens/common.py +++ b/gdplib/mod_hens/common.py @@ -13,6 +13,7 @@ References: Yee, T. F., & Grossmann, I. E. (1990). Simultaneous optimization models for heat integration—II. Heat exchanger network synthesis. Computers & Chemical Engineering, 14(10), 1165–1184. https://doi.org/10.1016/0098-1354(90)85010-8 """ + from __future__ import division from pyomo.environ import ( @@ -407,14 +408,16 @@ def overall_utility_stream_usage(m, strm): for stg in m.stages ) if strm in m.cold_utility_streams - else 0 - + sum( - m.heat_exchanged[strm, cold, stg] - for cold in m.cold_process_streams - for stg in m.stages + else ( + 0 + + sum( + m.heat_exchanged[strm, cold, stg] + for cold in m.cold_process_streams + for stg in m.stages + ) + if strm in m.hot_utility_streams + else 0 ) - if strm in m.hot_utility_streams - else 0 ) @m.Constraint( diff --git a/gdplib/mod_hens/conventional.py b/gdplib/mod_hens/conventional.py index 3eec0fb..a6f8080 100644 --- a/gdplib/mod_hens/conventional.py +++ b/gdplib/mod_hens/conventional.py @@ -7,6 +7,7 @@ This is an implementation of the conventional problem. """ + from __future__ import division from pyomo.environ import Constraint, log, value diff --git a/gdplib/mod_hens/modular_discrete.py b/gdplib/mod_hens/modular_discrete.py index 66205b1..6c70f1f 100644 --- a/gdplib/mod_hens/modular_discrete.py +++ b/gdplib/mod_hens/modular_discrete.py @@ -11,6 +11,7 @@ the nonlinear expressions. """ + from __future__ import division from pyomo.environ import Binary, Constraint, log, Set, Var diff --git a/gdplib/mod_hens/modular_discrete_single_module.py b/gdplib/mod_hens/modular_discrete_single_module.py index f3f863a..0fbe980 100644 --- a/gdplib/mod_hens/modular_discrete_single_module.py +++ b/gdplib/mod_hens/modular_discrete_single_module.py @@ -12,6 +12,7 @@ exchanger module type (size). """ + from __future__ import division from pyomo.environ import Binary, Constraint, RangeSet, Var diff --git a/gdplib/mod_hens/modular_integer.py b/gdplib/mod_hens/modular_integer.py index 6034037..6a4ccee 100644 --- a/gdplib/mod_hens/modular_integer.py +++ b/gdplib/mod_hens/modular_integer.py @@ -8,6 +8,7 @@ modules using integer variables for module selection. """ + from __future__ import division from pyomo.environ import Constraint, Integers, log, Var diff --git a/gdplib/modprodnet/__init__.py b/gdplib/modprodnet/__init__.py index 1bd64af..b0866e5 100644 --- a/gdplib/modprodnet/__init__.py +++ b/gdplib/modprodnet/__init__.py @@ -8,5 +8,10 @@ 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'] +__all__ = [ + 'build_cap_expand_growth', + 'build_cap_expand_dip', + 'build_cap_expand_decay', + 'build_distributed_model', + 'build_quarter_distributed_model', +] diff --git a/gdplib/modprodnet/distributed.py b/gdplib/modprodnet/distributed.py index 1988b4f..e4fa585 100644 --- a/gdplib/modprodnet/distributed.py +++ b/gdplib/modprodnet/distributed.py @@ -19,7 +19,9 @@ def build_model(): xls_data = pd.read_excel( os.path.join(os.path.dirname(__file__), "multiple_market_size.xlsx"), - sheet_name=["demand", "locations"], index_col=0) + sheet_name=["demand", "locations"], + index_col=0, + ) @m.Param(m.markets, m.months) def market_demand(m, mkt, mo): @@ -31,20 +33,22 @@ def transport_cost(m, mo): m.route_fixed_cost = Param( initialize=100, - doc="Cost of establishing a route from a modular site to a market") + doc="Cost of establishing a route from a modular site to a market", + ) m.conv_x = Var(bounds=(0, 300), doc="x-coordinate of centralized plant.") m.conv_y = Var(bounds=(0, 300), doc="y-coordinate of centralized plant.") - m.conv_size = Var(bounds=(10, 500), initialize=10, - doc="Size of conventional plant.") + m.conv_size = Var( + bounds=(10, 500), initialize=10, doc="Size of conventional plant." + ) m.conv_cost = Var() m.conv_base_cost = Param(initialize=1000, doc="Cost for size 20") m.conv_exponent = Param(initialize=0.6) m.cost_calc = Constraint( - expr=m.conv_cost == ( - m.conv_base_cost * (m.conv_size / 20) ** m.conv_exponent)) + expr=m.conv_cost == (m.conv_base_cost * (m.conv_size / 20) ** m.conv_exponent) + ) @m.Param(m.markets) def mkt_x(m, mkt): @@ -58,17 +62,21 @@ def mkt_y(m, mkt): @m.Constraint(m.markets) def distance_calculation(m, mkt): - return m.dist_to_mkt[mkt] == sqrt( - (m.conv_x / 300 - m.mkt_x[mkt] / 300)**2 + - (m.conv_y / 300 - m.mkt_y[mkt] / 300)**2) * 300 + return ( + m.dist_to_mkt[mkt] + == sqrt( + (m.conv_x / 300 - m.mkt_x[mkt] / 300) ** 2 + + (m.conv_y / 300 - m.mkt_y[mkt] / 300) ** 2 + ) + * 300 + ) m.shipments_to_mkt = Var(m.markets, m.months, bounds=(0, 100)) m.production = Var(m.months, bounds=(0, 500)) @m.Constraint(m.months) def production_satisfaction(m, mo): - return m.production[mo] == sum(m.shipments_to_mkt[mkt, mo] - for mkt in m.markets) + return m.production[mo] == sum(m.shipments_to_mkt[mkt, mo] for mkt in m.markets) @m.Constraint(m.months) def size_requirement(m, mo): @@ -83,12 +91,15 @@ def demand_satisfaction(m, mkt, mo): m.variable_shipment_cost = Expression( expr=sum( - m.shipments_to_mkt[mkt, mo] * m.dist_to_mkt[mkt] * - m.transport_cost[mo] - for mkt in m.markets for mo in m.months)) + m.shipments_to_mkt[mkt, mo] * m.dist_to_mkt[mkt] * m.transport_cost[mo] + for mkt in m.markets + for mo in m.months + ) + ) m.total_cost = Objective( - expr=m.variable_shipment_cost + m.conv_cost + 5 * m.route_fixed_cost) + expr=m.variable_shipment_cost + m.conv_cost + 5 * m.route_fixed_cost + ) return m @@ -103,14 +114,15 @@ def build_modular_model(): m.markets = RangeSet(5) m.modular_sites = RangeSet(1, 3) m.site_pairs = Set( - initialize=m.modular_sites * m.modular_sites, - filter=lambda _, x, y: not x == y) - m.unique_site_pairs = Set( - initialize=m.site_pairs, filter=lambda _, x, y: x < y) + initialize=m.modular_sites * m.modular_sites, filter=lambda _, x, y: not x == y + ) + m.unique_site_pairs = Set(initialize=m.site_pairs, filter=lambda _, x, y: x < y) xls_data = pd.read_excel( os.path.join(os.path.dirname(__file__), "multiple_market_size.xlsx"), - sheet_name=["demand", "locations"], index_col=0) + sheet_name=["demand", "locations"], + index_col=0, + ) @m.Param(m.markets, m.months) def market_demand(m, mkt, mo): @@ -122,11 +134,13 @@ def transport_cost(m, mo): m.route_fixed_cost = Param( initialize=100, - doc="Cost of establishing a route from a modular site to a market") + doc="Cost of establishing a route from a modular site to a market", + ) @m.Param(m.months, doc="Cost of transporting a module one mile") def modular_transport_cost(m, mo): return 1 * (1 + m.discount_rate / 12) ** (-mo / 12) + m.modular_base_cost = Param(initialize=1000, doc="Cost for size 20") @m.Param(m.months) @@ -141,32 +155,27 @@ def mkt_x(m, mkt): def mkt_y(m, mkt): return float(xls_data["locations"]["y"]["market%s" % mkt]) - m.site_x = Var( - m.modular_sites, bounds=(0, 300), initialize={ - 1: 50, 2: 225, 3: 250}) - m.site_y = Var( - m.modular_sites, bounds=(0, 300), initialize={ - 1: 300, 2: 275, 3: 50}) + m.site_x = Var(m.modular_sites, bounds=(0, 300), initialize={1: 50, 2: 225, 3: 250}) + m.site_y = Var(m.modular_sites, bounds=(0, 300), initialize={1: 300, 2: 275, 3: 50}) - m.num_modules = Var( - m.modular_sites, m.months, domain=Integers, bounds=(0, 25)) + m.num_modules = Var(m.modular_sites, m.months, domain=Integers, bounds=(0, 25)) m.modules_transferred = Var( - m.site_pairs, m.months, - domain=Integers, bounds=(0, 25), - doc="Number of modules moved from one site to another in a month.") + m.site_pairs, + m.months, + domain=Integers, + bounds=(0, 25), + doc="Number of modules moved from one site to another in a month.", + ) # m.modules_transferred[...].fix(0) - m.modules_added = Var( - m.modular_sites, m.months, domain=Integers, bounds=(0, 25)) + m.modules_added = Var(m.modular_sites, m.months, domain=Integers, bounds=(0, 25)) - m.dist_to_mkt = Var( - m.modular_sites, m.markets, bounds=(0, sqrt(300**2 + 300**2))) + m.dist_to_mkt = Var(m.modular_sites, m.markets, bounds=(0, sqrt(300**2 + 300**2))) m.sqr_scaled_dist_to_mkt = Var( - m.modular_sites, m.markets, bounds=(0.001, 8), initialize=0.001) + m.modular_sites, m.markets, bounds=(0.001, 8), initialize=0.001 + ) m.dist_to_site = Var(m.site_pairs, bounds=(0, sqrt(300**2 + 300**2))) - m.sqr_scaled_dist_to_site = Var( - m.site_pairs, bounds=(0.001, 8), initialize=0.001) - m.shipments_to_mkt = Var( - m.modular_sites, m.markets, m.months, bounds=(0, 100)) + m.sqr_scaled_dist_to_site = Var(m.site_pairs, bounds=(0.001, 8), initialize=0.001) + m.shipments_to_mkt = Var(m.modular_sites, m.markets, m.months, bounds=(0, 100)) m.production = Var(m.modular_sites, m.months, bounds=(0, 500)) @m.Disjunct(m.modular_sites) @@ -174,7 +183,8 @@ def site_active(disj, site): @disj.Constraint(m.months) def production_satisfaction(site_disj, mo): return m.production[site, mo] == sum( - m.shipments_to_mkt[site, mkt, mo] for mkt in m.markets) + m.shipments_to_mkt[site, mkt, mo] for mkt in m.markets + ) @disj.Constraint(m.months) def production_limit(site_disj, mo): @@ -183,34 +193,49 @@ def production_limit(site_disj, mo): @disj.Constraint(m.months) def module_balance(site_disj, mo): existing_modules = m.num_modules[site, mo - 1] if mo >= 1 else 0 - new_modules = (m.modules_added[site, mo - m.modular_setup_time] - if mo > m.modular_setup_time else 0) + new_modules = ( + m.modules_added[site, mo - m.modular_setup_time] + if mo > m.modular_setup_time + else 0 + ) xfrd_in_modules = sum( - m.modules_transferred[from_site, - site, mo - m.modular_move_time] + m.modules_transferred[from_site, site, mo - m.modular_move_time] for from_site in m.modular_sites - if (not from_site == site) and mo > m.modular_move_time) + if (not from_site == site) and mo > m.modular_move_time + ) xfrd_out_modules = sum( m.modules_transferred[site, to_site, mo] - for to_site in m.modular_sites if not to_site == site) + for to_site in m.modular_sites + if not to_site == site + ) return m.num_modules[site, mo] == ( - new_modules + xfrd_in_modules - xfrd_out_modules + - existing_modules) + new_modules + xfrd_in_modules - xfrd_out_modules + existing_modules + ) @m.Disjunct(m.modular_sites) def site_inactive(disj, site): disj.no_modules = Constraint( - expr=sum(m.num_modules[site, mo] for mo in m.months) == 0) + expr=sum(m.num_modules[site, mo] for mo in m.months) == 0 + ) disj.no_module_transfer = Constraint( - expr=sum(m.modules_transferred[site1, site2, mo] - for site1, site2 in m.site_pairs - for mo in m.months - if site1 == site or site2 == site) == 0) + expr=sum( + m.modules_transferred[site1, site2, mo] + for site1, site2 in m.site_pairs + for mo in m.months + if site1 == site or site2 == site + ) + == 0 + ) disj.no_shipments = Constraint( - expr=sum(m.shipments_to_mkt[site, mkt, mo] - for mkt in m.markets for mo in m.months) == 0) + expr=sum( + m.shipments_to_mkt[site, mkt, mo] + for mkt in m.markets + for mo in m.months + ) + == 0 + ) @m.Disjunction(m.modular_sites) def site_active_or_not(m, site): @@ -219,40 +244,52 @@ def site_active_or_not(m, site): @m.Constraint(m.modular_sites, doc="Symmetry breaking for site activation") def site_active_ordering(m, site): if site + 1 <= max(m.modular_sites): - return (m.site_active[site].binary_indicator_var >= - m.site_active[site + 1].binary_indicator_var) + return ( + m.site_active[site].binary_indicator_var + >= m.site_active[site + 1].binary_indicator_var + ) else: return Constraint.NoConstraint @m.Disjunct(m.unique_site_pairs) def pair_active(disj, site1, site2): disj.site1_active = Constraint( - expr=m.site_active[site1].binary_indicator_var == 1) + expr=m.site_active[site1].binary_indicator_var == 1 + ) disj.site2_active = Constraint( - expr=m.site_active[site2].binary_indicator_var == 1) + expr=m.site_active[site2].binary_indicator_var == 1 + ) disj.site_distance_calc = Constraint( - expr=m.dist_to_site[site1, site2] == sqrt( - m.sqr_scaled_dist_to_site[site1, site2]) * 150) + expr=m.dist_to_site[site1, site2] + == sqrt(m.sqr_scaled_dist_to_site[site1, site2]) * 150 + ) disj.site_distance_symmetry = Constraint( - expr=m.dist_to_site[site1, site2] == m.dist_to_site[site2, site1]) + expr=m.dist_to_site[site1, site2] == m.dist_to_site[site2, site1] + ) disj.site_sqr_distance_calc = Constraint( - expr=m.sqr_scaled_dist_to_site[site1, site2] == ( - (m.site_x[site1] / 150 - m.site_x[site2] / 150)**2 + - (m.site_y[site1] / 150 - m.site_y[site2] / 150)**2)) + expr=m.sqr_scaled_dist_to_site[site1, site2] + == ( + (m.site_x[site1] / 150 - m.site_x[site2] / 150) ** 2 + + (m.site_y[site1] / 150 - m.site_y[site2] / 150) ** 2 + ) + ) disj.site_sqr_distance_symmetry = Constraint( - expr=m.sqr_scaled_dist_to_site[site1, site2] == - m.sqr_scaled_dist_to_site[site2, site1]) + expr=m.sqr_scaled_dist_to_site[site1, site2] + == m.sqr_scaled_dist_to_site[site2, site1] + ) @m.Disjunct(m.unique_site_pairs) def pair_inactive(disj, site1, site2): disj.site1_inactive = Constraint( - expr=m.site_active[site1].binary_indicator_var == 0) + expr=m.site_active[site1].binary_indicator_var == 0 + ) disj.site2_inactive = Constraint( - expr=m.site_active[site2].binary_indicator_var == 0) + expr=m.site_active[site2].binary_indicator_var == 0 + ) disj.no_module_transfer = Constraint( - expr=sum(m.modules_transferred[site1, site2, mo] - for mo in m.months) == 0) + expr=sum(m.modules_transferred[site1, site2, mo] for mo in m.months) == 0 + ) @m.Disjunction(m.unique_site_pairs) def site_pair_active_or_not(m, site1, site2): @@ -261,61 +298,82 @@ def site_pair_active_or_not(m, site1, site2): @m.Constraint(m.markets, m.months) def demand_satisfaction(m, mkt, mo): return m.market_demand[mkt, mo] <= sum( - m.shipments_to_mkt[site, mkt, mo] for site in m.modular_sites) + m.shipments_to_mkt[site, mkt, mo] for site in m.modular_sites + ) @m.Disjunct(m.modular_sites, m.markets) def product_route_active(disj, site, mkt): disj.site_active = Constraint( - expr=m.site_active[site].binary_indicator_var == 1) + expr=m.site_active[site].binary_indicator_var == 1 + ) @disj.Constraint() def market_distance_calculation(site_disj): - return m.dist_to_mkt[site, mkt] == sqrt( - m.sqr_scaled_dist_to_mkt[site, mkt]) * 150 + return ( + m.dist_to_mkt[site, mkt] + == sqrt(m.sqr_scaled_dist_to_mkt[site, mkt]) * 150 + ) @disj.Constraint() def market_sqr_distance_calc(site_disj): return m.sqr_scaled_dist_to_mkt[site, mkt] == ( - (m.site_x[site] / 150 - m.mkt_x[mkt] / 150)**2 + - (m.site_y[site] / 150 - m.mkt_y[mkt] / 150)**2) + (m.site_x[site] / 150 - m.mkt_x[mkt] / 150) ** 2 + + (m.site_y[site] / 150 - m.mkt_y[mkt] / 150) ** 2 + ) @m.Disjunct(m.modular_sites, m.markets) def product_route_inactive(disj, site, mkt): disj.no_shipments = Constraint( - expr=sum(m.shipments_to_mkt[site, mkt, mo] - for mo in m.months) == 0) + expr=sum(m.shipments_to_mkt[site, mkt, mo] for mo in m.months) == 0 + ) @m.Disjunction(m.modular_sites, m.markets) def product_route_active_or_not(m, site, mkt): - return [m.product_route_active[site, mkt], - m.product_route_inactive[site, mkt]] + return [m.product_route_active[site, mkt], m.product_route_inactive[site, mkt]] m.variable_shipment_cost = Expression( expr=sum( - m.shipments_to_mkt[site, mkt, mo] * m.dist_to_mkt[site, mkt] * - m.transport_cost[mo] - for site in m.modular_sites for mkt in m.markets - for mo in m.months)) + m.shipments_to_mkt[site, mkt, mo] + * m.dist_to_mkt[site, mkt] + * m.transport_cost[mo] + for site in m.modular_sites + for mkt in m.markets + for mo in m.months + ) + ) m.fixed_shipment_cost = Expression( - expr=sum(m.product_route_active[site, mkt].binary_indicator_var * - m.route_fixed_cost - for site in m.modular_sites for mkt in m.markets)) + expr=sum( + m.product_route_active[site, mkt].binary_indicator_var * m.route_fixed_cost + for site in m.modular_sites + for mkt in m.markets + ) + ) m.module_purchase_cost = Expression( - expr=sum(m.modules_added[site, mo] * m.modular_unit_cost[mo] - for site in m.modular_sites for mo in m.months)) + expr=sum( + m.modules_added[site, mo] * m.modular_unit_cost[mo] + for site in m.modular_sites + for mo in m.months + ) + ) - m.module_transfer_cost = Expression(expr=sum( - m.modules_transferred[site1, site2, mo] - * m.dist_to_site[site1, site2] * m.modular_transport_cost[mo] - for site1, site2 in m.site_pairs for mo in m.months)) + m.module_transfer_cost = Expression( + expr=sum( + m.modules_transferred[site1, site2, mo] + * m.dist_to_site[site1, site2] + * m.modular_transport_cost[mo] + for site1, site2 in m.site_pairs + for mo in m.months + ) + ) m.total_cost = Objective( expr=m.variable_shipment_cost + m.fixed_shipment_cost + m.module_purchase_cost - + m.module_transfer_cost) + + m.module_transfer_cost + ) return m @@ -343,8 +401,9 @@ def product_route_active_or_not(m, site, mkt): # 'option reslim=30;']}}) TransformationFactory('gdp.bigm').apply_to(m, bigM=10000) # TransformationFactory('gdp.chull').apply_to(m) - res = SolverFactory('gams').solve(m, tee=True, io_options={ - 'add_options': ['option reslim = 300;']}) + res = SolverFactory('gams').solve( + m, tee=True, io_options={'add_options': ['option reslim = 300;']} + ) # SolverFactory('gams').solve( # m, tee=True, # io_options={ @@ -358,13 +417,13 @@ def record_generator(): for mo in m.months: yield ( (mo,) - + tuple(m.num_modules[site, mo].value - for site in m.modular_sites) - + tuple(m.production[site, mo].value - for site in m.modular_sites) - + tuple(m.shipments_to_mkt[site, mkt, mo].value - for site in m.modular_sites - for mkt in m.markets) + + tuple(m.num_modules[site, mo].value for site in m.modular_sites) + + tuple(m.production[site, mo].value for site in m.modular_sites) + + tuple( + m.shipments_to_mkt[site, mkt, mo].value + for site in m.modular_sites + for mkt in m.markets + ) ) df = pd.DataFrame.from_records( @@ -372,8 +431,12 @@ def record_generator(): columns=("Month",) + tuple("Num Site%s" % site for site in m.modular_sites) + tuple("Prod Site%s" % site for site in m.modular_sites) - + tuple("Ship Site%s to Mkt%s" % (site, mkt) - for site in m.modular_sites for mkt in m.markets)) + + tuple( + "Ship Site%s to Mkt%s" % (site, mkt) + for site in m.modular_sites + for mkt in m.markets + ), + ) df.to_excel("multiple_modular_config.xlsx") print("Total cost: %s" % value(m.total_cost.expr)) print(" Variable ship cost: %s" % value(m.variable_shipment_cost)) @@ -381,40 +444,69 @@ def record_generator(): print(" Module buy cost: %s" % value(m.module_purchase_cost)) print(" Module xfer cost: %s" % value(m.module_transfer_cost)) for site in m.modular_sites: - print("Site {:1.0f} at ({:3.0f}, {:3.0f})".format( - site, m.site_x[site].value, m.site_y[site].value)) - print(" Supplies markets {}".format(tuple( - mkt for mkt in m.markets - if m.product_route_active[site, - mkt].binary_indicator_var.value == 1))) - + print( + "Site {:1.0f} at ({:3.0f}, {:3.0f})".format( + site, m.site_x[site].value, m.site_y[site].value + ) + ) + print( + " Supplies markets {}".format( + tuple( + mkt + for mkt in m.markets + if m.product_route_active[site, mkt].binary_indicator_var.value == 1 + ) + ) + ) if res.solver.termination_condition is not TerminationCondition.optimal: exit() - plt.plot([x.value for x in m.site_x.values()], - [y.value for y in m.site_y.values()], 'k.', markersize=12) - plt.plot([x for x in m.mkt_x.values()], - [y for y in m.mkt_y.values()], 'bo', markersize=12) + plt.plot( + [x.value for x in m.site_x.values()], + [y.value for y in m.site_y.values()], + 'k.', + markersize=12, + ) + plt.plot( + [x for x in m.mkt_x.values()], + [y for y in m.mkt_y.values()], + 'bo', + markersize=12, + ) for mkt in m.markets: - plt.annotate('mkt%s' % mkt, (m.mkt_x[mkt], m.mkt_y[mkt]), - (m.mkt_x[mkt] + 2, m.mkt_y[mkt] + 0)) + plt.annotate( + 'mkt%s' % mkt, + (m.mkt_x[mkt], m.mkt_y[mkt]), + (m.mkt_x[mkt] + 2, m.mkt_y[mkt] + 0), + ) for site in m.modular_sites: plt.annotate( - 'site%s' % site, (m.site_x[site].value, m.site_y[site].value), - (m.site_x[site].value + 2, m.site_y[site].value + 0)) + 'site%s' % site, + (m.site_x[site].value, m.site_y[site].value), + (m.site_x[site].value + 2, m.site_y[site].value + 0), + ) for site, mkt in m.modular_sites * m.markets: if m.product_route_active[site, mkt].binary_indicator_var.value == 1: - plt.arrow(m.site_x[site].value, m.site_y[site].value, - m.mkt_x[mkt] - m.site_x[site].value, - m.mkt_y[mkt] - m.site_y[site].value, - width=0.8, length_includes_head=True, color='r') + plt.arrow( + m.site_x[site].value, + m.site_y[site].value, + m.mkt_x[mkt] - m.site_x[site].value, + m.mkt_y[mkt] - m.site_y[site].value, + width=0.8, + length_includes_head=True, + color='r', + ) for site1, site2 in m.site_pairs: - if sum(m.modules_transferred[site1, site2, mo].value - for mo in m.months) > 0: - plt.arrow(m.site_x[site1].value, m.site_y[site1].value, - m.site_x[site2].value - m.site_x[site1].value, - m.site_y[site2].value - m.site_y[site1].value, - width=0.9, length_includes_head=True, - linestyle='dotted', color='k') + if sum(m.modules_transferred[site1, site2, mo].value for mo in m.months) > 0: + plt.arrow( + m.site_x[site1].value, + m.site_y[site1].value, + m.site_x[site2].value - m.site_x[site1].value, + m.site_y[site2].value - m.site_y[site1].value, + width=0.9, + length_includes_head=True, + linestyle='dotted', + color='k', + ) plt.show() diff --git a/gdplib/modprodnet/model.py b/gdplib/modprodnet/model.py index d5f30b0..562556d 100644 --- a/gdplib/modprodnet/model.py +++ b/gdplib/modprodnet/model.py @@ -4,8 +4,19 @@ import pandas as pd from pyomo.environ import ( - ConcreteModel, Constraint, Integers, maximize, Objective, Param, RangeSet, SolverFactory, summation, - TransformationFactory, value, Var, ) + ConcreteModel, + Constraint, + Integers, + maximize, + Objective, + Param, + RangeSet, + SolverFactory, + summation, + TransformationFactory, + value, + Var, +) from pyomo.gdp import Disjunct, Disjunction @@ -28,17 +39,21 @@ def production_value(m, mo): return (7 / 12) * m.discount_factor[mo] xls_data = pd.read_excel( - os.path.join(os.path.dirname(__file__), "market_size.xlsx"), sheet_name="single_market", index_col=0) + os.path.join(os.path.dirname(__file__), "market_size.xlsx"), + sheet_name="single_market", + index_col=0, + ) @m.Param(m.months) def market_demand(m, mo): return float(xls_data[case][mo]) - m.conv_size = Var(bounds=(25, 350), initialize=25, - doc="Size of conventional plant") + m.conv_size = Var(bounds=(25, 350), initialize=25, doc="Size of conventional plant") m.conv_base_cost = Param(initialize=1000, doc="Cost for size 25") m.conv_exponent = Param(initialize=0.6) - m.conv_cost = Var(bounds=(0, value(m.conv_base_cost * (m.conv_size.ub / 25) ** m.conv_exponent))) + m.conv_cost = Var( + bounds=(0, value(m.conv_base_cost * (m.conv_size.ub / 25) ** m.conv_exponent)) + ) m.module_base_cost = Param(initialize=1000, doc="Cost for size 25") m.production = Var(m.months, bounds=(0, 350)) @@ -68,12 +83,22 @@ def module_buy_cost(m, mo): @m.Expression(m.months) def module_sell_value(m, mo): - return m.module_base_cost * m.discount_factor[mo] * m.module_salvage_value * m.modules_sold[mo] + return ( + m.module_base_cost + * m.discount_factor[mo] + * m.module_salvage_value + * m.modules_sold[mo] + ) @m.Expression() def module_final_salvage(m): mo = max(m.months) - return m.module_base_cost * m.discount_factor[mo] * m.module_salvage_value * m.num_modules[mo] + return ( + m.module_base_cost + * m.discount_factor[mo] + * m.module_salvage_value + * m.num_modules[mo] + ) m.profit = Objective( expr=sum(m.revenue[:]) @@ -82,7 +107,8 @@ def module_final_salvage(m): - summation(m.module_buy_cost) + summation(m.module_sell_value) + m.module_final_salvage, - sense=maximize) + sense=maximize, + ) _build_conventional_disjunct(m) _build_modular_disjunct(m) @@ -95,8 +121,8 @@ def _build_conventional_disjunct(m): m.conventional = Disjunct() m.conventional.cost_calc = Constraint( - expr=m.conv_cost == ( - m.conv_base_cost * (m.conv_size / 25) ** m.conv_exponent)) + expr=m.conv_cost == (m.conv_base_cost * (m.conv_size / 25) ** m.conv_exponent) + ) @m.conventional.Constraint(m.months) def conv_production_limit(conv_disj, mo): @@ -107,7 +133,10 @@ def conv_production_limit(conv_disj, mo): @m.conventional.Constraint() def no_modules(conv_disj): - return sum(m.num_modules[:]) + sum(m.modules_purchased[:]) + sum(m.modules_sold[:]) == 0 + return ( + sum(m.num_modules[:]) + sum(m.modules_purchased[:]) + sum(m.modules_sold[:]) + == 0 + ) def _build_modular_disjunct(m): @@ -116,7 +145,11 @@ def _build_modular_disjunct(m): @m.modular.Constraint(m.months) def module_balance(disj, mo): existing_modules = 0 if mo == 0 else m.num_modules[mo - 1] - new_modules = 0 if mo < m.modular_setup_time else m.modules_purchased[mo - m.modular_setup_time] + new_modules = ( + 0 + if mo < m.modular_setup_time + else m.modules_purchased[mo - m.modular_setup_time] + ) sold_modules = m.modules_sold[mo] return m.num_modules[mo] == existing_modules + new_modules - sold_modules @@ -126,15 +159,22 @@ def modular_production_limit(mod_disj, mo): def display_conventional(m, writer, sheet_name): - df = pd.DataFrame( - list([ - mo, - m.production[mo].value, - m.market_demand[mo], - m.conv_size.value if mo >= m.conv_setup_time else 0 - ] for mo in m.months), - columns=("Month", "Production", "Demand", "Capacity") - ).set_index('Month').round(2) + df = ( + pd.DataFrame( + list( + [ + mo, + m.production[mo].value, + m.market_demand[mo], + m.conv_size.value if mo >= m.conv_setup_time else 0, + ] + for mo in m.months + ), + columns=("Month", "Production", "Demand", "Capacity"), + ) + .set_index('Month') + .round(2) + ) df.to_excel(writer, sheet_name) print('Conventional Profit', round(value(m.profit))) print('Conventional Revenue', round(value(sum(m.revenue[:])))) @@ -145,26 +185,46 @@ def display_conventional(m, writer, sheet_name): def display_modular(m, writer, sheet_name): - df = pd.DataFrame( - list([ - mo, - m.production[mo].value, - m.market_demand[mo], - m.num_modules[mo].value * 25, - m.num_modules[mo].value, - m.modules_purchased[mo].value, - m.modules_sold[mo].value] for mo in m.months - ), - columns=("Month", "Production", "Demand", "Capacity", - "Num Modules", "Add Modules", "Sold Modules") - ).set_index('Month').round(2) + df = ( + pd.DataFrame( + list( + [ + mo, + m.production[mo].value, + m.market_demand[mo], + m.num_modules[mo].value * 25, + m.num_modules[mo].value, + m.modules_purchased[mo].value, + m.modules_sold[mo].value, + ] + for mo in m.months + ), + columns=( + "Month", + "Production", + "Demand", + "Capacity", + "Num Modules", + "Add Modules", + "Sold Modules", + ), + ) + .set_index('Month') + .round(2) + ) df.to_excel(writer, sheet_name) print('Modular Profit', round(value(m.profit))) print('Modular Revenue', round(value(sum(m.revenue[:])))) - print('Modular Revenue before conventional startup', round(value(sum(m.revenue[mo] for mo in m.months if mo < 12)))) + print( + 'Modular Revenue before conventional startup', + round(value(sum(m.revenue[mo] for mo in m.months if mo < 12))), + ) print('Modular Build Cost', round(value(sum(m.module_buy_cost[:])))) print('Modules Purchased', round(value(sum(m.modules_purchased[:])))) - print('Modular Nondiscount Cost', round(value(m.module_base_cost * sum(m.modules_purchased[:])))) + print( + 'Modular Nondiscount Cost', + round(value(m.module_base_cost * sum(m.modules_purchased[:]))), + ) print('Modular Sale Credit', round(value(sum(m.module_sell_value[:])))) print('Modular Final Salvage Credit', round(value(m.module_final_salvage))) print() diff --git a/gdplib/modprodnet/quarter_distributed.py b/gdplib/modprodnet/quarter_distributed.py index f2bae2e..7746881 100644 --- a/gdplib/modprodnet/quarter_distributed.py +++ b/gdplib/modprodnet/quarter_distributed.py @@ -5,10 +5,23 @@ import matplotlib.pyplot as plt import pandas as pd -from pyomo.environ import (ConcreteModel, Constraint, Expression, Integers, - Objective, Param, RangeSet, Set, SolverFactory, - Suffix, TerminationCondition, TransformationFactory, - Var, sqrt, value) +from pyomo.environ import ( + ConcreteModel, + Constraint, + Expression, + Integers, + Objective, + Param, + RangeSet, + Set, + SolverFactory, + Suffix, + TerminationCondition, + TransformationFactory, + Var, + sqrt, + value, +) def build_model(): @@ -20,7 +33,9 @@ def build_model(): xls_data = pd.read_excel( os.path.join(os.path.dirname(__file__), "quarter_multiple_market_size.xlsx"), - sheet_name=["demand", "locations"], index_col=0) + sheet_name=["demand", "locations"], + index_col=0, + ) @m.Param(m.markets, m.quarters) def market_demand(m, mkt, qtr): @@ -32,20 +47,22 @@ def transport_cost(m, qtr): m.route_fixed_cost = Param( initialize=100, - doc="Cost of establishing a route from a modular site to a market") + doc="Cost of establishing a route from a modular site to a market", + ) m.conv_x = Var(bounds=(0, 300), doc="x-coordinate of centralized plant.") m.conv_y = Var(bounds=(0, 300), doc="y-coordinate of centralized plant.") - m.conv_size = Var(bounds=(10, 700), initialize=10, - doc="Size of conventional plant.") + m.conv_size = Var( + bounds=(10, 700), initialize=10, doc="Size of conventional plant." + ) m.conv_cost = Var() m.conv_base_cost = Param(initialize=1000, doc="Cost for size 60") m.conv_exponent = Param(initialize=0.6) m.cost_calc = Constraint( - expr=m.conv_cost == ( - m.conv_base_cost * (m.conv_size / 60) ** m.conv_exponent)) + expr=m.conv_cost == (m.conv_base_cost * (m.conv_size / 60) ** m.conv_exponent) + ) @m.Param(m.markets) def mkt_x(m, mkt): @@ -59,17 +76,23 @@ def mkt_y(m, mkt): @m.Constraint(m.markets) def distance_calculation(m, mkt): - return m.dist_to_mkt[mkt] == sqrt( - (m.conv_x / 150 - m.mkt_x[mkt] / 150)**2 + - (m.conv_y / 150 - m.mkt_y[mkt] / 150)**2) * 150 + return ( + m.dist_to_mkt[mkt] + == sqrt( + (m.conv_x / 150 - m.mkt_x[mkt] / 150) ** 2 + + (m.conv_y / 150 - m.mkt_y[mkt] / 150) ** 2 + ) + * 150 + ) m.shipments_to_mkt = Var(m.markets, m.quarters, bounds=(0, 400)) m.production = Var(m.quarters, bounds=(0, 1500)) @m.Constraint(m.quarters) def production_satisfaction(m, qtr): - return m.production[qtr] == sum(m.shipments_to_mkt[mkt, qtr] - for mkt in m.markets) + return m.production[qtr] == sum( + m.shipments_to_mkt[mkt, qtr] for mkt in m.markets + ) @m.Constraint(m.quarters) def size_requirement(m, qtr): @@ -84,12 +107,15 @@ def demand_satisfaction(m, mkt, qtr): m.variable_shipment_cost = Expression( expr=sum( - m.shipments_to_mkt[mkt, qtr] * m.dist_to_mkt[mkt] * - m.transport_cost[qtr] - for mkt in m.markets for qtr in m.quarters)) + m.shipments_to_mkt[mkt, qtr] * m.dist_to_mkt[mkt] * m.transport_cost[qtr] + for mkt in m.markets + for qtr in m.quarters + ) + ) m.total_cost = Objective( - expr=m.variable_shipment_cost + 5 * m.route_fixed_cost + m.conv_cost) + expr=m.variable_shipment_cost + 5 * m.route_fixed_cost + m.conv_cost + ) return m @@ -104,14 +130,15 @@ def build_modular_model(): m.markets = RangeSet(5) m.modular_sites = RangeSet(3) m.site_pairs = Set( - initialize=m.modular_sites * m.modular_sites, - filter=lambda _, x, y: not x == y) - m.unique_site_pairs = Set( - initialize=m.site_pairs, filter=lambda _, x, y: x < y) + initialize=m.modular_sites * m.modular_sites, filter=lambda _, x, y: not x == y + ) + m.unique_site_pairs = Set(initialize=m.site_pairs, filter=lambda _, x, y: x < y) xls_data = pd.read_excel( os.path.join(os.path.dirname(__file__), "quarter_multiple_market_size.xlsx"), - sheet_name=["demand", "locations"], index_col=0) + sheet_name=["demand", "locations"], + index_col=0, + ) @m.Param(m.markets, m.quarters) def market_demand(m, mkt, qtr): @@ -123,11 +150,13 @@ def transport_cost(m, qtr): m.route_fixed_cost = Param( initialize=100, - doc="Cost of establishing a route from a modular site to a market") + doc="Cost of establishing a route from a modular site to a market", + ) @m.Param(m.quarters, doc="Cost of transporting a module one mile") def modular_transport_cost(m, qtr): return 1 * (1 + m.discount_rate / 4) ** (-qtr / 4) + m.modular_base_cost = Param(initialize=1000, doc="Cost for size 60") @m.Param(m.quarters) @@ -142,32 +171,27 @@ def mkt_x(m, mkt): def mkt_y(m, mkt): return float(xls_data["locations"]["y"]["market%s" % mkt]) - m.site_x = Var( - m.modular_sites, bounds=(0, 300), initialize={ - 1: 50, 2: 225, 3: 250}) - m.site_y = Var( - m.modular_sites, bounds=(0, 300), initialize={ - 1: 300, 2: 275, 3: 50}) + m.site_x = Var(m.modular_sites, bounds=(0, 300), initialize={1: 50, 2: 225, 3: 250}) + m.site_y = Var(m.modular_sites, bounds=(0, 300), initialize={1: 300, 2: 275, 3: 50}) - m.num_modules = Var( - m.modular_sites, m.quarters, domain=Integers, bounds=(0, 12)) + m.num_modules = Var(m.modular_sites, m.quarters, domain=Integers, bounds=(0, 12)) m.modules_transferred = Var( - m.site_pairs, m.quarters, - domain=Integers, bounds=(0, 12), - doc="Number of modules moved from one site to another in a quarter.") + m.site_pairs, + m.quarters, + domain=Integers, + bounds=(0, 12), + doc="Number of modules moved from one site to another in a quarter.", + ) # m.modules_transferred[...].fix(0) - m.modules_added = Var( - m.modular_sites, m.quarters, domain=Integers, bounds=(0, 12)) + m.modules_added = Var(m.modular_sites, m.quarters, domain=Integers, bounds=(0, 12)) - m.dist_to_mkt = Var( - m.modular_sites, m.markets, bounds=(0, sqrt(300**2 + 300**2))) + m.dist_to_mkt = Var(m.modular_sites, m.markets, bounds=(0, sqrt(300**2 + 300**2))) m.sqr_scaled_dist_to_mkt = Var( - m.modular_sites, m.markets, bounds=(0.001, 8), initialize=0.001) + m.modular_sites, m.markets, bounds=(0.001, 8), initialize=0.001 + ) m.dist_to_site = Var(m.site_pairs, bounds=(0, sqrt(300**2 + 300**2))) - m.sqr_scaled_dist_to_site = Var( - m.site_pairs, bounds=(0.001, 8), initialize=0.001) - m.shipments_to_mkt = Var( - m.modular_sites, m.markets, m.quarters, bounds=(0, 300)) + m.sqr_scaled_dist_to_site = Var(m.site_pairs, bounds=(0.001, 8), initialize=0.001) + m.shipments_to_mkt = Var(m.modular_sites, m.markets, m.quarters, bounds=(0, 300)) m.production = Var(m.modular_sites, m.quarters, bounds=(0, 1500)) @m.Disjunct(m.modular_sites) @@ -175,7 +199,8 @@ def site_active(disj, site): @disj.Constraint(m.quarters) def production_satisfaction(site_disj, qtr): return m.production[site, qtr] == sum( - m.shipments_to_mkt[site, mkt, qtr] for mkt in m.markets) + m.shipments_to_mkt[site, mkt, qtr] for mkt in m.markets + ) @disj.Constraint(m.quarters) def production_limit(site_disj, qtr): @@ -184,34 +209,49 @@ def production_limit(site_disj, qtr): @disj.Constraint(m.quarters) def module_balance(site_disj, qtr): existing_modules = m.num_modules[site, qtr - 1] if qtr >= 1 else 0 - new_modules = (m.modules_added[site, qtr - m.modular_setup_time] - if qtr > m.modular_setup_time else 0) + new_modules = ( + m.modules_added[site, qtr - m.modular_setup_time] + if qtr > m.modular_setup_time + else 0 + ) xfrd_in_modules = sum( - m.modules_transferred[from_site, - site, qtr - m.modular_move_time] + m.modules_transferred[from_site, site, qtr - m.modular_move_time] for from_site in m.modular_sites - if (not from_site == site) and qtr > m.modular_move_time) + if (not from_site == site) and qtr > m.modular_move_time + ) xfrd_out_modules = sum( m.modules_transferred[site, to_site, qtr] - for to_site in m.modular_sites if not to_site == site) + for to_site in m.modular_sites + if not to_site == site + ) return m.num_modules[site, qtr] == ( - new_modules + xfrd_in_modules - xfrd_out_modules + - existing_modules) + new_modules + xfrd_in_modules - xfrd_out_modules + existing_modules + ) @m.Disjunct(m.modular_sites) def site_inactive(disj, site): disj.no_modules = Constraint( - expr=sum(m.num_modules[site, qtr] for qtr in m.quarters) == 0) + expr=sum(m.num_modules[site, qtr] for qtr in m.quarters) == 0 + ) disj.no_module_transfer = Constraint( - expr=sum(m.modules_transferred[site1, site2, qtr] - for site1, site2 in m.site_pairs - for qtr in m.quarters - if site1 == site or site2 == site) == 0) + expr=sum( + m.modules_transferred[site1, site2, qtr] + for site1, site2 in m.site_pairs + for qtr in m.quarters + if site1 == site or site2 == site + ) + == 0 + ) disj.no_shipments = Constraint( - expr=sum(m.shipments_to_mkt[site, mkt, qtr] - for mkt in m.markets for qtr in m.quarters) == 0) + expr=sum( + m.shipments_to_mkt[site, mkt, qtr] + for mkt in m.markets + for qtr in m.quarters + ) + == 0 + ) @m.Disjunction(m.modular_sites) def site_active_or_not(m, site): @@ -220,40 +260,53 @@ def site_active_or_not(m, site): @m.Constraint(m.modular_sites, doc="Symmetry breaking for site activation") def site_active_ordering(m, site): if site + 1 <= max(m.modular_sites): - return (m.site_active[site].binary_indicator_var >= - m.site_active[site + 1].binary_indicator_var) + return ( + m.site_active[site].binary_indicator_var + >= m.site_active[site + 1].binary_indicator_var + ) else: return Constraint.NoConstraint @m.Disjunct(m.unique_site_pairs) def pair_active(disj, site1, site2): disj.site1_active = Constraint( - expr=m.site_active[site1].binary_indicator_var == 1) + expr=m.site_active[site1].binary_indicator_var == 1 + ) disj.site2_active = Constraint( - expr=m.site_active[site2].binary_indicator_var == 1) + expr=m.site_active[site2].binary_indicator_var == 1 + ) disj.site_distance_calc = Constraint( - expr=m.dist_to_site[site1, site2] == sqrt( - m.sqr_scaled_dist_to_site[site1, site2]) * 150) + expr=m.dist_to_site[site1, site2] + == sqrt(m.sqr_scaled_dist_to_site[site1, site2]) * 150 + ) disj.site_distance_symmetry = Constraint( - expr=m.dist_to_site[site1, site2] == m.dist_to_site[site2, site1]) + expr=m.dist_to_site[site1, site2] == m.dist_to_site[site2, site1] + ) disj.site_sqr_distance_calc = Constraint( - expr=m.sqr_scaled_dist_to_site[site1, site2] == ( - (m.site_x[site1] / 150 - m.site_x[site2] / 150)**2 + - (m.site_y[site1] / 150 - m.site_y[site2] / 150)**2)) + expr=m.sqr_scaled_dist_to_site[site1, site2] + == ( + (m.site_x[site1] / 150 - m.site_x[site2] / 150) ** 2 + + (m.site_y[site1] / 150 - m.site_y[site2] / 150) ** 2 + ) + ) disj.site_sqr_distance_symmetry = Constraint( - expr=m.sqr_scaled_dist_to_site[site1, site2] == - m.sqr_scaled_dist_to_site[site2, site1]) + expr=m.sqr_scaled_dist_to_site[site1, site2] + == m.sqr_scaled_dist_to_site[site2, site1] + ) @m.Disjunct(m.unique_site_pairs) def pair_inactive(disj, site1, site2): disj.site1_inactive = Constraint( - expr=m.site_active[site1].binary_indicator_var == 0) + expr=m.site_active[site1].binary_indicator_var == 0 + ) disj.site2_inactive = Constraint( - expr=m.site_active[site2].binary_indicator_var == 0) + expr=m.site_active[site2].binary_indicator_var == 0 + ) disj.no_module_transfer = Constraint( - expr=sum(m.modules_transferred[site1, site2, qtr] - for qtr in m.quarters) == 0) + expr=sum(m.modules_transferred[site1, site2, qtr] for qtr in m.quarters) + == 0 + ) @m.Disjunction(m.unique_site_pairs) def site_pair_active_or_not(m, site1, site2): @@ -262,61 +315,82 @@ def site_pair_active_or_not(m, site1, site2): @m.Constraint(m.markets, m.quarters) def demand_satisfaction(m, mkt, qtr): return m.market_demand[mkt, qtr] <= sum( - m.shipments_to_mkt[site, mkt, qtr] for site in m.modular_sites) + m.shipments_to_mkt[site, mkt, qtr] for site in m.modular_sites + ) @m.Disjunct(m.modular_sites, m.markets) def product_route_active(disj, site, mkt): disj.site_active = Constraint( - expr=m.site_active[site].binary_indicator_var == 1) + expr=m.site_active[site].binary_indicator_var == 1 + ) @disj.Constraint() def market_distance_calculation(site_disj): - return m.dist_to_mkt[site, mkt] == sqrt( - m.sqr_scaled_dist_to_mkt[site, mkt]) * 150 + return ( + m.dist_to_mkt[site, mkt] + == sqrt(m.sqr_scaled_dist_to_mkt[site, mkt]) * 150 + ) @disj.Constraint() def market_sqr_distance_calc(site_disj): return m.sqr_scaled_dist_to_mkt[site, mkt] == ( - (m.site_x[site] / 150 - m.mkt_x[mkt] / 150)**2 + - (m.site_y[site] / 150 - m.mkt_y[mkt] / 150)**2) + (m.site_x[site] / 150 - m.mkt_x[mkt] / 150) ** 2 + + (m.site_y[site] / 150 - m.mkt_y[mkt] / 150) ** 2 + ) @m.Disjunct(m.modular_sites, m.markets) def product_route_inactive(disj, site, mkt): disj.no_shipments = Constraint( - expr=sum(m.shipments_to_mkt[site, mkt, qtr] - for qtr in m.quarters) == 0) + expr=sum(m.shipments_to_mkt[site, mkt, qtr] for qtr in m.quarters) == 0 + ) @m.Disjunction(m.modular_sites, m.markets) def product_route_active_or_not(m, site, mkt): - return [m.product_route_active[site, mkt], - m.product_route_inactive[site, mkt]] + return [m.product_route_active[site, mkt], m.product_route_inactive[site, mkt]] m.variable_shipment_cost = Expression( expr=sum( - m.shipments_to_mkt[site, mkt, qtr] * m.dist_to_mkt[site, mkt] * - m.transport_cost[qtr] - for site in m.modular_sites for mkt in m.markets - for qtr in m.quarters)) + m.shipments_to_mkt[site, mkt, qtr] + * m.dist_to_mkt[site, mkt] + * m.transport_cost[qtr] + for site in m.modular_sites + for mkt in m.markets + for qtr in m.quarters + ) + ) m.fixed_shipment_cost = Expression( - expr=sum(m.product_route_active[site, mkt].binary_indicator_var * - m.route_fixed_cost - for site in m.modular_sites for mkt in m.markets)) + expr=sum( + m.product_route_active[site, mkt].binary_indicator_var * m.route_fixed_cost + for site in m.modular_sites + for mkt in m.markets + ) + ) m.module_purchase_cost = Expression( - expr=sum(m.modules_added[site, qtr] * m.modular_unit_cost[qtr] - for site in m.modular_sites for qtr in m.quarters)) + expr=sum( + m.modules_added[site, qtr] * m.modular_unit_cost[qtr] + for site in m.modular_sites + for qtr in m.quarters + ) + ) - m.module_transfer_cost = Expression(expr=sum( - m.modules_transferred[site1, site2, qtr] - * m.dist_to_site[site1, site2] * m.modular_transport_cost[qtr] - for site1, site2 in m.site_pairs for qtr in m.quarters)) + m.module_transfer_cost = Expression( + expr=sum( + m.modules_transferred[site1, site2, qtr] + * m.dist_to_site[site1, site2] + * m.modular_transport_cost[qtr] + for site1, site2 in m.site_pairs + for qtr in m.quarters + ) + ) m.total_cost = Objective( expr=m.variable_shipment_cost + m.fixed_shipment_cost + m.module_purchase_cost - + m.module_transfer_cost) + + m.module_transfer_cost + ) return m @@ -347,10 +421,10 @@ def product_route_active_or_not(m, site, mkt): # res = SolverFactory('gams').solve(m, tee=True, io_options={ # 'add_options': ['option reslim = 300;']}) res = SolverFactory('gams').solve( - m, tee=True, - io_options={ - 'solver': 'baron', - 'add_options': ['option reslim = 600;']}) + m, + tee=True, + io_options={'solver': 'baron', 'add_options': ['option reslim = 600;']}, + ) # from pyomo.util.infeasible import log_infeasible_constraints # log_infeasible_constraints(m) @@ -358,17 +432,18 @@ def record_generator(): for qtr in m.quarters: yield ( (qtr,) - + tuple(m.num_modules[site, qtr].value - for site in m.modular_sites) - + tuple(m.production[site, qtr].value - for site in m.modular_sites) - + tuple(m.shipments_to_mkt[site, mkt, qtr].value - for site in m.modular_sites - for mkt in m.markets) - + tuple(m.modules_added[site, qtr].value - for site in m.modular_sites) - + tuple(m.modules_transferred[site1, site2, qtr].value - for site1, site2 in m.site_pairs) + + tuple(m.num_modules[site, qtr].value for site in m.modular_sites) + + tuple(m.production[site, qtr].value for site in m.modular_sites) + + tuple( + m.shipments_to_mkt[site, mkt, qtr].value + for site in m.modular_sites + for mkt in m.markets + ) + + tuple(m.modules_added[site, qtr].value for site in m.modular_sites) + + tuple( + m.modules_transferred[site1, site2, qtr].value + for site1, site2 in m.site_pairs + ) ) df = pd.DataFrame.from_records( @@ -376,11 +451,14 @@ def record_generator(): columns=("Qtr",) + tuple("Num Site%s" % site for site in m.modular_sites) + tuple("Prod Site%s" % site for site in m.modular_sites) - + tuple("ShipSite%stoMkt%s" % (site, mkt) - for site in m.modular_sites for mkt in m.markets) + + tuple( + "ShipSite%stoMkt%s" % (site, mkt) + for site in m.modular_sites + for mkt in m.markets + ) + tuple("Add Site%s" % site for site in m.modular_sites) - + tuple("Xfer Site%s to %s" % (site1, site2) - for site1, site2 in m.site_pairs)) + + tuple("Xfer Site%s to %s" % (site1, site2) for site1, site2 in m.site_pairs), + ) df.to_excel("quarter_multiple_modular_config.xlsx") print("Total cost: %s" % value(m.total_cost.expr)) print(" Variable ship cost: %s" % value(m.variable_shipment_cost)) @@ -388,39 +466,72 @@ def record_generator(): print(" Module buy cost: %s" % value(m.module_purchase_cost)) print(" Module xfer cost: %s" % value(m.module_transfer_cost)) for site in m.modular_sites: - print("Site {:1.0f} at ({:3.0f}, {:3.0f})".format( - site, m.site_x[site].value, m.site_y[site].value)) - print(" Supplies markets {}".format(tuple( - mkt for mkt in m.markets - if m.product_route_active[site, - mkt].binary_indicator_var.value == 1))) + print( + "Site {:1.0f} at ({:3.0f}, {:3.0f})".format( + site, m.site_x[site].value, m.site_y[site].value + ) + ) + print( + " Supplies markets {}".format( + tuple( + mkt + for mkt in m.markets + if m.product_route_active[site, mkt].binary_indicator_var.value == 1 + ) + ) + ) if res.solver.termination_condition is not TerminationCondition.optimal: exit() - plt.plot([x.value for x in m.site_x.values()], - [y.value for y in m.site_y.values()], 'k.', markersize=12) - plt.plot([x for x in m.mkt_x.values()], - [y for y in m.mkt_y.values()], 'bo', markersize=12) + plt.plot( + [x.value for x in m.site_x.values()], + [y.value for y in m.site_y.values()], + 'k.', + markersize=12, + ) + plt.plot( + [x for x in m.mkt_x.values()], + [y for y in m.mkt_y.values()], + 'bo', + markersize=12, + ) for mkt in m.markets: - plt.annotate('mkt%s' % mkt, (m.mkt_x[mkt], m.mkt_y[mkt]), - (m.mkt_x[mkt] + 2, m.mkt_y[mkt] + 0)) + plt.annotate( + 'mkt%s' % mkt, + (m.mkt_x[mkt], m.mkt_y[mkt]), + (m.mkt_x[mkt] + 2, m.mkt_y[mkt] + 0), + ) for site in m.modular_sites: plt.annotate( - 'site%s' % site, (m.site_x[site].value, m.site_y[site].value), - (m.site_x[site].value + 2, m.site_y[site].value + 0)) + 'site%s' % site, + (m.site_x[site].value, m.site_y[site].value), + (m.site_x[site].value + 2, m.site_y[site].value + 0), + ) for site, mkt in m.modular_sites * m.markets: if m.product_route_active[site, mkt].binary_indicator_var.value == 1: - plt.arrow(m.site_x[site].value, m.site_y[site].value, - m.mkt_x[mkt] - m.site_x[site].value, - m.mkt_y[mkt] - m.site_y[site].value, - width=0.8, length_includes_head=True, color='r') + plt.arrow( + m.site_x[site].value, + m.site_y[site].value, + m.mkt_x[mkt] - m.site_x[site].value, + m.mkt_y[mkt] - m.site_y[site].value, + width=0.8, + length_includes_head=True, + color='r', + ) for site1, site2 in m.site_pairs: - if sum(m.modules_transferred[site1, site2, qtr].value - for qtr in m.quarters) > 1E-6: - plt.arrow(m.site_x[site1].value, m.site_y[site1].value, - m.site_x[site2].value - m.site_x[site1].value, - m.site_y[site2].value - m.site_y[site1].value, - width=0.9, length_includes_head=True, - linestyle='dotted', color='k') + if ( + sum(m.modules_transferred[site1, site2, qtr].value for qtr in m.quarters) + > 1e-6 + ): + plt.arrow( + m.site_x[site1].value, + m.site_y[site1].value, + m.site_x[site2].value - m.site_x[site1].value, + m.site_y[site2].value - m.site_y[site1].value, + width=0.9, + length_includes_head=True, + linestyle='dotted', + color='k', + ) plt.show() diff --git a/gdplib/stranded_gas/model.py b/gdplib/stranded_gas/model.py index 1989d7d..ce8ab72 100644 --- a/gdplib/stranded_gas/model.py +++ b/gdplib/stranded_gas/model.py @@ -17,9 +17,23 @@ import pandas as pd from pyomo.environ import ( - ConcreteModel, Constraint, Integers, NonNegativeReals, Objective, Param, - RangeSet, Set, SolverFactory, Suffix, TransformationFactory, Var, exp, log, - sqrt, summation, value + ConcreteModel, + Constraint, + Integers, + NonNegativeReals, + Objective, + Param, + RangeSet, + Set, + SolverFactory, + Suffix, + TransformationFactory, + Var, + exp, + log, + sqrt, + summation, + value, ) from gdplib.stranded_gas.util import alphanum_sorted from pyomo.environ import TerminationCondition as tc @@ -43,13 +57,13 @@ def build_model(): m.periods_per_year = Param(initialize=4, doc="Quarters per year") m.project_life = Param(initialize=15, doc="Years") - m.time = RangeSet(0, m.periods_per_year * - m.project_life - 1, doc="Time periods") + m.time = RangeSet(0, m.periods_per_year * m.project_life - 1, doc="Time periods") m.discount_rate = Param(initialize=0.08, doc="8%") - m.learning_rate = Param(initialize=0.1, doc="Fraction discount for doubling of quantity") + m.learning_rate = Param( + initialize=0.1, doc="Fraction discount for doubling of quantity" + ) - m.module_setup_time = Param( - initialize=1, doc="1 quarter for module transfer") + m.module_setup_time = Param(initialize=1, doc="1 quarter for module transfer") @m.Param(m.time) def discount_factor(m, t): @@ -70,7 +84,9 @@ def discount_factor(m, t): """ return (1 + m.discount_rate / m.periods_per_year) ** (-t / m.periods_per_year) - xlsx_data = pd.read_excel(os.path.join(os.path.dirname(__file__), "data.xlsx"), sheet_name=None) + xlsx_data = pd.read_excel( + os.path.join(os.path.dirname(__file__), "data.xlsx"), sheet_name=None + ) module_sheet = xlsx_data['modules'].set_index('Type') m.module_types = Set(initialize=module_sheet.columns.tolist(), doc="Module types") @@ -93,7 +109,9 @@ def module_base_cost(m, mtype): """ return float(module_sheet[mtype]['Capital Cost [MM$]']) - @m.Param(m.module_types, doc="Natural gas consumption per module of this type [MMSCF/d]") + @m.Param( + m.module_types, doc="Natural gas consumption per module of this type [MMSCF/d]" + ) def unit_gas_consumption(m, mtype): """ Calculates the natural gas consumption per module of a given type. @@ -131,7 +149,10 @@ def gasoline_production(m, mtype): """ return float(module_sheet[mtype]['Gasoline [kBD]']) - @m.Param(m.module_types, doc="Overall conversion of natural gas into gasoline per module of this type [kB/MMSCF]") + @m.Param( + m.module_types, + doc="Overall conversion of natural gas into gasoline per module of this type [kB/MMSCF]", + ) def module_conversion(m, mtype): """ Calculates the overall conversion of natural gas into gasoline per module of a given type. @@ -155,7 +176,8 @@ def module_conversion(m, mtype): m.site_pairs = Set( doc="Pairs of potential sites", initialize=m.potential_sites * m.potential_sites, - filter=lambda _, x, y: not x == y) + filter=lambda _, x, y: not x == y, + ) @m.Param(m.potential_sites) def site_x(m, site): @@ -237,14 +259,18 @@ def well_y(m, well): return float(well_sheet['y'][well]) sched_sheet = xlsx_data['well-schedule'] - decay_curve = [1] + [3.69 * exp(-1.31 * (t + 1) ** 0.292) for t in range(m.project_life * 12)] + decay_curve = [1] + [ + 3.69 * exp(-1.31 * (t + 1) ** 0.292) for t in range(m.project_life * 12) + ] well_profiles = {well: [0 for _ in decay_curve] for well in m.well_clusters} for _, well_info in sched_sheet.iterrows(): start_time = int(well_info['Month']) - prod = [0] * start_time + decay_curve[:len(decay_curve) - start_time] + prod = [0] * start_time + decay_curve[: len(decay_curve) - start_time] prod = [x * float(well_info['max prod [MMSCF/d]']) for x in prod] current_profile = well_profiles[well_info['well-cluster']] - well_profiles[well_info['well-cluster']] = [val + prod[i] for i, val in enumerate(current_profile)] + well_profiles[well_info['well-cluster']] = [ + val + prod[i] for i, val in enumerate(current_profile) + ] @m.Param(m.well_clusters, m.time, doc="Supply of gas from well cluster [MMSCF/day]") def gas_supply(m, well, t): @@ -265,7 +291,7 @@ def gas_supply(m, well, t): Pyomo.Parameter A float representing the supply of gas from the well cluster in the given time period. """ - return sum(well_profiles[well][t * 3:t * 3 + 2]) / 3 + return sum(well_profiles[well][t * 3 : t * 3 + 2]) / 3 mkt_sheet = xlsx_data['markets'].set_index('Market') m.markets = Set(initialize=mkt_sheet.index.tolist(), doc="Markets") @@ -335,7 +361,7 @@ def distance(m, src, dest): """ Calculates the Euclidean distance between a source and a destination within the gas processing network. Assuming `src_x`, `src_y` for a source and `dest_x`, `dest_y` for a destination are defined within the model, the distance is calculated as follows: - + distance = sqrt((src_x - dest_x) ** 2 + (src_y - dest_y) ** 2) Parameters @@ -367,17 +393,32 @@ def distance(m, src, dest): return sqrt((src_x - dest_x) ** 2 + (src_y - dest_y) ** 2) m.num_modules = Var( - m.module_types, m.potential_sites, m.time, + m.module_types, + m.potential_sites, + m.time, doc="Number of active modules of each type at a site in a period", - domain=Integers, bounds=(0, 50), initialize=1) + domain=Integers, + bounds=(0, 50), + initialize=1, + ) m.modules_transferred = Var( - m.module_types, m.site_pairs, m.time, + m.module_types, + m.site_pairs, + m.time, doc="Number of module transfers initiated from one site to another in a period.", - domain=Integers, bounds=(0, 15), initialize=0) + domain=Integers, + bounds=(0, 15), + initialize=0, + ) m.modules_purchased = Var( - m.module_types, m.potential_sites, m.time, + m.module_types, + m.potential_sites, + m.time, doc="Number of modules of each type purchased for a site in a period", - domain=Integers, bounds=(0, 30), initialize=1) + domain=Integers, + bounds=(0, 30), + initialize=1, + ) m.pipeline_unit_cost = Param(doc="MM$/mile", initialize=2) @@ -482,7 +523,10 @@ def gasoline_tranport_cost(m, t): m.learning_factor = Var( m.module_types, doc="Fraction of cost due to economies of mass production", - domain=NonNegativeReals, bounds=(0, 1), initialize=1) + domain=NonNegativeReals, + bounds=(0, 1), + initialize=1, + ) @m.Disjunct(m.module_types) def mtype_exists(disj, mtype): @@ -504,11 +548,16 @@ def mtype_exists(disj, mtype): Ensures that at least one module of this type is purchased, activating this disjunct. """ disj.learning_factor_calc = Constraint( - expr=m.learning_factor[mtype] == (1 - m.learning_rate) ** ( - log(sum(m.modules_purchased[mtype, :, :])) / log(2)), doc="Learning factor calculation") + expr=m.learning_factor[mtype] + == (1 - m.learning_rate) + ** (log(sum(m.modules_purchased[mtype, :, :])) / log(2)), + doc="Learning factor calculation", + ) m.BigM[disj.learning_factor_calc] = 1 disj.require_module_purchases = Constraint( - expr=sum(m.modules_purchased[mtype, :, :]) >= 1, doc="At least one module purchase") + expr=sum(m.modules_purchased[mtype, :, :]) >= 1, + doc="At least one module purchase", + ) @m.Disjunct(m.module_types) def mtype_absent(disj, mtype): @@ -523,7 +572,8 @@ def mtype_absent(disj, mtype): Index of the module type. """ disj.constant_learning_factor = Constraint( - expr=m.learning_factor[mtype] == 1, doc="Constant learning factor") + expr=m.learning_factor[mtype] == 1, doc="Constant learning factor" + ) @m.Disjunction(m.module_types) def mtype_existence(m, mtype): @@ -563,25 +613,46 @@ def module_unit_cost(m, mtype, t): Pyomo.Expression A Pyomo Expression that calculates the total unit cost of a module for a given type and time period. """ - return m.module_base_cost[mtype] * m.learning_factor[mtype] * m.discount_factor[t] + return ( + m.module_base_cost[mtype] * m.learning_factor[mtype] * m.discount_factor[t] + ) m.production = Var( - m.potential_sites, m.time, + m.potential_sites, + m.time, doc="Production of gasoline in a time period [kBD]", - domain=NonNegativeReals, bounds=(0, 30), initialize=10) + domain=NonNegativeReals, + bounds=(0, 30), + initialize=10, + ) m.gas_consumption = Var( - m.potential_sites, m.module_types, m.time, + m.potential_sites, + m.module_types, + m.time, doc="Consumption of natural gas by each module type " "at each site in a time period [MMSCF/d]", - domain=NonNegativeReals, bounds=(0, 250), initialize=50) + domain=NonNegativeReals, + bounds=(0, 250), + initialize=50, + ) m.gas_flows = Var( - m.well_clusters, m.potential_sites, m.time, + m.well_clusters, + m.potential_sites, + m.time, doc="Flow of gas from a well cluster to a site [MMSCF/d]", - domain=NonNegativeReals, bounds=(0, 200), initialize=15) + domain=NonNegativeReals, + bounds=(0, 200), + initialize=15, + ) m.product_flows = Var( - m.potential_sites, m.markets, m.time, + m.potential_sites, + m.markets, + m.time, doc="Product shipments from a site to a market in a period [kBD]", - domain=NonNegativeReals, bounds=(0, 30), initialize=10) + domain=NonNegativeReals, + bounds=(0, 30), + initialize=10, + ) @m.Constraint(m.potential_sites, m.module_types, m.time) def consumption_capacity(m, site, mtype, t): @@ -605,7 +676,8 @@ def consumption_capacity(m, site, mtype, t): A constraint that limits the gas consumption per module type at each site, ensuring it does not exceed the capacity provided by the number of active modules of that type at the site during the time period. """ return m.gas_consumption[site, mtype, t] <= ( - m.num_modules[mtype, site, t] * m.unit_gas_consumption[mtype]) + m.num_modules[mtype, site, t] * m.unit_gas_consumption[mtype] + ) @m.Constraint(m.potential_sites, m.time) def production_limit(m, site, t): @@ -628,7 +700,8 @@ def production_limit(m, site, t): """ return m.production[site, t] <= sum( m.gas_consumption[site, mtype, t] * m.module_conversion[mtype] - for mtype in m.module_types) + for mtype in m.module_types + ) @m.Expression(m.potential_sites, m.time) def capacity(m, site, t): @@ -650,8 +723,11 @@ def capacity(m, site, t): An expression that sums up the potential production capacity at a site, calculated as the product of the number of modules, their individual gas consumption rates, and their conversion efficiency. """ return sum( - m.num_modules[mtype, site, t] * m.unit_gas_consumption[mtype] - * m.module_conversion[mtype] for mtype in m.module_types) + m.num_modules[mtype, site, t] + * m.unit_gas_consumption[mtype] + * m.module_conversion[mtype] + for mtype in m.module_types + ) @m.Constraint(m.potential_sites, m.time) def gas_supply_meets_consumption(m, site, t): @@ -693,8 +769,10 @@ def gas_supply_limit(m, well, t): Pyomo.Constraint A constraint that limits the total gas flow from a well cluster to various sites to not exceed the gas supply available at that well cluster for the given time period. """ - return sum(m.gas_flows[well, site, t] - for site in m.potential_sites) <= m.gas_supply[well, t] + return ( + sum(m.gas_flows[well, site, t] for site in m.potential_sites) + <= m.gas_supply[well, t] + ) @m.Constraint(m.potential_sites, m.time) def gasoline_production_requirement(m, site, t): @@ -715,8 +793,10 @@ def gasoline_production_requirement(m, site, t): Pyomo.Constraint A constraint that the sum of product flows (gasoline) from a site to various markets equals the total production at that site for the given period. """ - return sum(m.product_flows[site, mkt, t] - for mkt in m.markets) == m.production[site, t] + return ( + sum(m.product_flows[site, mkt, t] for mkt in m.markets) + == m.production[site, t] + ) @m.Constraint(m.potential_sites, m.module_types, m.time) def module_balance(m, site, mtype, t): @@ -740,12 +820,14 @@ def module_balance(m, site, mtype, t): A constraint that maintains an accurate balance of module counts at each site, considering new purchases, transfers in, existing inventory, and transfers out. """ if t >= m.module_setup_time: - modules_added = m.modules_purchased[ - mtype, site, t - m.module_setup_time] + modules_added = m.modules_purchased[mtype, site, t - m.module_setup_time] modules_transferred_in = sum( m.modules_transferred[ - mtype, from_site, to_site, t - m.module_setup_time] - for from_site, to_site in m.site_pairs if to_site == site) + mtype, from_site, to_site, t - m.module_setup_time + ] + for from_site, to_site in m.site_pairs + if to_site == site + ) else: modules_added = 0 modules_transferred_in = 0 @@ -755,11 +837,16 @@ def module_balance(m, site, mtype, t): existing_modules = 0 modules_transferred_out = sum( m.modules_transferred[mtype, from_site, to_site, t] - for from_site, to_site in m.site_pairs if from_site == site) + for from_site, to_site in m.site_pairs + if from_site == site + ) return m.num_modules[mtype, site, t] == ( - existing_modules + modules_added - + modules_transferred_in - modules_transferred_out) + existing_modules + + modules_added + + modules_transferred_in + - modules_transferred_out + ) @m.Disjunct(m.potential_sites) def site_active(disj, site): @@ -787,27 +874,33 @@ def site_inactive(disj, site): site : str The index for the potential site. """ - disj.no_production = Constraint( - expr=sum(m.production[site, :]) == 0) + disj.no_production = Constraint(expr=sum(m.production[site, :]) == 0) disj.no_gas_consumption = Constraint( - expr=sum(m.gas_consumption[site, :, :]) == 0) - disj.no_gas_flows = Constraint( - expr=sum(m.gas_flows[:, site, :]) == 0) - disj.no_product_flows = Constraint( - expr=sum(m.product_flows[site, :, :]) == 0) - disj.no_modules = Constraint( - expr=sum(m.num_modules[:, site, :]) == 0) + expr=sum(m.gas_consumption[site, :, :]) == 0 + ) + disj.no_gas_flows = Constraint(expr=sum(m.gas_flows[:, site, :]) == 0) + disj.no_product_flows = Constraint(expr=sum(m.product_flows[site, :, :]) == 0) + disj.no_modules = Constraint(expr=sum(m.num_modules[:, site, :]) == 0) disj.no_modules_transferred = Constraint( expr=sum( m.modules_transferred[mtypes, from_site, to_site, t] for mtypes in m.module_types for from_site, to_site in m.site_pairs for t in m.time - if from_site == site or to_site == site) == 0, doc="No modules transferred") + if from_site == site or to_site == site + ) + == 0, + doc="No modules transferred", + ) disj.no_modules_purchased = Constraint( expr=sum( m.modules_purchased[mtype, site, t] - for mtype in m.module_types for t in m.time) == 0, doc="No modules purchased") + for mtype in m.module_types + for t in m.time + ) + == 0, + doc="No modules purchased", + ) @m.Disjunction(m.potential_sites) def site_active_or_not(m, site): @@ -859,7 +952,9 @@ def pipeline_absent(disj, well, site): The index for the potential site. """ disj.no_natural_gas_flow = Constraint( - expr=sum(m.gas_flows[well, site, t] for t in m.time) == 0, doc="No natural gas flow") + expr=sum(m.gas_flows[well, site, t] for t in m.time) == 0, + doc="No natural gas flow", + ) @m.Disjunction(m.well_clusters, m.potential_sites) def pipeline_existence(m, well, site): @@ -903,11 +998,13 @@ def product_revenue(m, site): return sum( m.product_flows[site, mkt, t] # kBD * 1000 # bbl/kB - / 1E6 # $ to MM$ + / 1e6 # $ to MM$ * m.days_per_period - * m.gasoline_price[t] * m.gal_per_bbl + * m.gasoline_price[t] + * m.gal_per_bbl for mkt in m.markets - for t in m.time) + for t in m.time + ) @m.Expression(m.potential_sites, doc="MM$") def raw_material_cost(m, site): @@ -927,15 +1024,20 @@ def raw_material_cost(m, site): An expression calculating the total cost of natural gas used, taking into account the gas price and the conversion factor from MSCF to MMSCF. """ return sum( - m.gas_consumption[site, mtype, t] * m.days_per_period - / 1E6 # $ to MM$ + m.gas_consumption[site, mtype, t] + * m.days_per_period + / 1e6 # $ to MM$ * m.nat_gas_price[t] * 1000 # MMSCF to MSCF - for mtype in m.module_types for t in m.time) + for mtype in m.module_types + for t in m.time + ) @m.Expression( - m.potential_sites, m.markets, - doc="Aggregate cost to transport gasoline from a site to market [MM$]") + m.potential_sites, + m.markets, + doc="Aggregate cost to transport gasoline from a site to market [MM$]", + ) def product_transport_cost(m, site, mkt): """ Computes the cost of transporting gasoline from each production site to different markets, expressed in million dollars. @@ -955,11 +1057,15 @@ def product_transport_cost(m, site, mkt): The total transportation cost for shipping gasoline from a site to a market, adjusted for the distance and transportation rate. """ return sum( - m.product_flows[site, mkt, t] * m.gal_per_bbl + m.product_flows[site, mkt, t] + * m.gal_per_bbl * 1000 # bbl/kB - / 1E6 # $ to MM$ - * m.distance[site, mkt] / 100 * m.gasoline_tranport_cost[t] - for t in m.time) + / 1e6 # $ to MM$ + * m.distance[site, mkt] + / 100 + * m.gasoline_tranport_cost[t] + for t in m.time + ) @m.Expression(m.well_clusters, m.potential_sites, doc="MM$") def pipeline_construction_cost(m, well, site): @@ -980,8 +1086,11 @@ def pipeline_construction_cost(m, well, site): Pyomo.Expression The cost of pipeline construction, in million dollars, if a pipeline is established between the well cluster and the site. """ - return (m.pipeline_unit_cost * m.distance[well, site] - * m.pipeline_exists[well, site].binary_indicator_var) + return ( + m.pipeline_unit_cost + * m.distance[well, site] + * m.pipeline_exists[well, site].binary_indicator_var + ) # Module transport cost @m.Expression(m.site_pairs, doc="MM$") @@ -1005,13 +1114,15 @@ def module_relocation_cost(m, from_site, to_site): """ return sum( m.modules_transferred[mtype, from_site, to_site, t] - * m.distance[from_site, to_site] / 100 + * m.distance[from_site, to_site] + / 100 * m.module_transport_distance_cost[t] - / 1E3 # M$ to MM$ + / 1e3 # M$ to MM$ + m.modules_transferred[mtype, from_site, to_site, t] * m.module_transport_unit_cost[t] for mtype in m.module_types - for t in m.time) + for t in m.time + ) @m.Expression(m.potential_sites, doc="MM$") def module_purchase_cost(m, site): @@ -1033,7 +1144,8 @@ def module_purchase_cost(m, site): return sum( m.module_unit_cost[mtype, t] * m.modules_purchased[mtype, site, t] for mtype in m.module_types - for t in m.time) + for t in m.time + ) @m.Expression(doc="MM$") def profit(m): @@ -1059,7 +1171,9 @@ def profit(m): - summation(m.module_purchase_cost) ) - m.neg_profit = Objective(expr=-m.profit, doc="Objective Function: Minimize Negative Profit") + m.neg_profit = Objective( + expr=-m.profit, doc="Objective Function: Minimize Negative Profit" + ) # Tightening constraints @m.Constraint(doc="Limit total module purchases over project span.") diff --git a/gdplib/syngas/syngas_adapted.py b/gdplib/syngas/syngas_adapted.py index e656ca5..634e1c8 100644 --- a/gdplib/syngas/syngas_adapted.py +++ b/gdplib/syngas/syngas_adapted.py @@ -3,7 +3,9 @@ import pandas as pd from pyomo.core.expr.logical_expr import * -from pyomo.core.plugins.transform.logical_to_linear import update_boolean_vars_from_binary +from pyomo.core.plugins.transform.logical_to_linear import ( + update_boolean_vars_from_binary, +) from pyomo.environ import * from pyomo.environ import TerminationCondition as tc import os @@ -16,39 +18,47 @@ def build_model(): m.syngas_techs = Set( doc="syngas process technologies", - initialize=['SMR', 'POX', 'ATR', 'CR', 'DMR', 'BR', 'TR'] + initialize=['SMR', 'POX', 'ATR', 'CR', 'DMR', 'BR', 'TR'], ) m.species = Set( - doc="chemical species", - initialize=['CH4', 'H2O', 'O2', 'H2', 'CO', 'CO2'] + doc="chemical species", initialize=['CH4', 'H2O', 'O2', 'H2', 'CO', 'CO2'] ) m.syngas_tech_units = Set( doc="process units involved in syngas process technologies", - initialize=['compressor', 'exchanger', 'reformer'] + initialize=['compressor', 'exchanger', 'reformer'], ) m.utilities = Set( - doc="process utilities", - initialize=['power', 'coolingwater', 'naturalgas'] + doc="process utilities", initialize=['power', 'coolingwater', 'naturalgas'] ) m.aux_equipment = Set( doc="auxiliary process equipment to condition syngas", - initialize=['absorber1', 'bypass1', 'WGS', 'flash', 'PSA', 'absorber2', 'bypass3', 'compressor', 'bypass4'] + initialize=[ + 'absorber1', + 'bypass1', + 'WGS', + 'flash', + 'PSA', + 'absorber2', + 'bypass3', + 'compressor', + 'bypass4', + ], ) m.process_options = Set( doc="process superstructure options", - initialize=m.syngas_techs | m.aux_equipment + initialize=m.syngas_techs | m.aux_equipment, ) m.extra_process_nodes = Set( doc="extra process nodes for mixers and splitters", - initialize=['in', 'ms1', 'm1', 's1', 'ms2', 'ms3', 'm2', 'ms4', 's2'] + initialize=['in', 'ms1', 'm1', 's1', 'ms2', 'ms3', 'm2', 'ms4', 's2'], ) m.all_unit_types = Set( doc="all process unit types", - initialize=m.process_options | m.syngas_tech_units | m.extra_process_nodes + initialize=m.process_options | m.syngas_tech_units | m.extra_process_nodes, ) m.superstructure_nodes = Set( doc="nodes in the superstructure", - initialize=m.process_options | m.extra_process_nodes + initialize=m.process_options | m.extra_process_nodes, ) group1 = {'absorber1', 'bypass1', 'WGS'} group2 = {'compressor', 'bypass3'} @@ -64,7 +74,7 @@ def build_model(): + [(option, 'ms3') for option in group2] + [('ms3', option) for option in group3] + [(option, 'm2') for option in group3] - + [('PSA', 'ms4'), ('ms4', 'ms3'), ('ms4', 's2'), ('s2', 'm1'), ('s2', 'ms1')] + + [('PSA', 'ms4'), ('ms4', 'ms3'), ('ms4', 's2'), ('s2', 'm1'), ('s2', 'ms1')], ) """ @@ -72,12 +82,21 @@ def build_model(): """ m.flow_limit = Param(initialize=5) - m.min_flow_division = Param(initialize=0.1, doc="minimum flow fraction into active unit") + m.min_flow_division = Param( + initialize=0.1, doc="minimum flow fraction into active unit" + ) m.raw_material_cost = Param( m.species, doc="raw material cost [dollar·kmol-1]", - initialize={'CH4': 2.6826, 'H2O': 0.18, 'O2': 0.7432, 'H2': 8, 'CO': 0, 'CO2': 1.8946} + initialize={ + 'CH4': 2.6826, + 'H2O': 0.18, + 'O2': 0.7432, + 'H2': 8, + 'CO': 0, + 'CO2': 1.8946, + }, ) feed_ratios = { @@ -95,137 +114,280 @@ def build_model(): ('TR', 'O2'): 0.47, } - data_dict = pd.read_csv('%s/syngas_conversion_data.txt' % syngas_dir, delimiter=r'\s+').fillna(0).stack().to_dict() + data_dict = ( + pd.read_csv('%s/syngas_conversion_data.txt' % syngas_dir, delimiter=r'\s+') + .fillna(0) + .stack() + .to_dict() + ) m.syngas_conversion_factor = Param(m.species, m.syngas_techs, initialize=data_dict) - m.co2_ratio = Param(doc="CO2-CO ratio of total carbon in final syngas", initialize=0.05) + m.co2_ratio = Param( + doc="CO2-CO ratio of total carbon in final syngas", initialize=0.05 + ) # Capital costs --> cap = (p1*x + p2)*(B1 + B2*Fmf*Fp) m.p1 = Param( m.all_unit_types, doc="capital cost variable parameter", initialize={ - 'compressor': 172.4, 'exchanger': 59.99, 'reformer': 67.64, 'absorber1': 314.1, 'bypass1': 0, - 'WGS': 314.1, 'flash': 314.1, 'PSA': 3.1863e+04, 'absorber2': 314.1, 'bypass3': 0}, - default=0 + 'compressor': 172.4, + 'exchanger': 59.99, + 'reformer': 67.64, + 'absorber1': 314.1, + 'bypass1': 0, + 'WGS': 314.1, + 'flash': 314.1, + 'PSA': 3.1863e04, + 'absorber2': 314.1, + 'bypass3': 0, + }, + default=0, ) m.p2 = Param( m.all_unit_types, doc="capital cost fixed parameter", initialize={ - 'compressor': 104300, 'exchanger': 187100, 'reformer': 480100, 'absorber1': 1.531e+04, 'bypass1': 0, - 'WGS': 1.531e+04, 'flash': 1.531e+04, 'PSA': 666.34e+03, 'absorber2': 1.531e+04, 'bypass3': 0}, - default=0 + 'compressor': 104300, + 'exchanger': 187100, + 'reformer': 480100, + 'absorber1': 1.531e04, + 'bypass1': 0, + 'WGS': 1.531e04, + 'flash': 1.531e04, + 'PSA': 666.34e03, + 'absorber2': 1.531e04, + 'bypass3': 0, + }, + default=0, ) m.material_factor = Param( m.all_unit_types, doc="capital cost material factor", initialize={ - 'compressor': 3.5, 'exchanger': 1.7, 'reformer': 4, 'absorber1': 1, 'bypass1': 0, - 'WGS': 1, 'flash': 1, 'PSA': 3.5, 'absorber2': 1, 'bypass3': 0}, - default=0 + 'compressor': 3.5, + 'exchanger': 1.7, + 'reformer': 4, + 'absorber1': 1, + 'bypass1': 0, + 'WGS': 1, + 'flash': 1, + 'PSA': 3.5, + 'absorber2': 1, + 'bypass3': 0, + }, + default=0, ) m.B1 = Param( m.all_unit_types, doc="bare module parameter 1", initialize={ - 'compressor': 0, 'exchanger': 1.63, 'reformer': 0, 'absorber1': 2.25, 'bypass1': 0, - 'WGS': 1.49, 'flash': 2.25, 'PSA': 0, 'absorber2': 2.25, 'bypass3': 0}, - default=0 + 'compressor': 0, + 'exchanger': 1.63, + 'reformer': 0, + 'absorber1': 2.25, + 'bypass1': 0, + 'WGS': 1.49, + 'flash': 2.25, + 'PSA': 0, + 'absorber2': 2.25, + 'bypass3': 0, + }, + default=0, ) m.B2 = Param( m.all_unit_types, doc="bare module parameter 2", initialize={ - 'compressor': 1, 'exchanger': 1.66, 'reformer': 1, 'absorber1': 1.82, 'bypass1': 0, - 'WGS': 1.52, 'flash': 1.82, 'PSA': 1, 'absorber2': 1.82, 'bypass3': 0}, - default=0 + 'compressor': 1, + 'exchanger': 1.66, + 'reformer': 1, + 'absorber1': 1.82, + 'bypass1': 0, + 'WGS': 1.52, + 'flash': 1.82, + 'PSA': 1, + 'absorber2': 1.82, + 'bypass3': 0, + }, + default=0, + ) + data_dict = ( + pd.read_csv('%s/syngas_pressure_factor_data.txt' % syngas_dir, delimiter=r'\s+') + .stack() + .to_dict() + ) + m.syngas_pressure_factor = Param( + m.syngas_tech_units, m.syngas_techs, initialize=data_dict ) - data_dict = pd.read_csv('%s/syngas_pressure_factor_data.txt' % syngas_dir, delimiter=r'\s+').stack().to_dict() - m.syngas_pressure_factor = Param(m.syngas_tech_units, m.syngas_techs, initialize=data_dict) m.utility_cost = Param( m.utilities, doc="dollars per kWh of utility u [dollar·KWh-1]", - initialize={'power': 0.127, 'coolingwater': 0.01, 'naturalgas': 0.036} + initialize={'power': 0.127, 'coolingwater': 0.01, 'naturalgas': 0.036}, ) m.utility_emission = Param( m.utilities, doc="CO2 emitted per kW [kgCO2·kWh-1]", - initialize={'power': 0.44732, 'coolingwater': 0, 'naturalgas': 0.2674} + initialize={'power': 0.44732, 'coolingwater': 0, 'naturalgas': 0.2674}, ) m.raw_material_emission = Param( m.species, doc="kg CO2 emitted per kmol of raw material [kgCO2·kmol-1]", - initialize={'CH4': 11.7749, 'H2O': 0, 'O2': 10.9525, 'H2': 0, 'CO': 0, 'CO2': 0} + initialize={ + 'CH4': 11.7749, + 'H2O': 0, + 'O2': 10.9525, + 'H2': 0, + 'CO': 0, + 'CO2': 0, + }, ) m.interest_rate = Param(initialize=0.1, doc="annualization index") m.project_years = Param(initialize=8, doc="annualization years") m.annualization_factor = Expression( - expr=m.interest_rate * (1 + m.interest_rate)**m.project_years / ((1 + m.interest_rate)**m.project_years - 1)) + expr=m.interest_rate + * (1 + m.interest_rate) ** m.project_years + / ((1 + m.interest_rate) ** m.project_years - 1) + ) m.CEPCI2015 = Param(initialize=560, doc="equipment cost index 2015") m.CEPCI2001 = Param(initialize=397, doc="equipment cost index 2001") - m.cost_index_ratio = Param(initialize=m.CEPCI2015 / m.CEPCI2001, doc="cost index ratio") + m.cost_index_ratio = Param( + initialize=m.CEPCI2015 / m.CEPCI2001, doc="cost index ratio" + ) - data_dict = pd.read_csv('%s/syngas_utility_data.txt' % syngas_dir, delimiter=r'\s+').stack().to_dict() + data_dict = ( + pd.read_csv('%s/syngas_utility_data.txt' % syngas_dir, delimiter=r'\s+') + .stack() + .to_dict() + ) m.syngas_tech_utility_rate = Param( - m.utilities, m.syngas_techs, initialize=data_dict, - doc="kWh of utility u per kmol of methane fed in syngas process i [kWh·kmol methane -1]") + m.utilities, + m.syngas_techs, + initialize=data_dict, + doc="kWh of utility u per kmol of methane fed in syngas process i [kWh·kmol methane -1]", + ) - data_dict = pd.read_csv('%s/syngas_num_units_data.txt' % syngas_dir, delimiter=r'\s+').stack().to_dict() + data_dict = ( + pd.read_csv('%s/syngas_num_units_data.txt' % syngas_dir, delimiter=r'\s+') + .stack() + .to_dict() + ) m.syngas_tech_num_units = Param( - m.syngas_tech_units, m.syngas_techs, initialize=data_dict, - doc="number of units h in syngas process i") + m.syngas_tech_units, + m.syngas_techs, + initialize=data_dict, + doc="number of units h in syngas process i", + ) m.syngas_tech_exchanger_area = Param( m.syngas_techs, doc="total exchanger area in process i per kmol·h-1 methane fed [m2·h·kmol methane -1]", - initialize={'SMR': 0.885917, 'POX': 0.153036, 'ATR': 0.260322, 'CR': 0.726294, - 'DMR': 0.116814, 'BR': 0.825808, 'TR': 0.10539}) + initialize={ + 'SMR': 0.885917, + 'POX': 0.153036, + 'ATR': 0.260322, + 'CR': 0.726294, + 'DMR': 0.116814, + 'BR': 0.825808, + 'TR': 0.10539, + }, + ) m.syngas_tech_reformer_duty = Param( m.syngas_techs, doc="reformer duties per kmol methane fed [kWh·kmol methane-1]", - initialize={'SMR': 54.654, 'POX': 39.0104, 'ATR': 44.4600, 'CR': 68.2382, - 'DMR': 68.412, 'BR': 61.442, 'TR': 6.592}) + initialize={ + 'SMR': 54.654, + 'POX': 39.0104, + 'ATR': 44.4600, + 'CR': 68.2382, + 'DMR': 68.412, + 'BR': 61.442, + 'TR': 6.592, + }, + ) m.process_tech_pressure = Param( m.syngas_techs, doc="process i operating pressure [bar]", - initialize={'SMR': 20, 'POX': 30, 'ATR': 25, 'CR': 25, 'DMR': 1, 'BR': 7, 'TR': 20} + initialize={ + 'SMR': 20, + 'POX': 30, + 'ATR': 25, + 'CR': 25, + 'DMR': 1, + 'BR': 7, + 'TR': 20, + }, ) m.final_syngas_pressure = Param(doc="final syngas pressure [bar]", initialize=1) - m.psa_hydrogen_recovery = Param(initialize=0.9, doc="percentage of hydrogen separated from the inlet syngas stream") + m.psa_hydrogen_recovery = Param( + initialize=0.9, + doc="percentage of hydrogen separated from the inlet syngas stream", + ) m.psa_separation_hydrogen_purity = Param(initialize=0.999) - m.Keqw = Param(initialize=83.3429, doc="equilibrium constant for WGS reaction at 250ºC") + m.Keqw = Param( + initialize=83.3429, doc="equilibrium constant for WGS reaction at 250ºC" + ) - m.stoichiometric_number = Param(doc="stoichiometric number of product syngas", initialize=1) + m.stoichiometric_number = Param( + doc="stoichiometric number of product syngas", initialize=1 + ) m.max_impurity = Param(initialize=0.1, doc="maximum allowed of impurities") - m.max_syngas_techs = Param(initialize=1, doc="Number of syngas technologies that can be selected") + m.max_syngas_techs = Param( + initialize=1, doc="Number of syngas technologies that can be selected" + ) """ Variables """ m.flow = Var(m.streams, m.species, bounds=(0, m.flow_limit)) - m.wgs_steam = Var(doc="steam molar flow provided in the WGS reactor [kmol·s-1]", bounds=(0, None)) - m.oxygen_flow = Var(doc="O2 molar flow provided in the selective oxidation reactor [kmol·s-1]", bounds=(0, None)) - m.Fabs1 = Var(bounds=(0, None), doc="molar flow of CO2 absorbed in absorber1 [kmol·s-1]") - m.Fabs2 = Var(bounds=(0, None), doc="molar flow of CO2 absorbed in absorber2 [kmol·s-1]") + m.wgs_steam = Var( + doc="steam molar flow provided in the WGS reactor [kmol·s-1]", bounds=(0, None) + ) + m.oxygen_flow = Var( + doc="O2 molar flow provided in the selective oxidation reactor [kmol·s-1]", + bounds=(0, None), + ) + m.Fabs1 = Var( + bounds=(0, None), doc="molar flow of CO2 absorbed in absorber1 [kmol·s-1]" + ) + m.Fabs2 = Var( + bounds=(0, None), doc="molar flow of CO2 absorbed in absorber2 [kmol·s-1]" + ) m.flash_water = Var(bounds=(0, None), doc="water removed in flash [kmol·s-1]") - m.co2_inject = Var(bounds=(0, None), doc="molar flow of CO2 used to adjust syngas composition [kmol·s-1]") - m.psa_recovered = Var(m.species, bounds=(0, None), doc="pure hydrogen stream retained in the PSA [kmol·s-1]") - m.purge_flow = Var(m.species, bounds=(0, None), doc="purged molar flow from the PSA [kmol·s-1]") - m.final_syngas_flow = Var(m.species, bounds=(0, m.flow_limit), doc="final adjusted syngas molar flow [kmol·s-1]") + m.co2_inject = Var( + bounds=(0, None), + doc="molar flow of CO2 used to adjust syngas composition [kmol·s-1]", + ) + m.psa_recovered = Var( + m.species, + bounds=(0, None), + doc="pure hydrogen stream retained in the PSA [kmol·s-1]", + ) + m.purge_flow = Var( + m.species, bounds=(0, None), doc="purged molar flow from the PSA [kmol·s-1]" + ) + m.final_syngas_flow = Var( + m.species, + bounds=(0, m.flow_limit), + doc="final adjusted syngas molar flow [kmol·s-1]", + ) @m.Expression(m.superstructure_nodes, m.species) def flow_into(m, option, species): - return sum(m.flow[src, sink, species] for src, sink in m.streams if sink == option) + return sum( + m.flow[src, sink, species] for src, sink in m.streams if sink == option + ) @m.Expression(m.superstructure_nodes, m.species) def flow_out_from(m, option, species): - return sum(m.flow[src, sink, species] for src, sink in m.streams if src == option) + return sum( + m.flow[src, sink, species] for src, sink in m.streams if src == option + ) @m.Expression(m.superstructure_nodes) def total_flow_into(m, option): @@ -235,63 +397,115 @@ def total_flow_into(m, option): def total_flow_from(m, option): return sum(m.flow_out_from[option, species] for species in m.species) - m.base_tech_capital_cost = Var(m.syngas_techs, m.syngas_tech_units, bounds=(0, None)) + m.base_tech_capital_cost = Var( + m.syngas_techs, m.syngas_tech_units, bounds=(0, None) + ) m.base_tech_operating_cost = Var(m.syngas_techs, m.utilities, bounds=(0, None)) - m.raw_material_total_cost = Var(bounds=(0, None), doc="total cost of raw materials [$·s-1]") + m.raw_material_total_cost = Var( + bounds=(0, None), doc="total cost of raw materials [$·s-1]" + ) @m.Expression(m.species) def raw_material_flow(m, species): return sum(m.flow['in', tech, species] for tech in m.syngas_techs) - m.syngas_tech_cost = Var(bounds=(0, None), doc="total cost of sygas process [$·y-1]") - m.syngas_tech_emissions = Var(bounds=(0, None), doc="CO2 emission of syngas processes") + m.syngas_tech_cost = Var( + bounds=(0, None), doc="total cost of sygas process [$·y-1]" + ) + m.syngas_tech_emissions = Var( + bounds=(0, None), doc="CO2 emission of syngas processes" + ) @m.Expression(m.syngas_techs, m.syngas_tech_units) def module_factors(m, tech, equip): - return m.B1[equip] + m.B2[equip] * m.material_factor[equip] * m.syngas_pressure_factor[equip, tech] + return ( + m.B1[equip] + + m.B2[equip] + * m.material_factor[equip] + * m.syngas_pressure_factor[equip, tech] + ) @m.Expression(m.syngas_techs, m.syngas_tech_units) def variable_utilization(m, tech, equip): - variable_rate_term = {'compressor': m.syngas_tech_utility_rate['power', tech], - 'exchanger': m.syngas_tech_exchanger_area[tech], - 'reformer': m.syngas_tech_reformer_duty[tech]} + variable_rate_term = { + 'compressor': m.syngas_tech_utility_rate['power', tech], + 'exchanger': m.syngas_tech_exchanger_area[tech], + 'reformer': m.syngas_tech_reformer_duty[tech], + } return variable_rate_term[equip] * m.flow['in', tech, 'CH4'] * 3600 - m.aux_unit_capital_cost = Var(m.aux_equipment, bounds=(0, None), doc="auxiliary unit capital cost [$·h-1]") + m.aux_unit_capital_cost = Var( + m.aux_equipment, bounds=(0, None), doc="auxiliary unit capital cost [$·h-1]" + ) - m.first_stage_outlet_pressure = Var(doc="final pressure in the mixer before WGS and absorber", bounds=(0, None)) - m.syngas_tech_outlet_pressure = Var(m.syngas_techs, doc="final pressure after compression after syngas synthesis [bar]", bounds=(0, None)) - m.syngas_tech_compressor_power = Var(m.syngas_techs, doc="utility of compressor i after syngas synthesis", bounds=(0, None)) - m.syngas_tech_compressor_cost = Var(doc="capital cost of compressors after syngas synthesis", bounds=(0, None)) + m.first_stage_outlet_pressure = Var( + doc="final pressure in the mixer before WGS and absorber", bounds=(0, None) + ) + m.syngas_tech_outlet_pressure = Var( + m.syngas_techs, + doc="final pressure after compression after syngas synthesis [bar]", + bounds=(0, None), + ) + m.syngas_tech_compressor_power = Var( + m.syngas_techs, + doc="utility of compressor i after syngas synthesis", + bounds=(0, None), + ) + m.syngas_tech_compressor_cost = Var( + doc="capital cost of compressors after syngas synthesis", bounds=(0, None) + ) m.Xw = Var(bounds=(0, None), doc="Moles per second reacted in WGS reactor") - m.wgs_inlet_temperature = Var(bounds=(0, None), doc="WGS reactor temperature before adjustment [ºC]") - m.wgs_heater = Var(bounds=(0, None), doc="duty required to preheat the syngas molar flow entering the WGS reactor [kW]") + m.wgs_inlet_temperature = Var( + bounds=(0, None), doc="WGS reactor temperature before adjustment [ºC]" + ) + m.wgs_heater = Var( + bounds=(0, None), + doc="duty required to preheat the syngas molar flow entering the WGS reactor [kW]", + ) m.psa_power = Var(bounds=(0, None), doc="power consumed by the PSA [kWh]") - m.syngas_power = Var(bounds=(0, None), doc="power needed to compress syngas from Pinlet to 30.01 bar") + m.syngas_power = Var( + bounds=(0, None), doc="power needed to compress syngas from Pinlet to 30.01 bar" + ) - m.syngas_total_flow = Expression(expr=sum(m.final_syngas_flow[species] for species in {'H2', 'CO2', 'CO', 'CH4'})) + m.syngas_total_flow = Expression( + expr=sum(m.final_syngas_flow[species] for species in {'H2', 'CO2', 'CO', 'CH4'}) + ) @m.Expression(m.aux_equipment) def aux_module_factors(m, equip): return m.B1[equip] + m.B2[equip] * m.material_factor[equip] m.final_total_emissions = Expression( - expr=m.syngas_tech_emissions*3600 - + m.Fabs1*3600*44 + m.Fabs2*3600*44 - - m.co2_inject*3600*44 + m.oxygen_flow*m.raw_material_emission['O2']*3600 - + m.wgs_steam*m.raw_material_emission['H2O']*3600 - + m.purge_flow['CO2']*3600*44 - + (m.psa_power + m.syngas_power + sum(m.syngas_tech_compressor_power[tech] for tech in m.syngas_techs) - )*m.utility_emission['power'] - + m.wgs_heater*0.094316389565193) + expr=m.syngas_tech_emissions * 3600 + + m.Fabs1 * 3600 * 44 + + m.Fabs2 * 3600 * 44 + - m.co2_inject * 3600 * 44 + + m.oxygen_flow * m.raw_material_emission['O2'] * 3600 + + m.wgs_steam * m.raw_material_emission['H2O'] * 3600 + + m.purge_flow['CO2'] * 3600 * 44 + + ( + m.psa_power + + m.syngas_power + + sum(m.syngas_tech_compressor_power[tech] for tech in m.syngas_techs) + ) + * m.utility_emission['power'] + + m.wgs_heater * 0.094316389565193 + ) m.final_total_cost = Expression( - expr=m.syngas_tech_cost*3600 + m.syngas_tech_compressor_cost - + sum(m.aux_unit_capital_cost[option] for option in m.aux_equipment) * m.annualization_factor - + (m.psa_power + m.syngas_power + sum(m.syngas_tech_compressor_power[tech] for tech in m.syngas_techs) - )*m.utility_cost['power'] - + m.wgs_heater*0.064) + expr=m.syngas_tech_cost * 3600 + + m.syngas_tech_compressor_cost + + sum(m.aux_unit_capital_cost[option] for option in m.aux_equipment) + * m.annualization_factor + + ( + m.psa_power + + m.syngas_power + + sum(m.syngas_tech_compressor_power[tech] for tech in m.syngas_techs) + ) + * m.utility_cost['power'] + + m.wgs_heater * 0.064 + ) """ Constraints @@ -301,16 +515,29 @@ def aux_module_factors(m, equip): def syngas_process_feed_species_ratio(m, tech, species): if species == 'CH4': return Constraint.Skip - return m.flow['in', tech, species] == feed_ratios.get((tech, species), 0) * m.flow['in', tech, 'CH4'] + return ( + m.flow['in', tech, species] + == feed_ratios.get((tech, species), 0) * m.flow['in', tech, 'CH4'] + ) @m.Constraint(m.syngas_techs, m.species) def syngas_conversion_calc(m, tech, species): - return m.flow[tech, 'ms1', species] == m.flow['in', tech, 'CH4'] * m.syngas_conversion_factor[species, tech] + return ( + m.flow[tech, 'ms1', species] + == m.flow['in', tech, 'CH4'] * m.syngas_conversion_factor[species, tech] + ) - m.raw_material_cost_calc = Constraint(expr=m.raw_material_total_cost == ( - sum(m.raw_material_flow[species] * m.raw_material_cost[species] for species in m.species) - + m.wgs_steam * m.raw_material_cost['H2O'] + m.oxygen_flow * m.raw_material_cost['O2'] - )) + m.raw_material_cost_calc = Constraint( + expr=m.raw_material_total_cost + == ( + sum( + m.raw_material_flow[species] * m.raw_material_cost[species] + for species in m.species + ) + + m.wgs_steam * m.raw_material_cost['H2O'] + + m.oxygen_flow * m.raw_material_cost['O2'] + ) + ) @m.Disjunct(m.process_options) def unit_exists(disj, option): @@ -330,7 +557,9 @@ def no_flow_out(disj, species): def unit_exists_or_not(disj, option): return [m.unit_exists[option], m.unit_absent[option]] - m.Yunit = BooleanVar(m.process_options, doc="Boolean variable for existence of a process unit") + m.Yunit = BooleanVar( + m.process_options, doc="Boolean variable for existence of a process unit" + ) for option in m.process_options: m.Yunit[option].associate_binary_var(m.unit_exists[option].binary_indicator_var) @@ -341,44 +570,93 @@ def unit_exists_or_not(disj, option): @tech_selected.Constraint(m.syngas_tech_units) def base_tech_capital_cost_calc(disj, equip): return m.base_tech_capital_cost[tech, equip] == ( - (m.p1[equip] * m.variable_utilization[tech, equip] - + m.p2[equip] * m.syngas_tech_num_units[equip, tech] * m.module_factors[tech, equip]) - * m.cost_index_ratio / 8000 + ( + m.p1[equip] * m.variable_utilization[tech, equip] + + m.p2[equip] + * m.syngas_tech_num_units[equip, tech] + * m.module_factors[tech, equip] + ) + * m.cost_index_ratio + / 8000 ) @tech_selected.Constraint(m.utilities) def base_tech_operating_cost_calc(disj, util): return m.base_tech_operating_cost[tech, util] == ( - m.syngas_tech_utility_rate[util, tech] * m.flow['in', tech, 'CH4'] * 3600 * m.utility_cost[util] + m.syngas_tech_utility_rate[util, tech] + * m.flow['in', tech, 'CH4'] + * 3600 + * m.utility_cost[util] ) - m.syngas_process_cost_calc = Constraint(expr=m.syngas_tech_cost == ( - sum(sum(m.base_tech_capital_cost[tech, equip] for equip in m.syngas_tech_units) * m.annualization_factor - + sum(m.base_tech_operating_cost[tech, util] for util in m.utilities) - for tech in m.syngas_techs) + m.raw_material_total_cost * 3600) / 3600) + m.syngas_process_cost_calc = Constraint( + expr=m.syngas_tech_cost + == ( + sum( + sum( + m.base_tech_capital_cost[tech, equip] + for equip in m.syngas_tech_units + ) + * m.annualization_factor + + sum(m.base_tech_operating_cost[tech, util] for util in m.utilities) + for tech in m.syngas_techs + ) + + m.raw_material_total_cost * 3600 + ) + / 3600 + ) - m.syngas_emissions_calc = Constraint(expr=m.syngas_tech_emissions == ( - # Emissions from utilities - sum(m.base_tech_operating_cost[tech, util] / m.utility_cost[util] * m.utility_emission[util] - for tech in m.syngas_techs for util in m.utilities) - # CO2 consumed by syngas processes - - sum(m.flow['in', tech, 'CO2'] for tech in m.syngas_techs) * 3600 * 44 - # Emissions from raw materials - + sum(m.raw_material_flow[species] * m.raw_material_emission[species] * 3600 for species in m.species)) / 3600) + m.syngas_emissions_calc = Constraint( + expr=m.syngas_tech_emissions + == ( + # Emissions from utilities + sum( + m.base_tech_operating_cost[tech, util] + / m.utility_cost[util] + * m.utility_emission[util] + for tech in m.syngas_techs + for util in m.utilities + ) + # CO2 consumed by syngas processes + - sum(m.flow['in', tech, 'CO2'] for tech in m.syngas_techs) * 3600 * 44 + # Emissions from raw materials + + sum( + m.raw_material_flow[species] * m.raw_material_emission[species] * 3600 + for species in m.species + ) + ) + / 3600 + ) # Syngas process pressure adjustment @m.Disjunct(m.syngas_techs) def stage_one_compressor(disj, tech): - disj.compressor_power_calc = Constraint(expr=m.syngas_tech_compressor_power[tech] == ( - (1.5 / (1.5 - 1)) / 0.8 * (40 + 273) * 8.314 * sum(m.flow[tech, 'ms1', species] for species in m.species) - * ((m.syngas_tech_outlet_pressure[tech] / m.process_tech_pressure[tech]) ** (1.5 - 1 / 1.5) - 1) - )) + disj.compressor_power_calc = Constraint( + expr=m.syngas_tech_compressor_power[tech] + == ( + (1.5 / (1.5 - 1)) + / 0.8 + * (40 + 273) + * 8.314 + * sum(m.flow[tech, 'ms1', species] for species in m.species) + * ( + ( + m.syngas_tech_outlet_pressure[tech] + / m.process_tech_pressure[tech] + ) + ** (1.5 - 1 / 1.5) + - 1 + ) + ) + ) pass @m.Disjunct(m.syngas_techs) def stage_one_bypass(bypass, tech): - bypass.no_pressure_increase = Constraint(expr=m.syngas_tech_outlet_pressure[tech] == m.process_tech_pressure[tech]) + bypass.no_pressure_increase = Constraint( + expr=m.syngas_tech_outlet_pressure[tech] == m.process_tech_pressure[tech] + ) pass @m.Disjunction(m.syngas_techs) @@ -387,21 +665,33 @@ def stage_one_compressor_or_bypass(m, tech): m.Ycomp = BooleanVar(m.syngas_techs) for tech in m.syngas_techs: - m.Ycomp[tech].associate_binary_var(m.stage_one_compressor[tech].binary_indicator_var) + m.Ycomp[tech].associate_binary_var( + m.stage_one_compressor[tech].binary_indicator_var + ) @m.LogicalConstraint(m.syngas_techs) def compressor_implies_tech(m, tech): return m.Ycomp[tech].implies(m.Yunit[tech]) - m.syngas_tech_compressor_cost_calc = Constraint(expr=m.syngas_tech_compressor_cost == ( - ((3.553 * 10 ** 5) * sum(m.Ycomp[tech].get_associated_binary() for tech in m.syngas_techs) - + 586 * sum(m.syngas_tech_compressor_power[tech] for tech in m.syngas_techs)) - * m.annualization_factor / 8000 - )) + m.syngas_tech_compressor_cost_calc = Constraint( + expr=m.syngas_tech_compressor_cost + == ( + ( + (3.553 * 10**5) + * sum(m.Ycomp[tech].get_associated_binary() for tech in m.syngas_techs) + + 586 + * sum(m.syngas_tech_compressor_power[tech] for tech in m.syngas_techs) + ) + * m.annualization_factor + / 8000 + ) + ) for tech in m.syngas_techs: tech_selected = m.unit_exists[tech] - tech_selected.pressure_balance = Constraint(expr=m.first_stage_outlet_pressure <= m.syngas_tech_outlet_pressure[tech]) + tech_selected.pressure_balance = Constraint( + expr=m.first_stage_outlet_pressure <= m.syngas_tech_outlet_pressure[tech] + ) # ms1 balances @m.Constraint(m.species) @@ -414,36 +704,66 @@ def ms1_mass_balance(m, species): @unit_exists.Constraint(m.species) def unit_inlet_composition_balance(disj, species): total_flow = sum(m.flow_out_from['ms1', jj] for jj in m.species) - total_flow_to_this_unit = sum(m.flow_into[this_unit, jj] for jj in m.species) + total_flow_to_this_unit = sum( + m.flow_into[this_unit, jj] for jj in m.species + ) total_flow_species = m.flow_out_from['ms1', species] - return total_flow * m.flow['ms1', this_unit, species] == total_flow_to_this_unit * total_flow_species + return ( + total_flow * m.flow['ms1', this_unit, species] + == total_flow_to_this_unit * total_flow_species + ) # WGS Reactor CO + H2O <-> CO2 + H2 wgs_exists = m.unit_exists['WGS'] - wgs_exists.CH4_balance = Constraint(expr=m.flow_out_from['WGS', 'CH4'] == m.flow_into['WGS', 'CH4']) - wgs_exists.CO_balance = Constraint(expr=m.flow_out_from['WGS', 'CO'] == m.flow_into['WGS', 'CO'] - m.Xw) - wgs_exists.CO2_balance = Constraint(expr=m.flow_out_from['WGS', 'CO2'] == m.flow_into['WGS', 'CO2'] + m.Xw) - wgs_exists.H2_balance = Constraint(expr=m.flow_out_from['WGS', 'H2'] == m.flow_into['WGS', 'H2'] + m.Xw) - wgs_exists.H2O_balance = Constraint(expr=m.flow_out_from['WGS', 'H2O'] == m.flow_into['WGS', 'H2O'] + m.wgs_steam - m.Xw) + wgs_exists.CH4_balance = Constraint( + expr=m.flow_out_from['WGS', 'CH4'] == m.flow_into['WGS', 'CH4'] + ) + wgs_exists.CO_balance = Constraint( + expr=m.flow_out_from['WGS', 'CO'] == m.flow_into['WGS', 'CO'] - m.Xw + ) + wgs_exists.CO2_balance = Constraint( + expr=m.flow_out_from['WGS', 'CO2'] == m.flow_into['WGS', 'CO2'] + m.Xw + ) + wgs_exists.H2_balance = Constraint( + expr=m.flow_out_from['WGS', 'H2'] == m.flow_into['WGS', 'H2'] + m.Xw + ) + wgs_exists.H2O_balance = Constraint( + expr=m.flow_out_from['WGS', 'H2O'] + == m.flow_into['WGS', 'H2O'] + m.wgs_steam - m.Xw + ) wgs_exists.max_molar_reaction = Constraint(expr=m.Xw <= m.flow_into['WGS', 'CO']) wgs_exists.rxn_equilibrium = Constraint( - expr=m.Keqw * m.flow_out_from['WGS', 'CO'] * m.flow_out_from['WGS', 'H2O'] == ( - m.flow_out_from['WGS', 'CO2'] * m.flow_out_from['WGS', 'H2'] - )) + expr=m.Keqw * m.flow_out_from['WGS', 'CO'] * m.flow_out_from['WGS', 'H2O'] + == (m.flow_out_from['WGS', 'CO2'] * m.flow_out_from['WGS', 'H2']) + ) - wgs_exists.capital_cost = Constraint(expr=m.aux_unit_capital_cost['WGS'] == ( - (m.p1['WGS'] * 100 * m.flow_out_from['WGS', 'H2'] + m.p2['WGS']) - * m.aux_module_factors['WGS'] / 8000 * m.cost_index_ratio - )) + wgs_exists.capital_cost = Constraint( + expr=m.aux_unit_capital_cost['WGS'] + == ( + (m.p1['WGS'] * 100 * m.flow_out_from['WGS', 'H2'] + m.p2['WGS']) + * m.aux_module_factors['WGS'] + / 8000 + * m.cost_index_ratio + ) + ) wgs_exists.temperature_balance = Constraint( - expr=m.wgs_inlet_temperature * m.total_flow_into['WGS'] == ( - sum(m.flow[tech, 'ms1', species] for tech in m.syngas_techs for species in m.species) * 250 + expr=m.wgs_inlet_temperature * m.total_flow_into['WGS'] + == ( + sum( + m.flow[tech, 'ms1', species] + for tech in m.syngas_techs + for species in m.species + ) + * 250 + sum(m.flow['s2', 'ms1', species] for species in m.species) * 40 - )) + ) + ) wgs_exists.heater_duty = Constraint( - expr=m.wgs_heater == m.total_flow_into['WGS'] * 46 * (250 - m.wgs_inlet_temperature)) + expr=m.wgs_heater + == m.total_flow_into['WGS'] * 46 * (250 - m.wgs_inlet_temperature) + ) # Bypass 1 bypass1_exists = m.unit_exists['bypass1'] @@ -457,20 +777,31 @@ def bypass1_mass_balance(disj, species): @absorber_exists.Constraint(m.species) def absorber_mass_balance(disj, species): - return m.flow_out_from['absorber1', species] == m.flow_into['absorber1', species] - ( - m.Fabs1 if species == 'CO2' else 0) + return m.flow_out_from['absorber1', species] == m.flow_into[ + 'absorber1', species + ] - (m.Fabs1 if species == 'CO2' else 0) - absorber_exists.co2_absorption = Constraint(expr=m.Fabs1 == 0.96 * m.flow_into['absorber1', 'CO2']) - absorber_exists.cost = Constraint(expr=m.aux_unit_capital_cost['absorber1'] == ( - (m.p1['absorber1'] * 100 * m.Fabs1 + m.p2['absorber1']) - * m.aux_module_factors['absorber1'] / 8000 * m.cost_index_ratio - )) + absorber_exists.co2_absorption = Constraint( + expr=m.Fabs1 == 0.96 * m.flow_into['absorber1', 'CO2'] + ) + absorber_exists.cost = Constraint( + expr=m.aux_unit_capital_cost['absorber1'] + == ( + (m.p1['absorber1'] * 100 * m.Fabs1 + m.p2['absorber1']) + * m.aux_module_factors['absorber1'] + / 8000 + * m.cost_index_ratio + ) + ) m.unit_absent['absorber1'].no_absorption = Constraint(expr=m.Fabs1 == 0) for this_unit in group1: unit_exists = m.unit_exists[this_unit] - unit_exists.minimum_flow = Constraint(expr=m.total_flow_into[this_unit] >= m.total_flow_from['ms1'] * m.min_flow_division) + unit_exists.minimum_flow = Constraint( + expr=m.total_flow_into[this_unit] + >= m.total_flow_from['ms1'] * m.min_flow_division + ) # Flash @m.Constraint(m.species) @@ -481,18 +812,30 @@ def m1_mass_balance(m, species): @flash_exists.Constraint(m.species) def flash_mass_balance(disj, species): - return m.flow_out_from['flash', species] == (m.flow_into['flash', species] if not species == 'H2O' else 0) + return m.flow_out_from['flash', species] == ( + m.flow_into['flash', species] if not species == 'H2O' else 0 + ) - flash_exists.water_sep = Constraint(expr=m.flash_water == m.flow_into['flash', 'H2O']) + flash_exists.water_sep = Constraint( + expr=m.flash_water == m.flow_into['flash', 'H2O'] + ) @m.Constraint(m.species) def post_flash_split_outlet(m, species): - return m.flow_out_from['flash', species] == m.flow_into['PSA', species] + m.flow_into['ms2', species] + return ( + m.flow_out_from['flash', species] + == m.flow_into['PSA', species] + m.flow_into['ms2', species] + ) - flash_exists.cost = Constraint(expr=m.aux_unit_capital_cost['flash'] == ( - (m.p1['flash'] * 100 * m.flash_water + m.p2['flash']) - * m.aux_module_factors['flash'] / 8000 * m.cost_index_ratio - )) + flash_exists.cost = Constraint( + expr=m.aux_unit_capital_cost['flash'] + == ( + (m.p1['flash'] * 100 * m.flash_water + m.p2['flash']) + * m.aux_module_factors['flash'] + / 8000 + * m.cost_index_ratio + ) + ) # PSA psa_exists = m.unit_exists['PSA'] @@ -502,32 +845,60 @@ def psa_inlet_composition_balance(disj, species): total_flow = sum(m.flow_out_from['s1', jj] for jj in m.species) total_flow_to_this_unit = sum(m.flow_into['PSA', jj] for jj in m.species) total_flow_species = m.flow_out_from['s1', species] - return total_flow * m.flow['s1', 'PSA', species] == total_flow_to_this_unit * total_flow_species + return ( + total_flow * m.flow['s1', 'PSA', species] + == total_flow_to_this_unit * total_flow_species + ) @m.Constraint(m.species) def ms2_inlet_composition_balance(disj, species): total_flow = sum(m.flow_out_from['s1', jj] for jj in m.species) total_flow_to_this_unit = sum(m.flow_into['ms2', jj] for jj in m.species) total_flow_species = m.flow_out_from['s1', species] - return total_flow * m.flow['s1', 'ms2', species] == total_flow_to_this_unit * total_flow_species + return ( + total_flow * m.flow['s1', 'ms2', species] + == total_flow_to_this_unit * total_flow_species + ) @psa_exists.Constraint(m.species) def psa_mass_balance(disj, species): - return m.flow_out_from['PSA', species] + m.psa_recovered[species] == m.flow_into['PSA', species] + return ( + m.flow_out_from['PSA', species] + m.psa_recovered[species] + == m.flow_into['PSA', species] + ) @psa_exists.Constraint(m.species) def psa_recovery(disj, species): return m.flow_out_from['PSA', species] == m.flow_into['PSA', species] * ( - (1 - m.psa_hydrogen_recovery if species == 'H2' else m.psa_separation_hydrogen_purity) + ( + 1 - m.psa_hydrogen_recovery + if species == 'H2' + else m.psa_separation_hydrogen_purity + ) ) - psa_exists.cost = Constraint(expr=m.aux_unit_capital_cost['PSA'] == ( - (m.p1['PSA'] * m.flow_into['PSA', 'H2'] + m.p2['PSA']) - * m.aux_module_factors['PSA'] / 8000 - )) - psa_exists.psa_utility = Constraint(expr=m.psa_power*m.first_stage_outlet_pressure**(1.5-1/1.5) == ( - (1.5/(1.5-1))/0.8*(40+273) * 8.314 * m.total_flow_into['PSA'] - * ((30+1e-6)**(1.5-1/1.5) - m.first_stage_outlet_pressure**(1.5-1/1.5)))) + psa_exists.cost = Constraint( + expr=m.aux_unit_capital_cost['PSA'] + == ( + (m.p1['PSA'] * m.flow_into['PSA', 'H2'] + m.p2['PSA']) + * m.aux_module_factors['PSA'] + / 8000 + ) + ) + psa_exists.psa_utility = Constraint( + expr=m.psa_power * m.first_stage_outlet_pressure ** (1.5 - 1 / 1.5) + == ( + (1.5 / (1.5 - 1)) + / 0.8 + * (40 + 273) + * 8.314 + * m.total_flow_into['PSA'] + * ( + (30 + 1e-6) ** (1.5 - 1 / 1.5) + - m.first_stage_outlet_pressure ** (1.5 - 1 / 1.5) + ) + ) + ) psa_absent = m.unit_absent['PSA'] @@ -546,7 +917,10 @@ def ms4_inlet_mass_balance(m, species): @m.Constraint(m.species) def ms4_outlet_mass_balance(m, species): - return m.flow_out_from['ms4', species] + m.purge_flow[species] == m.flow_into['ms4', species] + return ( + m.flow_out_from['ms4', species] + m.purge_flow[species] + == m.flow_into['ms4', species] + ) @m.Constraint(m.species) def purge_flow_limit(m, species): @@ -557,14 +931,20 @@ def s2_inlet_composition(m, species): total_flow = sum(m.flow_into['ms4', jj] for jj in m.species) total_flow_to_this_unit = sum(m.flow_into['s2', jj] for jj in m.species) total_flow_species = m.flow_into['ms4', species] - return total_flow * m.flow['ms4', 's2', species] == total_flow_to_this_unit * total_flow_species + return ( + total_flow * m.flow['ms4', 's2', species] + == total_flow_to_this_unit * total_flow_species + ) @m.Constraint(m.species) def ms4_to_ms3_composition(m, species): total_flow = sum(m.flow_into['ms4', jj] for jj in m.species) total_flow_to_this_unit = sum(m.flow['ms4', 's2', jj] for jj in m.species) total_flow_species = m.flow_into['ms4', species] - return total_flow * m.flow['ms4', 's2', species] == total_flow_to_this_unit * total_flow_species + return ( + total_flow * m.flow['ms4', 's2', species] + == total_flow_to_this_unit * total_flow_species + ) # s2 @m.Constraint(m.species) @@ -579,7 +959,8 @@ def no_flow_s2_to_m1(m, species): @m.Constraint(m.species) def ms2_mass_balance(m, species): return m.flow_out_from['ms2', species] == ( - m.flow_into['ms2', species] + (m.co2_inject if species == 'CO2' else 0)) + m.flow_into['ms2', species] + (m.co2_inject if species == 'CO2' else 0) + ) # bypass3 bypass3_exists = m.unit_exists['bypass3'] @@ -597,27 +978,47 @@ def compressor_inlet_mass_balance(disj, species): @compressor_exists.Constraint(m.species) def compressor_mass_balance(disj, species): - return m.flow_out_from['compressor', species] == m.flow_into['compressor', species] + return ( + m.flow_out_from['compressor', species] == m.flow_into['compressor', species] + ) - compressor_exists.cost = Constraint(expr=m.aux_unit_capital_cost['compressor'] == ( - ((3.553 * 10 ** 5) + 586 * m.syngas_power) / 8000 * m.cost_index_ratio - )) + compressor_exists.cost = Constraint( + expr=m.aux_unit_capital_cost['compressor'] + == (((3.553 * 10**5) + 586 * m.syngas_power) / 8000 * m.cost_index_ratio) + ) - compressor_exists.work = Constraint(expr=m.syngas_power * m.first_stage_outlet_pressure**(1.5-1/1.5) == ( - (1.5/(1.5-1))/0.8*(40+273)*8.314*m.total_flow_into['compressor'] - * (m.final_syngas_pressure**(1.5-1/1.5) - m.first_stage_outlet_pressure**(1.5-1/1.5)))) + compressor_exists.work = Constraint( + expr=m.syngas_power * m.first_stage_outlet_pressure ** (1.5 - 1 / 1.5) + == ( + (1.5 / (1.5 - 1)) + / 0.8 + * (40 + 273) + * 8.314 + * m.total_flow_into['compressor'] + * ( + m.final_syngas_pressure ** (1.5 - 1 / 1.5) + - m.first_stage_outlet_pressure ** (1.5 - 1 / 1.5) + ) + ) + ) no_compressor = m.unit_absent['compressor'] - no_compressor.final_pressure = Constraint(expr=m.first_stage_outlet_pressure >= m.final_syngas_pressure) + no_compressor.final_pressure = Constraint( + expr=m.first_stage_outlet_pressure >= m.final_syngas_pressure + ) - compressor_exists.compressor_minimum_flow = Constraint(expr=m.total_flow_into['compressor'] >= ( - m.total_flow_from['flash'] * m.min_flow_division - )) - psa_exists.psa_minimum_flow = Constraint(expr=m.total_flow_into['PSA'] >= ( - m.total_flow_from['flash'] * m.min_flow_division - )) + compressor_exists.compressor_minimum_flow = Constraint( + expr=m.total_flow_into['compressor'] + >= (m.total_flow_from['flash'] * m.min_flow_division) + ) + psa_exists.psa_minimum_flow = Constraint( + expr=m.total_flow_into['PSA'] + >= (m.total_flow_from['flash'] * m.min_flow_division) + ) - m.compressor_or_bypass = LogicalConstraint(expr=exactly(1, m.Yunit['bypass3'], m.Yunit['compressor'])) + m.compressor_or_bypass = LogicalConstraint( + expr=exactly(1, m.Yunit['bypass3'], m.Yunit['compressor']) + ) # ms3 @m.Constraint(m.species) @@ -633,37 +1034,53 @@ def bypass4_mass_balance(disj, species): # absorber 2 absorber2_exists = m.unit_exists['absorber2'] - + @absorber2_exists.Constraint(m.species) def absorber2_mass_balance(disj, species): return m.flow_out_from['absorber2', species] == ( - m.flow_into['absorber2', species] - (m.Fabs2 if species == 'CO2' else 0)) - - absorber2_exists.co2_absorption = Constraint(expr=m.Fabs2 == 0.96 * m.flow_into['absorber2', 'CO2']) - absorber2_exists.cost = Constraint(expr=m.aux_unit_capital_cost['absorber2'] == ( - (m.p1['absorber2'] * 100 * m.Fabs2 + m.p2['absorber2']) - * m.aux_module_factors['absorber2'] / 8000 * m.cost_index_ratio - )) + m.flow_into['absorber2', species] - (m.Fabs2 if species == 'CO2' else 0) + ) + + absorber2_exists.co2_absorption = Constraint( + expr=m.Fabs2 == 0.96 * m.flow_into['absorber2', 'CO2'] + ) + absorber2_exists.cost = Constraint( + expr=m.aux_unit_capital_cost['absorber2'] + == ( + (m.p1['absorber2'] * 100 * m.Fabs2 + m.p2['absorber2']) + * m.aux_module_factors['absorber2'] + / 8000 + * m.cost_index_ratio + ) + ) m.unit_absent['absorber2'].no_absorption = Constraint(expr=m.Fabs2 == 0) - - m.only_one_absorber = LogicalConstraint(expr=atmost(1, m.Yunit['absorber1'], m.Yunit['absorber2'])) + + m.only_one_absorber = LogicalConstraint( + expr=atmost(1, m.Yunit['absorber1'], m.Yunit['absorber2']) + ) @m.Constraint(m.species) def final_mass_balance(m, species): return m.final_syngas_flow[species] == m.flow_into['m2', species] m.syngas_stoich_number = Constraint( - expr=m.stoichiometric_number * (m.final_syngas_flow['CO'] + m.final_syngas_flow['CO2']) == ( - m.final_syngas_flow['H2'] - m.final_syngas_flow['CO2'] - )) + expr=m.stoichiometric_number + * (m.final_syngas_flow['CO'] + m.final_syngas_flow['CO2']) + == (m.final_syngas_flow['H2'] - m.final_syngas_flow['CO2']) + ) m.impurity_limit = Constraint( - expr=m.max_impurity * sum(m.final_syngas_flow[species] for species in {'CO', 'H2', 'CH4', 'CO2', 'H2O'}) + expr=m.max_impurity + * sum( + m.final_syngas_flow[species] + for species in {'CO', 'H2', 'CH4', 'CO2', 'H2O'} + ) >= sum(m.final_syngas_flow[species] for species in {'CH4', 'H2O'}) ) m.syngas_process_limits = LogicalConstraint( - expr=atmost(m.max_syngas_techs, [m.Yunit[tech] for tech in m.syngas_techs])) + expr=atmost(m.max_syngas_techs, [m.Yunit[tech] for tech in m.syngas_techs]) + ) # Bounds m.wgs_heater.setub(10000) @@ -717,7 +1134,7 @@ def final_mass_balance(m, species): m.syngas_maximum_demand = Constraint(expr=m.syngas_total_flow <= 5) m.final_syngas_flow['CO'].fix(0.3) - m.final_syngas_flow['CO2'].fix(0.3*m.co2_ratio) + m.final_syngas_flow['CO2'].fix(0.3 * m.co2_ratio) m.obj = Objective(expr=m.final_total_cost) @@ -742,15 +1159,26 @@ def final_mass_balance(m, species): def display_nonzeros(var): if var.is_indexed(): + def nonzero_rows(): for k, v in var.items(): if v.value == 0: continue yield k, [value(v.lb), v.value, value(v.ub), v.fixed, v.stale, v.domain] + _attr, _, _header, _ = var._pprint() var._pprint_base_impl( - None, False, "", var.local_name, var.doc, - var.is_constructed(), _attr, nonzero_rows(), _header, lambda k, v: v) + None, + False, + "", + var.local_name, + var.doc, + var.is_constructed(), + _attr, + nonzero_rows(), + _header, + lambda k, v: v, + ) else: var.display() @@ -765,9 +1193,14 @@ def nonzero_rows(): # # solver="cplex", add_options=['GAMS_MODEL.optfile=1;', '$onecho > cplex.opt', 'iis=1', '$offecho'], # ) result = SolverFactory('gdpopt').solve( - m, strategy='LOA', tee=True, mip_solver='gams', - nlp_solver='gams', nlp_solver_args=dict(solver='scip', add_options=['option optcr=0;']), - minlp_solver='gams', minlp_solver_args=dict(solver='baron', add_options=['option optcr=0;']) + m, + strategy='LOA', + tee=True, + mip_solver='gams', + nlp_solver='gams', + nlp_solver_args=dict(solver='scip', add_options=['option optcr=0;']), + minlp_solver='gams', + minlp_solver_args=dict(solver='baron', add_options=['option optcr=0;']), ) if not result.solver.termination_condition == tc.optimal: print("Termination Condition: ", result.solver.termination_condition) diff --git a/setup.py b/setup.py index a338303..bd40d61 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", - "Topic :: Software Development :: Libraries :: Python Modules" + "Topic :: Software Development :: Libraries :: Python Modules", ], ) @@ -41,7 +41,9 @@ setup(setup_requires=['setuptools_scm'], use_scm_version=True, **kwargs) except (ImportError, LookupError): default_version = '1.0.0' - warning('Cannot use .git version: package setuptools_scm not installed ' - 'or .git directory not present.') + warning( + 'Cannot use .git version: package setuptools_scm not installed ' + 'or .git directory not present.' + ) print('Defaulting to version: {}'.format(default_version)) setup(**kwargs) From 0afaa59ef1b04aed9e52fee2f9a4cf72bf7e8c06 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 16 May 2024 17:53:28 -0400 Subject: [PATCH 57/60] add default.extend - words for typo check --- .github/workflows/lint.yml | 3 ++- .github/workflows/typos.toml | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/typos.toml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 642b1a5..7c7b737 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,4 +14,5 @@ jobs: options: "-S -C --check --diff" - name: Spell Check uses: crate-ci/typos@master - + with: + config: ./.github/workflows/typos.toml diff --git a/.github/workflows/typos.toml b/.github/workflows/typos.toml new file mode 100644 index 0000000..9cbc622 --- /dev/null +++ b/.github/workflows/typos.toml @@ -0,0 +1,4 @@ +[default.extend - words] +# Ignore HDA +hda = "hda" +HDA = "HDA" From 6144f2a857a1dc52b60cf3a34e4a430822358150 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 16 May 2024 18:02:31 -0400 Subject: [PATCH 58/60] fix typos --- gdplib/biofuel/model.py | 64 +++++++------- gdplib/gdp_col/column.py | 2 +- gdplib/gdp_col/fenske.py | 2 +- gdplib/hda/HDA_GDP_gdpopt.py | 84 +++++++++---------- gdplib/kaibel/kaibel_init.py | 2 +- .../med_term_purchasing.py | 10 +-- gdplib/stranded_gas/model.py | 4 +- 7 files changed, 84 insertions(+), 84 deletions(-) diff --git a/gdplib/biofuel/model.py b/gdplib/biofuel/model.py index a568c6c..84820f4 100644 --- a/gdplib/biofuel/model.py +++ b/gdplib/biofuel/model.py @@ -44,7 +44,7 @@ def build_model(): Returns ------- Pyomo.ConcreteModel - The Pyomo concrete model which descibes the multiperiod location-allocation optimization model designed to determine the most cost-effective network layout and production allocation to meet market demands. + The Pyomo concrete model which describes the multiperiod location-allocation optimization model designed to determine the most cost-effective network layout and production allocation to meet market demands. References ---------- @@ -72,7 +72,7 @@ def discount_factor(m, t): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model t : int Index of time in months from 0 to 120 (10 years) @@ -97,7 +97,7 @@ def market_demand(m, mkt, t): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model mkt : int Index of the market from 1 to 10 t : int @@ -121,7 +121,7 @@ def available_supply(m, sup, t): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model sup : int Index of the supplier from 1 to 10 t : int @@ -149,7 +149,7 @@ def supplier_x(m, sup): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model sup : int Index of the supplier from 1 to 10 @@ -168,7 +168,7 @@ def supplier_y(m, sup): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model sup : int Index of the supplier from 1 to 10 @@ -187,7 +187,7 @@ def market_x(m, mkt): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model mkt : int Index of the market from 1 to 10 @@ -206,7 +206,7 @@ def market_y(m, mkt): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model mkt : int Index of the market from 1 to 10 @@ -225,7 +225,7 @@ def site_x(m, site): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model site : int Index of the facility site from 1 to 12 @@ -244,7 +244,7 @@ def site_y(m, site): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model site : int Index of the facility site from 1 to 12 @@ -263,7 +263,7 @@ def dist_supplier_to_site(m, sup, site): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model sup : int Index of the supplier from 1 to 10 site : int @@ -287,7 +287,7 @@ def dist_site_to_market(m, site, mkt): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model site : int Index of the facility site from 1 to 12 mkt : int @@ -362,7 +362,7 @@ def raw_material_unit_cost(m, sup, t): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model sup : int Index of the supplier from 1 to 10 t : int @@ -383,7 +383,7 @@ def module_unit_cost(m, t): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model t : int Index of time in months from 0 to 120 (10 years) @@ -402,7 +402,7 @@ def unit_production_cost(m, t): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model t : int Index of time in months from 0 to 120 (10 years) @@ -421,7 +421,7 @@ def transport_fixed_cost(m): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model Returns ------- @@ -438,7 +438,7 @@ def unit_product_transport_cost(m, t): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model t : int Index of time in months from 0 to 120 (10 years) @@ -457,7 +457,7 @@ def unit_raw_material_transport_cost(m, t): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model t : int Index of time in months from 0 to 120 (10 years) @@ -493,7 +493,7 @@ def supply_limits(m, sup, t): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model sup : int Index of the supplier from 1 to 10 t : int @@ -517,7 +517,7 @@ def demand_satisfaction(m, mkt, t): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model mkt : int Index of the market from 1 to 10 t : int @@ -541,7 +541,7 @@ def product_balance(m, site, t): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model site : int Index of the facility site from 1 to 12 t : int @@ -564,7 +564,7 @@ def require_raw_materials(m, site, t): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model site : int Index of the facility site from 1 to 12 t : int @@ -625,7 +625,7 @@ def site_type(m, site): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model site : int Index of the facility site from 1 to 12 @@ -644,7 +644,7 @@ def supply_route_active_or_not(m, sup, site): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model sup : int Index of the supplier from 1 to 10 site : int @@ -665,7 +665,7 @@ def product_route_active_or_not(m, site, mkt): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model site : int Index of the facility site from 1 to 12 mkt : int @@ -686,7 +686,7 @@ def raw_material_transport_cost(m, sup, site): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model sup : int _description_ site : int @@ -713,7 +713,7 @@ def raw_material_fixed_transport_cost(m): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model Returns ------- @@ -738,7 +738,7 @@ def product_transport_cost(m, site, mkt): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model site : int Index of the facility site from 1 to 12 mkt : int @@ -765,7 +765,7 @@ def product_fixed_transport_cost(m): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model Returns ------- @@ -792,7 +792,7 @@ def module_setup_cost(m, site, t): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model site : int Index of the facility site from 1 to 12 t : int @@ -817,7 +817,7 @@ def module_teardown_credit(m, site, t): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model site : int Index of the facility site from 1 to 12 t : int @@ -838,7 +838,7 @@ def conv_salvage_value(m, site): Parameters ---------- m : Pyomo.ConcreteModel - Pyomo concrete model which descibes the multiperiod location-allocation optimization model + Pyomo concrete model which describes the multiperiod location-allocation optimization model site : int Index of the facility site from 1 to 12 diff --git a/gdplib/gdp_col/column.py b/gdplib/gdp_col/column.py index 762a95a..16ee9ef 100644 --- a/gdplib/gdp_col/column.py +++ b/gdplib/gdp_col/column.py @@ -325,7 +325,7 @@ def monotonoic_temperature(_, t): m.gamma = Var( m.comps, m.trays, - doc='liquid activity coefficent of component on tray', + doc='liquid activity coefficient of component on tray', domain=NonNegativeReals, bounds=(0, 10), initialize=1, diff --git a/gdplib/gdp_col/fenske.py b/gdplib/gdp_col/fenske.py index 10e9813..d35aa8a 100644 --- a/gdplib/gdp_col/fenske.py +++ b/gdplib/gdp_col/fenske.py @@ -50,7 +50,7 @@ def calculate_Fenske(xD, xB): m.gamma = Var( m.comps, m.trays, - doc='liquid activity coefficent of component on tray', + doc='liquid activity coefficient of component on tray', domain=NonNegativeReals, bounds=(0, 10), initialize=1, diff --git a/gdplib/hda/HDA_GDP_gdpopt.py b/gdplib/hda/HDA_GDP_gdpopt.py index f90c165..cf817b1 100644 --- a/gdplib/hda/HDA_GDP_gdpopt.py +++ b/gdplib/hda/HDA_GDP_gdpopt.py @@ -19,10 +19,10 @@ def HDA_model(): # ## scalars 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") @@ -571,7 +571,7 @@ def Permset(m, compon): within=NonNegativeReals, bounds=(None, 10), initialize=1, - doc='heating requied (1.e+12 kj per yr)', + doc='heating required (1.e+12 kj per yr)', ) # cooler m.qc = Var( @@ -2583,7 +2583,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) @@ -2598,7 +2598,7 @@ def recycle_methane_membrane(disj): disj.compressor_4 = Block(m.four, rule=build_compressor) @m.Disjunction() - def methane_treatmet(m): + def methane_treatments(m): return [m.recycle_methane_purge, m.recycle_methane_membrane] # fourth disjunction: recycle hydrogen with absorber or not @@ -2731,9 +2731,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)" @@ -2741,62 +2741,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_separator_fixed_cost = Param( + initialize=43.24, doc="membrane separator fixed cost ($1e3 per year)" ) - m.membrane_seperator_linear_coeffcient = Param( + m.membrane_separator_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="adiabtic reactor fixed cost ($1e3 per year)" ) - m.adiabtic_reactor_linear_coeffcient = Param( + m.adiabtic_reactor_linear_coefficient = Param( initialize=1.257, - doc="adiabtic reactor linear coeffcient (times reactor volumn) ($1e3 per year)", + doc="adiabtic 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_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 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)") @@ -2820,8 +2820,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 @@ -2832,42 +2832,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_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_coeffcient * m.f[54] + + m.membrane_separator_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 @@ -2878,7 +2878,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_coeffcient * (m.elec[1] + m.elec[2] + m.elec[3]) - m.compressor_linear_coeffcient_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_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]) - (3.0 * 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 + # 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 @@ -3076,7 +3076,7 @@ def show_decision(m): # Check if constraints are violated infeasible_constraints(m) - # show optimial flowsheet selection + # show optimal flowsheet selection # show_decision(m) print(res) diff --git a/gdplib/kaibel/kaibel_init.py b/gdplib/kaibel/kaibel_init.py index 70883b5..ce28540 100644 --- a/gdplib/kaibel/kaibel_init.py +++ b/gdplib/kaibel/kaibel_init.py @@ -61,7 +61,7 @@ def initialize_kaibel(): ## Column 1 c_c1 = 4 # Components in Column 1 - lc_c1 = 3 # Ligh component 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 diff --git a/gdplib/med_term_purchasing/med_term_purchasing.py b/gdplib/med_term_purchasing/med_term_purchasing.py index 16ad6f8..84af537 100644 --- a/gdplib/med_term_purchasing/med_term_purchasing.py +++ b/gdplib/med_term_purchasing/med_term_purchasing.py @@ -181,7 +181,7 @@ def build_model(): # sigmad_jt # sigmad(j, t) in GAMS - # Minimum quantity of chemical j that must be bought before recieving a Discount under discount contract + # Minimum quantity of chemical j that must be bought before receiving a Discount under discount contract model.MinAmount_Discount = Param( model.Streams, model.TimePeriods, @@ -189,7 +189,7 @@ def build_model(): doc='Minimum quantity of chemical j that must be bought before receiving a Discount under discount contract', ) - # min quantity to recieve discount under bulk contract + # min quantity to receive discount under bulk contract # sigmab(j, t) in GAMS model.MinAmount_Bulk = Param( model.Streams, @@ -198,7 +198,7 @@ def build_model(): doc='Minimum quantity of chemical j that must be bought before receiving a Discount under bulk contract', ) - # min quantity to recieve discount under length contract + # min quantity to receive discount under length contract # sigmal(j, p) in GAMS model.MinAmount_Length = Param( model.Streams, @@ -815,7 +815,7 @@ def profit_rule(model): model.profit = Objective(rule=profit_rule, sense=maximize, doc='Maximize profit') - # flow of raw materials is the total amount purchased (accross all contracts) + # flow of raw materials is the total amount purchased (across all contracts) def raw_material_flow_rule(model, j, t): """ Ensures the total flow of raw material j in time period t equals the sum of amounts purchased under all contract types. @@ -1379,7 +1379,7 @@ def shortfall_max_rule(model, j, t): doc='Maximum shortfall allowed for each product j in each time period t', ) - # maxiumum capacities of suppliers + # maximum capacities of suppliers def supplier_capacity_rule(model, j, t): """ Enforces the upper limits on the supply capacity for each raw material j provided by suppliers in each time period t. diff --git a/gdplib/stranded_gas/model.py b/gdplib/stranded_gas/model.py index ce8ab72..adc6720 100644 --- a/gdplib/stranded_gas/model.py +++ b/gdplib/stranded_gas/model.py @@ -499,7 +499,7 @@ def gasoline_price(m, t): return 2.5 * m.discount_factor[t] @m.Param(m.time, doc="Gasoline transport cost [$/gal/100 miles]") - def gasoline_tranport_cost(m, t): + def gasoline_transport_cost(m, t): """ Calculates the gasoline transport cost in a given time period. @@ -1063,7 +1063,7 @@ def product_transport_cost(m, site, mkt): / 1e6 # $ to MM$ * m.distance[site, mkt] / 100 - * m.gasoline_tranport_cost[t] + * m.gasoline_transport_cost[t] for t in m.time ) From 383739959c7453e6890ecfccb35c579bdce45cbc Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 16 May 2024 18:05:05 -0400 Subject: [PATCH 59/60] fix typos.toml error --- .github/workflows/typos.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/typos.toml b/.github/workflows/typos.toml index 9cbc622..999b093 100644 --- a/.github/workflows/typos.toml +++ b/.github/workflows/typos.toml @@ -1,4 +1,4 @@ -[default.extend - words] +[default.extend-words] # Ignore HDA hda = "hda" HDA = "HDA" From 2d71c6077df9cf27d44341f20164157e057d530f Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 16 May 2024 18:07:14 -0400 Subject: [PATCH 60/60] fix typos --- .github/workflows/typos.toml | 1 + gdplib/med_term_purchasing/med_term_purchasing.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/typos.toml b/.github/workflows/typos.toml index 999b093..a7e28f0 100644 --- a/.github/workflows/typos.toml +++ b/.github/workflows/typos.toml @@ -2,3 +2,4 @@ # Ignore HDA hda = "hda" HDA = "HDA" +equil = "equil" diff --git a/gdplib/med_term_purchasing/med_term_purchasing.py b/gdplib/med_term_purchasing/med_term_purchasing.py index 84af537..73bb3c8 100644 --- a/gdplib/med_term_purchasing/med_term_purchasing.py +++ b/gdplib/med_term_purchasing/med_term_purchasing.py @@ -1106,7 +1106,7 @@ def process_balance_rule4(model, t): doc='Input/output balance equation 2 for Process 3 in the process network', ) - # process capacity contraints + # process capacity constraints # these are hardcoded based on the three processes and the process flow structure def process_capacity_rule1(model, t): """