From 47aa81823137460e7c158e60cfb9fd9e24072852 Mon Sep 17 00:00:00 2001 From: Cameron Wade Date: Fri, 18 Aug 2023 18:17:37 -0300 Subject: [PATCH 1/4] Update Solver Settings Custom solver settings for the cbc and cplex solver are added. --- temoa_model/temoa_run.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/temoa_model/temoa_run.py b/temoa_model/temoa_run.py index f6291a92..a81aea82 100755 --- a/temoa_model/temoa_run.py +++ b/temoa_model/temoa_run.py @@ -378,7 +378,23 @@ def solve_temoa_instance (self): if self.options.neos: self.result = self.optimizer.solve(self.instance, opt=self.options.solver) else: - self.result = self.optimizer.solve( self.instance, suffixes=['dual'],# 'rc', 'slack'], + if self.options.solver == 'cbc': + # Solver options. Reference: https://genxproject.github.io/GenX/dev/solver_configuration/ + self.optimizer.options["dualTolerance"] = 1e-6 + self.optimizer.options["primalTolerance"] = 1e-6 + self.optimizer.options["zeroTolerance"] = 1e-12 + self.optimizer.options["crossover"] = 'off' + + elif self.options.solver == 'cplex': + # Note: these parameter values are taken to be the same as those in PyPSA (see: https://pypsa-eur.readthedocs.io/en/latest/configuration.html) + self.optimizer.options["lpmethod"] = 4 # barrier + self.optimizer.options["solutiontype"] = 2 # non basic solution, ie no crossover + self.optimizer.options["barrier convergetol"] = 1.e-5 + self.optimizer.options["feasopt tolerance"] = 1.e-6 + + # Note: still need to add gurobi parameters. + + self.result = self.optimizer.solve( self.instance, suffixes=['dual'],# 'rc', 'slack'], keepfiles=self.options.keepPyomoLP, symbolic_solver_labels=self.options.keepPyomoLP ) yield '\t\t\t\t\t\t[%8.2f]\n' % duration() From 6b97219f0ea7dceb2fd09cef5bb00f28c86dde6d Mon Sep 17 00:00:00 2001 From: Cameron Wade Date: Fri, 18 Aug 2023 20:33:37 -0300 Subject: [PATCH 2/4] Optimize DemandConstraint and DemandActivityConstraint for build time This commit optimizes the DemandConstraint and DemandActivityConstraints (both their initialization and rules) for better performance. Previously these functions performed expensive table lookups. In tests, the build time for these functions was reduced from 307 sec to 8 sec. --- temoa_model/temoa_initialize.py | 12 ++---------- temoa_model/temoa_rules.py | 8 ++------ 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/temoa_model/temoa_initialize.py b/temoa_model/temoa_initialize.py index ebb9c52e..750d8e05 100644 --- a/temoa_model/temoa_initialize.py +++ b/temoa_model/temoa_initialize.py @@ -1046,21 +1046,13 @@ def DemandActivityConstraintIndices ( M ): yield (r,p,s,d,t,v,dem,s0,d0) def DemandConstraintIndices ( M ): - used_dems = set((r,dem) for r, p, dem in M.Demand.sparse_iterkeys()) - DSD_keys = M.DemandSpecificDistribution.sparse_keys() - dem_slices = { (r,dem) : set( - (s, d) - for s in M.time_season - for d in M.time_of_day - if (r, s, d, dem) in DSD_keys ) - for (r,dem) in used_dems - } indices = set( (r, p, s, d, dem) for r, p, dem in M.Demand.sparse_iterkeys() - for s, d in dem_slices[ (r,dem) ] + for s in M.time_season + for d in M.time_of_day ) return indices diff --git a/temoa_model/temoa_rules.py b/temoa_model/temoa_rules.py index 5682f4c4..6e99e485 100644 --- a/temoa_model/temoa_rules.py +++ b/temoa_model/temoa_rules.py @@ -425,8 +425,6 @@ def Demand_Constraint(M, r, p, s, d, dem): could satisfy both an end-use and internal system demand, then the output from :math:`\textbf{FO}` and :math:`\textbf{FOA}` would be double counted. """ - if (r,s,d,dem) not in M.DemandSpecificDistribution.sparse_keys(): - return Constraint.Skip supply = sum( M.V_FlowOut[r, p, s, d, S_i, S_t, S_v, dem] @@ -443,6 +441,7 @@ def Demand_Constraint(M, r, p, s, d, dem): DemandConstraintErrorCheck(supply + supply_annual, r, p, s, d, dem) expr = supply + supply_annual == M.Demand[r, p, dem] * M.DemandSpecificDistribution[r, s, d, dem] + return expr def DemandActivity_Constraint(M, r, p, s, d, t, v, dem, s_0, d_0): @@ -474,9 +473,6 @@ def DemandActivity_Constraint(M, r, p, s, d, t, v, dem, s_0, d_0): variations, and therefore the equation above only includes :math:`\textbf{FO}` and not :math:`\textbf{FOA}` """ - if (r,s,d,dem) not in M.DemandSpecificDistribution.sparse_keys(): - return Constraint.Skip - DSD = M.DemandSpecificDistribution # lazy programmer act_a = sum( M.V_FlowOut[r, p, s_0, d_0, S_i, t, v, dem] @@ -487,7 +483,7 @@ def DemandActivity_Constraint(M, r, p, s, d, t, v, dem, s_0, d_0): for S_i in M.ProcessInputsByOutput[r, p, t, v, dem] ) - expr = act_a * DSD[r, s, d, dem] == act_b * DSD[r, s_0, d_0, dem] + expr = act_a * M.DemandSpecificDistribution[r, s, d, dem] == act_b * M.DemandSpecificDistribution[r, s_0, d_0, dem] return expr From 8674c0e557c7f30ac94aab4af880b9a04d049963 Mon Sep 17 00:00:00 2001 From: Cameron Wade Date: Fri, 18 Aug 2023 20:43:15 -0300 Subject: [PATCH 3/4] Make DemandSpecificDristribution default value 0 --- temoa_model/temoa_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/temoa_model/temoa_model.py b/temoa_model/temoa_model.py index 730c36be..1649c7f8 100755 --- a/temoa_model/temoa_model.py +++ b/temoa_model/temoa_model.py @@ -121,7 +121,7 @@ def temoa_create_model(name="Temoa"): # Define demand- and resource-related parameters M.DemandDefaultDistribution = Param(M.time_season, M.time_of_day, mutable=True) M.DemandSpecificDistribution = Param( - M.regions, M.time_season, M.time_of_day, M.commodity_demand, mutable=True + M.regions, M.time_season, M.time_of_day, M.commodity_demand, mutable=True, default=0 ) M.Demand = Param(M.regions, M.time_optimize, M.commodity_demand) From c588312ffef9d50472a38a4f3feec91762fd0278 Mon Sep 17 00:00:00 2001 From: Cameron Wade Date: Fri, 18 Aug 2023 20:52:58 -0300 Subject: [PATCH 4/4] Update StorageEnergyUpperBound_Constraint for faster compile The constraint is updated to avoid computing lengthy sums in each iteration. This results in a ~20x reduction in time taken to build the constraint. --- temoa_model/temoa_model.py | 4 ++++ temoa_model/temoa_rules.py | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/temoa_model/temoa_model.py b/temoa_model/temoa_model.py index 1649c7f8..15b6a30b 100755 --- a/temoa_model/temoa_model.py +++ b/temoa_model/temoa_model.py @@ -379,6 +379,10 @@ def temoa_create_model(name="Temoa"): M.StorageConstraints_rpsdtv, rule=StorageEnergy_Constraint ) + # We make use of this following set in some of the storage constraints. + # Pre-computing it is considerably faster. + M.SegFracPerSeason = Param(M.time_season, initialize=SegFracPerSeason_rule) + M.StorageEnergyUpperBoundConstraint = Constraint( M.StorageConstraints_rpsdtv, rule=StorageEnergyUpperBound_Constraint ) diff --git a/temoa_model/temoa_rules.py b/temoa_model/temoa_rules.py index 6e99e485..17a973bf 100644 --- a/temoa_model/temoa_rules.py +++ b/temoa_model/temoa_rules.py @@ -985,7 +985,7 @@ def StorageEnergyUpperBound_Constraint(M, r, p, s, d, t, v): M.V_Capacity[r, t, v] * M.CapacityToActivity[r, t] * (M.StorageDuration[r, t] / 8760) - * sum(M.SegFrac[s,S_d] for S_d in M.time_of_day) * 365 + * M.SegFracPerSeason[s] * 365 * value(M.ProcessLifeFrac[r, p, t, v]) ) expr = M.V_StorageLevel[r, p, s, d, t, v] <= energy_capacity @@ -2596,7 +2596,8 @@ def ParamLoanAnnualize_rule(M, r, t, v): return annualized_rate - +def SegFracPerSeason_rule(M, s): + return sum(M.SegFrac[s, S_d] for S_d in M.time_of_day) def LinkedEmissionsTech_Constraint(M, r, p, s, d, t, v, e): r"""