From 9ef74b09852c92fb653a5c36914bf6c56f5f1134 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 14 Sep 2023 13:52:47 -0400 Subject: [PATCH 001/124] add citation --- CITATION.ccf | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 CITATION.ccf diff --git a/CITATION.ccf b/CITATION.ccf new file mode 100644 index 0000000..a529319 --- /dev/null +++ b/CITATION.ccf @@ -0,0 +1,16 @@ +cff-version: 1.0.0 +message: "If you use this software, please cite it as below." +authors: + - family-names: Bernal + given-names: David E. + orcid: https://orcid.org/0000-0002-8308-5016 + - family-names: Chen + given-names: Qi + - family-names: Liu + given-names: Yunshan + - family-names: Johnson + given-names: Emma +title: "GDPLib: an open library of Generalized Disjunctive Programming (GDP) models" +version: 1.0.0 +url: https://github.com/SECQUOIA/gdplib +license-url: https://github.com/SECQUOIA/gdplib/blob/master/LICENSE From 5f0319c5d8b70e0b1a1eeac527477176c05457a0 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Fri, 15 Sep 2023 13:37:47 -0400 Subject: [PATCH 002/124] update citation --- CITATION.ccf | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CITATION.ccf b/CITATION.ccf index a529319..84da065 100644 --- a/CITATION.ccf +++ b/CITATION.ccf @@ -14,3 +14,25 @@ title: "GDPLib: an open library of Generalized Disjunctive Programming (GDP) mod version: 1.0.0 url: https://github.com/SECQUOIA/gdplib license-url: https://github.com/SECQUOIA/gdplib/blob/master/LICENSE +preferred-citation: + type: incollection + booktitle: "Computer Aided Chemical Engineering" + volume: 49 + start: 1285 + end: 1290 + title: "Advances in Generalized Disjunctive and Mixed-Integer Nonlinear Programming Algorithms and Software for Superstructure Optimization" + year:2022 + publisher: "Elsevier" + authors: + - family-names: "Bernal" + given-names: "David E." + - family-names: "Liu" + given-names: "Yunshan" + - family-names: "Bynum" + given-names: "Michael L" + - family-names: "Laird" + given-names: "Carl D" + - family-names: "Siirola" + given-names: "John D" + - family-names: "Grossmann" + given-names: "Ignacio E" From 756c0239e4bc8295d26b643bca7ea1b3cf9a7d8c Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 19 Sep 2023 17:00:02 -0400 Subject: [PATCH 003/124] update --- CITATION.ccf | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CITATION.ccf b/CITATION.ccf index 84da065..54026d5 100644 --- a/CITATION.ccf +++ b/CITATION.ccf @@ -1,15 +1,20 @@ cff-version: 1.0.0 message: "If you use this software, please cite it as below." authors: - - family-names: Bernal + - family-names: Bernal Neira given-names: David E. orcid: https://orcid.org/0000-0002-8308-5016 + - family-names: Peng + given-names: Zedong + orcid: https://orcid.org/0000-0001-6001-1738 - family-names: Chen given-names: Qi + orcid: https://orcid.org/0000-0002-2389-2238 - family-names: Liu given-names: Yunshan - family-names: Johnson given-names: Emma + orcid: https://orcid.org/0000-0002-4285-5184 title: "GDPLib: an open library of Generalized Disjunctive Programming (GDP) models" version: 1.0.0 url: https://github.com/SECQUOIA/gdplib @@ -24,7 +29,7 @@ preferred-citation: year:2022 publisher: "Elsevier" authors: - - family-names: "Bernal" + - family-names: "Bernal Neira" given-names: "David E." - family-names: "Liu" given-names: "Yunshan" From a79ce209fe46cbefc70fa8363bfb869d5372a59a Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 19 Sep 2023 17:49:54 -0400 Subject: [PATCH 004/124] move gdpopt config --- gdplib/methanol/methanol.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/gdplib/methanol/methanol.py b/gdplib/methanol/methanol.py index 16f802a..dd7702c 100644 --- a/gdplib/methanol/methanol.py +++ b/gdplib/methanol/methanol.py @@ -650,11 +650,7 @@ def solve_with_gdp_opt(): lb, ub = compute_bounds_on_expr(_c.body) _d.BigM[_c] = max(abs(lb), abs(ub)) opt = pe.SolverFactory('gdpopt') - opt.CONFIG.strategy = 'LOA' - opt.CONFIG.mip_solver = 'gams' - opt.CONFIG.nlp_solver = 'gams' - opt.CONFIG.tee = True - res = opt.solve(m) + 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) From 430be5ceae6fa7b353032aa1a1ecfe6cbe1fd437 Mon Sep 17 00:00:00 2001 From: David Bernal Date: Fri, 20 Oct 2023 14:04:37 -0400 Subject: [PATCH 005/124] Rename CITATION.ccf to CITATION.cff --- CITATION.ccf => CITATION.cff | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename CITATION.ccf => CITATION.cff (100%) diff --git a/CITATION.ccf b/CITATION.cff similarity index 100% rename from CITATION.ccf rename to CITATION.cff From 3b718c4135db8fe8ae2ee3c39775c351a976dce4 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 2 Nov 2023 11:56:29 -0400 Subject: [PATCH 006/124] Added Documentation for pyomo examples --- gdplib/pyomo_examples/batch_processing.py | 29 +++++++++-------------- 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/gdplib/pyomo_examples/batch_processing.py b/gdplib/pyomo_examples/batch_processing.py index 3695e19..00daaea 100644 --- a/gdplib/pyomo_examples/batch_processing.py +++ b/gdplib/pyomo_examples/batch_processing.py @@ -1,3 +1,12 @@ +""" +batch_processing.py +This problem seeks to minimize the investment cost in the design of a plant with multiple units in parallel and intermediate storage tanks [1]. + +Reference: + [1] Ravemark, E. Optimization models for design and operation of chemical batch processes. Ph.D. Thesis, ETH Zurich, 1995. + [2] Vecchietti, A.; Grossmann, I. E. LOGMIP: a disjunctive 0-1 non-linear optimizer for process system models. Computers and Chemical Engineering 1994, 23, 555–565. + +""" from os.path import join from pyomo.common.fileutils import this_file_dir @@ -23,7 +32,7 @@ def build_model(): model.BigM[None] = 1000 - ## Constants from GAMS + # Constants from GAMS StorageTankSizeFactor = 2*5 # btw, I know 2*5 is 10... I don't know why it's written this way in GAMS? StorageTankSizeFactorByProd = 3 MinFlow = -log(10000) @@ -36,10 +45,7 @@ def build_model(): # TODO: YOU ARE HERE. YOU HAVEN'T ACTUALLY MADE THESE THE BOUNDS YET, NOR HAVE YOU FIGURED OUT WHOSE # BOUNDS THEY ARE. AND THERE ARE MORE IN GAMS. - - ########## # Sets - ########## model.PRODUCTS = Set() model.STAGES = Set(ordered=True) @@ -52,12 +58,9 @@ def filter_out_last(model, j): # TODO: these aren't in the formulation?? - #model.STORAGE_TANKS = Set() + # model.STORAGE_TANKS = Set() - - ############### # Parameters - ############### model.HorizonTime = Param() model.Alpha1 = Param() @@ -92,9 +95,7 @@ def get_log_coeffs(model, k): model.unitsOutOfPhaseUB = Param(model.STAGES, default=UnitsOutOfPhaseUB) - ################ # Variables - ################ # TODO: right now these match the formulation. There are more in GAMS... @@ -138,9 +139,7 @@ def get_storageTankSize_bounds(model, j): model.outOfPhase = Var(model.STAGES, model.PARALLELUNITS, within=Binary) model.inPhase = Var(model.STAGES, model.PARALLELUNITS, within=Binary) - ############### # Objective - ############### def get_cost_rule(model): return model.Alpha1 * sum(exp(model.unitsInPhase_log[j] + model.unitsOutOfPhase_log[j] + \ @@ -148,11 +147,7 @@ def get_cost_rule(model): model.Alpha2 * sum(exp(model.Beta2 * model.storageTankSize_log[j]) for j in model.STAGESExceptLast) model.min_cost = Objective(rule=get_cost_rule) - - ############## # Constraints - ############## - def processing_capacity_rule(model, j, i): return model.volume_log[j] >= log(model.ProductSizeFactor[i, j]) + model.batchSize_log[i, j] - \ model.unitsInPhase_log[j] @@ -169,9 +164,7 @@ def finish_in_time_rule(model): model.finish_in_time = Constraint(rule=finish_in_time_rule) - ############### # Disjunctions - ############### def storage_tank_selection_disjunct_rule(disjunct, selectStorageTank, j): model = disjunct.model() From 99b2abca659b7bfdf4ae7f981771aea835b9f977 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 21 Dec 2023 21:35:35 -0500 Subject: [PATCH 007/124] Add jobshop scheduling model and small concrete model documentation --- gdplib/pyomo_examples/jobshop.py | 97 +++++++++++++++++++++++++++++--- 1 file changed, 89 insertions(+), 8 deletions(-) diff --git a/gdplib/pyomo_examples/jobshop.py b/gdplib/pyomo_examples/jobshop.py index 84bb159..b9815f3 100644 --- a/gdplib/pyomo_examples/jobshop.py +++ b/gdplib/pyomo_examples/jobshop.py @@ -32,11 +32,22 @@ # def build_model(): + """ + Build the jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. + A zero wait transfer policy is assumed between stages. + To obtain a feasible solution it is necessary to eliminate all clashes between jobs. + It requires that no two jobs be performed at any stage at any time. The objective is to minimize the makespan, the time to complete all jobs. + + References: + Raman & Grossmann, 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.JOBS = Set(ordered=True) - model.STAGES = Set(ordered=True) + model.JOBS = Set(ordered=True, doc='Set of jobs') + model.STAGES = Set(ordered=True, doc='Set of stages') model.I_BEFORE_K = RangeSet(0,1) + # Task durations model.tau = Param(model.JOBS, model.STAGES, default=0) @@ -45,25 +56,72 @@ def build_model(): model.ms = Var() # Start time of each job def t_bounds(model, I): + """ + Calculate the time bounds for the start time of each job in a scheduling model. + + Args: + model (Pyomo.Abstractmodel): jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. + A zero wait transfer policy is assumed between stages. + I (str): job index + + Returns: + tuple: (lower bound, upper bound) for the start time of each job in a scheduling model. + """ return (0, sum(value(model.tau[idx]) for idx in model.tau)) - model.t = Var( model.JOBS, within=NonNegativeReals, bounds=t_bounds ) + model.t = Var( model.JOBS, within=NonNegativeReals, bounds=t_bounds, doc='Start time of each job') # Auto-generate the L set (potential collisions between 2 jobs at any stage. def _L_filter(model, I, K, J): + """ + Filter for the L set (potential collisions between 2 jobs at any stage. + + Args: + model (Pyomo.Abstractmodel): jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. + A zero wait transfer policy is assumed between stages. + I (str): job index + K (str): job index that is greater than I (After I) + J (int): stage index + + Returns: + bool: True if I < K and the parameters, model.tau[I,J] and model.tau[K,J] + """ return I < K and model.tau[I,J] and model.tau[K,J] model.L = Set( initialize=model.JOBS * model.JOBS * model.STAGES, - dimen=3, filter=_L_filter) + dimen=3, filter=_L_filter, doc='Set of potential collisions between 2 jobs at any stage') # Makespan is greater than the start time of every job + that job's # total duration def _feas(model, I): + """_summary_ + + Args: + model (Pyomo.Abstractmodel): jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. + A zero wait transfer policy is assumed between stages. + I (str): job index + + Returns: + bool: True if the makespan is greater than the sum of the start time of every job and that job's total duration. + """ return model.ms >= model.t[I] + sum(model.tau[I,M] for M in model.STAGES) - model.Feas = Constraint(model.JOBS, rule=_feas) + model.Feas = Constraint(model.JOBS, rule=_feas, doc='Makespan is greater than the start time of every job + that job''s total duration') # Disjunctions to prevent clashes at a stage: This creates a set of # disjunct pairs: one if job I occurs before job K and the other if job # K occurs before job I. def _NoClash(disjunct, I, K, J, IthenK): + """ + Disjunctions to prevent clashes at a stage: This creates a set of disjunct pairs: one if job I occurs before job K and the other if job K occurs before job I. + + Args: + model (Pyomo.Disjunction): The disjunction of the model. + I (str): job index + K (str): job index that is greater than I (After I) + J (int): stage index + IthenK (bool): True if I occurs before K, False if K occurs before I + + Returns: + None, but creates a disjunction to prevent clashes at a stage. + """ model = disjunct.model() lhs = model.t[I] + sum([M Date: Fri, 19 Jan 2024 14:13:15 -0500 Subject: [PATCH 008/124] Modified Errors --- gdplib/pyomo_examples/jobshop.py | 81 ++++++++++++++++++++++---------- 1 file changed, 57 insertions(+), 24 deletions(-) diff --git a/gdplib/pyomo_examples/jobshop.py b/gdplib/pyomo_examples/jobshop.py index b9815f3..06fb44b 100644 --- a/gdplib/pyomo_examples/jobshop.py +++ b/gdplib/pyomo_examples/jobshop.py @@ -31,36 +31,37 @@ # Aldo Vecchietti, LogMIP User's Manual, http://www.logmip.ceride.gov.ar/, 2007 # + def build_model(): """ Build the jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. A zero wait transfer policy is assumed between stages. - To obtain a feasible solution it is necessary to eliminate all clashes between jobs. + To obtain a feasible solution it is necessary to eliminate all clashes between jobs. It requires that no two jobs be performed at any stage at any time. The objective is to minimize the makespan, the time to complete all jobs. References: Raman & Grossmann, 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.JOBS = Set(ordered=True, doc='Set of jobs') model.STAGES = Set(ordered=True, doc='Set of stages') - model.I_BEFORE_K = RangeSet(0,1) - + model.I_BEFORE_K = RangeSet(0, 1) # Task durations model.tau = Param(model.JOBS, model.STAGES, default=0) # Total Makespan (this will be the objective) model.ms = Var() + # Start time of each job def t_bounds(model, I): """ Calculate the time bounds for the start time of each job in a scheduling model. Args: - model (Pyomo.Abstractmodel): jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. + model (Pyomo.Abstractmodel): jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. A zero wait transfer policy is assumed between stages. I (str): job index @@ -68,7 +69,13 @@ def t_bounds(model, I): tuple: (lower bound, upper bound) for the start time of each job in a scheduling model. """ return (0, sum(value(model.tau[idx]) for idx in model.tau)) - model.t = Var( model.JOBS, within=NonNegativeReals, bounds=t_bounds, doc='Start time of each job') + + model.t = Var( + model.JOBS, + within=NonNegativeReals, + bounds=t_bounds, + doc='Start time of each job', + ) # Auto-generate the L set (potential collisions between 2 jobs at any stage. def _L_filter(model, I, K, J): @@ -83,11 +90,16 @@ def _L_filter(model, I, K, J): J (int): stage index Returns: - bool: True if I < K and the parameters, model.tau[I,J] and model.tau[K,J] + expression: True if I < K and the parameters, model.tau[I,J] and model.tau[K,J] """ - return I < K and model.tau[I,J] and model.tau[K,J] - model.L = Set( initialize=model.JOBS * model.JOBS * model.STAGES, - dimen=3, filter=_L_filter, doc='Set of potential collisions between 2 jobs at any stage') + return I < K and model.tau[I, J] and model.tau[K, J] + + model.L = Set( + initialize=model.JOBS * model.JOBS * model.STAGES, + dimen=3, + filter=_L_filter, + doc='Set of potential collisions between 2 jobs at any stage', + ) # Makespan is greater than the start time of every job + that job's # total duration @@ -100,10 +112,16 @@ def _feas(model, I): I (str): job index Returns: - bool: True if the makespan is greater than the sum of the start time of every job and that job's total duration. + expression: True if the makespan is greater than the sum of the start time of every job and that job's total duration. """ - return model.ms >= model.t[I] + sum(model.tau[I,M] for M in model.STAGES) - model.Feas = Constraint(model.JOBS, rule=_feas, doc='Makespan is greater than the start time of every job + that job''s total duration') + return model.ms >= model.t[I] + sum(model.tau[I, M] for M in model.STAGES) + + model.Feas = Constraint( + model.JOBS, + rule=_feas, + doc='Makespan is greater than the start time of every job + that job' + 's total duration', + ) # Disjunctions to prevent clashes at a stage: This creates a set of # disjunct pairs: one if job I occurs before job K and the other if job @@ -113,7 +131,7 @@ def _NoClash(disjunct, I, K, J, IthenK): Disjunctions to prevent clashes at a stage: This creates a set of disjunct pairs: one if job I occurs before job K and the other if job K occurs before job I. Args: - model (Pyomo.Disjunction): The disjunction of the model. + model (Pyomo.Disjunction): The disjunction of the model. I (str): job index K (str): job index that is greater than I (After I) J (int): stage index @@ -123,13 +141,19 @@ def _NoClash(disjunct, I, K, J, IthenK): None, but creates a disjunction to prevent clashes at a stage. """ model = disjunct.model() - lhs = model.t[I] + sum([M Date: Fri, 19 Jan 2024 14:37:51 -0500 Subject: [PATCH 009/124] jobshop.py black formatted. --- gdplib/pyomo_examples/jobshop.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gdplib/pyomo_examples/jobshop.py b/gdplib/pyomo_examples/jobshop.py index 06fb44b..abc4bf0 100644 --- a/gdplib/pyomo_examples/jobshop.py +++ b/gdplib/pyomo_examples/jobshop.py @@ -26,7 +26,7 @@ # # References: # -# Raman & Grossmann, Computers and Chemical Engineering 18, 7, p.563-578, 1994. +# 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 # @@ -40,7 +40,7 @@ def build_model(): It requires that no two jobs be performed at any stage at any time. The objective is to minimize the makespan, the time to complete all jobs. References: - Raman & Grossmann, Computers and Chemical Engineering 18, 7, p.563-578, 1994. + 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() @@ -61,7 +61,7 @@ def t_bounds(model, I): Calculate the time bounds for the start time of each job in a scheduling model. Args: - model (Pyomo.Abstractmodel): jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. + model (Pyomo.Abstractmodel): job shop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. A zero wait transfer policy is assumed between stages. I (str): job index From 349c85c0fec5fcb4e171837682b1469c5055586f Mon Sep 17 00:00:00 2001 From: Carolina Tristan Date: Fri, 1 Mar 2024 12:03:42 -0500 Subject: [PATCH 010/124] Fix documentation comment in build_model function --- gdplib/pyomo_examples/jobshop.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gdplib/pyomo_examples/jobshop.py b/gdplib/pyomo_examples/jobshop.py index abc4bf0..d3eca97 100644 --- a/gdplib/pyomo_examples/jobshop.py +++ b/gdplib/pyomo_examples/jobshop.py @@ -108,11 +108,11 @@ def _feas(model, I): Args: model (Pyomo.Abstractmodel): jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. - A zero wait transfer policy is assumed between stages. + A zero wait transfer policy is assumed between stages. I (str): job index Returns: - expression: True if the makespan is greater than the sum of the start time of every job and that job's total duration. + bool: True if the makespan is greater than the sum of the start time of every job and that job's total duration. """ return model.ms >= model.t[I] + sum(model.tau[I, M] for M in model.STAGES) From ff9f3761d89349c958fc29f72d3ab6a891214d62 Mon Sep 17 00:00:00 2001 From: Carolina Tristan Date: Fri, 1 Mar 2024 12:16:34 -0500 Subject: [PATCH 011/124] Update return type in build_model function --- gdplib/pyomo_examples/jobshop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gdplib/pyomo_examples/jobshop.py b/gdplib/pyomo_examples/jobshop.py index d3eca97..2d959aa 100644 --- a/gdplib/pyomo_examples/jobshop.py +++ b/gdplib/pyomo_examples/jobshop.py @@ -112,7 +112,7 @@ def _feas(model, I): I (str): job index Returns: - bool: True if the makespan is greater than the sum of the start time of every job and that job's total duration. + expression: True if the makespan is greater than the sum of the start time of every job and that job's total duration. """ return model.ms >= model.t[I] + sum(model.tau[I, M] for M in model.STAGES) From e38e88618bccf97b1ff0f434ff52e5d39e5229c8 Mon Sep 17 00:00:00 2001 From: Carolina Tristan Date: Wed, 27 Mar 2024 14:56:37 -0400 Subject: [PATCH 012/124] Refactor jobshop.py: Update function documentation for _feas() --- gdplib/pyomo_examples/jobshop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gdplib/pyomo_examples/jobshop.py b/gdplib/pyomo_examples/jobshop.py index 2d959aa..02dd84d 100644 --- a/gdplib/pyomo_examples/jobshop.py +++ b/gdplib/pyomo_examples/jobshop.py @@ -104,7 +104,7 @@ def _L_filter(model, I, K, J): # Makespan is greater than the start time of every job + that job's # total duration def _feas(model, I): - """_summary_ + """This function creates a constraint that ensures the makespan is greater than the sum of the start time of every job and that job's total duration. Args: model (Pyomo.Abstractmodel): jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. From 7d8fe60a394150c5706be572f656c0559e5a8446 Mon Sep 17 00:00:00 2001 From: Carolina Tristan Date: Wed, 27 Mar 2024 15:02:22 -0400 Subject: [PATCH 013/124] Add jobshop scheduling model documentation --- gdplib/pyomo_examples/jobshop.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/gdplib/pyomo_examples/jobshop.py b/gdplib/pyomo_examples/jobshop.py index 02dd84d..81682d6 100644 --- a/gdplib/pyomo_examples/jobshop.py +++ b/gdplib/pyomo_examples/jobshop.py @@ -42,6 +42,8 @@ def build_model(): References: 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 + Args: None + Returns: AbstractModel: jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. """ model = AbstractModel() @@ -104,17 +106,17 @@ def _L_filter(model, I, K, J): # Makespan is greater than the start time of every job + that job's # total duration def _feas(model, I): - """This function creates a constraint that ensures the makespan is greater than the sum of the start time of every job and that job's total duration. + """This function creates a constraint that ensures the makespan is greater than the sum of the start time of every job and that job's total duration. - Args: - model (Pyomo.Abstractmodel): jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. - A zero wait transfer policy is assumed between stages. - I (str): job index + Args: + model (Pyomo.Abstractmodel): jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. + A zero wait transfer policy is assumed between stages. + I (str): job index - Returns: - expression: True if the makespan is greater than the sum of the start time of every job and that job's total duration. - """ - return model.ms >= model.t[I] + sum(model.tau[I, M] for M in model.STAGES) + Returns: + expression: True if the makespan is greater than the sum of the start time of every job and that job's total duration. + """ + return model.ms >= model.t[I] + sum(model.tau[I, M] for M in model.STAGES) model.Feas = Constraint( model.JOBS, From 96835207a37fdacbd19f7adb300f141a0a708220 Mon Sep 17 00:00:00 2001 From: Carolina Tristan Date: Wed, 27 Mar 2024 15:18:10 -0400 Subject: [PATCH 014/124] Black formatted --- gdplib/pyomo_examples/jobshop.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/gdplib/pyomo_examples/jobshop.py b/gdplib/pyomo_examples/jobshop.py index 81682d6..8eb09eb 100644 --- a/gdplib/pyomo_examples/jobshop.py +++ b/gdplib/pyomo_examples/jobshop.py @@ -42,8 +42,11 @@ def build_model(): References: 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 + Args: None - Returns: AbstractModel: jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. + + Returns: + AbstractModel: jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. """ model = AbstractModel() @@ -106,17 +109,18 @@ def _L_filter(model, I, K, J): # Makespan is greater than the start time of every job + that job's # total duration def _feas(model, I): - """This function creates a constraint that ensures the makespan is greater than the sum of the start time of every job and that job's total duration. + """ + This function creates a constraint that ensures the makespan is greater than the sum of the start time of every job and that job's total duration. - Args: - model (Pyomo.Abstractmodel): jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. - A zero wait transfer policy is assumed between stages. - I (str): job index + Args: + model (Pyomo.Abstractmodel): jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. + A zero wait transfer policy is assumed between stages. + I (str): job index - Returns: - expression: True if the makespan is greater than the sum of the start time of every job and that job's total duration. - """ - return model.ms >= model.t[I] + sum(model.tau[I, M] for M in model.STAGES) + Returns: + expression: True if the makespan is greater than the sum of the start time of every job and that job's total duration. + """ + return model.ms >= model.t[I] + sum(model.tau[I, M] for M in model.STAGES) model.Feas = Constraint( model.JOBS, From 252ee4e45c9695f29381d201247fca7178434f18 Mon Sep 17 00:00:00 2001 From: Carolina Tristan Date: Mon, 1 Apr 2024 18:54:50 -0400 Subject: [PATCH 015/124] Update copyright year in jobshop.py header --- gdplib/pyomo_examples/jobshop.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gdplib/pyomo_examples/jobshop.py b/gdplib/pyomo_examples/jobshop.py index 8eb09eb..369804a 100644 --- a/gdplib/pyomo_examples/jobshop.py +++ b/gdplib/pyomo_examples/jobshop.py @@ -1,7 +1,8 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC +# Copyright (c) 2008-2024 +# National Technology and Engineering Solutions of Sandia, LLC # Under the terms of Contract DE-NA0003525 with National Technology and # Engineering Solutions of Sandia, LLC, the U.S. Government retains certain # rights in this software. From 064199cb1ce9acb7c5e221845c8fcb71788198a5 Mon Sep 17 00:00:00 2001 From: Carolina Tristan Date: Mon, 1 Apr 2024 19:07:17 -0400 Subject: [PATCH 016/124] Update jobshop.py with reference DOI --- gdplib/pyomo_examples/jobshop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gdplib/pyomo_examples/jobshop.py b/gdplib/pyomo_examples/jobshop.py index 369804a..78b50f8 100644 --- a/gdplib/pyomo_examples/jobshop.py +++ b/gdplib/pyomo_examples/jobshop.py @@ -27,7 +27,7 @@ # # References: # -# Raman & Grossmann, Modelling and computational techniques for logic based integer programming, Computers and Chemical Engineering 18, 7, p.563-578, 1994. +# Raman & Grossmann, Modelling and computational techniques for logic based integer programming, Computers and Chemical Engineering 18, 7, p.563-578, 1994. DOI: 10.1016/0098-1354(93)E0010-7. # # Aldo Vecchietti, LogMIP User's Manual, http://www.logmip.ceride.gov.ar/, 2007 # From 0e15d0b4eb020b5435066d81acd34270321b2c19 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 2 Apr 2024 17:46:23 -0400 Subject: [PATCH 017/124] Basic Documentation for Med Term Purchasing.py --- gdplib/pyomo_examples/med_term_purchasing.py | 886 +++++++++++++++++-- 1 file changed, 824 insertions(+), 62 deletions(-) diff --git a/gdplib/pyomo_examples/med_term_purchasing.py b/gdplib/pyomo_examples/med_term_purchasing.py index d83d8d2..81c2695 100644 --- a/gdplib/pyomo_examples/med_term_purchasing.py +++ b/gdplib/pyomo_examples/med_term_purchasing.py @@ -21,6 +21,26 @@ # most notably the process structure itself and the mass balance information. def build_model(): + """ + Build a Pyomo abstract model for the medium-term purchasing contracts problem. + + Returns + ------- + Pyomo.AbstractModel + Pyomo abstract model for medium-term purchasing contracts problem. + + Raises + ------ + RuntimeError + _description_ + RuntimeError + _description_ + + References + ---------- + [1] Vecchietti, Aldo, and I. Grossmann. "Computational experience with logmip solving linear and nonlinear disjunctive programming problems." In Proc. of FOCAPD, pp. 587-590. 2004. + [2] Park, M., Park, S., Mele, F. D., & Grossmann, I. E. (2006). Modeling of purchase and sales contracts in supply chain optimization. Industrial & Engineering Chemistry Research, 45(14), 5013-5026. DOI: https://doi.org/10.1021/ie0513144 + """ model = AbstractModel() # Constants (data that was hard-coded in GAMS model) @@ -38,33 +58,33 @@ def build_model(): # T # t in GAMS - model.TimePeriods = Set(ordered=True) + model.TimePeriods = Set(ordered=True, doc="Set of time periods") # Available length contracts # p in GAMS - model.Contracts_Length = Set() + model.Contracts_Length = Set(doc="Set of available length contracts") # JP # final(j) in GAMS # Finished products - model.Products = Set() + model.Products = Set(doc="Set of finished products") # JM # rawmat(J) in GAMS # Set of Raw Materials-- raw materials, intermediate products, and final products partition J - model.RawMaterials = Set() + model.RawMaterials = Set(doc="Set of raw materials, intermediate products, and final products") # C # c in GAMS - model.Contracts = Set() + model.Contracts = Set(doc='Set of available contracts') # I # i in GAMS - model.Processes = Set() + model.Processes = Set(doc='Set of processes in the network') # J # j in GAMS - model.Streams = Set() + model.Streams = Set(doc='Set of streams in the network') ################## @@ -73,33 +93,34 @@ def build_model(): # Q_it # excap(i) in GAMS - model.Capacity = Param(model.Processes) + model.Capacity = Param(model.Processes, doc='Capacity of process i') # u_ijt # cov(i) in GAMS - model.ProcessConstants = Param(model.Processes) + model.ProcessConstants = Param(model.Processes, doc='Process constants') # a_jt^U and d_jt^U # spdm(j,t) in GAMS - model.SupplyAndDemandUBs = Param(model.Streams, model.TimePeriods, default=0) + model.SupplyAndDemandUBs = Param(model.Streams, model.TimePeriods, default=0, doc='Supply and demand upper bounds') # d_jt^L # lbdm(j, t) in GAMS - model.DemandLB = Param(model.Streams, model.TimePeriods, default=0) + model.DemandLB = Param(model.Streams, model.TimePeriods, default=0, doc='Demand lower bounds') # delta_it # delta(i, t) in GAMS # operating cost of process i at time t - model.OperatingCosts = Param(model.Processes, model.TimePeriods) + model.OperatingCosts = Param(model.Processes, model.TimePeriods, doc='Operating cost of process i at time t') # prices of raw materials under FP contract and selling prices of products # pf(j, t) in GAMS # omega_jt and pf_jt - model.Prices = Param(model.Streams, model.TimePeriods, default=0) + model.Prices = Param(model.Streams, model.TimePeriods, default=0, doc='Prices of raw materials under FP contract and selling prices of products') # Price for quantities less than min amount under discount contract # pd1(j, t) in GAMS model.RegPrice_Discount = Param(model.Streams, model.TimePeriods) + # Discounted price for the quantity purchased exceeding the min amount # pd2(j,t0 in GAMS model.DiscountPrice_Discount = Param(model.Streams, model.TimePeriods) @@ -107,40 +128,41 @@ def build_model(): # Price for quantities below min amount # pb1(j,t) in GAMS model.RegPrice_Bulk = Param(model.Streams, model.TimePeriods) - # Price for quantities aboce min amount + + # Price for quantities above min amount # pb2(j, t) in GAMS - model.DiscountPrice_Bulk = Param(model.Streams, model.TimePeriods) + model.DiscountPrice_Bulk = Param(model.Streams, model.TimePeriods, doc='Price for quantities above minimum amount under bulk contract') # prices with length contract # pl(j, p, t) in GAMS - model.Prices_Length = Param(model.Streams, model.Contracts_Length, model.TimePeriods, default=0) + model.Prices_Length = Param(model.Streams, model.Contracts_Length, model.TimePeriods, default=0, doc='Prices with length contract') # sigmad_jt # sigmad(j, t) in GAMS # Minimum quantity of chemical j that must be bought before recieving a Discount under discount contract - model.MinAmount_Discount = Param(model.Streams, model.TimePeriods, default=0) + model.MinAmount_Discount = Param(model.Streams, model.TimePeriods, default=0, 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 # sigmab(j, t) in GAMS - model.MinAmount_Bulk = Param(model.Streams, model.TimePeriods, default=0) + model.MinAmount_Bulk = Param(model.Streams, model.TimePeriods, default=0, 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 # sigmal(j, p) in GAMS - model.MinAmount_Length = Param(model.Streams, model.Contracts_Length, default=0) + model.MinAmount_Length = Param(model.Streams, model.Contracts_Length, default=0, doc='Minimum quantity of chemical j that must be bought before receiving a Discount under length contract') # main products of process i # These are 1 (true) if stream j is the main product of process i, false otherwise. # jm(j, i) in GAMS - model.MainProducts = Param(model.Streams, model.Processes, default=0) + model.MainProducts = Param(model.Streams, model.Processes, default=0, doc='Main products of process i') # theta_jt # psf(j, t) in GAMS # Shortfall penalty of product j at time t - model.ShortfallPenalty = Param(model.Products, model.TimePeriods) + model.ShortfallPenalty = Param(model.Products, model.TimePeriods, doc='Shortfall penalty of product j at time t') # shortfall upper bound # sfub(j, t) in GAMS - model.ShortfallUB = Param(model.Products, model.TimePeriods, default=0) + model.ShortfallUB = Param(model.Products, model.TimePeriods, default=0, doc='Shortfall upper bound') # epsilon_jt # cinv(j, t) in GAMS @@ -156,9 +178,41 @@ def build_model(): # All of these upper bounds are hardcoded. So I am just leaving them that way. # This means they all have to be the same as each other right now. def getAmountUBs(model, j, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + Pyomo abstract model for medium-term purchasing contracts problem. + j : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return AMOUNT_UB def getCostUBs(model, j, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + Pyomo abstract model for medium-term purchasing contracts problem. + j : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return COST_UB model.AmountPurchasedUB_FP = Param(model.Streams, model.TimePeriods, @@ -186,16 +240,33 @@ def getCostUBs(model, j, t): # prof in GAMS # will be objective - model.Profit = Var() + model.Profit = Var(doc='Profit') # f(j, t) in GAMS # mass flow rates in tons per time interval t - model.FlowRate = Var(model.Streams, model.TimePeriods, within=NonNegativeReals) + model.FlowRate = Var(model.Streams, model.TimePeriods, within=NonNegativeReals, doc='Mass flow rates in tons per time interval t') # V_jt # inv(j, t) in GAMS # inventory level of chemical j at time period t def getInventoryBounds(model, i, j): + """ + Inventory level of chemical j at time period t + + Parameters + ---------- + model : Pyomo.AbstractModel + Pyomo abstract model for medium-term purchasing contracts problem. + i : _type_ + _description_ + j : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return (0, model.InventoryLevelUB[i,j]) model.InventoryLevel = Var(model.Streams, model.TimePeriods, bounds=getInventoryBounds) @@ -204,6 +275,17 @@ def getInventoryBounds(model, i, j): # sf(j, t) in GAMS # Shortfall of demand for chemical j at time period t def getShortfallBounds(model, i, j): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + _description_ + i : _type_ + _description_ + j : _type_ + _description_ + """ return(0, model.ShortfallUB[i,j]) model.Shortfall = Var(model.Products, model.TimePeriods, bounds=getShortfallBounds) @@ -214,12 +296,44 @@ def getShortfallBounds(model, i, j): # spf(j, t) in GAMS # Amount of raw material j bought under fixed price contract at time period t def get_FP_bounds(model, j, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + Pyomo abstract model for medium-term purchasing contracts problem. + j : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return (0, model.AmountPurchasedUB_FP[j,t]) model.AmountPurchased_FP = Var(model.Streams, model.TimePeriods, bounds=get_FP_bounds) # spd(j, t) in GAMS def get_Discount_Total_bounds(model, j, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + Pyomo abstract model for medium-term purchasing contracts problem. + j : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return (0, model.AmountPurchasedUB_Discount[j,t]) model.AmountPurchasedTotal_Discount = Var(model.Streams, model.TimePeriods, bounds=get_Discount_Total_bounds) @@ -227,6 +341,22 @@ def get_Discount_Total_bounds(model, j, t): # Amount purchased below min amount for discount under discount contract # spd1(j, t) in GAMS def get_Discount_BelowMin_bounds(model, j, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + Pyomo abstract model for medium-term purchasing contracts problem. + j : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return (0, model.AmountPurchasedBelowMinUB_Discount[j,t]) model.AmountPurchasedBelowMin_Discount = Var(model.Streams, model.TimePeriods, bounds=get_Discount_BelowMin_bounds) @@ -234,6 +364,22 @@ def get_Discount_BelowMin_bounds(model, j, t): # spd2(j, t) in GAMS # Amount purchased above min amount for discount under discount contract def get_Discount_AboveMin_bounds(model, j, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + Pyomo abstract model for medium-term purchasing contracts problem. + j : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return (0, model.AmountPurchasedBelowMinUB_Discount[j,t]) model.AmountPurchasedAboveMin_Discount = Var(model.Streams, model.TimePeriods, bounds=get_Discount_AboveMin_bounds) @@ -241,6 +387,22 @@ def get_Discount_AboveMin_bounds(model, j, t): # Amount purchased under bulk contract # spb(j, t) in GAMS def get_bulk_bounds(model, j, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + Pyomo abstract model for medium-term purchasing contracts problem. + j : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return (0, model.AmountPurchasedUB_Bulk[j,t]) model.AmountPurchased_Bulk = Var(model.Streams, model.TimePeriods, bounds=get_bulk_bounds) @@ -248,6 +410,22 @@ def get_bulk_bounds(model, j, t): # spl(j, t) in GAMS # Amount purchased under Fixed Duration contract def get_FD_bounds(model, j, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + Pyomo abstract model for medium-term purchasing contracts problem. + j : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return (0, model.AmountPurchasedUB_FD[j,t]) model.AmountPurchased_FD = Var(model.Streams, model.TimePeriods, bounds=get_FD_bounds) @@ -258,18 +436,66 @@ def get_FD_bounds(model, j, t): # costpl(j, t) in GAMS # cost of variable length contract def get_CostUBs_FD(model, j, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + _description_ + j : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return (0, model.CostUB_FD[j,t]) model.Cost_FD = Var(model.Streams, model.TimePeriods, bounds=get_CostUBs_FD) # costpf(j, t) in GAMS # cost of fixed duration contract def get_CostUBs_FP(model, j, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + Pyomo abstract model for medium-term purchasing contracts problem. + j : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return (0, model.CostUB_FP[j,t]) model.Cost_FP = Var(model.Streams, model.TimePeriods, bounds=get_CostUBs_FP) # costpd(j, t) in GAMS # cost of discount contract def get_CostUBs_Discount(model, j, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + Pyomo abstract model for medium-term purchasing contracts problem. + j : _type_ + _description_ + t : Index + _description_ + + Returns + ------- + _type_ + _description_ + """ return (0, model.CostUB_Discount[j,t]) model.Cost_Discount = Var(model.Streams, model.TimePeriods, bounds=get_CostUBs_Discount) @@ -277,6 +503,22 @@ def get_CostUBs_Discount(model, j, t): # costpb(j, t) in GAMS # cost of bulk contract def get_CostUBs_Bulk(model, j, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + Pyomo abstract model for medium-term purchasing contracts problem. + j : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return (0, model.CostUB_Bulk[j,t]) model.Cost_Bulk = Var(model.Streams, model.TimePeriods, bounds=get_CostUBs_Bulk) @@ -295,6 +537,19 @@ def get_CostUBs_Bulk(model, j, t): # Objective: maximize profit def profit_rule(model): + """ + Objective function: maximize profit + + Parameters + ---------- + model : Pyomo.AbstractModel + Pyomo abstract model for medium-term purchasing contracts problem. + + Returns + ------- + _type_ + _description_ + """ salesIncome = sum(model.Prices[j,t] * model.FlowRate[j,t] for j in model.Products for t in model.TimePeriods) purchaseCost = sum(model.Cost_FD[j,t] @@ -317,6 +572,22 @@ def profit_rule(model): # flow of raw materials is the total amount purchased (accross all contracts) def raw_material_flow_rule(model, j, t): + """_summary_ + + Parameters + ---------- + model : _type_ + _description_ + j : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return model.FlowRate[j,t] == model.AmountPurchased_FD[j,t] + \ model.AmountPurchased_FP[j,t] + model.AmountPurchased_Bulk[j,t] + \ model.AmountPurchasedTotal_Discount[j,t] @@ -324,6 +595,22 @@ def raw_material_flow_rule(model, j, t): rule=raw_material_flow_rule) def discount_amount_total_rule(model, j, t): + """_summary_ + + Parameters + ---------- + model : _type_ + _description_ + j : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return model.AmountPurchasedTotal_Discount[j,t] == \ model.AmountPurchasedBelowMin_Discount[j,t] + \ model.AmountPurchasedAboveMin_Discount[j,t] @@ -333,10 +620,38 @@ def discount_amount_total_rule(model, j, t): # mass balance equations for each node # these are specific to the process network in this example. def mass_balance_rule1(model, t): + """_summary_ + + Parameters + ---------- + model : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return model.FlowRate[1, t] == model.FlowRate[2, t] + model.FlowRate[3, t] model.mass_balance1 = Constraint(model.TimePeriods, rule=mass_balance_rule1) def mass_balance_rule2(model, t): + """_summary_ + + Parameters + ---------- + model : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return model.FlowRate[5, t] == model.FlowRate[4, t] + model.FlowRate[8,t] model.mass_balance2 = Constraint(model.TimePeriods, rule=mass_balance_rule2) @@ -345,26 +660,96 @@ def mass_balance_rule3(model, t): model.mass_balance3 = Constraint(model.TimePeriods, rule=mass_balance_rule3) def mass_balance_rule4(model, t): + """_summary_ + + Parameters + ---------- + model : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return model.FlowRate[3, t] == 10*model.FlowRate[5, t] model.mass_balance4 = Constraint(model.TimePeriods, rule=mass_balance_rule4) # process input/output constraints # these are also totally specific to the process network def process_balance_rule1(model, t): + """_summary_ + + Parameters + ---------- + model : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return model.FlowRate[9, t] == model.ProcessConstants[1] * model.FlowRate[2, t] model.process_balance1 = Constraint(model.TimePeriods, rule=process_balance_rule1) def process_balance_rule2(model, t): + """_summary_ + + Parameters + ---------- + model : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return model.FlowRate[10, t] == model.ProcessConstants[2] * \ (model.FlowRate[5, t] + model.FlowRate[3, t]) model.process_balance2 = Constraint(model.TimePeriods, rule=process_balance_rule2) def process_balance_rule3(model, t): + """_summary_ + + Parameters + ---------- + model : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return model.FlowRate[8, t] == RandomConst_Line264 * \ model.ProcessConstants[3] * model.FlowRate[7, t] model.process_balance3 = Constraint(model.TimePeriods, rule=process_balance_rule3) def process_balance_rule4(model, t): + """_summary_ + + Parameters + ---------- + model : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return model.FlowRate[11, t] == RandomConst_Line265 * \ model.ProcessConstants[3] * model.FlowRate[7, t] model.process_balance4 = Constraint(model.TimePeriods, rule=process_balance_rule4) @@ -372,14 +757,56 @@ def process_balance_rule4(model, t): # process capacity contraints # these are hardcoded based on the three processes and the process flow structure def process_capacity_rule1(model, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return model.FlowRate[9, t] <= model.Capacity[1] model.process_capacity1 = Constraint(model.TimePeriods, rule=process_capacity_rule1) def process_capacity_rule2(model, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return model.FlowRate[10, t] <= model.Capacity[2] model.process_capacity2 = Constraint(model.TimePeriods, rule=process_capacity_rule2) def process_capacity_rule3(model, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return model.FlowRate[11, t] + model.FlowRate[8, t] <= model.Capacity[3] model.process_capacity3 = Constraint(model.TimePeriods, rule=process_capacity_rule3) @@ -387,11 +814,39 @@ def process_capacity_rule3(model, t): # again, these are hardcoded. def inventory_balance1(model, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ prev = 0 if t == min(model.TimePeriods) else model.InventoryLevel[12, t-1] return prev + model.FlowRate[9, t] == model.FlowRate[12, t] + model.InventoryLevel[12,t] model.inventory_balance1 = Constraint(model.TimePeriods, rule=inventory_balance1) def inventory_balance_rule2(model, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ if t != 1: return Constraint.Skip return model.FlowRate[10, t] + model.FlowRate[11, t] == \ @@ -399,6 +854,20 @@ def inventory_balance_rule2(model, t): model.inventory_balance2 = Constraint(model.TimePeriods, rule=inventory_balance_rule2) def inventory_balance_rule3(model, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ if t <= 1: return Constraint.Skip return model.InventoryLevel[13, t-1] + model.FlowRate[10, t] + \ @@ -407,30 +876,126 @@ def inventory_balance_rule3(model, t): # Max capacities of inventories def inventory_capacity_rule(model, j, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + _description_ + j : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return model.InventoryLevel[j,t] <= model.InventoryLevelUB[j,t] model.inventory_capacity_rule = Constraint(model.Products, model.TimePeriods, rule=inventory_capacity_rule) # Shortfall calculation def shortfall_rule(model, j, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + _description_ + j : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return model.Shortfall[j, t] == model.SupplyAndDemandUBs[j, t] - model.FlowRate[j,t] model.shortfall = Constraint(model.Products, model.TimePeriods, rule=shortfall_rule) # maximum shortfall allowed def shortfall_max_rule(model, j, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + _description_ + j : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return model.Shortfall[j, t] <= model.ShortfallUB[j, t] model.shortfall_max = Constraint(model.Products, model.TimePeriods, rule=shortfall_max_rule) # maxiumum capacities of suppliers def supplier_capacity_rule(model, j, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + _description_ + j : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return model.FlowRate[j, t] <= model.SupplyAndDemandUBs[j, t] model.supplier_capacity = Constraint(model.RawMaterials, model.TimePeriods, rule=supplier_capacity_rule) # demand upper bound def demand_UB_rule(model, j, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + _description_ + j : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return model.FlowRate[j, t] <= model.SupplyAndDemandUBs[j,t] model.demand_UB = Constraint(model.Products, model.TimePeriods, rule=demand_UB_rule) # demand lower bound def demand_LB_rule(model, j, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + _description_ + j : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return model.FlowRate[j, t] >= model.DemandLB[j,t] model.demand_LB = Constraint(model.Products, model.TimePeriods, rule=demand_LB_rule) @@ -439,6 +1004,19 @@ def demand_LB_rule(model, j, t): # Disjunction for Fixed Price contract buying options def FP_contract_disjunct_rule(disjunct, j, t, buy): + """_summary_ + + Parameters + ---------- + disjunct : Index + _description_ + j : _type_ + _description_ + t : Index + Time period. + buy : _type_ + _description_ + """ model = disjunct.model() if buy: disjunct.c = Constraint(expr=model.AmountPurchased_FP[j,t] <= MAX_AMOUNT_FP) @@ -449,12 +1027,44 @@ def FP_contract_disjunct_rule(disjunct, j, t, buy): # Fixed price disjunction def FP_contract_rule(model, j, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + _description_ + j : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return [model.FP_contract_disjunct[j,t,buy] for buy in model.BuyFPContract] model.FP_disjunction = Disjunction(model.RawMaterials, model.TimePeriods, rule=FP_contract_rule) - # cost constraint for fixed price contract (independent contraint) + # cost constraint for fixed price contract (independent constraint) def FP_contract_cost_rule(model, j, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + _description_ + j : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return model.Cost_FP[j,t] == model.AmountPurchased_FP[j,t] * \ model.Prices[j,t] model.FP_contract_cost = Constraint(model.RawMaterials, model.TimePeriods, @@ -465,6 +1075,24 @@ def FP_contract_cost_rule(model, j, t): # Disjunction for Discount contract def discount_contract_disjunct_rule(disjunct, j, t, buy): + """_summary_ + + Parameters + ---------- + disjunct : Index + _description_ + j : _type_ + _description_ + t : Index + _description_ + buy : _type_ + _description_ + + Raises + ------ + RuntimeError + _description_ + """ model = disjunct.model() if buy == 'BelowMin': disjunct.belowMin = Constraint( @@ -490,6 +1118,22 @@ def discount_contract_disjunct_rule(disjunct, j, t, buy): # Discount contract disjunction def discount_contract_rule(model, j, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + _description_ + j : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return [model.discount_contract_disjunct[j,t,buy] \ for buy in model.BuyDiscountContract] model.discount_contract = Disjunction(model.RawMaterials, model.TimePeriods, @@ -497,6 +1141,22 @@ def discount_contract_rule(model, j, t): # cost constraint for discount contract (independent constraint) def discount_cost_rule(model, j, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + _description_ + j : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ return model.Cost_Discount[j,t] == model.RegPrice_Discount[j,t] * \ model.AmountPurchasedBelowMin_Discount[j,t] + \ model.DiscountPrice_Discount[j,t] * model.AmountPurchasedAboveMin_Discount[j,t] @@ -508,6 +1168,24 @@ def discount_cost_rule(model, j, t): # Bulk contract buying options disjunct def bulk_contract_disjunct_rule(disjunct, j, t, buy): + """_summary_ + + Parameters + ---------- + disjunct : Index + _description_ + j : _type_ + _description_ + t : Index + Time period. + buy : _type_ + _description_ + + Raises + ------ + RuntimeError + _description_ + """ model = disjunct.model() if buy == 'BelowMin': disjunct.amount = Constraint( @@ -531,6 +1209,22 @@ def bulk_contract_disjunct_rule(disjunct, j, t, buy): # Bulk contract disjunction def bulk_contract_rule(model, j, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + _description_ + j : _type_ + _description_ + t : Index + _description_ + + Returns + ------- + _type_ + _description_ + """ return [model.bulk_contract_disjunct[j,t,buy] for buy in model.BuyBulkContract] model.bulk_contract = Disjunction(model.RawMaterials, model.TimePeriods, rule=bulk_contract_rule) @@ -539,53 +1233,97 @@ def bulk_contract_rule(model, j, t): # FIXED DURATION CONTRACT def FD_1mo_contract(disjunct, j, t): - model = disjunct.model() - disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] >= \ - MIN_AMOUNT_FD_1MONTH) - disjunct.price1 = Constraint(expr=model.Cost_FD[j,t] == \ - model.Prices_Length[j,1,t] * model.AmountPurchased_FD[j,t]) + """_summary_ + + Parameters + ---------- + disjunct : Index + _description_ + j : _type_ + _description_ + t : Index + Time period. + """ + model = disjunct.model() + disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] >= \ + MIN_AMOUNT_FD_1MONTH) + disjunct.price1 = Constraint(expr=model.Cost_FD[j,t] == \ + model.Prices_Length[j,1,t] * model.AmountPurchased_FD[j,t]) model.FD_1mo_contract = Disjunct( model.RawMaterials, model.TimePeriods, rule=FD_1mo_contract) def FD_2mo_contract(disjunct, j, t): - model = disjunct.model() - disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] >= \ - model.MinAmount_Length[j,2]) - disjunct.price1 = Constraint(expr=model.Cost_FD[j,t] == \ - model.Prices_Length[j,2,t] * model.AmountPurchased_FD[j,t]) + """_summary_ + + Parameters + ---------- + disjunct : Index + _description_ + j : _type_ + _description_ + t : Index + Time period. + """ + model = disjunct.model() + disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] >= \ + model.MinAmount_Length[j,2]) + disjunct.price1 = Constraint(expr=model.Cost_FD[j,t] == \ + model.Prices_Length[j,2,t] * model.AmountPurchased_FD[j,t]) # only enforce these if we aren't in the last time period - if t < model.TimePeriods[-1]: - disjunct.amount2 = Constraint(expr=model.AmountPurchased_FD[j, t+1] >= \ - model.MinAmount_Length[j,2]) - disjunct.price2 = Constraint(expr=model.Cost_FD[j,t+1] == \ - model.Prices_Length[j,2,t] * model.AmountPurchased_FD[j, t+1]) + if t < model.TimePeriods[-1]: + disjunct.amount2 = Constraint(expr=model.AmountPurchased_FD[j, t+1] >= \ + model.MinAmount_Length[j,2]) + disjunct.price2 = Constraint(expr=model.Cost_FD[j,t+1] == \ + model.Prices_Length[j,2,t] * model.AmountPurchased_FD[j, t+1]) model.FD_2mo_contract = Disjunct( model.RawMaterials, model.TimePeriods, rule=FD_2mo_contract) def FD_3mo_contract(disjunct, j, t): - model = disjunct.model() - # NOTE: I think there is a mistake in the GAMS file in line 327. - # they use the bulk minamount rather than the length one. - #I am doing the same here for validation purposes. - disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] >= \ - model.MinAmount_Bulk[j,3]) - disjunct.cost1 = Constraint(expr=model.Cost_FD[j,t] == \ - model.Prices_Length[j,3,t] * model.AmountPurchased_FD[j,t]) - # check we aren't in one of the last two time periods - if t < model.TimePeriods[-1]: - disjunct.amount2 = Constraint(expr=model.AmountPurchased_FD[j,t+1] >= \ - model.MinAmount_Length[j,3]) - disjunct.cost2 = Constraint(expr=model.Cost_FD[j,t+1] == \ - model.Prices_Length[j,3,t] * model.AmountPurchased_FD[j,t+1]) - if t < model.TimePeriods[-2]: - disjunct.amount3 = Constraint(expr=model.AmountPurchased_FD[j,t+2] >= \ - model.MinAmount_Length[j,3]) - disjunct.cost3 = Constraint(expr=model.Cost_FD[j,t+2] == \ - model.Prices_Length[j,3,t] * model.AmountPurchased_FD[j,t+2]) + """_summary_ + + Parameters + ---------- + disjunct : Index + _description_ + j : _type_ + _description_ + t : Index + Time period. + """ + model = disjunct.model() + # NOTE: I think there is a mistake in the GAMS file in line 327. + # they use the bulk minamount rather than the length one. + #I am doing the same here for validation purposes. + disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] >= \ + model.MinAmount_Bulk[j,3]) + disjunct.cost1 = Constraint(expr=model.Cost_FD[j,t] == \ + model.Prices_Length[j,3,t] * model.AmountPurchased_FD[j,t]) + # check we aren't in one of the last two time periods + if t < model.TimePeriods[-1]: + disjunct.amount2 = Constraint(expr=model.AmountPurchased_FD[j,t+1] >= \ + model.MinAmount_Length[j,3]) + disjunct.cost2 = Constraint(expr=model.Cost_FD[j,t+1] == \ + model.Prices_Length[j,3,t] * model.AmountPurchased_FD[j,t+1]) + if t < model.TimePeriods[-2]: + disjunct.amount3 = Constraint(expr=model.AmountPurchased_FD[j,t+2] >= \ + model.MinAmount_Length[j,3]) + disjunct.cost3 = Constraint(expr=model.Cost_FD[j,t+2] == \ + model.Prices_Length[j,3,t] * model.AmountPurchased_FD[j,t+2]) model.FD_3mo_contract = Disjunct( - model.RawMaterials, model.TimePeriods, rule=FD_3mo_contract) + model.RawMaterials, model.TimePeriods, rule=FD_3mo_contract) def FD_no_contract(disjunct, j, t): + """_summary_ + + Parameters + ---------- + disjunct : Index + _description_ + j : _type_ + _description_ + t : Index + Time period. + """ model = disjunct.model() disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] == 0) disjunct.cost1 = Constraint(expr=model.Cost_FD[j,t] == 0) @@ -596,10 +1334,26 @@ def FD_no_contract(disjunct, j, t): disjunct.amount3 = Constraint(expr=model.AmountPurchased_FD[j,t+2] == 0) disjunct.cost3 = Constraint(expr=model.Cost_FD[j,t+2] == 0) model.FD_no_contract = Disjunct( - model.RawMaterials, model.TimePeriods, rule=FD_no_contract) + model.RawMaterials, model.TimePeriods, rule=FD_no_contract) def FD_contract(model, j, t): - return [ model.FD_1mo_contract[j,t], model.FD_2mo_contract[j,t], + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + _description_ + j : _type_ + _description_ + t : Index + Time period. + + Returns + ------- + _type_ + _description_ + """ + return [ model.FD_1mo_contract[j,t], model.FD_2mo_contract[j,t], model.FD_3mo_contract[j,t], model.FD_no_contract[j,t], ] model.FD_contract = Disjunction(model.RawMaterials, model.TimePeriods, rule=FD_contract) @@ -608,6 +1362,14 @@ def FD_contract(model, j, t): 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')) From cec65356b983a3d3c5094dc551cf82c6957c2573 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 4 Apr 2024 13:03:23 -0400 Subject: [PATCH 018/124] Redocumentation of the jobshop into Numpy Format --- gdplib/pyomo_examples/jobshop.py | 153 ++++++++++++++++++++----------- 1 file changed, 98 insertions(+), 55 deletions(-) diff --git a/gdplib/pyomo_examples/jobshop.py b/gdplib/pyomo_examples/jobshop.py index 78b50f8..d85268d 100644 --- a/gdplib/pyomo_examples/jobshop.py +++ b/gdplib/pyomo_examples/jobshop.py @@ -35,19 +35,26 @@ def build_model(): """ - Build the jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. - A zero wait transfer policy is assumed between stages. - To obtain a feasible solution it is necessary to eliminate all clashes between jobs. - It requires that no two jobs be performed at any stage at any time. The objective is to minimize the makespan, the time to complete all jobs. - - References: - 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 + Build and return a jobshop scheduling model. + + This function constructs a Pyomo abstract model for jobshop scheduling, aiming to minimize the makespan. + It includes sets of jobs and stages, with the assumption of a zero-wait policy between stages. + The model enforces constraints to avoid job clashes at any stage and minimizes the total completion time. + - Args: None + Parameters + ---------- + None - Returns: - AbstractModel: jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. + Returns + ------- + model : Pyomo.AbstractModel + The jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. + + References + ---------- + 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() @@ -66,13 +73,18 @@ def t_bounds(model, I): """ Calculate the time bounds for the start time of each job in a scheduling model. - Args: - model (Pyomo.Abstractmodel): job shop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. - A zero wait transfer policy is assumed between stages. - I (str): job index + Parameters + ---------- + model : Pyomo.Abstractmodel + The job shop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. + A zero wait transfer policy is assumed between stages. + I : str + The index of the job index - Returns: - tuple: (lower bound, upper bound) for the start time of each job in a scheduling model. + Returns + ------- + tuple + A tuple containing the lower and upper bounds for the start time of the job. """ return (0, sum(value(model.tau[idx]) for idx in model.tau)) @@ -86,17 +98,25 @@ def t_bounds(model, I): # Auto-generate the L set (potential collisions between 2 jobs at any stage. def _L_filter(model, I, K, J): """ - Filter for the L set (potential collisions between 2 jobs at any stage. - - Args: - model (Pyomo.Abstractmodel): jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. - A zero wait transfer policy is assumed between stages. - I (str): job index - K (str): job index that is greater than I (After I) - J (int): stage index + Filter for the L set (potential collisions between 2 jobs at any stage). - Returns: - expression: True if I < K and the parameters, model.tau[I,J] and model.tau[K,J] + Parameters + ---------- + model : Pyomo.Abstractmodel + The jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. + A zero wait transfer policy is assumed between stages. + I : str + job index + K : str + job index that is greater than I (After I) + J : int + stage index + + Returns + ------- + bool + Returns `True` if job `I` precedes job `K` and both jobs require processing at stage `J`, indicating a potential scheduling clash. + 'False' otherwise. """ return I < K and model.tau[I, J] and model.tau[K, J] @@ -113,13 +133,18 @@ def _feas(model, I): """ This function creates a constraint that ensures the makespan is greater than the sum of the start time of every job and that job's total duration. - Args: - model (Pyomo.Abstractmodel): jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. + Parameters + ---------- + model : Pyomo.Abstractmodel + The jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. A zero wait transfer policy is assumed between stages. - I (str): job index + I : str + job index - Returns: - expression: True if the makespan is greater than the sum of the start time of every job and that job's total duration. + Returns + ------- + Pyomo.Constraint.Expression + A constraint expression that ensures the makespan is greater than or equal to the sum of the start time and total duration for the job. """ return model.ms >= model.t[I] + sum(model.tau[I, M] for M in model.STAGES) @@ -137,15 +162,23 @@ def _NoClash(disjunct, I, K, J, IthenK): """ Disjunctions to prevent clashes at a stage: This creates a set of disjunct pairs: one if job I occurs before job K and the other if job K occurs before job I. - Args: - model (Pyomo.Disjunction): The disjunction of the model. - I (str): job index - K (str): job index that is greater than I (After I) - J (int): stage index - IthenK (bool): True if I occurs before K, False if K occurs before I - - Returns: - None, but creates a disjunction to prevent clashes at a stage. + Parameters + ---------- + model : Pyomo.Disjunct + The disjunction of the model. + I : str + job index + K : str + job index that is greater than I (After I) + J : int + stage index + IthenK : bool + A boolean flag indicating if job I is scheduled before job K (`True`) or vice versa (`False`). + + Returns + ------- + None + However, a constraint is added to the disjunction to prevent clashes at a stage. """ model = disjunct.model() lhs = model.t[I] + sum([M < J and model.tau[I, M] or 0 for M in model.STAGES]) @@ -167,14 +200,21 @@ def _disj(model, I, K, J): """ Define the disjunctions: either job I occurs before K or K before I - Args: - model (Pyomo.Abstractmodel): jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. - I (str): job index - K (str): job index that is greater than I (After I) - J (int): stage index - - Returns: - list: list of disjunctions to prevent clashes at a stage. + Parameters + ---------- + model : Pyomo.Abstractmodel + jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. + I : str + job index + K : str + job index that is greater than I (After I) + J : int + stage index + + Returns + ------- + list of Pyomo.Disjunct + A list of disjunctions for the given jobs and stage, enforcing that one job must precede the other to avoid clashes. """ return [model.NoClash[I, K, J, IthenK] for IthenK in model.I_BEFORE_K] @@ -194,14 +234,17 @@ def _disj(model, I, K, J): def build_small_concrete(): """ Build a small jobshop scheduling model for testing purposes. - The AbstractModel is instantiated with the data in the file jobshop-small.dat turning it into a ConcreteModel. + The AbstractModel is instantiated with the data from 'jobshop-small.dat' turning it into a ConcreteModel. - Args: - None, but the data file jobshop-small.dat must be in the same directory as this file. + Parameters + ---------- + None + However the data file jobshop-small.dat must be in the same directory as this file. - Returns: - ConcreteModel: jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. - A zero wait transfer policy is assumed between stages. + 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')) From 58228c6e46fdafc157f4a492ee1e1892bddabb94 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 4 Apr 2024 13:04:48 -0400 Subject: [PATCH 019/124] Black Formatting. --- gdplib/pyomo_examples/jobshop.py | 36 ++++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/gdplib/pyomo_examples/jobshop.py b/gdplib/pyomo_examples/jobshop.py index d85268d..25ef303 100644 --- a/gdplib/pyomo_examples/jobshop.py +++ b/gdplib/pyomo_examples/jobshop.py @@ -37,15 +37,15 @@ def build_model(): """ Build and return a jobshop scheduling model. - This function constructs a Pyomo abstract model for jobshop scheduling, aiming to minimize the makespan. - It includes sets of jobs and stages, with the assumption of a zero-wait policy between stages. + This function constructs a Pyomo abstract model for jobshop scheduling, aiming to minimize the makespan. + It includes sets of jobs and stages, with the assumption of a zero-wait policy between stages. The model enforces constraints to avoid job clashes at any stage and minimizes the total completion time. - + Parameters ---------- None - + Returns ------- model : Pyomo.AbstractModel @@ -75,7 +75,7 @@ def t_bounds(model, I): Parameters ---------- - model : Pyomo.Abstractmodel + model : Pyomo.Abstractmodel The job shop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. A zero wait transfer policy is assumed between stages. I : str @@ -102,12 +102,12 @@ def _L_filter(model, I, K, J): Parameters ---------- - model : Pyomo.Abstractmodel + model : Pyomo.Abstractmodel The jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. A zero wait transfer policy is assumed between stages. I : str job index - K : str + K : str job index that is greater than I (After I) J : int stage index @@ -115,7 +115,7 @@ def _L_filter(model, I, K, J): Returns ------- bool - Returns `True` if job `I` precedes job `K` and both jobs require processing at stage `J`, indicating a potential scheduling clash. + Returns `True` if job `I` precedes job `K` and both jobs require processing at stage `J`, indicating a potential scheduling clash. 'False' otherwise. """ return I < K and model.tau[I, J] and model.tau[K, J] @@ -138,7 +138,7 @@ def _feas(model, I): model : Pyomo.Abstractmodel The jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. A zero wait transfer policy is assumed between stages. - I : str + I : str job index Returns @@ -164,11 +164,11 @@ def _NoClash(disjunct, I, K, J, IthenK): Parameters ---------- - model : Pyomo.Disjunct + model : Pyomo.Disjunct The disjunction of the model. - I : str + I : str job index - K : str + K : str job index that is greater than I (After I) J : int stage index @@ -202,18 +202,18 @@ def _disj(model, I, K, J): Parameters ---------- - model : Pyomo.Abstractmodel + model : Pyomo.Abstractmodel jobshop scheduling model, which has a set of jobs which must be processed in sequence of stages but not all jobs require all stages. - I : str + I : str job index - K : str + K : str job index that is greater than I (After I) - J : int + J : int stage index Returns ------- - list of Pyomo.Disjunct + list of Pyomo.Disjunct A list of disjunctions for the given jobs and stage, enforcing that one job must precede the other to avoid clashes. """ return [model.NoClash[I, K, J, IthenK] for IthenK in model.I_BEFORE_K] @@ -243,7 +243,7 @@ def build_small_concrete(): Returns ------- - ConcreteModel : Pyomo.ConcreteModel + 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')) From e384ef014ca700a14a90a4a281c0192328214531 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 4 Apr 2024 18:47:06 -0400 Subject: [PATCH 020/124] Added docstring on the function definition. --- gdplib/pyomo_examples/batch_processing.py | 278 +++++++++++++++++++++- 1 file changed, 277 insertions(+), 1 deletion(-) diff --git a/gdplib/pyomo_examples/batch_processing.py b/gdplib/pyomo_examples/batch_processing.py index 00daaea..c6f660b 100644 --- a/gdplib/pyomo_examples/batch_processing.py +++ b/gdplib/pyomo_examples/batch_processing.py @@ -24,7 +24,13 @@ def build_model(): + """_summary_ + Returns + ------- + _type_ + _description_ + """ model = AbstractModel() # TODO: it looks like they set a bigM for each j. Which I need to look up how to do... @@ -33,7 +39,7 @@ def build_model(): # Constants from GAMS - StorageTankSizeFactor = 2*5 # btw, I know 2*5 is 10... I don't know why it's written this way in GAMS? + StorageTankSizeFactor = 10 StorageTankSizeFactorByProd = 3 MinFlow = -log(10000) VolumeLB = log(300) @@ -53,6 +59,20 @@ def build_model(): # TODO: this seems like an over-complicated way to accomplish this task... def filter_out_last(model, j): + """_summary_ + + Parameters + ---------- + model : _type_ + _description_ + j : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return j != model.STAGES.last() model.STAGESExceptLast = Set(initialize=model.STAGES, filter=filter_out_last) @@ -120,17 +140,73 @@ def get_log_coeffs(model, k): # GAMS never un-logs them, I don't think. And I think the GAMs ones # must be the log ones. def get_volume_bounds(model, j): + """_summary_ + + Parameters + ---------- + model : _type_ + _description_ + j : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return (model.volumeLB[j], model.volumeUB[j]) model.volume_log = Var(model.STAGES, bounds=get_volume_bounds) model.batchSize_log = Var(model.PRODUCTS, model.STAGES) model.cycleTime_log = Var(model.PRODUCTS) def get_unitsOutOfPhase_bounds(model, j): + """_summary_ + + Parameters + ---------- + model : _type_ + _description_ + j : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return (0, model.unitsOutOfPhaseUB[j]) model.unitsOutOfPhase_log = Var(model.STAGES, bounds=get_unitsOutOfPhase_bounds) def get_unitsInPhase_bounds(model, j): + """_summary_ + + Parameters + ---------- + model : _type_ + _description_ + j : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return (0, model.unitsInPhaseUB[j]) model.unitsInPhase_log = Var(model.STAGES, bounds=get_unitsInPhase_bounds) def get_storageTankSize_bounds(model, j): + """_summary_ + + Parameters + ---------- + model : _type_ + _description_ + j : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return (model.storageTankSizeLB[j], model.storageTankSizeUB[j]) # TODO: these bounds make it infeasible... model.storageTankSize_log = Var(model.STAGES, bounds=get_storageTankSize_bounds) @@ -142,6 +218,18 @@ def get_storageTankSize_bounds(model, j): # Objective def get_cost_rule(model): + """_summary_ + + Parameters + ---------- + model : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ 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) @@ -149,16 +237,60 @@ def get_cost_rule(model): # Constraints def processing_capacity_rule(model, j, i): + """_summary_ + + Parameters + ---------- + model : _type_ + _description_ + j : _type_ + _description_ + i : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ 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) def processing_time_rule(model, j, i): + """_summary_ + + Parameters + ---------- + model : _type_ + _description_ + j : _type_ + _description_ + i : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ 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) def finish_in_time_rule(model): + """_summary_ + + Parameters + ---------- + model : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ 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) @@ -167,18 +299,92 @@ def finish_in_time_rule(model): # Disjunctions def storage_tank_selection_disjunct_rule(disjunct, selectStorageTank, j): + """_summary_ + + Parameters + ---------- + disjunct : _type_ + _description_ + selectStorageTank : _type_ + _description_ + j : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ model = disjunct.model() def volume_stage_j_rule(disjunct, i): + """_summary_ + + Parameters + ---------- + disjunct : _type_ + _description_ + i : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return model.storageTankSize_log[j] >= log(model.StorageTankSizeFactor[j]) + \ model.batchSize_log[i, j] + def volume_stage_jPlus1_rule(disjunct, i): + """_summary_ + + Parameters + ---------- + disjunct : _type_ + _description_ + i : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return model.storageTankSize_log[j] >= log(model.StorageTankSizeFactor[j]) + \ model.batchSize_log[i, j+1] + def batch_size_rule(disjunct, i): + """_summary_ + + Parameters + ---------- + disjunct : _type_ + _description_ + i : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ 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): + """_summary_ + + Parameters + ---------- + disjunct : _type_ + _description_ + i : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return model.batchSize_log[i,j] - model.batchSize_log[i,j+1] == 0 if selectStorageTank: @@ -195,27 +401,97 @@ def no_batch_rule(disjunct, i): rule=storage_tank_selection_disjunct_rule) def select_storage_tanks_rule(model, j): + """_summary_ + + Parameters + ---------- + model : _type_ + _description_ + j : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ 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) # though this is a disjunction in the GAMs model, it is more efficiently formulated this way: # TODO: what on earth is k? def units_out_of_phase_rule(model, j): + """_summary_ + + Parameters + ---------- + model : _type_ + _description_ + j : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ 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) def units_in_phase_rule(model, j): + """_summary_ + + Parameters + ---------- + model : _type_ + _description_ + j : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ 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) # and since I didn't do the disjunction as a disjunction, we need the XORs: def units_out_of_phase_xor_rule(model, j): + """_summary_ + + Parameters + ---------- + model : _type_ + _description_ + j : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ 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) def units_in_phase_xor_rule(model, j): + """_summary_ + + Parameters + ---------- + model : _type_ + _description_ + j : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ 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) From 94e811821ed8d9c969d4534f876baa0e1a94d008 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 4 Apr 2024 21:54:59 -0400 Subject: [PATCH 021/124] Intermedia commit for merge --- gdplib/pyomo_examples/batch_processing.py | 207 +++++++++++++--------- 1 file changed, 120 insertions(+), 87 deletions(-) diff --git a/gdplib/pyomo_examples/batch_processing.py b/gdplib/pyomo_examples/batch_processing.py index c6f660b..40d2f09 100644 --- a/gdplib/pyomo_examples/batch_processing.py +++ b/gdplib/pyomo_examples/batch_processing.py @@ -1,12 +1,3 @@ -""" -batch_processing.py -This problem seeks to minimize the investment cost in the design of a plant with multiple units in parallel and intermediate storage tanks [1]. - -Reference: - [1] Ravemark, E. Optimization models for design and operation of chemical batch processes. Ph.D. Thesis, ETH Zurich, 1995. - [2] Vecchietti, A.; Grossmann, I. E. LOGMIP: a disjunctive 0-1 non-linear optimizer for process system models. Computers and Chemical Engineering 1994, 23, 555–565. - -""" from os.path import join from pyomo.common.fileutils import this_file_dir @@ -24,12 +15,28 @@ def build_model(): - """_summary_ + """ + 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. + 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. + + Parameters + ---------- + None Returns ------- - _type_ - _description_ + Pyomo.ConcreteModel + An instance of the Pyomo ConcreteModel class representing the batch processing optimization model, + ready to be solved with an appropriate solver. + + References + ---------- + Ravemark, E. Optimization models for design and operation of chemical batch processes. Ph.D. Thesis, ETH Zurich, 1995. + 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. """ model = AbstractModel() @@ -53,9 +60,9 @@ def build_model(): # Sets - model.PRODUCTS = Set() - model.STAGES = Set(ordered=True) - model.PARALLELUNITS = Set(ordered=True) + model.PRODUCTS = Set(doc='Set of Products') + model.STAGES = Set(doc='Set of Stages', ordered=True) + model.PARALLELUNITS = Set(doc='Set of Parallel Units', ordered=True) # TODO: this seems like an over-complicated way to accomplish this task... def filter_out_last(model, j): @@ -63,10 +70,11 @@ def filter_out_last(model, j): Parameters ---------- - model : _type_ - _description_ - j : _type_ - _description_ + model : Pyomo.ConcreteModel + The Pyomo model for the batch processing optimization problem. + j : int + The index representing the stage in the processing sequence. Stages are ordered and include various + processing steps required for product completion. Returns ------- @@ -102,6 +110,20 @@ def filter_out_last(model, j): # I made PRODUCTS ordered so I could do this... Is that bad? And it does index # from 1, right? def get_log_coeffs(model, k): + """_summary_ + + Parameters + ---------- + model : Pyomo.ConcreteModel + The Pyomo model for the batch processing optimization problem. + k : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return log(model.PARALLELUNITS.ord(k)) model.LogCoeffs = Param(model.PARALLELUNITS, initialize=get_log_coeffs) @@ -144,10 +166,10 @@ def get_volume_bounds(model, j): Parameters ---------- - model : _type_ - _description_ - j : _type_ - _description_ + model : Pyomo.ConcreteModel + The Pyomo model for the batch processing optimization problem. + j : int + Index for the processing stages in the plant. Stages are ordered and include various processing steps. Returns ------- @@ -158,15 +180,16 @@ def get_volume_bounds(model, j): model.volume_log = Var(model.STAGES, bounds=get_volume_bounds) model.batchSize_log = Var(model.PRODUCTS, model.STAGES) model.cycleTime_log = Var(model.PRODUCTS) + def get_unitsOutOfPhase_bounds(model, j): """_summary_ Parameters ---------- - model : _type_ - _description_ - j : _type_ - _description_ + model : Pyomo.ConcreteModel + The Pyomo model for the batch processing optimization problem. + j : int + Index for the processing stages in the plant. Stages are ordered and include various processing steps. Returns ------- @@ -180,10 +203,10 @@ def get_unitsInPhase_bounds(model, j): Parameters ---------- - model : _type_ - _description_ - j : _type_ - _description_ + model : Pyomo.ConcreteModel + The Pyomo model for the batch processing optimization problem. + j : int + Index for the processing stages in the plant. Stages are ordered and include various processing steps. Returns ------- @@ -192,15 +215,16 @@ def get_unitsInPhase_bounds(model, j): """ return (0, model.unitsInPhaseUB[j]) model.unitsInPhase_log = Var(model.STAGES, bounds=get_unitsInPhase_bounds) + def get_storageTankSize_bounds(model, j): """_summary_ Parameters ---------- - model : _type_ - _description_ - j : _type_ - _description_ + model : Pyomo.ConcreteModel + The Pyomo model for the batch processing optimization problem. + j : int + Index for the processing stages in the plant. Stages are ordered and include various processing steps. Returns ------- @@ -222,8 +246,8 @@ def get_cost_rule(model): Parameters ---------- - model : _type_ - _description_ + model : Pyomo.ConcreteModel + The Pyomo model for the batch processing optimization problem. Returns ------- @@ -241,17 +265,18 @@ def processing_capacity_rule(model, j, i): Parameters ---------- - model : _type_ - _description_ - j : _type_ - _description_ - i : _type_ - _description_ + model : Pyomo.ConcreteModel + The Pyomo model for the batch processing optimization problem. + j : int + Index for the processing stages in the plant. Stages are ordered and include various processing steps. + i : int + The index representing a specific product. Products have unique processing requirements, including + batch sizes and processing times, that vary by stage. Returns ------- - _type_ - _description_ + Pyomo.Constraint.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] @@ -262,17 +287,19 @@ def processing_time_rule(model, j, i): Parameters ---------- - model : _type_ - _description_ - j : _type_ - _description_ - i : _type_ - _description_ + model : Pyomo.ConcreteModel + The Pyomo model for the batch processing optimization problem. + j : int + Index for the processing stages in the plant. Stages are ordered and include various processing steps. + i : int + Product index, representing different products being processed in the plant, each with its own set of + processing times across various stages. Returns ------- - _type_ - _description_ + Pyomo.Constraint.Expression + 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] @@ -283,8 +310,8 @@ def finish_in_time_rule(model): Parameters ---------- - model : _type_ - _description_ + model : Pyomo.ConcreteModel + The Pyomo model for the batch processing optimization problem. Returns ------- @@ -307,8 +334,8 @@ def storage_tank_selection_disjunct_rule(disjunct, selectStorageTank, j): _description_ selectStorageTank : _type_ _description_ - j : _type_ - _description_ + j : int + Index for the processing stages in the plant. Stages are ordered and include various processing steps. Returns ------- @@ -317,14 +344,16 @@ def storage_tank_selection_disjunct_rule(disjunct, selectStorageTank, j): """ model = disjunct.model() def volume_stage_j_rule(disjunct, i): - """_summary_ + """ + Parameters ---------- - disjunct : _type_ - _description_ - i : _type_ + disjunct : _description_ + i : int + Product index, representing different products being processed in the plant, each with its own set of + processing times across various stages. Returns ------- @@ -341,8 +370,9 @@ def volume_stage_jPlus1_rule(disjunct, i): ---------- disjunct : _type_ _description_ - i : _type_ - _description_ + i : int + Product index, representing different products being processed in the plant, each with its own set of + processing times across various stages. Returns ------- @@ -359,8 +389,9 @@ def batch_size_rule(disjunct, i): ---------- disjunct : _type_ _description_ - i : _type_ - _description_ + i : int + Product index, representing different products being processed in the plant, each with its own set of + processing times across various stages. Returns ------- @@ -370,6 +401,7 @@ def batch_size_rule(disjunct, i): 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): """_summary_ @@ -377,8 +409,9 @@ def no_batch_rule(disjunct, i): ---------- disjunct : _type_ _description_ - i : _type_ - _description_ + i : int + Product index, representing different products being processed in the plant, each with its own set of + processing times across various stages. Returns ------- @@ -405,10 +438,10 @@ def select_storage_tanks_rule(model, j): Parameters ---------- - model : _type_ - _description_ - j : _type_ - _description_ + model : Pyomo.ConcreteModel + The Pyomo model for the batch processing optimization problem. + j : int + Index for the processing stages in the plant. Stages are ordered and include various processing steps. Returns ------- @@ -425,10 +458,10 @@ def units_out_of_phase_rule(model, j): Parameters ---------- - model : _type_ - _description_ - j : _type_ - _description_ + model : Pyomo.ConcreteModel + The Pyomo model for the batch processing optimization problem. + j : int + Index for the processing stages in the plant. Stages are ordered and include various processing steps. Returns ------- @@ -444,10 +477,10 @@ def units_in_phase_rule(model, j): Parameters ---------- - model : _type_ - _description_ - j : _type_ - _description_ + model : Pyomo.ConcreteModel + The Pyomo model for the batch processing optimization problem. + j : int + Index for the processing stages in the plant. Stages are ordered and include various processing steps. Returns ------- @@ -464,10 +497,10 @@ def units_out_of_phase_xor_rule(model, j): Parameters ---------- - model : _type_ - _description_ - j : _type_ - _description_ + model : Pyomo.ConcreteModel + The Pyomo model for the batch processing optimization problem. + j : int + Index for the processing stages in the plant. Stages are ordered and include various processing steps. Returns ------- @@ -482,10 +515,10 @@ def units_in_phase_xor_rule(model, j): Parameters ---------- - model : _type_ - _description_ - j : _type_ - _description_ + model : Pyomo.ConcreteModel + The Pyomo model for the batch processing optimization problem. + j : int + Index for the processing stages in the plant. Stages are ordered and include various processing steps. Returns ------- From 2e1a9c84f7c39a9c6a306ef38886d4dd0ab5a538 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 4 Apr 2024 23:07:07 -0400 Subject: [PATCH 022/124] Added the documentation of batch processing py without disjunction part --- gdplib/pyomo_examples/batch_processing.py | 125 +++++++++++++--------- 1 file changed, 73 insertions(+), 52 deletions(-) diff --git a/gdplib/pyomo_examples/batch_processing.py b/gdplib/pyomo_examples/batch_processing.py index 40d2f09..c03eda3 100644 --- a/gdplib/pyomo_examples/batch_processing.py +++ b/gdplib/pyomo_examples/batch_processing.py @@ -4,14 +4,14 @@ from pyomo.environ import * from pyomo.gdp import * -'''Problem from http://www.minlp.org/library/problem/index.php?i=172&lib=GDP -We are minimizing the cost of a design of a plant with parallel processing units and storage tanks -in between. We decide the number and volume of units, and the volume and location of the storage -tanks. The problem is convexified and has a nonlinear objective and global constraints +# Problem from http://www.minlp.org/library/problem/index.php?i=172&lib=GDP +# We are minimizing the cost of a design of a plant with parallel processing units and storage tanks +# in between. We decide the number and volume of units, and the volume and location of the storage +# 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 -has the "optimal" bigM).''' +# 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 +# has the "optimal" bigM). def build_model(): @@ -66,7 +66,9 @@ def build_model(): # TODO: this seems like an over-complicated way to accomplish this task... def filter_out_last(model, j): - """_summary_ + """ + 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 ---------- @@ -78,8 +80,8 @@ def filter_out_last(model, j): Returns ------- - _type_ - _description_ + bool + 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) @@ -90,11 +92,11 @@ def filter_out_last(model, j): # Parameters - model.HorizonTime = Param() - model.Alpha1 = Param() - model.Alpha2 = Param() - model.Beta1 = Param() - model.Beta2 = Param() + model.HorizonTime = Param(doc='Horizon Time') + model.Alpha1 = Param(doc='Cost Parameter of the units') + model.Alpha2 = Param(doc='Cost Parameter of the intermediate storage tanks') + model.Beta1 = Param(doc='Exponent Parameter of the units') + model.Beta2 = Param(doc='Exponent Parameter of the intermediate storage tanks') model.ProductionAmount = Param(model.PRODUCTS) model.ProductSizeFactor = Param(model.PRODUCTS, model.STAGES) @@ -110,27 +112,29 @@ def filter_out_last(model, j): # I made PRODUCTS ordered so I could do this... Is that bad? And it does index # from 1, right? def get_log_coeffs(model, k): - """_summary_ + """ + Calculates the logarithmic coefficients used in the model, typically for transforming linear + relationships into logarithmic form for optimization purposes. Parameters ---------- model : Pyomo.ConcreteModel The Pyomo model for the batch processing optimization problem. - k : _type_ - _description_ + k : int + The index representing a parallel unit. Returns ------- - _type_ - _description_ + float + The logarithm of the position of the parallel unit within its set, used as a coefficient in the model. """ return log(model.PARALLELUNITS.ord(k)) model.LogCoeffs = Param(model.PARALLELUNITS, initialize=get_log_coeffs) # bounds - model.volumeLB = Param(model.STAGES, default=VolumeLB) - model.volumeUB = Param(model.STAGES, default=VolumeUB) + 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) model.storageTankSizeUB = Param(model.STAGES, default=StorageTankSizeUB) model.unitsInPhaseUB = Param(model.STAGES, default=UnitsInPhaseUB) @@ -162,7 +166,8 @@ def get_log_coeffs(model, k): # GAMS never un-logs them, I don't think. And I think the GAMs ones # must be the log ones. def get_volume_bounds(model, j): - """_summary_ + """ + Defines the bounds for the volume of processing units at each stage. Parameters ---------- @@ -173,8 +178,8 @@ def get_volume_bounds(model, j): Returns ------- - _type_ - _description_ + tuple + 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) @@ -182,7 +187,8 @@ def get_volume_bounds(model, j): model.cycleTime_log = Var(model.PRODUCTS) def get_unitsOutOfPhase_bounds(model, j): - """_summary_ + """ + Defines the bounds for the logarithmic representation of the number of units out of phase at each stage. Parameters ---------- @@ -193,13 +199,15 @@ def get_unitsOutOfPhase_bounds(model, j): Returns ------- - _type_ - _description_ + tuple + 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) + def get_unitsInPhase_bounds(model, j): - """_summary_ + """ + Defines the allowable bounds for the logarithmic number of processing units operating in phase at a given stage in the manufacturing process. Parameters ---------- @@ -210,14 +218,15 @@ def get_unitsInPhase_bounds(model, j): Returns ------- - _type_ - _description_ + tuple + 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) def get_storageTankSize_bounds(model, j): - """_summary_ + """ + Determines the lower and upper bounds for the logarithmic representation of the storage tank size between stages j and j+1. Parameters ---------- @@ -228,8 +237,8 @@ def get_storageTankSize_bounds(model, j): Returns ------- - _type_ - _description_ + tuple + 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... @@ -242,7 +251,8 @@ def get_storageTankSize_bounds(model, j): # Objective def get_cost_rule(model): - """_summary_ + """ + Defines the objective function for the model, representing the total cost of the plant design. Parameters ---------- @@ -251,8 +261,13 @@ def get_cost_rule(model): Returns ------- - _type_ - _description_ + Pyomo.Expression + A Pyomo expression representing the total cost of the plant design. + + Notes + ----- + 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) +\ @@ -261,7 +276,9 @@ def get_cost_rule(model): # Constraints def processing_capacity_rule(model, j, i): - """_summary_ + """ + 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 ---------- @@ -275,7 +292,7 @@ def processing_capacity_rule(model, j, i): Returns ------- - Pyomo.Constraint.Expression + 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] - \ @@ -283,7 +300,8 @@ def processing_capacity_rule(model, j, i): model.processing_capacity = Constraint(model.STAGES, model.PRODUCTS, rule=processing_capacity_rule) def processing_time_rule(model, j, i): - """_summary_ + """ + Ensures that the cycle time for product i at stage j, adjusted for the number of out-of-phase units, meets the required processing time. Parameters ---------- @@ -297,7 +315,7 @@ def processing_time_rule(model, j, i): Returns ------- - Pyomo.Constraint.Expression + Pyomo.Expression 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. """ @@ -306,7 +324,8 @@ def processing_time_rule(model, j, i): model.processing_time = Constraint(model.STAGES, model.PRODUCTS, rule=processing_time_rule) def finish_in_time_rule(model): - """_summary_ + """ + Ensures that the total production time across all products does not exceed the defined time horizon for the process. Parameters ---------- @@ -315,8 +334,8 @@ def finish_in_time_rule(model): Returns ------- - _type_ - _description_ + 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) @@ -345,7 +364,7 @@ def storage_tank_selection_disjunct_rule(disjunct, selectStorageTank, j): model = disjunct.model() def volume_stage_j_rule(disjunct, i): """ - + Parameters ---------- @@ -434,7 +453,8 @@ def no_batch_rule(disjunct, i): rule=storage_tank_selection_disjunct_rule) def select_storage_tanks_rule(model, j): - """_summary_ + """ + Defines a disjunction for the model to choose between including or not including a storage tank between stages j and j+1. Parameters ---------- @@ -445,8 +465,8 @@ def select_storage_tanks_rule(model, j): Returns ------- - _type_ - _description_ + 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) @@ -505,13 +525,14 @@ def units_out_of_phase_xor_rule(model, j): Returns ------- _type_ - _description_ + 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) def units_in_phase_xor_rule(model, j): - """_summary_ + """ + Enforces an exclusive OR (XOR) constraint ensuring that exactly one configuration for the number of units out of phase is selected at stage j. Parameters ---------- @@ -522,8 +543,8 @@ def units_in_phase_xor_rule(model, j): Returns ------- - _type_ - _description_ + 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) From 00a90d6eb4acb7418376ee25dcd829e7b73baee8 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 9 Apr 2024 21:24:20 -0400 Subject: [PATCH 023/124] Added the documentation of the disjunction part of the batch processing.py --- gdplib/pyomo_examples/batch_processing.py | 88 ++++++++++++++--------- 1 file changed, 53 insertions(+), 35 deletions(-) diff --git a/gdplib/pyomo_examples/batch_processing.py b/gdplib/pyomo_examples/batch_processing.py index c03eda3..96b7000 100644 --- a/gdplib/pyomo_examples/batch_processing.py +++ b/gdplib/pyomo_examples/batch_processing.py @@ -345,97 +345,103 @@ def finish_in_time_rule(model): # Disjunctions def storage_tank_selection_disjunct_rule(disjunct, selectStorageTank, j): - """_summary_ + """ + 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 ---------- - disjunct : _type_ - _description_ - selectStorageTank : _type_ - _description_ + disjunct : Pyomo.Disjunct + A Pyomo Disjunct object representing a specific case within the disjunction. + selectStorageTank : int + A binary indicator (0 or 1) where 1 means a storage tank is included and 0 means it is not. j : int Index for the processing stages in the plant. Stages are ordered and include various processing steps. Returns ------- - _type_ - _description_ + None + 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 ---------- - disjunct : - _description_ + disjunct : Pyomo.Disjunct + The disjunct within which this constraint is defined. i : int Product index, representing different products being processed in the plant, each with its own set of processing times across various stages. Returns ------- - _type_ - _description_ + 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] def volume_stage_jPlus1_rule(disjunct, i): - """_summary_ + """ + 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. Parameters ---------- - disjunct : _type_ - _description_ + disjunct : Pyomo.Disjunct + The disjunct within which this constraint is defined. i : int Product index, representing different products being processed in the plant, each with its own set of processing times across various stages. Returns ------- - _type_ - _description_ + 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] def batch_size_rule(disjunct, i): - """_summary_ + """ + 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. Parameters ---------- - disjunct : _type_ - _description_ + disjunct : Pyomo.Disjunct + The disjunct within which this constraint is defined. i : int Product index, representing different products being processed in the plant, each with its own set of processing times across various stages. Returns ------- - _type_ - _description_ + 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])) def no_batch_rule(disjunct, i): - """_summary_ + """ + Enforces batch size continuity between stages j and j+1 for product i, applicable when no storage tank is selected. Parameters ---------- - disjunct : _type_ - _description_ + disjunct : Pyomo.Disjunct + The disjunct within which this constraint is defined. i : int Product index, representing different products being processed in the plant, each with its own set of processing times across various stages. Returns ------- - _type_ - _description_ + 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 @@ -474,7 +480,8 @@ def select_storage_tanks_rule(model, j): # though this is a disjunction in the GAMs model, it is more efficiently formulated this way: # TODO: what on earth is k? def units_out_of_phase_rule(model, j): - """_summary_ + """ + Defines the constraints for the logarithmic representation of the number of units k out of phase in stage j. Parameters ---------- @@ -485,8 +492,13 @@ def units_out_of_phase_rule(model, j): Returns ------- - _type_ - _description_ + None + 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) @@ -504,8 +516,13 @@ def units_in_phase_rule(model, j): Returns ------- - _type_ - _description_ + None + 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) @@ -513,7 +530,8 @@ def units_in_phase_rule(model, j): # and since I didn't do the disjunction as a disjunction, we need the XORs: def units_out_of_phase_xor_rule(model, j): - """_summary_ + """ + Enforces an exclusive OR (XOR) constraint ensuring that exactly one configuration for the number of units out of phase is selected at stage j. Parameters ---------- @@ -524,7 +542,7 @@ def units_out_of_phase_xor_rule(model, j): Returns ------- - _type_ + 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 @@ -532,7 +550,7 @@ def units_out_of_phase_xor_rule(model, j): def units_in_phase_xor_rule(model, j): """ - Enforces an exclusive OR (XOR) constraint ensuring that exactly one configuration for the number of units out of phase is selected at stage j. + Enforces an exclusive OR (XOR) constraint ensuring that exactly one configuration for the number of units in phase is selected at stage j. Parameters ---------- From f4a9a51f973cac50698355448bf1eb862aaed353 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 9 Apr 2024 21:46:36 -0400 Subject: [PATCH 024/124] Added header and remove unusual comments. --- gdplib/pyomo_examples/batch_processing.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/gdplib/pyomo_examples/batch_processing.py b/gdplib/pyomo_examples/batch_processing.py index 96b7000..e8894d6 100644 --- a/gdplib/pyomo_examples/batch_processing.py +++ b/gdplib/pyomo_examples/batch_processing.py @@ -1,3 +1,13 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain +# rights in this software. +# This software is distributed under the 3-clause BSD License. +# ___________________________________________________________________________ + from os.path import join from pyomo.common.fileutils import this_file_dir @@ -528,7 +538,6 @@ def units_in_phase_rule(model, j): for k in model.PARALLELUNITS) model.units_in_phase = Constraint(model.STAGES, rule=units_in_phase_rule) - # and since I didn't do the disjunction as a disjunction, we need the XORs: def units_out_of_phase_xor_rule(model, j): """ Enforces an exclusive OR (XOR) constraint ensuring that exactly one configuration for the number of units out of phase is selected at stage j. From 7c981006ce32f9587bc7c6be2bbf11f18321462e Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 9 Apr 2024 22:13:42 -0400 Subject: [PATCH 025/124] Added doc on the parameters, variables and constraints. --- gdplib/pyomo_examples/batch_processing.py | 49 ++++++++++++----------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/gdplib/pyomo_examples/batch_processing.py b/gdplib/pyomo_examples/batch_processing.py index e8894d6..d481199 100644 --- a/gdplib/pyomo_examples/batch_processing.py +++ b/gdplib/pyomo_examples/batch_processing.py @@ -140,15 +140,15 @@ def get_log_coeffs(model, k): """ return log(model.PARALLELUNITS.ord(k)) - model.LogCoeffs = Param(model.PARALLELUNITS, initialize=get_log_coeffs) + 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) - model.storageTankSizeUB = Param(model.STAGES, default=StorageTankSizeUB) - model.unitsInPhaseUB = Param(model.STAGES, default=UnitsInPhaseUB) - model.unitsOutOfPhaseUB = Param(model.STAGES, default=UnitsOutOfPhaseUB) + 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 +192,9 @@ 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) - model.batchSize_log = Var(model.PRODUCTS, model.STAGES) - model.cycleTime_log = Var(model.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 +213,7 @@ 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) + model.unitsOutOfPhase_log = Var(model.STAGES, bounds=get_unitsOutOfPhase_bounds, doc='Logarithmic Units Out of Phase') def get_unitsInPhase_bounds(model, j): """ @@ -232,7 +232,7 @@ 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) + model.unitsInPhase_log = Var(model.STAGES, bounds=get_unitsInPhase_bounds, doc='Logarithmic Units In Phase') def get_storageTankSize_bounds(model, j): """ @@ -252,11 +252,11 @@ def get_storageTankSize_bounds(model, j): """ return (model.storageTankSizeLB[j], model.storageTankSizeUB[j]) # TODO: these bounds make it infeasible... - model.storageTankSize_log = Var(model.STAGES, bounds=get_storageTankSize_bounds) + 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) - model.inPhase = Var(model.STAGES, model.PARALLELUNITS, within=Binary) + 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 @@ -282,7 +282,7 @@ def get_cost_rule(model): 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) + 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): @@ -307,7 +307,7 @@ def processing_capacity_rule(model, j, i): """ 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) + model.processing_capacity = Constraint(model.STAGES, model.PRODUCTS, rule=processing_capacity_rule, doc='Processing Capacity') def processing_time_rule(model, j, i): """ @@ -331,7 +331,7 @@ def processing_time_rule(model, j, i): """ 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) + model.processing_time = Constraint(model.STAGES, model.PRODUCTS, rule=processing_time_rule, doc='Processing Time') def finish_in_time_rule(model): """ @@ -349,7 +349,7 @@ def finish_in_time_rule(model): """ 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) + model.finish_in_time = Constraint(rule=finish_in_time_rule, doc='Finish in Time') # Disjunctions @@ -466,7 +466,7 @@ def no_batch_rule(disjunct, i): # 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) + rule=storage_tank_selection_disjunct_rule, doc='Storage Tank Selection Disjunct') def select_storage_tanks_rule(model, j): """ @@ -485,10 +485,10 @@ def select_storage_tanks_rule(model, j): 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) + 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? + # TODO: what on earth is k? Number of Parallel units. def units_out_of_phase_rule(model, j): """ Defines the constraints for the logarithmic representation of the number of units k out of phase in stage j. @@ -512,10 +512,11 @@ def units_out_of_phase_rule(model, j): """ 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) + 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_ + Defines the constraints for the logarithmic representation of the number of units k in-phase in stage j. Parameters ---------- @@ -536,7 +537,7 @@ def units_in_phase_rule(model, j): """ 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) + 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,7 +556,7 @@ def units_out_of_phase_xor_rule(model, j): 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) + 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,7 +575,7 @@ def units_in_phase_xor_rule(model, j): 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) + 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')) From 797a29f4e561ffcd937bc89d3a21be70a68adaca Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 9 Apr 2024 22:25:29 -0400 Subject: [PATCH 026/124] Added headings of the documentation. --- gdplib/pyomo_examples/med_term_purchasing.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/gdplib/pyomo_examples/med_term_purchasing.py b/gdplib/pyomo_examples/med_term_purchasing.py index 81c2695..a85d262 100644 --- a/gdplib/pyomo_examples/med_term_purchasing.py +++ b/gdplib/pyomo_examples/med_term_purchasing.py @@ -1,3 +1,13 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain +# rights in this software. +# This software is distributed under the 3-clause BSD License. +# ___________________________________________________________________________ + from pyomo.environ import * from pyomo.gdp import * from pyomo.common.fileutils import this_file_dir From dc3d15bd7477aae337fe9c28634f35bb34c13f34 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Wed, 10 Apr 2024 15:17:50 -0400 Subject: [PATCH 027/124] Set up the Bounds on the variables. --- gdplib/pyomo_examples/med_term_purchasing.py | 571 ++++++++++--------- 1 file changed, 297 insertions(+), 274 deletions(-) diff --git a/gdplib/pyomo_examples/med_term_purchasing.py b/gdplib/pyomo_examples/med_term_purchasing.py index a85d262..e1d970f 100644 --- a/gdplib/pyomo_examples/med_term_purchasing.py +++ b/gdplib/pyomo_examples/med_term_purchasing.py @@ -49,7 +49,8 @@ def build_model(): References ---------- [1] Vecchietti, Aldo, and I. Grossmann. "Computational experience with logmip solving linear and nonlinear disjunctive programming problems." In Proc. of FOCAPD, pp. 587-590. 2004. - [2] Park, M., Park, S., Mele, F. D., & Grossmann, I. E. (2006). Modeling of purchase and sales contracts in supply chain optimization. Industrial & Engineering Chemistry Research, 45(14), 5013-5026. DOI: https://doi.org/10.1021/ie0513144 + [2] Vecchietti, A., S. Lee and I.E. Grossmann, “Modeling of Discrete/Continuous Optimization Problems: Characterization and Formulation of Disjunctions and their Relaxations,” + Computers and Chemical Engineering 27, 433-448 (2003). """ model = AbstractModel() @@ -129,15 +130,15 @@ def build_model(): # Price for quantities less than min amount under discount contract # pd1(j, t) in GAMS - model.RegPrice_Discount = Param(model.Streams, model.TimePeriods) + model.RegPrice_Discount = Param(model.Streams, model.TimePeriod, doc='Price for quantities less than min amount under discount contract') # Discounted price for the quantity purchased exceeding the min amount # pd2(j,t0 in GAMS - model.DiscountPrice_Discount = Param(model.Streams, model.TimePeriods) + model.DiscountPrice_Discount = Param(model.Streams, model.TimePeriods, doc='Discounted price for the quantity purchased exceeding the min amount') # Price for quantities below min amount # pb1(j,t) in GAMS - model.RegPrice_Bulk = Param(model.Streams, model.TimePeriods) + model.RegPrice_Bulk = Param(model.Streams, model.TimePeriods, doc='Price for quantities below min amount under bulk contract') # Price for quantities above min amount # pb2(j, t) in GAMS @@ -177,71 +178,73 @@ def build_model(): # epsilon_jt # cinv(j, t) in GAMS # inventory cost of material j at time t - model.InventoryCost = Param(model.Streams, model.TimePeriods) + model.InventoryCost = Param(model.Streams, model.TimePeriods, doc='Inventory cost of material j at time t') # invub(j, t) in GAMS # inventory upper bound - model.InventoryLevelUB = Param(model.Streams, model.TimePeriods, default=0) + model.InventoryLevelUB = Param(model.Streams, model.TimePeriods, default=0, doc='Inventory upper bound') ## UPPER BOUNDS HARDCODED INTO GAMS MODEL # All of these upper bounds are hardcoded. So I am just leaving them that way. # This means they all have to be the same as each other right now. def getAmountUBs(model, j, t): - """_summary_ + """ + Retrieves the upper bound for the amount that can be purchased or processed for a given material j and time period t. Parameters ---------- model : Pyomo.AbstractModel Pyomo abstract model for medium-term purchasing contracts problem. - j : _type_ - _description_ - t : Index - Time period. + j : int + Index of materials. + t : int + Index of time period. Returns ------- - _type_ - _description_ + float + The hardcoded upper bound on the amount for any material and time period, defined by the global variable `AMOUNT_UB`. """ return AMOUNT_UB def getCostUBs(model, j, t): - """_summary_ + """ + Retrieves the upper bound for the cost associated with purchasing or processing a given material in a specific time period. Parameters ---------- model : Pyomo.AbstractModel Pyomo abstract model for medium-term purchasing contracts problem. - j : _type_ - _description_ - t : Index - Time period. + j : int + Index of materials. + t : int + Index of time period. Returns ------- - _type_ - _description_ + float + The hardcoded upper bound on costs for any material and time period, specified by the global variable `COST_UB`. """ return COST_UB model.AmountPurchasedUB_FP = Param(model.Streams, model.TimePeriods, - initialize=getAmountUBs) + initialize=getAmountUBs, doc='Upper bound on amount purchased under fixed price contract') model.AmountPurchasedUB_Discount = Param(model.Streams, model.TimePeriods, - initialize=getAmountUBs) + initialize=getAmountUBs, doc='Upper bound on amount purchased under discount contract') model.AmountPurchasedBelowMinUB_Discount = Param(model.Streams, model.TimePeriods, - initialize=getAmountUBs) + initialize=getAmountUBs, doc='Upper bound on amount purchased below min amount for discount under discount contract') model.AmountPurchasedAboveMinUB_Discount = Param(model.Streams, model.TimePeriods, - initialize=getAmountUBs) + initialize=getAmountUBs, doc='Upper bound on amount purchased above min amount for discount under discount contract') model.AmountPurchasedUB_FD = Param(model.Streams, model.TimePeriods, - initialize=getAmountUBs) + initialize=getAmountUBs, doc='Upper bound on amount purchased under fixed duration contract') model.AmountPurchasedUB_Bulk = Param(model.Streams, model.TimePeriods, - initialize=getAmountUBs) + initialize=getAmountUBs, doc='Upper bound on amount purchased under bulk contract') - model.CostUB_FP = Param(model.Streams, model.TimePeriods, initialize=getCostUBs) - model.CostUB_FD = Param(model.Streams, model.TimePeriods, initialize=getCostUBs) - model.CostUB_Discount = Param(model.Streams, model.TimePeriods, initialize=getCostUBs) - model.CostUB_Bulk = Param(model.Streams, model.TimePeriods, initialize=getCostUBs) + model.CostUB_FP = Param(model.Streams, model.TimePeriods, initialize=getCostUBs, doc='Upper bound on cost of fixed price contract') + model.CostUB_FD = Param(model.Streams, model.TimePeriods, initialize=getCostUBs, doc='Upper bound on cost of fixed duration contract') + model.CostUB_Discount = Param(model.Streams, model.TimePeriods, initialize=getCostUBs, DOC='Upper bound on cost of discount contract') + model.CostUB_Bulk = Param(model.Streams, model.TimePeriods, initialize=getCostUBs, doc='Upper bound on cost of bulk contract') #################### @@ -261,44 +264,52 @@ def getCostUBs(model, j, t): # inventory level of chemical j at time period t def getInventoryBounds(model, i, j): """ - Inventory level of chemical j at time period t + Defines the lower and upper bounds for the inventory level of material j associated with process i. Parameters ---------- model : Pyomo.AbstractModel Pyomo abstract model for medium-term purchasing contracts problem. - i : _type_ - _description_ - j : _type_ - _description_ + i : int + Index of processes. + j : int + Index of materials. Returns ------- - _type_ - _description_ + tuple + A tuple containing two floats: the lower bound (0) and the upper bound for the inventory level of material j. + The upper bound is retrieved from the model's 'InventoryLevelUB' parameter using indices i and j. """ return (0, model.InventoryLevelUB[i,j]) model.InventoryLevel = Var(model.Streams, model.TimePeriods, - bounds=getInventoryBounds) + bounds=getInventoryBounds, doc='Inventory level of material j at time period t') # SF_jt # sf(j, t) in GAMS # Shortfall of demand for chemical j at time period t def getShortfallBounds(model, i, j): - """_summary_ + """ + Defines the lower and upper bounds for the shortfall in demand for material j during a specific time period, associated with process i. Parameters ---------- model : Pyomo.AbstractModel - _description_ - i : _type_ - _description_ - j : _type_ - _description_ + Pyomo abstract model for medium-term purchasing contracts problem. + i : int + Index of processes. + j : int + Index of materials. + + Returns + ------- + tuple + A tuple (0, upper_bound), where 0 is the lower bound (nonnegative shortfalls) and 'upper_bound' is extracted from 'model.ShortfallUB[i, j]'. + 'model.ShortfallUB[i, j]' represents the maximal permitted shortfall for material i in the context of process i. """ return(0, model.ShortfallUB[i,j]) model.Shortfall = Var(model.Products, model.TimePeriods, - bounds=getShortfallBounds) + bounds=getShortfallBounds, doc='Shortfall of demand for material j at time period t') # amounts purchased under different contracts @@ -306,139 +317,151 @@ def getShortfallBounds(model, i, j): # spf(j, t) in GAMS # Amount of raw material j bought under fixed price contract at time period t def get_FP_bounds(model, j, t): - """_summary_ + """ + Determines the bounds for the amount of raw material 'j' that can be purchased under a fixed price contract during time period 't'. Parameters ---------- model : Pyomo.AbstractModel Pyomo abstract model for medium-term purchasing contracts problem. - j : _type_ - _description_ - t : Index - Time period. + j : int + Index of materials. + t : int + Index of time period. Returns ------- _type_ - _description_ + A tuple (0, upper_bound), where '0' is the lower bound (non-negative constraint) and 'upper_bound' is sourced from 'model.AmountPurchasedUB_FP[j,t]'. + 'model.AmountPurchasedUB_FP[j,t]' represents the maximum allowed purchase amount for material 'j' in period 't' """ return (0, model.AmountPurchasedUB_FP[j,t]) model.AmountPurchased_FP = Var(model.Streams, model.TimePeriods, - bounds=get_FP_bounds) + bounds=get_FP_bounds, doc='Amount of raw material j bought under fixed price contract at time period t') # spd(j, t) in GAMS def get_Discount_Total_bounds(model, j, t): - """_summary_ + """ + Determines the lower and upper bounds for the total amount of material j that can be purchased under discount contracts during time period t. Parameters ---------- model : Pyomo.AbstractModel Pyomo abstract model for medium-term purchasing contracts problem. - j : _type_ - _description_ - t : Index - Time period. + j : int + Index of materials. + t : int + Index of time period. Returns ------- - _type_ - _description_ + tuple + A tuple containing two elements: the lower bound (0) and the upper bound for the total amount of material j that can be purchased under discount contracts in period t. + The upper bound is retrieved from 'model.AmountPurchasedUB_Discount[j, t]'. """ return (0, model.AmountPurchasedUB_Discount[j,t]) model.AmountPurchasedTotal_Discount = Var(model.Streams, model.TimePeriods, - bounds=get_Discount_Total_bounds) + bounds=get_Discount_Total_bounds, doc='Total amount of material j bought under discount contract at time period t') # Amount purchased below min amount for discount under discount contract # spd1(j, t) in GAMS def get_Discount_BelowMin_bounds(model, j, t): - """_summary_ + """ + Determines the lower and upper bounds for the amount of material j purchased below the minimum quantity for discounts under discount contracts during time period t. Parameters ---------- model : Pyomo.AbstractModel Pyomo abstract model for medium-term purchasing contracts problem. - j : _type_ - _description_ - t : Index - Time period. + j : int + Index of materials. + t : int + Index of time period. Returns ------- - _type_ - _description_ + tuple + A tuple (0, upper_bound), where '0' is the lower bound, and 'upper_bound' is 'model.AmountPurchasedBelowMinUB_Discount[j, t]'. + 'model.AmountPurchasedBelowMinUB_Discount[j, t] indicates the maximal purchasable amount below the discount threshold for material j in period't. """ return (0, model.AmountPurchasedBelowMinUB_Discount[j,t]) model.AmountPurchasedBelowMin_Discount = Var(model.Streams, - model.TimePeriods, bounds=get_Discount_BelowMin_bounds) + model.TimePeriods, bounds=get_Discount_BelowMin_bounds, doc='Amount purchased below min amount for discount under discount contract') # spd2(j, t) in GAMS # Amount purchased above min amount for discount under discount contract def get_Discount_AboveMin_bounds(model, j, t): - """_summary_ + """ + Determines the lower and upper bounds for the amount of material j purchased above the minimum quantity eligible for discounts under discount contracts during time period t. Parameters ---------- model : Pyomo.AbstractModel Pyomo abstract model for medium-term purchasing contracts problem. - j : _type_ - _description_ - t : Index - Time period. + j : int + Index of materials. + t : int + Index of time period. Returns ------- - _type_ - _description_ + tuple + A tuple (0, upper_bound), where '0' is the lower bound and 'upper_bound' is sourced from 'model.AmountPurchasedBelowMinUB_Discount[j, t]'. + 'model.AmountPurchasedBelowMinUB_Discount[j, t]' indicates the maximum amount that can be purchased above the discount threshold for material j in period t. """ return (0, model.AmountPurchasedBelowMinUB_Discount[j,t]) model.AmountPurchasedAboveMin_Discount = Var(model.Streams, - model.TimePeriods, bounds=get_Discount_AboveMin_bounds) + model.TimePeriods, bounds=get_Discount_AboveMin_bounds, doc='Amount purchased above min amount for discount under discount contract') # Amount purchased under bulk contract # spb(j, t) in GAMS def get_bulk_bounds(model, j, t): - """_summary_ + """ + Sets the lower and upper bounds for the amount of material j that can be purchased under a bulk contract during time period t. Parameters ---------- model : Pyomo.AbstractModel Pyomo abstract model for medium-term purchasing contracts problem. - j : _type_ - _description_ - t : Index - Time period. + j : int + Index of materials. + t : int + Index of time period. Returns ------- - _type_ - _description_ + tuple + A tuple (0, upper_bound), where '0' is the lower bound and 'upper_bound' is sourced from 'model.AmountPurchasedUB_Bulk[j,t]'. + 'model.AmountPurchasedUB_Bulk[j,t]' indicates the maximal allowable bulk purchase amount for material 'j' in period 't'. """ return (0, model.AmountPurchasedUB_Bulk[j,t]) model.AmountPurchased_Bulk = Var(model.Streams, model.TimePeriods, - bounds=get_bulk_bounds) + bounds=get_bulk_bounds, doc='Amount purchased under bulk contract') # spl(j, t) in GAMS # Amount purchased under Fixed Duration contract def get_FD_bounds(model, j, t): - """_summary_ + """ + Sets the lower and upper bounds for the quantity of material j that can be acquired under a fixed duration contract during the time period t. Parameters ---------- model : Pyomo.AbstractModel Pyomo abstract model for medium-term purchasing contracts problem. - j : _type_ - _description_ - t : Index - Time period. + j : int + Index of materials. + t : int + Index of time period. Returns ------- - _type_ - _description_ + tuple + A tuple (0, upper_bound), where '0' is the lower bound and upper_bound is taken from 'model.AmountPurchasedUB_FD[j, t]'. + 'model.AmountPurchasedUB_FD[j, t]' indicates the maximum permissible purchase quantity for material j under a fixed duration contract in time period t. """ return (0, model.AmountPurchasedUB_FD[j,t]) model.AmountPurchased_FD = Var(model.Streams, model.TimePeriods, - bounds=get_FD_bounds) + bounds=get_FD_bounds, doc='Amount purchased under Fixed Duration contract') # costs @@ -451,19 +474,19 @@ def get_CostUBs_FD(model, j, t): Parameters ---------- model : Pyomo.AbstractModel - _description_ - j : _type_ - _description_ - t : Index - Time period. + Pyomo abstract model for medium-term purchasing contracts problem. + j : int + Index of materials. + t : int + Index of time period. Returns ------- - _type_ + tuple _description_ """ return (0, model.CostUB_FD[j,t]) - model.Cost_FD = Var(model.Streams, model.TimePeriods, bounds=get_CostUBs_FD) + model.Cost_FD = Var(model.Streams, model.TimePeriods, bounds=get_CostUBs_FD, doc='Cost of variable length contract') # costpf(j, t) in GAMS # cost of fixed duration contract @@ -474,18 +497,18 @@ def get_CostUBs_FP(model, j, t): ---------- model : Pyomo.AbstractModel Pyomo abstract model for medium-term purchasing contracts problem. - j : _type_ - _description_ - t : Index - Time period. + j : int + Index of materials. + t : int + Index of time period. Returns ------- - _type_ + tuple _description_ """ return (0, model.CostUB_FP[j,t]) - model.Cost_FP = Var(model.Streams, model.TimePeriods, bounds=get_CostUBs_FP) + model.Cost_FP = Var(model.Streams, model.TimePeriods, bounds=get_CostUBs_FP, doc='Cost of fixed price contract') # costpd(j, t) in GAMS # cost of discount contract @@ -496,19 +519,19 @@ def get_CostUBs_Discount(model, j, t): ---------- model : Pyomo.AbstractModel Pyomo abstract model for medium-term purchasing contracts problem. - j : _type_ - _description_ - t : Index - _description_ + j : int + Index of materials. + t : int + Index of time period. Returns ------- - _type_ + tuple _description_ """ return (0, model.CostUB_Discount[j,t]) model.Cost_Discount = Var(model.Streams, model.TimePeriods, - bounds=get_CostUBs_Discount) + bounds=get_CostUBs_Discount, doc='Cost of discount contract') # costpb(j, t) in GAMS # cost of bulk contract @@ -519,18 +542,18 @@ def get_CostUBs_Bulk(model, j, t): ---------- model : Pyomo.AbstractModel Pyomo abstract model for medium-term purchasing contracts problem. - j : _type_ - _description_ - t : Index - Time period. + j : int + Index of materials. + t : int + Index of time period. Returns ------- - _type_ + tuple _description_ """ return (0, model.CostUB_Bulk[j,t]) - model.Cost_Bulk = Var(model.Streams, model.TimePeriods, bounds=get_CostUBs_Bulk) + model.Cost_Bulk = Var(model.Streams, model.TimePeriods, bounds=get_CostUBs_Bulk, doc='Cost of bulk contract') # binary variables @@ -557,8 +580,8 @@ def profit_rule(model): Returns ------- - _type_ - _description_ + Pyomo.Objective + Objective function: maximize profit of the medium-term purchasing contracts problem. """ salesIncome = sum(model.Prices[j,t] * model.FlowRate[j,t] for j in model.Products for t in model.TimePeriods) @@ -578,7 +601,7 @@ def profit_rule(model): inventoryCost = sum(model.InventoryCost[j,t] * model.InventoryLevel[j,t] for j in model.Products for t in model.TimePeriods) return salesIncome - purchaseCost - productionCost - inventoryCost - shortfallCost - model.profit = Objective(rule=profit_rule, sense=maximize) + model.profit = Objective(rule=profit_rule, sense=maximize, doc='Maximize profit') # flow of raw materials is the total amount purchased (accross all contracts) def raw_material_flow_rule(model, j, t): @@ -586,12 +609,12 @@ def raw_material_flow_rule(model, j, t): Parameters ---------- - model : _type_ - _description_ - j : _type_ - _description_ - t : Index - Time period. + model : Pyomo.AbstractModel + Pyomo abstract model for medium-term purchasing contracts problem. + j : int + Index of materials. + t : int + Index of time period. Returns ------- @@ -609,12 +632,12 @@ def discount_amount_total_rule(model, j, t): Parameters ---------- - model : _type_ - _description_ - j : _type_ - _description_ - t : Index - Time period. + model : Pyomo.AbstractModel + Pyomo abstract model for medium-term purchasing contracts problem. + j : int + Index of materials. + t : int + Index of time period. Returns ------- @@ -634,10 +657,10 @@ def mass_balance_rule1(model, t): Parameters ---------- - model : _type_ - _description_ - t : Index - Time period. + model : Pyomo.AbstractModel + Pyomo abstract model for medium-term purchasing contracts problem. + t : int + Index of time period. Returns ------- @@ -652,10 +675,10 @@ def mass_balance_rule2(model, t): Parameters ---------- - model : _type_ - _description_ - t : Index - Time period. + model : Pyomo.AbstractModel + Pyomo abstract model for medium-term purchasing contracts problem. + t : int + Index of time period. Returns ------- @@ -674,10 +697,10 @@ def mass_balance_rule4(model, t): Parameters ---------- - model : _type_ - _description_ - t : Index - Time period. + model : Pyomo.AbstractModel + Pyomo abstract model for medium-term purchasing contracts problem. + t : int + Index of time period. Returns ------- @@ -694,10 +717,10 @@ def process_balance_rule1(model, t): Parameters ---------- - model : _type_ - _description_ - t : Index - Time period. + model : Pyomo.AbstractModel + Pyomo abstract model for medium-term purchasing contracts problem. + t : int + Index of time period. Returns ------- @@ -712,10 +735,10 @@ def process_balance_rule2(model, t): Parameters ---------- - model : _type_ - _description_ - t : Index - Time period. + model : Pyomo.AbstractModel + Pyomo abstract model for medium-term purchasing contracts problem. + t : int + Index of time period. Returns ------- @@ -731,10 +754,10 @@ def process_balance_rule3(model, t): Parameters ---------- - model : _type_ - _description_ - t : Index - Time period. + model : Pyomo.AbstractModel + Pyomo abstract model for medium-term purchasing contracts problem. + t : int + Index of time period. Returns ------- @@ -750,10 +773,10 @@ def process_balance_rule4(model, t): Parameters ---------- - model : _type_ - _description_ - t : Index - Time period. + model : Pyomo.AbstractModel + Pyomo abstract model for medium-term purchasing contracts problem. + t : int + Index of time period. Returns ------- @@ -772,9 +795,9 @@ def process_capacity_rule1(model, t): Parameters ---------- model : Pyomo.AbstractModel - _description_ - t : Index - Time period. + Pyomo abstract model for medium-term purchasing contracts problem. + t : int + Index of time period. Returns ------- @@ -790,9 +813,9 @@ def process_capacity_rule2(model, t): Parameters ---------- model : Pyomo.AbstractModel - _description_ - t : Index - Time period. + Pyomo abstract model for medium-term purchasing contracts problem. + t : int + Index of time period. Returns ------- @@ -808,9 +831,9 @@ def process_capacity_rule3(model, t): Parameters ---------- model : Pyomo.AbstractModel - _description_ - t : Index - Time period. + Pyomo abstract model for medium-term purchasing contracts problem. + t : int + Index of time period. Returns ------- @@ -829,9 +852,9 @@ def inventory_balance1(model, t): Parameters ---------- model : Pyomo.AbstractModel - _description_ - t : Index - Time period. + Pyomo abstract model for medium-term purchasing contracts problem. + t : int + Index of time period. Returns ------- @@ -848,9 +871,9 @@ def inventory_balance_rule2(model, t): Parameters ---------- model : Pyomo.AbstractModel - _description_ - t : Index - Time period. + Pyomo abstract model for medium-term purchasing contracts problem. + t : int + Index of time period. Returns ------- @@ -869,9 +892,9 @@ def inventory_balance_rule3(model, t): Parameters ---------- model : Pyomo.AbstractModel - _description_ - t : Index - Time period. + Pyomo abstract model for medium-term purchasing contracts problem. + t : int + Index of time period. Returns ------- @@ -891,11 +914,11 @@ def inventory_capacity_rule(model, j, t): Parameters ---------- model : Pyomo.AbstractModel - _description_ - j : _type_ - _description_ - t : Index - Time period. + Pyomo abstract model for medium-term purchasing contracts problem. + j : int + Index of materials. + t : int + Index of time period. Returns ------- @@ -912,11 +935,11 @@ def shortfall_rule(model, j, t): Parameters ---------- model : Pyomo.AbstractModel - _description_ - j : _type_ - _description_ - t : Index - Time period. + Pyomo abstract model for medium-term purchasing contracts problem. + j : int + Index of materials. + t : int + Index of time period. Returns ------- @@ -933,11 +956,11 @@ def shortfall_max_rule(model, j, t): Parameters ---------- model : Pyomo.AbstractModel - _description_ - j : _type_ - _description_ - t : Index - Time period. + Pyomo abstract model for medium-term purchasing contracts problem. + j : int + Index of materials. + t : int + Index of time period. Returns ------- @@ -954,11 +977,11 @@ def supplier_capacity_rule(model, j, t): Parameters ---------- model : Pyomo.AbstractModel - _description_ - j : _type_ - _description_ - t : Index - Time period. + Pyomo abstract model for medium-term purchasing contracts problem. + j : int + Index of materials. + t : int + Index of time period. Returns ------- @@ -975,11 +998,11 @@ def demand_UB_rule(model, j, t): Parameters ---------- model : Pyomo.AbstractModel - _description_ - j : _type_ - _description_ - t : Index - Time period. + Pyomo abstract model for medium-term purchasing contracts problem. + j : int + Index of materials. + t : int + Index of time period. Returns ------- @@ -995,11 +1018,11 @@ def demand_LB_rule(model, j, t): Parameters ---------- model : Pyomo.AbstractModel - _description_ - j : _type_ - _description_ - t : Index - Time period. + Pyomo abstract model for medium-term purchasing contracts problem. + j : int + Index of materials. + t : int + Index of time period. Returns ------- @@ -1018,12 +1041,12 @@ def FP_contract_disjunct_rule(disjunct, j, t, buy): Parameters ---------- - disjunct : Index + disjunct : Pyomo.Disjunct _description_ - j : _type_ - _description_ - t : Index - Time period. + j : int + Index of materials. + t : int + Index of time period. buy : _type_ _description_ """ @@ -1042,11 +1065,11 @@ def FP_contract_rule(model, j, t): Parameters ---------- model : Pyomo.AbstractModel - _description_ - j : _type_ - _description_ - t : Index - Time period. + Pyomo abstract model for medium-term purchasing contracts problem. + j : int + Index of materials. + t : int + Index of time period. Returns ------- @@ -1064,11 +1087,11 @@ def FP_contract_cost_rule(model, j, t): Parameters ---------- model : Pyomo.AbstractModel - _description_ - j : _type_ - _description_ - t : Index - Time period. + Pyomo abstract model for medium-term purchasing contracts problem. + j : int + Index of materials. + t : int + Index of time period. Returns ------- @@ -1089,14 +1112,14 @@ def discount_contract_disjunct_rule(disjunct, j, t, buy): Parameters ---------- - disjunct : Index - _description_ - j : _type_ - _description_ - t : Index - _description_ - buy : _type_ + disjunct : Pyomo.Disjunct _description_ + j : int + Index of materials. + t : int + Index of time period. + buy : str + Decision parameter that indicates whether to buy under the fixed price contract or not. Raises ------ @@ -1133,11 +1156,11 @@ def discount_contract_rule(model, j, t): Parameters ---------- model : Pyomo.AbstractModel - _description_ - j : _type_ - _description_ - t : Index - Time period. + Pyomo abstract model for medium-term purchasing contracts problem. + j : int + Index of materials. + t : int + Index of time period. Returns ------- @@ -1156,11 +1179,11 @@ def discount_cost_rule(model, j, t): Parameters ---------- model : Pyomo.AbstractModel - _description_ - j : _type_ - _description_ - t : Index - Time period. + Pyomo abstract model for medium-term purchasing contracts problem. + j : int + Index of materials. + t : int + Index of time period. Returns ------- @@ -1182,14 +1205,14 @@ def bulk_contract_disjunct_rule(disjunct, j, t, buy): Parameters ---------- - disjunct : Index - _description_ - j : _type_ - _description_ - t : Index - Time period. - buy : _type_ - _description_ + disjunct : Pyomo.Disjunct + -description_ + j : int + Index of materials. + t : int + Index of time period. + buy : str + Decision parameter that indicates whether to buy under the fixed price contract or not. Raises ------ @@ -1224,11 +1247,11 @@ def bulk_contract_rule(model, j, t): Parameters ---------- model : Pyomo.AbstractModel - _description_ - j : _type_ - _description_ - t : Index - _description_ + Pyomo abstract model for medium-term purchasing contracts problem. + j : int + Index of materials. + t : int + Index of time period. Returns ------- @@ -1249,10 +1272,10 @@ def FD_1mo_contract(disjunct, j, t): ---------- disjunct : Index _description_ - j : _type_ - _description_ - t : Index - Time period. + j : int + Index of materials. + t : int + Index of time period. """ model = disjunct.model() disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] >= \ @@ -1269,10 +1292,10 @@ def FD_2mo_contract(disjunct, j, t): ---------- disjunct : Index _description_ - j : _type_ - _description_ - t : Index - Time period. + j : int + Index of materials. + t : int + Index of time period. """ model = disjunct.model() disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] >= \ @@ -1295,10 +1318,10 @@ def FD_3mo_contract(disjunct, j, t): ---------- disjunct : Index _description_ - j : _type_ - _description_ - t : Index - Time period. + j : int + Index of materials. + t : int + Index of time period. """ model = disjunct.model() # NOTE: I think there is a mistake in the GAMS file in line 327. @@ -1329,10 +1352,10 @@ def FD_no_contract(disjunct, j, t): ---------- disjunct : Index _description_ - j : _type_ - _description_ - t : Index - Time period. + j : int + Index of materials. + t : int + Index of time period. """ model = disjunct.model() disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] == 0) @@ -1352,11 +1375,11 @@ def FD_contract(model, j, t): Parameters ---------- model : Pyomo.AbstractModel - _description_ - j : _type_ - _description_ - t : Index - Time period. + Pyomo abstract model for medium-term purchasing contracts problem. + j : int + Index of materials. + t : int + Index of time period. Returns ------- From 0f413f59067e421acab31bfff664897fb5fc644f Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Wed, 10 Apr 2024 17:14:20 -0400 Subject: [PATCH 028/124] Added Documentation of the Variables. --- gdplib/pyomo_examples/med_term_purchasing.py | 62 +++++++++++--------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/gdplib/pyomo_examples/med_term_purchasing.py b/gdplib/pyomo_examples/med_term_purchasing.py index e1d970f..cf14132 100644 --- a/gdplib/pyomo_examples/med_term_purchasing.py +++ b/gdplib/pyomo_examples/med_term_purchasing.py @@ -469,7 +469,8 @@ def get_FD_bounds(model, j, t): # costpl(j, t) in GAMS # cost of variable length contract def get_CostUBs_FD(model, j, t): - """_summary_ + """ + Build the lower and upper bounds for the cost of purchasing material j under a fixed duration contract during time period t. Parameters ---------- @@ -483,7 +484,8 @@ def get_CostUBs_FD(model, j, t): Returns ------- tuple - _description_ + A tuple (0, upper_bound), where '0' is the lower bound, the minimal cost scenario, and 'upper_bound' is retrieved from 'model.CostUB_FD[j, t]', + 'model.CostUB_FD[j, t]' represents the highest permissible cost for purchasing material j under a fixed duration contract in time period t. """ return (0, model.CostUB_FD[j,t]) model.Cost_FD = Var(model.Streams, model.TimePeriods, bounds=get_CostUBs_FD, doc='Cost of variable length contract') @@ -491,7 +493,8 @@ def get_CostUBs_FD(model, j, t): # costpf(j, t) in GAMS # cost of fixed duration contract def get_CostUBs_FP(model, j, t): - """_summary_ + """ + Sets the lower and upper bounds for the cost of purchasing material j under a fixed price contract during time period t. Parameters ---------- @@ -505,7 +508,8 @@ def get_CostUBs_FP(model, j, t): Returns ------- tuple - _description_ + A tuple (0, upper_bound), where '0' is the lower bound and upper_bound is sourced from 'model.CostUB_FP[j, t]'. + 'model.CostUB_FP[j, t]' denotes the highest permissible cost for purchasing material 'j' under a fixed price contract in time period 't' """ return (0, model.CostUB_FP[j,t]) model.Cost_FP = Var(model.Streams, model.TimePeriods, bounds=get_CostUBs_FP, doc='Cost of fixed price contract') @@ -513,7 +517,8 @@ def get_CostUBs_FP(model, j, t): # costpd(j, t) in GAMS # cost of discount contract def get_CostUBs_Discount(model, j, t): - """_summary_ + """ + Set the lower and upper bounds for the cost of acquiring material j under discount contracts during time period t. Parameters ---------- @@ -527,7 +532,8 @@ def get_CostUBs_Discount(model, j, t): Returns ------- tuple - _description_ + A tuple (0, upper bound), where 0' represents the lower bound, indicating the lowest possible cost condition, and upper bound is the 'model.CostUB_Discount[j, t]'. + 'model.CostUB_Discount[j, t]' represents the maximum allowed cost for purchasing material j under a discount contract in the given time period t. """ return (0, model.CostUB_Discount[j,t]) model.Cost_Discount = Var(model.Streams, model.TimePeriods, @@ -536,7 +542,8 @@ def get_CostUBs_Discount(model, j, t): # costpb(j, t) in GAMS # cost of bulk contract def get_CostUBs_Bulk(model, j, t): - """_summary_ + """ + Set the lower and upper bounds for the cost incurred from purchasing material j under bulk contracts during time period t. Parameters ---------- @@ -550,7 +557,8 @@ def get_CostUBs_Bulk(model, j, t): Returns ------- tuple - _description_ + A tuple with 0 as the lower bound and 'model.CostUB_Bulk[j, t]' as the upper bound. + 'model.CostUB_Bulk[j, t]' represents the maximum cost for purchasing material j under a bulk contract in time period t. """ return (0, model.CostUB_Bulk[j,t]) model.Cost_Bulk = Var(model.Streams, model.TimePeriods, bounds=get_CostUBs_Bulk, doc='Cost of bulk contract') @@ -558,10 +566,10 @@ def get_CostUBs_Bulk(model, j, t): # binary variables - model.BuyFPContract = RangeSet(0,1) - model.BuyDiscountContract = Set(initialize=('BelowMin', 'AboveMin', 'NotSelected')) - model.BuyBulkContract = Set(initialize=('BelowMin', 'AboveMin', 'NotSelected')) - model.BuyFDContract = Set(initialize=('1Month', '2Month', '3Month', 'NotSelected')) + model.BuyFPContract = RangeSet(0,1) # buy fixed price contract + model.BuyDiscountContract = Set(initialize=('BelowMin', 'AboveMin', 'NotSelected'), doc='Buy discount contract') + model.BuyBulkContract = Set(initialize=('BelowMin', 'AboveMin', 'NotSelected'), doc='Buy bulk contract') + model.BuyFDContract = Set(initialize=('1Month', '2Month', '3Month', 'NotSelected'), doc='Buy fixed duration contract') ################ @@ -618,7 +626,7 @@ def raw_material_flow_rule(model, j, t): Returns ------- - _type_ + Pyomo.Constraint _description_ """ return model.FlowRate[j,t] == model.AmountPurchased_FD[j,t] + \ @@ -641,7 +649,7 @@ def discount_amount_total_rule(model, j, t): Returns ------- - _type_ + Pyomo.Constraint _description_ """ return model.AmountPurchasedTotal_Discount[j,t] == \ @@ -664,7 +672,7 @@ def mass_balance_rule1(model, t): Returns ------- - _type_ + Pyomo.Constraint _description_ """ return model.FlowRate[1, t] == model.FlowRate[2, t] + model.FlowRate[3, t] @@ -682,7 +690,7 @@ def mass_balance_rule2(model, t): Returns ------- - _type_ + Pyomo.Constraint _description_ """ return model.FlowRate[5, t] == model.FlowRate[4, t] + model.FlowRate[8,t] @@ -704,7 +712,7 @@ def mass_balance_rule4(model, t): Returns ------- - _type_ + Pyomo.Constraint _description_ """ return model.FlowRate[3, t] == 10*model.FlowRate[5, t] @@ -724,7 +732,7 @@ def process_balance_rule1(model, t): Returns ------- - _type_ + Pyomo.Constraint _description_ """ return model.FlowRate[9, t] == model.ProcessConstants[1] * model.FlowRate[2, t] @@ -742,7 +750,7 @@ def process_balance_rule2(model, t): Returns ------- - _type_ + Pyomo.Constraint _description_ """ return model.FlowRate[10, t] == model.ProcessConstants[2] * \ @@ -761,7 +769,7 @@ def process_balance_rule3(model, t): Returns ------- - _type_ + Pyomo.Constraint _description_ """ return model.FlowRate[8, t] == RandomConst_Line264 * \ @@ -780,7 +788,7 @@ def process_balance_rule4(model, t): Returns ------- - _type_ + Pyomo.Constraint _description_ """ return model.FlowRate[11, t] == RandomConst_Line265 * \ @@ -801,7 +809,7 @@ def process_capacity_rule1(model, t): Returns ------- - _type_ + Pyomo.Constraint _description_ """ return model.FlowRate[9, t] <= model.Capacity[1] @@ -819,7 +827,7 @@ def process_capacity_rule2(model, t): Returns ------- - _type_ + Pyomo.Constraint _description_ """ return model.FlowRate[10, t] <= model.Capacity[2] @@ -837,7 +845,7 @@ def process_capacity_rule3(model, t): Returns ------- - _type_ + Pyomo.Constraint _description_ """ return model.FlowRate[11, t] + model.FlowRate[8, t] <= model.Capacity[3] @@ -989,7 +997,7 @@ def supplier_capacity_rule(model, j, t): _description_ """ return model.FlowRate[j, t] <= model.SupplyAndDemandUBs[j, t] - model.supplier_capacity = Constraint(model.RawMaterials, model.TimePeriods, rule=supplier_capacity_rule) + model.supplier_capacity = Constraint(model.RawMaterials, model.TimePeriods, rule=supplier_capacity_rule, doc='') # demand upper bound def demand_UB_rule(model, j, t): @@ -1047,8 +1055,8 @@ def FP_contract_disjunct_rule(disjunct, j, t, buy): Index of materials. t : int Index of time period. - buy : _type_ - _description_ + buy : str + Decision parameter that indicates whether to buy under the fixed price contract or not. """ model = disjunct.model() if buy: From 6ede81f672a6be9f6a93af9b1baf18ae5abe6b4a Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Wed, 10 Apr 2024 17:52:09 -0400 Subject: [PATCH 029/124] Update objective function and constraints in medium-term purchasing contracts problem --- gdplib/pyomo_examples/med_term_purchasing.py | 31 +++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/gdplib/pyomo_examples/med_term_purchasing.py b/gdplib/pyomo_examples/med_term_purchasing.py index cf14132..61e1249 100644 --- a/gdplib/pyomo_examples/med_term_purchasing.py +++ b/gdplib/pyomo_examples/med_term_purchasing.py @@ -579,7 +579,8 @@ def get_CostUBs_Bulk(model, j, t): # Objective: maximize profit def profit_rule(model): """ - Objective function: maximize profit + Objective function: maximize profit of the medium-term purchasing contracts problem. + The profit is given by sales revenues, operating costs, purchasing costs, inventory costs, and shortfall penalties Parameters ---------- @@ -613,7 +614,8 @@ def profit_rule(model): # flow of raw materials is the total amount purchased (accross all contracts) def raw_material_flow_rule(model, j, t): - """_summary_ + """ + Ensures the total flow of raw material j in time period t equals the sum of amounts purchased under all contract types. Parameters ---------- @@ -627,16 +629,17 @@ def raw_material_flow_rule(model, j, t): Returns ------- Pyomo.Constraint - _description_ + An equality constraint ensuring the material flow balance for each raw material j in each time period t. """ return model.FlowRate[j,t] == model.AmountPurchased_FD[j,t] + \ model.AmountPurchased_FP[j,t] + model.AmountPurchased_Bulk[j,t] + \ model.AmountPurchasedTotal_Discount[j,t] model.raw_material_flow = Constraint(model.RawMaterials, model.TimePeriods, - rule=raw_material_flow_rule) + rule=raw_material_flow_rule, doc='Material flow balance for each raw material j in each time period t') def discount_amount_total_rule(model, j, t): - """_summary_ + """ + Balances the total amount of material j purchased under discount contracts in time period t with the sum of amounts purchased below and above the minimum discount threshold. Parameters ---------- @@ -650,13 +653,13 @@ def discount_amount_total_rule(model, j, t): Returns ------- Pyomo.Constraint - _description_ + An equality constraint that ensures the total discounted purchase amount of material j in time period t is the sum of amounts bought below and above the discount threshold. """ return model.AmountPurchasedTotal_Discount[j,t] == \ model.AmountPurchasedBelowMin_Discount[j,t] + \ model.AmountPurchasedAboveMin_Discount[j,t] model.discount_amount_total_rule = Constraint(model.RawMaterials, model.TimePeriods, - rule=discount_amount_total_rule) + rule=discount_amount_total_rule, doc='Total discounted purchase amount of material j in time period t is the sum of amounts bought below and above the discount threshold') # mass balance equations for each node # these are specific to the process network in this example. @@ -697,6 +700,20 @@ def mass_balance_rule2(model, t): model.mass_balance2 = Constraint(model.TimePeriods, rule=mass_balance_rule2) def mass_balance_rule3(model, t): + """_summary_ + + Parameters + ---------- + model : Pyomo.AbstractModel + Pyomo abstract model for medium-term purchasing contracts problem. + t : int + Index of time period. + + Returns + ------- + _type_ + _description_ + """ return model.FlowRate[6, t] == model.FlowRate[7, t] model.mass_balance3 = Constraint(model.TimePeriods, rule=mass_balance_rule3) From d6fe86a72c21d93f83d6294bd578d5a614e0b108 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 11 Apr 2024 13:09:25 -0400 Subject: [PATCH 030/124] Set up the maximum flow and material balances --- gdplib/pyomo_examples/med_term_purchasing.py | 107 +++++++++++++------ 1 file changed, 73 insertions(+), 34 deletions(-) diff --git a/gdplib/pyomo_examples/med_term_purchasing.py b/gdplib/pyomo_examples/med_term_purchasing.py index 61e1249..b26462b 100644 --- a/gdplib/pyomo_examples/med_term_purchasing.py +++ b/gdplib/pyomo_examples/med_term_purchasing.py @@ -664,7 +664,11 @@ def discount_amount_total_rule(model, j, t): # mass balance equations for each node # these are specific to the process network in this example. def mass_balance_rule1(model, t): - """_summary_ + """ + Represents the mass balance equation for the first node in the process network. + + Stream 1 is the inlet stream, and streams 2 and 3 are the outlet streams. + The mass balance equation states that the total flow rate into the node (stream 1) is equal to the sum of the flow rates out of the node (streams 2 and 3). Parameters ---------- @@ -676,13 +680,17 @@ def mass_balance_rule1(model, t): Returns ------- Pyomo.Constraint - _description_ + A constraint that enforces the mass balance equation for the first node in the process network. """ return model.FlowRate[1, t] == model.FlowRate[2, t] + model.FlowRate[3, t] - model.mass_balance1 = Constraint(model.TimePeriods, rule=mass_balance_rule1) + model.mass_balance1 = Constraint(model.TimePeriods, rule=mass_balance_rule1, doc='Mass balance equation for the first node in the process network') def mass_balance_rule2(model, t): - """_summary_ + """ + Represents the mass balance equation for the second node in the process network. + + Stream 4 and 8 are the inlet streams, and stream 5 is the outlet stream. + The mass balance equation states that the total flow rate into the node (streams 4 and 8) is equal to the flow rate out of the node (stream 5). Parameters ---------- @@ -694,13 +702,17 @@ def mass_balance_rule2(model, t): Returns ------- Pyomo.Constraint - _description_ + A constraint that enforces the mass balance equation for the second node in the process network. """ return model.FlowRate[5, t] == model.FlowRate[4, t] + model.FlowRate[8,t] - model.mass_balance2 = Constraint(model.TimePeriods, rule=mass_balance_rule2) + model.mass_balance2 = Constraint(model.TimePeriods, rule=mass_balance_rule2, doc='Mass balance equation for the second node in the process network') def mass_balance_rule3(model, t): - """_summary_ + """ + Represents the mass balance equation for the third node in the process network. + + Stream 6 is the inlet stream, and stream 7 is the outlet stream. + The mass balance equation states that the total flow rate into the node (stream 6) is equal to the flow rate out of the node (stream 7). Parameters ---------- @@ -711,14 +723,18 @@ def mass_balance_rule3(model, t): Returns ------- - _type_ - _description_ + Pyomo.Constraint + A constraint that enforces the mass balance equation for the third node in the process network. """ return model.FlowRate[6, t] == model.FlowRate[7, t] - model.mass_balance3 = Constraint(model.TimePeriods, rule=mass_balance_rule3) + model.mass_balance3 = Constraint(model.TimePeriods, rule=mass_balance_rule3, doc='Mass balance equation for the third node in the process network') def mass_balance_rule4(model, t): - """_summary_ + """ + Represents the mass balance equation for Process 2 in the process network. + + Stream 3 and Stream 5 are the inlet of Process 2. + The mass flowrate of Stream 3 is 10 times the mass flowrate of Stream 5. Parameters ---------- @@ -730,15 +746,18 @@ def mass_balance_rule4(model, t): Returns ------- Pyomo.Constraint - _description_ + A constraint that enforces the mass balance equation for Process 2 in the process network. """ return model.FlowRate[3, t] == 10*model.FlowRate[5, t] - model.mass_balance4 = Constraint(model.TimePeriods, rule=mass_balance_rule4) + model.mass_balance4 = Constraint(model.TimePeriods, rule=mass_balance_rule4, doc='Mass balance equation for Process 2 in the process network') # process input/output constraints # these are also totally specific to the process network def process_balance_rule1(model, t): - """_summary_ + """ + Represents the input/output balance equation for Process 1 in the process network. + + Process 1 has input Streams 2 and output Stream 9. Parameters ---------- @@ -750,13 +769,16 @@ def process_balance_rule1(model, t): Returns ------- Pyomo.Constraint - _description_ + A constraint that enforces the input/output balance equation for Process 1 in the process network. """ return model.FlowRate[9, t] == model.ProcessConstants[1] * model.FlowRate[2, t] - model.process_balance1 = Constraint(model.TimePeriods, rule=process_balance_rule1) + model.process_balance1 = Constraint(model.TimePeriods, rule=process_balance_rule1, doc='Input/output balance equation for Process 1 in the process network') def process_balance_rule2(model, t): - """_summary_ + """ + Represents the input/output balance equation for Process 2 in the process network. + + Process 2 has input Streams 5 and 3 and output Stream 10. Parameters ---------- @@ -768,14 +790,18 @@ def process_balance_rule2(model, t): Returns ------- Pyomo.Constraint - _description_ + A constraint that enforces the input/output balance equation for Process 2 in the process network. """ return model.FlowRate[10, t] == model.ProcessConstants[2] * \ (model.FlowRate[5, t] + model.FlowRate[3, t]) - model.process_balance2 = Constraint(model.TimePeriods, rule=process_balance_rule2) + model.process_balance2 = Constraint(model.TimePeriods, rule=process_balance_rule2, doc='Input/output balance equation for Process 2 in the process network') def process_balance_rule3(model, t): - """_summary_ + """ + Represents the input/output balance equation for Process 3 in the process network. + + Process 3 has input Stream 7 and outputs Streams 11 and 8. + RandomConst_Line264 is a hardcoded constant and determines the portion of Stream 7 that goes to Stream 8. Parameters ---------- @@ -787,14 +813,18 @@ def process_balance_rule3(model, t): Returns ------- Pyomo.Constraint - _description_ + A constraint that enforces the input/output balance equation for Process 3 in the process network. """ return model.FlowRate[8, t] == RandomConst_Line264 * \ model.ProcessConstants[3] * model.FlowRate[7, t] - model.process_balance3 = Constraint(model.TimePeriods, rule=process_balance_rule3) + model.process_balance3 = Constraint(model.TimePeriods, rule=process_balance_rule3, doc='Input/output balance equation 1 for Process 3 in the process network') def process_balance_rule4(model, t): - """_summary_ + """ + Represents the input/output balance equation for Process 3 in the process network. + + Process 3 has input Stream 7 and outputs Streams 11 and 8. + RandomConst_Line265 is a hardcoded constant and determines the portion of Stream 7 that goes to Stream 11. Parameters ---------- @@ -806,16 +836,19 @@ def process_balance_rule4(model, t): Returns ------- Pyomo.Constraint - _description_ + A constraint that enforces the input/output balance equation for Process 3 in the process network. """ return model.FlowRate[11, t] == RandomConst_Line265 * \ model.ProcessConstants[3] * model.FlowRate[7, t] - model.process_balance4 = Constraint(model.TimePeriods, rule=process_balance_rule4) + model.process_balance4 = Constraint(model.TimePeriods, rule=process_balance_rule4, doc='Input/output balance equation 2 for Process 3 in the process network') # process capacity contraints # these are hardcoded based on the three processes and the process flow structure def process_capacity_rule1(model, t): - """_summary_ + """ + Set the capacity constraint for Process 1 in the process network. + + Process 1 has a capacity constraint on Stream 9, which is the output stream of Process 1. Parameters ---------- @@ -827,13 +860,16 @@ def process_capacity_rule1(model, t): Returns ------- Pyomo.Constraint - _description_ + A constraint that enforces the capacity constraint for Process 1 in the process network. """ return model.FlowRate[9, t] <= model.Capacity[1] - model.process_capacity1 = Constraint(model.TimePeriods, rule=process_capacity_rule1) + model.process_capacity1 = Constraint(model.TimePeriods, rule=process_capacity_rule1, doc='Capacity constraint for Process 1 in the process network') def process_capacity_rule2(model, t): - """_summary_ + """ + Set the capacity constraint for Process 2 in the process network. + + Process 2 has a capacity constraint on Stream 10, which is the output stream of Process 2. Parameters ---------- @@ -845,13 +881,16 @@ def process_capacity_rule2(model, t): Returns ------- Pyomo.Constraint - _description_ + A constraint that enforces the capacity constraint for Process 2 in the process network. """ return model.FlowRate[10, t] <= model.Capacity[2] - model.process_capacity2 = Constraint(model.TimePeriods, rule=process_capacity_rule2) + model.process_capacity2 = Constraint(model.TimePeriods, rule=process_capacity_rule2, doc='Capacity constraint for Process 2 in the process network') def process_capacity_rule3(model, t): - """_summary_ + """ + Set the capacity constraint for Process 3 in the process network. + + Process 3 has capacity constraints on Streams 11 and 8, which are the output streams of Process 3. Parameters ---------- @@ -863,10 +902,10 @@ def process_capacity_rule3(model, t): Returns ------- Pyomo.Constraint - _description_ + A constraint that enforces the capacity constraint for Process 3 in the process network. """ return model.FlowRate[11, t] + model.FlowRate[8, t] <= model.Capacity[3] - model.process_capacity3 = Constraint(model.TimePeriods, rule=process_capacity_rule3) + model.process_capacity3 = Constraint(model.TimePeriods, rule=process_capacity_rule3, doc='Capacity constraint for Process 3 in the process network') # Inventory balance of final products # again, these are hardcoded. From c9d5556e2d0568efc96eaf4a2363eb4773b62578 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 11 Apr 2024 13:53:30 -0400 Subject: [PATCH 031/124] Added documentation of the Pyomo Constraints. --- gdplib/pyomo_examples/med_term_purchasing.py | 106 ++++++++++++------- 1 file changed, 66 insertions(+), 40 deletions(-) diff --git a/gdplib/pyomo_examples/med_term_purchasing.py b/gdplib/pyomo_examples/med_term_purchasing.py index b26462b..826cb18 100644 --- a/gdplib/pyomo_examples/med_term_purchasing.py +++ b/gdplib/pyomo_examples/med_term_purchasing.py @@ -331,7 +331,7 @@ def get_FP_bounds(model, j, t): Returns ------- - _type_ + tuple A tuple (0, upper_bound), where '0' is the lower bound (non-negative constraint) and 'upper_bound' is sourced from 'model.AmountPurchasedUB_FP[j,t]'. 'model.AmountPurchasedUB_FP[j,t]' represents the maximum allowed purchase amount for material 'j' in period 't' """ @@ -911,7 +911,11 @@ def process_capacity_rule3(model, t): # again, these are hardcoded. def inventory_balance1(model, t): - """_summary_ + """ + Maintains inventory balance for the material associated with stream 12 at the first inventory node across time periods. + + This constraint ensures that the inventory level of the material at the beginning of each time period t, combined with the incoming flow from stream 9, equals the sum of the outflow to the next process (or demand) represented by stream 12 and the inventory level at the end of the time period. For the initial time period, the previous inventory is assumed to be zero. + This balance is vital for tracking inventory levels accurately, allowing the model to make informed decisions about production, storage, and sales to maximize overall profit. Parameters ---------- @@ -922,15 +926,19 @@ def inventory_balance1(model, t): Returns ------- - _type_ - _description_ + Pyomo.Constraint + A constraint enforcing the balance of inventory levels for the material flowing through stream 12 at the first inventory node, taking into account the material inflows and outflows as well as changes in inventory from the previous to the current time period. """ prev = 0 if t == min(model.TimePeriods) else model.InventoryLevel[12, t-1] return prev + model.FlowRate[9, t] == model.FlowRate[12, t] + model.InventoryLevel[12,t] - model.inventory_balance1 = Constraint(model.TimePeriods, rule=inventory_balance1) + model.inventory_balance1 = Constraint(model.TimePeriods, rule=inventory_balance1, doc='Inventory balance for material associated with stream 12 at the first inventory node') def inventory_balance_rule2(model, t): - """_summary_ + """ + Ensures inventory balance for the material associated with stream 13 at the second inventory node for the first time period. + + This constraint is applied only to the first time period (t=1) and ensures that the sum of incoming flows from streams 10 and 11 equals the sum of the outflow to the next process represented by stream 13 and the inventory level at the end of the period. + For periods beyond the first, this constraint is skipped, as the balance for these periods may be governed by other conditions or constraints within the model. This selective application is crucial for accurately modeling the startup phase of the inventory system, where initial conditions significantly impact subsequent operations. Parameters ---------- @@ -941,17 +949,23 @@ def inventory_balance_rule2(model, t): Returns ------- - _type_ - _description_ + Pyomo.Constraint or Constraint.Skip + A constraint enforcing the inventory balance for the material flowing through stream 13 at the second inventory node during the first time period. + For all other periods, the function returns `Constraint.Skip`, indicating no constraint is applied. """ if t != 1: return Constraint.Skip return model.FlowRate[10, t] + model.FlowRate[11, t] == \ model.InventoryLevel[13,t] + model.FlowRate[13, t] - model.inventory_balance2 = Constraint(model.TimePeriods, rule=inventory_balance_rule2) + model.inventory_balance2 = Constraint(model.TimePeriods, rule=inventory_balance_rule2, doc='Inventory balance for material associated with stream 13 at the second inventory node') def inventory_balance_rule3(model, t): - """_summary_ + """ + Maintains the inventory balance for material associated with stream 13 at the second inventory node for all time periods after the first. + + This constraint is crucial for modeling the dynamic behavior of inventory levels over time, ensuring that the sum of the previous period's inventory level and the current period's inflows from streams 10 and 11 equals the current period's outflow (through stream 13) and ending inventory level. + It reflects the principle of inventory continuity, accounting for inflows, outflows, and storage from one period to the next. + The constraint is skipped for the first period (t=1) to accommodate initial conditions or startup behaviors specific to the model's context. Parameters ---------- @@ -962,18 +976,20 @@ def inventory_balance_rule3(model, t): Returns ------- - _type_ - _description_ + Pyomo.Constraint or Constraint.Skip + A constraint enforcing the inventory balance for the material flowing through stream 13 at the second inventory node from the second period onwards. + For the first period, the function returns `Constraint.Skip`, indicating the constraint does not apply. """ if t <= 1: return Constraint.Skip return model.InventoryLevel[13, t-1] + model.FlowRate[10, t] + \ model.FlowRate[11,t] == model.InventoryLevel[13, t] + model.FlowRate[13, t] - model.inventory_balance3 = Constraint(model.TimePeriods, rule=inventory_balance_rule3) + model.inventory_balance3 = Constraint(model.TimePeriods, rule=inventory_balance_rule3, doc='Inventory balance for material associated with stream 13 at the second inventory node') # Max capacities of inventories def inventory_capacity_rule(model, j, t): - """_summary_ + """ + Sets the maximum inventory capacity for each material j at each time period t. Parameters ---------- @@ -986,15 +1002,17 @@ def inventory_capacity_rule(model, j, t): Returns ------- - _type_ - _description_ + Pyomo.Constraint + A constraint that sets the maximum permissible inventory level for material j in time period t, ensuring that the inventory does not exceed the predefined upper bound 'InventoryLevelUB[j, t]'. + This maintains the model's alignment with practical storage limitations. """ return model.InventoryLevel[j,t] <= model.InventoryLevelUB[j,t] - model.inventory_capacity_rule = Constraint(model.Products, model.TimePeriods, rule=inventory_capacity_rule) + model.inventory_capacity_rule = Constraint(model.Products, model.TimePeriods, rule=inventory_capacity_rule, doc='Maximum inventory capacity for each material j at each time period t') # Shortfall calculation def shortfall_rule(model, j, t): - """_summary_ + """ + Calculates the shortfall for each product 'j' in each time period 't'. Parameters ---------- @@ -1007,15 +1025,17 @@ def shortfall_rule(model, j, t): Returns ------- - _type_ - _description_ + Pyomo.Constraint + A constraint defining the shortfall for product j during time period t as the difference between the supply or demand upper bound and the actual flow rate. + This calculation is pivotal for evaluating performance and identifying bottlenecks or excess capacities within the supply chain. """ return model.Shortfall[j, t] == model.SupplyAndDemandUBs[j, t] - model.FlowRate[j,t] - model.shortfall = Constraint(model.Products, model.TimePeriods, rule=shortfall_rule) + model.shortfall = Constraint(model.Products, model.TimePeriods, rule=shortfall_rule, doc='Shortfall calculation for each product j in each time period t') # maximum shortfall allowed def shortfall_max_rule(model, j, t): - """_summary_ + """ + Imposes an upper limit on the shortfall allowed for each product j in each time period t. Parameters ---------- @@ -1028,15 +1048,17 @@ def shortfall_max_rule(model, j, t): Returns ------- - _type_ - _description_ + Pyomo.Constraint + A constraint that limits the shortfall for product j during time period t to a maximum value specified by 'ShortfallUB[j, t]'. + This constraint is instrumental in aligning the model's solutions with real-world operational constraints and strategic objectives. """ return model.Shortfall[j, t] <= model.ShortfallUB[j, t] - model.shortfall_max = Constraint(model.Products, model.TimePeriods, rule=shortfall_max_rule) + model.shortfall_max = Constraint(model.Products, model.TimePeriods, rule=shortfall_max_rule, doc='Maximum shortfall allowed for each product j in each time period t') # maxiumum capacities of suppliers def supplier_capacity_rule(model, j, t): - """_summary_ + """ + Enforces the upper limits on the supply capacity for each raw material j provided by suppliers in each time period t. Parameters ---------- @@ -1049,15 +1071,17 @@ def supplier_capacity_rule(model, j, t): Returns ------- - _type_ - _description_ + Pyomo.Constraint + A constraint that limits the flow rate of raw material j from suppliers in time period t to not exceed the predefined upper bound 'SupplyAndDemandUBs[j, t]'. + This constraint is crucial for ensuring the feasibility of the supply chain model and its alignment with practical supply capabilities. """ return model.FlowRate[j, t] <= model.SupplyAndDemandUBs[j, t] - model.supplier_capacity = Constraint(model.RawMaterials, model.TimePeriods, rule=supplier_capacity_rule, doc='') + model.supplier_capacity = Constraint(model.RawMaterials, model.TimePeriods, rule=supplier_capacity_rule, doc='Maximum supply capacity for each raw material j in each time period t') # demand upper bound def demand_UB_rule(model, j, t): - """_summary_ + """ + Ensures that the supply of each product j does not exceed its maximum demand in each time period t. Parameters ---------- @@ -1070,14 +1094,16 @@ def demand_UB_rule(model, j, t): Returns ------- - _type_ - _description_ + Pyomo.Constraint + A constraint limiting the flow rate of product j to not exceed the predefined maximum demand 'SupplyAndDemandUBs[j, t]' in time period t, ensuring production is demand-driven. """ return model.FlowRate[j, t] <= model.SupplyAndDemandUBs[j,t] - model.demand_UB = Constraint(model.Products, model.TimePeriods, rule=demand_UB_rule) + model.demand_UB = Constraint(model.Products, model.TimePeriods, rule=demand_UB_rule, doc='Maximum demand allowed for each product j in each time period t') + # demand lower bound def demand_LB_rule(model, j, t): - """_summary_ + """ + Ensures that the supply of each product j meets at least the minimum demand in each time period t. Parameters ---------- @@ -1090,11 +1116,11 @@ def demand_LB_rule(model, j, t): Returns ------- - _type_ - _description_ + Pyomo.Constraint + A constraint ensuring that the flow rate of product j meets or exceeds the minimum demand 'DemandLB[j, t]' in time period t, supporting effective market engagement. """ return model.FlowRate[j, t] >= model.DemandLB[j,t] - model.demand_LB = Constraint(model.Products, model.TimePeriods, rule=demand_LB_rule) + model.demand_LB = Constraint(model.Products, model.TimePeriods, rule=demand_LB_rule, doc='Minimum demand required for each product j in each time period t') # FIXED PRICE CONTRACT @@ -1137,7 +1163,7 @@ def FP_contract_rule(model, j, t): Returns ------- - _type_ + Pyomo.Constraint _description_ """ return [model.FP_contract_disjunct[j,t,buy] for buy in model.BuyFPContract] @@ -1159,7 +1185,7 @@ def FP_contract_cost_rule(model, j, t): Returns ------- - _type_ + Pyomo.Constraint _description_ """ return model.Cost_FP[j,t] == model.AmountPurchased_FP[j,t] * \ @@ -1251,7 +1277,7 @@ def discount_cost_rule(model, j, t): Returns ------- - _type_ + Pyomo.Constraint _description_ """ return model.Cost_Discount[j,t] == model.RegPrice_Discount[j,t] * \ From 804b908787e2358525c77f3199e700bddb634078 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 11 Apr 2024 14:13:33 -0400 Subject: [PATCH 032/124] Added Documentation of the Fixed Price Disjunction. --- gdplib/pyomo_examples/med_term_purchasing.py | 34 ++++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/gdplib/pyomo_examples/med_term_purchasing.py b/gdplib/pyomo_examples/med_term_purchasing.py index 826cb18..86b360e 100644 --- a/gdplib/pyomo_examples/med_term_purchasing.py +++ b/gdplib/pyomo_examples/med_term_purchasing.py @@ -1127,18 +1127,29 @@ def demand_LB_rule(model, j, t): # Disjunction for Fixed Price contract buying options def FP_contract_disjunct_rule(disjunct, j, t, buy): - """_summary_ + """ + Defines disjunctive constraints for procurement decisions under a Fixed Price (FP) contract for material j in time period t. + + A decision must be made whether to engage in purchasing under the contract terms or not for each material in each time period. + This function encapsulates the disjunctive nature of this decision: if the decision is to buy ('buy' parameter is True), + the amount purchased under the FP contract is limited by a predefined maximum ('MAX_AMOUNT_FP'); + otherwise, no purchase is made under the FP contract for that material and period. + This disjunctive approach allows for modeling complex decision-making processes in procurement strategies. Parameters ---------- disjunct : Pyomo.Disjunct - _description_ + A Pyomo Disjunct object representing a part of the disjunction. It encapsulates the constraints that are valid under a specific scenario ('buy' or not buy). j : int Index of materials. t : int Index of time period. buy : str - Decision parameter that indicates whether to buy under the fixed price contract or not. + A decision parameter indicating whether to purchase ('buy' is True) under the FP contract or not ('buy' is False) for material j in time period t + + Notes + ----- + The 'buy' parameter is treated as a binary variable in the context of the model, where True indicates a decision to engage in purchasing under the FP contract, and False indicates otherwise. """ model = disjunct.model() if buy: @@ -1146,11 +1157,14 @@ def FP_contract_disjunct_rule(disjunct, j, t, buy): else: disjunct.c = Constraint(expr=model.AmountPurchased_FP[j,t] == 0) model.FP_contract_disjunct = Disjunct(model.RawMaterials, model.TimePeriods, - model.BuyFPContract, rule=FP_contract_disjunct_rule) + model.BuyFPContract, rule=FP_contract_disjunct_rule, doc='Disjunctive constraints for Fixed Price contract buying options') # Fixed price disjunction def FP_contract_rule(model, j, t): - """_summary_ + """ + Creates a choice between buying or not buying materials under a Fixed Price contract for each material 'j' and time 't'. + + This function sets up a disjunction, which is like a crossroads for the model: for each material and time period, it can choose one of the paths defined in the 'FP_contract_disjunct_rule'. Parameters ---------- @@ -1163,12 +1177,12 @@ def FP_contract_rule(model, j, t): Returns ------- - Pyomo.Constraint - _description_ + Pyomo.Disjunction + A disjunction that represents the decision-making point for FP contract purchases, contributing to the model's overall procurement strategy. """ return [model.FP_contract_disjunct[j,t,buy] for buy in model.BuyFPContract] model.FP_disjunction = Disjunction(model.RawMaterials, model.TimePeriods, - rule=FP_contract_rule) + rule=FP_contract_rule, doc='Disjunction for Fixed Price contract buying options') # cost constraint for fixed price contract (independent constraint) def FP_contract_cost_rule(model, j, t): @@ -1186,12 +1200,12 @@ def FP_contract_cost_rule(model, j, t): Returns ------- Pyomo.Constraint - _description_ + A constraint equating the FP contract cost for material j in period t to the product of the purchased amount and its price, ensuring accurate financial accounting in the model. """ return model.Cost_FP[j,t] == model.AmountPurchased_FP[j,t] * \ model.Prices[j,t] model.FP_contract_cost = Constraint(model.RawMaterials, model.TimePeriods, - rule=FP_contract_cost_rule) + rule=FP_contract_cost_rule, doc='Cost constraint for Fixed Price contract') # DISCOUNT CONTRACT From b6d7071d85a4769fc41a776cdfb609bbacf4848a Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 11 Apr 2024 14:18:57 -0400 Subject: [PATCH 033/124] Disjunctions for Discount Constract Documentation. --- gdplib/pyomo_examples/med_term_purchasing.py | 40 +++++++++++--------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/gdplib/pyomo_examples/med_term_purchasing.py b/gdplib/pyomo_examples/med_term_purchasing.py index 86b360e..cdf9119 100644 --- a/gdplib/pyomo_examples/med_term_purchasing.py +++ b/gdplib/pyomo_examples/med_term_purchasing.py @@ -1147,7 +1147,7 @@ def FP_contract_disjunct_rule(disjunct, j, t, buy): buy : str A decision parameter indicating whether to purchase ('buy' is True) under the FP contract or not ('buy' is False) for material j in time period t - Notes + Notes ----- The 'buy' parameter is treated as a binary variable in the context of the model, where True indicates a decision to engage in purchasing under the FP contract, and False indicates otherwise. """ @@ -1212,7 +1212,11 @@ def FP_contract_cost_rule(model, j, t): # Disjunction for Discount contract def discount_contract_disjunct_rule(disjunct, j, t, buy): - """_summary_ + """ + Sets rules for purchasing materials j in time t under discount contracts based on the buying decision 'buy'. + + For discount contracts, the decision involves purchasing below or above a minimum amount for a discount, or not selecting the contract at all. + This rule reflects these choices by adjusting purchasing amounts and enforcing corresponding constraints. Parameters ---------- @@ -1223,12 +1227,8 @@ def discount_contract_disjunct_rule(disjunct, j, t, buy): t : int Index of time period. buy : str - Decision parameter that indicates whether to buy under the fixed price contract or not. - - Raises - ------ - RuntimeError - _description_ + Decision on purchasing strategy: 'BelowMin', 'AboveMin', or 'NotSelected'. + """ model = disjunct.model() if buy == 'BelowMin': @@ -1251,11 +1251,14 @@ def discount_contract_disjunct_rule(disjunct, j, t, buy): else: raise RuntimeError("Unrecognized choice for discount contract: %s" % buy) model.discount_contract_disjunct = Disjunct(model.RawMaterials, model.TimePeriods, - model.BuyDiscountContract, rule=discount_contract_disjunct_rule) + model.BuyDiscountContract, rule=discount_contract_disjunct_rule, doc='Disjunctive constraints for Discount contract buying options') # Discount contract disjunction def discount_contract_rule(model, j, t): - """_summary_ + """ + Determines the disjunction for purchasing under discount contracts for each material and time period, based on available decisions. + + This function sets up the model to choose among different discount purchasing strategies, enhancing the flexibility in procurement planning. Parameters ---------- @@ -1268,17 +1271,20 @@ def discount_contract_rule(model, j, t): Returns ------- - _type_ - _description_ + Pyomo.Disjunction + The disjunction representing the choice among discount contract purchasing strategies. """ return [model.discount_contract_disjunct[j,t,buy] \ for buy in model.BuyDiscountContract] model.discount_contract = Disjunction(model.RawMaterials, model.TimePeriods, - rule=discount_contract_rule) + rule=discount_contract_rule, doc='Disjunction for Discount contract buying options') # cost constraint for discount contract (independent constraint) def discount_cost_rule(model, j, t): - """_summary_ + """ + Calculates the cost of purchasing material 'j' in time 't' under a discount contract, accounting for different price levels. + + This constraint ensures the model correctly accounts for the total cost of purchases under discount contracts, which may involve different prices based on quantity thresholds. Parameters ---------- @@ -1292,13 +1298,13 @@ def discount_cost_rule(model, j, t): Returns ------- Pyomo.Constraint - _description_ + The constraint that calculates the total cost of purchases under discount contracts. """ return model.Cost_Discount[j,t] == model.RegPrice_Discount[j,t] * \ model.AmountPurchasedBelowMin_Discount[j,t] + \ model.DiscountPrice_Discount[j,t] * model.AmountPurchasedAboveMin_Discount[j,t] model.discount_cost = Constraint(model.RawMaterials, model.TimePeriods, - rule=discount_cost_rule) + rule=discount_cost_rule, doc='Cost constraint for Discount contract') # BULK CONTRACT @@ -1316,7 +1322,7 @@ def bulk_contract_disjunct_rule(disjunct, j, t, buy): t : int Index of time period. buy : str - Decision parameter that indicates whether to buy under the fixed price contract or not. + _description_ Raises ------ From 37d961e3f406d3b2ddd3771da43dcb7fd1b65e0d Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 11 Apr 2024 14:26:28 -0400 Subject: [PATCH 034/124] Bulk Contract Disjunction Documented. --- gdplib/pyomo_examples/med_term_purchasing.py | 31 +++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/gdplib/pyomo_examples/med_term_purchasing.py b/gdplib/pyomo_examples/med_term_purchasing.py index cdf9119..0a96610 100644 --- a/gdplib/pyomo_examples/med_term_purchasing.py +++ b/gdplib/pyomo_examples/med_term_purchasing.py @@ -1221,8 +1221,7 @@ def discount_contract_disjunct_rule(disjunct, j, t, buy): Parameters ---------- disjunct : Pyomo.Disjunct - _description_ - j : int + A Pyomo Disjunct object representing a part of the disjunction. It encapsulates the constraints that are valid under a specific scenario ('BelowMin', 'AboveMin', or 'NotSelected'). Index of materials. t : int Index of time period. @@ -1311,23 +1310,23 @@ def discount_cost_rule(model, j, t): # Bulk contract buying options disjunct def bulk_contract_disjunct_rule(disjunct, j, t, buy): - """_summary_ + """ + Defines conditions for bulk purchases of material j at time t based on the decision 'buy'. + + This rule determines how much of a material is bought under a bulk contract and at what price, based on whether purchases are below or above a specified minimum amount, or if the bulk option is not selected. + It enforces different constraints for the amount and cost of materials under these scenarios. Parameters ---------- disjunct : Pyomo.Disjunct - -description_ + A Pyomo Disjunct object representing a part of the disjunction. It encapsulates the constraints that are valid under a specific scenario ('BelowMin', 'AboveMin', or 'NotSelected'). j : int Index of materials. t : int Index of time period. buy : str - _description_ + The decision on how to engage with the bulk contract: 'BelowMin', 'AboveMin', or 'NotSelected'. - Raises - ------ - RuntimeError - _description_ """ model = disjunct.model() if buy == 'BelowMin': @@ -1348,11 +1347,15 @@ def bulk_contract_disjunct_rule(disjunct, j, t, buy): else: raise RuntimeError("Unrecognized choice for bulk contract: %s" % buy) model.bulk_contract_disjunct = Disjunct(model.RawMaterials, model.TimePeriods, - model.BuyBulkContract, rule=bulk_contract_disjunct_rule) + model.BuyBulkContract, rule=bulk_contract_disjunct_rule, doc='Disjunctive constraints for Bulk contract buying options') # Bulk contract disjunction def bulk_contract_rule(model, j, t): - """_summary_ + """ + Establishes a decision-making framework for bulk purchases, allowing the model to choose among predefined scenarios. + + This function sets up a flexible structure for deciding on bulk purchases. + Each material and time period can be evaluated independently, allowing the model to adapt to various conditions and optimize procurement strategies under bulk contracts. Parameters ---------- @@ -1365,12 +1368,12 @@ def bulk_contract_rule(model, j, t): Returns ------- - _type_ - _description_ + Pyomo.Disjunction + A set of disjunctive conditions that the model can choose from when making bulk purchasing decisions. """ return [model.bulk_contract_disjunct[j,t,buy] for buy in model.BuyBulkContract] model.bulk_contract = Disjunction(model.RawMaterials, model.TimePeriods, - rule=bulk_contract_rule) + rule=bulk_contract_rule, doc='Disjunction for Bulk contract buying options') # FIXED DURATION CONTRACT From 7a2bc1fe0cf6687a9b44d2544068455c2a02647b Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 11 Apr 2024 14:32:20 -0400 Subject: [PATCH 035/124] Fixed Duration Contract Disjunction Documentation. --- gdplib/pyomo_examples/med_term_purchasing.py | 48 ++++++++++++-------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/gdplib/pyomo_examples/med_term_purchasing.py b/gdplib/pyomo_examples/med_term_purchasing.py index 0a96610..ad7f47c 100644 --- a/gdplib/pyomo_examples/med_term_purchasing.py +++ b/gdplib/pyomo_examples/med_term_purchasing.py @@ -1379,12 +1379,14 @@ def bulk_contract_rule(model, j, t): # FIXED DURATION CONTRACT def FD_1mo_contract(disjunct, j, t): - """_summary_ + """ + Defines the constraints for engaging in a 1-month fixed duration contract for material j at time t. + This includes a minimum purchase amount and the cost calculation based on contract-specific prices. Parameters ---------- - disjunct : Index - _description_ + disjunct : Pyomo.Disjunct + A component representing the 1-month contract scenario. j : int Index of materials. t : int @@ -1396,15 +1398,17 @@ def FD_1mo_contract(disjunct, j, t): disjunct.price1 = Constraint(expr=model.Cost_FD[j,t] == \ model.Prices_Length[j,1,t] * model.AmountPurchased_FD[j,t]) model.FD_1mo_contract = Disjunct( - model.RawMaterials, model.TimePeriods, rule=FD_1mo_contract) + model.RawMaterials, model.TimePeriods, rule=FD_1mo_contract, doc='1-month fixed duration contract') def FD_2mo_contract(disjunct, j, t): - """_summary_ + """ + Establishes conditions for a 2-month fixed duration contract. + This involves a minimum purchase requirement for two consecutive periods and corresponding cost calculations. Parameters ---------- - disjunct : Index - _description_ + disjunct : Pyomo.Disjunct + The 2-month contract scenario component j : int Index of materials. t : int @@ -1422,15 +1426,16 @@ def FD_2mo_contract(disjunct, j, t): disjunct.price2 = Constraint(expr=model.Cost_FD[j,t+1] == \ model.Prices_Length[j,2,t] * model.AmountPurchased_FD[j, t+1]) model.FD_2mo_contract = Disjunct( - model.RawMaterials, model.TimePeriods, rule=FD_2mo_contract) + model.RawMaterials, model.TimePeriods, rule=FD_2mo_contract, doc='2-month fixed duration contract') def FD_3mo_contract(disjunct, j, t): - """_summary_ + """ + Sets up a 3-month fixed duration contract scenario with minimum purchase requirements extending over three periods and the cost calculation. Parameters ---------- - disjunct : Index - _description_ + disjunct : Pyomo.Disjunct + The 3-month contract scenario component. j : int Index of materials. t : int @@ -1456,15 +1461,17 @@ def FD_3mo_contract(disjunct, j, t): disjunct.cost3 = Constraint(expr=model.Cost_FD[j,t+2] == \ model.Prices_Length[j,3,t] * model.AmountPurchased_FD[j,t+2]) model.FD_3mo_contract = Disjunct( - model.RawMaterials, model.TimePeriods, rule=FD_3mo_contract) + model.RawMaterials, model.TimePeriods, rule=FD_3mo_contract, doc='3-month fixed duration contract') def FD_no_contract(disjunct, j, t): - """_summary_ + """ + Represents the scenario where no fixed duration contract is selected for material j at time t. + Ensures no purchases or costs are accounted for under FD contracts. Parameters ---------- - disjunct : Index - _description_ + disjunct : Pyomo.Disjunct + The 'no contract' scenario component. j : int Index of materials. t : int @@ -1480,10 +1487,11 @@ def FD_no_contract(disjunct, j, t): disjunct.amount3 = Constraint(expr=model.AmountPurchased_FD[j,t+2] == 0) disjunct.cost3 = Constraint(expr=model.Cost_FD[j,t+2] == 0) model.FD_no_contract = Disjunct( - model.RawMaterials, model.TimePeriods, rule=FD_no_contract) + model.RawMaterials, model.TimePeriods, rule=FD_no_contract, doc='No fixed duration contract') def FD_contract(model, j, t): - """_summary_ + """ + Consolidates the FD contract scenarios into a single decision framework, allowing the model to choose the most optimal contract length or to not select an FD contract for each material and time period. Parameters ---------- @@ -1496,13 +1504,13 @@ def FD_contract(model, j, t): Returns ------- - _type_ - _description_ + Pyomo.Disjunction + The disjunctive decision structure for FD contracts. """ return [ model.FD_1mo_contract[j,t], model.FD_2mo_contract[j,t], model.FD_3mo_contract[j,t], model.FD_no_contract[j,t], ] model.FD_contract = Disjunction(model.RawMaterials, model.TimePeriods, - rule=FD_contract) + rule=FD_contract, doc='Fixed duration contract scenarios') return model From e6ea4227d586a35f329c7655cf64d95b36003ea0 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 11 Apr 2024 21:32:44 -0400 Subject: [PATCH 036/124] Added documentation of positioning --- gdplib/logical/positioning.py | 42 +++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/gdplib/logical/positioning.py b/gdplib/logical/positioning.py index b9e610c..a34c8af 100644 --- a/gdplib/logical/positioning.py +++ b/gdplib/logical/positioning.py @@ -23,10 +23,22 @@ def build_model(): + """ + Build the optimal positioning model + + Returns + ------- + m : Pyomo.ConcreteModel + The optimal positioning model + + Notes + ----- + + """ m = ConcreteModel() - m.locations = RangeSet(5) - m.consumers = RangeSet(25) - m.products = RangeSet(10) + m.locations = RangeSet(5) # 5 locations + m.consumers = RangeSet(25) # 25 consumers + m.products = RangeSet(10) # 10 products fixed_profit_data = { 1: 1, @@ -55,7 +67,7 @@ def build_model(): 24: 0.7, 25: 0.7, } - m.fixed_profit = Param(m.consumers, initialize=fixed_profit_data) + m.fixed_profit = Param(m.consumers, initialize=fixed_profit_data, doc='Fixed profit for each consumer') # fixed profit for each consumer minimum_weights_data = { 1: 77.84, @@ -84,7 +96,9 @@ def build_model(): 24: 340.581, 25: 407.52, } - m.minimum_weights = Param(m.consumers, initialize=minimum_weights_data) + m.minimum_weights = Param(m.consumers, initialize=minimum_weights_data, doc='Minimum weights for each consumer') # minimum weights for each consumer + + # Bounds for the locations location_bounds = { 1: (2, 4.5), 2: (0, 8.0), @@ -121,8 +135,8 @@ def build_model(): 24 8.79 5.04 4.83 6.94 0.38 25 2.66 4.19 6.49 8.04 1.66 """) - ideal_points_table = pd.read_csv(ideal_points_data, delimiter=r'\s+') - ideal_points_dict = {(k[0], int(k[1])): v for k, v in ideal_points_table.stack().to_dict().items()} + ideal_points_table = pd.read_csv(ideal_points_data, delimiter=r'\s+') # ideal points for each consumer and product + ideal_points_dict = {(k[0], int(k[1])): v for k, v in ideal_points_table.stack().to_dict().items()} m.ideal_points = Param(m.consumers, m.locations, initialize=ideal_points_dict) weights_data = StringIO(""" @@ -188,6 +202,20 @@ def build_model(): @m.Disjunction(m.consumers) def d(m, i): + """_summary_ + + Parameters + ---------- + m : Pyomo.ConcreteModel + _description_ + i : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ return [ [sum(m.weights[i, k] * (m.x[k] - m.ideal_points[i, k]) ** 2 for k in m.locations) - r[i] <= m.U], [] From 9df7ceda02ca3614bcfa06e6e8191deab6470ff7 Mon Sep 17 00:00:00 2001 From: Carolina Tristan Date: Tue, 16 Apr 2024 18:21:33 -0400 Subject: [PATCH 037/124] Update documentation link for Medium-term Purchasing Contracts problem --- gdplib/pyomo_examples/med_term_purchasing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gdplib/pyomo_examples/med_term_purchasing.py b/gdplib/pyomo_examples/med_term_purchasing.py index ad7f47c..7bb7f31 100644 --- a/gdplib/pyomo_examples/med_term_purchasing.py +++ b/gdplib/pyomo_examples/med_term_purchasing.py @@ -13,7 +13,7 @@ from pyomo.common.fileutils import this_file_dir from os.path import join -# Medium-term Purchasing Contracts problem from http://minlp.org/library/lib.php?lib=GDP +# Medium-term Purchasing Contracts problem from https://www.minlp.org/library/problem/index.php?i=129 # This model maximizes profit in a short-term horizon in which various contracts # are available for purchasing raw materials. The model decides inventory levels, # amounts to purchase, amount sold, and flows through the process nodes while From 2818b4374e1afb65eb0e5c004cac1275c751d753 Mon Sep 17 00:00:00 2001 From: Carolina Tristan Date: Tue, 16 Apr 2024 18:23:44 -0400 Subject: [PATCH 038/124] Update copyright year in med_term_purchasing.py --- gdplib/pyomo_examples/med_term_purchasing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gdplib/pyomo_examples/med_term_purchasing.py b/gdplib/pyomo_examples/med_term_purchasing.py index 7bb7f31..af77c49 100644 --- a/gdplib/pyomo_examples/med_term_purchasing.py +++ b/gdplib/pyomo_examples/med_term_purchasing.py @@ -1,7 +1,8 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC +# Copyright (c) 2008-2024 +# National Technology and Engineering Solutions of Sandia, LLC # Under the terms of Contract DE-NA0003525 with National Technology and # Engineering Solutions of Sandia, LLC, the U.S. Government retains certain # rights in this software. From d153238eac297259051ce79379093a9ca94ecdcf Mon Sep 17 00:00:00 2001 From: Carolina Tristan Date: Fri, 19 Apr 2024 18:07:49 -0400 Subject: [PATCH 039/124] Update references in med_term_purchasing.py --- gdplib/pyomo_examples/med_term_purchasing.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gdplib/pyomo_examples/med_term_purchasing.py b/gdplib/pyomo_examples/med_term_purchasing.py index af77c49..774c50b 100644 --- a/gdplib/pyomo_examples/med_term_purchasing.py +++ b/gdplib/pyomo_examples/med_term_purchasing.py @@ -49,9 +49,9 @@ def build_model(): References ---------- - [1] Vecchietti, Aldo, and I. Grossmann. "Computational experience with logmip solving linear and nonlinear disjunctive programming problems." In Proc. of FOCAPD, pp. 587-590. 2004. - [2] Vecchietti, A., S. Lee and I.E. Grossmann, “Modeling of Discrete/Continuous Optimization Problems: Characterization and Formulation of Disjunctions and their Relaxations,” - Computers and Chemical Engineering 27, 433-448 (2003). + [1] Vecchietti, A., & Grossmann, I. (2004). Computational experience with logmip solving linear and nonlinear disjunctive programming problems. Proc. of FOCAPD, 587–590. + [2] Vecchietti, A., Lee, S., & Grossmann, I. E. (2003). Modeling of discrete/continuous optimization problems: characterization and formulation of disjunctions and their relaxations. Computers & Chemical Engineering, 27(3), 433–448. DOI: 10.1016/S0098-1354(02)00220-X + [3] 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() From f43c5e7d863abf722d4e3c4c0654f26d4b7cf560 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Fri, 19 Apr 2024 19:04:00 -0400 Subject: [PATCH 040/124] Update positioning model with documentation and references --- gdplib/logical/positioning.py | 51 ++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/gdplib/logical/positioning.py b/gdplib/logical/positioning.py index a34c8af..bfa3a91 100644 --- a/gdplib/logical/positioning.py +++ b/gdplib/logical/positioning.py @@ -24,16 +24,21 @@ def build_model(): """ - Build the optimal positioning model + Constructs and returns a Pyomo ConcreteModel object configured to solve the optimal product positioning problem. The model seeks to position a new product in a way that optimizes its location in a multidimensional attribute space, balancing consumer satisfaction against production and positioning costs. Returns ------- m : Pyomo.ConcreteModel - The optimal positioning model + The optimal positioning model that includes variables for product locations, consumer satisfaction indicators, and the constraints and objective function necessary for finding the optimal product positioning strategy. Notes ----- + The model uses a disjunctive programming approach to handle the binary nature of consumer decisions and includes quadratic cost functions to represent varying cost behaviors associated with different product positioning strategies. + References + ---------- + [1] Duran, M. A., & Grossmann, I. E. (1986). An outer-approximation algorithm for a class of mixed-integer nonlinear programs. Mathematical programming, 36, 307-339. https://doi.org/10.1007/BF02592064 + [2] Gavish, B., Horsky, D., & Srikanth, K. (1983). An approach to the optimal positioning of a new product. Management Science, 29(11), 1277-1297. https://doi.org/10.1287/mnsc.29.11.1277 """ m = ConcreteModel() m.locations = RangeSet(5) # 5 locations @@ -137,7 +142,7 @@ def build_model(): """) ideal_points_table = pd.read_csv(ideal_points_data, delimiter=r'\s+') # ideal points for each consumer and product ideal_points_dict = {(k[0], int(k[1])): v for k, v in ideal_points_table.stack().to_dict().items()} - m.ideal_points = Param(m.consumers, m.locations, initialize=ideal_points_dict) + m.ideal_points = Param(m.consumers, m.locations, initialize=ideal_points_dict, doc='Ideal points for each consumer and product') weights_data = StringIO(""" 1 2 3 4 5 @@ -169,7 +174,7 @@ def build_model(): """) weights_table = pd.read_csv(weights_data, delimiter=r'\s+') weights_dict = {(k[0], int(k[1])): v for k, v in weights_table.stack().to_dict().items()} - m.weights = Param(m.consumers, m.locations, initialize=weights_dict) + m.weights = Param(m.consumers, m.locations, initialize=weights_dict, doc='Weights for each consumer and product') existing_products_data = StringIO(""" 1 2 3 4 5 @@ -186,35 +191,38 @@ def build_model(): """) existing_products_table = pd.read_csv(existing_products_data, delimiter=r'\s+') existing_products_dict = {(k[0], int(k[1])): v for k, v in existing_products_table.stack().to_dict().items()} - m.existing_products = Param(m.products, m.locations, initialize=existing_products_dict) + m.existing_products = Param(m.products, m.locations, initialize=existing_products_dict, doc='Existing products for each location and product') # m.consumers * m.products + # Squared weighted Euclidean distance between the existing products and the ideal points rr = {(i, j): sum(m.weights[i, k] * (m.existing_products[j, k] - m.ideal_points[i, k]) * ( m.existing_products[j, k] - m.ideal_points[i, k]) for k in m.locations) for (i, j) in m.consumers * m.products} + # minimum dissimilarity between the existing products and consumers' ideal points r = {i: min(rr[i, j] for j in m.products) for i in m.consumers} - m.x = Var(m.locations) - m.Y = BooleanVar(m.consumers) - m.H = Param(initialize=1000) - m.U = Var(bounds=(0, 5000)) + m.x = Var(m.locations, doc='Location of each product') + m.Y = BooleanVar(m.consumers, doc='Indicates if consumer positioning is satisfactory based on distance to ideal points.') + m.H = Param(initialize=1000, doc='Big M value') + m.U = Var(bounds=(0, 5000), doc='Upper bound on the sum of the distances to the ideal points') # Slack variable @m.Disjunction(m.consumers) def d(m, i): - """_summary_ + """ + Define the disjunction that model whether the distance squared sum of weights and deviations from ideal points for consumer i is less than or equal to a utility threshold (r[i]) adjusted by a slack variable m.U. Parameters ---------- m : Pyomo.ConcreteModel - _description_ - i : _type_ - _description_ + The optimal positioning model + i : int + Index for consumers Returns ------- - _type_ - _description_ + Pyomo.Disjunction + A Pyomo Disjunction object that evaluates if the consumer's preference is met (`true` scenario) with a single Pyomo expression, or not met (`false` scenario) where the list is empty. """ return [ [sum(m.weights[i, k] * (m.x[k] - m.ideal_points[i, k]) ** 2 for k in m.locations) - r[i] <= m.U], @@ -227,15 +235,16 @@ def d(m, i): m.x[k].setlb(lb) m.x[k].setub(ub) - m.c1 = Constraint(expr=m.x[1] - m.x[2] + m.x[3] + m.x[4] + m.x[5] <= 10) - m.c2 = Constraint(expr=0.6 * m.x[1] - 0.9 * m.x[2] - 0.5 * m.x[3] + 0.1 * m.x[4] + m.x[5] <= -0.64) - m.c3 = Constraint(expr=m.x[1] - m.x[2] + m.x[3] - m.x[4] + m.x[5] >= 0.69) - m.c4 = Constraint(expr=0.157 * m.x[1] + 0.05 * m.x[2] <= 1.5) - m.c5 = Constraint(expr=0.25 * m.x[2] + 1.05 * m.x[4] - 0.3 * m.x[5] >= 4.5) + m.c1 = Constraint(expr=m.x[1] - m.x[2] + m.x[3] + m.x[4] + m.x[5] <= 10, doc='Constraint 1') + m.c2 = Constraint(expr=0.6 * m.x[1] - 0.9 * m.x[2] - 0.5 * m.x[3] + 0.1 * m.x[4] + m.x[5] <= -0.64, doc='Constraint 2') + m.c3 = Constraint(expr=m.x[1] - m.x[2] + m.x[3] - m.x[4] + m.x[5] >= 0.69, doc='Constraint 3') + m.c4 = Constraint(expr=0.157 * m.x[1] + 0.05 * m.x[2] <= 1.5, doc='Constraint 4') + m.c5 = Constraint(expr=0.25 * m.x[2] + 1.05 * m.x[4] - 0.3 * m.x[5] >= 4.5, doc='Constraint 5') + # Minimizes total adjusted dissimilarity costs and maximizes consumer-based fixed profit, while balancing quadratic and linear operational costs across multiple product locations. m.obj = Objective( expr=10 * m.U - sum(m.fixed_profit[i] * m.Y[i].get_associated_binary() for i in m.consumers) + 0.6 * m.x[1] ** 2 - 0.9 * m.x[ - 2] - 0.5 * m.x[3] + 0.1 * m.x[4] ** 2 + m.x[5]) + 2] - 0.5 * m.x[3] + 0.1 * m.x[4] ** 2 + m.x[5], doc='Objective function') return m From 5af8182ae5c44e8ace1a4c51164914e40c83b344 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Fri, 19 Apr 2024 20:23:16 -0400 Subject: [PATCH 041/124] 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 d941ddd0b50ad24fd29761678e0034c365ef6ef9 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Fri, 19 Apr 2024 20:24:48 -0400 Subject: [PATCH 042/124] Black Formatting --- gdplib/logical/positioning.py | 156 ++++++++++++++++++++++++---------- 1 file changed, 113 insertions(+), 43 deletions(-) diff --git a/gdplib/logical/positioning.py b/gdplib/logical/positioning.py index bfa3a91..8047c21 100644 --- a/gdplib/logical/positioning.py +++ b/gdplib/logical/positioning.py @@ -16,7 +16,9 @@ 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 @@ -30,7 +32,7 @@ def build_model(): ------- m : Pyomo.ConcreteModel The optimal positioning model that includes variables for product locations, consumer satisfaction indicators, and the constraints and objective function necessary for finding the optimal product positioning strategy. - + Notes ----- The model uses a disjunctive programming approach to handle the binary nature of consumer decisions and includes quadratic cost functions to represent varying cost behaviors associated with different product positioning strategies. @@ -41,9 +43,9 @@ def build_model(): [2] Gavish, B., Horsky, D., & Srikanth, K. (1983). An approach to the optimal positioning of a new product. Management Science, 29(11), 1277-1297. https://doi.org/10.1287/mnsc.29.11.1277 """ m = ConcreteModel() - m.locations = RangeSet(5) # 5 locations - m.consumers = RangeSet(25) # 25 consumers - m.products = RangeSet(10) # 10 products + m.locations = RangeSet(5) # 5 locations + m.consumers = RangeSet(25) # 25 consumers + m.products = RangeSet(10) # 10 products fixed_profit_data = { 1: 1, @@ -72,7 +74,9 @@ def build_model(): 24: 0.7, 25: 0.7, } - m.fixed_profit = Param(m.consumers, initialize=fixed_profit_data, doc='Fixed profit for each consumer') # fixed profit for each consumer + m.fixed_profit = Param( + m.consumers, initialize=fixed_profit_data, doc='Fixed profit for each consumer' + ) # fixed profit for each consumer minimum_weights_data = { 1: 77.84, @@ -101,18 +105,17 @@ def build_model(): 24: 340.581, 25: 407.52, } - m.minimum_weights = Param(m.consumers, initialize=minimum_weights_data, doc='Minimum weights for each consumer') # minimum weights for each consumer + m.minimum_weights = Param( + m.consumers, + initialize=minimum_weights_data, + doc='Minimum weights for each consumer', + ) # minimum weights for each consumer # Bounds for the locations - location_bounds = { - 1: (2, 4.5), - 2: (0, 8.0), - 3: (3, 9.0), - 4: (0, 5.0), - 5: (4, 10), - } + location_bounds = {1: (2, 4.5), 2: (0, 8.0), 3: (3, 9.0), 4: (0, 5.0), 5: (4, 10)} - ideal_points_data = StringIO(""" + ideal_points_data = StringIO( + """ 1 2 3 4 5 1 2.26 5.15 4.03 1.74 4.74 2 5.51 9.01 3.84 1.47 9.92 @@ -139,12 +142,23 @@ def build_model(): 23 1.37 0.54 1.55 5.56 5.85 24 8.79 5.04 4.83 6.94 0.38 25 2.66 4.19 6.49 8.04 1.66 - """) - ideal_points_table = pd.read_csv(ideal_points_data, delimiter=r'\s+') # ideal points for each consumer and product - ideal_points_dict = {(k[0], int(k[1])): v for k, v in ideal_points_table.stack().to_dict().items()} - m.ideal_points = Param(m.consumers, m.locations, initialize=ideal_points_dict, doc='Ideal points for each consumer and product') + """ + ) + ideal_points_table = pd.read_csv( + ideal_points_data, delimiter=r'\s+' + ) # ideal points for each consumer and product + ideal_points_dict = { + (k[0], int(k[1])): v for k, v in ideal_points_table.stack().to_dict().items() + } + m.ideal_points = Param( + m.consumers, + m.locations, + initialize=ideal_points_dict, + doc='Ideal points for each consumer and product', + ) - weights_data = StringIO(""" + weights_data = StringIO( + """ 1 2 3 4 5 1 9.57 2.74 9.75 3.96 8.67 2 8.38 3.93 5.18 5.2 7.82 @@ -171,12 +185,21 @@ def build_model(): 23 1.47 5.71 6.95 1.42 3.49 24 5.4 3.12 5.37 6.1 3.71 25 6.32 0.81 6.12 6.73 7.93 - """) + """ + ) weights_table = pd.read_csv(weights_data, delimiter=r'\s+') - weights_dict = {(k[0], int(k[1])): v for k, v in weights_table.stack().to_dict().items()} - m.weights = Param(m.consumers, m.locations, initialize=weights_dict, doc='Weights for each consumer and product') + weights_dict = { + (k[0], int(k[1])): v for k, v in weights_table.stack().to_dict().items() + } + m.weights = Param( + m.consumers, + m.locations, + initialize=weights_dict, + doc='Weights for each consumer and product', + ) - existing_products_data = StringIO(""" + existing_products_data = StringIO( + """ 1 2 3 4 5 1 0.62 5.06 7.82 0.22 4.42 2 5.21 2.66 9.54 5.03 8.01 @@ -188,24 +211,44 @@ def build_model(): 8 8.35 3.79 1.19 1.96 5.88 9 6.44 0.17 9.93 6.8 9.75 10 6.49 1.92 0.05 4.89 6.43 - """) + """ + ) existing_products_table = pd.read_csv(existing_products_data, delimiter=r'\s+') - existing_products_dict = {(k[0], int(k[1])): v for k, v in existing_products_table.stack().to_dict().items()} - m.existing_products = Param(m.products, m.locations, initialize=existing_products_dict, doc='Existing products for each location and product') + existing_products_dict = { + (k[0], int(k[1])): v + for k, v in existing_products_table.stack().to_dict().items() + } + m.existing_products = Param( + m.products, + m.locations, + initialize=existing_products_dict, + doc='Existing products for each location and product', + ) # m.consumers * m.products # Squared weighted Euclidean distance between the existing products and the ideal points - rr = {(i, j): sum(m.weights[i, k] * (m.existing_products[j, k] - m.ideal_points[i, k]) * ( - m.existing_products[j, k] - m.ideal_points[i, k]) - for k in m.locations) - for (i, j) in m.consumers * m.products} - # minimum dissimilarity between the existing products and consumers' ideal points + rr = { + (i, j): sum( + m.weights[i, k] + * (m.existing_products[j, k] - m.ideal_points[i, k]) + * (m.existing_products[j, k] - m.ideal_points[i, k]) + for k in m.locations + ) + for (i, j) in m.consumers * m.products + } + # minimum dissimilarity between the existing products and consumers' ideal points r = {i: min(rr[i, j] for j in m.products) for i in m.consumers} m.x = Var(m.locations, doc='Location of each product') - m.Y = BooleanVar(m.consumers, doc='Indicates if consumer positioning is satisfactory based on distance to ideal points.') + m.Y = BooleanVar( + m.consumers, + doc='Indicates if consumer positioning is satisfactory based on distance to ideal points.', + ) m.H = Param(initialize=1000, doc='Big M value') - m.U = Var(bounds=(0, 5000), doc='Upper bound on the sum of the distances to the ideal points') # Slack variable + m.U = Var( + bounds=(0, 5000), + doc='Upper bound on the sum of the distances to the ideal points', + ) # Slack variable @m.Disjunction(m.consumers) def d(m, i): @@ -225,9 +268,17 @@ def d(m, i): A Pyomo Disjunction object that evaluates if the consumer's preference is met (`true` scenario) with a single Pyomo expression, or not met (`false` scenario) where the list is empty. """ return [ - [sum(m.weights[i, k] * (m.x[k] - m.ideal_points[i, k]) ** 2 for k in m.locations) - r[i] <= m.U], - [] + [ + sum( + m.weights[i, k] * (m.x[k] - m.ideal_points[i, k]) ** 2 + for k in m.locations + ) + - r[i] + <= m.U + ], + [], ] + for i in m.consumers: m.Y[i].associate_binary_var(m.d[i].disjuncts[0].binary_indicator_var) for k in m.locations: @@ -235,16 +286,33 @@ def d(m, i): m.x[k].setlb(lb) m.x[k].setub(ub) - m.c1 = Constraint(expr=m.x[1] - m.x[2] + m.x[3] + m.x[4] + m.x[5] <= 10, doc='Constraint 1') - m.c2 = Constraint(expr=0.6 * m.x[1] - 0.9 * m.x[2] - 0.5 * m.x[3] + 0.1 * m.x[4] + m.x[5] <= -0.64, doc='Constraint 2') - m.c3 = Constraint(expr=m.x[1] - m.x[2] + m.x[3] - m.x[4] + m.x[5] >= 0.69, doc='Constraint 3') + m.c1 = Constraint( + expr=m.x[1] - m.x[2] + m.x[3] + m.x[4] + m.x[5] <= 10, doc='Constraint 1' + ) + m.c2 = Constraint( + expr=0.6 * m.x[1] - 0.9 * m.x[2] - 0.5 * m.x[3] + 0.1 * m.x[4] + m.x[5] + <= -0.64, + doc='Constraint 2', + ) + m.c3 = Constraint( + expr=m.x[1] - m.x[2] + m.x[3] - m.x[4] + m.x[5] >= 0.69, doc='Constraint 3' + ) m.c4 = Constraint(expr=0.157 * m.x[1] + 0.05 * m.x[2] <= 1.5, doc='Constraint 4') - m.c5 = Constraint(expr=0.25 * m.x[2] + 1.05 * m.x[4] - 0.3 * m.x[5] >= 4.5, doc='Constraint 5') + m.c5 = Constraint( + expr=0.25 * m.x[2] + 1.05 * m.x[4] - 0.3 * m.x[5] >= 4.5, doc='Constraint 5' + ) # Minimizes total adjusted dissimilarity costs and maximizes consumer-based fixed profit, while balancing quadratic and linear operational costs across multiple product locations. m.obj = Objective( - expr=10 * m.U - sum(m.fixed_profit[i] * m.Y[i].get_associated_binary() for i in m.consumers) + 0.6 * m.x[1] ** 2 - 0.9 * m.x[ - 2] - 0.5 * m.x[3] + 0.1 * m.x[4] ** 2 + m.x[5], doc='Objective function') + expr=10 * m.U + - sum(m.fixed_profit[i] * m.Y[i].get_associated_binary() for i in m.consumers) + + 0.6 * m.x[1] ** 2 + - 0.9 * m.x[2] + - 0.5 * m.x[3] + + 0.1 * m.x[4] ** 2 + + m.x[5], + doc='Objective function', + ) return m @@ -254,6 +322,8 @@ def d(m, i): TransformationFactory('core.logical_to_linear').apply_to(m) # res = SolverFactory('gdpopt').solve(m, tee=True, nlp_solver='gams') TransformationFactory('gdp.bigm').apply_to(m) - SolverFactory('gams').solve(m, tee=True, solver='baron', add_options=['option optcr=0;']) + SolverFactory('gams').solve( + m, tee=True, solver='baron', add_options=['option optcr=0;'] + ) update_boolean_vars_from_binary(m) m.Y.display() From 10666b5b51c04f5490a7cd253e242c0318b68684 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Fri, 19 Apr 2024 20:57:17 -0400 Subject: [PATCH 043/124] 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 8a0b4a6d769503d62c67d070dfe24245fc1e62aa Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 23 Apr 2024 11:20:56 -0400 Subject: [PATCH 044/124] Updated medium-term purchasing contracts problem documentation --- gdplib/pyomo_examples/med_term_purchasing.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/gdplib/pyomo_examples/med_term_purchasing.py b/gdplib/pyomo_examples/med_term_purchasing.py index 774c50b..0ee4a74 100644 --- a/gdplib/pyomo_examples/med_term_purchasing.py +++ b/gdplib/pyomo_examples/med_term_purchasing.py @@ -40,13 +40,6 @@ def build_model(): Pyomo.AbstractModel Pyomo abstract model for medium-term purchasing contracts problem. - Raises - ------ - RuntimeError - _description_ - RuntimeError - _description_ - References ---------- [1] Vecchietti, A., & Grossmann, I. (2004). Computational experience with logmip solving linear and nonlinear disjunctive programming problems. Proc. of FOCAPD, 587–590. From 10e91a456866aaf34d93b27fb00b0d5107129e4e Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 23 Apr 2024 12:01:03 -0400 Subject: [PATCH 045/124] Added References --- gdplib/pyomo_examples/med_term_purchasing.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gdplib/pyomo_examples/med_term_purchasing.py b/gdplib/pyomo_examples/med_term_purchasing.py index 0ee4a74..240d80f 100644 --- a/gdplib/pyomo_examples/med_term_purchasing.py +++ b/gdplib/pyomo_examples/med_term_purchasing.py @@ -42,9 +42,15 @@ def build_model(): References ---------- +<<<<<<< HEAD [1] Vecchietti, A., & Grossmann, I. (2004). Computational experience with logmip solving linear and nonlinear disjunctive programming problems. Proc. of FOCAPD, 587–590. [2] Vecchietti, A., Lee, S., & Grossmann, I. E. (2003). Modeling of discrete/continuous optimization problems: characterization and formulation of disjunctions and their relaxations. Computers & Chemical Engineering, 27(3), 433–448. DOI: 10.1016/S0098-1354(02)00220-X [3] 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 +======= + [1] Vecchietti, Aldo, and I. Grossmann. "Computational experience with logmip solving linear and nonlinear disjunctive programming problems." In Proc. of FOCAPD, pp. 587-590. 2004. + [2] Vecchietti, A., S. Lee and I.E. Grossmann, “Modeling of Discrete/Continuous Optimization Problems: Characterization and Formulation of Disjunctions and their Relaxations,” Computers and Chemical Engineering 27, 433-448 (2003). https://doi.org/10.1016/S0098-1354(02)00220-X + [3] 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. https://doi.org/10.1021/ie0513144 +>>>>>>> a04daf9 (Added References) """ model = AbstractModel() From 1080e3abebc8878b37d9bd59bea4a978c02c7989 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 23 Apr 2024 12:13:06 -0400 Subject: [PATCH 046/124] black formatting applied --- gdplib/pyomo_examples/med_term_purchasing.py | 978 ++++++++++++++----- 1 file changed, 719 insertions(+), 259 deletions(-) diff --git a/gdplib/pyomo_examples/med_term_purchasing.py b/gdplib/pyomo_examples/med_term_purchasing.py index 240d80f..4e05b5f 100644 --- a/gdplib/pyomo_examples/med_term_purchasing.py +++ b/gdplib/pyomo_examples/med_term_purchasing.py @@ -31,6 +31,7 @@ # ShortTermContractCH.gms from the website. Some data is hardcoded into this model, # most notably the process structure itself and the mass balance information. + def build_model(): """ Build a Pyomo abstract model for the medium-term purchasing contracts problem. @@ -83,7 +84,9 @@ def build_model(): # JM # rawmat(J) in GAMS # Set of Raw Materials-- raw materials, intermediate products, and final products partition J - model.RawMaterials = Set(doc="Set of raw materials, intermediate products, and final products") + model.RawMaterials = Set( + doc="Set of raw materials, intermediate products, and final products" + ) # C # c in GAMS @@ -97,7 +100,6 @@ def build_model(): # j in GAMS model.Streams = Set(doc='Set of streams in the network') - ################## # Parameters ################## @@ -112,77 +114,140 @@ def build_model(): # a_jt^U and d_jt^U # spdm(j,t) in GAMS - model.SupplyAndDemandUBs = Param(model.Streams, model.TimePeriods, default=0, doc='Supply and demand upper bounds') + model.SupplyAndDemandUBs = Param( + model.Streams, + model.TimePeriods, + default=0, + doc='Supply and demand upper bounds', + ) # d_jt^L # lbdm(j, t) in GAMS - model.DemandLB = Param(model.Streams, model.TimePeriods, default=0, doc='Demand lower bounds') + model.DemandLB = Param( + model.Streams, model.TimePeriods, default=0, doc='Demand lower bounds' + ) # delta_it # delta(i, t) in GAMS # operating cost of process i at time t - model.OperatingCosts = Param(model.Processes, model.TimePeriods, doc='Operating cost of process i at time t') + model.OperatingCosts = Param( + model.Processes, model.TimePeriods, doc='Operating cost of process i at time t' + ) # prices of raw materials under FP contract and selling prices of products # pf(j, t) in GAMS # omega_jt and pf_jt - model.Prices = Param(model.Streams, model.TimePeriods, default=0, doc='Prices of raw materials under FP contract and selling prices of products') + model.Prices = Param( + model.Streams, + model.TimePeriods, + default=0, + doc='Prices of raw materials under FP contract and selling prices of products', + ) # Price for quantities less than min amount under discount contract # pd1(j, t) in GAMS - model.RegPrice_Discount = Param(model.Streams, model.TimePeriod, doc='Price for quantities less than min amount under discount contract') + model.RegPrice_Discount = Param( + model.Streams, + model.TimePeriod, + doc='Price for quantities less than min amount under discount contract', + ) # Discounted price for the quantity purchased exceeding the min amount # pd2(j,t0 in GAMS - model.DiscountPrice_Discount = Param(model.Streams, model.TimePeriods, doc='Discounted price for the quantity purchased exceeding the min amount') + model.DiscountPrice_Discount = Param( + model.Streams, + model.TimePeriods, + doc='Discounted price for the quantity purchased exceeding the min amount', + ) # Price for quantities below min amount # pb1(j,t) in GAMS - model.RegPrice_Bulk = Param(model.Streams, model.TimePeriods, doc='Price for quantities below min amount under bulk contract') + model.RegPrice_Bulk = Param( + model.Streams, + model.TimePeriods, + doc='Price for quantities below min amount under bulk contract', + ) # Price for quantities above min amount # pb2(j, t) in GAMS - model.DiscountPrice_Bulk = Param(model.Streams, model.TimePeriods, doc='Price for quantities above minimum amount under bulk contract') + model.DiscountPrice_Bulk = Param( + model.Streams, + model.TimePeriods, + doc='Price for quantities above minimum amount under bulk contract', + ) # prices with length contract # pl(j, p, t) in GAMS - model.Prices_Length = Param(model.Streams, model.Contracts_Length, model.TimePeriods, default=0, doc='Prices with length contract') + model.Prices_Length = Param( + model.Streams, + model.Contracts_Length, + model.TimePeriods, + default=0, + doc='Prices with length contract', + ) # sigmad_jt # sigmad(j, t) in GAMS # Minimum quantity of chemical j that must be bought before recieving a Discount under discount contract - model.MinAmount_Discount = Param(model.Streams, model.TimePeriods, default=0, doc='Minimum quantity of chemical j that must be bought before receiving a Discount under discount contract') + model.MinAmount_Discount = Param( + model.Streams, + model.TimePeriods, + default=0, + 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 # sigmab(j, t) in GAMS - model.MinAmount_Bulk = Param(model.Streams, model.TimePeriods, default=0, doc='Minimum quantity of chemical j that must be bought before receiving a Discount under bulk contract') + model.MinAmount_Bulk = Param( + model.Streams, + model.TimePeriods, + default=0, + 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 # sigmal(j, p) in GAMS - model.MinAmount_Length = Param(model.Streams, model.Contracts_Length, default=0, doc='Minimum quantity of chemical j that must be bought before receiving a Discount under length contract') + model.MinAmount_Length = Param( + model.Streams, + model.Contracts_Length, + default=0, + doc='Minimum quantity of chemical j that must be bought before receiving a Discount under length contract', + ) # main products of process i # These are 1 (true) if stream j is the main product of process i, false otherwise. # jm(j, i) in GAMS - model.MainProducts = Param(model.Streams, model.Processes, default=0, doc='Main products of process i') + model.MainProducts = Param( + model.Streams, model.Processes, default=0, doc='Main products of process i' + ) # theta_jt # psf(j, t) in GAMS # Shortfall penalty of product j at time t - model.ShortfallPenalty = Param(model.Products, model.TimePeriods, doc='Shortfall penalty of product j at time t') + model.ShortfallPenalty = Param( + model.Products, + model.TimePeriods, + doc='Shortfall penalty of product j at time t', + ) # shortfall upper bound # sfub(j, t) in GAMS - model.ShortfallUB = Param(model.Products, model.TimePeriods, default=0, doc='Shortfall upper bound') + model.ShortfallUB = Param( + model.Products, model.TimePeriods, default=0, doc='Shortfall upper bound' + ) # epsilon_jt # cinv(j, t) in GAMS # inventory cost of material j at time t - model.InventoryCost = Param(model.Streams, model.TimePeriods, doc='Inventory cost of material j at time t') + model.InventoryCost = Param( + model.Streams, model.TimePeriods, doc='Inventory cost of material j at time t' + ) # invub(j, t) in GAMS # inventory upper bound - model.InventoryLevelUB = Param(model.Streams, model.TimePeriods, default=0, doc='Inventory upper bound') + model.InventoryLevelUB = Param( + model.Streams, model.TimePeriods, default=0, doc='Inventory upper bound' + ) ## UPPER BOUNDS HARDCODED INTO GAMS MODEL @@ -228,27 +293,70 @@ def getCostUBs(model, j, t): """ return COST_UB - model.AmountPurchasedUB_FP = Param(model.Streams, model.TimePeriods, - initialize=getAmountUBs, doc='Upper bound on amount purchased under fixed price contract') - model.AmountPurchasedUB_Discount = Param(model.Streams, model.TimePeriods, - initialize=getAmountUBs, doc='Upper bound on amount purchased under discount contract') - model.AmountPurchasedBelowMinUB_Discount = Param(model.Streams, model.TimePeriods, - initialize=getAmountUBs, doc='Upper bound on amount purchased below min amount for discount under discount contract') - model.AmountPurchasedAboveMinUB_Discount = Param(model.Streams, model.TimePeriods, - initialize=getAmountUBs, doc='Upper bound on amount purchased above min amount for discount under discount contract') - model.AmountPurchasedUB_FD = Param(model.Streams, model.TimePeriods, - initialize=getAmountUBs, doc='Upper bound on amount purchased under fixed duration contract') - model.AmountPurchasedUB_Bulk = Param(model.Streams, model.TimePeriods, - initialize=getAmountUBs, doc='Upper bound on amount purchased under bulk contract') - - model.CostUB_FP = Param(model.Streams, model.TimePeriods, initialize=getCostUBs, doc='Upper bound on cost of fixed price contract') - model.CostUB_FD = Param(model.Streams, model.TimePeriods, initialize=getCostUBs, doc='Upper bound on cost of fixed duration contract') - model.CostUB_Discount = Param(model.Streams, model.TimePeriods, initialize=getCostUBs, DOC='Upper bound on cost of discount contract') - model.CostUB_Bulk = Param(model.Streams, model.TimePeriods, initialize=getCostUBs, doc='Upper bound on cost of bulk contract') - + model.AmountPurchasedUB_FP = Param( + model.Streams, + model.TimePeriods, + initialize=getAmountUBs, + doc='Upper bound on amount purchased under fixed price contract', + ) + model.AmountPurchasedUB_Discount = Param( + model.Streams, + model.TimePeriods, + initialize=getAmountUBs, + doc='Upper bound on amount purchased under discount contract', + ) + model.AmountPurchasedBelowMinUB_Discount = Param( + model.Streams, + model.TimePeriods, + initialize=getAmountUBs, + doc='Upper bound on amount purchased below min amount for discount under discount contract', + ) + model.AmountPurchasedAboveMinUB_Discount = Param( + model.Streams, + model.TimePeriods, + initialize=getAmountUBs, + doc='Upper bound on amount purchased above min amount for discount under discount contract', + ) + model.AmountPurchasedUB_FD = Param( + model.Streams, + model.TimePeriods, + initialize=getAmountUBs, + doc='Upper bound on amount purchased under fixed duration contract', + ) + model.AmountPurchasedUB_Bulk = Param( + model.Streams, + model.TimePeriods, + initialize=getAmountUBs, + doc='Upper bound on amount purchased under bulk contract', + ) + + model.CostUB_FP = Param( + model.Streams, + model.TimePeriods, + initialize=getCostUBs, + doc='Upper bound on cost of fixed price contract', + ) + model.CostUB_FD = Param( + model.Streams, + model.TimePeriods, + initialize=getCostUBs, + doc='Upper bound on cost of fixed duration contract', + ) + model.CostUB_Discount = Param( + model.Streams, + model.TimePeriods, + initialize=getCostUBs, + DOC='Upper bound on cost of discount contract', + ) + model.CostUB_Bulk = Param( + model.Streams, + model.TimePeriods, + initialize=getCostUBs, + doc='Upper bound on cost of bulk contract', + ) #################### - #VARIABLES + # VARIABLES #################### # prof in GAMS @@ -257,7 +365,12 @@ def getCostUBs(model, j, t): # f(j, t) in GAMS # mass flow rates in tons per time interval t - model.FlowRate = Var(model.Streams, model.TimePeriods, within=NonNegativeReals, doc='Mass flow rates in tons per time interval t') + model.FlowRate = Var( + model.Streams, + model.TimePeriods, + within=NonNegativeReals, + doc='Mass flow rates in tons per time interval t', + ) # V_jt # inv(j, t) in GAMS @@ -278,12 +391,17 @@ def getInventoryBounds(model, i, j): Returns ------- tuple - A tuple containing two floats: the lower bound (0) and the upper bound for the inventory level of material j. + A tuple containing two floats: the lower bound (0) and the upper bound for the inventory level of material j. The upper bound is retrieved from the model's 'InventoryLevelUB' parameter using indices i and j. """ - return (0, model.InventoryLevelUB[i,j]) - model.InventoryLevel = Var(model.Streams, model.TimePeriods, - bounds=getInventoryBounds, doc='Inventory level of material j at time period t') + return (0, model.InventoryLevelUB[i, j]) + + model.InventoryLevel = Var( + model.Streams, + model.TimePeriods, + bounds=getInventoryBounds, + doc='Inventory level of material j at time period t', + ) # SF_jt # sf(j, t) in GAMS @@ -307,10 +425,14 @@ def getShortfallBounds(model, i, j): A tuple (0, upper_bound), where 0 is the lower bound (nonnegative shortfalls) and 'upper_bound' is extracted from 'model.ShortfallUB[i, j]'. 'model.ShortfallUB[i, j]' represents the maximal permitted shortfall for material i in the context of process i. """ - return(0, model.ShortfallUB[i,j]) - model.Shortfall = Var(model.Products, model.TimePeriods, - bounds=getShortfallBounds, doc='Shortfall of demand for material j at time period t') + return (0, model.ShortfallUB[i, j]) + model.Shortfall = Var( + model.Products, + model.TimePeriods, + bounds=getShortfallBounds, + doc='Shortfall of demand for material j at time period t', + ) # amounts purchased under different contracts @@ -332,12 +454,17 @@ def get_FP_bounds(model, j, t): Returns ------- tuple - A tuple (0, upper_bound), where '0' is the lower bound (non-negative constraint) and 'upper_bound' is sourced from 'model.AmountPurchasedUB_FP[j,t]'. + A tuple (0, upper_bound), where '0' is the lower bound (non-negative constraint) and 'upper_bound' is sourced from 'model.AmountPurchasedUB_FP[j,t]'. 'model.AmountPurchasedUB_FP[j,t]' represents the maximum allowed purchase amount for material 'j' in period 't' """ - return (0, model.AmountPurchasedUB_FP[j,t]) - model.AmountPurchased_FP = Var(model.Streams, model.TimePeriods, - bounds=get_FP_bounds, doc='Amount of raw material j bought under fixed price contract at time period t') + return (0, model.AmountPurchasedUB_FP[j, t]) + + model.AmountPurchased_FP = Var( + model.Streams, + model.TimePeriods, + bounds=get_FP_bounds, + doc='Amount of raw material j bought under fixed price contract at time period t', + ) # spd(j, t) in GAMS def get_Discount_Total_bounds(model, j, t): @@ -356,12 +483,17 @@ def get_Discount_Total_bounds(model, j, t): Returns ------- tuple - A tuple containing two elements: the lower bound (0) and the upper bound for the total amount of material j that can be purchased under discount contracts in period t. + A tuple containing two elements: the lower bound (0) and the upper bound for the total amount of material j that can be purchased under discount contracts in period t. The upper bound is retrieved from 'model.AmountPurchasedUB_Discount[j, t]'. """ - return (0, model.AmountPurchasedUB_Discount[j,t]) - model.AmountPurchasedTotal_Discount = Var(model.Streams, model.TimePeriods, - bounds=get_Discount_Total_bounds, doc='Total amount of material j bought under discount contract at time period t') + return (0, model.AmountPurchasedUB_Discount[j, t]) + + model.AmountPurchasedTotal_Discount = Var( + model.Streams, + model.TimePeriods, + bounds=get_Discount_Total_bounds, + doc='Total amount of material j bought under discount contract at time period t', + ) # Amount purchased below min amount for discount under discount contract # spd1(j, t) in GAMS @@ -381,12 +513,17 @@ def get_Discount_BelowMin_bounds(model, j, t): Returns ------- tuple - A tuple (0, upper_bound), where '0' is the lower bound, and 'upper_bound' is 'model.AmountPurchasedBelowMinUB_Discount[j, t]'. + A tuple (0, upper_bound), where '0' is the lower bound, and 'upper_bound' is 'model.AmountPurchasedBelowMinUB_Discount[j, t]'. 'model.AmountPurchasedBelowMinUB_Discount[j, t] indicates the maximal purchasable amount below the discount threshold for material j in period't. """ - return (0, model.AmountPurchasedBelowMinUB_Discount[j,t]) - model.AmountPurchasedBelowMin_Discount = Var(model.Streams, - model.TimePeriods, bounds=get_Discount_BelowMin_bounds, doc='Amount purchased below min amount for discount under discount contract') + return (0, model.AmountPurchasedBelowMinUB_Discount[j, t]) + + model.AmountPurchasedBelowMin_Discount = Var( + model.Streams, + model.TimePeriods, + bounds=get_Discount_BelowMin_bounds, + doc='Amount purchased below min amount for discount under discount contract', + ) # spd2(j, t) in GAMS # Amount purchased above min amount for discount under discount contract @@ -406,12 +543,17 @@ def get_Discount_AboveMin_bounds(model, j, t): Returns ------- tuple - A tuple (0, upper_bound), where '0' is the lower bound and 'upper_bound' is sourced from 'model.AmountPurchasedBelowMinUB_Discount[j, t]'. + A tuple (0, upper_bound), where '0' is the lower bound and 'upper_bound' is sourced from 'model.AmountPurchasedBelowMinUB_Discount[j, t]'. 'model.AmountPurchasedBelowMinUB_Discount[j, t]' indicates the maximum amount that can be purchased above the discount threshold for material j in period t. """ - return (0, model.AmountPurchasedBelowMinUB_Discount[j,t]) - model.AmountPurchasedAboveMin_Discount = Var(model.Streams, - model.TimePeriods, bounds=get_Discount_AboveMin_bounds, doc='Amount purchased above min amount for discount under discount contract') + return (0, model.AmountPurchasedBelowMinUB_Discount[j, t]) + + model.AmountPurchasedAboveMin_Discount = Var( + model.Streams, + model.TimePeriods, + bounds=get_Discount_AboveMin_bounds, + doc='Amount purchased above min amount for discount under discount contract', + ) # Amount purchased under bulk contract # spb(j, t) in GAMS @@ -434,9 +576,14 @@ def get_bulk_bounds(model, j, t): A tuple (0, upper_bound), where '0' is the lower bound and 'upper_bound' is sourced from 'model.AmountPurchasedUB_Bulk[j,t]'. 'model.AmountPurchasedUB_Bulk[j,t]' indicates the maximal allowable bulk purchase amount for material 'j' in period 't'. """ - return (0, model.AmountPurchasedUB_Bulk[j,t]) - model.AmountPurchased_Bulk = Var(model.Streams, model.TimePeriods, - bounds=get_bulk_bounds, doc='Amount purchased under bulk contract') + return (0, model.AmountPurchasedUB_Bulk[j, t]) + + model.AmountPurchased_Bulk = Var( + model.Streams, + model.TimePeriods, + bounds=get_bulk_bounds, + doc='Amount purchased under bulk contract', + ) # spl(j, t) in GAMS # Amount purchased under Fixed Duration contract @@ -459,10 +606,14 @@ def get_FD_bounds(model, j, t): A tuple (0, upper_bound), where '0' is the lower bound and upper_bound is taken from 'model.AmountPurchasedUB_FD[j, t]'. 'model.AmountPurchasedUB_FD[j, t]' indicates the maximum permissible purchase quantity for material j under a fixed duration contract in time period t. """ - return (0, model.AmountPurchasedUB_FD[j,t]) - model.AmountPurchased_FD = Var(model.Streams, model.TimePeriods, - bounds=get_FD_bounds, doc='Amount purchased under Fixed Duration contract') + return (0, model.AmountPurchasedUB_FD[j, t]) + model.AmountPurchased_FD = Var( + model.Streams, + model.TimePeriods, + bounds=get_FD_bounds, + doc='Amount purchased under Fixed Duration contract', + ) # costs @@ -484,11 +635,17 @@ def get_CostUBs_FD(model, j, t): Returns ------- tuple - A tuple (0, upper_bound), where '0' is the lower bound, the minimal cost scenario, and 'upper_bound' is retrieved from 'model.CostUB_FD[j, t]', + A tuple (0, upper_bound), where '0' is the lower bound, the minimal cost scenario, and 'upper_bound' is retrieved from 'model.CostUB_FD[j, t]', 'model.CostUB_FD[j, t]' represents the highest permissible cost for purchasing material j under a fixed duration contract in time period t. """ - return (0, model.CostUB_FD[j,t]) - model.Cost_FD = Var(model.Streams, model.TimePeriods, bounds=get_CostUBs_FD, doc='Cost of variable length contract') + return (0, model.CostUB_FD[j, t]) + + model.Cost_FD = Var( + model.Streams, + model.TimePeriods, + bounds=get_CostUBs_FD, + doc='Cost of variable length contract', + ) # costpf(j, t) in GAMS # cost of fixed duration contract @@ -511,8 +668,14 @@ def get_CostUBs_FP(model, j, t): A tuple (0, upper_bound), where '0' is the lower bound and upper_bound is sourced from 'model.CostUB_FP[j, t]'. 'model.CostUB_FP[j, t]' denotes the highest permissible cost for purchasing material 'j' under a fixed price contract in time period 't' """ - return (0, model.CostUB_FP[j,t]) - model.Cost_FP = Var(model.Streams, model.TimePeriods, bounds=get_CostUBs_FP, doc='Cost of fixed price contract') + return (0, model.CostUB_FP[j, t]) + + model.Cost_FP = Var( + model.Streams, + model.TimePeriods, + bounds=get_CostUBs_FP, + doc='Cost of fixed price contract', + ) # costpd(j, t) in GAMS # cost of discount contract @@ -532,12 +695,17 @@ def get_CostUBs_Discount(model, j, t): Returns ------- tuple - A tuple (0, upper bound), where 0' represents the lower bound, indicating the lowest possible cost condition, and upper bound is the 'model.CostUB_Discount[j, t]'. + A tuple (0, upper bound), where 0' represents the lower bound, indicating the lowest possible cost condition, and upper bound is the 'model.CostUB_Discount[j, t]'. 'model.CostUB_Discount[j, t]' represents the maximum allowed cost for purchasing material j under a discount contract in the given time period t. """ - return (0, model.CostUB_Discount[j,t]) - model.Cost_Discount = Var(model.Streams, model.TimePeriods, - bounds=get_CostUBs_Discount, doc='Cost of discount contract') + return (0, model.CostUB_Discount[j, t]) + + model.Cost_Discount = Var( + model.Streams, + model.TimePeriods, + bounds=get_CostUBs_Discount, + doc='Cost of discount contract', + ) # costpb(j, t) in GAMS # cost of bulk contract @@ -560,17 +728,28 @@ def get_CostUBs_Bulk(model, j, t): A tuple with 0 as the lower bound and 'model.CostUB_Bulk[j, t]' as the upper bound. 'model.CostUB_Bulk[j, t]' represents the maximum cost for purchasing material j under a bulk contract in time period t. """ - return (0, model.CostUB_Bulk[j,t]) - model.Cost_Bulk = Var(model.Streams, model.TimePeriods, bounds=get_CostUBs_Bulk, doc='Cost of bulk contract') + return (0, model.CostUB_Bulk[j, t]) + model.Cost_Bulk = Var( + model.Streams, + model.TimePeriods, + bounds=get_CostUBs_Bulk, + doc='Cost of bulk contract', + ) # binary variables - model.BuyFPContract = RangeSet(0,1) # buy fixed price contract - model.BuyDiscountContract = Set(initialize=('BelowMin', 'AboveMin', 'NotSelected'), doc='Buy discount contract') - model.BuyBulkContract = Set(initialize=('BelowMin', 'AboveMin', 'NotSelected'), doc='Buy bulk contract') - model.BuyFDContract = Set(initialize=('1Month', '2Month', '3Month', 'NotSelected'), doc='Buy fixed duration contract') - + model.BuyFPContract = RangeSet(0, 1) # buy fixed price contract + model.BuyDiscountContract = Set( + initialize=('BelowMin', 'AboveMin', 'NotSelected'), doc='Buy discount contract' + ) + model.BuyBulkContract = Set( + initialize=('BelowMin', 'AboveMin', 'NotSelected'), doc='Buy bulk contract' + ) + model.BuyFDContract = Set( + initialize=('1Month', '2Month', '3Month', 'NotSelected'), + doc='Buy fixed duration contract', + ) ################ # CONSTRAINTS @@ -592,24 +771,55 @@ def profit_rule(model): Pyomo.Objective Objective function: maximize profit of the medium-term purchasing contracts problem. """ - salesIncome = sum(model.Prices[j,t] * model.FlowRate[j,t] - for j in model.Products for t in model.TimePeriods) - purchaseCost = sum(model.Cost_FD[j,t] - for j in model.RawMaterials for t in model.TimePeriods) + \ - sum(model.Cost_Discount[j,t] - for j in model.RawMaterials for t in model.TimePeriods) + \ - sum(model.Cost_Bulk[j,t] - for j in model.RawMaterials for t in model.TimePeriods) + \ - sum(model.Cost_FP[j,t] - for j in model.RawMaterials for t in model.TimePeriods) - productionCost = sum(model.OperatingCosts[i,t] * sum(model.FlowRate[j,t] - for j in model.Streams if model.MainProducts[j,i]) - for i in model.Processes for t in model.TimePeriods) - shortfallCost = sum(model.Shortfall[j,t] * model.ShortfallPenalty[j, t] - for j in model.Products for t in model.TimePeriods) - inventoryCost = sum(model.InventoryCost[j,t] * model.InventoryLevel[j,t] - for j in model.Products for t in model.TimePeriods) - return salesIncome - purchaseCost - productionCost - inventoryCost - shortfallCost + salesIncome = sum( + model.Prices[j, t] * model.FlowRate[j, t] + for j in model.Products + for t in model.TimePeriods + ) + purchaseCost = ( + sum( + model.Cost_FD[j, t] + for j in model.RawMaterials + for t in model.TimePeriods + ) + + sum( + model.Cost_Discount[j, t] + for j in model.RawMaterials + for t in model.TimePeriods + ) + + sum( + model.Cost_Bulk[j, t] + for j in model.RawMaterials + for t in model.TimePeriods + ) + + sum( + model.Cost_FP[j, t] + for j in model.RawMaterials + for t in model.TimePeriods + ) + ) + productionCost = sum( + model.OperatingCosts[i, t] + * sum( + model.FlowRate[j, t] for j in model.Streams if model.MainProducts[j, i] + ) + for i in model.Processes + for t in model.TimePeriods + ) + shortfallCost = sum( + model.Shortfall[j, t] * model.ShortfallPenalty[j, t] + for j in model.Products + for t in model.TimePeriods + ) + inventoryCost = sum( + model.InventoryCost[j, t] * model.InventoryLevel[j, t] + for j in model.Products + for t in model.TimePeriods + ) + return ( + salesIncome - purchaseCost - productionCost - inventoryCost - shortfallCost + ) + model.profit = Objective(rule=profit_rule, sense=maximize, doc='Maximize profit') # flow of raw materials is the total amount purchased (accross all contracts) @@ -631,11 +841,20 @@ def raw_material_flow_rule(model, j, t): Pyomo.Constraint An equality constraint ensuring the material flow balance for each raw material j in each time period t. """ - return model.FlowRate[j,t] == model.AmountPurchased_FD[j,t] + \ - model.AmountPurchased_FP[j,t] + model.AmountPurchased_Bulk[j,t] + \ - model.AmountPurchasedTotal_Discount[j,t] - model.raw_material_flow = Constraint(model.RawMaterials, model.TimePeriods, - rule=raw_material_flow_rule, doc='Material flow balance for each raw material j in each time period t') + return ( + model.FlowRate[j, t] + == model.AmountPurchased_FD[j, t] + + model.AmountPurchased_FP[j, t] + + model.AmountPurchased_Bulk[j, t] + + model.AmountPurchasedTotal_Discount[j, t] + ) + + model.raw_material_flow = Constraint( + model.RawMaterials, + model.TimePeriods, + rule=raw_material_flow_rule, + doc='Material flow balance for each raw material j in each time period t', + ) def discount_amount_total_rule(model, j, t): """ @@ -655,18 +874,25 @@ def discount_amount_total_rule(model, j, t): Pyomo.Constraint An equality constraint that ensures the total discounted purchase amount of material j in time period t is the sum of amounts bought below and above the discount threshold. """ - return model.AmountPurchasedTotal_Discount[j,t] == \ - model.AmountPurchasedBelowMin_Discount[j,t] + \ - model.AmountPurchasedAboveMin_Discount[j,t] - model.discount_amount_total_rule = Constraint(model.RawMaterials, model.TimePeriods, - rule=discount_amount_total_rule, doc='Total discounted purchase amount of material j in time period t is the sum of amounts bought below and above the discount threshold') + return ( + model.AmountPurchasedTotal_Discount[j, t] + == model.AmountPurchasedBelowMin_Discount[j, t] + + model.AmountPurchasedAboveMin_Discount[j, t] + ) + + model.discount_amount_total_rule = Constraint( + model.RawMaterials, + model.TimePeriods, + rule=discount_amount_total_rule, + doc='Total discounted purchase amount of material j in time period t is the sum of amounts bought below and above the discount threshold', + ) # mass balance equations for each node # these are specific to the process network in this example. def mass_balance_rule1(model, t): """ Represents the mass balance equation for the first node in the process network. - + Stream 1 is the inlet stream, and streams 2 and 3 are the outlet streams. The mass balance equation states that the total flow rate into the node (stream 1) is equal to the sum of the flow rates out of the node (streams 2 and 3). @@ -683,7 +909,12 @@ def mass_balance_rule1(model, t): A constraint that enforces the mass balance equation for the first node in the process network. """ return model.FlowRate[1, t] == model.FlowRate[2, t] + model.FlowRate[3, t] - model.mass_balance1 = Constraint(model.TimePeriods, rule=mass_balance_rule1, doc='Mass balance equation for the first node in the process network') + + model.mass_balance1 = Constraint( + model.TimePeriods, + rule=mass_balance_rule1, + doc='Mass balance equation for the first node in the process network', + ) def mass_balance_rule2(model, t): """ @@ -704,8 +935,13 @@ def mass_balance_rule2(model, t): Pyomo.Constraint A constraint that enforces the mass balance equation for the second node in the process network. """ - return model.FlowRate[5, t] == model.FlowRate[4, t] + model.FlowRate[8,t] - model.mass_balance2 = Constraint(model.TimePeriods, rule=mass_balance_rule2, doc='Mass balance equation for the second node in the process network') + return model.FlowRate[5, t] == model.FlowRate[4, t] + model.FlowRate[8, t] + + model.mass_balance2 = Constraint( + model.TimePeriods, + rule=mass_balance_rule2, + doc='Mass balance equation for the second node in the process network', + ) def mass_balance_rule3(model, t): """ @@ -727,7 +963,12 @@ def mass_balance_rule3(model, t): A constraint that enforces the mass balance equation for the third node in the process network. """ return model.FlowRate[6, t] == model.FlowRate[7, t] - model.mass_balance3 = Constraint(model.TimePeriods, rule=mass_balance_rule3, doc='Mass balance equation for the third node in the process network') + + model.mass_balance3 = Constraint( + model.TimePeriods, + rule=mass_balance_rule3, + doc='Mass balance equation for the third node in the process network', + ) def mass_balance_rule4(model, t): """ @@ -748,8 +989,13 @@ def mass_balance_rule4(model, t): Pyomo.Constraint A constraint that enforces the mass balance equation for Process 2 in the process network. """ - return model.FlowRate[3, t] == 10*model.FlowRate[5, t] - model.mass_balance4 = Constraint(model.TimePeriods, rule=mass_balance_rule4, doc='Mass balance equation for Process 2 in the process network') + return model.FlowRate[3, t] == 10 * model.FlowRate[5, t] + + model.mass_balance4 = Constraint( + model.TimePeriods, + rule=mass_balance_rule4, + doc='Mass balance equation for Process 2 in the process network', + ) # process input/output constraints # these are also totally specific to the process network @@ -772,7 +1018,12 @@ def process_balance_rule1(model, t): A constraint that enforces the input/output balance equation for Process 1 in the process network. """ return model.FlowRate[9, t] == model.ProcessConstants[1] * model.FlowRate[2, t] - model.process_balance1 = Constraint(model.TimePeriods, rule=process_balance_rule1, doc='Input/output balance equation for Process 1 in the process network') + + model.process_balance1 = Constraint( + model.TimePeriods, + rule=process_balance_rule1, + doc='Input/output balance equation for Process 1 in the process network', + ) def process_balance_rule2(model, t): """ @@ -792,9 +1043,15 @@ def process_balance_rule2(model, t): Pyomo.Constraint A constraint that enforces the input/output balance equation for Process 2 in the process network. """ - return model.FlowRate[10, t] == model.ProcessConstants[2] * \ - (model.FlowRate[5, t] + model.FlowRate[3, t]) - model.process_balance2 = Constraint(model.TimePeriods, rule=process_balance_rule2, doc='Input/output balance equation for Process 2 in the process network') + return model.FlowRate[10, t] == model.ProcessConstants[2] * ( + model.FlowRate[5, t] + model.FlowRate[3, t] + ) + + model.process_balance2 = Constraint( + model.TimePeriods, + rule=process_balance_rule2, + doc='Input/output balance equation for Process 2 in the process network', + ) def process_balance_rule3(model, t): """ @@ -815,9 +1072,16 @@ def process_balance_rule3(model, t): Pyomo.Constraint A constraint that enforces the input/output balance equation for Process 3 in the process network. """ - return model.FlowRate[8, t] == RandomConst_Line264 * \ - model.ProcessConstants[3] * model.FlowRate[7, t] - model.process_balance3 = Constraint(model.TimePeriods, rule=process_balance_rule3, doc='Input/output balance equation 1 for Process 3 in the process network') + return ( + model.FlowRate[8, t] + == RandomConst_Line264 * model.ProcessConstants[3] * model.FlowRate[7, t] + ) + + model.process_balance3 = Constraint( + model.TimePeriods, + rule=process_balance_rule3, + doc='Input/output balance equation 1 for Process 3 in the process network', + ) def process_balance_rule4(model, t): """ @@ -838,9 +1102,16 @@ def process_balance_rule4(model, t): Pyomo.Constraint A constraint that enforces the input/output balance equation for Process 3 in the process network. """ - return model.FlowRate[11, t] == RandomConst_Line265 * \ - model.ProcessConstants[3] * model.FlowRate[7, t] - model.process_balance4 = Constraint(model.TimePeriods, rule=process_balance_rule4, doc='Input/output balance equation 2 for Process 3 in the process network') + return ( + model.FlowRate[11, t] + == RandomConst_Line265 * model.ProcessConstants[3] * model.FlowRate[7, t] + ) + + model.process_balance4 = Constraint( + model.TimePeriods, + rule=process_balance_rule4, + doc='Input/output balance equation 2 for Process 3 in the process network', + ) # process capacity contraints # these are hardcoded based on the three processes and the process flow structure @@ -863,7 +1134,12 @@ def process_capacity_rule1(model, t): A constraint that enforces the capacity constraint for Process 1 in the process network. """ return model.FlowRate[9, t] <= model.Capacity[1] - model.process_capacity1 = Constraint(model.TimePeriods, rule=process_capacity_rule1, doc='Capacity constraint for Process 1 in the process network') + + model.process_capacity1 = Constraint( + model.TimePeriods, + rule=process_capacity_rule1, + doc='Capacity constraint for Process 1 in the process network', + ) def process_capacity_rule2(model, t): """ @@ -884,7 +1160,12 @@ def process_capacity_rule2(model, t): A constraint that enforces the capacity constraint for Process 2 in the process network. """ return model.FlowRate[10, t] <= model.Capacity[2] - model.process_capacity2 = Constraint(model.TimePeriods, rule=process_capacity_rule2, doc='Capacity constraint for Process 2 in the process network') + + model.process_capacity2 = Constraint( + model.TimePeriods, + rule=process_capacity_rule2, + doc='Capacity constraint for Process 2 in the process network', + ) def process_capacity_rule3(model, t): """ @@ -905,7 +1186,12 @@ def process_capacity_rule3(model, t): A constraint that enforces the capacity constraint for Process 3 in the process network. """ return model.FlowRate[11, t] + model.FlowRate[8, t] <= model.Capacity[3] - model.process_capacity3 = Constraint(model.TimePeriods, rule=process_capacity_rule3, doc='Capacity constraint for Process 3 in the process network') + + model.process_capacity3 = Constraint( + model.TimePeriods, + rule=process_capacity_rule3, + doc='Capacity constraint for Process 3 in the process network', + ) # Inventory balance of final products # again, these are hardcoded. @@ -914,7 +1200,7 @@ def inventory_balance1(model, t): """ Maintains inventory balance for the material associated with stream 12 at the first inventory node across time periods. - This constraint ensures that the inventory level of the material at the beginning of each time period t, combined with the incoming flow from stream 9, equals the sum of the outflow to the next process (or demand) represented by stream 12 and the inventory level at the end of the time period. For the initial time period, the previous inventory is assumed to be zero. + This constraint ensures that the inventory level of the material at the beginning of each time period t, combined with the incoming flow from stream 9, equals the sum of the outflow to the next process (or demand) represented by stream 12 and the inventory level at the end of the time period. For the initial time period, the previous inventory is assumed to be zero. This balance is vital for tracking inventory levels accurately, allowing the model to make informed decisions about production, storage, and sales to maximize overall profit. Parameters @@ -929,15 +1215,23 @@ def inventory_balance1(model, t): Pyomo.Constraint A constraint enforcing the balance of inventory levels for the material flowing through stream 12 at the first inventory node, taking into account the material inflows and outflows as well as changes in inventory from the previous to the current time period. """ - prev = 0 if t == min(model.TimePeriods) else model.InventoryLevel[12, t-1] - return prev + model.FlowRate[9, t] == model.FlowRate[12, t] + model.InventoryLevel[12,t] - model.inventory_balance1 = Constraint(model.TimePeriods, rule=inventory_balance1, doc='Inventory balance for material associated with stream 12 at the first inventory node') + prev = 0 if t == min(model.TimePeriods) else model.InventoryLevel[12, t - 1] + return ( + prev + model.FlowRate[9, t] + == model.FlowRate[12, t] + model.InventoryLevel[12, t] + ) + + model.inventory_balance1 = Constraint( + model.TimePeriods, + rule=inventory_balance1, + doc='Inventory balance for material associated with stream 12 at the first inventory node', + ) def inventory_balance_rule2(model, t): """ Ensures inventory balance for the material associated with stream 13 at the second inventory node for the first time period. - This constraint is applied only to the first time period (t=1) and ensures that the sum of incoming flows from streams 10 and 11 equals the sum of the outflow to the next process represented by stream 13 and the inventory level at the end of the period. + This constraint is applied only to the first time period (t=1) and ensures that the sum of incoming flows from streams 10 and 11 equals the sum of the outflow to the next process represented by stream 13 and the inventory level at the end of the period. For periods beyond the first, this constraint is skipped, as the balance for these periods may be governed by other conditions or constraints within the model. This selective application is crucial for accurately modeling the startup phase of the inventory system, where initial conditions significantly impact subsequent operations. Parameters @@ -950,21 +1244,28 @@ def inventory_balance_rule2(model, t): Returns ------- Pyomo.Constraint or Constraint.Skip - A constraint enforcing the inventory balance for the material flowing through stream 13 at the second inventory node during the first time period. + A constraint enforcing the inventory balance for the material flowing through stream 13 at the second inventory node during the first time period. For all other periods, the function returns `Constraint.Skip`, indicating no constraint is applied. """ if t != 1: return Constraint.Skip - return model.FlowRate[10, t] + model.FlowRate[11, t] == \ - model.InventoryLevel[13,t] + model.FlowRate[13, t] - model.inventory_balance2 = Constraint(model.TimePeriods, rule=inventory_balance_rule2, doc='Inventory balance for material associated with stream 13 at the second inventory node') + return ( + model.FlowRate[10, t] + model.FlowRate[11, t] + == model.InventoryLevel[13, t] + model.FlowRate[13, t] + ) + + model.inventory_balance2 = Constraint( + model.TimePeriods, + rule=inventory_balance_rule2, + doc='Inventory balance for material associated with stream 13 at the second inventory node', + ) def inventory_balance_rule3(model, t): """ Maintains the inventory balance for material associated with stream 13 at the second inventory node for all time periods after the first. - This constraint is crucial for modeling the dynamic behavior of inventory levels over time, ensuring that the sum of the previous period's inventory level and the current period's inflows from streams 10 and 11 equals the current period's outflow (through stream 13) and ending inventory level. - It reflects the principle of inventory continuity, accounting for inflows, outflows, and storage from one period to the next. + This constraint is crucial for modeling the dynamic behavior of inventory levels over time, ensuring that the sum of the previous period's inventory level and the current period's inflows from streams 10 and 11 equals the current period's outflow (through stream 13) and ending inventory level. + It reflects the principle of inventory continuity, accounting for inflows, outflows, and storage from one period to the next. The constraint is skipped for the first period (t=1) to accommodate initial conditions or startup behaviors specific to the model's context. Parameters @@ -977,14 +1278,23 @@ def inventory_balance_rule3(model, t): Returns ------- Pyomo.Constraint or Constraint.Skip - A constraint enforcing the inventory balance for the material flowing through stream 13 at the second inventory node from the second period onwards. + A constraint enforcing the inventory balance for the material flowing through stream 13 at the second inventory node from the second period onwards. For the first period, the function returns `Constraint.Skip`, indicating the constraint does not apply. """ if t <= 1: return Constraint.Skip - return model.InventoryLevel[13, t-1] + model.FlowRate[10, t] + \ - model.FlowRate[11,t] == model.InventoryLevel[13, t] + model.FlowRate[13, t] - model.inventory_balance3 = Constraint(model.TimePeriods, rule=inventory_balance_rule3, doc='Inventory balance for material associated with stream 13 at the second inventory node') + return ( + model.InventoryLevel[13, t - 1] + + model.FlowRate[10, t] + + model.FlowRate[11, t] + == model.InventoryLevel[13, t] + model.FlowRate[13, t] + ) + + model.inventory_balance3 = Constraint( + model.TimePeriods, + rule=inventory_balance_rule3, + doc='Inventory balance for material associated with stream 13 at the second inventory node', + ) # Max capacities of inventories def inventory_capacity_rule(model, j, t): @@ -1003,11 +1313,17 @@ def inventory_capacity_rule(model, j, t): Returns ------- Pyomo.Constraint - A constraint that sets the maximum permissible inventory level for material j in time period t, ensuring that the inventory does not exceed the predefined upper bound 'InventoryLevelUB[j, t]'. + A constraint that sets the maximum permissible inventory level for material j in time period t, ensuring that the inventory does not exceed the predefined upper bound 'InventoryLevelUB[j, t]'. This maintains the model's alignment with practical storage limitations. """ - return model.InventoryLevel[j,t] <= model.InventoryLevelUB[j,t] - model.inventory_capacity_rule = Constraint(model.Products, model.TimePeriods, rule=inventory_capacity_rule, doc='Maximum inventory capacity for each material j at each time period t') + return model.InventoryLevel[j, t] <= model.InventoryLevelUB[j, t] + + model.inventory_capacity_rule = Constraint( + model.Products, + model.TimePeriods, + rule=inventory_capacity_rule, + doc='Maximum inventory capacity for each material j at each time period t', + ) # Shortfall calculation def shortfall_rule(model, j, t): @@ -1026,11 +1342,20 @@ def shortfall_rule(model, j, t): Returns ------- Pyomo.Constraint - A constraint defining the shortfall for product j during time period t as the difference between the supply or demand upper bound and the actual flow rate. + A constraint defining the shortfall for product j during time period t as the difference between the supply or demand upper bound and the actual flow rate. This calculation is pivotal for evaluating performance and identifying bottlenecks or excess capacities within the supply chain. """ - return model.Shortfall[j, t] == model.SupplyAndDemandUBs[j, t] - model.FlowRate[j,t] - model.shortfall = Constraint(model.Products, model.TimePeriods, rule=shortfall_rule, doc='Shortfall calculation for each product j in each time period t') + return ( + model.Shortfall[j, t] + == model.SupplyAndDemandUBs[j, t] - model.FlowRate[j, t] + ) + + model.shortfall = Constraint( + model.Products, + model.TimePeriods, + rule=shortfall_rule, + doc='Shortfall calculation for each product j in each time period t', + ) # maximum shortfall allowed def shortfall_max_rule(model, j, t): @@ -1049,11 +1374,17 @@ def shortfall_max_rule(model, j, t): Returns ------- Pyomo.Constraint - A constraint that limits the shortfall for product j during time period t to a maximum value specified by 'ShortfallUB[j, t]'. + A constraint that limits the shortfall for product j during time period t to a maximum value specified by 'ShortfallUB[j, t]'. This constraint is instrumental in aligning the model's solutions with real-world operational constraints and strategic objectives. """ return model.Shortfall[j, t] <= model.ShortfallUB[j, t] - model.shortfall_max = Constraint(model.Products, model.TimePeriods, rule=shortfall_max_rule, doc='Maximum shortfall allowed for each product j in each time period t') + + model.shortfall_max = Constraint( + model.Products, + model.TimePeriods, + rule=shortfall_max_rule, + doc='Maximum shortfall allowed for each product j in each time period t', + ) # maxiumum capacities of suppliers def supplier_capacity_rule(model, j, t): @@ -1072,11 +1403,17 @@ def supplier_capacity_rule(model, j, t): Returns ------- Pyomo.Constraint - A constraint that limits the flow rate of raw material j from suppliers in time period t to not exceed the predefined upper bound 'SupplyAndDemandUBs[j, t]'. + A constraint that limits the flow rate of raw material j from suppliers in time period t to not exceed the predefined upper bound 'SupplyAndDemandUBs[j, t]'. This constraint is crucial for ensuring the feasibility of the supply chain model and its alignment with practical supply capabilities. """ return model.FlowRate[j, t] <= model.SupplyAndDemandUBs[j, t] - model.supplier_capacity = Constraint(model.RawMaterials, model.TimePeriods, rule=supplier_capacity_rule, doc='Maximum supply capacity for each raw material j in each time period t') + + model.supplier_capacity = Constraint( + model.RawMaterials, + model.TimePeriods, + rule=supplier_capacity_rule, + doc='Maximum supply capacity for each raw material j in each time period t', + ) # demand upper bound def demand_UB_rule(model, j, t): @@ -1097,8 +1434,14 @@ def demand_UB_rule(model, j, t): Pyomo.Constraint A constraint limiting the flow rate of product j to not exceed the predefined maximum demand 'SupplyAndDemandUBs[j, t]' in time period t, ensuring production is demand-driven. """ - return model.FlowRate[j, t] <= model.SupplyAndDemandUBs[j,t] - model.demand_UB = Constraint(model.Products, model.TimePeriods, rule=demand_UB_rule, doc='Maximum demand allowed for each product j in each time period t') + return model.FlowRate[j, t] <= model.SupplyAndDemandUBs[j, t] + + model.demand_UB = Constraint( + model.Products, + model.TimePeriods, + rule=demand_UB_rule, + doc='Maximum demand allowed for each product j in each time period t', + ) # demand lower bound def demand_LB_rule(model, j, t): @@ -1119,9 +1462,14 @@ def demand_LB_rule(model, j, t): Pyomo.Constraint A constraint ensuring that the flow rate of product j meets or exceeds the minimum demand 'DemandLB[j, t]' in time period t, supporting effective market engagement. """ - return model.FlowRate[j, t] >= model.DemandLB[j,t] - model.demand_LB = Constraint(model.Products, model.TimePeriods, rule=demand_LB_rule, doc='Minimum demand required for each product j in each time period t') + return model.FlowRate[j, t] >= model.DemandLB[j, t] + model.demand_LB = Constraint( + model.Products, + model.TimePeriods, + rule=demand_LB_rule, + doc='Minimum demand required for each product j in each time period t', + ) # FIXED PRICE CONTRACT @@ -1130,10 +1478,10 @@ def FP_contract_disjunct_rule(disjunct, j, t, buy): """ Defines disjunctive constraints for procurement decisions under a Fixed Price (FP) contract for material j in time period t. - A decision must be made whether to engage in purchasing under the contract terms or not for each material in each time period. - This function encapsulates the disjunctive nature of this decision: if the decision is to buy ('buy' parameter is True), - the amount purchased under the FP contract is limited by a predefined maximum ('MAX_AMOUNT_FP'); - otherwise, no purchase is made under the FP contract for that material and period. + A decision must be made whether to engage in purchasing under the contract terms or not for each material in each time period. + This function encapsulates the disjunctive nature of this decision: if the decision is to buy ('buy' parameter is True), + the amount purchased under the FP contract is limited by a predefined maximum ('MAX_AMOUNT_FP'); + otherwise, no purchase is made under the FP contract for that material and period. This disjunctive approach allows for modeling complex decision-making processes in procurement strategies. Parameters @@ -1153,11 +1501,19 @@ def FP_contract_disjunct_rule(disjunct, j, t, buy): """ model = disjunct.model() if buy: - disjunct.c = Constraint(expr=model.AmountPurchased_FP[j,t] <= MAX_AMOUNT_FP) + disjunct.c = Constraint( + expr=model.AmountPurchased_FP[j, t] <= MAX_AMOUNT_FP + ) else: - disjunct.c = Constraint(expr=model.AmountPurchased_FP[j,t] == 0) - model.FP_contract_disjunct = Disjunct(model.RawMaterials, model.TimePeriods, - model.BuyFPContract, rule=FP_contract_disjunct_rule, doc='Disjunctive constraints for Fixed Price contract buying options') + disjunct.c = Constraint(expr=model.AmountPurchased_FP[j, t] == 0) + + model.FP_contract_disjunct = Disjunct( + model.RawMaterials, + model.TimePeriods, + model.BuyFPContract, + rule=FP_contract_disjunct_rule, + doc='Disjunctive constraints for Fixed Price contract buying options', + ) # Fixed price disjunction def FP_contract_rule(model, j, t): @@ -1180,9 +1536,14 @@ def FP_contract_rule(model, j, t): Pyomo.Disjunction A disjunction that represents the decision-making point for FP contract purchases, contributing to the model's overall procurement strategy. """ - return [model.FP_contract_disjunct[j,t,buy] for buy in model.BuyFPContract] - model.FP_disjunction = Disjunction(model.RawMaterials, model.TimePeriods, - rule=FP_contract_rule, doc='Disjunction for Fixed Price contract buying options') + return [model.FP_contract_disjunct[j, t, buy] for buy in model.BuyFPContract] + + model.FP_disjunction = Disjunction( + model.RawMaterials, + model.TimePeriods, + rule=FP_contract_rule, + doc='Disjunction for Fixed Price contract buying options', + ) # cost constraint for fixed price contract (independent constraint) def FP_contract_cost_rule(model, j, t): @@ -1202,20 +1563,25 @@ def FP_contract_cost_rule(model, j, t): Pyomo.Constraint A constraint equating the FP contract cost for material j in period t to the product of the purchased amount and its price, ensuring accurate financial accounting in the model. """ - return model.Cost_FP[j,t] == model.AmountPurchased_FP[j,t] * \ - model.Prices[j,t] - model.FP_contract_cost = Constraint(model.RawMaterials, model.TimePeriods, - rule=FP_contract_cost_rule, doc='Cost constraint for Fixed Price contract') + return ( + model.Cost_FP[j, t] == model.AmountPurchased_FP[j, t] * model.Prices[j, t] + ) + model.FP_contract_cost = Constraint( + model.RawMaterials, + model.TimePeriods, + rule=FP_contract_cost_rule, + doc='Cost constraint for Fixed Price contract', + ) # DISCOUNT CONTRACT # Disjunction for Discount contract def discount_contract_disjunct_rule(disjunct, j, t, buy): """ - Sets rules for purchasing materials j in time t under discount contracts based on the buying decision 'buy'. + Sets rules for purchasing materials j in time t under discount contracts based on the buying decision 'buy'. - For discount contracts, the decision involves purchasing below or above a minimum amount for a discount, or not selecting the contract at all. + For discount contracts, the decision involves purchasing below or above a minimum amount for a discount, or not selecting the contract at all. This rule reflects these choices by adjusting purchasing amounts and enforcing corresponding constraints. Parameters @@ -1227,30 +1593,42 @@ def discount_contract_disjunct_rule(disjunct, j, t, buy): Index of time period. buy : str Decision on purchasing strategy: 'BelowMin', 'AboveMin', or 'NotSelected'. - + """ model = disjunct.model() if buy == 'BelowMin': disjunct.belowMin = Constraint( - expr=model.AmountPurchasedBelowMin_Discount[j,t] <= \ - model.MinAmount_Discount[j,t]) + expr=model.AmountPurchasedBelowMin_Discount[j, t] + <= model.MinAmount_Discount[j, t] + ) disjunct.aboveMin = Constraint( - expr=model.AmountPurchasedAboveMin_Discount[j,t] == 0) + expr=model.AmountPurchasedAboveMin_Discount[j, t] == 0 + ) elif buy == 'AboveMin': disjunct.belowMin = Constraint( - expr=model.AmountPurchasedBelowMin_Discount[j,t] == \ - model.MinAmount_Discount[j,t]) + expr=model.AmountPurchasedBelowMin_Discount[j, t] + == model.MinAmount_Discount[j, t] + ) disjunct.aboveMin = Constraint( - expr=model.AmountPurchasedAboveMin_Discount[j,t] >= 0) + expr=model.AmountPurchasedAboveMin_Discount[j, t] >= 0 + ) elif buy == 'NotSelected': disjunct.belowMin = Constraint( - expr=model.AmountPurchasedBelowMin_Discount[j,t] == 0) + expr=model.AmountPurchasedBelowMin_Discount[j, t] == 0 + ) disjunct.aboveMin = Constraint( - expr=model.AmountPurchasedAboveMin_Discount[j,t] == 0) + expr=model.AmountPurchasedAboveMin_Discount[j, t] == 0 + ) else: raise RuntimeError("Unrecognized choice for discount contract: %s" % buy) - model.discount_contract_disjunct = Disjunct(model.RawMaterials, model.TimePeriods, - model.BuyDiscountContract, rule=discount_contract_disjunct_rule, doc='Disjunctive constraints for Discount contract buying options') + + model.discount_contract_disjunct = Disjunct( + model.RawMaterials, + model.TimePeriods, + model.BuyDiscountContract, + rule=discount_contract_disjunct_rule, + doc='Disjunctive constraints for Discount contract buying options', + ) # Discount contract disjunction def discount_contract_rule(model, j, t): @@ -1273,10 +1651,17 @@ def discount_contract_rule(model, j, t): Pyomo.Disjunction The disjunction representing the choice among discount contract purchasing strategies. """ - return [model.discount_contract_disjunct[j,t,buy] \ - for buy in model.BuyDiscountContract] - model.discount_contract = Disjunction(model.RawMaterials, model.TimePeriods, - rule=discount_contract_rule, doc='Disjunction for Discount contract buying options') + return [ + model.discount_contract_disjunct[j, t, buy] + for buy in model.BuyDiscountContract + ] + + model.discount_contract = Disjunction( + model.RawMaterials, + model.TimePeriods, + rule=discount_contract_rule, + doc='Disjunction for Discount contract buying options', + ) # cost constraint for discount contract (independent constraint) def discount_cost_rule(model, j, t): @@ -1299,12 +1684,20 @@ def discount_cost_rule(model, j, t): Pyomo.Constraint The constraint that calculates the total cost of purchases under discount contracts. """ - return model.Cost_Discount[j,t] == model.RegPrice_Discount[j,t] * \ - model.AmountPurchasedBelowMin_Discount[j,t] + \ - model.DiscountPrice_Discount[j,t] * model.AmountPurchasedAboveMin_Discount[j,t] - model.discount_cost = Constraint(model.RawMaterials, model.TimePeriods, - rule=discount_cost_rule, doc='Cost constraint for Discount contract') - + return ( + model.Cost_Discount[j, t] + == model.RegPrice_Discount[j, t] + * model.AmountPurchasedBelowMin_Discount[j, t] + + model.DiscountPrice_Discount[j, t] + * model.AmountPurchasedAboveMin_Discount[j, t] + ) + + model.discount_cost = Constraint( + model.RawMaterials, + model.TimePeriods, + rule=discount_cost_rule, + doc='Cost constraint for Discount contract', + ) # BULK CONTRACT @@ -1313,7 +1706,7 @@ def bulk_contract_disjunct_rule(disjunct, j, t, buy): """ Defines conditions for bulk purchases of material j at time t based on the decision 'buy'. - This rule determines how much of a material is bought under a bulk contract and at what price, based on whether purchases are below or above a specified minimum amount, or if the bulk option is not selected. + This rule determines how much of a material is bought under a bulk contract and at what price, based on whether purchases are below or above a specified minimum amount, or if the bulk option is not selected. It enforces different constraints for the amount and cost of materials under these scenarios. Parameters @@ -1331,30 +1724,40 @@ def bulk_contract_disjunct_rule(disjunct, j, t, buy): model = disjunct.model() if buy == 'BelowMin': disjunct.amount = Constraint( - expr=model.AmountPurchased_Bulk[j,t] <= model.MinAmount_Bulk[j,t]) + expr=model.AmountPurchased_Bulk[j, t] <= model.MinAmount_Bulk[j, t] + ) disjunct.price = Constraint( - expr=model.Cost_Bulk[j,t] == model.RegPrice_Bulk[j,t] * \ - model.AmountPurchased_Bulk[j,t]) + expr=model.Cost_Bulk[j, t] + == model.RegPrice_Bulk[j, t] * model.AmountPurchased_Bulk[j, t] + ) elif buy == 'AboveMin': disjunct.amount = Constraint( - expr=model.AmountPurchased_Bulk[j,t] >= model.MinAmount_Bulk[j,t]) + expr=model.AmountPurchased_Bulk[j, t] >= model.MinAmount_Bulk[j, t] + ) disjunct.price = Constraint( - expr=model.Cost_Bulk[j,t] == model.DiscountPrice_Bulk[j,t] * \ - model.AmountPurchased_Bulk[j,t]) + expr=model.Cost_Bulk[j, t] + == model.DiscountPrice_Bulk[j, t] * model.AmountPurchased_Bulk[j, t] + ) elif buy == 'NotSelected': - disjunct.amount = Constraint(expr=model.AmountPurchased_Bulk[j,t] == 0) - disjunct.price = Constraint(expr=model.Cost_Bulk[j,t] == 0) + disjunct.amount = Constraint(expr=model.AmountPurchased_Bulk[j, t] == 0) + disjunct.price = Constraint(expr=model.Cost_Bulk[j, t] == 0) else: raise RuntimeError("Unrecognized choice for bulk contract: %s" % buy) - model.bulk_contract_disjunct = Disjunct(model.RawMaterials, model.TimePeriods, - model.BuyBulkContract, rule=bulk_contract_disjunct_rule, doc='Disjunctive constraints for Bulk contract buying options') + + model.bulk_contract_disjunct = Disjunct( + model.RawMaterials, + model.TimePeriods, + model.BuyBulkContract, + rule=bulk_contract_disjunct_rule, + doc='Disjunctive constraints for Bulk contract buying options', + ) # Bulk contract disjunction def bulk_contract_rule(model, j, t): """ Establishes a decision-making framework for bulk purchases, allowing the model to choose among predefined scenarios. - This function sets up a flexible structure for deciding on bulk purchases. + This function sets up a flexible structure for deciding on bulk purchases. Each material and time period can be evaluated independently, allowing the model to adapt to various conditions and optimize procurement strategies under bulk contracts. Parameters @@ -1371,16 +1774,22 @@ def bulk_contract_rule(model, j, t): Pyomo.Disjunction A set of disjunctive conditions that the model can choose from when making bulk purchasing decisions. """ - return [model.bulk_contract_disjunct[j,t,buy] for buy in model.BuyBulkContract] - model.bulk_contract = Disjunction(model.RawMaterials, model.TimePeriods, - rule=bulk_contract_rule, doc='Disjunction for Bulk contract buying options') + return [ + model.bulk_contract_disjunct[j, t, buy] for buy in model.BuyBulkContract + ] + model.bulk_contract = Disjunction( + model.RawMaterials, + model.TimePeriods, + rule=bulk_contract_rule, + doc='Disjunction for Bulk contract buying options', + ) # FIXED DURATION CONTRACT def FD_1mo_contract(disjunct, j, t): """ - Defines the constraints for engaging in a 1-month fixed duration contract for material j at time t. + Defines the constraints for engaging in a 1-month fixed duration contract for material j at time t. This includes a minimum purchase amount and the cost calculation based on contract-specific prices. Parameters @@ -1393,16 +1802,24 @@ def FD_1mo_contract(disjunct, j, t): Index of time period. """ model = disjunct.model() - disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] >= \ - MIN_AMOUNT_FD_1MONTH) - disjunct.price1 = Constraint(expr=model.Cost_FD[j,t] == \ - model.Prices_Length[j,1,t] * model.AmountPurchased_FD[j,t]) + disjunct.amount1 = Constraint( + expr=model.AmountPurchased_FD[j, t] >= MIN_AMOUNT_FD_1MONTH + ) + disjunct.price1 = Constraint( + expr=model.Cost_FD[j, t] + == model.Prices_Length[j, 1, t] * model.AmountPurchased_FD[j, t] + ) + model.FD_1mo_contract = Disjunct( - model.RawMaterials, model.TimePeriods, rule=FD_1mo_contract, doc='1-month fixed duration contract') + model.RawMaterials, + model.TimePeriods, + rule=FD_1mo_contract, + doc='1-month fixed duration contract', + ) def FD_2mo_contract(disjunct, j, t): """ - Establishes conditions for a 2-month fixed duration contract. + Establishes conditions for a 2-month fixed duration contract. This involves a minimum purchase requirement for two consecutive periods and corresponding cost calculations. Parameters @@ -1415,18 +1832,29 @@ def FD_2mo_contract(disjunct, j, t): Index of time period. """ model = disjunct.model() - disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] >= \ - model.MinAmount_Length[j,2]) - disjunct.price1 = Constraint(expr=model.Cost_FD[j,t] == \ - model.Prices_Length[j,2,t] * model.AmountPurchased_FD[j,t]) - # only enforce these if we aren't in the last time period + disjunct.amount1 = Constraint( + expr=model.AmountPurchased_FD[j, t] >= model.MinAmount_Length[j, 2] + ) + disjunct.price1 = Constraint( + expr=model.Cost_FD[j, t] + == model.Prices_Length[j, 2, t] * model.AmountPurchased_FD[j, t] + ) + # only enforce these if we aren't in the last time period if t < model.TimePeriods[-1]: - disjunct.amount2 = Constraint(expr=model.AmountPurchased_FD[j, t+1] >= \ - model.MinAmount_Length[j,2]) - disjunct.price2 = Constraint(expr=model.Cost_FD[j,t+1] == \ - model.Prices_Length[j,2,t] * model.AmountPurchased_FD[j, t+1]) + disjunct.amount2 = Constraint( + expr=model.AmountPurchased_FD[j, t + 1] >= model.MinAmount_Length[j, 2] + ) + disjunct.price2 = Constraint( + expr=model.Cost_FD[j, t + 1] + == model.Prices_Length[j, 2, t] * model.AmountPurchased_FD[j, t + 1] + ) + model.FD_2mo_contract = Disjunct( - model.RawMaterials, model.TimePeriods, rule=FD_2mo_contract, doc='2-month fixed duration contract') + model.RawMaterials, + model.TimePeriods, + rule=FD_2mo_contract, + doc='2-month fixed duration contract', + ) def FD_3mo_contract(disjunct, j, t): """ @@ -1444,28 +1872,42 @@ def FD_3mo_contract(disjunct, j, t): model = disjunct.model() # NOTE: I think there is a mistake in the GAMS file in line 327. # they use the bulk minamount rather than the length one. - #I am doing the same here for validation purposes. - disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] >= \ - model.MinAmount_Bulk[j,3]) - disjunct.cost1 = Constraint(expr=model.Cost_FD[j,t] == \ - model.Prices_Length[j,3,t] * model.AmountPurchased_FD[j,t]) + # I am doing the same here for validation purposes. + disjunct.amount1 = Constraint( + expr=model.AmountPurchased_FD[j, t] >= model.MinAmount_Bulk[j, 3] + ) + disjunct.cost1 = Constraint( + expr=model.Cost_FD[j, t] + == model.Prices_Length[j, 3, t] * model.AmountPurchased_FD[j, t] + ) # check we aren't in one of the last two time periods if t < model.TimePeriods[-1]: - disjunct.amount2 = Constraint(expr=model.AmountPurchased_FD[j,t+1] >= \ - model.MinAmount_Length[j,3]) - disjunct.cost2 = Constraint(expr=model.Cost_FD[j,t+1] == \ - model.Prices_Length[j,3,t] * model.AmountPurchased_FD[j,t+1]) + disjunct.amount2 = Constraint( + expr=model.AmountPurchased_FD[j, t + 1] >= model.MinAmount_Length[j, 3] + ) + disjunct.cost2 = Constraint( + expr=model.Cost_FD[j, t + 1] + == model.Prices_Length[j, 3, t] * model.AmountPurchased_FD[j, t + 1] + ) if t < model.TimePeriods[-2]: - disjunct.amount3 = Constraint(expr=model.AmountPurchased_FD[j,t+2] >= \ - model.MinAmount_Length[j,3]) - disjunct.cost3 = Constraint(expr=model.Cost_FD[j,t+2] == \ - model.Prices_Length[j,3,t] * model.AmountPurchased_FD[j,t+2]) + disjunct.amount3 = Constraint( + expr=model.AmountPurchased_FD[j, t + 2] >= model.MinAmount_Length[j, 3] + ) + disjunct.cost3 = Constraint( + expr=model.Cost_FD[j, t + 2] + == model.Prices_Length[j, 3, t] * model.AmountPurchased_FD[j, t + 2] + ) + model.FD_3mo_contract = Disjunct( - model.RawMaterials, model.TimePeriods, rule=FD_3mo_contract, doc='3-month fixed duration contract') + model.RawMaterials, + model.TimePeriods, + rule=FD_3mo_contract, + doc='3-month fixed duration contract', + ) def FD_no_contract(disjunct, j, t): """ - Represents the scenario where no fixed duration contract is selected for material j at time t. + Represents the scenario where no fixed duration contract is selected for material j at time t. Ensures no purchases or costs are accounted for under FD contracts. Parameters @@ -1478,16 +1920,21 @@ def FD_no_contract(disjunct, j, t): Index of time period. """ model = disjunct.model() - disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] == 0) - disjunct.cost1 = Constraint(expr=model.Cost_FD[j,t] == 0) + disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j, t] == 0) + disjunct.cost1 = Constraint(expr=model.Cost_FD[j, t] == 0) if t < model.TimePeriods[-1]: - disjunct.amount2 = Constraint(expr=model.AmountPurchased_FD[j,t+1] == 0) - disjunct.cost2 = Constraint(expr=model.Cost_FD[j,t+1] == 0) + disjunct.amount2 = Constraint(expr=model.AmountPurchased_FD[j, t + 1] == 0) + disjunct.cost2 = Constraint(expr=model.Cost_FD[j, t + 1] == 0) if t < model.TimePeriods[-2]: - disjunct.amount3 = Constraint(expr=model.AmountPurchased_FD[j,t+2] == 0) - disjunct.cost3 = Constraint(expr=model.Cost_FD[j,t+2] == 0) + disjunct.amount3 = Constraint(expr=model.AmountPurchased_FD[j, t + 2] == 0) + disjunct.cost3 = Constraint(expr=model.Cost_FD[j, t + 2] == 0) + model.FD_no_contract = Disjunct( - model.RawMaterials, model.TimePeriods, rule=FD_no_contract, doc='No fixed duration contract') + model.RawMaterials, + model.TimePeriods, + rule=FD_no_contract, + doc='No fixed duration contract', + ) def FD_contract(model, j, t): """ @@ -1507,10 +1954,19 @@ def FD_contract(model, j, t): Pyomo.Disjunction The disjunctive decision structure for FD contracts. """ - return [ model.FD_1mo_contract[j,t], model.FD_2mo_contract[j,t], - model.FD_3mo_contract[j,t], model.FD_no_contract[j,t], ] - model.FD_contract = Disjunction(model.RawMaterials, model.TimePeriods, - rule=FD_contract, doc='Fixed duration contract scenarios') + return [ + model.FD_1mo_contract[j, t], + model.FD_2mo_contract[j, t], + model.FD_3mo_contract[j, t], + model.FD_no_contract[j, t], + ] + + model.FD_contract = Disjunction( + model.RawMaterials, + model.TimePeriods, + rule=FD_contract, + doc='Fixed duration contract scenarios', + ) return model @@ -1524,11 +1980,15 @@ def build_concrete(): 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')) + return build_model().create_instance( + join(this_file_dir(), 'med_term_purchasing.dat') + ) if __name__ == "__main__": m = build_concrete() 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.profit.display() From d93a4663b679cc1437f2719ed90bf422605dfaf6 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 23 Apr 2024 12:19:54 -0400 Subject: [PATCH 047/124] black formatting --- gdplib/pyomo_examples/med_term_purchasing.py | 28 ++++++++------------ 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/gdplib/pyomo_examples/med_term_purchasing.py b/gdplib/pyomo_examples/med_term_purchasing.py index 4e05b5f..c2a0bab 100644 --- a/gdplib/pyomo_examples/med_term_purchasing.py +++ b/gdplib/pyomo_examples/med_term_purchasing.py @@ -34,24 +34,18 @@ def build_model(): """ - Build a Pyomo abstract model for the medium-term purchasing contracts problem. + Build a Pyomo abstract model for the medium-term purchasing contracts problem. - Returns - ------- - Pyomo.AbstractModel - Pyomo abstract model for medium-term purchasing contracts problem. - - References - ---------- -<<<<<<< HEAD - [1] Vecchietti, A., & Grossmann, I. (2004). Computational experience with logmip solving linear and nonlinear disjunctive programming problems. Proc. of FOCAPD, 587–590. - [2] Vecchietti, A., Lee, S., & Grossmann, I. E. (2003). Modeling of discrete/continuous optimization problems: characterization and formulation of disjunctions and their relaxations. Computers & Chemical Engineering, 27(3), 433–448. DOI: 10.1016/S0098-1354(02)00220-X - [3] 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 -======= - [1] Vecchietti, Aldo, and I. Grossmann. "Computational experience with logmip solving linear and nonlinear disjunctive programming problems." In Proc. of FOCAPD, pp. 587-590. 2004. - [2] Vecchietti, A., S. Lee and I.E. Grossmann, “Modeling of Discrete/Continuous Optimization Problems: Characterization and Formulation of Disjunctions and their Relaxations,” Computers and Chemical Engineering 27, 433-448 (2003). https://doi.org/10.1016/S0098-1354(02)00220-X - [3] 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. https://doi.org/10.1021/ie0513144 ->>>>>>> a04daf9 (Added References) + Returns + ------- + Pyomo.AbstractModel + Pyomo abstract model for medium-term purchasing contracts problem. + + References + ---------- + [1] Vecchietti, Aldo, and I. Grossmann. "Computational experience with logmip solving linear and nonlinear disjunctive programming problems." In Proc. of FOCAPD, pp. 587-590. 2004. + [2] Vecchietti, A., S. Lee and I.E. Grossmann, “Modeling of Discrete/Continuous Optimization Problems: Characterization and Formulation of Disjunctions and their Relaxations,” Computers and Chemical Engineering 27, 433-448 (2003). https://doi.org/10.1016/S0098-1354(02)00220-X + [3] 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. https://doi.org/10.1021/ie0513144 """ model = AbstractModel() From 67ddd73b2efbfeaa207b29bf4fb9c3a760b5f979 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 23 Apr 2024 14:26:19 -0400 Subject: [PATCH 048/124] Added Reference DOI --- gdplib/pyomo_examples/batch_processing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gdplib/pyomo_examples/batch_processing.py b/gdplib/pyomo_examples/batch_processing.py index d481199..04600e8 100644 --- a/gdplib/pyomo_examples/batch_processing.py +++ b/gdplib/pyomo_examples/batch_processing.py @@ -45,8 +45,8 @@ def build_model(): References ---------- - Ravemark, E. Optimization models for design and operation of chemical batch processes. Ph.D. Thesis, ETH Zurich, 1995. - 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. + [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() From c122d1d8010197d091e3eb5d36a2ca2a6efdbce4 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 23 Apr 2024 15:08:52 -0400 Subject: [PATCH 049/124] Add doc on the Parameters --- gdplib/pyomo_examples/batch_processing.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gdplib/pyomo_examples/batch_processing.py b/gdplib/pyomo_examples/batch_processing.py index 04600e8..55cae74 100644 --- a/gdplib/pyomo_examples/batch_processing.py +++ b/gdplib/pyomo_examples/batch_processing.py @@ -108,14 +108,14 @@ def filter_out_last(model, j): model.Beta1 = Param(doc='Exponent Parameter of the units') model.Beta2 = Param(doc='Exponent Parameter of the intermediate storage tanks') - model.ProductionAmount = Param(model.PRODUCTS) - model.ProductSizeFactor = Param(model.PRODUCTS, model.STAGES) - model.ProcessingTime = Param(model.PRODUCTS, model.STAGES) + model.ProductionAmount = Param(model.PRODUCTS, doc='Production Amount') + 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) + model.StorageTankSizeFactor = Param(model.STAGES, default=StorageTankSizeFactor, doc='Storage Tank Size Factor') model.StorageTankSizeFactorByProd = Param(model.PRODUCTS, model.STAGES, - default=StorageTankSizeFactorByProd) + 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. From 64202f40904c973f7157907e649fb7ca2a27854e Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Tue, 23 Apr 2024 19:54:50 -0400 Subject: [PATCH 050/124] 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 051/124] 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 052/124] 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 053/124] 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 054/124] 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 055/124] 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 056/124] 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 057/124] 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 a57b32dbcf3a4a3797ee515bd9e3c66ad0eeced0 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 25 Apr 2024 12:25:30 -0400 Subject: [PATCH 058/124] Revert Back into Carolina's reference documentation and black formatting. --- gdplib/pyomo_examples/med_term_purchasing.py | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/gdplib/pyomo_examples/med_term_purchasing.py b/gdplib/pyomo_examples/med_term_purchasing.py index c2a0bab..47af318 100644 --- a/gdplib/pyomo_examples/med_term_purchasing.py +++ b/gdplib/pyomo_examples/med_term_purchasing.py @@ -34,18 +34,18 @@ def build_model(): """ - Build a Pyomo abstract model for the medium-term purchasing contracts problem. + Build a Pyomo abstract model for the medium-term purchasing contracts problem. - Returns - ------- - Pyomo.AbstractModel - Pyomo abstract model for medium-term purchasing contracts problem. - - References - ---------- - [1] Vecchietti, Aldo, and I. Grossmann. "Computational experience with logmip solving linear and nonlinear disjunctive programming problems." In Proc. of FOCAPD, pp. 587-590. 2004. - [2] Vecchietti, A., S. Lee and I.E. Grossmann, “Modeling of Discrete/Continuous Optimization Problems: Characterization and Formulation of Disjunctions and their Relaxations,” Computers and Chemical Engineering 27, 433-448 (2003). https://doi.org/10.1016/S0098-1354(02)00220-X - [3] 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. https://doi.org/10.1021/ie0513144 + Returns + ------- + Pyomo.AbstractModel + Pyomo abstract model for medium-term purchasing contracts problem. + + References + ---------- + [1] Vecchietti, A., & Grossmann, I. (2004). Computational experience with logmip solving linear and nonlinear disjunctive programming problems. Proc. of FOCAPD, 587–590. + [2] Vecchietti, A., Lee, S., & Grossmann, I. E. (2003). Modeling of discrete/continuous optimization problems: characterization and formulation of disjunctions and their relaxations. Computers & Chemical Engineering, 27(3), 433–448. DOI: 10.1016/S0098-1354(02)00220-X + [3] 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() From 56ff1d84f3fca9c289fd02a02b7bff2dca507717 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Thu, 25 Apr 2024 23:12:51 -0400 Subject: [PATCH 059/124] 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 060/124] 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 061/124] 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 062/124] 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 063/124] 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 064/124] 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 065/124] 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 066/124] 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 067/124] 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 068/124] 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 069/124] 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 070/124] 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 071/124] 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 df40046be4387c46e0ab5009a114634106e546e4 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Mon, 6 May 2024 14:58:15 -0400 Subject: [PATCH 072/124] Removed the unnecesary references. --- gdplib/pyomo_examples/med_term_purchasing.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/gdplib/pyomo_examples/med_term_purchasing.py b/gdplib/pyomo_examples/med_term_purchasing.py index 47af318..9377735 100644 --- a/gdplib/pyomo_examples/med_term_purchasing.py +++ b/gdplib/pyomo_examples/med_term_purchasing.py @@ -43,9 +43,8 @@ def build_model(): References ---------- - [1] Vecchietti, A., & Grossmann, I. (2004). Computational experience with logmip solving linear and nonlinear disjunctive programming problems. Proc. of FOCAPD, 587–590. - [2] Vecchietti, A., Lee, S., & Grossmann, I. E. (2003). Modeling of discrete/continuous optimization problems: characterization and formulation of disjunctions and their relaxations. Computers & Chemical Engineering, 27(3), 433–448. DOI: 10.1016/S0098-1354(02)00220-X - [3] 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 + [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() From bffffdbd57ae11394b0a8f689ad8f24ec1e5cb5a Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Mon, 6 May 2024 17:01:01 -0400 Subject: [PATCH 073/124] 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 074/124] 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 075/124] 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 076/124] 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 077/124] 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 078/124] 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 079/124] 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 080/124] 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 081/124] 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 082/124] 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 083/124] 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 084/124] 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 085/124] 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 086/124] 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 087/124] 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 088/124] 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 089/124] 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 090/124] 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 091/124] 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 092/124] 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 093/124] 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 094/124] 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 095/124] 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 096/124] 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 097/124] 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 098/124] 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 099/124] 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 100/124] 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 101/124] 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 102/124] 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 103/124] 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 104/124] 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 105/124] 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 106/124] 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 107/124] 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 108/124] 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 109/124] 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): """ From b308fb05dffd30a601dda9c958662a75110a9c69 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Fri, 17 May 2024 17:33:53 -0400 Subject: [PATCH 110/124] Added the gdp_reactor file on the gdp lib. --- gdplib/cstr/gdp_reactor.py | 741 +++++++++++++++++++++++++++++++++++++ 1 file changed, 741 insertions(+) create mode 100644 gdplib/cstr/gdp_reactor.py diff --git a/gdplib/cstr/gdp_reactor.py b/gdplib/cstr/gdp_reactor.py new file mode 100644 index 0000000..c34d35e --- /dev/null +++ b/gdplib/cstr/gdp_reactor.py @@ -0,0 +1,741 @@ +import os +import sys +import pyomo.environ as pyo +from pyomo.core.base.misc import display +from pyomo.gdp import Disjunct, Disjunction +from pyomo.opt.base.solvers import SolverFactory + + +def build_cstrs(NT: int = 5) -> pyo.ConcreteModel(): + """ + Function that builds CSTR superstructure model of size NT. + The CSTRs have a single 1st order reaction A -> B and minimizes (TODO Check) + total reactor volume. The optimal solution should yield NT reactors with a recycle before reactor NT. + Reference: Paper Linhan 1. TODO Correct reference + + Args: + NT: int. Positive Integer defining the maximum number of CSTRs + Returns: + m = Pyomo GDP model + """ + + # PYOMO MODEL + m = pyo.ConcreteModel(name='gdp_reactors') + + # SETS + m.I = pyo.Set(initialize=['A', 'B'], doc='Set of components') + m.N = pyo.RangeSet(1, NT, doc='Set of units in the superstructure') + + # PARAMETERS + m.k = pyo.Param(initialize=2, ) # Kinetic constant [L/(mol*s)] + m.order1 = pyo.Param(initialize=1) # Partial order of reacton 1 + m.order2 = pyo.Param(initialize=1) # Partial order of reaction 2 + m.QF0 = pyo.Param(initialize=1) # Inlet volumetric flow [L/s] + C0_Def = {'A': 0.99, 'B': 0.01} + + # Initial concentration of reagents [mol/L] + m.C0 = pyo.Param(m.I, initialize=C0_Def) + + # Inlet molar flow [mol/s] + + def F0_Def(m, i): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + i : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + return m.C0[i]*m.QF0 + m.F0 = pyo.Param(m.I, initialize=F0_Def) + + # BOOLEAN VARIABLES + + # Unreacted feed in reactor n + m.YF = pyo.BooleanVar(m.N) + + # Existence of recycle flow in unit n + m.YR = pyo.BooleanVar(m.N) + + # Unit operation in n (True if unit n is a CSTR, False if unit n is a bypass) + m.YP = pyo.BooleanVar(m.N) + + # REAL VARIABLES + + # Network Variables + # Outlet flow rate of the superstructure unit [L/s] + m.Q = pyo.Var(m.N, initialize=0, within=pyo.NonNegativeReals, bounds=(0, 10)) + + # Outlet flow rate recycle activation of the superstructure unit [L/s] + m.QFR = pyo.Var(m.N, initialize=0, + within=pyo.NonNegativeReals, bounds=(0, 10)) + + # Molar flow [mol/s] + m.F = pyo.Var(m.I, m.N, initialize=0, + within=pyo.NonNegativeReals, bounds=(0, 10)) + + # Molar flow recycle activation [mol/s] + m.FR = pyo.Var(m.I, m.N, initialize=0, + within=pyo.NonNegativeReals, bounds=(0, 10)) + + # Reaction rate [mol/(L*s)] + m.rate = pyo.Var(m.I, m.N, initialize=0, within=pyo.Reals, bounds=(-10, 10)) + + # Reactor volume [L] + m.V = pyo.Var(m.N, initialize=0, within=pyo.NonNegativeReals, bounds=(0, 10)) + + # Volume activation [L] + m.c = pyo.Var(m.N, initialize=0, within=pyo.NonNegativeReals, bounds=(0, 10)) + + # Splitter Variables + # Recycle flow rate [L/s] + m.QR = pyo.Var(initialize=0, within=pyo.NonNegativeReals, bounds=(0, 10)) + + # Product flow rate [L/s] + m.QP = pyo.Var(initialize=0, within=pyo.NonNegativeReals, bounds=(0, 10)) + + # Recycle molar flow [mol/s] + m.R = pyo.Var(m.I, initialize=0, within=pyo.NonNegativeReals, bounds=(0, 10)) + + # Product molar flow [mol/s] + m.P = pyo.Var(m.I, initialize=0, within=pyo.NonNegativeReals, bounds=(0, 10)) + + # CONSTRAINTS + + # Unreacted Feed Balances + # Unreacted feed unit mole balance + + def unreact_mole_rule(m, i, n): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + i : _type_ + _description_ + n : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + if n == NT: + return m.F0[i] + m.FR[i, n] - m.F[i, n] + m.rate[i, n]*m.V[n] == 0 + else: + return pyo.Constraint.Skip + + m.unreact_mole = pyo.Constraint(m.I, m.N, rule=unreact_mole_rule) + + # Unreacted feed unit continuity + + def unreact_cont_rule(m, n): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + n : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + if n == NT: + return m.QF0 + m.QFR[n] - m.Q[n] == 0 + else: + return pyo.Constraint.Skip + + m.unreact_cont = pyo.Constraint(m.N, rule=unreact_cont_rule) + + # Reactor Balances + # Reactor mole balance + + def react_mole_rule(m, i, n): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + i : _type_ + _description_ + n : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + if n != NT: + return m.F[i, n+1] + m.FR[i, n] - m.F[i, n] + m.rate[i, n]*m.V[n] == 0 + else: + return pyo.Constraint.Skip + + m.react_mole = pyo.Constraint(m.I, m.N, rule=react_mole_rule) + + # Reactor continuity + + def react_cont_rule(m, n): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + n : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + if n != NT: + return m.Q[n+1] + m.QFR[n] - m.Q[n] == 0 + else: + return pyo.Constraint.Skip + + m.react_cont = pyo.Constraint(m.N, rule=react_cont_rule) + + # Splitting Point Balances + # Splitting point mole balance + + def split_mole_rule(m, i): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + i : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + return m.F[i, 1] - m.P[i] - m.R[i] == 0 + + m.split_mole = pyo.Constraint(m.I, rule=split_mole_rule) + + # Splitting point continuity + + def split_cont_rule(m): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + return m.Q[1] - m.QP - m.QR == 0 + + m.split_cont = pyo.Constraint(rule=split_cont_rule) + + # Splitting point additional constraints + + def split_add_rule(m, i): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + i : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + return m.P[i]*m.Q[1] - m.F[i, 1]*m.QP == 0 + + m.split_add = pyo.Constraint(m.I, rule=split_add_rule) + + # Product Specification + + def prod_spec_rule(m): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + return m.QP*0.95 - m.P['B'] == 0 + + m.prod_spec = pyo.Constraint(rule=prod_spec_rule) + + # Volume Constraint + + def vol_cons_rule(m, n): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + n : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + if n != 1: + return m.V[n] - m.V[n-1] == 0 + else: + return pyo.Constraint.Skip + + m.vol_cons = pyo.Constraint(m.N, rule=vol_cons_rule) + + # YD Disjunction block equation definition + + def build_cstr_equations(disjunct, n): + """_summary_ + + Parameters + ---------- + disjunct : _type_ + _description_ + n : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + m = disjunct.model() + + # Reaction rates calculation + @disjunct.Constraint() + def YPD_rate_calc(disjunct): + """_summary_ + + Parameters + ---------- + disjunct : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + return m.rate['A', n]*((m.Q[n])**m.order1)*((m.Q[n])**m.order2)+m.k*((m.F['A', n])**m.order1)*((m.F['B', n])**m.order2) == 0 + + # Reaction rate relation + @disjunct.Constraint() + def YPD_rate_rel(disjunct): + """_summary_ + + Parameters + ---------- + disjunct : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + return m.rate['B', n] + m.rate['A', n] == 0 + + # Volume activation + @disjunct.Constraint() + def YPD_vol_act(disjunct): + """_summary_ + + Parameters + ---------- + disjunct : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + return m.c[n] - m.V[n] == 0 + + def build_bypass_equations(disjunct, n): + """_summary_ + + Parameters + ---------- + disjunct : _type_ + _description_ + n : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + m = disjunct.model() + + # FR desactivation + @disjunct.Constraint(m.I) + def neg_YPD_FR_desact(disjunct, i): + """_summary_ + + Parameters + ---------- + disjunct : _type_ + _description_ + i : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + return m.FR[i, n] == 0 + + # Rate desactivation + @disjunct.Constraint(m.I) + def neg_YPD_rate_desact(disjunct, i): + """_summary_ + + Parameters + ---------- + disjunct : _type_ + _description_ + i : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + return m.rate[i, n] == 0 + + # QFR desactivation + @disjunct.Constraint() + def neg_YPD_QFR_desact(disjunct): + """_summary_ + + Parameters + ---------- + disjunct : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + return m.QFR[n] == 0 + + @disjunct.Constraint() + def neg_YPD_vol_desact(disjunct): + ''' + Volume desactivation function for defining pyomo model + args: + disjunct: pyomo block with disjunct to include the constraint + n: pyomo set with reactor index + return: + return constraint + ''' + return m.c[n] == 0 + + # YR Disjuction block equation definition + + def build_recycle_equations(disjunct, n): + """_summary_ + + Parameters + ---------- + disjunct : _type_ + _description_ + n : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + m = disjunct.model() + + # FR activation + @disjunct.Constraint(m.I) + def YRD_FR_act(disjunct, i): + """_summary_ + + Parameters + ---------- + disjunct : _type_ + _description_ + i : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + return m.FR[i, n] - m.R[i] == 0 + + # QFR activation + @disjunct.Constraint() + def YRD_QFR_act(disjunct): + """_summary_ + + Parameters + ---------- + disjunct : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + return m.QFR[n] - m.QR == 0 + + def build_no_recycle_equations(disjunct, n): + """_summary_ + + Parameters + ---------- + disjunct : _type_ + _description_ + n : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + m = disjunct.model() + + # FR desactivation + @disjunct.Constraint(m.I) + def neg_YRD_FR_desact(disjunct, i): + """_summary_ + + Parameters + ---------- + disjunct : _type_ + _description_ + i : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + return m.FR[i, n] == 0 + + # QFR desactivation + @disjunct.Constraint() + def neg_YRD_QFR_desact(disjunct): + """_summary_ + + Parameters + ---------- + disjunct : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + return m.QFR[n] == 0 + + # Create disjunction blocks + m.YR_is_recycle = Disjunct(m.N, rule=build_recycle_equations) + m.YR_is_not_recycle = Disjunct(m.N, rule=build_no_recycle_equations) + + m.YP_is_cstr = Disjunct(m.N, rule=build_cstr_equations) + m.YP_is_bypass = Disjunct(m.N, rule=build_bypass_equations) + + # Create disjunctions + + @m.Disjunction(m.N) + def YP_is_cstr_or_bypass(m, n): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + n : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + return [m.YP_is_cstr[n], m.YP_is_bypass[n]] + + @m.Disjunction(m.N) + def YR_is_recycle_or_not(m, n): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + n : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + return [m.YR_is_recycle[n], m.YR_is_not_recycle[n]] + + # Associate Boolean variables with with disjunctions + for n in m.N: + m.YP[n].associate_binary_var(m.YP_is_cstr[n].indicator_var) + m.YR[n].associate_binary_var(m.YR_is_recycle[n].indicator_var) + + # Logic Constraints + # Unit must be a CSTR to include a recycle + + def cstr_if_recycle_rule(m, n): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + n : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + return m.YR[n].implies(m.YP[n]) + + m.cstr_if_recycle = pyo.LogicalConstraint(m.N, rule=cstr_if_recycle_rule) + + # There is only one unreacted feed + + def one_unreacted_feed_rule(m): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + return pyo.exactly(1, m.YF) + + m.one_unreacted_feed = pyo.LogicalConstraint(rule=one_unreacted_feed_rule) + + # There is only one recycle stream + + def one_recycle_rule(m): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + return pyo.exactly(1, m.YR) + + m.one_recycle = pyo.LogicalConstraint(rule=one_recycle_rule) + + # Unit operation in n constraint + + def unit_in_n_rule(m, n): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + n : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + if n == 1: + return m.YP[n].equivalent_to(True) + else: + return m.YP[n].equivalent_to(pyo.lor(pyo.land(~m.YF[n2] for n2 in range(1, n)), m.YF[n])) + + m.unit_in_n = pyo.LogicalConstraint(m.N, rule=unit_in_n_rule) + + # OBJECTIVE + + def obj_rule(m): + """_summary_ + + Parameters + ---------- + m : _type_ + _description_ + + Returns + ------- + _type_ + _description_ + """ + return sum(m.c[n] for n in m.N) + + m.obj = pyo.Objective(rule=obj_rule, sense=pyo.minimize) + + return m + +if __name__ == "__main__": + m = build_cstrs() + pyo.TransformationFactory('core.logical_to_linear').apply_to(m) + pyo.TransformationFactory('gdp.bigm').apply_to(m) + pyo.SolverFactory('gams').solve(m, solver='baron', tee=True, add_options=['option optcr=1e-6;']) + display(m) \ No newline at end of file From 0aa9e0c1c23db72b773831f8f8daab3fb45c38fe Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Fri, 17 May 2024 17:34:13 -0400 Subject: [PATCH 111/124] Added the simple documentation of the cstr using readme. --- gdplib/cstr/README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 gdplib/cstr/README.md diff --git a/gdplib/cstr/README.md b/gdplib/cstr/README.md new file mode 100644 index 0000000..18882a4 --- /dev/null +++ b/gdplib/cstr/README.md @@ -0,0 +1,30 @@ +# GDP Reactor Series Design +Function that builds CSTR superstructure model of size NT (default = 5). +NT is the number of reactors in series. +The CSTRs have a single 1st order auto catalytic reaction A -> B and minimizes total reactors series volume. +The optimal solution should yield NT reactors with a recycle before reactor NT. + +Reference: +> Linan, D. A., Bernal, D. E., Gomez, J. M., & Ricardez-Sandoval, L. A. (2021). Optimal synthesis and design of catalytic distillation columns: A rate-based modeling approach. Chemical Engineering Science, 231, 116294. https://doi.org/10.1016/j.ces.2020.116294 + +### Solution + +Best known objective value: 3.06181298849707 + +### Size + +Number of reactors in series is 5. + +| Problem | vars | Bool | bin | int | cont | cons | nl | disj | disjtn | +|-----------|------|------|-----|-----|------|------|----|------|--------| +| gdp_reactors | 71 | 15 | 0 | 0 | 56 | 25 | 2 | 20 | 10 | + +- ``vars``: variables +- ``Bool``: Boolean variables +- ``bin``: binary variables +- ``int``: integer variables +- ``cont``: continuous variables +- ``cons``: constraints +- ``nl``: nonlinear constraints +- ``disj``: disjuncts +- ``disjtn``: disjunctions \ No newline at end of file From d91ca9d994823c2f5008a555206758d6eb32ac99 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Fri, 17 May 2024 18:01:34 -0400 Subject: [PATCH 112/124] gdp reactor series add docs. --- gdplib/cstr/gdp_reactor.py | 69 ++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/gdplib/cstr/gdp_reactor.py b/gdplib/cstr/gdp_reactor.py index c34d35e..f55cd7d 100644 --- a/gdplib/cstr/gdp_reactor.py +++ b/gdplib/cstr/gdp_reactor.py @@ -7,16 +7,29 @@ def build_cstrs(NT: int = 5) -> pyo.ConcreteModel(): + # """ + # Function that builds CSTR superstructure model of size NT. + # The CSTRs have a single 1st order reaction A -> B and minimizes (TODO Check) + # total reactor volume. The optimal solution should yield NT reactors with a recycle before reactor NT. + # Reference: Paper Linhan 1. TODO Correct reference + + # Args: + # NT: int. Positive Integer defining the maximum number of CSTRs + # Returns: + # m = Pyomo GDP model + # """ """ - Function that builds CSTR superstructure model of size NT. - The CSTRs have a single 1st order reaction A -> B and minimizes (TODO Check) - total reactor volume. The optimal solution should yield NT reactors with a recycle before reactor NT. - Reference: Paper Linhan 1. TODO Correct reference - - Args: - NT: int. Positive Integer defining the maximum number of CSTRs - Returns: - m = Pyomo GDP model + Build the CSTR superstructure model of size NT. + + + Parameters + ---------- + NT : int + Number of possible reactors in the reactor series superstructure + Returns + ------- + m : Pyomo.ConcreteModel + _description_ """ # PYOMO MODEL @@ -27,8 +40,8 @@ def build_cstrs(NT: int = 5) -> pyo.ConcreteModel(): m.N = pyo.RangeSet(1, NT, doc='Set of units in the superstructure') # PARAMETERS - m.k = pyo.Param(initialize=2, ) # Kinetic constant [L/(mol*s)] - m.order1 = pyo.Param(initialize=1) # Partial order of reacton 1 + m.k = pyo.Param(initialize=2, doc="Kinetic constant [L/(mol*s)]") # Kinetic constant [L/(mol*s)] + m.order1 = pyo.Param(initialize=1, doc="Partial order of") # Partial order of reacton 1 m.order2 = pyo.Param(initialize=1) # Partial order of reaction 2 m.QF0 = pyo.Param(initialize=1) # Inlet volumetric flow [L/s] C0_Def = {'A': 0.99, 'B': 0.01} @@ -43,10 +56,10 @@ def F0_Def(m, i): Parameters ---------- - m : _type_ - _description_ - i : _type_ + m : Pyomo.ConcreteModel _description_ + i : float + Index of the component in the reactor series. Returns ------- @@ -59,53 +72,53 @@ def F0_Def(m, i): # BOOLEAN VARIABLES # Unreacted feed in reactor n - m.YF = pyo.BooleanVar(m.N) + m.YF = pyo.BooleanVar(m.N, doc="Unreacted feed in reactor n") # Existence of recycle flow in unit n - m.YR = pyo.BooleanVar(m.N) + m.YR = pyo.BooleanVar(m.N, doc="Existence of recycle flow in unit n") # Unit operation in n (True if unit n is a CSTR, False if unit n is a bypass) - m.YP = pyo.BooleanVar(m.N) + m.YP = pyo.BooleanVar(m.N, doc="Unit operation in n") # REAL VARIABLES # Network Variables # Outlet flow rate of the superstructure unit [L/s] - m.Q = pyo.Var(m.N, initialize=0, within=pyo.NonNegativeReals, bounds=(0, 10)) + m.Q = pyo.Var(m.N, initialize=0, within=pyo.NonNegativeReals, bounds=(0, 10), doc="Outlet flow rate of the superstructure unit [L/s]") # Outlet flow rate recycle activation of the superstructure unit [L/s] m.QFR = pyo.Var(m.N, initialize=0, - within=pyo.NonNegativeReals, bounds=(0, 10)) + within=pyo.NonNegativeReals, bounds=(0, 10), doc="Outlet flow rate recycle activation of the superstructure unit [L/s]") # Molar flow [mol/s] m.F = pyo.Var(m.I, m.N, initialize=0, - within=pyo.NonNegativeReals, bounds=(0, 10)) + within=pyo.NonNegativeReals, bounds=(0, 10), doc="Molar flow [mol/s]") # Molar flow recycle activation [mol/s] m.FR = pyo.Var(m.I, m.N, initialize=0, - within=pyo.NonNegativeReals, bounds=(0, 10)) + within=pyo.NonNegativeReals, bounds=(0, 10), doc="Molar flow recycle activation [mol/s]") # Reaction rate [mol/(L*s)] - m.rate = pyo.Var(m.I, m.N, initialize=0, within=pyo.Reals, bounds=(-10, 10)) + m.rate = pyo.Var(m.I, m.N, initialize=0, within=pyo.Reals, bounds=(-10, 10), doc="Reaction rate [mol/(L*s)]") # Reactor volume [L] - m.V = pyo.Var(m.N, initialize=0, within=pyo.NonNegativeReals, bounds=(0, 10)) + m.V = pyo.Var(m.N, initialize=0, within=pyo.NonNegativeReals, bounds=(0, 10), doc="Reactor volume [L]") # Volume activation [L] - m.c = pyo.Var(m.N, initialize=0, within=pyo.NonNegativeReals, bounds=(0, 10)) + m.c = pyo.Var(m.N, initialize=0, within=pyo.NonNegativeReals, bounds=(0, 10), doc="Volume activation [L]") # Splitter Variables # Recycle flow rate [L/s] - m.QR = pyo.Var(initialize=0, within=pyo.NonNegativeReals, bounds=(0, 10)) + m.QR = pyo.Var(initialize=0, within=pyo.NonNegativeReals, bounds=(0, 10), doc="Recycle flow rate [L/s]") # Product flow rate [L/s] - m.QP = pyo.Var(initialize=0, within=pyo.NonNegativeReals, bounds=(0, 10)) + m.QP = pyo.Var(initialize=0, within=pyo.NonNegativeReals, bounds=(0, 10), doc="Product flow rate [L/s]") # Recycle molar flow [mol/s] - m.R = pyo.Var(m.I, initialize=0, within=pyo.NonNegativeReals, bounds=(0, 10)) + m.R = pyo.Var(m.I, initialize=0, within=pyo.NonNegativeReals, bounds=(0, 10), doc="Recycle molar flow [mol/s]") # Product molar flow [mol/s] - m.P = pyo.Var(m.I, initialize=0, within=pyo.NonNegativeReals, bounds=(0, 10)) + m.P = pyo.Var(m.I, initialize=0, within=pyo.NonNegativeReals, bounds=(0, 10), doc="Product molar flow [mol/s]") # CONSTRAINTS From bdef4682c7e4198232ad3026032eb883c6c70c20 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Sat, 18 May 2024 17:55:47 -0400 Subject: [PATCH 113/124] add documentation on the inlet flow. --- gdplib/cstr/gdp_reactor.py | 210 ++++++++++++++++++------------------- 1 file changed, 103 insertions(+), 107 deletions(-) diff --git a/gdplib/cstr/gdp_reactor.py b/gdplib/cstr/gdp_reactor.py index f55cd7d..305424e 100644 --- a/gdplib/cstr/gdp_reactor.py +++ b/gdplib/cstr/gdp_reactor.py @@ -7,29 +7,21 @@ def build_cstrs(NT: int = 5) -> pyo.ConcreteModel(): - # """ - # Function that builds CSTR superstructure model of size NT. - # The CSTRs have a single 1st order reaction A -> B and minimizes (TODO Check) - # total reactor volume. The optimal solution should yield NT reactors with a recycle before reactor NT. - # Reference: Paper Linhan 1. TODO Correct reference - - # Args: - # NT: int. Positive Integer defining the maximum number of CSTRs - # Returns: - # m = Pyomo GDP model - # """ """ Build the CSTR superstructure model of size NT. - + NT is the number of reactors in series. + The CSTRs have a single 1st order auto catalytic reaction A -> B and minimizes total reactors series volume. + The optimal solution should yield NT reactors with a recycle before reactor NT. Parameters ---------- NT : int Number of possible reactors in the reactor series superstructure + Returns ------- m : Pyomo.ConcreteModel - _description_ + Pyomo GDP model which represents the superstructure model of size of NT reactors. """ # PYOMO MODEL @@ -41,30 +33,32 @@ def build_cstrs(NT: int = 5) -> pyo.ConcreteModel(): # PARAMETERS m.k = pyo.Param(initialize=2, doc="Kinetic constant [L/(mol*s)]") # Kinetic constant [L/(mol*s)] - m.order1 = pyo.Param(initialize=1, doc="Partial order of") # Partial order of reacton 1 - m.order2 = pyo.Param(initialize=1) # Partial order of reaction 2 - m.QF0 = pyo.Param(initialize=1) # Inlet volumetric flow [L/s] + m.order1 = pyo.Param(initialize=1, doc="Partial order of reaction 1") # Partial order of reacton 1 + m.order2 = pyo.Param(initialize=1, doc="Partial order of reaction 2") # Partial order of reaction 2 + m.QF0 = pyo.Param(initialize=1, doc="Inlet volumetric flow [L/s]") # Inlet volumetric flow [L/s] C0_Def = {'A': 0.99, 'B': 0.01} # Initial concentration of reagents [mol/L] - m.C0 = pyo.Param(m.I, initialize=C0_Def) + m.C0 = pyo.Param(m.I, initialize=C0_Def, doc="Initial concentration of reagents [mol/L]") # Inlet molar flow [mol/s] def F0_Def(m, i): - """_summary_ + """ + Inlet molar flow [mol/s] for component i. + The function multiplies the initial concentration of component i by the inlet volumetric flow. Parameters ---------- m : Pyomo.ConcreteModel - _description_ + Pyomo GDP model of the CSTR superstructure. i : float Index of the component in the reactor series. Returns ------- - _type_ - _description_ + Pyomo.Param + Inlet molar flow [mol/s] for component i """ return m.C0[i]*m.QF0 m.F0 = pyo.Param(m.I, initialize=F0_Def) @@ -126,20 +120,21 @@ def F0_Def(m, i): # Unreacted feed unit mole balance def unreact_mole_rule(m, i, n): - """_summary_ + """ + Unreacted feed unit mole balance. Parameters ---------- - m : _type_ - _description_ - i : _type_ - _description_ - n : _type_ - _description_ + m : Pyomo.ConcreteModel + Pyomo GDP model of the CSTR superstructure. + i : float + Index of the component in the reactor series. + n : int + Index of the reactor in the reactor series. The reactor index starts at 1. The number increases on the left direction Returns ------- - _type_ + Pyomo.Constraint or Pyomo.Constraint.Skip _description_ """ if n == NT: @@ -156,10 +151,10 @@ def unreact_cont_rule(m, n): Parameters ---------- - m : _type_ - _description_ - n : _type_ - _description_ + m : Pyomo.ConcreteModel + Pyomo GDP model of the CSTR superstructure. + n : int + Index of the reactor in the reactor series. Returns ------- @@ -181,12 +176,12 @@ def react_mole_rule(m, i, n): Parameters ---------- - m : _type_ - _description_ - i : _type_ - _description_ - n : _type_ - _description_ + m : Pyomo.ConcreteModel + Pyomo GDP model of the CSTR superstructure. + i : float + Index of the component in the reactor series. + n : int + Index of the reactor in the reactor series. Returns ------- @@ -207,10 +202,10 @@ def react_cont_rule(m, n): Parameters ---------- - m : _type_ - _description_ - n : _type_ - _description_ + m : Pyomo.ConcreteModel + Pyomo GDP model of the CSTR superstructure. + n : int + Index of the reactor in the reactor series. Returns ------- @@ -232,10 +227,10 @@ def split_mole_rule(m, i): Parameters ---------- - m : _type_ - _description_ - i : _type_ - _description_ + m : Pyomo.ConcreteModel + Pyomo GDP model of the CSTR superstructure. + i : float + Index of the component in the reactor series. Returns ------- @@ -253,8 +248,8 @@ def split_cont_rule(m): Parameters ---------- - m : _type_ - _description_ + m : Pyomo.ConcreteModel + Pyomo GDP model of the CSTR superstructure. Returns ------- @@ -272,10 +267,10 @@ def split_add_rule(m, i): Parameters ---------- - m : _type_ - _description_ - i : _type_ - _description_ + m : Pyomo.ConcreteModel + Pyomo GDP model of the CSTR superstructure. + i : float + Index of the component in the reactor series. Returns ------- @@ -293,8 +288,8 @@ def prod_spec_rule(m): Parameters ---------- - m : _type_ - _description_ + m : Pyomo.ConcreteModel + Pyomo GDP model of the CSTR superstructure. Returns ------- @@ -312,10 +307,10 @@ def vol_cons_rule(m, n): Parameters ---------- - m : _type_ - _description_ - n : _type_ - _description_ + m : Pyomo.ConcreteModel + Pyomo GDP model of the CSTR superstructure. + n : int + Index of the reactor in the reactor series. Returns ------- @@ -338,8 +333,8 @@ def build_cstr_equations(disjunct, n): ---------- disjunct : _type_ _description_ - n : _type_ - _description_ + n : int + Index of the reactor in the reactor series. Returns ------- @@ -406,8 +401,8 @@ def build_bypass_equations(disjunct, n): ---------- disjunct : _type_ _description_ - n : _type_ - _description_ + n : int + Index of the reactor in the reactor series. Returns ------- @@ -425,8 +420,8 @@ def neg_YPD_FR_desact(disjunct, i): ---------- disjunct : _type_ _description_ - i : _type_ - _description_ + i : float + Index of the component in the reactor series. Returns ------- @@ -444,8 +439,8 @@ def neg_YPD_rate_desact(disjunct, i): ---------- disjunct : _type_ _description_ - i : _type_ - _description_ + i : float + Index of the component in the reactor series. Returns ------- @@ -492,8 +487,8 @@ def build_recycle_equations(disjunct, n): ---------- disjunct : _type_ _description_ - n : _type_ - _description_ + n : int + Index of the reactor in the reactor series. Returns ------- @@ -511,8 +506,8 @@ def YRD_FR_act(disjunct, i): ---------- disjunct : _type_ _description_ - i : _type_ - _description_ + i : float + Index of the component in the reactor series. Returns ------- @@ -545,8 +540,8 @@ def build_no_recycle_equations(disjunct, n): ---------- disjunct : _type_ _description_ - n : _type_ - _description_ + n : int + Index of the reactor in the reactor series. Returns ------- @@ -564,9 +559,9 @@ def neg_YRD_FR_desact(disjunct, i): ---------- disjunct : _type_ _description_ - i : _type_ - _description_ - + i : float + Index of the component in the reactor series. + Returns ------- _type_ @@ -606,14 +601,14 @@ def YP_is_cstr_or_bypass(m, n): Parameters ---------- - m : _type_ - _description_ - n : _type_ - _description_ + m : Pyomo.ConcreteModel + Pyomo GDP model of the CSTR superstructure. + n : int + Index of the reactor in the reactor series. Returns ------- - _type_ + list _description_ """ return [m.YP_is_cstr[n], m.YP_is_bypass[n]] @@ -624,14 +619,14 @@ def YR_is_recycle_or_not(m, n): Parameters ---------- - m : _type_ - _description_ - n : _type_ - _description_ + m : Pyomo.ConcreteModel + Pyomo GDP model of the CSTR superstructure. + n : int + Index of the reactor in the reactor series. Returns ------- - _type_ + list _description_ """ return [m.YR_is_recycle[n], m.YR_is_not_recycle[n]] @@ -649,10 +644,10 @@ def cstr_if_recycle_rule(m, n): Parameters ---------- - m : _type_ - _description_ - n : _type_ - _description_ + m : Pyomo.ConcreteModel + Pyomo GDP model of the CSTR superstructure. + n : int + Index of the reactor in the reactor series. Returns ------- @@ -670,12 +665,12 @@ def one_unreacted_feed_rule(m): Parameters ---------- - m : _type_ - _description_ + m : Pyomo.ConcreteModel + Pyomo GDP model of the CSTR superstructure. Returns ------- - _type_ + Pyomo.LogicalConstraint _description_ """ return pyo.exactly(1, m.YF) @@ -689,12 +684,12 @@ def one_recycle_rule(m): Parameters ---------- - m : _type_ - _description_ + m : Pyomo.ConcreteModel + Pyomo GDP model of the CSTR superstructure. Returns ------- - _type_ + Pyomo.LogicalConstraint _description_ """ return pyo.exactly(1, m.YR) @@ -708,14 +703,14 @@ def unit_in_n_rule(m, n): Parameters ---------- - m : _type_ - _description_ - n : _type_ - _description_ + m : Pyomo.ConcreteModel + Pyomo GDP model of the CSTR superstructure. + n : int + Index of the reactor in the reactor series. Returns ------- - _type_ + Pyomo.LogicalConstraint _description_ """ if n == 1: @@ -728,21 +723,22 @@ def unit_in_n_rule(m, n): # OBJECTIVE def obj_rule(m): - """_summary_ + """ + Objective function to minimize the total reactor volume. Parameters ---------- - m : _type_ - _description_ + m : Pyomo.ConcreteModel + Pyomo GDP model of the CSTR superstructure. Returns ------- - _type_ - _description_ + Pyomo Objective + Objective function to minimize the total reactor volume. """ return sum(m.c[n] for n in m.N) - m.obj = pyo.Objective(rule=obj_rule, sense=pyo.minimize) + m.obj = pyo.Objective(rule=obj_rule, sense=pyo.minimize, doc="minimum total reactor volume") return m From faddeaf8087e008a1d72a79269d14dfa8b904684 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Sat, 18 May 2024 18:11:08 -0400 Subject: [PATCH 114/124] leftest reactor molar and volume balance. --- gdplib/cstr/gdp_reactor.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/gdplib/cstr/gdp_reactor.py b/gdplib/cstr/gdp_reactor.py index 305424e..e038850 100644 --- a/gdplib/cstr/gdp_reactor.py +++ b/gdplib/cstr/gdp_reactor.py @@ -122,6 +122,7 @@ def F0_Def(m, i): def unreact_mole_rule(m, i, n): """ Unreacted feed unit mole balance. + The mole balance is calculated using the inlet molar flow, the recycle molar flow, the outlet molar flow, and the reaction rate for each component. Parameters ---------- @@ -130,24 +131,26 @@ def unreact_mole_rule(m, i, n): i : float Index of the component in the reactor series. n : int - Index of the reactor in the reactor series. The reactor index starts at 1. The number increases on the left direction + Index of the reactor in the reactor series. The reactor index starts at 1. The number increases on the left direction of the reactor series. The last reactor is indexed as NT which is the feed for reagent. Returns ------- Pyomo.Constraint or Pyomo.Constraint.Skip - _description_ + The constraint for the unreacted feed unit mole balance if n is equal to NT. Otherwise, it returns Pyomo.Constraint.Skip. """ if n == NT: return m.F0[i] + m.FR[i, n] - m.F[i, n] + m.rate[i, n]*m.V[n] == 0 else: return pyo.Constraint.Skip - m.unreact_mole = pyo.Constraint(m.I, m.N, rule=unreact_mole_rule) + m.unreact_mole = pyo.Constraint(m.I, m.N, rule=unreact_mole_rule, doc="Unreacted feed unit mole balance") # Unreacted feed unit continuity def unreact_cont_rule(m, n): - """_summary_ + """ + Unreacted feed unit continuity. + The continuity is calculated using the inlet volumetric flow, the recycle flow rate, and the outlet flow rate for the reactor NT. Parameters ---------- @@ -158,15 +161,15 @@ def unreact_cont_rule(m, n): Returns ------- - _type_ - _description_ + Pyomo.Constraint or Pyomo.Constraint.Skip + The constraint for the unreacted feed unit continuity if n is equal to NT. Otherwise, it returns Pyomo.Constraint.Skip. """ if n == NT: return m.QF0 + m.QFR[n] - m.Q[n] == 0 else: return pyo.Constraint.Skip - m.unreact_cont = pyo.Constraint(m.N, rule=unreact_cont_rule) + m.unreact_cont = pyo.Constraint(m.N, rule=unreact_cont_rule, doc="Unreacted feed unit continuity") # Reactor Balances # Reactor mole balance From dc94ae153fad6dcfa4a5ba485a942142ebc2a355 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Sat, 18 May 2024 18:28:22 -0400 Subject: [PATCH 115/124] general constraint documentation --- gdplib/cstr/gdp_reactor.py | 70 +++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/gdplib/cstr/gdp_reactor.py b/gdplib/cstr/gdp_reactor.py index e038850..0642019 100644 --- a/gdplib/cstr/gdp_reactor.py +++ b/gdplib/cstr/gdp_reactor.py @@ -175,7 +175,9 @@ def unreact_cont_rule(m, n): # Reactor mole balance def react_mole_rule(m, i, n): - """_summary_ + """ + Reactor mole balance. + The mole balance is calculated using the inlet molar flow, the recycle molar flow, the outlet molar flow, and the reaction rate for each component. Parameters ---------- @@ -188,20 +190,22 @@ def react_mole_rule(m, i, n): Returns ------- - _type_ - _description_ + Pyomo.Constraint or Pyomo.Constraint.Skip + The constraint for the reactor mole balance if n is different from NT. Otherwise, it returns Pyomo.Constraint.Skip. """ if n != NT: return m.F[i, n+1] + m.FR[i, n] - m.F[i, n] + m.rate[i, n]*m.V[n] == 0 else: return pyo.Constraint.Skip - m.react_mole = pyo.Constraint(m.I, m.N, rule=react_mole_rule) + m.react_mole = pyo.Constraint(m.I, m.N, rule=react_mole_rule, doc="Reactor mole balance") # Reactor continuity def react_cont_rule(m, n): - """_summary_ + """ + Reactor continuity. + The continuity is calculated using the inlet volumetric flow, the recycle flow rate, and the outlet flow rate for the reactor n. Parameters ---------- @@ -212,21 +216,23 @@ def react_cont_rule(m, n): Returns ------- - _type_ - _description_ + Pyomo.Constraint or Pyomo.Constraint.Skip + The constraint for the reactor continuity if n is different from NT. Otherwise, it returns Pyomo.Constraint.Skip. """ if n != NT: return m.Q[n+1] + m.QFR[n] - m.Q[n] == 0 else: return pyo.Constraint.Skip - m.react_cont = pyo.Constraint(m.N, rule=react_cont_rule) + m.react_cont = pyo.Constraint(m.N, rule=react_cont_rule, doc="Reactor continuity") # Splitting Point Balances # Splitting point mole balance def split_mole_rule(m, i): - """_summary_ + """ + Splitting point mole balance. + The mole balance is calculated using the product molar flow, the recycle molar flow, and the outlet molar flow for each component. Parameters ---------- @@ -237,17 +243,19 @@ def split_mole_rule(m, i): Returns ------- - _type_ - _description_ + Pyomo.Constraint + The constraint for the splitting point mole balance. """ return m.F[i, 1] - m.P[i] - m.R[i] == 0 - m.split_mole = pyo.Constraint(m.I, rule=split_mole_rule) + m.split_mole = pyo.Constraint(m.I, rule=split_mole_rule, doc="Splitting point mole balance") # Splitting point continuity def split_cont_rule(m): - """_summary_ + """ + Splitting point continuity. + The continuity is calculated using the product flow rate, the recycle flow rate, and the outlet flow rate for the reactor 1. Parameters ---------- @@ -256,17 +264,20 @@ def split_cont_rule(m): Returns ------- - _type_ - _description_ + Pyomo.Constraint + The constraint for the splitting point continuity. """ return m.Q[1] - m.QP - m.QR == 0 - m.split_cont = pyo.Constraint(rule=split_cont_rule) + m.split_cont = pyo.Constraint(rule=split_cont_rule, doc="Splitting point continuity") # Splitting point additional constraints def split_add_rule(m, i): - """_summary_ + """ + Splitting point additional constraints. + Molarity constraints over initial and final flows, read as an multiplication avoid the numerical complication. + m.P[i]/m.QP = m.F[i,1]/m.Q[1] (molarity balance) Parameters ---------- @@ -277,17 +288,19 @@ def split_add_rule(m, i): Returns ------- - _type_ - _description_ + Pyomo.Constraint + The constraint for the splitting point additional constraints. """ return m.P[i]*m.Q[1] - m.F[i, 1]*m.QP == 0 - m.split_add = pyo.Constraint(m.I, rule=split_add_rule) + m.split_add = pyo.Constraint(m.I, rule=split_add_rule, doc="Splitting point additional constraints") # Product Specification def prod_spec_rule(m): - """_summary_ + """ + Product B Specification. + The product B specification is calculated using the product flow rate and the product molar flow. Parameters ---------- @@ -296,17 +309,18 @@ def prod_spec_rule(m): Returns ------- - _type_ - _description_ + Pyomo.Constraint + The constraint for the product B specification. """ return m.QP*0.95 - m.P['B'] == 0 - m.prod_spec = pyo.Constraint(rule=prod_spec_rule) + m.prod_spec = pyo.Constraint(rule=prod_spec_rule, doc="Product B Specification") # Volume Constraint def vol_cons_rule(m, n): - """_summary_ + """ + Volume Constraint. Parameters ---------- @@ -317,15 +331,15 @@ def vol_cons_rule(m, n): Returns ------- - _type_ - _description_ + Pyomo.Constraint or Pyomo.Constraint.Skip + The constraint for the volume constraint if n is different from 1. Otherwise, it returns Pyomo.Constraint.Skip. """ if n != 1: return m.V[n] - m.V[n-1] == 0 else: return pyo.Constraint.Skip - m.vol_cons = pyo.Constraint(m.N, rule=vol_cons_rule) + m.vol_cons = pyo.Constraint(m.N, rule=vol_cons_rule, doc"Volume Constraint") # YD Disjunction block equation definition From f0c95ac8dcf5bc243d1ea35eb6afbbb6ee34c1e4 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Sun, 19 May 2024 13:45:35 -0400 Subject: [PATCH 116/124] Complete the documentation of the GDP reactor. --- gdplib/cstr/gdp_reactor.py | 241 +++++++++++++++++++++---------------- 1 file changed, 139 insertions(+), 102 deletions(-) diff --git a/gdplib/cstr/gdp_reactor.py b/gdplib/cstr/gdp_reactor.py index 0642019..b88475d 100644 --- a/gdplib/cstr/gdp_reactor.py +++ b/gdplib/cstr/gdp_reactor.py @@ -339,7 +339,7 @@ def vol_cons_rule(m, n): else: return pyo.Constraint.Skip - m.vol_cons = pyo.Constraint(m.N, rule=vol_cons_rule, doc"Volume Constraint") + m.vol_cons = pyo.Constraint(m.N, rule=vol_cons_rule, doc="Volume Constraint") # YD Disjunction block equation definition @@ -348,48 +348,55 @@ def build_cstr_equations(disjunct, n): Parameters ---------- - disjunct : _type_ - _description_ + disjunct : Pyomo.Disjunct + Pyomo Disjunct block to include the constraints for the activation of the CSTR reactor. n : int Index of the reactor in the reactor series. Returns ------- - _type_ - _description_ + None + None, the function builds the constraints for the activation of the CSTR reactor. """ m = disjunct.model() # Reaction rates calculation @disjunct.Constraint() def YPD_rate_calc(disjunct): - """_summary_ + """ + Calculate the reaction rate of A for each reactor. + The reaction rate is calculated using the kinetic constant, the outlet flow rate, the molar flow of A, and the molar flow of B. + The outlet flow is multiplied on the reaction rate to avoid numerical complications. Parameters ---------- - disjunct : _type_ - _description_ + disjunct : Pyomo.Disjunct + Pyomo Disjunct block to include the constraints for the activation of the CSTR reactor. Returns ------- - _type_ - _description_ + Pyomo.Constraint + The constraint for the calculation of the reaction rate of A for each reactor. """ return m.rate['A', n]*((m.Q[n])**m.order1)*((m.Q[n])**m.order2)+m.k*((m.F['A', n])**m.order1)*((m.F['B', n])**m.order2) == 0 # Reaction rate relation @disjunct.Constraint() def YPD_rate_rel(disjunct): - """_summary_ + """ + Reaction rate relation for defining pyomo model. + Since the chemical reaction goes from A to B, the rate of A is equal to the negative rate of B. + + A -> B Parameters ---------- - disjunct : _type_ - _description_ + disjunct : Pyomo.Disjunct + Pyomo Disjunct block to include the constraints for the activation of the CSTR reactor. Returns ------- - _type_ + Pyomo.Constraint _description_ """ return m.rate['B', n] + m.rate['A', n] == 0 @@ -397,224 +404,245 @@ def YPD_rate_rel(disjunct): # Volume activation @disjunct.Constraint() def YPD_vol_act(disjunct): - """_summary_ + """ + Volume Activation function for defining pyomo model. Parameters ---------- - disjunct : _type_ - _description_ + disjunct : Pyomo.Disjunct + Pyomo Disjunct block to include the constraints for the activation of the CSTR reactor. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Activation function for the volume of the reactor. """ return m.c[n] - m.V[n] == 0 def build_bypass_equations(disjunct, n): - """_summary_ + """ + Build the constraints for the deactivation of the reactor (bypass the reactor). Parameters ---------- - disjunct : _type_ - _description_ + disjunct : Pyomo.Disjunct + Pyomo Disjunct block to include the constraints for the deactivation of the reactor (bypass the reactor). n : int Index of the reactor in the reactor series. Returns ------- - _type_ - _description_ + None + None, the function builds the constraints for the deactivation of the reactor (bypass the reactor). """ m = disjunct.model() - # FR desactivation + # FR deactivation @disjunct.Constraint(m.I) def neg_YPD_FR_desact(disjunct, i): - """_summary_ + """ + Deactivation of the recycle flow for each component in the reactor series. + There are no recycle flows when the reactor is deactivated (bypassed). Parameters ---------- - disjunct : _type_ - _description_ + disjunct : Pyomo.Disjunct + Pyomo Disjunct block to include the constraints for the deactivation of the reactor (bypass the reactor). i : float Index of the component in the reactor series. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Deactivation of the recycle flow for each component in the reactor series. """ return m.FR[i, n] == 0 - # Rate desactivation + # Rate deactivation @disjunct.Constraint(m.I) def neg_YPD_rate_desact(disjunct, i): - """_summary_ + """ + Deactivate the reaction rate for each component in the reactor series. + There are no reaction rates when the reactor is deactivated (bypassed). Parameters ---------- - disjunct : _type_ - _description_ + disjunct : Pyomo.Disjunct + Pyomo Disjunct block to include the constraints for the deactivation of the reactor (bypass the reactor). i : float Index of the component in the reactor series. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Deactivation of the reaction rate for each component in the reactor series. """ return m.rate[i, n] == 0 - # QFR desactivation + # QFR deactivation @disjunct.Constraint() def neg_YPD_QFR_desact(disjunct): - """_summary_ + """ + Deactivate the outlet flow rate recycle activation of the reactor. + There is no outlet flow rate recycle activation when the reactor is deactivated (bypassed). Parameters ---------- - disjunct : _type_ - _description_ + disjunct : Pyomo.Disjunct + Pyomo Disjunct block to include the constraints for the deactivation of the reactor (bypass the reactor). Returns ------- - _type_ - _description_ + Pyomo.Constraint + Deactivation of the outlet flow rate recycle activation of the reactor. """ return m.QFR[n] == 0 @disjunct.Constraint() def neg_YPD_vol_desact(disjunct): - ''' - Volume desactivation function for defining pyomo model - args: - disjunct: pyomo block with disjunct to include the constraint - n: pyomo set with reactor index - return: - return constraint - ''' + """ + Volume deactivation function for defining pyomo model. + There is no volume when the reactor is deactivated (bypassed). + + Parameters + ---------- + disjunct : Pyomo.Disjunct + Pyomo Disjunct block to include the constraints for the deactivation of the reactor (bypass the reactor). + + Returns + ------- + Pyomo.Constraint + Volume deactivation function for defining pyomo model. + """ return m.c[n] == 0 # YR Disjuction block equation definition def build_recycle_equations(disjunct, n): - """_summary_ + """ + Build the constraints for the activation of the recycle flow. Parameters ---------- - disjunct : _type_ - _description_ + disjunct : Pyomo.Disjunct + Pyomo Disjunct block to include the constraints for the activation of the reactor (recycle flow existence). n : int Index of the reactor in the reactor series. Returns ------- - _type_ - _description_ + None + None, the function builds the constraints for the activation of the recycle flow. """ m = disjunct.model() # FR activation @disjunct.Constraint(m.I) def YRD_FR_act(disjunct, i): - """_summary_ + """ + Activation of the recycle flow for each component in the reactor series. Parameters ---------- - disjunct : _type_ - _description_ + disjunct : Pyomo.Disjunct + Pyomo Disjunct block to include the constraints for the activation of the reactor (recycle flow existence). i : float Index of the component in the reactor series. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Activation of the recycle flow for each component in the reactor series. """ return m.FR[i, n] - m.R[i] == 0 # QFR activation @disjunct.Constraint() def YRD_QFR_act(disjunct): - """_summary_ + """ + Activation of the outlet flow rate recycle activation of the reactor. Parameters ---------- - disjunct : _type_ - _description_ + disjunct : Pyomo.Disjunct + Pyomo Disjunct block to include the constraints for the activation of the reactor (recycle flow existence). Returns ------- - _type_ - _description_ + Pyomo.Constraint + Activation of the outlet flow rate recycle activation of the reactor. """ return m.QFR[n] - m.QR == 0 def build_no_recycle_equations(disjunct, n): - """_summary_ + """ + Build the constraints for the deactivation of the recycle flow. Parameters ---------- - disjunct : _type_ - _description_ + disjunct : Pyomo.Disjunct + Pyomo Disjunct block to include the constraints for the deactivation of the reactor (recycle flow absence). n : int Index of the reactor in the reactor series. Returns ------- - _type_ - _description_ + None + None, the function builds the constraints for the deactivation of the recycle flow. """ m = disjunct.model() - # FR desactivation + # FR deactivation @disjunct.Constraint(m.I) def neg_YRD_FR_desact(disjunct, i): - """_summary_ + """ + Deactivation of the recycle flow for each component in the reactor series. Parameters ---------- - disjunct : _type_ - _description_ + disjunct : Pyomo.Disjunct + Pyomo Disjunct block to include the constraints for the deactivation of the reactor (recycle flow absence). i : float Index of the component in the reactor series. Returns ------- - _type_ - _description_ + Pyomo.Constraint + Deactivation of the recycle flow for each component in the reactor series. """ return m.FR[i, n] == 0 - # QFR desactivation + # QFR deactivation @disjunct.Constraint() def neg_YRD_QFR_desact(disjunct): - """_summary_ + """ + Deactivation of the outlet flow rate recycle activation of the reactor. Parameters ---------- - disjunct : _type_ - _description_ + disjunct : Pyomo.Disjunct + Pyomo Disjunct block to include the constraints for the deactivation of the reactor (recycle flow absence). Returns ------- - _type_ - _description_ + Pyomo.Constraint + Deactivation of the outlet flow rate recycle activation of the reactor. """ return m.QFR[n] == 0 # Create disjunction blocks - m.YR_is_recycle = Disjunct(m.N, rule=build_recycle_equations) - m.YR_is_not_recycle = Disjunct(m.N, rule=build_no_recycle_equations) + m.YR_is_recycle = Disjunct(m.N, rule=build_recycle_equations, doc="Recycle flow in reactor n") + m.YR_is_not_recycle = Disjunct(m.N, rule=build_no_recycle_equations, doc="No recycle flow in reactor n") - m.YP_is_cstr = Disjunct(m.N, rule=build_cstr_equations) - m.YP_is_bypass = Disjunct(m.N, rule=build_bypass_equations) + m.YP_is_cstr = Disjunct(m.N, rule=build_cstr_equations, doc="CSTR reactor n") + m.YP_is_bypass = Disjunct(m.N, rule=build_bypass_equations, doc="Bypass reactor n") # Create disjunctions @m.Disjunction(m.N) def YP_is_cstr_or_bypass(m, n): - """_summary_ + """ + Build the disjunction for the activation of the CSTR reactor or bypass the reactor. Parameters ---------- @@ -626,13 +654,14 @@ def YP_is_cstr_or_bypass(m, n): Returns ------- list - _description_ + list of the disjunctions for the activation of the CSTR reactor or bypass the reactor """ return [m.YP_is_cstr[n], m.YP_is_bypass[n]] @m.Disjunction(m.N) def YR_is_recycle_or_not(m, n): - """_summary_ + """ + Build the disjunction for the existence of a recycle flow in the reactor. Parameters ---------- @@ -644,7 +673,7 @@ def YR_is_recycle_or_not(m, n): Returns ------- list - _description_ + list of the disjunctions for the existence of a recycle flow in the reactor """ return [m.YR_is_recycle[n], m.YR_is_not_recycle[n]] @@ -657,7 +686,9 @@ def YR_is_recycle_or_not(m, n): # Unit must be a CSTR to include a recycle def cstr_if_recycle_rule(m, n): - """_summary_ + """ + Build the logical constraint for the unit to be a CSTR to include a recycle. + The existence of a recycle flow implies the existence of a CSTR reactor. Parameters ---------- @@ -668,17 +699,18 @@ def cstr_if_recycle_rule(m, n): Returns ------- - _type_ - _description_ + Pyomo.LogicalConstraint + Logical constraint for the unit to be a CSTR to include a recycle. """ return m.YR[n].implies(m.YP[n]) - m.cstr_if_recycle = pyo.LogicalConstraint(m.N, rule=cstr_if_recycle_rule) + m.cstr_if_recycle = pyo.LogicalConstraint(m.N, rule=cstr_if_recycle_rule, doc="Unit must be a CSTR to include a recycle") # There is only one unreacted feed def one_unreacted_feed_rule(m): - """_summary_ + """ + Build the logical constraint for the existence of only one unreacted feed. Parameters ---------- @@ -688,16 +720,17 @@ def one_unreacted_feed_rule(m): Returns ------- Pyomo.LogicalConstraint - _description_ + Logical constraint for the existence of only one unreacted feed. """ return pyo.exactly(1, m.YF) - m.one_unreacted_feed = pyo.LogicalConstraint(rule=one_unreacted_feed_rule) + m.one_unreacted_feed = pyo.LogicalConstraint(rule=one_unreacted_feed_rule, doc="There is only one unreacted feed") # There is only one recycle stream def one_recycle_rule(m): - """_summary_ + """ + Build the logical constraint for the existence of only one recycle stream. Parameters ---------- @@ -707,16 +740,20 @@ def one_recycle_rule(m): Returns ------- Pyomo.LogicalConstraint - _description_ + Logical constraint for the existence of only one recycle stream. """ return pyo.exactly(1, m.YR) - m.one_recycle = pyo.LogicalConstraint(rule=one_recycle_rule) + m.one_recycle = pyo.LogicalConstraint(rule=one_recycle_rule, doc="There is only one recycle stream") # Unit operation in n constraint def unit_in_n_rule(m, n): - """_summary_ + """ + Build the logical constraint for the unit operation in n. + If n is equal to 1, the unit operation is a CSTR. + Otherwise, the unit operation for reactor n except reactor 1 is equivalent to the logical OR of the negation of the unreacted feed of the previous reactors and the unreacted feed of reactor n. + Reactor n is active if either the previous reactors (1 through n-1) have no unreacted feed or reactor n has unreacted feed. Parameters ---------- @@ -728,7 +765,7 @@ def unit_in_n_rule(m, n): Returns ------- Pyomo.LogicalConstraint - _description_ + Logical constraint for the unit operation in n. """ if n == 1: return m.YP[n].equivalent_to(True) From 4d3a013a71e7b1c4b68f20973f853c22e103ceed Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Sun, 19 May 2024 13:47:06 -0400 Subject: [PATCH 117/124] Replaced the Spanglish function definition into English definition. --- gdplib/cstr/gdp_reactor.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gdplib/cstr/gdp_reactor.py b/gdplib/cstr/gdp_reactor.py index b88475d..09209c9 100644 --- a/gdplib/cstr/gdp_reactor.py +++ b/gdplib/cstr/gdp_reactor.py @@ -439,7 +439,7 @@ def build_bypass_equations(disjunct, n): # FR deactivation @disjunct.Constraint(m.I) - def neg_YPD_FR_desact(disjunct, i): + def neg_YPD_FR_deactivation(disjunct, i): """ Deactivation of the recycle flow for each component in the reactor series. There are no recycle flows when the reactor is deactivated (bypassed). @@ -460,7 +460,7 @@ def neg_YPD_FR_desact(disjunct, i): # Rate deactivation @disjunct.Constraint(m.I) - def neg_YPD_rate_desact(disjunct, i): + def neg_YPD_rate_deactivation(disjunct, i): """ Deactivate the reaction rate for each component in the reactor series. There are no reaction rates when the reactor is deactivated (bypassed). @@ -481,7 +481,7 @@ def neg_YPD_rate_desact(disjunct, i): # QFR deactivation @disjunct.Constraint() - def neg_YPD_QFR_desact(disjunct): + def neg_YPD_QFR_deactivation(disjunct): """ Deactivate the outlet flow rate recycle activation of the reactor. There is no outlet flow rate recycle activation when the reactor is deactivated (bypassed). @@ -499,7 +499,7 @@ def neg_YPD_QFR_desact(disjunct): return m.QFR[n] == 0 @disjunct.Constraint() - def neg_YPD_vol_desact(disjunct): + def neg_YPD_vol_deactivation(disjunct): """ Volume deactivation function for defining pyomo model. There is no volume when the reactor is deactivated (bypassed). @@ -594,7 +594,7 @@ def build_no_recycle_equations(disjunct, n): # FR deactivation @disjunct.Constraint(m.I) - def neg_YRD_FR_desact(disjunct, i): + def neg_YRD_FR_deactivation(disjunct, i): """ Deactivation of the recycle flow for each component in the reactor series. @@ -614,7 +614,7 @@ def neg_YRD_FR_desact(disjunct, i): # QFR deactivation @disjunct.Constraint() - def neg_YRD_QFR_desact(disjunct): + def neg_YRD_QFR_deactivation(disjunct): """ Deactivation of the outlet flow rate recycle activation of the reactor. From 1734d3ab62d2aa897f6de64d057482d26820a102 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Sun, 19 May 2024 13:48:03 -0400 Subject: [PATCH 118/124] Black format --- gdplib/cstr/gdp_reactor.py | 206 ++++++++++++++++++++++++++++--------- 1 file changed, 157 insertions(+), 49 deletions(-) diff --git a/gdplib/cstr/gdp_reactor.py b/gdplib/cstr/gdp_reactor.py index 09209c9..3d3700b 100644 --- a/gdplib/cstr/gdp_reactor.py +++ b/gdplib/cstr/gdp_reactor.py @@ -10,7 +10,7 @@ def build_cstrs(NT: int = 5) -> pyo.ConcreteModel(): """ Build the CSTR superstructure model of size NT. NT is the number of reactors in series. - The CSTRs have a single 1st order auto catalytic reaction A -> B and minimizes total reactors series volume. + The CSTRs have a single 1st order auto catalytic reaction A -> B and minimizes total reactors series volume. The optimal solution should yield NT reactors with a recycle before reactor NT. Parameters @@ -32,20 +32,30 @@ def build_cstrs(NT: int = 5) -> pyo.ConcreteModel(): m.N = pyo.RangeSet(1, NT, doc='Set of units in the superstructure') # PARAMETERS - m.k = pyo.Param(initialize=2, doc="Kinetic constant [L/(mol*s)]") # Kinetic constant [L/(mol*s)] - m.order1 = pyo.Param(initialize=1, doc="Partial order of reaction 1") # Partial order of reacton 1 - m.order2 = pyo.Param(initialize=1, doc="Partial order of reaction 2") # Partial order of reaction 2 - m.QF0 = pyo.Param(initialize=1, doc="Inlet volumetric flow [L/s]") # Inlet volumetric flow [L/s] + m.k = pyo.Param( + initialize=2, doc="Kinetic constant [L/(mol*s)]" + ) # Kinetic constant [L/(mol*s)] + m.order1 = pyo.Param( + initialize=1, doc="Partial order of reaction 1" + ) # Partial order of reacton 1 + m.order2 = pyo.Param( + initialize=1, doc="Partial order of reaction 2" + ) # Partial order of reaction 2 + m.QF0 = pyo.Param( + initialize=1, doc="Inlet volumetric flow [L/s]" + ) # Inlet volumetric flow [L/s] C0_Def = {'A': 0.99, 'B': 0.01} # Initial concentration of reagents [mol/L] - m.C0 = pyo.Param(m.I, initialize=C0_Def, doc="Initial concentration of reagents [mol/L]") + m.C0 = pyo.Param( + m.I, initialize=C0_Def, doc="Initial concentration of reagents [mol/L]" + ) # Inlet molar flow [mol/s] def F0_Def(m, i): """ - Inlet molar flow [mol/s] for component i. + Inlet molar flow [mol/s] for component i. The function multiplies the initial concentration of component i by the inlet volumetric flow. Parameters @@ -60,7 +70,8 @@ def F0_Def(m, i): Pyomo.Param Inlet molar flow [mol/s] for component i """ - return m.C0[i]*m.QF0 + return m.C0[i] * m.QF0 + m.F0 = pyo.Param(m.I, initialize=F0_Def) # BOOLEAN VARIABLES @@ -78,41 +89,105 @@ def F0_Def(m, i): # Network Variables # Outlet flow rate of the superstructure unit [L/s] - m.Q = pyo.Var(m.N, initialize=0, within=pyo.NonNegativeReals, bounds=(0, 10), doc="Outlet flow rate of the superstructure unit [L/s]") + m.Q = pyo.Var( + m.N, + initialize=0, + within=pyo.NonNegativeReals, + bounds=(0, 10), + doc="Outlet flow rate of the superstructure unit [L/s]", + ) # Outlet flow rate recycle activation of the superstructure unit [L/s] - m.QFR = pyo.Var(m.N, initialize=0, - within=pyo.NonNegativeReals, bounds=(0, 10), doc="Outlet flow rate recycle activation of the superstructure unit [L/s]") + m.QFR = pyo.Var( + m.N, + initialize=0, + within=pyo.NonNegativeReals, + bounds=(0, 10), + doc="Outlet flow rate recycle activation of the superstructure unit [L/s]", + ) # Molar flow [mol/s] - m.F = pyo.Var(m.I, m.N, initialize=0, - within=pyo.NonNegativeReals, bounds=(0, 10), doc="Molar flow [mol/s]") + m.F = pyo.Var( + m.I, + m.N, + initialize=0, + within=pyo.NonNegativeReals, + bounds=(0, 10), + doc="Molar flow [mol/s]", + ) # Molar flow recycle activation [mol/s] - m.FR = pyo.Var(m.I, m.N, initialize=0, - within=pyo.NonNegativeReals, bounds=(0, 10), doc="Molar flow recycle activation [mol/s]") + m.FR = pyo.Var( + m.I, + m.N, + initialize=0, + within=pyo.NonNegativeReals, + bounds=(0, 10), + doc="Molar flow recycle activation [mol/s]", + ) # Reaction rate [mol/(L*s)] - m.rate = pyo.Var(m.I, m.N, initialize=0, within=pyo.Reals, bounds=(-10, 10), doc="Reaction rate [mol/(L*s)]") + m.rate = pyo.Var( + m.I, + m.N, + initialize=0, + within=pyo.Reals, + bounds=(-10, 10), + doc="Reaction rate [mol/(L*s)]", + ) # Reactor volume [L] - m.V = pyo.Var(m.N, initialize=0, within=pyo.NonNegativeReals, bounds=(0, 10), doc="Reactor volume [L]") + m.V = pyo.Var( + m.N, + initialize=0, + within=pyo.NonNegativeReals, + bounds=(0, 10), + doc="Reactor volume [L]", + ) # Volume activation [L] - m.c = pyo.Var(m.N, initialize=0, within=pyo.NonNegativeReals, bounds=(0, 10), doc="Volume activation [L]") + m.c = pyo.Var( + m.N, + initialize=0, + within=pyo.NonNegativeReals, + bounds=(0, 10), + doc="Volume activation [L]", + ) # Splitter Variables # Recycle flow rate [L/s] - m.QR = pyo.Var(initialize=0, within=pyo.NonNegativeReals, bounds=(0, 10), doc="Recycle flow rate [L/s]") + m.QR = pyo.Var( + initialize=0, + within=pyo.NonNegativeReals, + bounds=(0, 10), + doc="Recycle flow rate [L/s]", + ) # Product flow rate [L/s] - m.QP = pyo.Var(initialize=0, within=pyo.NonNegativeReals, bounds=(0, 10), doc="Product flow rate [L/s]") + m.QP = pyo.Var( + initialize=0, + within=pyo.NonNegativeReals, + bounds=(0, 10), + doc="Product flow rate [L/s]", + ) # Recycle molar flow [mol/s] - m.R = pyo.Var(m.I, initialize=0, within=pyo.NonNegativeReals, bounds=(0, 10), doc="Recycle molar flow [mol/s]") + m.R = pyo.Var( + m.I, + initialize=0, + within=pyo.NonNegativeReals, + bounds=(0, 10), + doc="Recycle molar flow [mol/s]", + ) # Product molar flow [mol/s] - m.P = pyo.Var(m.I, initialize=0, within=pyo.NonNegativeReals, bounds=(0, 10), doc="Product molar flow [mol/s]") + m.P = pyo.Var( + m.I, + initialize=0, + within=pyo.NonNegativeReals, + bounds=(0, 10), + doc="Product molar flow [mol/s]", + ) # CONSTRAINTS @@ -139,11 +214,13 @@ def unreact_mole_rule(m, i, n): The constraint for the unreacted feed unit mole balance if n is equal to NT. Otherwise, it returns Pyomo.Constraint.Skip. """ if n == NT: - return m.F0[i] + m.FR[i, n] - m.F[i, n] + m.rate[i, n]*m.V[n] == 0 + return m.F0[i] + m.FR[i, n] - m.F[i, n] + m.rate[i, n] * m.V[n] == 0 else: return pyo.Constraint.Skip - m.unreact_mole = pyo.Constraint(m.I, m.N, rule=unreact_mole_rule, doc="Unreacted feed unit mole balance") + m.unreact_mole = pyo.Constraint( + m.I, m.N, rule=unreact_mole_rule, doc="Unreacted feed unit mole balance" + ) # Unreacted feed unit continuity @@ -169,7 +246,9 @@ def unreact_cont_rule(m, n): else: return pyo.Constraint.Skip - m.unreact_cont = pyo.Constraint(m.N, rule=unreact_cont_rule, doc="Unreacted feed unit continuity") + m.unreact_cont = pyo.Constraint( + m.N, rule=unreact_cont_rule, doc="Unreacted feed unit continuity" + ) # Reactor Balances # Reactor mole balance @@ -194,11 +273,13 @@ def react_mole_rule(m, i, n): The constraint for the reactor mole balance if n is different from NT. Otherwise, it returns Pyomo.Constraint.Skip. """ if n != NT: - return m.F[i, n+1] + m.FR[i, n] - m.F[i, n] + m.rate[i, n]*m.V[n] == 0 + return m.F[i, n + 1] + m.FR[i, n] - m.F[i, n] + m.rate[i, n] * m.V[n] == 0 else: return pyo.Constraint.Skip - m.react_mole = pyo.Constraint(m.I, m.N, rule=react_mole_rule, doc="Reactor mole balance") + m.react_mole = pyo.Constraint( + m.I, m.N, rule=react_mole_rule, doc="Reactor mole balance" + ) # Reactor continuity @@ -220,7 +301,7 @@ def react_cont_rule(m, n): The constraint for the reactor continuity if n is different from NT. Otherwise, it returns Pyomo.Constraint.Skip. """ if n != NT: - return m.Q[n+1] + m.QFR[n] - m.Q[n] == 0 + return m.Q[n + 1] + m.QFR[n] - m.Q[n] == 0 else: return pyo.Constraint.Skip @@ -248,7 +329,9 @@ def split_mole_rule(m, i): """ return m.F[i, 1] - m.P[i] - m.R[i] == 0 - m.split_mole = pyo.Constraint(m.I, rule=split_mole_rule, doc="Splitting point mole balance") + m.split_mole = pyo.Constraint( + m.I, rule=split_mole_rule, doc="Splitting point mole balance" + ) # Splitting point continuity @@ -269,13 +352,15 @@ def split_cont_rule(m): """ return m.Q[1] - m.QP - m.QR == 0 - m.split_cont = pyo.Constraint(rule=split_cont_rule, doc="Splitting point continuity") + m.split_cont = pyo.Constraint( + rule=split_cont_rule, doc="Splitting point continuity" + ) # Splitting point additional constraints def split_add_rule(m, i): """ - Splitting point additional constraints. + Splitting point additional constraints. Molarity constraints over initial and final flows, read as an multiplication avoid the numerical complication. m.P[i]/m.QP = m.F[i,1]/m.Q[1] (molarity balance) @@ -291,9 +376,11 @@ def split_add_rule(m, i): Pyomo.Constraint The constraint for the splitting point additional constraints. """ - return m.P[i]*m.Q[1] - m.F[i, 1]*m.QP == 0 + return m.P[i] * m.Q[1] - m.F[i, 1] * m.QP == 0 - m.split_add = pyo.Constraint(m.I, rule=split_add_rule, doc="Splitting point additional constraints") + m.split_add = pyo.Constraint( + m.I, rule=split_add_rule, doc="Splitting point additional constraints" + ) # Product Specification @@ -312,7 +399,7 @@ def prod_spec_rule(m): Pyomo.Constraint The constraint for the product B specification. """ - return m.QP*0.95 - m.P['B'] == 0 + return m.QP * 0.95 - m.P['B'] == 0 m.prod_spec = pyo.Constraint(rule=prod_spec_rule, doc="Product B Specification") @@ -335,7 +422,7 @@ def vol_cons_rule(m, n): The constraint for the volume constraint if n is different from 1. Otherwise, it returns Pyomo.Constraint.Skip. """ if n != 1: - return m.V[n] - m.V[n-1] == 0 + return m.V[n] - m.V[n - 1] == 0 else: return pyo.Constraint.Skip @@ -378,7 +465,11 @@ def YPD_rate_calc(disjunct): Pyomo.Constraint The constraint for the calculation of the reaction rate of A for each reactor. """ - return m.rate['A', n]*((m.Q[n])**m.order1)*((m.Q[n])**m.order2)+m.k*((m.F['A', n])**m.order1)*((m.F['B', n])**m.order2) == 0 + return ( + m.rate['A', n] * ((m.Q[n]) ** m.order1) * ((m.Q[n]) ** m.order2) + + m.k * ((m.F['A', n]) ** m.order1) * ((m.F['B', n]) ** m.order2) + == 0 + ) # Reaction rate relation @disjunct.Constraint() @@ -387,7 +478,7 @@ def YPD_rate_rel(disjunct): Reaction rate relation for defining pyomo model. Since the chemical reaction goes from A to B, the rate of A is equal to the negative rate of B. - A -> B + A -> B Parameters ---------- @@ -508,7 +599,7 @@ def neg_YPD_vol_deactivation(disjunct): ---------- disjunct : Pyomo.Disjunct Pyomo Disjunct block to include the constraints for the deactivation of the reactor (bypass the reactor). - + Returns ------- Pyomo.Constraint @@ -604,7 +695,7 @@ def neg_YRD_FR_deactivation(disjunct, i): Pyomo Disjunct block to include the constraints for the deactivation of the reactor (recycle flow absence). i : float Index of the component in the reactor series. - + Returns ------- Pyomo.Constraint @@ -631,8 +722,12 @@ def neg_YRD_QFR_deactivation(disjunct): return m.QFR[n] == 0 # Create disjunction blocks - m.YR_is_recycle = Disjunct(m.N, rule=build_recycle_equations, doc="Recycle flow in reactor n") - m.YR_is_not_recycle = Disjunct(m.N, rule=build_no_recycle_equations, doc="No recycle flow in reactor n") + m.YR_is_recycle = Disjunct( + m.N, rule=build_recycle_equations, doc="Recycle flow in reactor n" + ) + m.YR_is_not_recycle = Disjunct( + m.N, rule=build_no_recycle_equations, doc="No recycle flow in reactor n" + ) m.YP_is_cstr = Disjunct(m.N, rule=build_cstr_equations, doc="CSTR reactor n") m.YP_is_bypass = Disjunct(m.N, rule=build_bypass_equations, doc="Bypass reactor n") @@ -704,7 +799,9 @@ def cstr_if_recycle_rule(m, n): """ return m.YR[n].implies(m.YP[n]) - m.cstr_if_recycle = pyo.LogicalConstraint(m.N, rule=cstr_if_recycle_rule, doc="Unit must be a CSTR to include a recycle") + m.cstr_if_recycle = pyo.LogicalConstraint( + m.N, rule=cstr_if_recycle_rule, doc="Unit must be a CSTR to include a recycle" + ) # There is only one unreacted feed @@ -724,7 +821,9 @@ def one_unreacted_feed_rule(m): """ return pyo.exactly(1, m.YF) - m.one_unreacted_feed = pyo.LogicalConstraint(rule=one_unreacted_feed_rule, doc="There is only one unreacted feed") + m.one_unreacted_feed = pyo.LogicalConstraint( + rule=one_unreacted_feed_rule, doc="There is only one unreacted feed" + ) # There is only one recycle stream @@ -744,14 +843,16 @@ def one_recycle_rule(m): """ return pyo.exactly(1, m.YR) - m.one_recycle = pyo.LogicalConstraint(rule=one_recycle_rule, doc="There is only one recycle stream") + m.one_recycle = pyo.LogicalConstraint( + rule=one_recycle_rule, doc="There is only one recycle stream" + ) # Unit operation in n constraint def unit_in_n_rule(m, n): """ Build the logical constraint for the unit operation in n. - If n is equal to 1, the unit operation is a CSTR. + If n is equal to 1, the unit operation is a CSTR. Otherwise, the unit operation for reactor n except reactor 1 is equivalent to the logical OR of the negation of the unreacted feed of the previous reactors and the unreacted feed of reactor n. Reactor n is active if either the previous reactors (1 through n-1) have no unreacted feed or reactor n has unreacted feed. @@ -770,7 +871,9 @@ def unit_in_n_rule(m, n): if n == 1: return m.YP[n].equivalent_to(True) else: - return m.YP[n].equivalent_to(pyo.lor(pyo.land(~m.YF[n2] for n2 in range(1, n)), m.YF[n])) + return m.YP[n].equivalent_to( + pyo.lor(pyo.land(~m.YF[n2] for n2 in range(1, n)), m.YF[n]) + ) m.unit_in_n = pyo.LogicalConstraint(m.N, rule=unit_in_n_rule) @@ -792,13 +895,18 @@ def obj_rule(m): """ return sum(m.c[n] for n in m.N) - m.obj = pyo.Objective(rule=obj_rule, sense=pyo.minimize, doc="minimum total reactor volume") + m.obj = pyo.Objective( + rule=obj_rule, sense=pyo.minimize, doc="minimum total reactor volume" + ) return m + if __name__ == "__main__": m = build_cstrs() pyo.TransformationFactory('core.logical_to_linear').apply_to(m) pyo.TransformationFactory('gdp.bigm').apply_to(m) - pyo.SolverFactory('gams').solve(m, solver='baron', tee=True, add_options=['option optcr=1e-6;']) - display(m) \ No newline at end of file + pyo.SolverFactory('gams').solve( + m, solver='baron', tee=True, add_options=['option optcr=1e-6;'] + ) + display(m) From 87250364e7c5ad9f03a774f1a3beef2b107f2363 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Sun, 19 May 2024 15:13:51 -0400 Subject: [PATCH 119/124] black formatted again --- gdplib/cstr/gdp_reactor.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/gdplib/cstr/gdp_reactor.py b/gdplib/cstr/gdp_reactor.py index 3d3700b..c010a7c 100644 --- a/gdplib/cstr/gdp_reactor.py +++ b/gdplib/cstr/gdp_reactor.py @@ -25,11 +25,11 @@ def build_cstrs(NT: int = 5) -> pyo.ConcreteModel(): """ # PYOMO MODEL - m = pyo.ConcreteModel(name='gdp_reactors') + m = pyo.ConcreteModel(name="gdp_reactors") # SETS - m.I = pyo.Set(initialize=['A', 'B'], doc='Set of components') - m.N = pyo.RangeSet(1, NT, doc='Set of units in the superstructure') + m.I = pyo.Set(initialize=["A", "B"], doc="Set of components") + m.N = pyo.RangeSet(1, NT, doc="Set of units in the superstructure") # PARAMETERS m.k = pyo.Param( @@ -44,7 +44,7 @@ def build_cstrs(NT: int = 5) -> pyo.ConcreteModel(): m.QF0 = pyo.Param( initialize=1, doc="Inlet volumetric flow [L/s]" ) # Inlet volumetric flow [L/s] - C0_Def = {'A': 0.99, 'B': 0.01} + C0_Def = {"A": 0.99, "B": 0.01} # Initial concentration of reagents [mol/L] m.C0 = pyo.Param( @@ -399,7 +399,7 @@ def prod_spec_rule(m): Pyomo.Constraint The constraint for the product B specification. """ - return m.QP * 0.95 - m.P['B'] == 0 + return m.QP * 0.95 - m.P["B"] == 0 m.prod_spec = pyo.Constraint(rule=prod_spec_rule, doc="Product B Specification") @@ -466,8 +466,8 @@ def YPD_rate_calc(disjunct): The constraint for the calculation of the reaction rate of A for each reactor. """ return ( - m.rate['A', n] * ((m.Q[n]) ** m.order1) * ((m.Q[n]) ** m.order2) - + m.k * ((m.F['A', n]) ** m.order1) * ((m.F['B', n]) ** m.order2) + m.rate["A", n] * ((m.Q[n]) ** m.order1) * ((m.Q[n]) ** m.order2) + + m.k * ((m.F["A", n]) ** m.order1) * ((m.F["B", n]) ** m.order2) == 0 ) @@ -490,7 +490,7 @@ def YPD_rate_rel(disjunct): Pyomo.Constraint _description_ """ - return m.rate['B', n] + m.rate['A', n] == 0 + return m.rate["B", n] + m.rate["A", n] == 0 # Volume activation @disjunct.Constraint() @@ -904,9 +904,9 @@ def obj_rule(m): if __name__ == "__main__": m = build_cstrs() - pyo.TransformationFactory('core.logical_to_linear').apply_to(m) - pyo.TransformationFactory('gdp.bigm').apply_to(m) - pyo.SolverFactory('gams').solve( - m, solver='baron', tee=True, add_options=['option optcr=1e-6;'] + pyo.TransformationFactory("core.logical_to_linear").apply_to(m) + pyo.TransformationFactory("gdp.bigm").apply_to(m) + pyo.SolverFactory("gams").solve( + m, solver="baron", tee=True, add_options=["option optcr=1e-6;"] ) display(m) From 756b4eff1ecfa63cf7dd7f21e1504698cb3728b3 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Sun, 19 May 2024 17:12:11 -0400 Subject: [PATCH 120/124] Add the documentation build_cstr_equation. --- gdplib/cstr/gdp_reactor.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gdplib/cstr/gdp_reactor.py b/gdplib/cstr/gdp_reactor.py index c010a7c..27fb387 100644 --- a/gdplib/cstr/gdp_reactor.py +++ b/gdplib/cstr/gdp_reactor.py @@ -431,7 +431,8 @@ def vol_cons_rule(m, n): # YD Disjunction block equation definition def build_cstr_equations(disjunct, n): - """_summary_ + """ + Build the constraints for the activation of the CSTR reactor. Parameters ---------- From 995790d6db4c5b3d1012b270cfc264e89e80eb6d Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Sun, 19 May 2024 17:14:19 -0400 Subject: [PATCH 121/124] Added the reaction rate relation documentation. --- gdplib/cstr/gdp_reactor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gdplib/cstr/gdp_reactor.py b/gdplib/cstr/gdp_reactor.py index 27fb387..036fa4f 100644 --- a/gdplib/cstr/gdp_reactor.py +++ b/gdplib/cstr/gdp_reactor.py @@ -489,7 +489,7 @@ def YPD_rate_rel(disjunct): Returns ------- Pyomo.Constraint - _description_ + Reaction rate relation for defining pyomo model. """ return m.rate["B", n] + m.rate["A", n] == 0 From 33685ebfed947b552c205840a8bdbbeb98a021a5 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Sun, 19 May 2024 17:33:59 -0400 Subject: [PATCH 122/124] Added the header of the code. --- gdplib/cstr/gdp_reactor.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/gdplib/cstr/gdp_reactor.py b/gdplib/cstr/gdp_reactor.py index 036fa4f..7473f0d 100644 --- a/gdplib/cstr/gdp_reactor.py +++ b/gdplib/cstr/gdp_reactor.py @@ -4,7 +4,16 @@ from pyomo.core.base.misc import display from pyomo.gdp import Disjunct, Disjunction from pyomo.opt.base.solvers import SolverFactory +""" +gdp_reactor.py +This script builds a Pyomo GDP model for a CSTR superstructure with a single 1st order auto catalytic reaction A -> B. +The model minimizes the total reactors series volume. +The optimal solution should yield NT reactors with a recycle before reactor NT. + +Reference: + > Linan, D. A., Bernal, D. E., Gomez, J. M., & Ricardez-Sandoval, L. A. (2021). Optimal synthesis and design of catalytic distillation columns: A rate-based modeling approach. Chemical Engineering Science, 231, 116294. https://doi.org/10.1016/j.ces.2020.116294 +""" def build_cstrs(NT: int = 5) -> pyo.ConcreteModel(): """ From eecbcd12afbca382bbc52579f3998d2aab86eeb7 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Sun, 19 May 2024 17:35:01 -0400 Subject: [PATCH 123/124] black format --- gdplib/cstr/gdp_reactor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gdplib/cstr/gdp_reactor.py b/gdplib/cstr/gdp_reactor.py index 7473f0d..09cd536 100644 --- a/gdplib/cstr/gdp_reactor.py +++ b/gdplib/cstr/gdp_reactor.py @@ -4,6 +4,7 @@ from pyomo.core.base.misc import display from pyomo.gdp import Disjunct, Disjunction from pyomo.opt.base.solvers import SolverFactory + """ gdp_reactor.py @@ -15,6 +16,7 @@ > Linan, D. A., Bernal, D. E., Gomez, J. M., & Ricardez-Sandoval, L. A. (2021). Optimal synthesis and design of catalytic distillation columns: A rate-based modeling approach. Chemical Engineering Science, 231, 116294. https://doi.org/10.1016/j.ces.2020.116294 """ + def build_cstrs(NT: int = 5) -> pyo.ConcreteModel(): """ Build the CSTR superstructure model of size NT. From 0ade99df270015be2dc2733dc713e6745fa15133 Mon Sep 17 00:00:00 2001 From: Albert Lee Date: Sun, 19 May 2024 20:12:56 -0400 Subject: [PATCH 124/124] relocate the header. --- gdplib/cstr/gdp_reactor.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/gdplib/cstr/gdp_reactor.py b/gdplib/cstr/gdp_reactor.py index 09cd536..b357384 100644 --- a/gdplib/cstr/gdp_reactor.py +++ b/gdplib/cstr/gdp_reactor.py @@ -1,10 +1,3 @@ -import os -import sys -import pyomo.environ as pyo -from pyomo.core.base.misc import display -from pyomo.gdp import Disjunct, Disjunction -from pyomo.opt.base.solvers import SolverFactory - """ gdp_reactor.py @@ -16,6 +9,13 @@ > Linan, D. A., Bernal, D. E., Gomez, J. M., & Ricardez-Sandoval, L. A. (2021). Optimal synthesis and design of catalytic distillation columns: A rate-based modeling approach. Chemical Engineering Science, 231, 116294. https://doi.org/10.1016/j.ces.2020.116294 """ +import os +import sys +import pyomo.environ as pyo +from pyomo.core.base.misc import display +from pyomo.gdp import Disjunct, Disjunction +from pyomo.opt.base.solvers import SolverFactory + def build_cstrs(NT: int = 5) -> pyo.ConcreteModel(): """